Compare commits

...

29 Commits

Author SHA1 Message Date
1a436dba8a Merge pull request #168 from SDSRV-IDP/feature/add_filter_review2
Feature/add filter review2
2024-12-06 10:01:41 +07:00
yagin
aaf028054c [yagin] change column reivew filters 2024-12-05 17:11:05 +07:00
ab1ec7a59d Merge pull request #167 from SDSRV-IDP/dev/search_content
Fix: misalign text in exclude reason
2024-12-05 16:48:10 +07:00
a106c96f2a Merge pull request #166 from SDSRV-IDP/yagin-hot-fix
Update DocumentCompareInfo.tsx
2024-12-05 16:47:45 +07:00
15a9f31eed Fix: misalign text in exclude reason 2024-12-05 16:46:40 +07:00
Nguyễn Duy Hiển
7bfb1c46b3 Update DocumentCompareInfo.tsx 2024-12-05 16:42:30 +07:00
yagin
53654197af [yagin] fix padding input 2024-12-05 16:36:55 +07:00
Nguyễn Duy Hiển
7767f37c52 Merge pull request #165 from SDSRV-IDP/dev/search_content
Indexing reason for the better perf of searching
2024-12-05 15:33:37 +07:00
d2e0c96ce2 Indexing reason for the better perf of searching 2024-12-05 15:04:13 +07:00
fe56fb91a4 Merge pull request #164 from SDSRV-IDP/feature/add_filter_review2
Feature/add filter review2
2024-12-05 15:01:44 +07:00
Nguyễn Duy Hiển
590a08c119 Merge pull request #163 from SDSRV-IDP/dev/search_content
Dev/search content
2024-12-05 14:46:24 +07:00
yagin
7841a775b7 [yagin] change style form review2 2024-12-05 14:44:49 +07:00
yagin
e51994958e [yagin] add bad reason filter and fix response 2024-12-05 10:30:37 +07:00
478e09408d Index reason for better perfomance 2024-12-04 17:19:17 +07:00
1867259d5e Add: bad_reason filter 2024-12-04 17:07:13 +07:00
55c81a2dcf Fix: settings 2024-12-04 15:47:05 +07:00
5fb227b2f2 Merge pull request #162 from SDSRV-IDP/feature/add_filter_review2
Feature/add filter review2
2024-12-04 15:35:20 +07:00
yagin
c7ca48bbd8 [yagin]fix hot issue and warning 2024-12-04 13:27:39 +07:00
yagin
a6f6eb5a34 [yagin]add condition render for viewer component 2024-12-04 10:57:02 +07:00
bd703c6eb5 Merge pull request #161 from SDSRV-IDP/feature/add_filter_review2
Feature/add filter review2
2024-12-03 16:54:56 +07:00
yagin
8b3cde536b [yagin]add key for SOURCE_KEYS list 2024-12-03 16:47:10 +07:00
yagin
87634b3d3f [yagin] add 4 more filter for review2 2024-12-03 16:18:20 +07:00
40cd3189ac doc_type to be "only_include" 2024-12-02 14:19:04 +07:00
cdfdb0c25b Fix: Dockerfile 2024-11-28 16:45:49 +07:00
dbf9a889da Fix: Build error 2024-11-28 16:45:32 +07:00
69ac10dec3 Add: model index and migrations 2024-11-28 16:45:06 +07:00
a282e9c505 Add: filter to search 2024-11-28 16:44:39 +07:00
669bef9528 Add migrations 2024-11-05 09:56:30 +07:00
3b6b7f9a30 Fix: FE build error 2024-11-04 16:01:41 +07:00
22 changed files with 619 additions and 149 deletions

View File

@ -14,7 +14,7 @@ RUN pip install --upgrade pip
RUN pip install uvicorn gunicorn Celery RUN pip install uvicorn gunicorn Celery
# For intergration with sdskvu # For intergration with sdskvu
RUN pip install pip install torch==1.13.1+cu116 torchvision==0.14.1+cu116 torchaudio==0.13.1 --extra-index-url https://download.pytorch.org/whl/cu116 RUN pip install torch==1.13.1+cu116 torchvision==0.14.1+cu116 torchaudio==0.13.1 --extra-index-url https://download.pytorch.org/whl/cu116
RUN pip install -U openmim==0.3.7 --no-cache-dir RUN pip install -U openmim==0.3.7 --no-cache-dir
RUN mim install mmcv-full==1.7.2 RUN mim install mmcv-full==1.7.2
# RUN pip install fastdeploy-gpu-python==1.0.7 -f https://www.paddlepaddle.org.cn/whl/fastdeploy.html --no-cache-dir # RUN pip install fastdeploy-gpu-python==1.0.7 -f https://www.paddlepaddle.org.cn/whl/fastdeploy.html --no-cache-dir

View File

