o
    ɬch/                     @   s   d Z ddlZddlZddlmZ ddlmZmZ ddlZddlm	Z	 ddl
mZ eeZeej ee  edZedZed	Zed
ZedejZdedefddZdedefddZdedB dee fddZG dd deZdS )u  
PDF‑парсер АТ «Райффайзен Банк» / форма «Виписка за рахунком».
version 1.6  (2025‑07‑01)

Отличия от v1.5
────────────────
* исправлена критическая ошибка ValueError: could not convert string to float: ''
  (возникала на строках, где регулярка находила «комісія …» без цифр —
  или цифры содержали лишь пробелы/неразрывные пробелы, которые затем
  очищались до пустой строки).  Теперь:
    • регулярка _RE_FEE гарантирует наличие **минимум одной** цифры
    • конвертация в float выполняется «безопасно» (_safe_to_float) и
      возвращает None вместо исключения

* добавлено подробное логирование ситуаций, когда комиссия не распознана
  или не может быть преобразована в число

* незначительные косметические правки (PEP 8, type hints)
    N)datetime)ListOptional)Transaction)BaseBankStatementParserz[ \u00A0\u202F]z^-?[\d \u00A0\u202F]+[,.]\d{2}$z\bUA\d{25}\bz&\d{2}\.\d{2}\.\d{4}(?:\s+\d{2}:\d{2})?ud   (?:коміс\w*|ком[\s\-]*бан)\s*([0-9][0-9 \u00A0\u202F]*(?:[,.]\d{1,2})?)\s*(?:uah|грн)?numreturnc                 C   s   t d| ddS )uu   Удаляет обычные/неразрывные пробелы, заменяет запятую на точку. ,.)_RE_NBSPsubreplacer    r   2/var/www/html/app/parsers/raiffeisen_pdf_parser.py
