o
    IEiy                  	   @  s  d dl mZ d dlZd dlZd dlZd dlZd dlZd dlZd dl	Zd dl
mZ d dlmZmZmZmZmZmZ d dlmZ dd
dZddddZdddZdddZi ddd dd!d"d#d"d$d%d&d%d'd(d)d(d*d+d,d+d-d.d/d.d0d1d2d1d3d4d5d4d6d7i d8d7d9d:d;d:d<d=d>d=d?d@dAdBdCdDdEdDdFdGdHdIdJdIdKdLdMdLdNdOdPdOZh dQZddTdUZddXdYZdd\d]Zdd^d_ZddbdcZddfdgZ ddhddmdnZ!ddpdqZ"eG drds dsZ#ddudvZ$ddzd{Z%ddd|dd}d~ddddddZ&dS )    )annotationsN)	dataclass)AnyDictListOptionalTupleCallable)get_identity_defaultspayloadDict[str, Any]returnOptional[str]c                 C  s   |  d}t|tr?| d}t|tr| d}|rt|S | d}t|tr?|r?t|d tr?|d  d}|r?t|S |  d}t|trT| d}|rTt|S dS )z
    Your PHP output:
    {
      "status":"success",
      "data":{
        "config":{"yolo_model_path":"C:\\...\\best.pt"},
        "rules":[{"yolo_model_path":"C:\\...\\best.pt", ...}]
      }
    }
    dataconfigyolo_model_pathrulesr   N)get
isinstancedictstrlist)r   r   cfgpr    r   '/var/www/html/policy_provider_llm_v2.py_extract_yolo_model_path   s$   








r         (@   urlr   params	timeout_sfloatretriesintc                 C  s  dd l }dd l}dd l}|jdd | D }| d| v r dnd | }dddd	d
dd}	|jd}
|
r=d|
 |	d< d }t|d D ]}z|j	j
||	dd}|j	j||d,}t|dd pd| }| }|jdd}|jddpxd }| }W d    n1 sw   Y  |dkrz||}W n	 ty   Y nw z|d}W n ty   |jddd}Y nw |pd }|std| d| d| d |d!rd"|d d#  v sd$|d d%  v r|d d& d'd(}td)| d| d| d*| z	t|W W   S  tjy8 } z|d d& d'd(}td+| d| d| d*| |d }~ww  |jjy } zm|}z+| }z	|jddd}W n tya   t|}Y nw |pfdd d& d'd(}W n ty|   d}Y nw |j d,krtd-| d*| |||k rt!"d.d/|   W Y d }~qEtd0| d1| d*| |d }~w ty } z!|}||k rt!"d.d/|   W Y d }~qEtd2| d1| |d }~ww td2| d1| )3Nr   c                 S  s   i | ]\}}|d ur||qS Nr   ).0kvr   r   r   
<dictcomp><   s    z"_http_get_json.<locals>.<dictcomp>?&zoMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36z!application/json, text/plain, */*zen-US,en;q=0.9,fa;q=0.8zhttps://chat.fxtrendo.com/closezgzip, deflate)z
User-AgentAcceptzAccept-LanguageReferer
ConnectionzAccept-EncodingKYC_RULES_TOKENzBearer Authorization   GET)headersmethod)timeoutstatuszContent-Type zContent-Encodinggzipzutf-8zlatin-1replace)errorszEmpty response body (status=z, url=z, ctype=)<z<html   
cloudflarei  iX  
z\nz7Non-JSON response (likely WAF/Cloudflare HTML). status=z
, preview=zJSON decode failed. status=i  z&403 Forbidden from policy server. url=g?r   zHTTP error from z: zFailed to fetch JSON from )#osr9   urllib.errorparse	urlencodeitemsenvironr   rangerequestRequesturlopengetattrgetcodegeturlr4   lowerread
decompress	ExceptiondecodeUnicodeDecodeErrorlstripRuntimeError
startswithr:   jsonloadsJSONDecodeErrorerror	HTTPErrorr   codetimesleep)r   r    r!   r#   rA   r9   urllibqsfullr4   tokenlast_errireqrespr7   	final_urlctypeencrawtexttpreviewjeebody	body_textr   r   r   _http_get_json7   s   
6

rr   r(   r   c                 C  s   | d u rd S t | ttfr| S t | trB|  }|sd S |dr&|ds0|drB|drBzt|W S  t	yA   |  Y S w | S )N{}[])
r   r   r   r   striprV   endswithrW   rX   rQ   )r(   sr   r   r   _coerce_json   s   
(rz   List[Dict[str, Any]]c                 C  s  t | dtrt | d dtr| d d S t | dtr$| d S t | dtrG| d }|rGt |d trGd|d v sEd|d v rG|S |  D ]5\}}t |trbt |dtrb|d   S t |tr|rt |d trd|d v s|d|d v r|  S qKg S )Nr   r   r   required_stepsvalidation_logic)r   r   r   r   rE   )r   lst_r(   r   r   r   _find_rules_list   s    $*r   Document_Bodydocument_bodyzDocument BodyPhoto	doc_photoz	Doc PhotozDoc Logodoc_logoDoc_Logoz	Full Name	full_name	Full_Namez
First Name
first_name
First_Namez	Last Name	last_name	Last_Namez	ID Number	id_number	ID_NumberzPassport Nopassport_noPassport_Noz
Birth Date
birth_date
Birth_DatezExpiry Dateexpiry_dateExpiry_Datez
Issue Date
issue_date
Issue_DateGendergenderNationalitynationalityzMRZ ZonemrzMRZ_ZoneAddressaddress
QR_Barcode
qr_barcodez
QR BarcodeIssuing_Authorityissuing_authorityzIssuing AuthorityPlace_of_Birthplace_of_birthzPlace of Birth>   r   r   r   r   r   countrydefaultsc                 C  sB   | dp
tj d}|rt|S | pd  }|dkrdS dS )z+Used to pick OCR language hint for the LLM.ocr_locale_hintKYC_OCR_LOCALE_HINTr8   IRfaen)r   rA   rF   r   rw   upper)r   r   r(   cr   r   r   _infer_ocr_locale_hint   s   r   
class_namelogicc                 C  s   | dp| dpd  }|dv r|S | pd }d|v r"dS d|v r(dS d|v s5d|v s5|d	r7d
S d|v s?d|v rAdS d|v sQd|v sQd|v sQ|dkrSdS dS )Ntypeocr_kindr8   >   r   datenonerk   numericr   r   numberz nonor   qrbarcoder   photologor   rk   )r   rw   rN   rx   )r   r   rl   cnr   r   r   _infer_ocr_kind  s     r   keyTuple[str, bool, bool, float]c                 C  s   | dv rdS | dv rdS dS )N>   r   r   )exactTTg@>   r   r   r   )fuzzyTTg?)optionalFF      ?r   )r   r   r   r   _default_match_for_key  s
   r   c                 C  s   i }d| v r|  d|d< nd| v r|  d|d< d| v r%|  d|d< nd| v r0|  d|d< d| v r<|  d|d< nd| v rG|  d|d< dD ]}|| v rU| | ||< qIdD ]}|| v rd| | ||< qX|S )	a^  
    Extracts constraint-like keys from validation_logic.

    Supports common aliases:
      - len -> length
      - min_len -> min_length
      - max_len -> max_length

    Also passes through checksum-related params (weights/mod/check_index/check_rule) so the engine
    can run deterministic validators without hard-coding per-country logic.
    lengthlen
min_lengthmin_len
max_lengthmax_len)prefixregex
must_parse)weightsweight_listmodcheck_index
check_rule)r   )r   r   r'   r   r   r   _constraints_from_logic  s,   r   xOptional[bool]c                 C  sb   | d u rd S t | tr| S t | ttfrt| S t | tr/|   }|dv r)dS |dv r/dS d S )N>   1yonyestrueenabledrequiredT>   0nr   offr   falsedisabledr   F)r   boolr$   r"   r   rw   rN   )r   ry   r   r   r   _to_boolG  s   

