From 4ff48110e1fcc1d886099585cef8352a0ed0cd46 Mon Sep 17 00:00:00 2001 From: daovietanh99 Date: Fri, 26 Jan 2024 16:37:12 +0700 Subject: [PATCH 1/7] add get and update request api --- cope2n-api/fwd_api/api/accuracy_view.py | 86 ++++++++++++++++++++++++- cope2n-api/fwd_api/api_router.py | 3 +- 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/cope2n-api/fwd_api/api/accuracy_view.py b/cope2n-api/fwd_api/api/accuracy_view.py index e482e58..3ed49f2 100644 --- a/cope2n-api/fwd_api/api/accuracy_view.py +++ b/cope2n-api/fwd_api/api/accuracy_view.py @@ -165,7 +165,7 @@ class AccuracyViewSet(viewsets.ViewSet): 'Client Request Time (ms)': request.client_request_time, 'Server Processing Time (ms)': request.preprocessing_time + request.ai_inference_time, 'Is Reviewed': request.is_reviewed, - 'Is Bad Quality': request.is_bad_image_quality, + # 'Is Bad Quality': request.is_bad_image_quality, 'created_at': request.created_at.isoformat() }) @@ -180,4 +180,86 @@ class AccuracyViewSet(viewsets.ViewSet): return JsonResponse(response) - return JsonResponse({'error': 'Invalid request method.'}, status=405) \ No newline at end of file + return JsonResponse({'error': 'Invalid request method.'}, status=405) + + +class RequestViewSet(viewsets.ViewSet): + lookup_field = "username" + + @extend_schema(request = { + 'multipart/form-data': { + 'type': 'object', + 'properties': { + 'reviewed_result': { + 'type': 'string', + }, + } + }, + }, responses=None, tags=['Request'] + ) + @action(detail=False, url_path=r"request/(?P[\w\-]+)", methods=["GET", "POST"]) + def get_subscription_request(self, request, request_id=None): + if request.method == 'GET': + base_query = Q(request_id=request_id) + + subscription_request = SubscriptionRequest.objects.filter(base_query).first() + + data = [] + + imeis = [] + purchase_date = [] + retailer = "" + try: + if subscription_request.reviewed_result is not None: + imeis = subscription_request.reviewed_result.get("imei_number", []) + purchase_date = subscription_request.reviewed_result.get("purchase_date", []) + retailer = subscription_request.reviewed_result.get("retailername", "") + elif subscription_request.feedback_result is not None : + imeis = subscription_request.feedback_result.get("imei_number", []) + purchase_date = subscription_request.feedback_result.get("purchase_date", []) + retailer = subscription_request.feedback_result.get("retailername", "") + elif subscription_request.predict_result is not None: + if subscription_request.predict_result.get("status", 404) == 200: + imeis = subscription_request.predict_result.get("content", {}).get("document", [])[0].get("content", [])[3].get("value", []) + purchase_date = subscription_request.predict_result.get("content", {}).get("document", [])[0].get("content", [])[2].get("value", []) + retailer = subscription_request.predict_result.get("content", {}).get("document", [])[0].get("content", [])[0].get("value", []) + except Exception as e: + print(f"[ERROR]: {e}") + print(f"[ERROR]: {subscription_request}") + data.append({ + 'RequestID': subscription_request.request_id, + 'RedemptionID': subscription_request.redemption_id, + 'IMEIs': imeis, + 'Purchase Date': purchase_date, + 'Retailer': retailer, + 'Reviewed result': subscription_request.reviewed_result, + 'Feedback result': subscription_request.feedback_result, + 'Client Request Time (ms)': subscription_request.client_request_time, + 'Server Processing Time (ms)': subscription_request.preprocessing_time + subscription_request.ai_inference_time, + 'Is Reviewed': subscription_request.is_reviewed, + # 'Is Bad Quality': subscription_request.is_bad_image_quality, + 'created_at': subscription_request.created_at.isoformat() + }) + + response = { + 'subscription_requests': data + } + + return JsonResponse(response) + + elif request.method == 'POST': + data = request.data + + base_query = Q(request_id=request_id) + + subscription_request = SubscriptionRequest.objects.filter(base_query).first() + + try: + subscription_request.reviewed_result = data['reviewed_result'] + except Exception as e: + print(f"[ERROR]: {e}") + print(f"[ERROR]: {subscription_request}") + + return JsonResponse({'message': 'success.'}, status=200) + else: + return JsonResponse({'error': 'Invalid request method.'}, status=405) \ No newline at end of file diff --git a/cope2n-api/fwd_api/api_router.py b/cope2n-api/fwd_api/api_router.py index 9a466dc..322cc20 100755 --- a/cope2n-api/fwd_api/api_router.py +++ b/cope2n-api/fwd_api/api_router.py @@ -2,7 +2,7 @@ from django.conf import settings from rest_framework.routers import DefaultRouter, SimpleRouter from fwd_api.api.ctel_view import CtelViewSet -from fwd_api.api.accuracy_view import AccuracyViewSet +from fwd_api.api.accuracy_view import AccuracyViewSet, RequestViewSet from fwd_api.api.ctel_user_view import CtelUserViewSet @@ -16,6 +16,7 @@ else: router.register("ctel", CtelViewSet, basename="CtelAPI") router.register("ctel", CtelUserViewSet, basename="CtelUserAPI") router.register("ctel", AccuracyViewSet, basename="AccuracyAPI") +router.register("ctel", RequestViewSet, basename="RequestAPI") app_name = "api" urlpatterns = router.urls From e4c439c7cd9daa6327cc40366a67cc3159671f75 Mon Sep 17 00:00:00 2001 From: daovietanh99 Date: Mon, 29 Jan 2024 17:43:10 +0700 Subject: [PATCH 2/7] api update --- cope2n-api/fwd_api/api/accuracy_view.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/cope2n-api/fwd_api/api/accuracy_view.py b/cope2n-api/fwd_api/api/accuracy_view.py index 3ed49f2..abeb682 100644 --- a/cope2n-api/fwd_api/api/accuracy_view.py +++ b/cope2n-api/fwd_api/api/accuracy_view.py @@ -10,6 +10,9 @@ from django.db.models import Q from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes # from drf_spectacular.types import OpenApiString from ..models import SubscriptionRequest +from ..exception.exceptions import RequiredFieldException + +import json class AccuracyViewSet(viewsets.ViewSet): @@ -238,7 +241,8 @@ class RequestViewSet(viewsets.ViewSet): 'Server Processing Time (ms)': subscription_request.preprocessing_time + subscription_request.ai_inference_time, 'Is Reviewed': subscription_request.is_reviewed, # 'Is Bad Quality': subscription_request.is_bad_image_quality, - 'created_at': subscription_request.created_at.isoformat() + 'created_at': subscription_request.created_at.isoformat(), + 'updated_at': subscription_request.updated_at.isoformat() }) response = { @@ -254,11 +258,14 @@ class RequestViewSet(viewsets.ViewSet): subscription_request = SubscriptionRequest.objects.filter(base_query).first() - try: - subscription_request.reviewed_result = data['reviewed_result'] - except Exception as e: - print(f"[ERROR]: {e}") - print(f"[ERROR]: {subscription_request}") + reviewed_result = json.loads(data["reviewed_result"][1:-1]) + for field in ['retailername', 'sold_to_party', 'purchase_date', 'imei_number']: + if not field in reviewed_result.keys(): + raise RequiredFieldException(excArgs=f'reviewed_result.{field}') + subscription_request.reviewed_result = reviewed_result + subscription_request.reviewed_result['request_id'] = request_id + subscription_request.is_reviewed = True + subscription_request.save() return JsonResponse({'message': 'success.'}, status=200) else: From cd88f3b95950baa4815af27e914397f72f85c7a9 Mon Sep 17 00:00:00 2001 From: daovietanh99 Date: Tue, 30 Jan 2024 16:33:42 +0700 Subject: [PATCH 3/7] convert dict to xlsx --- cope2n-api/fwd_api/utils/file.py | 120 ++++++++++++++++++++++++++----- cope2n-api/report.xlsx | Bin 0 -> 6546 bytes 2 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 cope2n-api/report.xlsx diff --git a/cope2n-api/fwd_api/utils/file.py b/cope2n-api/fwd_api/utils/file.py index 92142da..44a2e9b 100644 --- a/cope2n-api/fwd_api/utils/file.py +++ b/cope2n-api/fwd_api/utils/file.py @@ -19,6 +19,9 @@ from ..celery_worker.client_connector import c_connector import imagesize import csv +from openpyxl import load_workbook +from openpyxl.styles import Font, Border, Side, PatternFill, NamedStyle + def validate_feedback_file(csv_file_path): required_columns = ['redemptionNumber', 'requestId', 'imeiNumber', 'imeiNumber2', 'Purchase Date', 'retailer', 'Sold to party', 'timetakenmilli'] missing_columns = [] @@ -327,21 +330,104 @@ def build_media_url_v2(media_id: str, user_id: int, sub_id: int, u_sync_id: str) token = image_authenticator.generate_img_token_v2(user_id, sub_id, u_sync_id) return f'{settings.BASE_URL}/api/ctel/v2/media/request/{media_id}/?token={token}' -def json2xlsx(input: json): - """_summary_ - Args: - input (json): - : [{ - Subs: Jan, # Subtotal name - Metadata: {num_imei: 1, - ...: ...} - Data: [{num_imei: 1, - ...: ...}] - }] - OR - input (json): - : [] - Return xlsx (object) - """ - pass \ No newline at end of file +def get_value(_dict, keys): + keys = keys.split('.') + value = _dict + for key in keys: + if not key in value.keys(): + return "-" + else: + value = value.get(key, {}) + + if value != 0: + return value + else: + return "-" + + +def dict2xlsx(input: json): + red = "FF0000" + black = "000000" + green = "E2EFDA" + yellow = "FFF2CC" + gray = "D0CECE" + font_black = Font(name="Calibri", size=11, color=black) + font_black_bold = Font(name="Calibri", size=11, color=black, bold=True) + font_red = Font(name="Calibri", size=11, color=red) + thin = Side(border_style="thin", color=black) + border = Border(left=thin, right=thin, top=thin, bottom=thin) + fill_green = PatternFill(start_color=green, end_color=green, fill_type = "solid") + fill_yellow = PatternFill(start_color=yellow, end_color=yellow, fill_type = "solid") + fill_gray = PatternFill(start_color=gray, end_color=gray, fill_type = "solid") + normal_cell = NamedStyle(name="normal_cell", font=font_black, border=border) + normal_cell_red = NamedStyle(name="normal_cell_red", font=font_red, border=border) + + wb = load_workbook(filename = 'report.xlsx') + ws = wb['Sheet1'] + + mapping = { + 'A': 'subs', + 'B': 'extraction_date', + 'C': 'num_imei', + 'D': 'num_invoice', + 'E': 'total_images', + 'F': 'images_quality.successful', + 'G': 'images_quality.successful_percent', + 'H': 'images_quality.bad', + 'I': 'images_quality.bad_percent', + 'J': 'average_accuracy_rate.imei', + 'K': 'average_accuracy_rate.purchase_date', + 'L': 'average_accuracy_rate.retailer_name', + 'M': 'average_processing_time.imei', + 'N': 'average_processing_time.invoice', + 'O': 'usage.imei', + 'P': 'usage.invoice', + } + + start_index = 5 + + for subtotal in input: + ws['A' + str(start_index)] = subtotal['subs'] + ws['A' + str(start_index)].font = font_black + ws['A' + str(start_index)].border = border + ws['A' + str(start_index)].fill = fill_gray + + ws['B' + str(start_index)] = subtotal['extraction_date'] + ws['B' + str(start_index)].font = font_black_bold + ws['B' + str(start_index)].border = border + ws['B' + str(start_index)].fill = fill_green + + ws['C' + str(start_index)].border = border + ws['D' + str(start_index)].border = border + + for key in ['E', 'F', 'G', 'H', 'I']: + ws[key + str(start_index)] = get_value(subtotal, mapping[key]) + ws[key + str(start_index)].font = font_black + ws[key + str(start_index)].border = border + ws[key + str(start_index)].fill = fill_yellow + + for key in ['J', 'K', 'L', 'M', 'N']: + ws[key + str(start_index)] = get_value(subtotal, mapping[key]) + ws[key + str(start_index)].font = font_black + ws[key + str(start_index)].border = border + ws[key + str(start_index)].fill = fill_gray + + start_index += 1 + + for record in subtotal['data']: + for key in mapping.keys(): + value = get_value(record, mapping[key]) + ws[key + str(start_index)] = value + print(type(value)) + if 'average_accuracy_rate' in mapping[key] and type(value) in [int, float] and value < 95: + ws[key + str(start_index)].style = normal_cell_red + elif 'average_processing_time' in mapping[key] and type(value) in [int, float] and value > 2.0: + ws[key + str(start_index)].style = normal_cell_red + elif 'bad_percent' in mapping[key] and type(value) in [int, float] and value > 10: + ws[key + str(start_index)].style = normal_cell_red + else : + ws[key + str(start_index)].style = normal_cell + start_index += 1 + + return wb diff --git a/cope2n-api/report.xlsx b/cope2n-api/report.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..fc5b37d989c90238850317ab48fca2472ee7f76f GIT binary patch literal 6546 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 H+^+rysSZ9~ literal 0 HcmV?d00001 From 75819d97807f76b21e1e5f37c77f2a07c0219cf3 Mon Sep 17 00:00:00 2001 From: daovietanh99 Date: Tue, 30 Jan 2024 16:34:30 +0700 Subject: [PATCH 4/7] convert dict to xlxs --- cope2n-api/fwd_api/utils/file.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cope2n-api/fwd_api/utils/file.py b/cope2n-api/fwd_api/utils/file.py index 44a2e9b..266a038 100644 --- a/cope2n-api/fwd_api/utils/file.py +++ b/cope2n-api/fwd_api/utils/file.py @@ -419,7 +419,6 @@ def dict2xlsx(input: json): for key in mapping.keys(): value = get_value(record, mapping[key]) ws[key + str(start_index)] = value - print(type(value)) if 'average_accuracy_rate' in mapping[key] and type(value) in [int, float] and value < 95: ws[key + str(start_index)].style = normal_cell_red elif 'average_processing_time' in mapping[key] and type(value) in [int, float] and value > 2.0: From 34cd161c41f4135181212b3bd9608e8454b62ba1 Mon Sep 17 00:00:00 2001 From: daovietanh99 Date: Tue, 30 Jan 2024 17:38:05 +0700 Subject: [PATCH 5/7] convert dict to xlsx --- cope2n-api/fwd_api/utils/file.py | 132 ++++++++++++++++++------------- cope2n-api/report_detail.xlsx | Bin 0 -> 6182 bytes 2 files changed, 77 insertions(+), 55 deletions(-) create mode 100644 cope2n-api/report_detail.xlsx diff --git a/cope2n-api/fwd_api/utils/file.py b/cope2n-api/fwd_api/utils/file.py index 266a038..17d22ee 100644 --- a/cope2n-api/fwd_api/utils/file.py +++ b/cope2n-api/fwd_api/utils/file.py @@ -346,7 +346,7 @@ def get_value(_dict, keys): return "-" -def dict2xlsx(input: json): +def dict2xlsx(input: json, _type='report'): red = "FF0000" black = "000000" green = "E2EFDA" @@ -366,67 +366,89 @@ def dict2xlsx(input: json): wb = load_workbook(filename = 'report.xlsx') ws = wb['Sheet1'] - mapping = { - 'A': 'subs', - 'B': 'extraction_date', - 'C': 'num_imei', - 'D': 'num_invoice', - 'E': 'total_images', - 'F': 'images_quality.successful', - 'G': 'images_quality.successful_percent', - 'H': 'images_quality.bad', - 'I': 'images_quality.bad_percent', - 'J': 'average_accuracy_rate.imei', - 'K': 'average_accuracy_rate.purchase_date', - 'L': 'average_accuracy_rate.retailer_name', - 'M': 'average_processing_time.imei', - 'N': 'average_processing_time.invoice', - 'O': 'usage.imei', - 'P': 'usage.invoice', - } + if _type == 'report': + wb = load_workbook(filename = 'report.xlsx') + ws = wb['Sheet1'] + mapping = { + 'A': 'subs', + 'B': 'extraction_date', + 'C': 'num_imei', + 'D': 'num_invoice', + 'E': 'total_images', + 'F': 'images_quality.successful', + 'G': 'images_quality.successful_percent', + 'H': 'images_quality.bad', + 'I': 'images_quality.bad_percent', + 'J': 'average_accuracy_rate.imei', + 'K': 'average_accuracy_rate.purchase_date', + 'L': 'average_accuracy_rate.retailer_name', + 'M': 'average_processing_time.imei', + 'N': 'average_processing_time.invoice', + 'O': 'usage.imei', + 'P': 'usage.invoice', + } + start_index = 5 - start_index = 5 + elif _type == 'report_detail': + wb = load_workbook(filename = 'report_detail.xlsx') + ws = wb['Sheet1'] + mapping = { + 'A': 'request_id', + 'B': 'redemption_number', + 'C': 'image_type', + 'D': 'imei_user_submitted', + 'E': "imei_ocr_retrieved", + 'F': "imei1_accuracy", + 'G': "purchase_date_user_submitted", + 'H': "purchase_date_ocr_retrieved", + 'I': "purchase_date_accuracy", + 'J': "retailer_user_submitted", + 'K': "retailer_ocr_retrieved", + 'L': "retailer_accuracy", + 'M': "average_accuracy", + 'N': "ocr_processing_time", + 'O': "is_reviewed", + 'P': "bad_image_reasons", + 'R': "countermeasures", + } + start_index = 4 for subtotal in input: - ws['A' + str(start_index)] = subtotal['subs'] - ws['A' + str(start_index)].font = font_black - ws['A' + str(start_index)].border = border - ws['A' + str(start_index)].fill = fill_gray - - ws['B' + str(start_index)] = subtotal['extraction_date'] - ws['B' + str(start_index)].font = font_black_bold - ws['B' + str(start_index)].border = border - ws['B' + str(start_index)].fill = fill_green - - ws['C' + str(start_index)].border = border - ws['D' + str(start_index)].border = border - - for key in ['E', 'F', 'G', 'H', 'I']: - ws[key + str(start_index)] = get_value(subtotal, mapping[key]) - ws[key + str(start_index)].font = font_black + for key_index, key in enumerate(mapping.keys()): + value = get_value(subtotal, mapping[key]) + ws[key + str(start_index)] = value ws[key + str(start_index)].border = border - ws[key + str(start_index)].fill = fill_yellow - for key in ['J', 'K', 'L', 'M', 'N']: - ws[key + str(start_index)] = get_value(subtotal, mapping[key]) - ws[key + str(start_index)].font = font_black - ws[key + str(start_index)].border = border - ws[key + str(start_index)].fill = fill_gray + if _type == 'report': + ws[key + str(start_index)].font = font_black_bold + if key_index == 0 or (key_index >= 9 and key_index <= 15): + ws[key + str(start_index)].fill = fill_gray + elif key_index >= 4 and key_index <= 8: + ws[key + str(start_index)].fill = fill_yellow + elif _type == 'report_detail': + if 'accuracy' in mapping[key] and type(value) in [int, float] and value < 95: + ws[key + str(start_index)].style = normal_cell_red + elif 'time' in mapping[key] and type(value) in [int, float] and value > 2.0: + ws[key + str(start_index)].style = normal_cell_red + else: + ws[key + str(start_index)].style = normal_cell start_index += 1 - for record in subtotal['data']: - for key in mapping.keys(): - value = get_value(record, mapping[key]) - ws[key + str(start_index)] = value - if 'average_accuracy_rate' in mapping[key] and type(value) in [int, float] and value < 95: - ws[key + str(start_index)].style = normal_cell_red - elif 'average_processing_time' in mapping[key] and type(value) in [int, float] and value > 2.0: - ws[key + str(start_index)].style = normal_cell_red - elif 'bad_percent' in mapping[key] and type(value) in [int, float] and value > 10: - ws[key + str(start_index)].style = normal_cell_red - else : - ws[key + str(start_index)].style = normal_cell - start_index += 1 + if 'data' in subtotal.keys(): + for record in subtotal['data']: + for key in mapping.keys(): + value = get_value(record, mapping[key]) + ws[key + str(start_index)] = value + if 'average_accuracy_rate' in mapping[key] and type(value) in [int, float] and value < 95: + ws[key + str(start_index)].style = normal_cell_red + elif 'average_processing_time' in mapping[key] and type(value) in [int, float] and value > 2.0: + ws[key + str(start_index)].style = normal_cell_red + elif 'bad_percent' in mapping[key] and type(value) in [int, float] and value > 10: + ws[key + str(start_index)].style = normal_cell_red + else : + ws[key + str(start_index)].style = normal_cell + + start_index += 1 return wb diff --git a/cope2n-api/report_detail.xlsx b/cope2n-api/report_detail.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..3df13d8d91d576538b183207c12c63de1f61f725 GIT binary patch literal 6182 zcmaJ_1z3}7`v+lC0;54elx{|McM79Hgh`HOV<6qqjdY5LfYKpHaDbqsbdHjckQ5{Z z{_LD{ee(JJ&$-|0diFlMc0cdl-`zhQ4J>SO3|w4Xj0$~eV~lG;aCL71vvGp+@}So@ z$ph+Ge87ku|L8)Mvc!E9Ckpp3o?h)5=g2I21;ifdY0jFX@iu-`ADkTpwc z`y)C>C0g0F%#<@W{wK9>Q9jYpw>vG4M*WPR4b_>5t$NBvJDd*gCA(nO+yPBK)NMC@ zB)(`_OxqM;J!|b7=4@_(b!okJ7+HEaXFqoqXBcr5mR?o*O*z7A`Oyt!v7tqy=ZV}$ zY|6X>%_I*76!i=WZfn1D;`KW5)y8ETH>+NVbkBXOUmKf&L`XJ8z;b2CR>LYT;9_ra zl@l(SleN}+?Z;Y&SnXvnbTn{qZOcL$7p`7g;s(am{-1!kef5hcPP~6%FH9}QmelN+Bmc6R)i-9^n zC5~1m@!If&JODCH_+e)+4eMyTR(ea=SWj!YcfV&>mJT#~UNznxBL!tTip#`_#SRurU!dPVWcBY0x;8{wq$zf8lf;p*X#67ZeECa>R)} zXQMXfa3>ARC_WKm7Fg1qnXF9d7){=_m-hT}j>M*zeOiH7Sd3u4InF2`}5uFbg6 zpzpa{m!wV;PuO(lCdz7KAj2-HJVfKoJzJ`(P1_$X-ov)}(3YdMmKwftV73x)vNeeG=_+{&%{D(o3gi5<2&XI zpI1B8bA^YMRNPMx>9>1CwiVW!11L9|wunpL^9!^G_r#lY7XuvXhD&&-ytHz$WZ;EtLpz;%%4ayl@^HLHcZm{JJEUrxYY!WoXI!7~)$E zQMj|^h>P7AbL4hldoq%QG70qQAslUqH7)a)qtc&R$7c5+H?&Ts&JHgKYT{KUs*Wi1 zgl8dTL8Q;$^xHSoweuaH_2 z&Dz1zi;m_{627y&0P!4_+Vw&DilQWXrAD&L5B8FKBPhY*Hs);WybC51u~EQza{PNG zh50V5R20FR!x=TFEjB>v*tvwQTxH}KFWP7NP@MWlm>3u?SpODVWPkaLhrNxn4ey^T zA3CyTpP9R&?vX#;IpPE!ak|eas*#sur7698RTtyYBGWec64MFOIXzJ3miA(0>4+e7 z!;g$ea;G;K{4|1TNdr< z@b5M$TR{ZXjjV0kaWXI10S~-fhH>t`t5Y&~d;c&#{TUFUcsUb%D}h;GM7|O)?^EsR zMgh$Awvm}gszH`z5iC}AdAmsV&0+sTs8yq7bYjRnyS^%{*JD2byfcz{z#CyhQ*=lr zFfsTBkTNueBu=UBBRbi>+?>B?Q(bk{xJiGw-}jXj?PIvgIb)1hIcWm--+YYlFCT+@ zcstp^(cZOXG8CUJO71^jDH|{^FaH#eqR=7Jrk=OFELZ)R;n_kEX8A|ukILBkRks5_^o3dKraJ+~eT3$oZHwoUnvtaLq*IS%APohLiy6`axB@tN^ zGE|!{Q9$TE8i0^iV90$^CJ`<)yfs1VNDO$*Ck9>m16~?MHQ2&gv?Fxy7?a)Updrgw z<6fI@eC$OH{&z#|dKPU450Ph{nq3*5^!5t0LOQq|P>ws=mp`94EYIkgXijTQ>{$4u z)8-j|dv~dAPt(=})@6wWZSTU6SkTkmT~zkLqo%2WU?#Pdv6K8`+9HIASdY|F(?gCA zq7#-NF|nu2MTvg)o&`ZKlq?0Ms(3v>A4%lG6Ct8JoCvDdx(*q@*J;F9DtJSAyfF(N zy?UR$nbRkLF3{BKbQW%Fs8Hz!y)~F$vCCKK*f>XEN$vip#r2J1a|IWEj-vCT!%-2- zkwm?o)RYR#`Y&1&Hnu)mZfcHoYsYBQ3xh+Y)SoPh9Su5cklwFa9xP~P z9xMbi4(c{r-EiOqk5Cb1t3YYV5};N zZ0oP(&`!_wG57uZZ|NuNzCVjA_dYF_elN{)O}(gX85tOE5G!$((n9s*G`ODA9Qb|a zhsye8)wF#-*ZX=nCUT-BX80I`#ZV>Jbh9;o0~D!Q(;#EX02<>yV!sbT5Jp^-zoThu zB+WrKBUQj4Mu75gFkG>Aq^l|4U3kFVwz@%Jt|jV-(p|`ckvT{AAgWs|Gr3Y*YS~CmG$qZMFHP4|8QSv!-n?6ptMNZhZUB*nWiQ0rX_)w>1HVbWc7y5yZWoN6%Y+DBMs zkF0WZ2!nl@raTTAd;tn%nl9DC;=LB?n+!-X?p#W%_lkM#-!CxOVbJQ&qkqr~R7?UPIXJsEUP zXw7T|PhHiV8Jd5~3Dm!O6}Y{PjR&0f&yPQg&l@9i__R3CABk*Vijbat+$U06n4+OR zVs-;=ZTa(HH+hvyxWjm?S=F zt%{trij@B@BVK~vPGqVT`C9tpje|dClgJp8C!cw#M#hy&m*44a-_KR@xh`#0VCS4BMIua} zSka)&D6#Xxu_l%zuxI$FH8%t-As;2V9=6p~a0$TKzL` z198%|-KN7O0UzkL$n(Vv_IcIC75^dAe-?Qb}ns*Z?@`)|0x;B0)mjMrD#&G8OSUtA|1c`BJZ)wIG+i6Cx3+&$V z90_7_CuN`g3T5IWxB?& z^Gp(-aN>*`k8t$sgK<*w^$0AtgI~!9a(J_=hjUnJZ`jG#h$i%(&It`rbwy>IDN+lCE)g$>g*x2QCB!A>P0 zbb4TTJww#jyLKuBL?*~xr!ot1J=80awbEpq-pr6x7M+r9a4QfCw*Fx(QmgloSp|D~HlE>fT^uh!Surg% z5}@#~c>QefCncBRexZ)PL|YFp6&Qg+Q0}CP?B?P%Hw#*%EwV7XDU>Qk-_GHd*1#Ko zhg}D9;I3CTlx+uYq^kA@64ioSLl#E9_!ad_QDWORXCf8cPa%ae^XEMoKX-9MpH+>6 zt_m78bD+$El&01UmqQL}#9|^3{!%xVB}2waKg;;^sO4pUlVnL2t7O_Gpzjg~ot-YB zIwHo{7#M1|{w+I^|J||JLtr-6dLA&Si`}1%OKz&xgAbrvPj`Msvw5Y(VHK2a2?{5A zE+|ms=%!@}fm2c^X*TDV2r;Jt#S+ur^k?gRsH0< zpH~1kAo(zEJ0t?Ujj{4toD--9+Ne(D)|D~9*CNnfVmnwkS>aYJiTdzXcT#1LumNLP z9Km8 zS9bX4sh{M^2G*`tIxtr^IIoo}%m&>##(~XULqO=x@t%JK802uf)!NK2tDDrkVe<{A z(dNx!X!#n_0#mN>q^i+(TTJSxynE63*&JpaNxdcsp8Rs~GJ?lC>3Mqpa6aRJF$>&q zoD}SFBb0K=yq#jD{;8cg=b&NGUQUu=YB~AVYXfqt4_-QaWTN37%MIfcwL}{?I6oqd zF-7q|uvfI!ZBZ6{dUf>CtHrMhJgh&~$^*atCHjU45Iu*etT~c2j= z_KOi62_FbEhr4r^MqLqJv;9=+gz<}8w{^7_!GP>3?>BA! zm(zM_eUr~5P7P%P0%3PWUwt{&>_MSq+mV*Z4ZosYiUR5KHFy{%|%RRiA zL&mt)PQyvk(*xkeNy5~hgoA9^#}Vr*6QTZh(hLDgZu7{1%B*mC0p|;&W}+v`I{5tR%gY%Ekm6|^7@}WUwx$DXvJrJHtVrg z8_2o(Dl*EfyS{d?5KFPn+UtN(l=wb-q!P>IWTCTqfSfA18Ax(*rlCpT56dS#Hj_Y+ zc=gtDvJ}khE`Ni1>_fcQ*e0h0z}%=9^HW}Zarb84inGuGNo8NqqkZhyWXI`u9f-f&MJ;$flh z?PdcvM?=@5-~XT;M2_4y;%doyv(QFe+t!v)lpJZNhiZINQ?F$PyzsRkAnm{-jZ7E% z=8LRsTv;hnLNURsv>gl*<;Gd@*eb&H4dfoN5U&f-5c-~2LtB|A%OA=)?DP{B$4?CM zr$e#uIg1;(_m3So9jNnb@YigRfl5t=cT*quFWmd^lSp0I(GP}f{q~HfT@Chbg8&SG zd=yL;NU*tGR!3%6=E$4{gRPvT zZw$f8X6WSCxmBz^tMa%H2G(DmkXayoWJ_*FwfZz&5;&13oi2|8(_%38^xmb9yv>Zr zM_cJfLY$Aym4&(hXe(jfAjkN{$X=%;(TwbW%Qb%Xcg5=*9h$NHB{Wx$`e**~_j9iE zHfTogm&jcOT`Ca{b w2%{6+FR3BDrv7)j`(69GYyGzYHm Date: Wed, 31 Jan 2024 09:00:04 +0700 Subject: [PATCH 6/7] update dict2xlsx --- cope2n-api/fwd_api/utils/file.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cope2n-api/fwd_api/utils/file.py b/cope2n-api/fwd_api/utils/file.py index 17d22ee..04eb5ce 100644 --- a/cope2n-api/fwd_api/utils/file.py +++ b/cope2n-api/fwd_api/utils/file.py @@ -363,9 +363,6 @@ def dict2xlsx(input: json, _type='report'): normal_cell = NamedStyle(name="normal_cell", font=font_black, border=border) normal_cell_red = NamedStyle(name="normal_cell_red", font=font_red, border=border) - wb = load_workbook(filename = 'report.xlsx') - ws = wb['Sheet1'] - if _type == 'report': wb = load_workbook(filename = 'report.xlsx') ws = wb['Sheet1'] @@ -409,7 +406,7 @@ def dict2xlsx(input: json, _type='report'): 'N': "ocr_processing_time", 'O': "is_reviewed", 'P': "bad_image_reasons", - 'R': "countermeasures", + 'Q': "countermeasures", } start_index = 4 From d17b1c741be02c315323c4c341ed4100bdd2ecec Mon Sep 17 00:00:00 2001 From: daovietanh99 Date: Wed, 31 Jan 2024 09:03:12 +0700 Subject: [PATCH 7/7] update report_detail.xlsx --- cope2n-api/report_detail.xlsx | Bin 6182 -> 6209 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/cope2n-api/report_detail.xlsx b/cope2n-api/report_detail.xlsx index 3df13d8d91d576538b183207c12c63de1f61f725..aa552cc235a564a0164573d7f340e3791b9ebb5c 100644 GIT binary patch delta 2094 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 TJ_g*4l>${|P@Atx{p9@v;s2M3