a
    !ºÛi^J  ã                   @   s”  d Z ddlZddlZddlZddlZddlmZ z4ddlmZmZm	Z	m
Z
 ddlZddlZddlZW nF eyª Z z.ee dde› i¡ƒ e d¡ W Y dZ[n
dZ[0 0 dd	d
ddddddddddd	d
ddddddddddœZejejdœdd„Zdd„ Zdd„ Zedœdd„Zeedœd d!„Zd4eed"œd#d$„Zd5eed&œd'd(„Zd6eed"œd)d*„Zed+œd,d-„Z eed.œd/d0„Z!d1d2„ Z"e#d3kre"ƒ  dS )7uä   
process_receipt.py â€” OCR pipeline para boletas/tickets
Uso: python3 process_receipt.py <ruta_imagen> <categorias_json> [patrones_json]
Salida: JSON con texto, monto, fecha, proveedor, categoria_id + scores de confianza (0-1)
é    N)Údatetime)ÚImageÚImageFilterÚImageEnhanceÚImageOpsÚerrorzDependencia faltante: é   Z01Z02Z03Z04Z05Z06Z07Z08Z09Z10Z11Z12)ZeneroZfebreroZmarzoZabrilZmayoZjunioZjulioZagostoZ
septiembreZoctubreZ	noviembreZ	diciembreZjanZfebZmarZaprZmayZjunZjulZaugÚsepÚoctZnovZdec)ÚimgÚreturnc                 C   s(   zt  | ¡W S  ty"   |  Y S 0 dS )u<   Rota la imagen segÃºn metadatos EXIF para que quede derecha.N)r   Zexif_transposeÚ	Exception)r   © r   ú7/var/www/html/respaldagastos/scripts/process_receipt.pyÚ_corregir_exif   s    r   c                 C   st  | j dd… \}}dt||ƒ }|dk rJt | t|| ƒt|| ƒf¡}n|  ¡ }d}|j dd… \}}t |tj¡}t |dd¡}t 	|dd	¡}	tj
|	t d
tj¡dd}	t |	tjtj¡\}
}t|
tjdd}
d}|
dd… D ]R}t |d¡}t |d| d¡}t |¡}t|ƒdkrâ||| d krâ|} q6qâ|du rD| S | dd¡|  tj¡}|jdd}tj|dd ¡ }|t |¡ }|t |¡ }|t |¡ }|t |¡ }tj||||gtjd}tttj  || ¡tj  || ¡ƒƒ}tttj  || ¡tj  || ¡ƒƒ}|d	k s*|d	k r.| S tjddg|dg||gd|ggtjd}t !||¡}t "| |||f¡S )uÃ   
    Detecta el rectÃ¡ngulo de la boleta en la imagen y aplica perspectiva
    para obtener solo el papel plano. Si no se detecta con confianza,
    devuelve la imagen original sin recortar.
    Né   i°  r   g      ð?)é   r   r   é   éd   ©é   r   )Z
iterationsT©ÚkeyÚreverseé   g{®Gáz”?é   gš™™™™™É?)Zaxis)Zdtype)#ÚshapeÚmaxÚcv2ÚresizeÚintÚcopyÚcvtColorÚCOLOR_BGR2GRAYÚGaussianBlurZCannyZdilateÚnpZonesZuint8ZfindContoursZRETR_EXTERNALZCHAIN_APPROX_SIMPLEÚsortedZcontourAreaZ	arcLengthZapproxPolyDPÚlenZreshapeZastypeZfloat32ÚsumÚdiffZflattenZargminZargmaxÚarrayZlinalgZnormZgetPerspectiveTransformZwarpPerspective)Úcv_imgÚhÚwÚescalaZsmallZshÚswZgray_sÚblurZedgesZ	contornosÚ_ZquadZcntZperiZapproxZareaZptsÚsr)   ZtlÚbrZtrZblZpts_ordZanchoZaltoZdstÚMr   r   r   Ú_detectar_boleta&   sX    "

þþ(r5   c                 C   s²   | j dd… \}}|dk rHd| }tj| t|| ƒt|| ƒftjd} tjddd}| | ¡} t | dd	¡}tj|d
tj	tj
ddd}t g d¢g d¢g d¢g¡}t |d|¡S )u/  
    Aplica CLAHE + umbralizaciÃ³n adaptativa para obtener texto negro sobre
    blanco limpio, ideal para Tesseract. CLAHE normaliza el contraste local
    antes del umbral, lo que mejora imÃ¡genes con iluminaciÃ³n desigual o bajo
    contraste (p.ej. tickets de peaje, papel tÃ©rmico desgastado).
    Nr   i  )Zinterpolationg      ø?)r   r   )Z	clipLimitZtileGridSizer   r   éÿ   é   é   )Z	blockSizeÚC)r   éÿÿÿÿr   )r:   r   r:   r:   )r   r   r   r    ZINTER_CUBICZcreateCLAHEZapplyr$   ZadaptiveThresholdZADAPTIVE_THRESH_GAUSSIAN_CZTHRESH_BINARYr%   r*   Zfilter2D)Úcv_grayr,   r-   r.   Zclaher0   ZthreshZkernelr   r   r   Ú_mejorar_para_ocrl   s     &
ûr<   )Úimg_pathc                 C   sP   t t | ¡ d¡ƒ}t t |¡tj¡}t	|ƒ}t |tj
¡}t|ƒ}t |¡S )uÕ   
    Pipeline completo:
    1. Carga y corrige orientaciÃ³n EXIF
    2. Detecta y recorta el rectÃ¡ngulo de la boleta
    3. Convierte a B&N con umbral adaptativo
    Devuelve imagen PIL lista para Tesseract.
    ZRGB)r   r   ÚopenÚconvertr   r"   r%   r*   ZCOLOR_RGB2BGRr5   r#   r<   Z	fromarray)r=   Zpil_imgr+   Zcv_cropr;   Zcv_ocrr   r   r   Ú
preprocess‹   s    	r@   )Ú	orig_pathr   c           	      C   sÀ   t j |¡}t j |¡\}}t j t j t j |¡dd¡¡}t j|dd | jdkr^|  	d¡} d}| j
|kr’|| j
 }|  |t| j| ƒftj¡} t j ||d ¡}| j|dd	d
 d| d S )Nz..Z	processedT)Úexist_okÚLi„  z	_proc.jpgZJPEGéU   )Zqualityz
processed/)ÚosÚpathÚbasenameÚsplitextÚnormpathÚjoinÚdirnameÚmakedirsÚmoder?   Úwidthr   r    Zheightr   ZLANCZOSZsave)	r   rA   ÚbaseÚnamer1   Zout_dirZmax_wZratioZout_pathr   r   r   Úsave_processed¢   s     