r   variantr}   c                 C  s   i }dD ]}t | |}t|tr|| qdD ]}t|tr(t ||nd}t|tr4|| qd| v rLd|vrLd|vrLd|vrL| d|d< d| v r[d	|vr[| d|d	< d
| v rjd|vrj| d
|d< |S )a  
    New rule: Face is either ENABLED (required) or DISABLED. No optional mode.

    Reads in order:
      1) validation_logic['__face__' or 'face_match' or '_face' or 'selfie']
      2) variant['face_match' or 'face_policy' or 'selfie_policy' ...]
      3) flat keys on variant
    )
face_matchface_policyselfieselfie_policy)__face__r   _facer   Nrequire_face_matchr   moder   face_match_threshold	thresholdface_metricmetric)rz   r   r   r   update)r   r}   outr'   r(   r   r   r   _extract_face_policy_on_offW  s$   	



 r   )r   class_id_to_nameDict[int, str]doc_typeOptional[Dict[str, Any]]c           .      C  s  |pi }t |dptjdpd}t|dptjdp!d}t| dp*g }t|t r6t|p5g }t|ts=g }dd	 |D }t| d
pLi }	t|	t rXt|	pWi }	t|	ts_i }	t|		 D ]2\}
}zt
|
}W n	 tyx   Y qew t|tsqet|dp|dp|d}|r|| qet|}|	 D ]}z	|t
| W q ty   Y qw g }t|D ]{}||}|sqt||  dd}|	t |p|	|pi }t|tsi }zt||||t|d}W n! ty } zttd|||| d }W Y d }~nd }~ww t|tr,|r,t|}|| |}t||}t|\}}}}t |d|}t|d|}t|d|}t|d|}t|d|d||v rgdnd}t|d|dd}t|}t
|dp|dp|dpt|tr|dnd p|ddpd} |d}!| d kr|d!kr|!pd"}!| pd#} t
|d$|d$|t v rd%nd&pd&}"|d'}#t|#t r|#g}#|#d urt|#tsd }#|!i d(|gd)|dt||v d|d|d*|d|d+t|d+|d+d,d| d|!d-|d|d|d|d$|"d'|# q| d.pH| d/pH| d0}$| d1p\|  d|  d|$ }%|d2}&|&d u rp|rnt"|nd }&t#| |	}'t |'d3p}d  }(t|'d4p|'dp|'d5})t|d5d}*|(d6v rd7}*n|(d8v rd}*n	|)d urt|)}*t |'d9p|d:d;}+t|'d<|d=d>},i d?|  d|  d|% d0|$d1|%d@|dA|dBt$||dC|d2|&dDt|dDdEdFt|dFdGdHt|dHdIdJt|dJdKdLt|dLd>dMt|dMdNdOt|dOdPd5t|*d:t |+t|,|dQg t|dRd7t|dSdTdU}-|-S )VNidentity_rules_pathKYC_IDENTITY_RULES_PATHr8   strict_identity_registryKYC_STRICT_IDENTITY_REGISTRYFr|   c                 S  s    h | ]}t | rt|qS r   )r   isdigitr$   )r&   r   r   r   r   	<setcomp>  s     z8build_doc_config_payload_from_variant.<locals>.<setcomp>r}   r   require
must_exist r   )r   r   	field_keypathstrictzBidentity_defaults_load_failed country=%s doc_type=%s key=%s err=%s
match_type
match_gate
must_matchweightmin_det_confgffffff?      ?min_ocr_conf皙?expected_lenr   r   r   	validatorr   r   iran_national_code
   max_candidatesr   r2   input_aliasesclass_namesr   r   match_thresholdg333333?constraintsidrule_id
variant_idvariant_namemin_detected_fields_countr   r   r   >   r   r   r   r   	mandatoryT>   r   r   r   r   r   r   score01r   r   g      ?doc_idr   r   r   r   approve_min_coverager   approve_min_extractiong(\?approve_min_match_coregq=
ףp?approve_min_match_all        review_min_coveragereject_below_coverageg      ?approve_no_input_extra_bufferg?
swap_pairsenable_name_swapname_swap_margingQ?)r   r%  r&  r'  )%r   r   rA   rF   r   rz   r   r   r   rE   r$   rQ   addsetkeyssorted_CANON_KEYSrw   rN   r:   r
   r   logging	getLogger__name__warningr   r   r   r"   r   r   
_CORE_KEYSappendr   r   r   ).r   r   r   r   r   r   strict_identityr|   required_idsr}   _cid_k_logic_cidre   idsr'   r   cidr   r   r   identity_defaultsro   mergedr   r  r  r  r  r  r
  r  r  r  r  r  r  r  	min_countr   r   enabled_flagr   r   r   r   r   r   r   %build_doc_config_payload_from_varianty  sj  




"





&



,

"(


&



	
r>  payloadsc           	   
   C  sZ  i }| D ]v}| dpg D ]l}t|tsqt| dpd}|s!q||vr2t|}d|d< |||< q|| }ttt| ddt| dd|d< ttt| dd	t| dd	|d< ttt| d
dpjdt| d
dpsd|d
< qq| r| d ni }dt| dpdt| dpdt| dp| dpdt|	 ddd}|S )am  
    Creates a "union" payload to run ONE extraction (YOLO crops + LLM OCR) for all fields.
    For each key, we keep:
      - min_det_conf: minimum across variants (to not miss)
      - min_ocr_conf: minimum across variants
      - max_candidates: maximum across variants
      - weight/match settings don't matter for extraction, but kept from the first rule
    r   r   r8   Fr   r  r	  r
  r  r  r2   r   union_extractionr   r   r   locale_hintN)r  r   r   r   r   r  r   )
r   r   r   r   r"   minr$   maxr   values)	r?  by_keyr   rr   rrcurbaseunion_payloadr   r   r   _merge_union_payloadC  s6   	

**4

rK  c                   @  s>   e Zd ZU ded< ded< ded< ded< ded< d	ed
< dS )VariantCandidater   r  payload_doc_iddecisionr   scoresz	List[str]reasonsr   sort_keyN)r/  
__module____qualname____annotations__r   r   r   r   rL  n  s   
 rL  dc                 C  s4   | pd  } | dkrdS | dkrdS | dkrdS dS )	Nr8   APPROVE   REVIEWr   REJECTr2   r   )r   )rU  r   r   r   _decision_rankx  s   rZ  rN  rO  r   c           	   	   C  s   |pi }| d}| d}| dd}| dd}| d}| d}t| t|d ur.|ndt|d ur7|ndt|d ur@|ndt|t|t|d urQ|fS dfS )	N
match_core	match_all
extractionr!  coveragefacefinal_scoreg      )r   rZ  r"   )	rN  rO  ry   mcmaexcovr_  r`  r   r   r   _candidate_sort_key  s"   