@ -245,6 +245,7 @@ FIELDS_BY_SUB = {
BAD_THRESHOLD = 0.75 BAD_THRESHOLD = 0.75
NEED_REVIEW = 1.0 NEED_REVIEW = 1.0
DOC_TYPES = ["imei", "invoice"]
SUB_FOR_BILLING = ["all", "seao"] SUB_FOR_BILLING = ["all", "seao"]
FIELD = ["imei_number", "purchase_date", "retailername", "sold_to_party", "invoice_no"] FIELD = ["imei_number", "purchase_date", "retailername", "sold_to_party", "invoice_no"]

View File

@ -78,6 +78,7 @@ class AccuracyViewSet(viewsets.ViewSet):
description='Which subsidiary to be included', description='Which subsidiary to be included',
type=OpenApiTypes.STR, type=OpenApiTypes.STR,
enum=list(settings.SUBS.keys()), enum=list(settings.SUBS.keys()),
required=True
), ),
OpenApiParameter( OpenApiParameter(
name='request_id', name='request_id',
@ -112,6 +113,42 @@ class AccuracyViewSet(viewsets.ViewSet):
type=OpenApiTypes.FLOAT, type=OpenApiTypes.FLOAT,
required=False required=False
), ),
OpenApiParameter(
name='predict_result',
location=OpenApiParameter.QUERY,
description='Filter by predict result',
type=OpenApiTypes.STR,
required=False
),
OpenApiParameter(
name='feedback_result',
location=OpenApiParameter.QUERY,
description='Filter by feedback result',
type=OpenApiTypes.STR,
required=False
),
OpenApiParameter(
name='reviewed_result',
location=OpenApiParameter.QUERY,
description='Filter by reviewed result',
type=OpenApiTypes.STR,
required=False
),
OpenApiParameter(
name='doc_type',
location=OpenApiParameter.QUERY,
description='Filter by document type',
type=OpenApiTypes.STR,
enum=list(settings.DOC_TYPES),
required=False
),
OpenApiParameter(
name='bad_reason',
location=OpenApiParameter.QUERY,
description='Filter by bad reason',
type=OpenApiTypes.STR,
required=False
),
], ],
responses=None, tags=['Accuracy'] responses=None, tags=['Accuracy']
) )
@ -129,7 +166,11 @@ class AccuracyViewSet(viewsets.ViewSet):
include_test = request.GET.get('includes_test', False) include_test = request.GET.get('includes_test', False)
subsidiary = request.GET.get("subsidiary", "all") subsidiary = request.GET.get("subsidiary", "all")
max_accuracy = float(request.GET.get("max_accuracy", 100)) max_accuracy = float(request.GET.get("max_accuracy", 100))
# subsidiary = map_subsidiary_long_to_short(subsidiary) predict_result = request.GET.get('predict_result', None)
feedback_result = request.GET.get('feedback_result', None)
reviewed_result = request.GET.get('reviewed_result', None)
doc_type = request.GET.get('doc_type', None)
bad_reason = request.GET.get('bad_reason', None)
base_query = Q(status=200) base_query = Q(status=200)
if start_date_str or end_date_str: if start_date_str or end_date_str:
@ -193,10 +234,28 @@ class AccuracyViewSet(viewsets.ViewSet):
base_query &= Q( base_query &= Q(
redemption_id__startswith=map_subsidiary_long_to_short(subsidiary)) redemption_id__startswith=map_subsidiary_long_to_short(subsidiary))
if predict_result:
base_query &= Q(predict_result__icontains=predict_result)
if feedback_result:
base_query &= Q(feedback_result__icontains=feedback_result)
if reviewed_result:
base_query &= Q(reviewed_result__icontains=reviewed_result)
if doc_type:
if doc_type.lower() == 'invoice':
base_query &= Q(doc_type__regex=r'^invoice(,\s*invoice)*$')
elif doc_type.lower() == 'imei':
base_query &= Q(doc_type__regex=r'^imei(,\s*imei)*$')
if isinstance(max_accuracy, float): if isinstance(max_accuracy, float):
base_query &= Q(raw_accuracy__lt=( base_query &= Q(raw_accuracy__lt=(
max_accuracy/100)) | Q(raw_accuracy__isnull=True) max_accuracy/100)) | Q(raw_accuracy__isnull=True)
if bad_reason:
bad_reason_subquery = SubscriptionRequestFile.objects.filter(
reason__icontains=bad_reason
).values_list('request_id', flat=True)
base_query &= Q(id__in=bad_reason_subquery)
subscription_requests = SubscriptionRequest.objects.filter( subscription_requests = SubscriptionRequest.objects.filter(
base_query).order_by('created_at') base_query).order_by('created_at')

View File

@ -0,0 +1,41 @@
from django.core.management.base import BaseCommand
from tqdm import tqdm
from fwd_api.models import SubscriptionRequestFile
from fwd_api.models.SemiAutoCorrection import SemiAutoCorrection
from fwd_api.exception.exceptions import InvalidException
# Mapping dictionary for reasons
REASON_MAP = {
'Invalid image': 'invalid_image',
'Missing information': 'missing_information',
'Too blurry text': 'too_blurry_text',
'Too small text': 'too_small_text',
'Handwritten': 'handwritten',
'Recheck': 'recheck',
}
class Command(BaseCommand):
help = 'Replace the reason field in SubscriptionRequestFile and SemiAutoCorrection based on the provided mapping dictionary'
def handle(self, *args, **options):
# Process SubscriptionRequestFile instances
self.update_reasons(SubscriptionRequestFile, "SubscriptionRequestFile")
# Process SemiAutoCorrection instances
self.update_reasons(SemiAutoCorrection, "SemiAutoCorrection")
self.stdout.write(self.style.SUCCESS('All applicable reasons updated successfully!'))
def update_reasons(self, model, model_name):
instances = model.objects.exclude(reason__isnull=True).exclude(reason='').iterator()
for instance in tqdm(instances, desc=f"Updating reasons in {model_name}"):
try:
original_reason = instance.reason
new_reason = REASON_MAP.get(original_reason)
if new_reason is not None:
instance.reason = new_reason
instance.save()
self.stdout.write(self.style.SUCCESS(f"Updated reason for {model_name} ID {instance.id}: {original_reason} -> {new_reason}"))
except Exception as e:
self.stdout.write(self.style.ERROR(f"Updated reason failed for {model_name} ID {instance.id} due to {e}"))

View File

@ -0,0 +1,31 @@
# Generated by Django 4.1.3 on 2024-11-05 02:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fwd_api', '0194_alter_semiautocorrection_feedback_accuracy_and_more'),
]
operations = [
migrations.AlterField(
model_name='subscriptionrequest',
name='request_id',
field=models.CharField(db_index=True, max_length=200),
),
migrations.AlterField(
model_name='userprofile',
name='id',
field=models.AutoField(db_index=True, primary_key=True, serialize=False),
),
migrations.AddIndex(
model_name='subscriptionrequestfile',
index=models.Index(fields=['request', 'index_in_request', 'doc_type'], name='fwd_api_sub_request_890e13_idx'),
),
migrations.AddIndex(
model_name='subscriptionrequestfile',
index=models.Index(fields=['request', 'file_name'], name='fwd_api_sub_request_1a42cd_idx'),
),
]

View File

@ -0,0 +1,26 @@
# Generated by Django 4.1.3 on 2024-11-28 07:10
import django.contrib.postgres.indexes
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('fwd_api', '0195_alter_subscriptionrequest_request_id_and_more'),
]
operations = [
migrations.AddIndex(
model_name='subscriptionrequestfile',
index=django.contrib.postgres.indexes.GinIndex(fields=['predict_result'], name='idx_gin_predict_result'),
),
migrations.AddIndex(
model_name='subscriptionrequestfile',
index=django.contrib.postgres.indexes.GinIndex(fields=['feedback_result'], name='idx_gin_feedback_result'),
),
migrations.AddIndex(
model_name='subscriptionrequestfile',
index=django.contrib.postgres.indexes.GinIndex(fields=['reviewed_result'], name='idx_gin_reviewed_result'),
),
]

View File

@ -0,0 +1,26 @@
# Generated by Django 4.1.3 on 2024-11-28 09:04
import django.contrib.postgres.indexes
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('fwd_api', '0196_subscriptionrequestfile_idx_gin_predict_result_and_more'),
]
operations = [
migrations.AddIndex(
model_name='subscriptionrequest',
index=django.contrib.postgres.indexes.GinIndex(fields=['predict_result'], name='idx_gin_rq_predict_result'),
),
migrations.AddIndex(
model_name='subscriptionrequest',
index=django.contrib.postgres.indexes.GinIndex(fields=['feedback_result'], name='idx_gin_rq_feedback_result'),
),
migrations.AddIndex(
model_name='subscriptionrequest',
index=django.contrib.postgres.indexes.GinIndex(fields=['reviewed_result'], name='idx_gin_rq_reviewed_result'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.1.3 on 2024-11-28 09:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fwd_api', '0197_subscriptionrequest_idx_gin_rq_predict_result_and_more'),
]
operations = [
migrations.AlterField(
model_name='subscriptionrequest',
name='doc_type',
field=models.CharField(db_index=True, max_length=100),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.1.3 on 2024-12-04 10:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fwd_api', '0198_alter_subscriptionrequest_doc_type'),
]
operations = [
migrations.AlterField(
model_name='subscriptionrequestfile',
name='reason',
field=models.TextField(blank=True, db_index=True),
),
]

View File

@ -1,5 +1,6 @@
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.contrib.postgres.indexes import GinIndex
from fwd_api.models.Subscription import Subscription from fwd_api.models.Subscription import Subscription
@ -7,7 +8,7 @@ class SubscriptionRequest(models.Model):
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
pages: int = models.IntegerField() pages: int = models.IntegerField()
pages_left: int = models.IntegerField(default=1) pages_left: int = models.IntegerField(default=1)
doc_type: str = models.CharField(max_length=100) doc_type: str = models.CharField(max_length=100, db_index=True)
request_id = models.CharField(max_length=200, db_index=True) # Change to request_id request_id = models.CharField(max_length=200, db_index=True) # Change to request_id
redemption_id = models.CharField(max_length=200, null=True) redemption_id = models.CharField(max_length=200, null=True)
process_type = models.CharField(max_length=200) # driver/id/invoice process_type = models.CharField(max_length=200) # driver/id/invoice
@ -39,3 +40,10 @@ class SubscriptionRequest(models.Model):
is_reviewed = models.BooleanField(default=False) is_reviewed = models.BooleanField(default=False)
is_required = models.BooleanField(default=True) is_required = models.BooleanField(default=True)
subsidiary = models.CharField(default="", null=True, max_length=200) subsidiary = models.CharField(default="", null=True, max_length=200)
class Meta:
indexes = [
GinIndex(fields=['predict_result'], name='idx_gin_rq_predict_result'),
GinIndex(fields=['feedback_result'], name='idx_gin_rq_feedback_result'),
GinIndex(fields=['reviewed_result'], name='idx_gin_rq_reviewed_result'),
]

View File

@ -1,5 +1,5 @@
from django.db import models from django.db import models
from django.contrib.postgres.indexes import GinIndex
from fwd_api.constant.common import FileCategory from fwd_api.constant.common import FileCategory
from fwd_api.models import SubscriptionRequest from fwd_api.models import SubscriptionRequest
from fwd_api.models.fields.EncryptedCharField import EncryptedCharField from fwd_api.models.fields.EncryptedCharField import EncryptedCharField
@ -23,7 +23,7 @@ class SubscriptionRequestFile(models.Model):
doc_type = models.CharField(max_length=10, default="") doc_type = models.CharField(max_length=10, default="")
index_in_request = models.IntegerField(default=0) # by doc_type index_in_request = models.IntegerField(default=0) # by doc_type
processing_time = models.FloatField(default=-1) # in milisecond processing_time = models.FloatField(default=-1) # in milisecond
reason = models.TextField(blank=True) reason = models.TextField(blank=True, db_index=True)
counter_measures = models.TextField(blank=True) counter_measures = models.TextField(blank=True)
is_reviewed = models.BooleanField(default=False) is_reviewed = models.BooleanField(default=False)
is_required = models.BooleanField(default=True) is_required = models.BooleanField(default=True)
@ -40,5 +40,8 @@ class SubscriptionRequestFile(models.Model):
indexes = [ indexes = [
models.Index(fields=['request', 'index_in_request', 'doc_type']), # For updating results models.Index(fields=['request', 'index_in_request', 'doc_type']), # For updating results
models.Index(fields=['request', 'file_name']), # for getting image files by AI models.Index(fields=['request', 'file_name']), # for getting image files by AI
GinIndex(fields=['predict_result'], name='idx_gin_predict_result'),
GinIndex(fields=['feedback_result'], name='idx_gin_feedback_result'),
GinIndex(fields=['reviewed_result'], name='idx_gin_reviewed_result'),
] ]

View File

@ -83,7 +83,7 @@
"prettier-plugin-organize-imports": "^3.2.1", "prettier-plugin-organize-imports": "^3.2.1",
"rollup-plugin-visualizer": "^5.9.0", "rollup-plugin-visualizer": "^5.9.0",
"sass": "^1.57.1", "sass": "^1.57.1",
"typescript": "^4.9.4", "typescript": "^5.6.3",
"vite": "^4.0.3", "vite": "^4.0.3",
"vite-plugin-svgr": "^2.4.0", "vite-plugin-svgr": "^2.4.0",
"vite-tsconfig-paths": "^4.0.3" "vite-tsconfig-paths": "^4.0.3"

View File

@ -1,4 +1,4 @@
import { AppstoreOutlined, BarChartOutlined, RotateRightOutlined, FileSearchOutlined } from '@ant-design/icons'; import { AppstoreOutlined, BarChartOutlined, RotateRightOutlined } from '@ant-design/icons';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Menu, MenuProps } from 'antd'; import { Menu, MenuProps } from 'antd';
import React from 'react'; import React from 'react';

View File

@ -4,7 +4,6 @@ export function GlobalSpin() {
return ( return (
<Spin <Spin
size='large' size='large'
tip='Loading ...'
style={{ style={{
position: 'fixed', position: 'fixed',
top: '50%', top: '50%',

View File

@ -40,6 +40,7 @@
"Remove this image from the evaluation report": "Remove this image from the evaluation report", "Remove this image from the evaluation report": "Remove this image from the evaluation report",
"Report Details": "Report Details", "Report Details": "Report Details",
"Report Filters": "Report Filters", "Report Filters": "Report Filters",
"Review Filters": "Review Filters",
"Report Type": "Report Type", "Report Type": "Report Type",
"Reports": "Reports", "Reports": "Reports",
"Retry": "Retry", "Retry": "Retry",
@ -70,5 +71,9 @@
"You are only allowed to upload {0} file.": "You are only allowed to upload {0} file.", "You are only allowed to upload {0} file.": "You are only allowed to upload {0} file.",
"You have unsaved changes!": "You have unsaved changes!", "You have unsaved changes!": "You have unsaved changes!",
"Your current password has expired. Please change your password to continue.": "Your current password has expired. Please change your password to continue.", "Your current password has expired. Please change your password to continue.": "Your current password has expired. Please change your password to continue.",
"max_accuracy": "Max accuracy" "max_accuracy": "Max accuracy",
"docType": "Only type",
"Feedback Result": "Feedback Result",
"Predict Result": "Predict Result",
"Reviewed Result": "Reviewed Result"
} }

View File

@ -40,6 +40,7 @@
"Remove this image from the evaluation report": "", "Remove this image from the evaluation report": "",
"Report Details": "", "Report Details": "",
"Report Filters": "", "Report Filters": "",
"Review Filters": "",
"Report Type": "", "Report Type": "",
"Reports": "", "Reports": "",
"Retry": "Thử lại", "Retry": "Thử lại",
@ -70,5 +71,9 @@
"You are only allowed to upload {0} file.": "Bạn chỉ được phép tải lên {0}.", "You are only allowed to upload {0} file.": "Bạn chỉ được phép tải lên {0}.",
"You have unsaved changes!": "Bạn có những thay đổi chưa được lưu!", "You have unsaved changes!": "Bạn có những thay đổi chưa được lưu!",
"Your current password has expired. Please change your password to continue.": "", "Your current password has expired. Please change your password to continue.": "",
"max_accuracy": "Độ chính xác tối đa" "max_accuracy": "Độ chính xác tối đa",
"DocType": "Kiểu tài liệu",
"Feedback Result": "",
"Predict Result": "",
"Reviewed Result": ""
} }

View File

@ -0,0 +1,69 @@
import React from 'react';
import { Button, Descriptions, Input } from 'antd';
import type { DescriptionsProps } from 'antd';
import { CopyOutlined } from '@ant-design/icons';
import { FEEDBACK_RESULT, PREDICTED_RESULT, REVIEWED_RESULT } from './const';
const DocumentCompareInfo = ({ key, data, selectedFileDataSource, updateRevisedByFeedback, handleUpdateFileInField, shouldRevised, disabledInput }) => {
const items: DescriptionsProps['items'] = [
{
key: selectedFileDataSource[data]?.[FEEDBACK_RESULT] || '1',
label: 'Feedback',
children: selectedFileDataSource[data]?.[FEEDBACK_RESULT],
labelStyle: { color: '#333', padding: '4px 16px' },
contentStyle: { padding: '4px 16px' },
span: 3
},
{
key: selectedFileDataSource[data]?.[PREDICTED_RESULT] || '2',
label: 'Predicted',
children: selectedFileDataSource[data]?.[PREDICTED_RESULT],
labelStyle: { color: '#333', padding: '4px 16px' },
contentStyle: { padding: '4px 16px' },
span: 3
},
{
key: selectedFileDataSource[data]?.[REVIEWED_RESULT] || '3',
label: 'Revised',
children: <Input
style={{ background: shouldRevised ? 'yellow' : '', padding: '0' }}
value={selectedFileDataSource[data]?.[REVIEWED_RESULT]}
size='small'
onChange={(e) =>
handleUpdateFileInField(data, e.target.value)
}
variant="borderless"
disabled={disabledInput === undefined || disabledInput === 0}
/>,
labelStyle: { color: '#333', padding: '4px 16px' },
contentStyle: { padding: '4px 16px' },
span: 3
},
];
return (
<div style={{ margin: '0 0 8px' }} className='file-input-group' key={key}>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
margin: '0 0 4px',
}}
>
<p style={{ fontWeight: 'bold', margin: 0 }}>{data}</p>
<Button
shape='round'
type='primary'
ghost
icon={<CopyOutlined />}
size='small'
onClick={() => updateRevisedByFeedback(data)}
disabled={disabledInput === undefined || disabledInput === 0}
/>
</div>
<Descriptions bordered items={items} layout="horizontal" size='small' contentStyle={{ height: '13px' }} />
</div>
)
}
export default DocumentCompareInfo;

View File

@ -0,0 +1,39 @@
import React from 'react';
import { Descriptions } from 'antd';
import type { DescriptionsProps } from 'antd';
const DocumentHeadInfo = ({ currentRequest }) => {
const items: DescriptionsProps['items'] = [
{
key: '1',
label: 'Request ID',
children: currentRequest?.RequestID,
span: 2,
labelStyle: { color: '#333', width: '200px' }
},
{
key: '2',
label: 'Raw accuracy',
children: currentRequest?.raw_accuracy,
labelStyle: { color: '#333', width: '200px' }
},
{
key: '3',
label: 'Redemption ID',
children: currentRequest?.RedemptionID,
span: 2,
labelStyle: { color: '#333', width: '200px' }
},
{
key: '4',
label: 'Processing time',
children: currentRequest?.['Server Processing Time (ms)'],
labelStyle: { color: '#333', width: '200px' }
}
];
return (
<Descriptions bordered items={items} size="small" />
)
}
export default DocumentHeadInfo;

View File

@ -2,10 +2,16 @@ import { baseURL } from 'request/api';
import { RecentRequest } from './const'; import { RecentRequest } from './const';
export const fetchAllRequests = async ( export const fetchAllRequests = async (
filterDateRange, filterDateRange: any[],
filterSubsidiaries, filterSubsidiaries: string,
filterReviewState, filterReviewState: string,
filterIncludeTests, filterIncludeTests: string,
filterDoctype: string,
filterFeedbackResult: string,
filterPredictResult: string,
filterReviewedResult: string,
filterBadReason: string,
filterOtherReason: string,
page = 1, page = 1,
page_size = 20, page_size = 20,
max_accuracy = 100, max_accuracy = 100,
@ -25,6 +31,25 @@ export const fetchAllRequests = async (
if (filterIncludeTests) { if (filterIncludeTests) {
filterStr += `includes_test=${filterIncludeTests}&`; filterStr += `includes_test=${filterIncludeTests}&`;
} }
// add 4 more field
if (filterDoctype) {
filterStr += `doc_type=${filterDoctype}&`;
}
if (filterFeedbackResult) {
filterStr += `feedback_result=${filterFeedbackResult}&`;
}
if (filterPredictResult) {
filterStr += `predict_result=${filterPredictResult}&`;
}
if (filterReviewedResult) {
filterStr += `reviewed_result=${filterReviewedResult}&`;
}
if (filterBadReason === 'other' && filterOtherReason.trim()) {
filterStr += `bad_reason=${filterOtherReason}&`;
} else if(filterBadReason !== 'other') {
filterStr += `bad_reason=${filterBadReason}&`;
}
//
if (startDate && endDate) { if (startDate && endDate) {
filterStr += `start_date=${startDate}&end_date=${endDate}&`; filterStr += `start_date=${startDate}&end_date=${endDate}&`;
} }
@ -77,7 +102,7 @@ export const updateRevisedDataByFile = async (
) )
}; };
export const fetchRequest = async (id) => { export const fetchRequest = async (id: string) => {
const token = localStorage.getItem('sbt-token') || ''; const token = localStorage.getItem('sbt-token') || '';
const response = await fetch(`${baseURL}/ctel/request/${id}/`, { const response = await fetch(`${baseURL}/ctel/request/${id}/`, {
method: 'GET', method: 'GET',
@ -87,7 +112,7 @@ export const fetchRequest = async (id) => {
}); });
return await ( return await (
await response.json() await response.json()
).subscription_requests[0]; ).subscription_requests?.[0];
}; };
export const addRecentRequest = ( export const addRecentRequest = (

View File

@ -48,6 +48,12 @@ export const SUBSIDIARIES = [
{ value: 'SEIN', label: 'SEIN' }, { value: 'SEIN', label: 'SEIN' },
]; ];
export const DOCTYPE = [
{ value: null, label: '--' },
{ value: 'imei', label: 'imei' },
{ value: 'invoice', label: 'invoice' },
];
export const SOURCE_KEYS = [ export const SOURCE_KEYS = [
'retailername', 'retailername',
'sold_to_party', 'sold_to_party',

View File

@ -3,7 +3,6 @@ import {
ArrowRightOutlined, ArrowRightOutlined,
CheckCircleOutlined, CheckCircleOutlined,
ClockCircleFilled, ClockCircleFilled,
CopyOutlined,
FullscreenExitOutlined, FullscreenExitOutlined,
FullscreenOutlined, FullscreenOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
@ -41,6 +40,7 @@ import {
} from './api'; } from './api';
import { import {
counter_measure_map, counter_measure_map,
DOCTYPE,
FEEDBACK_ACCURACY, FEEDBACK_ACCURACY,
FEEDBACK_RESULT, FEEDBACK_RESULT,
PREDICTED_RESULT, PREDICTED_RESULT,
@ -54,6 +54,8 @@ import {
import FileCard from './FileCard'; import FileCard from './FileCard';
import RecentRequest from './RecentRequest'; import RecentRequest from './RecentRequest';
import './style.css'; import './style.css';
import DocumentHeadInfo from './DocumentHeadInfo';
import DocumentCompareInfo from './DocumentCompareInfo';
const ReviewPage = () => { const ReviewPage = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -71,6 +73,13 @@ const ReviewPage = () => {
const [filterAccuracy, setFilterAccuracy] = useState(100); const [filterAccuracy, setFilterAccuracy] = useState(100);
const [filterReviewState, setFilterReviewState] = useState('all'); const [filterReviewState, setFilterReviewState] = useState('all');
const [filterIncludeTests, setFilterIncludesTests] = useState('true'); const [filterIncludeTests, setFilterIncludesTests] = useState('true');
const [filterDoctype, setFilterDoctype] = useState(null);
const [filterFeedbackResult, setFilterFeedbackResult] = useState('');
const [filterPredictResult, setFilterPredictResult] = useState('');
const [filterReviewedResult, setFilterReviewedResult] = useState('');
const [filterBadReason, setFilterBadReason] = useState('other');
const [filterOtherReason, setFilterOtherReason] = useState('');
// const [requests, setRequests] = useState([]); // const [requests, setRequests] = useState([]);
const [currentRequest, setCurrentRequest] = useState(null); const [currentRequest, setCurrentRequest] = useState(null);
const [currentRequestIndex, setCurrentRequestIndex] = useState(1); const [currentRequestIndex, setCurrentRequestIndex] = useState(1);
@ -109,6 +118,12 @@ const ReviewPage = () => {
filterSubsidiaries, filterSubsidiaries,
filterReviewState, filterReviewState,
filterIncludeTests, filterIncludeTests,
filterDoctype,
filterFeedbackResult,
filterPredictResult,
filterReviewedResult,
filterBadReason,
filterOtherReason,
1, 1,
1, 1,
filterAccuracy, filterAccuracy,
@ -116,7 +131,7 @@ const ReviewPage = () => {
setTotalPages(data?.page?.total_requests); setTotalPages(data?.page?.total_requests);
setHasNextRequest(1 < data?.page?.total_requests); setHasNextRequest(1 < data?.page?.total_requests);
const firstRequest = fetchRequest( const firstRequest = fetchRequest(
data?.subscription_requests[0].RequestID, data?.subscription_requests?.[0]?.RequestID,
); );
firstRequest.then(async (data) => { firstRequest.then(async (data) => {
if (data) setCurrentRequest(data); if (data) setCurrentRequest(data);
@ -163,7 +178,7 @@ const ReviewPage = () => {
const setAndLoadSelectedFile = async (requestData, index) => { const setAndLoadSelectedFile = async (requestData, index) => {
setSelectedFileId(index); setSelectedFileId(index);
if (!requestData['Files'][index]) { if (!requestData?.['Files'][index]) {
setSelectedFileData('FAILED_TO_LOAD_FILE'); setSelectedFileData('FAILED_TO_LOAD_FILE');
setImageLoading(false); setImageLoading(false);
return; return;
@ -218,6 +233,12 @@ const ReviewPage = () => {
filterSubsidiaries, filterSubsidiaries,
filterReviewState, filterReviewState,
filterIncludeTests, filterIncludeTests,
filterDoctype,
filterFeedbackResult,
filterPredictResult,
filterReviewedResult,
filterBadReason,
filterOtherReason,
requestIndex, requestIndex,
1, 1,
filterAccuracy, filterAccuracy,
@ -273,6 +294,12 @@ const ReviewPage = () => {
filterSubsidiaries, filterSubsidiaries,
filterReviewState, filterReviewState,
filterIncludeTests, filterIncludeTests,
filterDoctype,
filterFeedbackResult,
filterPredictResult,
filterReviewedResult,
filterBadReason,
filterOtherReason,
1, 1,
1, 1,
filterAccuracy, filterAccuracy,
@ -474,42 +501,10 @@ const ReviewPage = () => {
> >
{fullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />} {fullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
</Button> </Button>
</div>
{totalRequests && ( {totalRequests && (
<div <DocumentHeadInfo currentRequest={currentRequest} />
style={{
flexGrow: 1,
display: 'grid',
gridTemplateColumns: '1fr 1fr 1fr',
marginLeft: '16px',
}}
>
<div style={{ gridColumn: 'span 2 / span 2' }}>
<b>Request ID: &nbsp;</b>
{currentRequest?.RequestID}
</div>{' '}
<div>
<b>Created at: &nbsp;</b>
{currentRequest?.created_at}
</div>{' '}
<div>
<b>Request time: &nbsp;</b>
{currentRequest?.['Client Request Time (ms)']}
</div>{' '}
<div style={{ gridColumn: 'span 2 / span 2' }}>
<b>Redemption ID: &nbsp;</b>
{currentRequest?.RedemptionID}
</div>{' '}
<div>
<b>Raw accuracy: &nbsp;</b>
{currentRequest?.raw_accuracy}
</div>{' '}
<div style={{ gridColumn: 'span 2 / span 2' }}>
<b>Processing time: &nbsp;</b>
{currentRequest?.['Server Processing Time (ms)']}
</div>{' '}
</div>
)} )}
</div>
{totalRequests > 0 && ( {totalRequests > 0 && (
<div <div
style={{ style={{
@ -561,17 +556,17 @@ const ReviewPage = () => {
overflow: 'auto', overflow: 'auto',
}} }}
> >
<Viewer {selectedFileData && <Viewer
plugins={[defaultLayoutPluginInstance]} plugins={[defaultLayoutPluginInstance]}
fileUrl={selectedFileData} fileUrl={selectedFileData}
onDocumentLoad={() => setImageLoading(false)} onDocumentLoad={() => setImageLoading(false)}
/> />}
</div> </div>
) : ( ) : (
<div <div
style={{ style={{
flexGrow: 1, flexGrow: 1,
overflow: 'auto', overflowY: 'auto',
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
@ -739,8 +734,7 @@ const ReviewPage = () => {
> >
<Input <Input
size='middle' size='middle'
value={`Sub: ${filterSubsidiaries}, Date:${ value={`Sub: ${filterSubsidiaries}, Date:${filterDateRange[0]
filterDateRange[0]
? filterDateRange[0] + ' to ' + filterDateRange[1] ? filterDateRange[0] + ' to ' + filterDateRange[1]
: 'All' : 'All'
}, Reviewed: ${filterReviewState}, Tests: ${filterIncludeTests}`} }, Reviewed: ${filterReviewState}, Tests: ${filterIncludeTests}`}
@ -764,54 +758,21 @@ const ReviewPage = () => {
let shouldRevised = false; let shouldRevised = false;
try { try {
if ( if (
selectedFileDataSource[data]?.[FEEDBACK_ACCURACY].length > 0 selectedFileDataSource[data]?.[FEEDBACK_ACCURACY]?.length > 0
) { ) {
shouldRevised = shouldRevised =
selectedFileDataSource[data][FEEDBACK_ACCURACY][0] < 1; selectedFileDataSource[data][FEEDBACK_ACCURACY][0] < 1;
} }
} catch (error) { } } catch (error) { }
return ( return (
<div style={{ margin: '0 0 8px' }} className='file-input-group'> <DocumentCompareInfo key={data}
<div data={data}
style={{ selectedFileDataSource={selectedFileDataSource}
display: 'flex', updateRevisedByFeedback={updateRevisedByFeedback}
justifyContent: 'space-between', handleUpdateFileInField={handleUpdateFileInField}
alignItems: 'center', shouldRevised={shouldRevised}
margin: '0 0 4px', disabledInput = {currentRequest?.Files?.length}
}}
>
<p style={{ fontWeight: 'bold', margin: 0 }}>{data}</p>
<Button
shape='round'
type='primary'
ghost
icon={<CopyOutlined />}
size='small'
onClick={() => updateRevisedByFeedback(data)}
/> />
</div>
<Input
addonBefore='Feedback'
size='small'
readOnly
value={selectedFileDataSource[data]?.[FEEDBACK_RESULT]}
/>
<Input
addonBefore='Predicted'
readOnly
size='small'
value={selectedFileDataSource[data]?.[PREDICTED_RESULT]}
/>
<Input
addonBefore='Revised'
style={{ background: shouldRevised ? 'yellow' : '' }}
size='small'
value={selectedFileDataSource[data]?.[REVIEWED_RESULT]}
onChange={(e) =>
handleUpdateFileInField(data, e.target.value)
}
/>
</div>
); );
})} })}
<b>{t`Bad image reason:`}</b> <b>{t`Bad image reason:`}</b>
@ -875,7 +836,7 @@ const ReviewPage = () => {
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button <Button
type='primary' type='primary'
color='success' // color='success'
size='middle' size='middle'
style={{ style={{
height: '36px', height: '36px',
@ -888,7 +849,7 @@ const ReviewPage = () => {
</div> </div>
</div> </div>
<Modal <Modal
title={t`Report Filters`} title={t`Review Filters`}
open={isModalOpen} open={isModalOpen}
width={700} width={700}
onOk={() => { onOk={() => {
@ -903,6 +864,16 @@ const ReviewPage = () => {
style={{ style={{
marginTop: 30, marginTop: 30,
}} }}
layout="vertical"
>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
marginLeft: 0,
padding: 0,
}}
> >
<Form.Item <Form.Item
name='dateRange' name='dateRange'
@ -914,20 +885,54 @@ const ReviewPage = () => {
}, },
]} ]}
style={{ style={{
marginBottom: 24, flex: 1,
}} }}
> >
<DatePicker.RangePicker <DatePicker.RangePicker
onChange={(date, dateString) => { onChange={(date, dateString) => {
setFilterDateRange(dateString); setFilterDateRange(dateString);
}} }}
style={{ width: 200 }} style={{ width: 300 }}
/> />
</Form.Item> </Form.Item>
<div style={{
flex: 1,
}}>
<Form.Item
name='bad_reason'
label={t`Bad image reason`}
>
<Select
placeholder='Select a reason'
style={{ width: 300 }}
options={REASON_BAD_QUALITY}
value={filterBadReason}
defaultValue={filterBadReason}
onChange={setFilterBadReason}
/>
</Form.Item>
{filterBadReason === 'other' && (
<Form.Item
name='other_reason'
style={{
flex: 1,
}}
>
<Input
placeholder='Other reason'
value={filterOtherReason}
style={{ width: 300 }}
onChange={(e) => {
setFilterOtherReason(e.target.value);
}}
/>
</Form.Item>
)}
</div>
</div>
<div <div
style={{ style={{
marginTop: 10,
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
marginLeft: 0, marginLeft: 0,
@ -943,10 +948,13 @@ const ReviewPage = () => {
message: 'Please select a subsidiary', message: 'Please select a subsidiary',
}, },
]} ]}
style={{
flex: 1,
}}
> >
<Select <Select
placeholder='Select a subsidiary' placeholder='Select a subsidiary'
style={{ width: 200 }} style={{ width: 300 }}
options={SUBSIDIARIES} options={SUBSIDIARIES}
value={filterSubsidiaries} value={filterSubsidiaries}
defaultValue={filterSubsidiaries} defaultValue={filterSubsidiaries}
@ -962,8 +970,12 @@ const ReviewPage = () => {
message: 'Please select max accuracy', message: 'Please select max accuracy',
}, },
]} ]}
style={{
flex: 1,
}}
> >
<InputNumber <InputNumber
style={{ width: 300 }}
min={1} min={1}
max={100} max={100}
defaultValue={filterAccuracy} defaultValue={filterAccuracy}
@ -973,7 +985,7 @@ const ReviewPage = () => {
</div> </div>
<div <div
style={{ style={{
marginTop: 10,
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
marginLeft: 0, marginLeft: 0,
@ -989,9 +1001,12 @@ const ReviewPage = () => {
message: 'Please select review status', message: 'Please select review status',
}, },
]} ]}
style={{
flex: 1,
}}
> >
<Select <Select
style={{ width: 200 }} style={{ width: 300 }}
options={[ options={[
{ label: 'All', value: 'all' }, { label: 'All', value: 'all' },
{ label: 'Reviewed', value: 'reviewed' }, { label: 'Reviewed', value: 'reviewed' },
@ -1011,10 +1026,10 @@ const ReviewPage = () => {
message: 'Please select test status', message: 'Please select test status',
}, },
]} ]}
style={{ marginLeft: 16 }} style={{ flex: 1 }}
> >
<Select <Select
style={{ width: 200 }} style={{ width: 300 }}
options={[ options={[
{ label: 'Include tests', value: 'true' }, { label: 'Include tests', value: 'true' },
{ label: 'Exclude tests', value: 'false' }, { label: 'Exclude tests', value: 'false' },
@ -1025,6 +1040,82 @@ const ReviewPage = () => {
/> />
</Form.Item> </Form.Item>
</div> </div>
{/* add 4 more filter fields */}
<div
style={{
display: 'flex',
justifyContent: 'space-between',
marginLeft: 0,
padding: 0,
}}
>
<Form.Item
name='doc_type'
label={t`Only type`}
style={{
flex: 1,
}}
>
<Select
placeholder='Select a document type'
style={{ width: 300 }}
options={DOCTYPE}
value={filterDoctype}
defaultValue={filterDoctype}
onChange={setFilterDoctype}
/>
</Form.Item>
<Form.Item
name='feedback_result'
label={t`Feedback includes`}
style={{
flex: 1,
}}
>
<Input
style={{ width: 300 }}
defaultValue={filterFeedbackResult}
onChange={(e) => setFilterFeedbackResult(e.target.value)}
/>
</Form.Item>
</div>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
marginLeft: 0,
padding: 0,
}}
>
<Form.Item
name='predict_result'
label={t`Predict includes`}
style={{
flex: 1,
}}
>
<Input
style={{ width: 300 }}
defaultValue={filterPredictResult}
onChange={(e) => setFilterPredictResult(e.target.value)}
/>
</Form.Item>
<Form.Item
name='reviewed_result'
label={t`Review includes`}
style={{
flex: 1,
}}
>
<Input
style={{ width: 300 }}
defaultValue={filterReviewedResult}
onChange={(e) => setFilterReviewedResult(e.target.value)}
/>
</Form.Item>
</div>
</Form> </Form>
</Modal> </Modal>
<Modal <Modal

View File

@ -11,7 +11,7 @@ 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://107.120.133.27:9881/api' : '/api'; export const baseURL = environment === 'development' ? 'http://107.120.133.27:19001/api' : '/api';
// export const baseURL = '/api'; // export const baseURL = '/api';