rQ   )ÚtextÚpatrones_aprendidosc                 C   sº  |   ¡ }t dd|¡}|r|t|dd„ ddD ]N}t |d   ¡ ¡}|› d}t ||¡}|r,t| d	¡ƒ}|r,|d
f  S q,g d¢}g d¢}	g d¢}
|D ]6}t ||¡}|r˜t| d	¡dd}|r˜|df  S q˜|	D ]6}t ||¡}|rÔt| d	¡dd}|rÔ|df  S qÔt| 	d¡ƒD ]J}t d|¡s0qt d|¡}|rt| d	¡ƒ}|r|df  S q|
D ]J}t
t ||¡ƒ}t|ƒD ]*}t| d	¡ƒ}|r†|df    S q†qjdS )zRetorna (monto, confianza 0-1)u$   (?<![a-zA-Z0-9])([3sSÂ§])\s+(?=[\d])z$ c                 S   s   |   dd¡S ©NÚhitsr   ©Úget©Úxr   r   r   Ú<lambda>Ã   ó    zextract_monto.<locals>.<lambda>Tr   Úpatronz\s*[:\$]?\s*\$?\s*([\d\.\,]+)r   çffffffî?)u8   total\s+a\s+pagar\s*[:\s]*[,\s]*[\$3sSÂ§]?\s*([\d\.\,]+)u+   total\s*:?\s*[,\s]*[\$3sSÂ§]?\s*([\d\.\,]+)u/   a\s+pagar\s*[:\s]*[,\s]*[\$3sSÂ§]\s*([\d\.\,]+)u+   a\s+pagar\s*\n[^\n]*[\$3sSÂ§]\s*([\d\.\,]+)u6   importe\s+total\s*[:\s]*[,\s]*[\$3sSÂ§]?\s*([\d\.\,]+)z'pagado\s+([\d\.\,]+)\s*(?:clp|cop|usd)?u4   monto\s+total\s*[:\s]*[,\s]*[\$3sSÂ§]?\s*([\d\.\,]+)u,   monto\s*[:\s]*[,\s]*[\$3sSÂ§]?\s*([\d\.\,]+))u/   subtotal\s*[:\s]*[,\s]*[\$3sSÂ§]?\s*([\d\.\,]+)u5   precio\s+total\s*[:\s]*[,\s]*[\$3sSÂ§]?\s*([\d\.\,]+)u+   neto\s*[:\s]*[,\s]*[\$3sSÂ§]?\s*([\d\.\,]+)u,   valor\s*[:\s]*[,\s]*[\$3sSÂ§]?\s*([\d\.\,]+)u.   cobrado\s*[:\s]*[,\s]*[\$3sSÂ§]?\s*([\d\.\,]+)u0   cancelado\s*[:\s]*[,\s]*[\$3sSÂ§]?\s*([\d\.\,]+)ztarifa\s+([\d\.\,]+)\s*(?:clp)?)z[\$]\s*([\d\.\,]+)õ   [\$3sSÂ§]\s*([\d\.\,]+)z&([\d]{1,3}(?:[\.\,]\d{3})+)\s*(?:clp)?)Úskip_year_checkçÍÌÌÌÌÌì?g      è?Ú
z([\$\#]|clp|total|pago|cobro|precio|valorr^   g      à?çš™™™™™Ù?©Ng        )ÚlowerÚreÚsubr&   ÚescapeÚsearchÚ_parse_montoÚgroupÚreversedÚsplitÚlistÚfinditer)rR   rS   ÚtÚitemÚetiquetaZpatÚmÚvZpatrones_altaZpatrones_mediaZpatrones_bajaÚlineaÚmatchesr   r   r   Úextract_monto¹   sH    
rv   F)Úrawr_   c                 C   sÂ   |   ¡  d¡} t d| ¡r0|  dd¡ dd¡} n&t d| ¡rJ|  dd¡} n|  dd¡} zRt| ƒ}|dk sp|dkrvW d S |s¤d	|  krŽd
kr¤n n|t|ƒkr¤W d S |W S  ty¼   Y d S 0 d S )Nz.,z^\d{1,3}(\.\d{3})+(,\d{1,2})?$Ú.Ú ú,z^\d{1,3}(,\d{3})+$é2   i€–˜ il  i3  )ÚstripÚrstripre   ÚmatchÚreplaceÚfloatr    Ú
ValueError)rw   r_   rs   r   r   r   ri     s    (ri   c              	      s`  t  ¡ ‰ d}‡ fdd„}|r¸t|dd„ ddD ]ˆ}t |d  ¡ ¡}|  ¡  d	¡D ]b}t ||¡sdqRt ||¡D ]B}|t	| 
d
¡ƒt	| 
d¡ƒ| 
d¡ƒ}|rp|df      S qpqRq.t || ¡D ]:}|t	| 
d
¡ƒt	| 
d¡ƒ| 
d¡ƒ}|rÄ|df  S qÄd}	t |	| ¡D ]>}|t	| 
d
¡ƒt	| 
d¡ƒ| 
d¡ƒ}|r|df  S qt d| tj¡D ]b}t	| 
d
¡ƒ| 
d¡ ¡ | 
d¡  }
}}t |¡}|r`||
t	|ƒ|ƒ}|r`|df  S q`t d| tj¡D ]V}| 
d¡ ¡ }t |¡}|rÔ|t	| 
d
¡ƒt	|ƒ| 
d¡ƒ}|rÔ|df  S qÔt d| ¡}|ršzFt t	| 
d¡ƒt	| 
d
¡ƒd
ƒ}| ¡ ˆ  ¡ kr‚| d¡dfW S W n ty˜   Y n0 t d| ¡}|r\t	| 
d
¡ƒt	| 
d¡ƒ }}d
|  krâdkr\n nvd
|  krþdkr\n nZˆ j}z>t |||ƒ}| ¡ ˆ  ¡ kr8t |d
 ||ƒ}| d¡dfW S  tyZ   Y n0 dS )z+Retorna (fecha_str 'YYYY-MM-DD', confianza)z7(\d{1,2})\s*[\/\-\.]\s*(\d{1,2})\s*[\/\-\.]\s*(\d{2,4})c                    s’   t |ƒdkrd| }t|ƒ}d|  kr0dkrJn nd|   krHdksNn d S z,t||| ƒ}| ¡ ˆ  ¡ krx| d¡W S W n tyŒ   Y n0 d S )Nr   Z20r   é   r7   ú%Y-%m-%d)r'   r    r   ÚdateÚstrftimer   )ÚdÚmoZy_strÚyÚfecha©Zhoyr   r   Ú
_try_fecha(  s    2z!extract_fecha.<locals>._try_fechac                 S   s   |   dd¡S rT   rV   rX   r   r   r   rZ   6  r[   zextract_fecha.<locals>.<lambda>Tr   r\   ra   r   r   r   r]   r`   z,(\d{1,2})[\/\-](\d{1,2})[^\d\n]{0,12}(\d{4})çffffffæ?z*(\d{1,2})\s+de\s+(\w+)\s+(?:de\s+)?(\d{4})g333333ë?u7   (\d{1,2})[\/\-]([a-zÃ¡Ã©Ã­Ã³ÃºÃ±A-Z]{3})[\/\-](\d{2,4})z(\d{1,2})[\/\-](\d{4})z%Y-%m-01gš™™™™™á?z\b(\d{1,2})[\/\-](\d{1,2})\br‚   r7   rƒ   gÍÌÌÌÌÌÜ?rc   )r   Ztodayr&   re   rg   rd   rl   rh   rn   r    rj   Ú
IGNORECASEÚMESESrW   r„   r…   r   Zyear)rR   rS   Zfecha_patternr‹   rp   rq   rt   rr   ÚrZ	noisy_patr†   Zmes_strrˆ   r‡   r‰   Zd_valZmo_valr   rŠ   r   Úextract_fecha#  sh    &&&*

  8r   )rR   c                 C   sj   dd„ |   d¡D ƒ}|dd… D ]D}t d|¡r2q t d|¡s@q t d|tj¡rRq |dd	… d
f  S dS )zRetorna (proveedor, confianza)c                 S   s   g | ]}|  ¡ r|  ¡ ‘qS r   )r|   )Ú.0Úlr   r   r   Ú
<listcomp>w  r[   z%extract_proveedor.<locals>.<listcomp>ra   Né   z^[\d\$\.\,\-\/\*\#\@\s]+$u$   [a-zA-ZÃ¡Ã©Ã­Ã³ÃºÃ±ÃÃ‰ÃÃ“ÃšÃ‘]{3,}z=(rut|nit|telefono|tel:|fono|www\.|http|boleta|ticket|factura)éP   rŒ   rc   )rl   re   r~   rh   ÚI)rR   ÚlinesÚliner   r   r   Úextract_proveedoru  s    r™   )rR   Ú
categoriasc                 C   sÌ   |   ¡ }d}d}g }|D ]¢}| d¡s(qg }|d  d¡D ]&}| ¡   ¡ }|r:||v r:| |¡ q:|rt| dd¡pvdƒ}	t|ƒd t|	d d	ƒ d }
t|
d
ƒ}
|
|kr|
}|d }|}q|t|dƒ|fS )z2Retorna (categoria_id, confianza, keyword_matches)Nr   Úkeywordsrz   rU   r   g333333Ó?é
   rb   r]   Úidr   )	rd   rW   rl   r|   Úappendr    r'   ÚminÚround)rR   rš   Z
text_lowerZbest_idZ
best_scoreZbest_kwÚcatru   ÚkwrU   Zscorer   r   r   Úsuggest_categoria†  s*    

r£   c                  C   sF  t tjƒdk r*tt ddi¡ƒ t d¡ tjd } g }g g dœ}t tjƒdkrzzt tjd ¡}W n tjyx   Y n0 t tjƒdkr²zt tjd ¡}W n tjy°   Y n0 t	j
 | ¡sàtt dd| › i¡ƒ t d¡ zt| ƒ}d	}tj||d
}t | ¡ ƒdk r<tj|dd
}t | ¡ ƒt | ¡ ƒkr<|}| dd¡jddd d¡}t|| dg ¡ƒ\}}t|| dg ¡ƒ\}	}
t|ƒ\}}t||ƒ\}}}d }zt|| ƒ}W n tyÆ   Y n0 ttjd| ¡ |||	|
||||||dœddƒ W nF ty@ } z,tt dt|ƒi¡ƒ t d¡ W Y d }~n
d }~0 0 d S )Nr   r   zBUso: process_receipt.py <imagen> [categorias_json] [patrones_json]r   )Úmontor‰   r   r   zImagen no encontrada: z--oem 3 --psm 6 -l spa+eng)Úconfigr•   z--oem 3 --psm 4 -l spa+engú ry   zutf-8r   )Úerrorsr¤   r‰   T)ÚokZ	texto_ocrr¤   Zmonto_confianzaZfecha_boletaZfecha_confianzaÚ	proveedorZproveedor_confianzaZcategoria_idZcategoria_confianzaZcategoria_keywordsZimagen_procesadaF)Zensure_ascii)r'   ÚsysÚargvÚprintÚjsonÚdumpsÚexitÚloadsZJSONDecodeErrorrE   rF   Úexistsr@   ÚpytesseractZimage_to_stringr|   r   ÚencodeÚdecoderv   rW   r   r™   r£   rQ   r   Ústr)r=   rš   ZpatronesZprocessed_imgr¥   ZtextoZtexto2r¤   Z
monto_confr‰   Z
fecha_confr©   Z	prov_confZcat_idZcat_confZkwsZprocessed_pathÚer   r   r   Úmain£  sp    



ôór·   Ú__main__)N)F)N)$Ú__doc__rª   rE   r­   re   r   ZPILr   r   r   r   r²   r   Znumpyr%   ÚImportErrorr¶   r¬   r®   r¯   rŽ   r   r5   r<   rµ   r@   rQ   rm   rv   Úboolri   r   r™   r£   r·   Ú__name__r   r   r   r   Ú<module>   s8     ü	FRRH