re  Fr   r	  g333333?r>   )selfie_image_path
user_inputdebugr   device	yolo_confyolo_ioumax_detphp_endpoint_url
model_pathdoc_image_pathllm_ocr*Callable[[Dict[str, Any]], Dict[str, Any]]rf  rg  Optional[Dict[str, str]]rh  r   ri  rj  rk  rl  c           2      C  s  |
pi }
|pi }t |||dt|
ddt|
ddd}t|dd d	krCd d d
i d|d gi d}|	rA||d< |S t|}|rK|}|s_d d d
i dgi d}|	r]||d< |S |	rktd| td| | |}t	|j
}t|}|sd d d
i dgi d}|	r||d< |S g }g }g }|D ]#}t|t	sqt|||||
d}|| || | || q|sd d d
i dgi d}|	r||d< |S |j|||||d}t|}| |}|j||||d |	d}|d }|d }d } tdd |D }!|!r'|r |di dp|di d}"| j|||"d} nd d dd d } d }#d }$d }%g }&t|D ]\}'}(||' }||' })t|}*t|}+| |*|+|( | |*|+|(| |(jr^| nd },|j|(|*||,|	d!}-t|-d"prd}.|-d#p{i }/t|.|/}0t|d$|.|/|-d%pg |-d&pi |(jrd'nd(|(jr|sd)gng d*}1|&tt|d+t|d$|.|/|1d%pg |0d, |#d u s|0|#kr|0}#|1}$|)}%q3|$d u rd d d
i dgi d}|	r||d< |S t|$d$t|$d$|$d"|$d#|$d%|$d&|$d-|$d.d/}|	rSd0d1 t|&d2d3 d4d5D |d6< |%|d7< ||d< ||d8< ||d< ||d< | |d9< |d:|d:< |d;|d;< |S )<N)r   r   policy_timeout_sr   policy_retriesr   )r    r!   r#   r7   r8   successrX  zpolicy_fetch_failed:message)selected_variant_doc_idselected_variantrN  rO  rP  	per_field
raw_policy!yolo_model_path_missing_in_policyz#[KYC] yolo_model_path(from server):z[KYC] yolo_model_path(used):no_variants_found)r   r   r   r   r   no_variant_executed)confiouri  rl  )rp  rg  rh  fields	internalsc                 s  s    | ]	}t |d dV  qdS )r   FN)rK   )r&   r   r   r   r   	<genexpr>#  s    z'run_kyc_auto_variant.<locals>.<genexpr>quadsr   r   )ro  rf  doc_photo_quadselfie_missing)r  cosinereasondetails)rg  	face_packrh  rN  rO  r  rP  ry  r   r   upload_selfie_required)r  rN  rO  rP  ry  selfie_mode
next_stepsr  )r  rM  rN  rO  rP  rQ  r  r  )rw  rx  rN  rO  rP  ry  r  r  c                 S  s   g | ]}|j qS r   )__dict__)r&   r   r   r   r   
<listcomp>w  s    z(run_kyc_auto_variant.<locals>.<listcomp>c                 S  s   | j S r%   )rQ  )r   r   r   r   <lambda>w  s    z&run_kyc_auto_variant.<locals>.<lambda>T)r   reversevariant_candidatesbest_variant_raw_rule
detectionsr_  
llm_bundlellm_response_raw)rr   r"   r   r$   r   rN   r   print
get_enginer   r  r   r   r>  r2  doc_config_from_payloaddetectrK  extract_with_llmanycompute_face_pack	enumeratecopydeepcopyapply_config_swapsapply_name_swap_if_neededr   scorere  rL  r+  )2kyc_engine_modulerm  rn  r   r   ro  rp  rf  rg  rh  r   ri  rj  rk  rl  r   r   server_model_pathenginer   variantsvariant_payloadsvariant_cfgsraw_variantsr(   doc_cfg_payloaddetsrJ  	union_cfgpackbase_fieldsbase_internalsface_pack_globalany_face_requiredquadbest_keybest_resultbest_variant_raw	cand_listidxr   vrawr  r  r  scoringrN  rO  skresr   r   r   run_kyc_auto_variant  sR  






	&



	
 r  )r   r   r   r   )r   r   )
r   r   r    r   r!   r"   r#   r$   r   r   )r(   r   r   r   )r   r   r   r{   )r   r   r   r   r   r   )r   r   r   r   r   r   )r   r   r   r   )r   r   r   r   )r   r   r   r   )r   r   r}   r   r   r   )r   r   r   r   r   r   r   r   r   r   r   r   )r?  r{   r   r   )rU  r   r   r$   )rN  r   rO  r   r   r   )rm  r   rn  r   r   r   r   r   ro  r   rp  rq  rf  r   rg  rr  rh  r   r   r   ri  r   rj  r"   rk  r"   rl  r$   r   r   )'
__future__r   rW   r]   r  rA   r-  urllib.parser_   urllib.requestdataclassesr   typingr   r   r   r   r   r	   identity_registryr
   r   rr   rz   r   r,  r1  r   r   r   r   r   r   r>  rK  rL  rZ  re  r  r   r   r   r   <module>   s    
"
b
	 !#$&'),-./014




*
( 
K+
	
