import json import time import logging import traceback from datetime import datetime, timedelta from collections import OrderedDict from . import app from apps.doc.models import AFCOCRResult, HILOCRResult, AFCComparisonInfo, HILComparisonInfo, Configs, HILCompareReport, AFCCompareReport from apps.doc import consts from apps.doc.ocr.gcap import gcap from apps.doc.exceptions import GCAPException from apps.doc.named_enum import RequestTeam, RequestTrigger, ProcessName from common.tools.comparison import cp compare_log = logging.getLogger('compare') log_base = '[CA Compare]' def get_order_dict(src_dict, order_tuple): # if consts.SECOND_ID_TYPE_FIELD in src_dict: # if src_dict.get(consts.SECOND_ID_TYPE_FIELD) not in consts.SECOND_ID_TYPE_COMPARE: # src_dict.pop(consts.SECOND_ID_TYPE_FIELD, None) # src_dict.pop(consts.SECOND_ID_FIELD, None) order_dict = OrderedDict({}) for field in order_tuple: if field in src_dict: order_dict[field] = src_dict[field] return order_dict def do_not_compare(info_dict, compare_list): for compare_tuple in compare_list: info_dict[compare_tuple[4]] = consts.RESULT_NA if compare_tuple[0] in info_dict: del info_dict[compare_tuple[0]] def field_compare(info_dict, ocr_res_dict, ocr_field, compare_list, res_set, has_expiry_date=False, sep_name=None, capital_ignore=False): is_find = False ocr_res_str = ocr_res_dict.get(ocr_field) if ocr_res_str is not None: ocr_res_list = json.loads(ocr_res_str) # length = len(ocr_res_list) # sep营业执照根据法人过滤 if isinstance(sep_name, str): tmp_list = [] for ocr_res in ocr_res_list: ocr_sep_name = ocr_res.get(consts.LEGAL_REP_NAME) if isinstance(ocr_sep_name, str) and ocr_sep_name == sep_name: tmp_list.append(ocr_res) else: tmp_list = ocr_res_list length = len(tmp_list) # 过期期限特殊处理 if has_expiry_date: expiry_dates = [] key = compare_list[2][1] for ocr_res in tmp_list: # for ocr_res in ocr_res_list: if ocr_res.get(key): expiry_dates.append(ocr_res.get(key)) else: expiry_dates = [] for ocr_res in tmp_list: # for ocr_res in ocr_res_list: if is_find: break for idx, compare_tuple in enumerate(compare_list): input_str = info_dict.get(compare_tuple[0]) ocr_str = ocr_res.get(compare_tuple[1]) compare_res, ocr_output = getattr(cp, compare_tuple[2])( input_str, ocr_str, idx, **compare_tuple[3]) # print('type: {0}, idx: {1}, field: {2}, input: {3}, ocr: {4}, res: {5}, out: {6}'.format(ocr_field, idx, compare_tuple[0], input_str, ocr_str, compare_res, ocr_output)) if idx == 0 and compare_res in [consts.RESULT_N, consts.RESULT_NA] and length > 1: break is_find = True # 过期期限特殊处理 if idx == 2 and has_expiry_date and compare_res == consts.RESULT_NA: for expiry_date in expiry_dates: expiry_date_compare_res, expiry_date_ocr_output = getattr(cp, compare_tuple[2])( input_str, expiry_date, idx, **compare_tuple[3] ) if expiry_date_compare_res == consts.RESULT_Y: compare_res = consts.RESULT_Y ocr_output = expiry_date_ocr_output ocr_str = expiry_date break info_dict[compare_tuple[4]] = compare_res if input_str is not None: if ocr_str is None or ocr_output is None: del info_dict[compare_tuple[0]] else: info_dict[compare_tuple[0]] = ocr_output if not capital_ignore or idx != 1: res_set.add(compare_res) if not is_find: res_set.add(consts.RESULT_N) for compare_tuple in compare_list: info_dict[compare_tuple[4]] = consts.RESULT_NA if compare_tuple[0] in info_dict: del info_dict[compare_tuple[0]] def usedcar_info_compare(info_dict, ocr_res_dict, ocr_field, compare_list, res_set): no_match_vino = [] is_find = False key_right = False ocr_res_str = ocr_res_dict.get(ocr_field) if ocr_res_str is not None: ocr_res_list = json.loads(ocr_res_str) # 3、4页/副页去除 # if ocr_field == consts.MVC_OCR_FIELD: tmp_list = [] for res in ocr_res_list: if res.get(compare_list[0][1]) is not None: tmp_list.append(res) length = len(tmp_list) for ocr_res in tmp_list: if is_find: break for idx, compare_tuple in enumerate(compare_list): input_str = info_dict.get(compare_tuple[0]) ocr_str = ocr_res.get(compare_tuple[1]) compare_res, ocr_output = getattr(cp, compare_tuple[2])( input_str, ocr_str, idx, **compare_tuple[3]) # print('type: {0}, idx: {1}, field: {2}, input: {3}, ocr: {4}, res: {5}, out: {6}'.format(ocr_field, idx, compare_tuple[0], input_str, ocr_str, compare_res, ocr_output)) if idx == 0 and compare_res in [consts.RESULT_N, consts.RESULT_NA]: if ocr_str is not None: no_match_vino.append(ocr_str) if length > 1: break is_find = True if idx == 0 and compare_res == consts.RESULT_Y: key_right = True info_dict[compare_tuple[4]] = compare_res if input_str is not None: if idx != 0: if ocr_str is None or ocr_output is None: del info_dict[compare_tuple[0]] else: info_dict[compare_tuple[0]] = ocr_output res_set.add(compare_res) if not is_find: res_set.add(consts.RESULT_N) for idx, compare_tuple in enumerate(compare_list): info_dict[compare_tuple[4]] = consts.RESULT_NA if idx == 0: continue if compare_tuple[0] in info_dict: del info_dict[compare_tuple[0]] return is_find and key_right, no_match_vino @app.task def compare(application_id, application_entity, uniq_seq, ocr_res_id): # POS: application_id, application_entity, uniq_seq, None # OCR: application_id, business_type(application_entity), None, ocr_res_id compare_log.info('{0} [receive task] [entity={1}] [id={2}] [uniq_seq={3}] [ocr_res_id={4}]'.format( log_base, application_entity, application_id, uniq_seq, ocr_res_id)) # 根据application_id查找最新的比对信息,如果没有,结束 comparison_class = HILComparisonInfo if application_entity == consts.HIL_PREFIX else AFCComparisonInfo last_obj = comparison_class.objects.filter(application_id=application_id).last() if last_obj is None: compare_log.info('{0} [comparison info empty] [entity={1}] [id={2}] [uniq_seq={3}] [ocr_res_id={4}]'.format( log_base, application_entity, application_id, uniq_seq, ocr_res_id)) return # 根据application_id查找OCR累计结果指定license字段,如果没有,结束 result_class = HILOCRResult if application_entity == consts.HIL_PREFIX else AFCOCRResult if ocr_res_id is None: ocr_res_dict = result_class.objects.filter(application_id=application_id).values(*consts.COMPARE_FIELDS).first() else: ocr_res_dict = result_class.objects.filter(id=ocr_res_id).values(*consts.COMPARE_FIELDS).first() if ocr_res_dict is None: compare_log.info('{0} [ocr info empty] [entity={1}] [id={2}] [uniq_seq={3}] [ocr_res_id={4}]'.format( log_base, application_entity, application_id, uniq_seq, ocr_res_id )) return start_time = datetime.now() compare_failed = False # 比对信息 try: comparison_res = OrderedDict({ 'OCR_Input': { 'uniqSeq': last_obj.uniq_seq, 'applicationId': application_id, 'applicationEntity': application_entity, 'applicationVersion': last_obj.application_version, 'vehicleStatus': last_obj.vehicle_status, 'wholeResult': 'N', 'wholeResultMessage': '', 'applicationLink': r'//china.bmw.corp/WINFS/SF-CN-data/SF3-CN-S/SF3-CN-S-1-New Business/CA/OCR II test', } }) res_set = set() # is_sep = True if last_obj.customer_type == consts.CUSTOMER_TYPE[5] else False individual_cus_info_list = json.loads(last_obj.individual_cus_info) order_individual_cus_info_list = [] for individual_cus_info in individual_cus_info_list: order_individual_cus_info = get_order_dict(individual_cus_info, consts.IN_ORDER) cus_type = order_individual_cus_info.get('customerType') # 获取sep下营业执照法人代表 if cus_type == consts.CUSTOMER_TYPE[5]: sep_name = order_individual_cus_info.get('customerChineseName') if isinstance(sep_name, str): sep_name = sep_name.strip() if sep_name == '': sep_name = None else: sep_name = None # 个人信息证件 id_type = order_individual_cus_info.get('idType') if cus_type in consts.NO_COMPARE_CUSTOMER_TYPE or id_type not in consts.ID_TYPE_COMPARE: do_not_compare(order_individual_cus_info, consts.ITPRC) else: ocr_field, compare_list, has_expiry_date = consts.ID_TYPE_COMPARE.get(id_type) field_compare(order_individual_cus_info, ocr_res_dict, ocr_field, compare_list, res_set, has_expiry_date=has_expiry_date) # 第二证件 second_id_type = order_individual_cus_info.get('secondIdType') if second_id_type is not None: if cus_type in consts.NO_COMPARE_CUSTOMER_TYPE or second_id_type not in consts.SECOND_ID_TYPE_COMPARE: do_not_compare(order_individual_cus_info, consts.SECOND_ITPRC) else: second_ocr_field, second_compare_list = consts.SECOND_ID_TYPE_COMPARE.get(second_id_type) field_compare(order_individual_cus_info, ocr_res_dict, second_ocr_field, second_compare_list, res_set) # 重新排列 new_dict = OrderedDict({}) second_id_res = order_individual_cus_info.pop(consts.SECOND_ID_RES, consts.RESULT_NA) for key, value in order_individual_cus_info.items(): new_dict[key] = value if key == consts.ID_RES: new_dict[consts.SECOND_ID_RES] = second_id_res order_individual_cus_info = new_dict # sep营业执照 if cus_type == consts.CUSTOMER_TYPE[5]: field_compare(order_individual_cus_info, ocr_res_dict, consts.BL_OCR_FIELD, consts.TCSEP, res_set, sep_name=sep_name, capital_ignore=True) order_individual_cus_info_list.append(order_individual_cus_info) comparison_res['OCR_Input']['individualCusInfo'] = order_individual_cus_info_list if last_obj.vehicle_status == consts.VEHICLE_STATUS[0] and last_obj.usedcar_info is not None: usedcar_info = json.loads(last_obj.usedcar_info) order_usedcar_info = get_order_dict(usedcar_info, consts.UC_ORDER) mvc_find, mvc_vinos = usedcar_info_compare(order_usedcar_info, ocr_res_dict, consts.MVC_OCR_FIELD, consts.PCUSD_MVC, res_set) # if order_usedcar_info[consts.PCUSD_MVC[0][0] + 'Result'] == consts.RESULT_Y: dl_find, dl_vinos = usedcar_info_compare(order_usedcar_info, ocr_res_dict, consts.DL_OCR_FIELD, consts.PCUSD_DL, res_set) if mvc_find is True and dl_find is False: vino = dl_vinos[-1] if len(dl_vinos) > 0 else '' order_usedcar_info[consts.PCUSD_MVC[0][0]] = '{0}-{1} {2} {3}-{4}'.format( consts.PREFIX_MVC, consts.RESULT_Y, consts.SPLIT, consts.PREFIX_DL, vino) order_usedcar_info[consts.PCUSD_MVC[0][4]] = consts.RESULT_N elif mvc_find is False and dl_find is True: vino = mvc_vinos[-1] if len(mvc_vinos) > 0 else '' order_usedcar_info[consts.PCUSD_MVC[0][0]] = '{0}-{1} {2} {3}-{4}'.format( consts.PREFIX_MVC, vino, consts.SPLIT, consts.PREFIX_DL, consts.RESULT_Y) order_usedcar_info[consts.PCUSD_MVC[0][4]] = consts.RESULT_N elif mvc_find is False and dl_find is False: if len(mvc_vinos) == 0 and len(dl_vinos) == 0: order_usedcar_info[consts.PCUSD_MVC[0][0]] = None order_usedcar_info[consts.PCUSD_MVC[0][4]] = consts.RESULT_NA else: mvc_vino = mvc_vinos[-1] if len(mvc_vinos) > 0 else '' dl_vino = dl_vinos[-1] if len(dl_vinos) > 0 else '' vino = '{0}-{1} {2} {3}-{4}'.format( consts.PREFIX_MVC, mvc_vino, consts.SPLIT, consts.PREFIX_DL, dl_vino) order_usedcar_info[consts.PCUSD_MVC[0][0]] = vino order_usedcar_info[consts.PCUSD_MVC[0][4]] = consts.RESULT_N comparison_res['OCR_Input']['usedCarInfo'] = order_usedcar_info if last_obj.corporate_cus_info is not None: corporate_cus_info = json.loads(last_obj.corporate_cus_info) order_corporate_cus_info = get_order_dict(corporate_cus_info, consts.CO_ORDER) field_compare(order_corporate_cus_info, ocr_res_dict, consts.BL_OCR_FIELD, consts.TCCOR, res_set) comparison_res['OCR_Input']['corporateCusInfo'] = order_corporate_cus_info comparison_res['OCR_Input']['wholeResult'] = consts.RESULT_N if consts.RESULT_N in res_set or consts.RESULT_NA in res_set else consts.RESULT_Y except Exception as e: compare_failed = True compare_log.error('{0} [compare error] [entity={1}] [id={2}] [uniq_seq={3}] [ocr_res_id={4}] ' '[error={5}]'.format(log_base, application_entity, application_id, uniq_seq, ocr_res_id, traceback.format_exc())) else: compare_log.info('{0} [compare success] [entity={1}] [id={2}] [uniq_seq={3}] [ocr_res_id={4}] ' '[compare_res={5}]'.format(log_base, application_entity, application_id, uniq_seq, ocr_res_id, comparison_res)) is_gcap_send = Configs.objects.filter(id=1).first() if is_gcap_send is not None and is_gcap_send.value == 'N': compare_log.info('{0} [gcap closed] [entity={1}] [id={2}] [uniq_seq={3}] [ocr_res_id={4}]'.format( log_base, application_entity, application_id, uniq_seq, ocr_res_id)) return # 时间延迟 send_time = last_obj.create_time + timedelta(seconds=15) while datetime.now() < send_time: compare_log.info('{0} [time wait 5s] [entity={1}] [id={2}] [uniq_seq={3}] [ocr_res_id={4}]'.format( log_base, application_entity, application_id, uniq_seq, ocr_res_id)) time.sleep(5) # 将比对结果发送GCAP try: data = gcap.dict_to_xml(comparison_res) except Exception as e: compare_log.error('{0} [dict to xml failed] [entity={1}] [id={2}] [uniq_seq={3}] [ocr_res_id={4}] ' '[error={5}]'.format(log_base, application_entity, application_id, uniq_seq, ocr_res_id, traceback.format_exc())) else: try: for times in range(consts.RETRY_TIMES): try: res_text = gcap.send(data) except Exception as e: gcap_exc = str(e) else: break else: raise GCAPException(gcap_exc) except Exception as e: compare_log.error('{0} [gcap failed] [entity={1}] [id={2}] [uniq_seq={3}] [ocr_res_id={4}] ' '[error={5}]'.format(log_base, application_entity, application_id, uniq_seq, ocr_res_id, traceback.format_exc())) else: compare_log.info('{0} [gcap success] [entity={1}] [id={2}] [uniq_seq={3}] [ocr_res_id={4}] ' '[response={5}]'.format(log_base, application_entity, application_id, uniq_seq, ocr_res_id, res_text)) compare_log.info('{0} [task success] [entity={1}] [id={2}] [uniq_seq={3}] [ocr_res_id={4}]'.format( log_base, application_entity, application_id, uniq_seq, ocr_res_id)) finally: try: end_time = datetime.now() if compare_failed: successful_at_this_level = False failure_reason = 'Compare process error' total_fields = 0 else: successful_at_this_level = True if comparison_res['OCR_Input']['wholeResult'] == consts.RESULT_Y else False field_failed = { 'individualCusInfo': [], 'corporateCusInfo': [], 'usedCarInfo': [] } individual_list = comparison_res.get('OCR_Input', {}).get('individualCusInfo', []) total_fields = len(individual_list) * 4 for individual in individual_list: field_list = [] if not successful_at_this_level: for field_name, _, _, _, result_field in consts.ITPRC: if individual.get(result_field) != consts.RESULT_Y: field_list.append(field_name) if individual.get('secondIdType') in consts.SECOND_ID_TYPE_COMPARE: total_fields += 1 if not successful_at_this_level: if individual.get(consts.SECOND_ID_RES) != consts.RESULT_Y: field_list.append(consts.SECOND_ID_FIELD) if individual.get('customerType') == consts.CUSTOMER_TYPE[5]: total_fields += 3 if not successful_at_this_level: for field_name, _, _, _, result_field in consts.TCSEP: if individual.get(result_field) != consts.RESULT_Y: field_list.append(field_name) if len(field_list) > 0: field_failed['individualCusInfo'].append(';'.join(field_list)) corporate_res = comparison_res.get('OCR_Input', {}).get('corporateCusInfo') if corporate_res is not None: total_fields += 8 if not successful_at_this_level: corporate_field_list = [] for field_name, _, _, _, result_field in consts.TCCOR: if corporate_res.get(result_field) != consts.RESULT_Y: corporate_field_list.append(field_name) if len(corporate_field_list) > 0: field_failed['corporateCusInfo'].append(';'.join(corporate_field_list)) used_car_res = comparison_res.get('OCR_Input', {}).get('usedCarInfo') if used_car_res is not None: total_fields += 3 if not successful_at_this_level: used_car_field_list = [] for field_name, _, _, _, result_field in consts.PCUSD_MVC: if used_car_res.get(result_field) != consts.RESULT_Y: used_car_field_list.append(field_name) if len(used_car_field_list) > 0: field_failed['usedCarInfo'].append(';'.join(used_car_field_list)) if not successful_at_this_level: reason_list = [] for key, value in field_failed.items(): if len(value) > 0: value_str = json.dumps(value) reason_list.append('{0}: {1}'.format(key, value_str)) failure_reason = '、'.join(reason_list) else: failure_reason = '' request_trigger = RequestTrigger.SUBMITING.value if ocr_res_id is None else RequestTrigger.UPLOADING.value report_class = HILCompareReport if application_entity == consts.HIL_PREFIX else AFCCompareReport report_class.objects.create( case_number=application_id, request_team=RequestTeam.ACCEPTANCE.value, request_trigger=request_trigger, transaction_start=start_time, transaction_end=end_time, successful_at_this_level=successful_at_this_level, failure_reason=failure_reason, process_name=ProcessName.CACOMPARE.value, total_fields=total_fields, workflow_name=last_obj.customer_type, ) compare_log.info('{0} [report save success] [entity={1}] [id={2}] [uniq_seq={3}] [ocr_res_id={4}]'.format( log_base, application_entity, application_id, uniq_seq, ocr_res_id)) except Exception as e: compare_log.error('{0} [report save success] [entity={1}] [id={2}] [uniq_seq={3}] [ocr_res_id={4}] ' '[error={5}]'.format(log_base, application_entity, application_id, uniq_seq, ocr_res_id, traceback.format_exc()))