Merge branch 'main' of https://code.sdsdev.co.kr/SDSRV-IDP/sbt-idp into trungpt/invoice_no

This commit is contained in:
PhanThanhTrung 2024-03-13 13:23:50 +07:00
commit 565e2c52a6
24 changed files with 1024 additions and 234 deletions

View File

@ -241,6 +241,7 @@ BAD_THRESHOLD = 0.75
NEED_REVIEW = 1.0 NEED_REVIEW = 1.0
SUB_FOR_BILLING = ["all", "seao"] SUB_FOR_BILLING = ["all", "seao"]
FIELD = ["imei_number", "purchase_date", "retailername", "sold_to_party", "invoice_no"]
CACHES = { CACHES = {
'default': { 'default': {

View File

@ -284,6 +284,7 @@ class AccuracyViewSet(viewsets.ViewSet):
return JsonResponse(status=status.HTTP_200_OK, data={"report_id": report_id}) return JsonResponse(status=status.HTTP_200_OK, data={"report_id": report_id})
# Redundant, will be removed by 19 March 2024
@extend_schema( @extend_schema(
parameters=[ parameters=[
OpenApiParameter( OpenApiParameter(
@ -417,6 +418,9 @@ class AccuracyViewSet(viewsets.ViewSet):
acc[key] = report.combined_accuracy.get(key, 0) if report.combined_accuracy else max([fb, rv]) acc[key] = report.combined_accuracy.get(key, 0) if report.combined_accuracy else max([fb, rv])
else: else:
acc[key] = None acc[key] = None
processing_time = report.average_OCR_time.get("avg", None) if report.average_OCR_time else None
if processing_time and processing_time == 0:
processing_time = None
data.append({ data.append({
"ID": report.id, "ID": report.id,
"Created Date": report.created_at, "Created Date": report.created_at,
@ -429,7 +433,7 @@ class AccuracyViewSet(viewsets.ViewSet):
"IMEI Acc": acc["imei_number"], "IMEI Acc": acc["imei_number"],
"Avg. Accuracy": acc["avg"], "Avg. Accuracy": acc["avg"],
"Avg. Client Request Time": report.average_client_time.get("avg", 0) if report.average_client_time else 0, "Avg. Client Request Time": report.average_client_time.get("avg", 0) if report.average_client_time else 0,
"Avg. OCR Processing Time": report.average_OCR_time.get("avg", 0) if report.average_OCR_time else 0, "Avg. OCR Processing Time": processing_time,
"report_id": report.report_id, "report_id": report.report_id,
"Subsidiary": map_subsidiary_short_to_long(report.subsidiary), "Subsidiary": map_subsidiary_short_to_long(report.subsidiary),
}) })
@ -544,7 +548,7 @@ class AccuracyViewSet(viewsets.ViewSet):
for key in keys: for key in keys:
if report_fine_data[i][key]: if report_fine_data[i][key]:
for x_key in report_fine_data[i][key].keys(): for x_key in report_fine_data[i][key].keys():
report_fine_data[i][key][x_key] = report_fine_data[i][key][x_key]*100 report_fine_data[i][key][x_key] = report_fine_data[i][key][x_key]*100 if report_fine_data[i][key][x_key] is not None else None
overview_filename = _subsidiary + "_" + duration + ".xlsx" overview_filename = _subsidiary + "_" + duration + ".xlsx"
data_workbook = dict2xlsx(report_fine_data, _type='report') data_workbook = dict2xlsx(report_fine_data, _type='report')

View File

@ -144,6 +144,8 @@ def create_accuracy_report(report_id, **kwargs):
report.average_OCR_time = {"invoice": time_cost["invoice"](), "imei": time_cost["imei"](), report.average_OCR_time = {"invoice": time_cost["invoice"](), "imei": time_cost["imei"](),
"invoice_count": time_cost["invoice"].count, "imei_count": time_cost["imei"].count} "invoice_count": time_cost["invoice"].count, "imei_count": time_cost["imei"].count}
report.average_OCR_time["invoice"] = 0 if report.average_OCR_time["invoice"] is None else report.average_OCR_time["invoice"]
report.average_OCR_time["imei"] = 0 if report.average_OCR_time["imei"] is None else report.average_OCR_time["imei"]
report.average_OCR_time["avg"] = (report.average_OCR_time["invoice"]*report.average_OCR_time["invoice_count"] + report.average_OCR_time["imei"]*report.average_OCR_time["imei_count"])/( report.average_OCR_time["avg"] = (report.average_OCR_time["invoice"]*report.average_OCR_time["invoice_count"] + report.average_OCR_time["imei"]*report.average_OCR_time["imei_count"])/(
report.average_OCR_time["imei_count"] + report.average_OCR_time["invoice_count"]) if (report.average_OCR_time["imei_count"] + report.average_OCR_time["invoice_count"]) > 0 else None report.average_OCR_time["imei_count"] + report.average_OCR_time["invoice_count"]) if (report.average_OCR_time["imei_count"] + report.average_OCR_time["invoice_count"]) > 0 else None
report.number_imei_transaction = transaction_att.get("imei", 0) report.number_imei_transaction = transaction_att.get("imei", 0)

View File

@ -101,8 +101,8 @@ class Command(BaseCommand):
request.is_reviewed = False request.is_reviewed = False
request.save() request.save()
image.predict_result = _predict_result image.predict_result = _predict_result
image.feedback_result = _feedback_result # image.feedback_result = _feedback_result
image.reviewed_result = _reviewed_result # image.reviewed_result = _reviewed_result
image.save() image.save()
except Exception as e: except Exception as e:
self.stdout.write(self.style.ERROR(f"Request: {request.request_id} failed with {e}")) self.stdout.write(self.style.ERROR(f"Request: {request.request_id} failed with {e}"))

View File

@ -0,0 +1,73 @@
# myapp/management/commands/mycustomcommand.py
from django.core.management.base import BaseCommand
from tqdm import tqdm
from fwd_api.models import SubscriptionRequestFile, SubscriptionRequest
from fwd_api.exception.exceptions import InvalidException
from fwd_api.utils.accuracy import predict_result_to_ready
import traceback
import copy
from django.utils import timezone
class Command(BaseCommand):
help = 'Move predict result to image level'
def add_arguments(self, parser):
# Add your command-line arguments here
parser.add_argument('start', type=str, help='start date, sample: 2023-01-02T00:00:00+0700')
parser.add_argument('end', type=str, help='end date, sample: 2023-01-03T00:00:00+0700')
def process_request(self, request):
if len(request.request_id.split(".")[0].split("_")) < 2:
return
images = SubscriptionRequestFile.objects.filter(request=request)
time_cost = {"imei": [], "invoice": [], "all": []}
if request.ai_inference_profile is None:
time_cost["imei"] = [-1 for _ in range(len(images))]
time_cost["invoice"] = [-1]
time_cost["all"] = [-1]
else:
for k, v in request.ai_inference_profile.items():
time_cost[k.split("_")[0]].append(v["inference"][1][0] - v["inference"][0] + (v["postprocess"][1]-v["postprocess"][0]))
for i, image in enumerate(images):
try:
image.index_in_request = int(image.file_name.split(".")[0].split("_")[-1]) if len(image.file_name.split(".")[0].split("_")) > 4 else 0
image.doc_type = image.file_name.split(".")[0].split("_")[1] if len(image.file_name.split(".")[0].split("_")) > 4 else "all"
image.processing_time = time_cost[image.doc_type][image.index_in_request]
if not request.predict_result:
self.stdout.write(self.style.WARNING(f"Key predict_result not found in {request.request_id}"))
return
if request.predict_result.get("status", 200) != 200:
self.stdout.write(self.style.WARNING(f"Failed request: {request.request_id}"))
return
_predict_result = copy.deepcopy(predict_result_to_ready(request.predict_result))
if image.doc_type == "invoice":
_predict_result["imei_number"] = []
else:
_predict_result = {"retailername": None, "sold_to_party": None, "purchase_date": [], "imei_number": [_predict_result["imei_number"][image.index_in_request]]}
image.predict_result = _predict_result
image.save()
except Exception as e:
self.stdout.write(self.style.ERROR(f"Request: {request.request_id} failed with {e}"))
print(traceback.format_exc())
continue
def handle(self, *args, **options):
start = options['start']
end = options['end']
if start or end:
try:
start_date = timezone.datetime.strptime(start, '%Y-%m-%dT%H:%M:%S%z')
end_date = timezone.datetime.strptime(end, '%Y-%m-%dT%H:%M:%S%z')
except Exception as e:
print(f"[INFO]: start: {start}")
print(f"[INFO]: end: {end}")
raise InvalidException(excArgs="Date format")
subcription_iter = SubscriptionRequest.objects.filter(created_at__range=(start_date, end_date))
else:
subcription_iter = SubscriptionRequest.objects.all()
for request in tqdm(subcription_iter.iterator()):
self.process_request(request)
self.stdout.write(self.style.SUCCESS('Sample Django management command executed successfully!'))

View File

@ -84,7 +84,7 @@ class ReportAccumulateByRequest:
'bad_percent': 0 'bad_percent': 0
}, },
'average_accuracy_rate': { 'average_accuracy_rate': {
'imei': IterAvg(), 'imei_number': IterAvg(),
'purchase_date': IterAvg(), 'purchase_date': IterAvg(),
'retailer_name': IterAvg(), 'retailer_name': IterAvg(),
'sold_to_party': IterAvg(), 'sold_to_party': IterAvg(),
@ -133,22 +133,12 @@ class ReportAccumulateByRequest:
total["num_imei"] += 1 if doc_type == "imei" else 0 total["num_imei"] += 1 if doc_type == "imei" else 0
total["num_invoice"] += 1 if doc_type == "invoice" else 0 total["num_invoice"] += 1 if doc_type == "invoice" else 0
for key in settings.FIELD:
if sum([len(report_file.reviewed_accuracy[x]) for x in report_file.reviewed_accuracy.keys() if "_count" not in x]) > 0 : if sum([len(report_file.reviewed_accuracy[x]) for x in report_file.reviewed_accuracy.keys() if "_count" not in x]) > 0 :
total["average_accuracy_rate"]["imei"].add(report_file.reviewed_accuracy.get("imei_number", [])) total["average_accuracy_rate"][key].add(report_file.reviewed_accuracy.get(key, []))
total["average_accuracy_rate"]["purchase_date"].add(report_file.reviewed_accuracy.get("purchase_date", []))
total["average_accuracy_rate"]["retailer_name"].add(report_file.reviewed_accuracy.get("retailername", []))
total["average_accuracy_rate"]["sold_to_party"].add(report_file.reviewed_accuracy.get("sold_to_party", []))
total["average_accuracy_rate"]["invoice_no"].add(report_file.reviewed_accuracy.get("invoice_no", []))
elif sum([len(report_file.feedback_accuracy[x]) for x in report_file.feedback_accuracy.keys() if "_count" not in x]) > 0: elif sum([len(report_file.feedback_accuracy[x]) for x in report_file.feedback_accuracy.keys() if "_count" not in x]) > 0:
total["average_accuracy_rate"]["imei"].add(report_file.feedback_accuracy.get("imei_number", [])) total["average_accuracy_rate"][key].add(report_file.feedback_accuracy.get(key, []))
total["average_accuracy_rate"]["purchase_date"].add(report_file.feedback_accuracy.get("purchase_date", []))
total["average_accuracy_rate"]["retailer_name"].add(report_file.feedback_accuracy.get("retailername", []))
total["average_accuracy_rate"]["sold_to_party"].add(report_file.feedback_accuracy.get("sold_to_party", []))
total["average_accuracy_rate"]["invoice_no"].add(report_file.feedback_accuracy.get("invoice_no", []))
for key in ["imei_number", "purchase_date", "invoice_no", "retailername", "sold_to_party"]:
total["feedback_accuracy"][key].add(report_file.feedback_accuracy.get(key, [])) total["feedback_accuracy"][key].add(report_file.feedback_accuracy.get(key, []))
for key in ["imei_number", "purchase_date", "invoice_no", "retailername", "sold_to_party"]:
total["reviewed_accuracy"][key].add(report_file.reviewed_accuracy.get(key, [])) total["reviewed_accuracy"][key].add(report_file.reviewed_accuracy.get(key, []))
if not total["average_processing_time"].get(report_file.doc_type, None): if not total["average_processing_time"].get(report_file.doc_type, None):
@ -182,22 +172,12 @@ class ReportAccumulateByRequest:
day_data["num_invoice"] += 1 if doc_type == "invoice" else 0 day_data["num_invoice"] += 1 if doc_type == "invoice" else 0
day_data["report_files"].append(report_file) day_data["report_files"].append(report_file)
for key in settings.FIELD:
if sum([len(report_file.reviewed_accuracy[x]) for x in report_file.reviewed_accuracy.keys() if "_count" not in x]) > 0: if sum([len(report_file.reviewed_accuracy[x]) for x in report_file.reviewed_accuracy.keys() if "_count" not in x]) > 0:
day_data["average_accuracy_rate"]["imei"].add(report_file.reviewed_accuracy.get("imei_number", [])) day_data["average_accuracy_rate"][key].add(report_file.reviewed_accuracy.get(key, []))
day_data["average_accuracy_rate"]["purchase_date"].add(report_file.reviewed_accuracy.get("purchase_date", []))
day_data["average_accuracy_rate"]["retailer_name"].add(report_file.reviewed_accuracy.get("retailername", []))
day_data["average_accuracy_rate"]["sold_to_party"].add(report_file.reviewed_accuracy.get("sold_to_party", []))
day_data["average_accuracy_rate"]["invoice_no"].add(report_file.reviewed_accuracy.get("invoice_no", []))
elif sum([len(report_file.feedback_accuracy[x]) for x in report_file.feedback_accuracy.keys() if "_count" not in x]) > 0: elif sum([len(report_file.feedback_accuracy[x]) for x in report_file.feedback_accuracy.keys() if "_count" not in x]) > 0:
day_data["average_accuracy_rate"]["imei"].add(report_file.feedback_accuracy.get("imei_number", [])) day_data["average_accuracy_rate"][key].add(report_file.feedback_accuracy.get(key, []))
day_data["average_accuracy_rate"]["purchase_date"].add(report_file.feedback_accuracy.get("purchase_date", []))
day_data["average_accuracy_rate"]["retailer_name"].add(report_file.feedback_accuracy.get("retailername", []))
day_data["average_accuracy_rate"]["sold_to_party"].add(report_file.feedback_accuracy.get("sold_to_party", []))
day_data["average_accuracy_rate"]["invoice_no"].add(report_file.feedback_accuracy.get("invoice_no", []))
for key in ["imei_number", "purchase_date", "invoice_no", "retailername", "sold_to_party"]:
day_data["feedback_accuracy"][key].add(report_file.feedback_accuracy.get(key, [])) day_data["feedback_accuracy"][key].add(report_file.feedback_accuracy.get(key, []))
for key in ["imei_number", "purchase_date", "invoice_no", "retailername", "sold_to_party"]:
day_data["reviewed_accuracy"][key].add(report_file.reviewed_accuracy.get(key, [])) day_data["reviewed_accuracy"][key].add(report_file.reviewed_accuracy.get(key, []))
if not day_data["average_processing_time"].get(report_file.doc_type, None): if not day_data["average_processing_time"].get(report_file.doc_type, None):
@ -274,7 +254,7 @@ class ReportAccumulateByRequest:
"reviewed_accuracy": {}} "reviewed_accuracy": {}}
for acc_type in ["feedback_accuracy", "reviewed_accuracy"]: for acc_type in ["feedback_accuracy", "reviewed_accuracy"]:
avg_acc = IterAvg() avg_acc = IterAvg()
for key in ["imei_number", "purchase_date", "invoice_no", "retailername", "sold_to_party"]: for key in settings.FIELD:
acumulated_acc[acc_type][key] = self.data[month][1][day][acc_type][key]() acumulated_acc[acc_type][key] = self.data[month][1][day][acc_type][key]()
acumulated_acc[acc_type][key+"_count"] = self.data[month][1][day][acc_type][key].count acumulated_acc[acc_type][key+"_count"] = self.data[month][1][day][acc_type][key].count
avg_acc.add_avg(acumulated_acc[acc_type][key], acumulated_acc[acc_type][key+"_count"]) avg_acc.add_avg(acumulated_acc[acc_type][key], acumulated_acc[acc_type][key+"_count"])
@ -318,26 +298,13 @@ class ReportAccumulateByRequest:
for day in _data[month][1].keys(): for day in _data[month][1].keys():
num_transaction_imei += _data[month][1][day]["usage"].get("imei", 0) num_transaction_imei += _data[month][1][day]["usage"].get("imei", 0)
num_transaction_invoice += _data[month][1][day]["usage"].get("invoice", 0) num_transaction_invoice += _data[month][1][day]["usage"].get("invoice", 0)
_data[month][1][day]["average_accuracy_rate"]["imei"] = _data[month][1][day]["average_accuracy_rate"]["imei"]()
_data[month][1][day]["average_accuracy_rate"]["purchase_date"] = _data[month][1][day]["average_accuracy_rate"]["purchase_date"]()
_data[month][1][day]["average_accuracy_rate"]["retailer_name"] = _data[month][1][day]["average_accuracy_rate"]["retailer_name"]()
_data[month][1][day]["average_accuracy_rate"]["sold_to_party"] = _data[month][1][day]["average_accuracy_rate"]["sold_to_party"]()
_data[month][1][day]["average_accuracy_rate"]["invoice_no"] = _data[month][1][day]["average_accuracy_rate"]["invoice_no"]()
for key in _data[month][1][day]["average_processing_time"].keys(): for key in _data[month][1][day]["average_processing_time"].keys():
_data[month][1][day]["average_processing_time"][key] = _data[month][1][day]["average_processing_time"][key]() _data[month][1][day]["average_processing_time"][key] = _data[month][1][day]["average_processing_time"][key]()
_data[month][1][day]["feedback_accuracy"]["imei_number"] = _data[month][1][day]["feedback_accuracy"]["imei_number"]() for key in settings.FIELD:
_data[month][1][day]["feedback_accuracy"]["purchase_date"] = _data[month][1][day]["feedback_accuracy"]["purchase_date"]() _data[month][1][day]["average_accuracy_rate"][key] = _data[month][1][day]["average_accuracy_rate"][key]()
_data[month][1][day]["feedback_accuracy"]["retailername"] = _data[month][1][day]["feedback_accuracy"]["retailername"]() for accuracy_type in ["feedback_accuracy", key]:
_data[month][1][day]["feedback_accuracy"]["sold_to_party"] = _data[month][1][day]["feedback_accuracy"]["sold_to_party"]() _data[month][1][day][accuracy_type]["imei_number"] = _data[month][1][day]["feedback_accuracy"]["imei_number"]()
_data[month][1][day]["feedback_accuracy"]["invoice_no"] = _data[month][1][day]["feedback_accuracy"]["invoice_no"]()
_data[month][1][day]["reviewed_accuracy"]["imei_number"] = _data[month][1][day]["reviewed_accuracy"]["imei_number"]()
_data[month][1][day]["reviewed_accuracy"]["purchase_date"] = _data[month][1][day]["reviewed_accuracy"]["purchase_date"]()
_data[month][1][day]["reviewed_accuracy"]["retailername"] = _data[month][1][day]["reviewed_accuracy"]["retailername"]()
_data[month][1][day]["reviewed_accuracy"]["sold_to_party"] = _data[month][1][day]["reviewed_accuracy"]["sold_to_party"]()
_data[month][1][day]["reviewed_accuracy"]["invoice_no"] = _data[month][1][day]["reviewed_accuracy"]["invoice_no"]()
_data[month][1][day]["review_progress"] = _data[month][1][day]["review_progress"].count(1)/(_data[month][1][day]["review_progress"].count(0)+ _data[month][1][day]["review_progress"].count(1)) if (_data[month][1][day]["review_progress"].count(0)+ _data[month][1][day]["review_progress"].count(1)) >0 else 0 _data[month][1][day]["review_progress"] = _data[month][1][day]["review_progress"].count(1)/(_data[month][1][day]["review_progress"].count(0)+ _data[month][1][day]["review_progress"].count(1)) if (_data[month][1][day]["review_progress"].count(0)+ _data[month][1][day]["review_progress"].count(1)) >0 else 0
_data[month][1][day].pop("report_files") _data[month][1][day].pop("report_files")
@ -347,28 +314,13 @@ class ReportAccumulateByRequest:
_data[month][0]["usage"]["imei"] = num_transaction_imei _data[month][0]["usage"]["imei"] = num_transaction_imei
_data[month][0]["usage"]["invoice"] = num_transaction_invoice _data[month][0]["usage"]["invoice"] = num_transaction_invoice
_data[month][0]["usage"]["total_images"] = num_transaction_invoice + num_transaction_imei _data[month][0]["usage"]["total_images"] = num_transaction_invoice + num_transaction_imei
_data[month][0]["average_accuracy_rate"]["imei"] = _data[month][0]["average_accuracy_rate"]["imei"]()
_data[month][0]["average_accuracy_rate"]["purchase_date"] = _data[month][0]["average_accuracy_rate"]["purchase_date"]()
_data[month][0]["average_accuracy_rate"]["retailer_name"] = _data[month][0]["average_accuracy_rate"]["retailer_name"]()
_data[month][0]["average_accuracy_rate"]["sold_to_party"] = _data[month][0]["average_accuracy_rate"]["sold_to_party"]()
_data[month][0]["average_accuracy_rate"]["invoice_no"] = _data[month][0]["average_accuracy_rate"]["invoice_no"]()
for key in _data[month][0]["average_processing_time"].keys(): for key in _data[month][0]["average_processing_time"].keys():
_data[month][0]["average_processing_time"][key] = _data[month][0]["average_processing_time"][key]() _data[month][0]["average_processing_time"][key] = _data[month][0]["average_processing_time"][key]()
for key in settings.FIELD:
_data[month][0]["feedback_accuracy"]["imei_number"] = _data[month][0]["feedback_accuracy"]["imei_number"]() _data[month][0]["average_accuracy_rate"][key] = _data[month][0]["average_accuracy_rate"][key]()
_data[month][0]["feedback_accuracy"]["purchase_date"] = _data[month][0]["feedback_accuracy"]["purchase_date"]() for accuracy_type in ["feedback_accuracy", key]:
_data[month][0]["feedback_accuracy"]["retailername"] = _data[month][0]["feedback_accuracy"]["retailername"]() _data[month][0][accuracy_type][key] = _data[month][0][accuracy_type][key]()
_data[month][0]["feedback_accuracy"]["sold_to_party"] = _data[month][0]["feedback_accuracy"]["sold_to_party"]()
_data[month][0]["feedback_accuracy"]["invoice_no"] = _data[month][0]["feedback_accuracy"]["invoice_no"]()
_data[month][0]["reviewed_accuracy"]["imei_number"] = _data[month][0]["reviewed_accuracy"]["imei_number"]()
_data[month][0]["reviewed_accuracy"]["purchase_date"] = _data[month][0]["reviewed_accuracy"]["purchase_date"]()
_data[month][0]["reviewed_accuracy"]["retailername"] = _data[month][0]["reviewed_accuracy"]["retailername"]()
_data[month][0]["reviewed_accuracy"]["sold_to_party"] = _data[month][0]["reviewed_accuracy"]["sold_to_party"]()
_data[month][0]["reviewed_accuracy"]["invoice_no"] = _data[month][0]["reviewed_accuracy"]["invoice_no"]()
_data[month][0]["review_progress"] = _data[month][0]["review_progress"].count(1)/(_data[month][0]["review_progress"].count(0)+ _data[month][0]["review_progress"].count(1)) if (_data[month][0]["review_progress"].count(0)+ _data[month][0]["review_progress"].count(1)) >0 else 0 _data[month][0]["review_progress"] = _data[month][0]["review_progress"].count(1)/(_data[month][0]["review_progress"].count(0)+ _data[month][0]["review_progress"].count(1)) if (_data[month][0]["review_progress"].count(0)+ _data[month][0]["review_progress"].count(1)) >0 else 0
return _data return _data
class MonthReportAccumulate: class MonthReportAccumulate:
@ -580,6 +532,7 @@ def first_of_list(the_list):
def extract_report_detail_list(report_detail_list, lower=False, in_percent=True): def extract_report_detail_list(report_detail_list, lower=False, in_percent=True):
data = [] data = []
for report_file in report_detail_list: for report_file in report_detail_list:
# FIXME: #79 Fill None with value
data.append({ data.append({
"Subs": report_file.subsidiary, "Subs": report_file.subsidiary,
"Request ID": report_file.correspond_request_id, "Request ID": report_file.correspond_request_id,
@ -587,12 +540,19 @@ def extract_report_detail_list(report_detail_list, lower=False, in_percent=True)
"Image type": report_file.doc_type, "Image type": report_file.doc_type,
"IMEI_user submitted": first_of_list(report_file.feedback_result.get("imei_number", [None])), "IMEI_user submitted": first_of_list(report_file.feedback_result.get("imei_number", [None])),
"IMEI_OCR retrieved": first_of_list(report_file.predict_result.get("imei_number", [None])), "IMEI_OCR retrieved": first_of_list(report_file.predict_result.get("imei_number", [None])),
"IMEI Revised": None,
"IMEI1 Accuracy": first_of_list(report_file.feedback_accuracy.get("imei_number", [None])), "IMEI1 Accuracy": first_of_list(report_file.feedback_accuracy.get("imei_number", [None])),
"Invoice_Number_User": None,
"Invoice_Number_OCR": None,
"Invoice_Number Revised": None,
"Invoice_Number_Accuracy": None,
"Invoice_Purchase Date_Consumer": report_file.feedback_result.get("purchase_date", None), "Invoice_Purchase Date_Consumer": report_file.feedback_result.get("purchase_date", None),
"Invoice_Purchase Date_OCR": report_file.predict_result.get("purchase_date", []), "Invoice_Purchase Date_OCR": report_file.predict_result.get("purchase_date", []),
"Invoice_Purchase Date Revised": None,
"Invoice_Purchase Date Accuracy": first_of_list(report_file.feedback_accuracy.get("purchase_date", [None])), "Invoice_Purchase Date Accuracy": first_of_list(report_file.feedback_accuracy.get("purchase_date", [None])),
"Invoice_Retailer_Consumer": report_file.feedback_result.get("retailername", None), "Invoice_Retailer_Consumer": report_file.feedback_result.get("retailername", None),
"Invoice_Retailer_OCR": report_file.predict_result.get("retailername", None), "Invoice_Retailer_OCR": report_file.predict_result.get("retailername", None),
"Invoice_Purchase Date Revised": None,
"Invoice_Retailer Accuracy": first_of_list(report_file.feedback_accuracy.get("retailername", [None])), "Invoice_Retailer Accuracy": first_of_list(report_file.feedback_accuracy.get("retailername", [None])),
"Invoice_No_Consumer": report_file.feedback_result.get("invoice_no", None), "Invoice_No_Consumer": report_file.feedback_result.get("invoice_no", None),
"Invoice_No_OCR": report_file.predict_result.get("invoice_no", None), "Invoice_No_OCR": report_file.predict_result.get("invoice_no", None),
@ -811,6 +771,12 @@ def create_billing_data(subscription_requests):
return billing_data return billing_data
def calculate_a_request(report, request): def calculate_a_request(report, request):
def review_status_map(input):
review_status = {-1: "Not Required",
0: "No",
1: "Yes"}
return review_status.get(input, "N/A")
request_att = {"acc": {"feedback": {"imei_number": [], request_att = {"acc": {"feedback": {"imei_number": [],
"purchase_date": [], "purchase_date": [],
"retailername": [], "retailername": [],
@ -871,8 +837,8 @@ def calculate_a_request(report, request):
if len(att["normalized_data"]["reviewed"].get("purchase_date", [])) > 0: if len(att["normalized_data"]["reviewed"].get("purchase_date", [])) > 0:
image.predict_result["purchase_date"] = [att["normalized_data"]["reviewed"]["purchase_date"][i][0] for i in range(len(att["normalized_data"]["reviewed"]["purchase_date"]))] image.predict_result["purchase_date"] = [att["normalized_data"]["reviewed"]["purchase_date"][i][0] for i in range(len(att["normalized_data"]["reviewed"]["purchase_date"]))]
image.reviewed_result["purchase_date"] = att["normalized_data"]["reviewed"]["purchase_date"][rv_max_indexes["purchase_date"]][1] image.reviewed_result["purchase_date"] = att["normalized_data"]["reviewed"]["purchase_date"][rv_max_indexes["purchase_date"]][1]
if request.is_reviewed: # if request.is_reviewed:
att["is_reviewed"] = 1 # att["is_reviewed"] = 1
request_att["is_reviewed"].append(att["is_reviewed"]) request_att["is_reviewed"].append(att["is_reviewed"])
new_report_file = ReportFile(report=report, new_report_file = ReportFile(report=report,
subsidiary=_sub, subsidiary=_sub,
@ -886,7 +852,7 @@ def calculate_a_request(report, request):
reviewed_accuracy=att["acc"]["reviewed"], reviewed_accuracy=att["acc"]["reviewed"],
acc=att["avg_acc"], acc=att["avg_acc"],
is_bad_image=att["is_bad_image"], is_bad_image=att["is_bad_image"],
is_reviewed= "Yes" if request.is_reviewed else "No", is_reviewed= review_status_map(att["is_reviewed"]),
time_cost=image.processing_time, time_cost=image.processing_time,
bad_image_reason=image.reason, bad_image_reason=image.reason,
counter_measures=image.counter_measures, counter_measures=image.counter_measures,
@ -979,6 +945,10 @@ def calculate_subcription_file(subcription_request_file):
avg_acc = avg_reviewed avg_acc = avg_reviewed
att["is_reviewed"] = 1 att["is_reviewed"] = 1
# Little trick to overcome issue caused by misleading manually review process
if subcription_request_file.reason or subcription_request_file.counter_measures:
att["is_reviewed"] = 1
att["avg_acc"] = avg_acc att["avg_acc"] = avg_acc
if avg_acc < settings.BAD_THRESHOLD: if avg_acc < settings.BAD_THRESHOLD:
att["is_bad_image"] = True att["is_bad_image"] = True

View File

@ -487,9 +487,10 @@ def dict2xlsx(input: json, _type='report'):
'M': 'average_accuracy_rate.imei', 'M': 'average_accuracy_rate.imei',
'N': 'average_accuracy_rate.purchase_date', 'N': 'average_accuracy_rate.purchase_date',
'O': 'average_accuracy_rate.retailer_name', 'O': 'average_accuracy_rate.retailer_name',
'P': 'average_processing_time.imei', 'P': 'average_accuracy_rate.invoice_number',
'Q': 'average_processing_time.invoice', 'Q': 'average_processing_time.imei',
'R': 'review_progress' 'R': 'average_processing_time.invoice',
'S': 'review_progress'
} }
start_index = 5 start_index = 5
@ -503,21 +504,29 @@ def dict2xlsx(input: json, _type='report'):
'D': 'image_type', 'D': 'image_type',
'E': 'imei_user_submitted', 'E': 'imei_user_submitted',
'F': "imei_ocr_retrieved", 'F': "imei_ocr_retrieved",
'G': "imei1_accuracy", 'G': "imei_revised",
'H': "invoice_purchase_date_consumer", 'H': "imei1_accuracy",
'I': "invoice_purchase_date_ocr", 'I': "invoice_number_user",
'J': "invoice_purchase_date_accuracy", 'J': "invoice_number_ocr",
'K': "invoice_retailer_consumer", 'K': "invoice_number_revised",
'L': "invoice_retailer_ocr", 'L': "invoice_number_accuracy",
'M': "invoice_retailer_accuracy", 'M': "invoice_purchase_date_consumer",
'N': "ocr_image_accuracy", 'N': "invoice_purchase_date_ocr",
'O': "ocr_image_speed_(seconds)", 'O': "invoice_purchase_date_revised",
'P': "is_reviewed", 'P': "invoice_purchase_date_accuracy",
'Q': "bad_image_reasons", 'Q': "invoice_retailer_consumer",
'R': "countermeasures", 'R': "invoice_retailer_ocr",
'S': 'imei_revised_accuracy', 'S': 'invoice_retailer_revised',
'T': 'purchase_date_revised_accuracy', 'T': "invoice_retailer_accuracy",
'U': 'retailer_revised_accuracy', 'U': "ocr_image_accuracy",
'V': "ocr_image_speed_(seconds)",
'W': "is_reviewed",
'X': "bad_image_reasons",
'Y': "countermeasures",
'Z': "imei_revised_accuracy",
'AA': "invoice_number_revised_accuracy",
'AB': 'purchase_date_revised_accuracy',
'AC': 'retailer_revised_accuracy',
} }
start_index = 4 start_index = 4

Binary file not shown.

Binary file not shown.

View File

@ -1,3 +1,3 @@
VITE_PORT=8080 VITE_PORT=8080
VITE_PROXY=https://107.120.133.22/ VITE_PROXY=http://42.96.42.13:9881
VITE_KUBEFLOW_HOST=https://107.120.133.22:8085 VITE_KUBEFLOW_HOST=https://107.120.133.22:8085

View File

@ -1,7 +1,7 @@
import type { TableColumnsType } from 'antd'; import type { TableColumnsType } from 'antd';
import { Table } from 'antd'; import { Table } from 'antd';
import React from 'react'; import React from 'react';
import { ensureMax, ensureMin, formatPercent } from 'utils/metric-format'; import { ensureMax, ensureMin, formatPercent, formatNumber } from 'utils/metric-format';
interface DataType { interface DataType {
key: React.Key; key: React.Key;
@ -224,7 +224,7 @@ const columns: TableColumnsType<DataType> = [
const isAbnormal = ensureMax(record.snImeiAPT, 2); const isAbnormal = ensureMax(record.snImeiAPT, 2);
return ( return (
<span style={{ color: isAbnormal ? 'red' : '' }}> <span style={{ color: isAbnormal ? 'red' : '' }}>
{record?.snImeiAPT?.toFixed(1)} {formatNumber(record?.snImeiAPT)}
</span> </span>
); );
}, },
@ -237,7 +237,7 @@ const columns: TableColumnsType<DataType> = [
const isAbnormal = ensureMax(record.invoiceAPT, 2); const isAbnormal = ensureMax(record.invoiceAPT, 2);
return ( return (
<span style={{ color: isAbnormal ? 'red' : '' }}> <span style={{ color: isAbnormal ? 'red' : '' }}>
{record?.invoiceAPT?.toFixed(1)} {formatNumber(record?.invoiceAPT)}
</span> </span>
); );
}, },

View File

@ -5,7 +5,7 @@ import { useReportList } from 'queries/report';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { downloadReport } from 'request/report'; import { downloadReport } from 'request/report';
import { formatPercent, ensureMin, ensureMax } from 'utils/metric-format'; import { formatPercent, ensureMin, ensureMax, formatNumber } from 'utils/metric-format';
import { datetimeStrToDate } from 'utils/time'; import { datetimeStrToDate } from 'utils/time';
@ -125,8 +125,7 @@ const ReportTable: React.FC = () => {
const isAbnormal = ensureMin(record['IMEI Acc'], 0.95); const isAbnormal = ensureMin(record['IMEI Acc'], 0.95);
return ( return (
<span style={{ color: isAbnormal ? 'red' : '' }}> <span style={{ color: isAbnormal ? 'red' : '' }}>
{record['IMEI Acc'] && {formatPercent(record['IMEI Acc'])}
formatPercent(Number(record['IMEI Acc']))}
</span> </span>
); );
}, },
@ -139,22 +138,20 @@ const ReportTable: React.FC = () => {
const isAbnormal = ensureMin(record['Avg. Accuracy'], 0.95); const isAbnormal = ensureMin(record['Avg. Accuracy'], 0.95);
return ( return (
<span style={{ color: isAbnormal ? 'red' : '' }}> <span style={{ color: isAbnormal ? 'red' : '' }}>
{record['Avg. Accuracy'] && {formatPercent(record['Avg. Accuracy'])}
formatPercent(Number(record['Avg. Accuracy']))}
</span> </span>
); );
}, },
}, },
{ {
title: 'Avg. OCR Processing Time', title: 'Avg. OCR Processing Time (ms)',
dataIndex: 'Avg. OCR Processing Time', dataIndex: 'Avg. OCR Processing Time',
key: 'Avg. OCR Processing Time', key: 'Avg. OCR Processing Time',
render: (_, record) => { render: (_, record) => {
const isAbnormal = ensureMax(record['Avg. OCR Processing Time'], 2); const isAbnormal = ensureMax(record['Avg. OCR Processing Time'], 2);
return ( return (
<span style={{ color: isAbnormal ? 'red' : '' }}> <span style={{ color: isAbnormal ? 'red' : '' }}>
{record['Avg. OCR Processing Time'] && {formatNumber(record['Avg. OCR Processing Time'], 1)}
Number(record['Avg. OCR Processing Time'])?.toFixed(2)}
</span> </span>
); );
}, },

View File

@ -11,11 +11,14 @@
"Email format is not correct": "Email format is not correct", "Email format is not correct": "Email format is not correct",
"English": "English", "English": "English",
"Go to Reports": "Go to Reports", "Go to Reports": "Go to Reports",
"Handwritten": "Handwritten",
"Inference": "Inference", "Inference": "Inference",
"Invalid image": "Invalid image",
"Is Test": "Is Test", "Is Test": "Is Test",
"Language": "Language", "Language": "Language",
"Login": "Login", "Login": "Login",
"Logout": "Logout", "Logout": "Logout",
"Missing information": "Missing information",
"New Report": "New Report", "New Report": "New Report",
"Only characters (a-z), (A-Z), (0-9), @, ., +, -, _ are available": "Only characters (a-z), (A-Z), (0-9), @, ., +, -, _ are available", "Only characters (a-z), (A-Z), (0-9), @, ., +, -, _ are available": "Only characters (a-z), (A-Z), (0-9), @, ., +, -, _ are available",
"Password": "Password", "Password": "Password",
@ -28,8 +31,11 @@
"Please enter a valid domain": "Please enter a valid domain", "Please enter a valid domain": "Please enter a valid domain",
"Please specify a password": "Please specify a password", "Please specify a password": "Please specify a password",
"Please specify a username": "Please specify a username", "Please specify a username": "Please specify a username",
"Reason for bad quality:": "Reason for bad quality:",
"Recheck": "Recheck",
"Report Details": "Report Details", "Report Details": "Report Details",
"Report Filters": "Report Filters", "Report Filters": "Report Filters",
"Report Type": "Report Type",
"Reports": "Reports", "Reports": "Reports",
"Retry": "Retry", "Retry": "Retry",
"Review": "Review", "Review": "Review",
@ -43,6 +49,8 @@
"This field must not have more than {MAX_EMAIL_LENGTH} characters": "This field must not have more than {MAX_EMAIL_LENGTH} characters", "This field must not have more than {MAX_EMAIL_LENGTH} characters": "This field must not have more than {MAX_EMAIL_LENGTH} characters",
"This field must not have more than {MAX_STRING_LENGTH} characters": "This field must not have more than {MAX_STRING_LENGTH} characters", "This field must not have more than {MAX_STRING_LENGTH} characters": "This field must not have more than {MAX_STRING_LENGTH} characters",
"This field must not have more than {MAX_USERNAME_LENGTH} characters": "This field must not have more than {MAX_USERNAME_LENGTH} characters", "This field must not have more than {MAX_USERNAME_LENGTH} characters": "This field must not have more than {MAX_USERNAME_LENGTH} characters",
"Too blurry text": "Too blurry text",
"Too small text": "Too small text",
"Upload files to process. The requests here will not be used in accuracy or payment calculations.": "Upload files to process. The requests here will not be used in accuracy or payment calculations.", "Upload files to process. The requests here will not be used in accuracy or payment calculations.": "Upload files to process. The requests here will not be used in accuracy or payment calculations.",
"User log in successfully": "User log in successfully", "User log in successfully": "User log in successfully",
"Username": "Username", "Username": "Username",

View File

@ -11,11 +11,14 @@
"Email format is not correct": "Định dạng email không hợp lệ", "Email format is not correct": "Định dạng email không hợp lệ",
"English": "Tiếng Anh", "English": "Tiếng Anh",
"Go to Reports": "", "Go to Reports": "",
"Handwritten": "",
"Inference": "", "Inference": "",
"Invalid image": "",
"Is Test": "", "Is Test": "",
"Language": "Ngôn ngữ", "Language": "Ngôn ngữ",
"Login": "Đăng nhập", "Login": "Đăng nhập",
"Logout": "Đăng xuất", "Logout": "Đăng xuất",
"Missing information": "",
"New Report": "", "New Report": "",
"Only characters (a-z), (A-Z), (0-9), @, ., +, -, _ are available": "Chỉ cho phép các ký tự (a-z), (A-Z), (0-9), @, ., +, -, _", "Only characters (a-z), (A-Z), (0-9), @, ., +, -, _ are available": "Chỉ cho phép các ký tự (a-z), (A-Z), (0-9), @, ., +, -, _",
"Password": "Mật khẩu", "Password": "Mật khẩu",
@ -28,8 +31,11 @@
"Please enter a valid domain": "Vui lòng nhập một tên miền hợp lệ", "Please enter a valid domain": "Vui lòng nhập một tên miền hợp lệ",
"Please specify a password": "Vui lòng nhập một mật khẩu", "Please specify a password": "Vui lòng nhập một mật khẩu",
"Please specify a username": "Vui lòng nhập một tên tài khoản", "Please specify a username": "Vui lòng nhập một tên tài khoản",
"Reason for bad quality:": "",
"Recheck": "",
"Report Details": "", "Report Details": "",
"Report Filters": "", "Report Filters": "",
"Report Type": "",
"Reports": "", "Reports": "",
"Retry": "Thử lại", "Retry": "Thử lại",
"Review": "", "Review": "",
@ -43,6 +49,8 @@
"This field must not have more than {MAX_EMAIL_LENGTH} characters": "Độ dài chuỗi không được vượt quá {MAX_EMAIL_LENGTH} kí tự", "This field must not have more than {MAX_EMAIL_LENGTH} characters": "Độ dài chuỗi không được vượt quá {MAX_EMAIL_LENGTH} kí tự",
"This field must not have more than {MAX_STRING_LENGTH} characters": "Độ dài chuỗi không được vượt quá {MAX_STRING_LENGTH} kí tự", "This field must not have more than {MAX_STRING_LENGTH} characters": "Độ dài chuỗi không được vượt quá {MAX_STRING_LENGTH} kí tự",
"This field must not have more than {MAX_USERNAME_LENGTH} characters": "Độ dài chuỗi không được vượt quá {MAX_USERNAME_LENGTH} kí tự", "This field must not have more than {MAX_USERNAME_LENGTH} characters": "Độ dài chuỗi không được vượt quá {MAX_USERNAME_LENGTH} kí tự",
"Too blurry text": "",
"Too small text": "",
"Upload files to process. The requests here will not be used in accuracy or payment calculations.": "", "Upload files to process. The requests here will not be used in accuracy or payment calculations.": "",
"User log in successfully": "Đăng nhập thành công", "User log in successfully": "Đăng nhập thành công",
"Username": "Tên tài khoản", "Username": "Tên tài khoản",

View File

@ -65,6 +65,7 @@ export interface MakeReportParams {
start_date: string; start_date: string;
end_date: string; end_date: string;
subsidiary: string; subsidiary: string;
report_type: string;
} }
export type CustomUseMutationOptions< export type CustomUseMutationOptions<

View File

@ -1,21 +1,219 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Button, message, Upload } from 'antd'; import { Button, Input, Table, Tag, DatePicker, Form, Modal, Select, Spin, message, Upload } from 'antd';
import React, { useContext, useEffect, useRef, useState } from 'react';
import type { GetRef } from 'antd';
import { Layout } from 'antd';
import {
DownloadOutlined, CheckCircleOutlined,
ClockCircleFilled,
} from '@ant-design/icons';
import styled from 'styled-components';
const { Sider, Content } = Layout;
import { baseURL } from "request/api";
import { Viewer } from '@react-pdf-viewer/core';
import { SbtPageHeader } from 'components/page-header'; import { SbtPageHeader } from 'components/page-header';
import { useState } from 'react';
import { UploadOutlined } from '@ant-design/icons'; import { UploadOutlined } from '@ant-design/icons';
import type { GetProp, UploadFile, UploadProps } from 'antd'; import type { GetProp, UploadFile, UploadProps } from 'antd';
import { JsonView, allExpanded, defaultStyles } from 'react-json-view-lite';
import 'react-json-view-lite/dist/index.css'; import 'react-json-view-lite/dist/index.css';
import { baseURL } from "request/api"
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0]; type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
const ENABLE_REVIEW = true;
// Import the styles
import '@react-pdf-viewer/core/lib/styles/index.css';
const siderStyle: React.CSSProperties = {
backgroundColor: '#fafafa',
padding: 10,
width: 200,
};
const StyledTable = styled(Table)`
& .sbt-table-cell {
padding: 4px!important;
}
`;
type InputRef = GetRef<typeof Input>;
type FormInstance<T> = GetRef<typeof Form<T>>;
const EditableContext = React.createContext<FormInstance<any> | null>(null);
interface Item {
key: string;
accuracy: number;
revised: string;
action: string;
}
interface EditableRowProps {
index: number;
}
const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => {
const [form] = Form.useForm();
return (
<Form form={form} component={false}>
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
</Form>
);
};
interface EditableCellProps {
title: React.ReactNode;
editable: boolean;
children: React.ReactNode;
dataIndex: keyof Item;
record: Item;
handleSave: (record: Item) => void;
}
const EditableCell: React.FC<EditableCellProps> = ({
title,
editable,
children,
dataIndex,
record,
handleSave,
...restProps
}) => {
const [editing, setEditing] = useState(false);
const inputRef = useRef<InputRef>(null);
const form = useContext(EditableContext)!;
useEffect(() => {
if (editing) {
inputRef.current!.focus();
}
}, [editing]);
const toggleEdit = () => {
setEditing(!editing);
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
};
const save = async () => {
try {
const values = await form.validateFields();
toggleEdit();
handleSave({ ...record, ...values });
} catch (errInfo) {
console.log('Save failed:', errInfo);
}
};
let childNode = children;
if (editable) {
childNode = editing ? (
<Form.Item
style={{ margin: 0 }}
name={dataIndex}
>
<Input ref={inputRef} onPressEnter={save} onBlur={save} />
</Form.Item>
) : (
<div className="editable-cell-value-wrap" style={{ paddingRight: 24 }} onClick={toggleEdit}>
{children}
</div>
);
}
return <td {...restProps}>{childNode}</td>;
};
// type EditableTableProps = Parameters<typeof Table>[0];
const FileCard = ({ file, isSelected, onClick, setIsReasonModalOpen }) => {
const fileName = file["File Name"];
return (
<div style={{
border: '1px solid #ccc',
width: '200px',
backgroundColor: isSelected ? '#d4ecff' : '#fff',
padding: '4px 8px',
marginRight: '4px',
marginTop: '2px',
position: 'relative',
height: '100px',
overflow: 'hidden',
}} onClick={onClick}>
<div>
<span style={{
fontSize: '12px',
color: '#333',
fontWeight: 'bold',
padding: '4px 8px',
cursor: 'default',
}}>{file["Doc Type"].toUpperCase()}</span>
<span style={{
fontSize: '12px',
color: '#aaa',
fontWeight: 'bold',
padding: '4px 8px',
cursor: 'default',
maxWidth: '50px',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}>
{fileName ? fileName.substring(0, 25).replace("temp_", "") : fileName}
</span>
</div>
<div style={{
padding: '4px',
position: 'absolute',
bottom: 2,
right: 2,
}}>
{/* <Button style={{
margin: '4px 2px',
}}
onClick={() => {
setIsReasonModalOpen(true);
}}
>
Review
</Button> */}
<Button style={{
margin: '4px 2px',
}} onClick={() => {
const downloadUrl = file["File URL"];
window.open(downloadUrl, '_blank');
}}>
<DownloadOutlined />
</Button>
</div>
</div>
);
};
const fetchRequest = async (id) => {
const token = localStorage.getItem('sbt-token') || '';
const response = await fetch(`${baseURL}/ctel/request/${id}/`, {
method: 'GET',
headers: {
"Authorization": `${JSON.parse(token)}`
}
});
return await (await response.json()).subscription_requests[0];
};
const InferencePage = () => { const InferencePage = () => {
const [invoiceFiles, setInvoiceFiles] = useState<UploadFile[]>([]); const [invoiceFiles, setInvoiceFiles] = useState<UploadFile[]>([]);
const [imei1Files, setImei1Files] = useState<UploadFile[]>([]); const [imei1Files, setImei1Files] = useState<UploadFile[]>([]);
const [imei2Files, setImei2Files] = useState<UploadFile[]>([]); const [imei2Files, setImei2Files] = useState<UploadFile[]>([]);
const [uploading, setUploading] = useState(false); const [uploading, setUploading] = useState(false);
const [jsonData, setJsonData] = useState({}); const [responseData, setResponseData] = useState(null);
const [finishedProcessing, setFinishedProcessing] = useState(false); const [finishedProcessing, setFinishedProcessing] = useState(false);
const handleUpload = () => { const handleUpload = () => {
@ -31,9 +229,7 @@ const InferencePage = () => {
} }
formData.append('is_test_request', 'true'); formData.append('is_test_request', 'true');
setUploading(true); setUploading(true);
setJsonData({ setResponseData(null);
"message": "Please wait..."
})
const token = localStorage.getItem('sbt-token') || ''; const token = localStorage.getItem('sbt-token') || '';
fetch(`${baseURL}/ctel/images/process_sync/`, { fetch(`${baseURL}/ctel/images/process_sync/`, {
method: 'POST', method: 'POST',
@ -44,31 +240,285 @@ const InferencePage = () => {
}) })
.then(async (res) => { .then(async (res) => {
const data = await res.json(); const data = await res.json();
setJsonData(data); if (data["status"] != "200") {
setResponseData(null);
return;
}
setTimeout(() => {
loadRequestById(data["request_id"]);
}, 2000);
setFinishedProcessing(true); setFinishedProcessing(true);
return data; return data;
}) })
.then(() => { .then(() => {
message.success('Upload successfully.'); message.success('Upload successfully.');
}) })
.catch(() => { .catch((e) => {
console.log(e);
message.error('Upload failed.'); message.error('Upload failed.');
}) })
.finally(() => { .finally(() => {
setUploading(false); setUploading(false);
}); });
}; };
const [loading, setLoading] = useState(false);
const [isReasonModalOpen, setIsReasonModalOpen] = useState(false);
const [selectedFileId, setSelectedFileId] = useState(0);
const [selectedFileData, setSelectedFileData] = useState(null);
const [selectedFileName, setSelectedFileName] = useState(null);
const [currentRequest, setCurrentRequest] = useState(null);
const [dataSource, setDataSource] = useState([]);
const setAndLoadSelectedFile = async (requestData, index) => {
setSelectedFileId(index);
if (!requestData["Files"][index]) {
setSelectedFileData("FAILED_TO_LOAD_FILE");
return;
};
const fileName = requestData["Files"][index]["File Name"];
const fileURL = requestData["Files"][index]["File URL"];
const response = await fetch(fileURL);
if (response.status === 200) {
setSelectedFileName(fileName);
setSelectedFileData(fileURL);
console.log("Loading file: " + fileName);
console.log("URL: " + fileURL);
} else {
setSelectedFileData("FAILED_TO_LOAD_FILE");
}
};
const loadRequestById = (requestID) => {
setLoading(true);
const requestData = fetchRequest(requestID);
requestData.then(async (data) => {
if (data) setCurrentRequest(data);
const predicted = (data && data["Predicted Result"]) ? data["Predicted Result"] : {};
const revised = (data && data["Reviewed Result"]) ? data["Reviewed Result"] : {};
const keys = Object.keys(predicted);
const tableRows = [];
for (let i = 0; i < keys.length; i++) {
let instance = {};
instance["key"] = keys[i];
instance["predicted"] = predicted[keys[i]];
instance["revised"] = revised[keys[i]];
tableRows.push(instance);
}
setDataSource(tableRows);
setLoading(false);
setAndLoadSelectedFile(data, 0);
}).finally(() => {
setLoading(false);
});
};
const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};
// "Key", "Accuracy", "Revised"
interface DataType {
key: string;
accuracy: number;
revised: string;
};
const updateRevisedData = async (newRevisedData: any) => {
const requestID = currentRequest.RequestID;
const token = localStorage.getItem('sbt-token') || '';
await fetch(`${baseURL}/ctel/request/${requestID}/`, {
method: 'POST',
headers: {
"Authorization": `${JSON.parse(token)}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"reviewed_result": newRevisedData
}),
}).catch((error) => {
console.log(error);
message.error("Could not update revised data");
});
};
const handleSave = (row: DataType) => {
const newData = [...dataSource];
const index = newData.findIndex((item) => row.key === item.key);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row,
});
setDataSource(newData);
const newRevisedData = {};
for (let i = 0; i < newData.length; i++) {
newRevisedData[newData[i].key] = newData[i].revised;
}
updateRevisedData(newRevisedData).then(() => {
// "[Is Reviewed]" => true
setCurrentRequest({
...currentRequest,
["Is Reviewed"]: true,
})
})
};
const submitRevisedData = async () => {
const newData = [...dataSource];
const newRevisedData = {};
for (let i = 0; i < newData.length; i++) {
if (newData[i].revised === "<empty>") {
newData[i].revised = null;
}
if (typeof(newData[i].revised) === "string") {
newData[i].revised = newData[i].revised.trim();
}
if (newData[i].revised === "" || newData[i].revised === null || newData[i].revised === undefined) {
newData[i].revised = null;
}
if ((newData[i].key === "imei_number" || newData[i].key === "purchase_date") && typeof(newData[i].revised) === "string") {
// Convert to list
newData[i].revised = new Array(newData[i].revised.split(","));
}
if (Array.isArray(newData[i].revised)) {
// Trim all empty strings
for (let j = 0; j < newData[i].revised.length; j++) {
if (typeof(newData[i].revised[j]) === "string") {
newData[i].revised[j] = newData[i].revised[j].trim();
}
if (newData[i].revised[j] === "<empty>") {
newData[i].revised[j] = null;
}
}
}
newRevisedData[newData[i].key] = newData[i].revised;
}
updateRevisedData(newRevisedData).then(() => {
// "[Is Reviewed]" => true
setCurrentRequest({
...currentRequest,
["Is Reviewed"]: true,
})
})
};
const defaultColumns = [
{
title: 'Key',
dataIndex: 'key',
key: 'key',
width: 200,
},
{
title: 'Predicted',
dataIndex: 'predicted',
key: 'predicted',
render: (text) => {
if (!text) return <span style={{ color: '#888' }}>{"<empty>"}</span>;
const displayedContent = text;
if (typeof(displayedContent) === "string") {
return <span style={{ color: '#000000' }}>{displayedContent}</span>;
} else if (typeof(displayedContent) === "object") {
if (displayedContent.length === 0) {
return <span style={{ color: '#888' }}>{"<empty>"}</span>;
}
// Set all empty values to "<empty>"
for (const key in displayedContent) {
if (!displayedContent[key]) {
displayedContent[key] = "<empty>";
}
}
return <span style={{ color: '#000000' }}>{displayedContent.join(", ")}</span>;
}
return <span style={{ color: '#000000' }}>{displayedContent}</span>;
},
},
{
title: (<div style={{
width: 120,
display: 'flex',
lineHeight: '32px',
marginLeft: 10,
}}>Revised&nbsp;&nbsp;
{ENABLE_REVIEW && <Button
onClick={() => {
if (!dataSource || !dataSource.length) return;
setDataSource(dataSource.map(item => {
item.revised = item.predicted;
return item;
}));
setTimeout(() => {
submitRevisedData();
}, 1000);
}}
>
Copy Predicted
</Button>}
</div>),
dataIndex: 'revised',
key: 'revised',
editable: ENABLE_REVIEW,
render: (text) => {
if (!text) return <span style={{ color: '#888' }}>{"<empty>"}</span>;
const displayedContent = text;
if (typeof(displayedContent) === "string") {
return <span style={{ color: '#000000' }}>{displayedContent}</span>;
} else if (typeof(displayedContent) === "object") {
if (displayedContent.length === 0) {
return <span style={{ color: '#888' }}>{"<empty>"}</span>;
}
// Set all empty values to "<empty>"
for (const key in displayedContent) {
if (!displayedContent[key]) {
displayedContent[key] = "<empty>";
}
}
return <span style={{ color: '#000000' }}>{displayedContent.join(", ")}</span>;
}
return <span style={{ color: '#000000' }}>{displayedContent}</span>;
},
},
];
const columns = defaultColumns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record: DataType) => ({
record,
editable: col.key != "request_id" && col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave,
}),
};
});
const fileExtension = selectedFileName ? selectedFileName.split('.').pop() : '';
return ( return (
<> <>
<div style={{
height: '100%',
position: 'relative',
}}>
<SbtPageHeader <SbtPageHeader
title={t`Inference`} title={t`Inference`}
/> />
<p>
{t`Upload files to process. The requests here will not be used in accuracy or payment calculations.`}
</p>
<div style={{ <div style={{
paddingTop: "0.5rem" display: 'flex',
flexDirection: 'row',
}}>
<div style={{
paddingTop: "0.5rem",
padding: "0.5rem",
height: "80px",
}}> }}>
<Upload <Upload
onRemove={(file) => { onRemove={(file) => {
@ -82,11 +532,13 @@ const InferencePage = () => {
}} }}
fileList={invoiceFiles} fileList={invoiceFiles}
> >
Invoice File: <Button disabled={finishedProcessing} icon={<UploadOutlined />}>Select Image/PDF</Button> Invoice: <Button disabled={finishedProcessing} icon={<UploadOutlined />}>Select Image/PDF</Button>
</Upload> </Upload>
</div> </div>
<div style={{ <div style={{
paddingTop: "0.5rem" paddingTop: "0.5rem",
padding: "0.5rem",
height: "80px",
}}> }}>
<Upload <Upload
onRemove={(file) => { onRemove={(file) => {
@ -100,11 +552,13 @@ const InferencePage = () => {
}} }}
fileList={imei1Files} fileList={imei1Files}
> >
IMEI File 1: <Button disabled={finishedProcessing} icon={<UploadOutlined />}>Select Image</Button> IMEI 1: <Button disabled={finishedProcessing} icon={<UploadOutlined />}>Select Image</Button>
</Upload> </Upload>
</div> </div>
<div style={{ <div style={{
paddingTop: "0.5rem" paddingTop: "0.5rem",
padding: "0.5rem",
height: "80px",
}}> }}>
<Upload <Upload
onRemove={(file) => { onRemove={(file) => {
@ -118,15 +572,21 @@ const InferencePage = () => {
}} }}
fileList={imei2Files} fileList={imei2Files}
> >
IMEI File 2: <Button disabled={finishedProcessing} icon={<UploadOutlined />}>Select Image</Button> IMEI 2: <Button disabled={finishedProcessing} icon={<UploadOutlined />}>Select Image</Button>
</Upload> </Upload>
</div> </div>
<div style={{
paddingTop: "0.5rem",
padding: "0.5rem",
height: "80px",
display: 'flex',
flexDirection: 'row',
}}>
{!finishedProcessing && <Button {!finishedProcessing && <Button
type="primary" type="primary"
onClick={handleUpload} onClick={handleUpload}
disabled={imei1Files.length === 0 && imei2Files.length === 0} disabled={imei1Files.length === 0 && imei2Files.length === 0}
loading={uploading} loading={uploading}
style={{ marginTop: 16, marginBottom: 24 }}
> >
{uploading ? 'Uploading' : 'Process Data'} {uploading ? 'Uploading' : 'Process Data'}
</Button>} </Button>}
@ -134,20 +594,153 @@ const InferencePage = () => {
type="primary" type="primary"
onClick={() => { onClick={() => {
setFinishedProcessing(false); setFinishedProcessing(false);
setJsonData({}); setResponseData(null);
setInvoiceFiles([]); setInvoiceFiles([]);
setImei1Files([]); setImei1Files([]);
setImei2Files([]); setImei2Files([]);
setCurrentRequest(null);
setDataSource([]);
setSelectedFileData(null);
setUploading(false);
}} }}
style={{ marginTop: 16, marginBottom: 24 }}
> >
Reset Reset
</Button>} </Button>}
<div style={{ </div>
paddingTop: "0.5rem" </div>
<div
style={{ height: '100%', position: 'absolute', top: 0, left: 0, width: '100%', background: "#00000033", zIndex: 1000, display: loading ? 'block' : 'none' }}
>
<Spin spinning={true} style={{
position: 'absolute',
top: '50%',
left: '50%',
marginTop: -12,
marginLeft: -12,
width: 24,
height: 24,
borderRadius: '50%',
}} size='large' />
</div>
<Layout style={{
overflow: 'auto',
width: '100%',
height: '100%',
maxWidth: '100%',
minHeight: '70%',
maxHeight: '70%',
display: 'flex',
padding: '8px',
}}> }}>
<h3>Result:</h3> <Content style={{
<JsonView data={jsonData} shouldExpandNode={allExpanded} style={defaultStyles} /> textAlign: 'center',
color: '#fff',
backgroundColor: '#efefef',
height: '100%',
display: 'flex',
flexDirection: 'row',
}}>
{currentRequest?.Files?.length && <div
style={{
width: "200px",
display: "flex",
flexDirection: "column",
flexGrow: 0,
}}>
<h2
style={{
color: "#333",
padding: 10,
fontWeight: 'bold',
fontSize: 18,
textTransform: 'uppercase'
}}
>Files ({currentRequest?.Files?.length})</h2>
{currentRequest?.Files.map((file, index) => (
<FileCard key={index} file={file} isSelected={index === selectedFileId} onClick={
() => {
setAndLoadSelectedFile(currentRequest, index);
}
} setIsReasonModalOpen={setIsReasonModalOpen} />
))}
</div>}
{selectedFileData && <div style={{
border: "1px solid #ccc",
flexGrow: 1,
height: '100%',
}}>
{selectedFileData === "FAILED_TO_LOAD_FILE" ? <p style={{ color: "#333" }}></p> : (fileExtension === "pdf" ? (<Viewer
fileUrl={selectedFileData}
/>) : <div style={{
height: "100%",
width: "100%",
overflow: "auto",
}}><img width={"100%"} src={selectedFileData} alt="file" /></div>)}
</div>}
</Content>
<Sider width="400px" style={siderStyle}>
<div>
<Input size='small' addonBefore="Request ID" style={{ marginBottom: "4px" }} readOnly value={currentRequest ? currentRequest.RequestID : ""} />
<Input size='small' addonBefore="Redemption" style={{ marginBottom: "4px" }} readOnly value={currentRequest?.RedemptionID ? currentRequest.RedemptionID : ""} />
<Input size='small' addonBefore="Uploaded date" style={{ marginBottom: "4px" }} readOnly value={currentRequest ? currentRequest.created_at : ""} />
<Input size='small' addonBefore="Request time" style={{ marginBottom: "4px" }} readOnly value={currentRequest ? currentRequest["Client Request Time (ms)"] : ""} />
<Input size='small' addonBefore="Processing time" style={{ marginBottom: "4px" }} readOnly value={currentRequest ? currentRequest["Server Processing Time (ms)"] : ""} />
<div style={{ marginBottom: "8px", marginTop: "8px", display: "flex" }}>
{currentRequest && (currentRequest["Is Reviewed"] ? <Tag icon={<CheckCircleOutlined />} color="success" style={{ padding: "4px 16px" }}>
Reviewed
</Tag> : <Tag icon={<ClockCircleFilled />} color="warning" style={{ padding: "4px 16px" }}>
Not Reviewed
</Tag>)}
</div>
</div>
</Sider>
</Layout>
<Modal
title={t`Review`}
open={isReasonModalOpen}
width={700}
onOk={
() => {
}
}
onCancel={
() => {
setIsReasonModalOpen(false);
}
}
>
<Form
style={{
marginTop: 30,
}}
>
<Form.Item
name='reason'
label={t`Reason for bad quality:`}
style={{
marginBottom: 10,
}}
>
<Select
placeholder='Select a reason'
style={{ width: 200 }}
options={[
{ value: 'invalid_image', label: t`Invalid image` },
{ value: 'missing_information', label: t`Missing information` },
{ value: 'too_blurry_text', label: t`Too blurry text` },
{ value: 'too_small_text', label: t`Too small text` },
{ value: 'handwritten', label: t`Handwritten` },
{ value: 'recheck', label: t`Recheck` },
]}
/>
</Form.Item>
</Form>
</Modal>
<StyledTable components={components}
rowClassName={() => 'editable-row'}
bordered dataSource={dataSource} columns={columns}
/>
</div> </div>
</> </>
); );

View File

@ -10,12 +10,34 @@ import { useState } from 'react';
export interface ReportFormValues { export interface ReportFormValues {
dateRange: [Dayjs, Dayjs]; dateRange: [Dayjs, Dayjs];
subsidiary: string; subsidiary: string;
reportType: string;
} }
const DEFAULT_SUBSIDIARY_OPTIONS = [
{ value: 'SEAO', label: 'SEAO' },
{ value: 'SEAU', label: 'SEAU' },
{ value: 'SESP', label: 'SESP' },
{ value: 'SME', label: 'SME' },
{ value: 'SEPCO', label: 'SEPCO' },
{ value: 'TSE', label: 'TSE' },
{ value: 'SEIN', label: 'SEIN' },
];
const ReportsPage = () => { const ReportsPage = () => {
const [form] = Form.useForm<ReportFormValues>(); const [form] = Form.useForm<ReportFormValues>();
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const makeReportMutation = useMakeReport(); const makeReportMutation = useMakeReport();
const [subsidiaryOptions, setSubsidiaryOptions] = useState(DEFAULT_SUBSIDIARY_OPTIONS);
const onReportTypeChange = (value: string) => {
if (value === 'billing') {
setSubsidiaryOptions([DEFAULT_SUBSIDIARY_OPTIONS[0]]);
form.setFieldValue('subsidiary', 'SEAO');
} else if (value === 'payment') {
} else {
setSubsidiaryOptions(DEFAULT_SUBSIDIARY_OPTIONS);
}
};
const showModal = () => { const showModal = () => {
setIsModalOpen(true); setIsModalOpen(true);
@ -30,10 +52,10 @@ const ReportsPage = () => {
end_date: values.dateRange[1].format('YYYY-MM-DD'), end_date: values.dateRange[1].format('YYYY-MM-DD'),
start_date: values.dateRange[0].format('YYYY-MM-DD'), start_date: values.dateRange[0].format('YYYY-MM-DD'),
subsidiary: values.subsidiary, subsidiary: values.subsidiary,
report_type: values.reportType,
}) })
.then((data) => { .then((data) => {
if (!!data && data?.report_id) { if (!!data && data?.report_id) {
form.resetFields();
setIsModalOpen(false); setIsModalOpen(false);
window.location.reload(); window.location.reload();
} }
@ -107,18 +129,33 @@ const ReportsPage = () => {
message: 'Please select a subsidiary', message: 'Please select a subsidiary',
}, },
]} ]}
initialValue={'SEAO'}
> >
<Select <Select
placeholder='Select a subsidiary' placeholder='Select a subsidiary'
style={{ width: 200 }} style={{ width: 200 }}
options={subsidiaryOptions}
/>
</Form.Item>
<Form.Item
name='reportType'
label={t`Report Type`}
rules={[
{
required: true,
message: 'Please select a type',
},
]}
initialValue={'accuracy'}
>
<Select
onSelect={onReportTypeChange}
placeholder='Select a report type'
style={{ width: 200 }}
options={[ options={[
{ value: 'SEAO', label: 'SEAO' }, { value: 'billing', label: 'Billing' },
{ value: 'SEAU', label: 'SEAU' }, { value: 'accuracy', label: 'Accuracy' },
{ value: 'SESP', label: 'SESP' },
{ value: 'SME', label: 'SME' },
{ value: 'SEPCO', label: 'SEPCO' },
{ value: 'TSE', label: 'TSE' },
{ value: 'SEIN', label: 'SEIN' },
]} ]}
/> />
</Form.Item> </Form.Item>

View File

@ -290,14 +290,14 @@ const ReviewPage = () => {
const loadCurrentRequest = (requestIndex) => { const loadCurrentRequest = (requestIndex) => {
setLoading(true); setLoading(true);
fetchAllRequests(filterDateRange, filterSubsidiaries, filterReviewState, filterIncludeTests, requestIndex, 1).then((data) => { fetchAllRequests(filterDateRange, filterSubsidiaries, filterReviewState, filterIncludeTests, requestIndex, 2).then((data) => {
setRequests(data?.subscription_requests); setRequests(data?.subscription_requests);
setHasNextRequest(data?.subscription_requests.length > 1); setHasNextRequest(data?.subscription_requests.length > 1);
setTotalPages(data?.page?.total_requests); setTotalPages(data?.page?.total_requests);
const requestData = fetchRequest(data?.subscription_requests[0].RequestID); const requestData = fetchRequest(data?.subscription_requests[0].RequestID);
requestData.then(async (data) => { requestData.then(async (data) => {
if (data) setCurrentRequest(data); if (data) setCurrentRequest(data);
const predicted = (data && data["Reviewed Result"]) ? data["Reviewed Result"] : {}; const predicted = (data && data["Predicted Result"]) ? data["Predicted Result"] : {};
const submitted = (data && data["Feedback Result"]) ? data["Feedback Result"] : {}; const submitted = (data && data["Feedback Result"]) ? data["Feedback Result"] : {};
const revised = (data && data["Reviewed Result"]) ? data["Reviewed Result"] : {}; const revised = (data && data["Reviewed Result"]) ? data["Reviewed Result"] : {};
const keys = Object.keys(predicted); const keys = Object.keys(predicted);
@ -430,9 +430,32 @@ const ReviewPage = () => {
const newData = [...dataSource]; const newData = [...dataSource];
const newRevisedData = {}; const newRevisedData = {};
for (let i = 0; i < newData.length; i++) { for (let i = 0; i < newData.length; i++) {
if (newData[i].revised === "<empty>") {
newData[i].revised = null;
}
if (typeof(newData[i].revised) === "string") {
newData[i].revised = newData[i].revised.trim();
}
if (newData[i].revised === "" || newData[i].revised === null || newData[i].revised === undefined) {
newData[i].revised = null;
}
if ((newData[i].key === "imei_number" || newData[i].key === "purchase_date") && typeof(newData[i].revised) === "string") {
// Convert to list
newData[i].revised = new Array(newData[i].revised.split(","));
}
if (Array.isArray(newData[i].revised)) {
// Trim all empty strings
for (let j = 0; j < newData[i].revised.length; j++) {
if (typeof(newData[i].revised[j]) === "string") {
newData[i].revised[j] = newData[i].revised[j].trim();
}
if (newData[i].revised[j] === "<empty>") {
newData[i].revised[j] = null;
}
}
}
newRevisedData[newData[i].key] = newData[i].revised; newRevisedData[newData[i].key] = newData[i].revised;
} }
console.log(currentRequest)
updateRevisedData(newRevisedData).then(() => { updateRevisedData(newRevisedData).then(() => {
// "[Is Reviewed]" => true // "[Is Reviewed]" => true
setCurrentRequest({ setCurrentRequest({
@ -443,6 +466,7 @@ const ReviewPage = () => {
}; };
const defaultColumns = [ const defaultColumns = [
{ {
title: 'Key', title: 'Key',
@ -454,11 +478,49 @@ const ReviewPage = () => {
title: 'Predicted', title: 'Predicted',
dataIndex: 'predicted', dataIndex: 'predicted',
key: 'predicted', key: 'predicted',
render: (text) => {
if (!text) return <span style={{ color: '#888' }}>{"<empty>"}</span>;
const displayedContent = text;
if (typeof(displayedContent) === "string") {
return <span style={{ color: '#000000' }}>{displayedContent}</span>;
} else if (typeof(displayedContent) === "object") {
if (displayedContent.length === 0) {
return <span style={{ color: '#888' }}>{"<empty>"}</span>;
}
// Set all empty values to "<empty>"
for (const key in displayedContent) {
if (!displayedContent[key]) {
displayedContent[key] = "<empty>";
}
}
return <span style={{ color: '#000000' }}>{displayedContent.join(", ")}</span>;
}
return <span style={{ color: '#000000' }}>{displayedContent}</span>;
},
}, },
{ {
title: 'Submitted', title: 'Submitted',
dataIndex: 'submitted', dataIndex: 'submitted',
key: 'submitted', key: 'submitted',
render: (text) => {
if (!text) return <span style={{ color: '#888' }}>{"<empty>"}</span>;
const displayedContent = text;
if (typeof(displayedContent) === "string") {
return <span style={{ color: '#000000' }}>{displayedContent}</span>;
} else if (typeof(displayedContent) === "object") {
if (displayedContent.length === 0) {
return <span style={{ color: '#888' }}>{"<empty>"}</span>;
}
// Set all empty values to "<empty>"
for (const key in displayedContent) {
if (!displayedContent[key]) {
displayedContent[key] = "<empty>";
}
}
return <span style={{ color: '#000000' }}>{displayedContent.join(", ")}</span>;
}
return <span style={{ color: '#000000' }}>{displayedContent}</span>;
},
}, },
{ {
title: (<div style={{ title: (<div style={{
@ -499,11 +561,24 @@ const ReviewPage = () => {
dataIndex: 'revised', dataIndex: 'revised',
key: 'revised', key: 'revised',
editable: true, editable: true,
render: (text, record) => { render: (text) => {
return <div style={{ if (!text) return <span style={{ color: '#888' }}>{"<empty>"}</span>;
color: '#000', const displayedContent = text;
fontWeight: 'bold', if (typeof(displayedContent) === "string") {
}}>{text}</div>; return <span style={{ color: '#000000' }}>{displayedContent}</span>;
} else if (typeof(displayedContent) === "object") {
if (displayedContent.length === 0) {
return <span style={{ color: '#888' }}>{"<empty>"}</span>;
}
// Set all empty values to "<empty>"
for (const key in displayedContent) {
if (!displayedContent[key]) {
displayedContent[key] = "<empty>";
}
}
return <span style={{ color: '#000000' }}>{displayedContent.join(", ")}</span>;
}
return <span style={{ color: '#000000' }}>{displayedContent}</span>;
}, },
}, },
]; ];

View File

@ -11,7 +11,9 @@ const environment = process.env.NODE_ENV;
const AXIOS_TIMEOUT_MS = 30 * 60 * 1000; // This config sastified long-live upload file request const AXIOS_TIMEOUT_MS = 30 * 60 * 1000; // This config sastified long-live upload file request
const EXPIRED_PASSWORD_SIGNAL = 'expired_password'; const EXPIRED_PASSWORD_SIGNAL = 'expired_password';
export const baseURL = environment === 'development' ? 'http://42.96.42.13:9881/api' : '/api'; // export const baseURL = environment === 'development' ? 'http://42.96.42.13:9881/api' : '/api';
export const baseURL = '/api';
export const API = axios.create({ export const API = axios.create({
timeout: AXIOS_TIMEOUT_MS, timeout: AXIOS_TIMEOUT_MS,

View File

@ -32,12 +32,13 @@ export async function getReportDetailList(params: ReportDetailListParams) {
} }
export async function makeReport(params: MakeReportParams) { export async function makeReport(params: MakeReportParams) {
const { end_date, start_date, subsidiary } = params; const { end_date, start_date, subsidiary, report_type } = params;
try { try {
const response = await API.post<MakeReportResponse>(`/ctel/make_report/`, { const response = await API.post<MakeReportResponse>(`/ctel/make_report/`, {
start_date: start_date, start_date: start_date,
end_date: end_date, end_date: end_date,
subsidiary: subsidiary, subsidiary: subsidiary,
report_type: report_type,
}); });
return response.data; return response.data;
} catch (error) { } catch (error) {

View File

@ -9,7 +9,7 @@ import { PrivateRoute, PublicRoute } from './guard-route';
const LoginPage = React.lazy(() => import('pages/login')); const LoginPage = React.lazy(() => import('pages/login'));
const DashboardPage = React.lazy(() => import('pages/dashboard')); const DashboardPage = React.lazy(() => import('pages/dashboard'));
const InferencePage = React.lazy(() => import('pages/inference')); const InferencePage = React.lazy(() => import('pages/inference/index'));
const ReviewsPage = React.lazy(() => import('pages/reviews')); const ReviewsPage = React.lazy(() => import('pages/reviews'));
const ReportsPage = React.lazy(() => import('pages/reports')); const ReportsPage = React.lazy(() => import('pages/reports'));

View File

@ -1,5 +1,5 @@
export const formatPercent = (value: number, floatingPoint: number = 1) => { export const formatPercent = (value: any, floatingPoint: number = 1, maskZero: boolean = false) => {
if (value === null || value === undefined) { if (value === null || value === undefined || (value === 0 && maskZero)) {
return '-'; return '-';
} }
if (value < 100.0) { if (value < 100.0) {
@ -8,11 +8,19 @@ export const formatPercent = (value: number, floatingPoint: number = 1) => {
return value.toFixed(floatingPoint); return value.toFixed(floatingPoint);
}; };
export const formatNumber = (value: any, floatingPoint: number = 1, maskZero: boolean = false) => {
if (value === null || value === undefined || (value === 0 && maskZero)) {
return '-';
}
return value.toFixed(floatingPoint);
};
export const ensureMin = ( export const ensureMin = (
value: number, value: number,
min: number, min: number,
skipZero: boolean = true, skipZero: boolean = true,
) => { ) => {
if (formatPercent(value) == '-') return false;
if (skipZero && value === 0) { if (skipZero && value === 0) {
return false; return false;
} }
@ -27,6 +35,7 @@ export const ensureMax = (
max: number, max: number,
skipZero: boolean = true, skipZero: boolean = true,
) => { ) => {
if (formatPercent(value) == '-') return false;
if (skipZero && value === 0) { if (skipZero && value === 0) {
return false; return false;
} }

View File

@ -45,11 +45,11 @@ export default defineConfig(({ mode = 'development' }) => {
viteTsconfigPaths(), viteTsconfigPaths(),
svgrPlugin(), svgrPlugin(),
], ],
// server: { server: {
// open: true, open: true,
// port: configPort(env), port: configPort(env),
// proxy: configProxy(env), proxy: configProxy(env),
// }, },
define: { define: {
'process.env': env, 'process.env': env,
}, },

View File

@ -89,12 +89,12 @@ services:
depends_on: depends_on:
db-sbt: db-sbt:
condition: service_started condition: service_started
# command: sh -c "chmod -R 777 /app; sleep 5; python manage.py collectstatic --no-input && command: sh -c "chmod -R 777 /app; sleep 5; python manage.py collectstatic --no-input &&
# python manage.py makemigrations && python manage.py makemigrations &&
# python manage.py migrate && python manage.py migrate &&
# python manage.py compilemessages && python manage.py compilemessages &&
# gunicorn fwd.asgi:application -k uvicorn.workers.UvicornWorker --timeout 300 -b 0.0.0.0:9000" # pre-makemigrations on prod gunicorn fwd.asgi:application -k uvicorn.workers.UvicornWorker --timeout 300 -b 0.0.0.0:9000" # pre-makemigrations on prod
command: bash -c "tail -f > /dev/null" # command: bash -c "tail -f > /dev/null"
minio: minio:
image: minio/minio image: minio/minio
@ -179,8 +179,8 @@ services:
- ./cope2n-api:/app - ./cope2n-api:/app
working_dir: /app working_dir: /app
# command: sh -c "celery -A fwd_api.celery_worker.worker worker -l INFO -c 5" command: sh -c "celery -A fwd_api.celery_worker.worker worker -l INFO -c 5"
command: bash -c "tail -f > /dev/null" # command: bash -c "tail -f > /dev/null"
# Back-end persistent # Back-end persistent
db-sbt: db-sbt: