Merge pull request #17 from dx-tan/feature/add_health_check

Refactor + Add usage report API
This commit is contained in:
Nguyen Viet Anh 2023-12-15 15:28:37 +07:00 committed by GitHub Enterprise
commit a97559b65c
31 changed files with 231 additions and 169 deletions

@ -1 +1 @@
Subproject commit 97218a499a6375e943c1c59c592e1b1780d43477 Subproject commit bdba044bb2eacac7c7cfe0e0f321196d03b681f6

View File

@ -175,8 +175,8 @@ REST_FRAMEWORK = {
} }
SPECTACULAR_SETTINGS = { SPECTACULAR_SETTINGS = {
'TITLE': 'SDS C2open', 'TITLE': 'OCR Engine for Invoices',
'DESCRIPTION': 'AI powered by SamsungSDS VietNam', 'DESCRIPTION': 'AI Powered by Samsung SDS Vietnam',
'VERSION': '1.0.0', 'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': True, 'SERVE_INCLUDE_SCHEMA': True,
# OTHER SETTINGS # OTHER SETTINGS
@ -188,6 +188,13 @@ SPECTACULAR_SETTINGS = {
# Custom Spectacular Settings # Custom Spectacular Settings
"EXCLUDE_PATH": [reverse_lazy("schema")], "EXCLUDE_PATH": [reverse_lazy("schema")],
"EXCLUDE_RELATIVE_PATH": ["/rsa", '/gen-token', '/app/'], "EXCLUDE_RELATIVE_PATH": ["/rsa", '/gen-token', '/app/'],
"TAGS": [
"Login",
"OCR",
"Data",
"System",
],
"TAGS_SORTER": "alpha"
} }
FILE_UPLOAD_HANDLERS = [ FILE_UPLOAD_HANDLERS = [

View File

@ -17,8 +17,9 @@ from ..constant.common import EntityStatus, TEMPLATE_ID
from ..exception.exceptions import InvalidException from ..exception.exceptions import InvalidException
from ..request.UpdateTemplateRequest import UpdateTemplateRequest from ..request.UpdateTemplateRequest import UpdateTemplateRequest
from ..response.TemplateResponse import TemplateResponse from ..response.TemplateResponse import TemplateResponse
from ..utils import FileUtils, ProcessUtil from ..utils import file as FileUtils
from ..utils.ProcessUtil import UserData from ..utils import process as ProcessUtil
from ..utils.process import UserData
from drf_spectacular.utils import extend_schema from drf_spectacular.utils import extend_schema

View File

@ -15,13 +15,13 @@ from ..exception.exceptions import InvalidException, NotFoundException, LockedEn
from ..models import UserProfile, PricingPlan, Subscription from ..models import UserProfile, PricingPlan, Subscription
from ..request.UpsertUserRequest import UpsertUserRequest from ..request.UpsertUserRequest import UpsertUserRequest
from ..response.SubscriptionResponse import SubscriptionResponse from ..response.SubscriptionResponse import SubscriptionResponse
from ..utils import ProcessUtil, DateUtil from ..utils.health import get_health_report
from ..utils.CryptoUtils import sds_authenticator, admin_sds_authenticator, SdsAuthentication from ..utils import date as DateUtil
from ..utils.crypto import sds_authenticator, admin_sds_authenticator, SdsAuthentication
import datetime import datetime
from ..request.LoginRequest import LoginRequest from ..request.LoginRequest import LoginRequest
from ..request.HealcheckSerializer import HealthCheckSerializer from ..utils.date import default_zone
from ..utils.DateUtil import default_zone
from fwd import settings from fwd import settings
class CtelUserViewSet(viewsets.ViewSet): class CtelUserViewSet(viewsets.ViewSet):
@ -41,7 +41,7 @@ class CtelUserViewSet(viewsets.ViewSet):
}, },
'required': {'username', 'password'} 'required': {'username', 'password'}
} }
}, responses=None, tags=['ocr']) }, responses=None, tags=['Login'])
@action(detail=False, url_path="login", methods=["POST"]) @action(detail=False, url_path="login", methods=["POST"])
def login(self, request): def login(self, request):
serializer = LoginRequest(data=request.data) serializer = LoginRequest(data=request.data)
@ -89,49 +89,55 @@ class CtelUserViewSet(viewsets.ViewSet):
'token': sds_authenticator.generate_token(user_id=settings.FI_USER_NAME, internal_id=user.id, status=EntityStatus.ACTIVE.value, sub_id=sub.id) 'token': sds_authenticator.generate_token(user_id=settings.FI_USER_NAME, internal_id=user.id, status=EntityStatus.ACTIVE.value, sub_id=sub.id)
}) })
@extend_schema(request=None, responses=None, tags=['users'])
@action(detail=False, url_path="users/info", methods=["GET"])
@throw_on_failure(InvalidException(excArgs='data'))
def get_be_user(self, request):
user_data = ProcessUtil.get_user(request)
return Response(status=status.HTTP_200_OK, data={ # TODO (vietanhdev): Make this optional. Comment out first
'userId': user_data.user.sync_id, # NOTE: Don't remove this code
'message': 'User is valid' # @extend_schema(request=None, responses=None, tags=['users'])
}) # @action(detail=False, url_path="users/info", methods=["GET"])
# @throw_on_failure(InvalidException(excArgs='data'))
# def get_be_user(self, request):
# user_data = ProcessUtil.get_user(request)
@extend_schema(request=None, responses=None, tags=['users'], methods=['GET'], parameters=[ # return Response(status=status.HTTP_200_OK, data={
OpenApiParameter("user", OpenApiTypes.STR, OpenApiParameter.HEADER, description="CMC/SDS encrypted token"), # 'userId': user_data.user.sync_id,
OpenApiParameter("subscription_id", OpenApiTypes.STR, OpenApiParameter.QUERY, description="Subscription id"), # 'message': 'User is valid'
# })
])
@extend_schema(request=UpsertUserRequest, responses=None, tags=['users'], methods=['POST'], parameters=[ # TODO (vietanhdev): Make this optional. Comment out first
OpenApiParameter("user", OpenApiTypes.STR, OpenApiParameter.HEADER, description="CMC/SDS encrypted token"), # NOTE: Don't remove this code
], examples=[ # @extend_schema(request=None, responses=None, tags=['users'], methods=['GET'], parameters=[
OpenApiExample( # OpenApiParameter("user", OpenApiTypes.STR, OpenApiParameter.HEADER, description="CMC/SDS encrypted token"),
'Example', # OpenApiParameter("subscription_id", OpenApiTypes.STR, OpenApiParameter.QUERY, description="Subscription id"),
summary='Request Sample',
description='Status : 0 ( active ) / 1 (inactive). Default is 0. <br>' # ])
'Datetime Format: <b>dd/mm/YYYY HH:MM:SS</b> <br>' # @extend_schema(request=UpsertUserRequest, responses=None, tags=['users'], methods=['POST'], parameters=[
'Plan code ( TRIAL / BASIC / ADVANCED ) ( TRIAL apply only one-time per user account )', # OpenApiParameter("user", OpenApiTypes.STR, OpenApiParameter.HEADER, description="CMC/SDS encrypted token"),
value={ # ], examples=[
'plan_code': 'A03', # OpenApiExample(
'plan_start_at': '01/04/2023 00:00:00', # 'Example',
'status': 1, # summary='Request Sample',
'email': 'abc@mamil.vn', # description='Status : 0 ( active ) / 1 (inactive). Default is 0. <br>'
'name': 'Pham Van A' # 'Datetime Format: <b>dd/mm/YYYY HH:MM:SS</b> <br>'
}, # 'Plan code ( TRIAL / BASIC / ADVANCED ) ( TRIAL apply only one-time per user account )',
request_only=True, # value={
response_only=False # 'plan_code': 'A03',
), # 'plan_start_at': '01/04/2023 00:00:00',
]) # 'status': 1,
@action(detail=False, url_path="users", methods=["POST", "GET"]) # 'email': 'abc@mamil.vn',
@throw_on_failure(InvalidException(excArgs='data')) # 'name': 'Pham Van A'
def user_view(self, request): # },
if request.method == "POST": # request_only=True,
return self.upsert_user(request) # response_only=False
else: # ),
return self.get_user(request) # ])
# @action(detail=False, url_path="users", methods=["POST", "GET"])
# @throw_on_failure(InvalidException(excArgs='data'))
# def user_view(self, request):
# if request.method == "POST":
# return self.upsert_user(request)
# else:
# return self.get_user(request)
def upsert_user(self, request): def upsert_user(self, request):
if not hasattr(request, 'user_data'): if not hasattr(request, 'user_data'):
@ -241,56 +247,48 @@ class CtelUserViewSet(viewsets.ViewSet):
'token': gen_x.generate_token(user.sync_id, user.id, user.status, sub['id']), 'token': gen_x.generate_token(user.sync_id, user.id, user.status, sub['id']),
}) })
# @extend_schema(request={ # TODO (vietanhdev): Make this optional. Comment out first
# 'application/json': { # NOTE: Don't remove this code
# 'type': 'object', # @action(detail=False, url_path="users/gen-token", methods=["POST"])
# 'properties': { # @throw_on_failure(InvalidException(excArgs='data'))
# 'userId': { # def gen_cmc_token(self, request):
# 'type': 'string', # data = request.data
# 'example': "C2H5OH"
# }, # if "expiredAt" not in request.data:
# 'expiredAt': { # raise InvalidException(excArgs='expiredAt')
# 'type': 'string', # uid = ProcessUtil.get_random_string(10) if "userId" not in data or data['userId'] is None else data['userId']
# 'example': "21/12/2023 00:00:00" # m_text = {
# 'userId': uid,
# "expiredAt": data['expiredAt']
# } # }
# }, # import os
# 'required': {'collection_id', 'source', 'files'} # from ..utils.crypto import ctel_cryptor
# import json
# iv = os.urandom(16).hex()
# e_text = ctel_cryptor.encrypt_ctel(json.dumps(m_text), iv)
# e_data = {
# 'content': e_text,
# 'iv': iv
# } # }
# return Response(status=status.HTTP_200_OK, data={
# 'token': base64.b64encode(json.dumps(e_data).encode())
# })
# }, responses=None, tags=['users'], @extend_schema(responses=None, tags=['System'])
# description="API mô phỏng token CMC truyền sang SDS. Dùng để truyền vào header user")
@action(detail=False, url_path="users/gen-token", methods=["POST"])
@throw_on_failure(InvalidException(excArgs='data'))
def gen_cmc_token(self, request):
data = request.data
if "expiredAt" not in request.data:
raise InvalidException(excArgs='expiredAt')
uid = ProcessUtil.get_random_string(10) if "userId" not in data or data['userId'] is None else data['userId']
m_text = {
'userId': uid,
"expiredAt": data['expiredAt']
}
import os
from ..utils.CryptoUtils import ctel_cryptor
import json
iv = os.urandom(16).hex()
e_text = ctel_cryptor.encrypt_ctel(json.dumps(m_text), iv)
e_data = {
'content': e_text,
'iv': iv
}
return Response(status=status.HTTP_200_OK, data={
'token': base64.b64encode(json.dumps(e_data).encode())
})
@action(detail=False, url_path="healthcheck", methods=["GET"], authentication_classes=[], permission_classes=[]) @action(detail=False, url_path="healthcheck", methods=["GET"], authentication_classes=[], permission_classes=[])
def health_check(self, request): def health_check(self, request):
# Perform any checks to determine the health status of the application response = {
# TODO: check database connectivity, S3 service availability, etc. "status": "OK",
serializer = HealthCheckSerializer(data={ }
'status': "OK", return JsonResponse(status=status.HTTP_200_OK, data=response)
'message': 'Application is running smoothly.'
}) @extend_schema(responses=None, tags=['System'])
serializer.is_valid(raise_exception=True) @action(detail=False, url_path="system_usage", methods=["GET"])
return JsonResponse(status=status.HTTP_200_OK, data=serializer.data) def usage(self, request):
data = get_health_report()
response = {
"status": "OK",
"data": data,
}
return JsonResponse(status=status.HTTP_200_OK, data=response)

View File

@ -20,7 +20,8 @@ from ..exception.exceptions import RequiredFieldException, InvalidException, Not
PermissionDeniedException, LockedEntityException, FileContentInvalidException, ServiceTimeoutException PermissionDeniedException, LockedEntityException, FileContentInvalidException, ServiceTimeoutException
from ..models import SubscriptionRequest, SubscriptionRequestFile, OcrTemplate from ..models import SubscriptionRequest, SubscriptionRequestFile, OcrTemplate
from ..response.ReportSerializer import ReportSerializer from ..response.ReportSerializer import ReportSerializer
from ..utils import FileUtils, ProcessUtil from ..utils import file as FileUtils
from ..utils import process as ProcessUtil
class CtelViewSet(viewsets.ViewSet): class CtelViewSet(viewsets.ViewSet):
lookup_field = "username" lookup_field = "username"
@ -40,14 +41,11 @@ class CtelViewSet(viewsets.ViewSet):
}, },
'required': {'file', 'processType'} 'required': {'file', 'processType'}
} }
}, responses=None, tags=['ocr']) }, responses=None, tags=['OCR'])
@action(detail=False, url_path="image/process", methods=["POST"]) @action(detail=False, url_path="image/process", methods=["POST"])
# @transaction.atomic # @transaction.atomic
def process(self, request): def process(self, request):
s_time = time.time() s_time = time.time()
# print(30*"=")
# print(f"[DEBUG]: request: {request}")
# print(30*"=")
user_info = ProcessUtil.get_user(request) user_info = ProcessUtil.get_user(request)
user = user_info.user user = user_info.user
sub = user_info.current_sub sub = user_info.current_sub
@ -120,7 +118,7 @@ class CtelViewSet(viewsets.ViewSet):
}, },
'required': {'imei_files'} 'required': {'imei_files'}
} }
}, responses=None, tags=['ocr']) }, responses=None, tags=['OCR'])
@action(detail=False, url_path="images/process", methods=["POST"]) @action(detail=False, url_path="images/process", methods=["POST"])
def processes(self, request): def processes(self, request):
user_info = ProcessUtil.get_user(request) user_info = ProcessUtil.get_user(request)
@ -191,7 +189,7 @@ class CtelViewSet(viewsets.ViewSet):
}, },
'required': {'imei_files'} 'required': {'imei_files'}
} }
}, responses=None, tags=['ocr']) }, responses=None, tags=['OCR'])
@action(detail=False, url_path="images/process_sync", methods=["POST"]) @action(detail=False, url_path="images/process_sync", methods=["POST"])
def processes_sync(self, request): def processes_sync(self, request):
user_info = ProcessUtil.get_user(request) user_info = ProcessUtil.get_user(request)
@ -306,7 +304,7 @@ class CtelViewSet(viewsets.ViewSet):
}, },
'required': ['request_id', 'retailername', 'sold_to_party', 'purchase_date', 'imei_number'] 'required': ['request_id', 'retailername', 'sold_to_party', 'purchase_date', 'imei_number']
} }
}, responses=None, tags=['ocr']) }, responses=None, tags=['OCR'])
@action(detail=False, url_path="images/feedback", methods=["POST"]) @action(detail=False, url_path="images/feedback", methods=["POST"])
def feedback(self, request): def feedback(self, request):
validated_data = ProcessUtil.sbt_validate_feedback(request) validated_data = ProcessUtil.sbt_validate_feedback(request)
@ -329,7 +327,7 @@ class CtelViewSet(viewsets.ViewSet):
return JsonResponse(status=status.HTTP_200_OK, data={"request_id": rq_id}) return JsonResponse(status=status.HTTP_200_OK, data={"request_id": rq_id})
@extend_schema(request=None, responses=None, tags=['data']) @extend_schema(request=None, responses=None, tags=['Data'])
@extend_schema(request=None, responses=None, tags=['templates'], methods=['GET']) @extend_schema(request=None, responses=None, tags=['templates'], methods=['GET'])
@action(detail=False, url_path=r"media/(?P<folder_type>\w+)/(?P<uq_id>\w+)", methods=["GET"]) @action(detail=False, url_path=r"media/(?P<folder_type>\w+)/(?P<uq_id>\w+)", methods=["GET"])
def get_file_v2(self, request, uq_id=None, folder_type=None): def get_file_v2(self, request, uq_id=None, folder_type=None):
@ -378,7 +376,7 @@ class CtelViewSet(viewsets.ViewSet):
else: else:
raise InvalidException(excArgs='type') raise InvalidException(excArgs='type')
@extend_schema(request=None, responses=None, tags=['data']) @extend_schema(request=None, responses=None, tags=['Data'])
@action(detail=False, url_path=r"v2/media/request/(?P<media_id>\w+)", methods=["GET"]) @action(detail=False, url_path=r"v2/media/request/(?P<media_id>\w+)", methods=["GET"])
def get_file_v3(self, request, media_id=None): def get_file_v3(self, request, media_id=None):
@ -406,7 +404,7 @@ class CtelViewSet(viewsets.ViewSet):
headers={'Content-Disposition': 'filename={fn}'.format(fn=file_name)}, headers={'Content-Disposition': 'filename={fn}'.format(fn=file_name)},
content_type=content_type) content_type=content_type)
@extend_schema(request=None, responses=None, tags=['data']) @extend_schema(request=None, responses=None, tags=['Data'])
@throw_on_failure(InvalidException(excArgs='data')) @throw_on_failure(InvalidException(excArgs='data'))
@action(detail=False, url_path=r"result/(?P<request_id>\w+)", methods=["GET"], renderer_classes=[JSONRenderer, XMLRenderer]) @action(detail=False, url_path=r"result/(?P<request_id>\w+)", methods=["GET"], renderer_classes=[JSONRenderer, XMLRenderer])
def get_result(self, request, request_id=None): def get_result(self, request, request_id=None):

View File

@ -13,7 +13,7 @@ else:
router.register("ctel", CtelViewSet, basename="CtelAPI") router.register("ctel", CtelViewSet, basename="CtelAPI")
router.register("ctel", CtelUserViewSet, basename="CtelUserAPI") router.register("ctel", CtelUserViewSet, basename="CtelUserAPI")
router.register("ctel", CtelTemplateViewSet, basename="CtelTemplateAPI") # router.register("ctel", CtelTemplateViewSet, basename="CtelTemplateAPI")
app_name = "api" app_name = "api"
urlpatterns = router.urls urlpatterns = router.urls

View File

@ -9,14 +9,17 @@ from fwd_api.celery_worker.worker import app
from ..constant.common import FolderFileType, image_extensions from ..constant.common import FolderFileType, image_extensions
from ..exception.exceptions import FileContentInvalidException from ..exception.exceptions import FileContentInvalidException
from fwd_api.models import SubscriptionRequestFile from fwd_api.models import SubscriptionRequestFile
from ..utils import FileUtils, ProcessUtil, S3_process from ..utils import file as FileUtils
from ..utils import process as ProcessUtil
from ..utils import s3 as S3Util
from celery.utils.log import get_task_logger from celery.utils.log import get_task_logger
from fwd import settings from fwd import settings
logger = get_task_logger(__name__) logger = get_task_logger(__name__)
s3_client = S3_process.MinioS3Client( s3_client = S3Util.MinioS3Client(
endpoint=settings.S3_ENDPOINT, endpoint=settings.S3_ENDPOINT,
access_key=settings.S3_ACCESS_KEY, access_key=settings.S3_ACCESS_KEY,
secret_key=settings.S3_SECRET_KEY, secret_key=settings.S3_SECRET_KEY,

View File

@ -7,35 +7,42 @@ from fwd_api.models import SubscriptionRequest
from fwd_api.exception.exceptions import InvalidException from fwd_api.exception.exceptions import InvalidException
from fwd_api.models import SubscriptionRequest from fwd_api.models import SubscriptionRequest
from fwd_api.constant.common import ProcessType from fwd_api.constant.common import ProcessType
from fwd_api.utils.RedisUtils import RedisUtils from fwd_api.utils.redis import RedisUtils
from fwd_api.utils import process as ProcessUtil
redis_client = RedisUtils() redis_client = RedisUtils()
def aggregate_result(resutls, doc_types): def aggregate_result(results, doc_types):
doc_types = doc_types.split(',') doc_types = doc_types.split(',')
des_result = deepcopy(list(resutls.values()))[0] des_result = deepcopy(list(results.values()))[0]
des_result["content"]["total_pages"] = 0 des_result["content"]["total_pages"] = 0
des_result["content"]["ocr_num_pages"] = 0 des_result["content"]["ocr_num_pages"] = 0
des_result["content"]["document"][0]["end_page"] = 0 des_result["content"]["document"][0]["end_page"] = 0
des_result["content"]["document"][0]["content"][3]["value"] = [None for _ in range(doc_types.count("imei"))] des_result["content"]["document"][0]["content"][3]["value"] = [None for _ in range(doc_types.count("imei"))]
des_result["content"]["document"][0]["content"][2]["value"] = [] des_result["content"]["document"][0]["content"][2]["value"] = []
for index, resutl in resutls.items(): sorted_results = [None] * len(doc_types)
for index, result in results.items():
index = int(index) index = int(index)
doc_type = doc_types[index] doc_type = doc_types[index]
sorted_results[index] = ((doc_type, result))
imei_count = 0
for doc_type, result in sorted_results:
des_result["content"]["total_pages"] += 1 des_result["content"]["total_pages"] += 1
des_result["content"]["ocr_num_pages"] += 1 des_result["content"]["ocr_num_pages"] += 1
des_result["content"]["document"][0]["end_page"] += 1 des_result["content"]["document"][0]["end_page"] += 1
if doc_type == "imei": if doc_type == "imei":
des_result["content"]["document"][0]["content"][3]["value"][index] = resutl["content"]["document"][0]["content"][3]["value"][0] des_result["content"]["document"][0]["content"][3]["value"][imei_count] = result["content"]["document"][0]["content"][3]["value"][0]
imei_count += 1
elif doc_type == "invoice": elif doc_type == "invoice":
des_result["content"]["document"][0]["content"][0]["value"] = resutl["content"]["document"][0]["content"][0]["value"] des_result["content"]["document"][0]["content"][0]["value"] = result["content"]["document"][0]["content"][0]["value"]
des_result["content"]["document"][0]["content"][1]["value"] = resutl["content"]["document"][0]["content"][1]["value"] des_result["content"]["document"][0]["content"][1]["value"] = result["content"]["document"][0]["content"][1]["value"]
des_result["content"]["document"][0]["content"][2]["value"] += resutl["content"]["document"][0]["content"][2]["value"] des_result["content"]["document"][0]["content"][2]["value"] += result["content"]["document"][0]["content"][2]["value"]
elif doc_type == "all": elif doc_type == "all":
des_result.update(resutl) des_result.update(result)
else: else:
raise InvalidException(f"doc_type: {doc_type}") raise InvalidException(f"doc_type: {doc_type}")
@ -56,7 +63,6 @@ def update_user(rq: SubscriptionRequest):
sub = rq.subscription sub = rq.subscription
predict_status = rq.status predict_status = rq.status
if predict_status == 3: if predict_status == 3:
from fwd_api.utils import ProcessUtil
sub.current_token += ProcessUtil.token_value(int(rq.process_type)) sub.current_token += ProcessUtil.token_value(int(rq.process_type))
sub.save() sub.save()

View File

@ -6,8 +6,8 @@ from rest_framework import authentication
from rest_framework.exceptions import NotAuthenticated from rest_framework.exceptions import NotAuthenticated
from fwd_api.annotation.api import throw_on_failure from fwd_api.annotation.api import throw_on_failure
from fwd_api.utils import DateUtil from fwd_api.utils import date as DateUtil
from fwd_api.utils.CryptoUtils import ctel_cryptor, sds_authenticator, image_authenticator from fwd_api.utils.crypto import ctel_cryptor, sds_authenticator, image_authenticator
from fwd_api.exception.exceptions import InvalidException, TokenExpiredException, PermissionDeniedException from fwd_api.exception.exceptions import InvalidException, TokenExpiredException, PermissionDeniedException

View File

@ -0,0 +1,19 @@
# Generated by Django 4.1.3 on 2023-12-15 05:10
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('fwd_api', '0156_alter_subscriptionrequestfile_code'),
]
operations = [
migrations.AlterField(
model_name='subscriptionrequest',
name='created_at',
field=models.DateTimeField(db_index=True, default=django.utils.timezone.now),
),
]

View File

@ -17,5 +17,5 @@ class SubscriptionRequest(models.Model):
feedback_result = models.JSONField(null=True) feedback_result = models.JSONField(null=True)
status = models.IntegerField() # 1: Processing(Pending) 2: PredictCompleted 3: ReturnCompleted status = models.IntegerField() # 1: Processing(Pending) 2: PredictCompleted 3: ReturnCompleted
subscription = models.ForeignKey(Subscription, on_delete=models.CASCADE) subscription = models.ForeignKey(Subscription, on_delete=models.CASCADE)
created_at = models.DateTimeField(default=timezone.now) created_at = models.DateTimeField(default=timezone.now, db_index=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)

View File

@ -1,6 +1,6 @@
from django.db import models from django.db import models
from fwd_api.utils.CryptoUtils import sds_db_encryptor from fwd_api.utils.crypto import sds_db_encryptor
class EncryptedCharField(models.CharField): class EncryptedCharField(models.CharField):

View File

@ -3,7 +3,7 @@ from rest_framework import serializers
from fwd_api.constant.common import MAX_NUMBER_OF_TEMPLATE_DATA_BOX, MAX_NUMBER_OF_TEMPLATE_ANCHOR_BOX, \ from fwd_api.constant.common import MAX_NUMBER_OF_TEMPLATE_DATA_BOX, MAX_NUMBER_OF_TEMPLATE_ANCHOR_BOX, \
NUMBER_OF_ITEM_IN_A_BOX, ESCAPE_VALUE NUMBER_OF_ITEM_IN_A_BOX, ESCAPE_VALUE
from fwd_api.exception.exceptions import InvalidException from fwd_api.exception.exceptions import InvalidException
from fwd_api.utils import ProcessUtil from fwd_api.utils import process as ProcessUtil
class CreateTemplateRequest(serializers.Serializer): class CreateTemplateRequest(serializers.Serializer):

View File

@ -3,7 +3,7 @@ from rest_framework import serializers
from fwd_api.constant.common import MAX_NUMBER_OF_TEMPLATE_DATA_BOX, NUMBER_OF_ITEM_IN_A_BOX, \ from fwd_api.constant.common import MAX_NUMBER_OF_TEMPLATE_DATA_BOX, NUMBER_OF_ITEM_IN_A_BOX, \
MAX_NUMBER_OF_TEMPLATE_ANCHOR_BOX, ESCAPE_VALUE MAX_NUMBER_OF_TEMPLATE_ANCHOR_BOX, ESCAPE_VALUE
from fwd_api.exception.exceptions import InvalidException from fwd_api.exception.exceptions import InvalidException
from fwd_api.utils import ProcessUtil from fwd_api.utils import process as ProcessUtil
class UpdateTemplateRequest(serializers.Serializer): class UpdateTemplateRequest(serializers.Serializer):

View File

@ -3,7 +3,7 @@ from rest_framework import serializers
from fwd import settings from fwd import settings
from fwd_api.constant.common import FileCategory, FolderFileType from fwd_api.constant.common import FileCategory, FolderFileType
from fwd_api.models import SubscriptionRequestFile, SubscriptionRequest from fwd_api.models import SubscriptionRequestFile, SubscriptionRequest
from fwd_api.utils import FileUtils from fwd_api.utils import file as FileUtils
class ReportFileSerializer(serializers.Serializer): class ReportFileSerializer(serializers.Serializer):

View File

@ -3,9 +3,9 @@ from rest_framework import serializers
from fwd_api.constant.common import ProcessType, FileCategory from fwd_api.constant.common import ProcessType, FileCategory
from fwd_api.models import SubscriptionRequest, SubscriptionRequestFile from fwd_api.models import SubscriptionRequest, SubscriptionRequestFile
from fwd_api.response.ReportFileSerializer import ReportFileSerializer from fwd_api.response.ReportFileSerializer import ReportFileSerializer
from fwd_api.utils.DateUtil import FORMAT from fwd_api.utils.date import FORMAT
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from fwd_api.utils import FileUtils from fwd_api.utils import file as FileUtils
def i18n_for_label(data) -> list: def i18n_for_label(data) -> list:
if 'fields' in data and isinstance(data['fields'], list): if 'fields' in data and isinstance(data['fields'], list):

View File

@ -2,7 +2,7 @@ from rest_framework import serializers
from fwd_api.models import SubscriptionRequest, Subscription from fwd_api.models import SubscriptionRequest, Subscription
from fwd_api.utils.DateUtil import FORMAT from fwd_api.utils.date import FORMAT
class SubscriptionResponse(serializers.Serializer): class SubscriptionResponse(serializers.Serializer):

View File

@ -4,7 +4,7 @@ from fwd_api.constant.common import TEMPLATE_BOX_TYPE, FolderFileType
from fwd_api.models.OcrTemplate import OcrTemplate from fwd_api.models.OcrTemplate import OcrTemplate
from fwd_api.models.OcrTemplateBox import OcrTemplateBox from fwd_api.models.OcrTemplateBox import OcrTemplateBox
from fwd_api.utils import FileUtils from fwd_api.utils import file as FileUtils
class TemplateBoxResponse(serializers.Serializer): class TemplateBoxResponse(serializers.Serializer):

View File

@ -11,7 +11,9 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from fwd import settings from fwd import settings
from fwd_api.annotation.api import throw_on_failure from fwd_api.annotation.api import throw_on_failure
from fwd_api.exception.exceptions import InvalidException from fwd_api.exception.exceptions import InvalidException
from fwd_api.utils.DateUtil import default_zone, get_date_time_now from fwd_api.utils.date import default_zone, get_date_time_now, FORMAT
from fwd_api.utils import date as DateUtil
class InternalCryptor: class InternalCryptor:
@ -56,8 +58,6 @@ class SdsAuthentication:
def generate_token(self, user_id, internal_id, status, sub_id=-1) -> str: def generate_token(self, user_id, internal_id, status, sub_id=-1) -> str:
c_time = get_date_time_now() + datetime.timedelta(hours=self.duration_of_token) c_time = get_date_time_now() + datetime.timedelta(hours=self.duration_of_token)
c_time.replace(tzinfo=ZoneInfo(default_zone)) c_time.replace(tzinfo=ZoneInfo(default_zone))
from fwd_api.utils import DateUtil
from fwd_api.utils.DateUtil import FORMAT
payload = {"id": user_id, "expired_at": DateUtil.to_str(c_time, FORMAT.DD_MM_YYYY_HHMMSS.value), payload = {"id": user_id, "expired_at": DateUtil.to_str(c_time, FORMAT.DD_MM_YYYY_HHMMSS.value),
'internal_id': internal_id, 'status': status, 'subscription_id': sub_id} 'internal_id': internal_id, 'status': status, 'subscription_id': sub_id}
return self.encode_data(payload) return self.encode_data(payload)
@ -65,16 +65,12 @@ class SdsAuthentication:
def generate_img_token(self, user_id) -> str: def generate_img_token(self, user_id) -> str:
c_time = get_date_time_now() + datetime.timedelta(hours=self.duration_of_token) c_time = get_date_time_now() + datetime.timedelta(hours=self.duration_of_token)
c_time.replace(tzinfo=ZoneInfo(default_zone)) c_time.replace(tzinfo=ZoneInfo(default_zone))
from fwd_api.utils import DateUtil
from fwd_api.utils.DateUtil import FORMAT
payload = {"expired_at": DateUtil.to_str(c_time, FORMAT.DD_MM_YYYY_HHMMSS.value), "internal_id": user_id} payload = {"expired_at": DateUtil.to_str(c_time, FORMAT.DD_MM_YYYY_HHMMSS.value), "internal_id": user_id}
return self.encode_data(payload) return self.encode_data(payload)
def generate_img_token_v2(self, user_id, sub_id=None, user_sync_id=None) -> str: def generate_img_token_v2(self, user_id, sub_id=None, user_sync_id=None) -> str:
c_time = get_date_time_now() + datetime.timedelta(hours=self.duration_of_token) c_time = get_date_time_now() + datetime.timedelta(hours=self.duration_of_token)
c_time.replace(tzinfo=ZoneInfo(default_zone)) c_time.replace(tzinfo=ZoneInfo(default_zone))
from fwd_api.utils import DateUtil
from fwd_api.utils.DateUtil import FORMAT
payload = {"expired_at": DateUtil.to_str(c_time, FORMAT.DD_MM_YYYY_HHMMSS.value), "internal_id": user_id, 'subscription_id': sub_id, "id": user_sync_id} payload = {"expired_at": DateUtil.to_str(c_time, FORMAT.DD_MM_YYYY_HHMMSS.value), "internal_id": user_id, 'subscription_id': sub_id, "id": user_sync_id}
return self.encode_data(payload) return self.encode_data(payload)

View File

@ -12,8 +12,8 @@ from fwd_api.constant.common import allowed_file_extensions
from fwd_api.exception.exceptions import GeneralException, RequiredFieldException, InvalidException, \ from fwd_api.exception.exceptions import GeneralException, RequiredFieldException, InvalidException, \
ServiceUnavailableException, FileFormatInvalidException, LimitReachedException, InvalidDecompressedSizeException ServiceUnavailableException, FileFormatInvalidException, LimitReachedException, InvalidDecompressedSizeException
from fwd_api.models import SubscriptionRequest, OcrTemplate from fwd_api.models import SubscriptionRequest, OcrTemplate
from fwd_api.utils import ProcessUtil from fwd_api.utils import process as ProcessUtil
from fwd_api.utils.CryptoUtils import image_authenticator from fwd_api.utils.crypto import image_authenticator
from fwd_api.utils.image import resize from fwd_api.utils.image import resize
from ..celery_worker.client_connector import c_connector from ..celery_worker.client_connector import c_connector
import imagesize import imagesize

View File

@ -0,0 +1,37 @@
from django.db.models import Count
from django.utils import timezone
from datetime import timedelta
from ..models import SubscriptionRequest
def get_latest_requests(limit=50):
requests = SubscriptionRequest.objects.order_by("-created_at")[:limit]
requests_dict = []
for request in requests:
requests_dict.append({
"request_id": request.request_id,
"pages": request.pages,
"doc_type": request.doc_type,
# "predict_result": request.predict_result,
"created_at": request.created_at,
})
return requests_dict
def count_requests_by_date(days_limit=5):
today = timezone.now().date()
start_date = today - timedelta(days=days_limit)
requests_by_date = SubscriptionRequest.objects.filter(created_at__gte=start_date).values('created_at__date').annotate(count=Count('id')).values('created_at__date', 'count').order_by('created_at__date')
count_dict = []
for rbd in requests_by_date:
count_dict.append({
"date": rbd["created_at__date"],
"count": rbd["count"],
})
return count_dict
def get_health_report():
return {
"latest_requests": get_latest_requests(),
"count_by_date": count_requests_by_date(),
}

View File

@ -1,4 +1,5 @@
import os import os
import uuid
import random import random
import string import string
import tempfile import tempfile
@ -15,14 +16,14 @@ from fwd_api.constant.common import LIST_BOX_MESSAGE, pattern, NAME_MESSAGE, all
FolderFileType, FileCategory FolderFileType, FileCategory
from fwd_api.exception.exceptions import NumberOfBoxLimitReachedException, \ from fwd_api.exception.exceptions import NumberOfBoxLimitReachedException, \
ServiceUnavailableException, DuplicateEntityException, LimitReachedException, BadGatewayException ServiceUnavailableException, DuplicateEntityException, LimitReachedException, BadGatewayException
from fwd_api.utils import DateUtil, FileUtils from fwd_api.utils import date as DateUtil
from fwd_api.utils import file as FileUtils
from ..constant.common import ProcessType, TEMPLATE_BOX_TYPE, EntityStatus from ..constant.common import ProcessType, TEMPLATE_BOX_TYPE, EntityStatus
from ..exception.exceptions import InvalidException, NotFoundException, \ from ..exception.exceptions import InvalidException, NotFoundException, \
PermissionDeniedException, RequiredFieldException, InvalidException, InvalidDecompressedSizeException PermissionDeniedException, RequiredFieldException, InvalidException, InvalidDecompressedSizeException
from ..models import UserProfile, OcrTemplate, OcrTemplateBox, \ from ..models import UserProfile, OcrTemplate, OcrTemplateBox, \
Subscription, SubscriptionRequestFile, SubscriptionRequest Subscription, SubscriptionRequestFile, SubscriptionRequest
from ..celery_worker.client_connector import c_connector from ..celery_worker.client_connector import c_connector
import uuid
from celery.utils.log import get_task_logger from celery.utils.log import get_task_logger
@ -132,9 +133,6 @@ def sbt_validate_ocr_request_and_get(request, subscription):
FileUtils.validate_list_file(imei_files, file_field="imei_file") FileUtils.validate_list_file(imei_files, file_field="imei_file")
FileUtils.validate_list_file(invoice_file, max_file_num=1, min_file_num=0, file_field="invoice_file") FileUtils.validate_list_file(invoice_file, max_file_num=1, min_file_num=0, file_field="invoice_file")
# if not isinstance(redemption_ID, str):
# # raise RequiredFieldException(excArgs="redemption_ID")
# raise InvalidException(excArgs="redemption_ID")
validated_data['imei_file'] = imei_files validated_data['imei_file'] = imei_files
validated_data['invoice_file'] = invoice_file validated_data['invoice_file'] = invoice_file
@ -413,7 +411,7 @@ def process_image_local_file(file_name: str, file_path: str, request: Subscripti
def pdf_to_images_urls(doc_path, request: SubscriptionRequest, user, dpi: int = 300) -> list: def pdf_to_images_urls(doc_path, request: SubscriptionRequest, user, dpi: int = 300) -> list:
pdf_extracted = [] pdf_extracted = []
saving_path = FileUtils.get_folder_path(request) saving_path = FileUtils.get_folder_path(request)
break_file_name = f'break_0.jpg' break_file_name = f'{os.path.basename(doc_path.name)}_page_0.jpg'
saving_path = os.path.join(saving_path, break_file_name) saving_path = os.path.join(saving_path, break_file_name)
image = get_first_page_pdf(doc_path, 300) image = get_first_page_pdf(doc_path, 300)

View File

@ -110,12 +110,15 @@ export function MainLayout() {
style={{ style={{
marginBottom: 0, marginBottom: 0,
color: gray[0], color: gray[0],
textAlign: 'center', textAlign: 'left',
}} }}
ellipsis={{ ellipsis={{
rows: 2, rows: 10,
}} }}
> >
<b>NOTE: </b> This UI is only for reference. Please use swagger UI for full-option API testing.
<br />
<br />
Powered by SAMSUNG SDS. Powered by SAMSUNG SDS.
<br /> <br />
All rights reserved. All rights reserved.
@ -142,9 +145,11 @@ export function MainLayout() {
style={{ style={{
marginBottom: 0, marginBottom: 0,
color: gray[0], color: gray[0],
textAlign: 'center', textAlign: 'left',
}} }}
> >
<b>NOTE: </b> This UI is only for reference. Please use swagger UI for full-option API testing.
<br />
Powered by SAMSUNG SDS. Powered by SAMSUNG SDS.
</Typography.Paragraph> </Typography.Paragraph>
</Layout.Header> </Layout.Header>

View File

@ -51,9 +51,7 @@
"Key Information Extraction": "Key Information Extraction", "Key Information Extraction": "Key Information Extraction",
"Label": "Label", "Label": "Label",
"Label of extracted field must not have more than 255 characters": "Label of extracted field must not have more than 255 characters", "Label of extracted field must not have more than 255 characters": "Label of extracted field must not have more than 255 characters",
"Language": "Language",
"Login": "Login", "Login": "Login",
"Login with  <0>CMC Account</0>?": "Login with  <0>CMC Account</0>?",
"Name": "Name", "Name": "Name",
"Nation": "Nation", "Nation": "Nation",
"Nationality": "Nationality", "Nationality": "Nationality",

View File

@ -51,9 +51,7 @@
"Key Information Extraction": "Trích xuất thông tin", "Key Information Extraction": "Trích xuất thông tin",
"Label": "Nhãn", "Label": "Nhãn",
"Label of extracted field must not have more than 255 characters": "Độ dài nhãn của trường thông tin trích xuất không được vượt quá 255 kí tự", "Label of extracted field must not have more than 255 characters": "Độ dài nhãn của trường thông tin trích xuất không được vượt quá 255 kí tự",
"Language": "",
"Login": "", "Login": "",
"Login with  <0>CMC Account</0>?": "",
"Name": "Tên", "Name": "Tên",
"Nation": "Dân tộc", "Nation": "Dân tộc",
"Nationality": "Quốc tịch", "Nationality": "Quốc tịch",

View File

@ -93,6 +93,7 @@ services:
- ./data/minio_data:/data - ./data/minio_data:/data
networks: networks:
- ctel-sbt - ctel-sbt
restart: always
command: server --address :9884 --console-address :9885 /data command: server --address :9884 --console-address :9885 /data
profiles: ["local"] profiles: ["local"]
@ -149,11 +150,9 @@ services:
- BASE_URL=http://be-ctel-sbt:${BASE_PORT} - BASE_URL=http://be-ctel-sbt:${BASE_PORT}
- REDIS_HOST=result-cache - REDIS_HOST=result-cache
- REDIS_PORT=6379 - REDIS_PORT=6379
restart: always
networks: networks:
- ctel-sbt - ctel-sbt
# restart: always
depends_on: depends_on:
db-sbt: db-sbt:
condition: service_started condition: service_started
@ -168,6 +167,7 @@ services:
# Back-end persistent # Back-end persistent
db-sbt: db-sbt:
restart: always
mem_reservation: 500m mem_reservation: 500m
# mem_limit: 1g # mem_limit: 1g
# container_name: sidp-cope2n-be-sbt-db # container_name: sidp-cope2n-be-sbt-db
@ -200,6 +200,7 @@ services:
# Front-end services # Front-end services
fe-sbt: fe-sbt:
restart: always
build: build:
context: cope2n-fe context: cope2n-fe
shm_size: 10gb shm_size: 10gb

View File

@ -94,12 +94,12 @@ def process_file(data):
# invoice_files = [
# ('invoice_file', ('invoice.pdf', open("test_samples/20220303025923NHNE_20220222_Starhub_Order_Confirmation_by_Email.pdf", "rb").read())),
# ]
invoice_files = [ invoice_files = [
('invoice_file', ('invoice.jpg', open("test_samples/sbt/invoice.jpg", "rb").read())), ('invoice_file', ('invoice.pdf', open("test_samples/20220303025923NHNE_20220222_Starhub_Order_Confirmation_by_Email.pdf", "rb").read())),
] ]
# invoice_files = [
# ('invoice_file', ('invoice.jpg', open("test_samples/sbt/invoice.jpg", "rb").read())),
# ]
imei_files = [ imei_files = [
('imei_files', ("test_samples/sbt/imei1.jpg", open("test_samples/sbt/imei1.jpg", "rb").read())), ('imei_files', ("test_samples/sbt/imei1.jpg", open("test_samples/sbt/imei1.jpg", "rb").read())),
('imei_files', ("test_samples/sbt/imei2.jpg", open("test_samples/sbt/imei2.jpg", "rb").read())), ('imei_files', ("test_samples/sbt/imei2.jpg", open("test_samples/sbt/imei2.jpg", "rb").read())),
@ -127,7 +127,6 @@ print("## TEST REPORT #################################")
print("Number of requests: {}".format(args.num_requests)) print("Number of requests: {}".format(args.num_requests))
print("Number of concurrent requests: {}".format(args.num_workers)) print("Number of concurrent requests: {}".format(args.num_workers))
print("Number of files: 1 invoice, 1-5 imei files (random)") print("Number of files: 1 invoice, 1-5 imei files (random)")
print("Query time interval for result: {:.3f}s ".format(args.checking_interval))
print("--------------------------------------") print("--------------------------------------")
print("SUCCESS RATE") print("SUCCESS RATE")
counter = {} counter = {}
@ -143,14 +142,12 @@ if len(uploading_time) == 0:
print("No valid uploading time") print("No valid uploading time")
print("Check the results!") print("Check the results!")
processing_time = [x["process_time"] for x in results if x["success"]] processing_time = [x["process_time"] for x in results if x["success"]]
print("Uploading time (Avg / Min / Max): {:.3f}s {:.3f}s {:.3f}s".format(sum(uploading_time) / len(uploading_time), min(uploading_time), max(uploading_time))) print("Uploading + Processing time (Avg / Min / Max): {:.3f}s {:.3f}s {:.3f}s".format(sum(processing_time) / len(processing_time), min(processing_time), max(processing_time)))
print("Processing time (Avg / Min / Max): {:.3f}s {:.3f}s {:.3f}s".format(sum(processing_time) / len(processing_time), min(processing_time), max(processing_time)))
print("--------------------------------------") print("--------------------------------------")
print("TIME BY IMAGE") print("TIME BY IMAGE")
uploading_time = [x["upload_time"] for x in results if x["success"]] uploading_time = [x["upload_time"] for x in results if x["success"]]
processing_time = [x["process_time"] for x in results if x["success"]] processing_time = [x["process_time"] for x in results if x["success"]]
num_images = sum(x["num_files"] for x in results if x["success"]) num_images = sum(x["num_files"] for x in results if x["success"])
print("Total images:", num_images) print("Total images:", num_images)
print("Uploading time: {:.3f}s".format(sum(uploading_time) / num_images)) print("Uploading + Processing time: {:.3f}s".format(sum(processing_time) / num_images))
print("Processing time: {:.3f}s".format(sum(processing_time) / num_images))
print("--------------------------------------") print("--------------------------------------")