_clean_num3   s   r   c                 C   s   t t| S )ul   Прямое преобразование; бросает ValueError при невалидном числе.)floatr   r   r   r   r   	_to_float8   s   r   c                 C   sF   | sdS t | }|sdS zt|W S  ty"   td| | Y dS w )uj   Более безопасная версия: возвращает None вместо исключения.NuZ   Не удалось преобразовать %r в float (после очистки: %r))r   r   
ValueErrorloggerdebug)r   cleanedr   r   r   _safe_to_float=   s   
r   c                   @   s   e Zd ZdZdedee fddZd ddZdeee  fd	d
Z	dee de
fddZdeee  deee  fddZdee dee fddZededefddZededee fddZdededefddZededefddZdS )!RaiffeisenBankPdfParseruW   Главный публичный метод — parse(file_path) → List[Transaction]	file_pathr   c                    s4  t d| g }t|!}|js|W  d    S  |jd   |}W d    n1 s1w   Y   fdd|D } |}t }|D ]F} 	|}	|	sSqI|	j
|	j|	jf}
|
|v r`qI||
 ||	  |	j}|d u ruqI|	jdk rtt|	j| dk rqI| |	| qIt dt| |S )Nu#   Старт парсинга PDF: %sr   c                    s   g | ]	}  |s|qS r   )_is_trash_row.0rselfr   r   
<listcomp>[       z1RaiffeisenBankPdfParser.parse.<locals>.<listcomp>g{Gz?u4   Всего Transaction (с комиссиями): %d)r   r   
pdfplumberopenpages_extract_header_collect_all_rows_merge_continuationsset_row_to_transactionnumberdateamountaddappend_extract_feepayment_detailsabs_make_fee_txlen)r!   r   txspdfZraw_rowsfilteredmergedseenrowZbase_txkeyZfee_amtr   r    r   parseP   s:   



 zRaiffeisenBankPdfParser.parseNc                    sX   |  pd  fdd}|d| _|d| _|ddd| _td| j| j| j d S )	Nr	   c                    s   t |  p	d dgd  S )Nr	      )researchstrip)ptxtr   r   <lambda>{   s    z9RaiffeisenBankPdfParser._extract_header.<locals>.<lambda>u#   Назва\s+клієнта:\s*(.+)u1   Код\s+за\s+ЄДРПОУ/ІНН:\s*(\d{8,10})z
(UA\d{25}) u$   Header → name=%s | inn=%s | acc=%s)extract_textour_company_nameour_company_innr   our_company_accountr   r   )r!   pagegr   rC   r   r'   y   s   

z'RaiffeisenBankPdfParser._extract_headerc                 C   sP   g }|j D ] }|dddddp|dddd}|r%|dd	 |D 7 }q|S )
Nlines      )vertical_strategyhorizontal_strategyintersection_tolerancesnap_tolerancetextT)rP   rQ   keep_blank_charsc                 S   s   g | ]	}d d |D qS )c                 S   s   g | ]}|pd   qS )r	   )rA   )r   cr   r   r   r"      s    zHRaiffeisenBankPdfParser._collect_all_rows.<locals>.<listcomp>.<listcomp>r   r   r   r   r   r"      r#   z=RaiffeisenBankPdfParser._collect_all_rows.<locals>.<listcomp>)r&   extract_table)r!   r7   rowsrK   tblr   r   r   r(      s(   
z)RaiffeisenBankPdfParser._collect_all_rowsr;   c                 C   s"   d | }|  p|dS )NrF   )u   номер документu   оборот заu   вихідний залишокu   вхідний залишокu   сторінка)joinlowerrA   
startswith)r!   r;   rD   r   r   r   r      s   z%RaiffeisenBankPdfParser._is_trash_rowrX   c                 C   s   g }d }|D ]4}|dgdt |  7 }|d r#|r|| | }q|r:dD ]}|| r9||  d||  7  < q'q|rB|| |S )Nr	   	   r   )rO         
)r5   r0   copy)r!   rX   r9   curr   idxr   r   r   r)      s    


z,RaiffeisenBankPdfParser._merge_continuationsr   c                 C   sz  |d }|sd S |  |d p|d  }|d pd |d p!d }}|r3t|r3t| }n|r?t|r?t|}nd S t|d rUt|d  ddnd}|d	 p\d }|d
 pdd }	d	|d 
 }
|dk r| j| j| j}}}|	||}}}|d }}n|	||}}}| j| j| j}}}d |}}t||||
||||||||d}| |
|_|S )Nr   rN   r>      r	      rO   rF      r^   r_   )r,   r-   r.   r2   
payer_name	payer_innpayer_accountrecipient_namerecipient_innrecipient_accountdate_outcomedate_income)_parse_daterA   	_RE_FLOATmatchr   _RE_IBANr@   groupr   rZ   splitrH   rI   rJ   r   _to_single_linepayment_details_one_line)r!   r   Zdoc_numdtZ	debit_rawZ
credit_rawr.   Zcounter_accZcounter_innZcounter_namepurposerg   rh   	payer_acc
recip_name	recip_inn	recip_accZd_outZd_intxr   r   r   r+      sX   "
*


z+RaiffeisenBankPdfParser._row_to_transactionrT   c                 C   s   d |  S )NrF   )rZ   rt   )rT   r   r   r   ru      s   z'RaiffeisenBankPdfParser._to_single_linec                 C   sF   t | dd}|sdS t|d}|du r!td|d |S )ut   Извлекает сумму комиссии (UAH) или None, если не найдена/непарсится.r`   rF   Nr>   uU   Комиссия найдена, но не распознана как число: %rr   )_RE_FEEr@   r   r   rs   r   r   )rT   mfeer   r   r   r1      s   z$RaiffeisenBankPdfParser._extract_feebaser   c                 C   sR   t |j d|jt| | j| j| jdddd|j |jd d}| |j|_	|S )Nz-FEEu$   АТ "Райффайзен Банк"Z14305909r	   u1   Банківська комісія за док. )r,   r-   r.   rg   rh   ri   rj   rk   rl   r2   rm   rn   )
r   r,   r-   r3   rH   rI   rJ   ru   r2   rv   )r!   r   r   r}   r   r   r   r4     s    

z$RaiffeisenBankPdfParser._make_fee_txsc              	   C   sd   |   dd} dD ]}z	t| |W   S  ty   Y q
w t| }|r.t| dS t S )Nr`   rF   )z%d.%m.%Y %H:%M%d.%m.%Yr   )	rA   r   r   strptimer   _RE_DATEr@   rs   now)r   fmtr   r   r   r   ro     s   
z#RaiffeisenBankPdfParser._parse_date)r   N)__name__
__module____qualname____doc__strr   r   r=   r'   r(   boolr   r)   r   r+   staticmethodru   r   r1   r4   r   ro   r   r   r   r   r   L   s    
)"9
r   ) r   loggingr?   r   typingr   r   r$   app.models.transactionr   app.parsers.base_parserr   	getLoggerr   r   setLevelDEBUG
addHandlerStreamHandlercompiler   rp   rr   r   
IGNORECASEr~   r   r   r   r   r   r   r   r   r   r   <module>   s.   




