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
SUB_FOR_BILLING = ["all", "seao"]
FIELD = ["imei_number", "purchase_date", "retailername", "sold_to_party", "invoice_no"]
CACHES = {
'default': {

View File

@ -284,6 +284,7 @@ class AccuracyViewSet(viewsets.ViewSet):
return JsonResponse(status=status.HTTP_200_OK, data={"report_id": report_id})
# Redundant, will be removed by 19 March 2024
@extend_schema(
parameters=[
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])
else:
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({
"ID": report.id,
"Created Date": report.created_at,
@ -429,7 +433,7 @@ class AccuracyViewSet(viewsets.ViewSet):
"IMEI Acc": acc["imei_number"],
"Avg. Accuracy": acc["avg"],
"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,
"Subsidiary": map_subsidiary_short_to_long(report.subsidiary),
})
@ -544,7 +548,7 @@ class AccuracyViewSet(viewsets.ViewSet):
for key in keys:
if report_fine_data[i][key]:
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"
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"](),
"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["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)

View File

@ -101,8 +101,8 @@ class Command(BaseCommand):
request.is_reviewed = False
request.save()
image.predict_result = _predict_result
image.feedback_result = _feedback_result
image.reviewed_result = _reviewed_result
# image.feedback_result = _feedback_result
# image.reviewed_result = _reviewed_result
image.save()
except Exception as 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
},
'average_accuracy_rate': {
'imei': IterAvg(),
'imei_number': IterAvg(),
'purchase_date': IterAvg(),
'retailer_name': IterAvg(),
'sold_to_party': IterAvg(),
@ -133,22 +133,12 @@ class ReportAccumulateByRequest:
total["num_imei"] += 1 if doc_type == "imei" 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 :
total["average_accuracy_rate"]["imei"].add(report_file.reviewed_accuracy.get("imei_number", []))
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", []))
total["average_accuracy_rate"][key].add(report_file.reviewed_accuracy.get(key, []))
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"]["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["average_accuracy_rate"][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, []))
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["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:
day_data["average_accuracy_rate"]["imei"].add(report_file.reviewed_accuracy.get("imei_number", []))
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", []))
day_data["average_accuracy_rate"][key].add(report_file.reviewed_accuracy.get(key, []))
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"]["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["average_accuracy_rate"][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, []))
if not day_data["average_processing_time"].get(report_file.doc_type, None):
@ -274,7 +254,7 @@ class ReportAccumulateByRequest:
"reviewed_accuracy": {}}
for acc_type in ["feedback_accuracy", "reviewed_accuracy"]:
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+"_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"])
@ -318,26 +298,13 @@ class ReportAccumulateByRequest:
for day in _data[month][1].keys():
num_transaction_imei += _data[month][1][day]["usage"].get("imei", 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():
_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"]()
_data[month][1][day]["feedback_accuracy"]["purchase_date"] = _data[month][1][day]["feedback_accuracy"]["purchase_date"]()
_data[month][1][day]["feedback_accuracy"]["retailername"] = _data[month][1][day]["feedback_accuracy"]["retailername"]()
_data[month][1][day]["feedback_accuracy"]["sold_to_party"] = _data[month][1][day]["feedback_accuracy"]["sold_to_party"]()
_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"]()
for key in settings.FIELD:
_data[month][1][day]["average_accuracy_rate"][key] = _data[month][1][day]["average_accuracy_rate"][key]()
for accuracy_type in ["feedback_accuracy", key]:
_data[month][1][day][accuracy_type]["imei_number"] = _data[month][1][day]["feedback_accuracy"]["imei_number"]()
_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")
@ -347,28 +314,13 @@ class ReportAccumulateByRequest:
_data[month][0]["usage"]["imei"] = num_transaction_imei
_data[month][0]["usage"]["invoice"] = num_transaction_invoice
_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():
_data[month][0]["average_processing_time"][key] = _data[month][0]["average_processing_time"][key]()
_data[month][0]["feedback_accuracy"]["imei_number"] = _data[month][0]["feedback_accuracy"]["imei_number"]()
_data[month][0]["feedback_accuracy"]["purchase_date"] = _data[month][0]["feedback_accuracy"]["purchase_date"]()
_data[month][0]["feedback_accuracy"]["retailername"] = _data[month][0]["feedback_accuracy"]["retailername"]()
_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"]()
for key in settings.FIELD:
_data[month][0]["average_accuracy_rate"][key] = _data[month][0]["average_accuracy_rate"][key]()
for accuracy_type in ["feedback_accuracy", key]:
_data[month][0][accuracy_type][key] = _data[month][0][accuracy_type][key]()
_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
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):
data = []
for report_file in report_detail_list:
# FIXME: #79 Fill None with value
data.append({
"Subs": report_file.subsidiary,
"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,
"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 Revised": 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_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_Retailer_Consumer": report_file.feedback_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_No_Consumer": report_file.feedback_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
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": [],
"purchase_date": [],
"retailername": [],
@ -871,8 +837,8 @@ def calculate_a_request(report, request):
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.reviewed_result["purchase_date"] = att["normalized_data"]["reviewed"]["purchase_date"][rv_max_indexes["purchase_date"]][1]
if request.is_reviewed:
att["is_reviewed"] = 1
# if request.is_reviewed:
# att["is_reviewed"] = 1
request_att["is_reviewed"].append(att["is_reviewed"])
new_report_file = ReportFile(report=report,
subsidiary=_sub,
@ -886,7 +852,7 @@ def calculate_a_request(report, request):
reviewed_accuracy=att["acc"]["reviewed"],
acc=att["avg_acc"],
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,
bad_image_reason=image.reason,
counter_measures=image.counter_measures,
@ -979,6 +945,10 @@ def calculate_subcription_file(subcription_request_file):
avg_acc = avg_reviewed
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
if avg_acc < settings.BAD_THRESHOLD:
att["is_bad_image"] = True

View File

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

Binary file not shown.

Binary file not shown.

View File

@ -1,3 +1,3 @@
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

View File

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

View File

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

View File

@ -11,11 +11,14 @@
"Email format is not correct": "Email format is not correct",
"English": "English",
"Go to Reports": "Go to Reports",
"Handwritten": "Handwritten",
"Inference": "Inference",
"Invalid image": "Invalid image",
"Is Test": "Is Test",
"Language": "Language",
"Login": "Login",
"Logout": "Logout",
"Missing information": "Missing information",
"New Report": "New Report",
"Only characters (a-z), (A-Z), (0-9), @, ., +, -, _ are available": "Only characters (a-z), (A-Z), (0-9), @, ., +, -, _ are available",
"Password": "Password",
@ -28,8 +31,11 @@
"Please enter a valid domain": "Please enter a valid domain",
"Please specify a password": "Please specify a password",
"Please specify a username": "Please specify a username",
"Reason for bad quality:": "Reason for bad quality:",
"Recheck": "Recheck",
"Report Details": "Report Details",
"Report Filters": "Report Filters",
"Report Type": "Report Type",
"Reports": "Reports",
"Retry": "Retry",
"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_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",
"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.",
"User log in successfully": "User log in successfully",
"Username": "Username",

View File

@ -11,11 +11,14 @@
"Email format is not correct": "Định dạng email không hợp lệ",
"English": "Tiếng Anh",
"Go to Reports": "",
"Handwritten": "",
"Inference": "",
"Invalid image": "",
"Is Test": "",
"Language": "Ngôn ngữ",
"Login": "Đăng nhập",
"Logout": "Đăng xuất",
"Missing information": "",
"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), @, ., +, -, _",
"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 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",
"Reason for bad quality:": "",
"Recheck": "",
"Report Details": "",
"Report Filters": "",
"Report Type": "",
"Reports": "",
"Retry": "Thử lại",
"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_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ự",
"Too blurry text": "",
"Too small text": "",
"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",
"Username": "Tên tài khoản",

View File

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

View File

@ -1,21 +1,219 @@
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 { useState } from 'react';
import { UploadOutlined } from '@ant-design/icons';
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 { baseURL } from "request/api"
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 [invoiceFiles, setInvoiceFiles] = useState<UploadFile[]>([]);
const [imei1Files, setImei1Files] = useState<UploadFile[]>([]);
const [imei2Files, setImei2Files] = useState<UploadFile[]>([]);
const [uploading, setUploading] = useState(false);
const [jsonData, setJsonData] = useState({});
const [responseData, setResponseData] = useState(null);
const [finishedProcessing, setFinishedProcessing] = useState(false);
const handleUpload = () => {
@ -31,9 +229,7 @@ const InferencePage = () => {
}
formData.append('is_test_request', 'true');
setUploading(true);
setJsonData({
"message": "Please wait..."
})
setResponseData(null);
const token = localStorage.getItem('sbt-token') || '';
fetch(`${baseURL}/ctel/images/process_sync/`, {
method: 'POST',
@ -42,33 +238,287 @@ const InferencePage = () => {
"Authorization": `${JSON.parse(token)}`
}
})
.then(async(res) => {
.then(async (res) => {
const data = await res.json();
setJsonData(data);
if (data["status"] != "200") {
setResponseData(null);
return;
}
setTimeout(() => {
loadRequestById(data["request_id"]);
}, 2000);
setFinishedProcessing(true);
return data;
})
.then(() => {
message.success('Upload successfully.');
})
.catch(() => {
.catch((e) => {
console.log(e);
message.error('Upload failed.');
})
.finally(() => {
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 (
<>
<div style={{
height: '100%',
position: 'relative',
}}>
<SbtPageHeader
title={t`Inference`}
/>
<p>
{t`Upload files to process. The requests here will not be used in accuracy or payment calculations.`}
</p>
<div style={{
paddingTop: "0.5rem"
display: 'flex',
flexDirection: 'row',
}}>
<div style={{
paddingTop: "0.5rem",
padding: "0.5rem",
height: "80px",
}}>
<Upload
onRemove={(file) => {
@ -82,11 +532,13 @@ const InferencePage = () => {
}}
fileList={invoiceFiles}
>
Invoice File: <Button disabled={finishedProcessing} icon={<UploadOutlined />}>Select Image/PDF</Button>
Invoice: <Button disabled={finishedProcessing} icon={<UploadOutlined />}>Select Image/PDF</Button>
</Upload>
</div>
<div style={{
paddingTop: "0.5rem"
paddingTop: "0.5rem",
padding: "0.5rem",
height: "80px",
}}>
<Upload
onRemove={(file) => {
@ -100,11 +552,13 @@ const InferencePage = () => {
}}
fileList={imei1Files}
>
IMEI File 1: <Button disabled={finishedProcessing} icon={<UploadOutlined />}>Select Image</Button>
IMEI 1: <Button disabled={finishedProcessing} icon={<UploadOutlined />}>Select Image</Button>
</Upload>
</div>
<div style={{
paddingTop: "0.5rem"
paddingTop: "0.5rem",
padding: "0.5rem",
height: "80px",
}}>
<Upload
onRemove={(file) => {
@ -118,15 +572,21 @@ const InferencePage = () => {
}}
fileList={imei2Files}
>
IMEI File 2: <Button disabled={finishedProcessing} icon={<UploadOutlined />}>Select Image</Button>
IMEI 2: <Button disabled={finishedProcessing} icon={<UploadOutlined />}>Select Image</Button>
</Upload>
</div>
<div style={{
paddingTop: "0.5rem",
padding: "0.5rem",
height: "80px",
display: 'flex',
flexDirection: 'row',
}}>
{!finishedProcessing && <Button
type="primary"
onClick={handleUpload}
disabled={imei1Files.length === 0 && imei2Files.length === 0}
loading={uploading}
style={{ marginTop: 16, marginBottom: 24 }}
>
{uploading ? 'Uploading' : 'Process Data'}
</Button>}
@ -134,20 +594,153 @@ const InferencePage = () => {
type="primary"
onClick={() => {
setFinishedProcessing(false);
setJsonData({});
setResponseData(null);
setInvoiceFiles([]);
setImei1Files([]);
setImei2Files([]);
setCurrentRequest(null);
setDataSource([]);
setSelectedFileData(null);
setUploading(false);
}}
style={{ marginTop: 16, marginBottom: 24 }}
>
Reset
</Button>}
<div style={{
paddingTop: "0.5rem"
</div>
</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>
<JsonView data={jsonData} shouldExpandNode={allExpanded} style={defaultStyles} />
<Content style={{
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>
</>
);

View File

@ -10,12 +10,34 @@ import { useState } from 'react';
export interface ReportFormValues {
dateRange: [Dayjs, Dayjs];
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 [form] = Form.useForm<ReportFormValues>();
const [isModalOpen, setIsModalOpen] = useState(false);
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 = () => {
setIsModalOpen(true);
@ -30,10 +52,10 @@ const ReportsPage = () => {
end_date: values.dateRange[1].format('YYYY-MM-DD'),
start_date: values.dateRange[0].format('YYYY-MM-DD'),
subsidiary: values.subsidiary,
report_type: values.reportType,
})
.then((data) => {
if (!!data && data?.report_id) {
form.resetFields();
setIsModalOpen(false);
window.location.reload();
}
@ -107,18 +129,33 @@ const ReportsPage = () => {
message: 'Please select a subsidiary',
},
]}
initialValue={'SEAO'}
>
<Select
placeholder='Select a subsidiary'
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={[
{ 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' },
{ value: 'billing', label: 'Billing' },
{ value: 'accuracy', label: 'Accuracy' },
]}
/>
</Form.Item>

View File

@ -290,14 +290,14 @@ const ReviewPage = () => {
const loadCurrentRequest = (requestIndex) => {
setLoading(true);
fetchAllRequests(filterDateRange, filterSubsidiaries, filterReviewState, filterIncludeTests, requestIndex, 1).then((data) => {
fetchAllRequests(filterDateRange, filterSubsidiaries, filterReviewState, filterIncludeTests, requestIndex, 2).then((data) => {
setRequests(data?.subscription_requests);
setHasNextRequest(data?.subscription_requests.length > 1);
setTotalPages(data?.page?.total_requests);
const requestData = fetchRequest(data?.subscription_requests[0].RequestID);
requestData.then(async (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 revised = (data && data["Reviewed Result"]) ? data["Reviewed Result"] : {};
const keys = Object.keys(predicted);
@ -430,9 +430,32 @@ const ReviewPage = () => {
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;
}
console.log(currentRequest)
updateRevisedData(newRevisedData).then(() => {
// "[Is Reviewed]" => true
setCurrentRequest({
@ -443,6 +466,7 @@ const ReviewPage = () => {
};
const defaultColumns = [
{
title: 'Key',
@ -454,11 +478,49 @@ const ReviewPage = () => {
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: 'Submitted',
dataIndex: '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={{
@ -499,11 +561,24 @@ const ReviewPage = () => {
dataIndex: 'revised',
key: 'revised',
editable: true,
render: (text, record) => {
return <div style={{
color: '#000',
fontWeight: 'bold',
}}>{text}</div>;
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>;
},
},
];

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 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({
timeout: AXIOS_TIMEOUT_MS,

View File

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

View File

@ -9,7 +9,7 @@ import { PrivateRoute, PublicRoute } from './guard-route';
const LoginPage = React.lazy(() => import('pages/login'));
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 ReportsPage = React.lazy(() => import('pages/reports'));

View File

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

View File

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

View File

@ -89,12 +89,12 @@ services:
depends_on:
db-sbt:
condition: service_started
# command: sh -c "chmod -R 777 /app; sleep 5; python manage.py collectstatic --no-input &&
# python manage.py makemigrations &&
# python manage.py migrate &&
# python manage.py compilemessages &&
# 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: sh -c "chmod -R 777 /app; sleep 5; python manage.py collectstatic --no-input &&
python manage.py makemigrations &&
python manage.py migrate &&
python manage.py compilemessages &&
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"
minio:
image: minio/minio
@ -179,8 +179,8 @@ services:
- ./cope2n-api:/app
working_dir: /app
# command: sh -c "celery -A fwd_api.celery_worker.worker worker -l INFO -c 5"
command: bash -c "tail -f > /dev/null"
command: sh -c "celery -A fwd_api.celery_worker.worker worker -l INFO -c 5"
# command: bash -c "tail -f > /dev/null"
# Back-end persistent
db-sbt: