o
    J{h@                     @  sL  d Z ddlmZ ddlZddlZddlmZ ddlmZmZm	Z	 ddl
Z
ddlmZ ddlmZ eeZee  edejZed	ejZed
ejZedZedZedZedZedZedejejB ZedZ dd Z!dZ"edd#e" dejej$B Z%edejZ&d ddZ'G dd deZ(dS )!u  
PDF‑парсер АТ «СЕНС БАНК»  v2.8  (2025‑07‑28)

• приход/расход определяется строго по заголовкам
  «Реєстр документів за дебетом / кредитом»;
• из блока «Кореспондент …» вытягивает
    ─ юр‑наименования вида  (ТОВ|ТЗОВ|…) "Название"
    ─ а также любые другие выражения без кавычек;
• корректно парсит:
      №39  →  ТОВ "Професійні консультаційні послуги для бізнесу"
      №902532 →  БАНКНОТИ ТА МОНЕТИ В КАСІ ВІД"ЛЬВІСЬКЕ"
• если в призначенні есть «комісія XX,YYгрн», создаётся
  отдельная расходная транзакция‑комиссия;
• двойные кавычки удваиваются под формат 1С‑ClientBank;
• убирает дубли по (№, дата‑время, |amount|);
• подробный DEBUG‑лог (_grab_counterparty) помогает отслеживать,
  что именно «съедает» требуемый текст.
    )annotationsN)datetime)ListOptionalTuple)Transaction)BaseBankStatementParseru   Док\.?\s*№\s*(\d+)u    Дата\s+(\d{2}\.\d{2}\.\d{4})u%   Сума\s+([\d\u00A0\u202F ]+,\d{2})z\b\d[\d\u00A0\u202F ]*,\d{2}\bz\bUA\d{25}\bz\bUA[\d\u00A0\u202F ]{25,}\bz\b\d{8,10}\bz
\b\d{7,}\bu   коміс\w*?       
        \s+                
        (?P<amount>        
            [\d\u00A0\u202F]+   
            [\.,]\d{2}          
        )
        \s*грн
    z[ \u00A0\u202F]c                 C  s   t td| ddS )N ,.)float_SPACESsubreplaces r   1/var/www/html/app/parsers/sensebank_pdf_parser.py<lambda>A   s    r   )u   ТОВu   ТЗОВu   TЗОВu   ППu   АТu   ФОПz\b(|z)\s+\"[^\"\.]+?\"(?![\w\"])u2   [А-ЯA-ZЇІЄҐ0-9][\w\s\"'‑–\-,\.«»]{10,}r   strreturnc                 C  s   |  ddS )uV   Дублирует двойные кавычки под формат 1С‑ClientBank."z"")r   r   r   r   r   esc_1cO   s   r   c                   @  sl   e Zd ZdZd"ddZed#d
dZd$ddZd%ddZd&ddZ	ed'ddZ
d(ddZed)dd Zd!S )*SenseBankPdfParseru=   Парсер PDF‑выписок АТ «СЕНС БАНК».	file_pathr   r   List[Transaction]c           
      C  s   |  |}| |d d  | |}g t }}|D ]*\}}| ||p&g D ]}|j|jdt|j	f}	|	|vrD|
|	 || q'q|S )N(   z%d.%m.%Y %H:%M)	_read_pdf_init_header_split_blocksset_parse_blocknumberdatestrftimeabsamountaddappend)
selfr   linesblockstxsseenZledgerblktkeyr   r   r   parseY   s   



zSenseBankPdfParser.parsepath	List[str]c                 C  sd   g }t | !}|jD ]}| pd}|dd | D  qW d    |S 1 s+w   Y  |S )Nr	   c                 s  s     | ]}|  r|  V  qd S )N)strip).0liner   r   r   	<genexpr>n   s    z/SenseBankPdfParser._read_pdf.<locals>.<genexpr>)
pdfplumberopenpagesextract_textextend
splitlines)r3   outpdfpagetxtr   r   r   r   h   s   

zSenseBankPdfParser._read_pdfheadc                 C  s\   d |}ttd|pdgd | _t|pdgd | _t|p%dgd | _d| _	d S )N
u   (ТОВ|ФОП)\s+"[^"]+"r	   r   u   АТ ""СЕНС БАНК"")
joinr   researchour_company_name
_RE_IBAN_Tour_company_account_RE_INNour_company_inn
_bank_name)r*   rC   hdrr   r   r   r   q   s
   

zSenseBankPdfParser._init_headerr+   List[Tuple[str, List[str]]]c                 C  s   g }g }d}|D ]E}d|v r|r|r| ||f g }d}qd|v r2|r/|r/| ||f g }d}qt|rF|rB|rB| ||f |g}q|rM| | q|rY|rY| ||f |S )uT  
        Розбиває весь текст виписки на окремі «блоки документа»,
        прив’язуючи кожен блок до поточного реєстру
        (DEBIT ‑ списання, CREDIT ‑ надходження).

        Алгоритм:
        • якщо зустріли заголовок реєстру — спершу зберігаємо вже
          накопичений блок (якщо він є), потім перемикаємо cur_ledger;
        • якщо зустріли рядок «Док. № …» — так само спершу закриваємо
          попередній блок, а потім починаємо новий;
        • усі інші рядки просто додаємо до поточного блока.
        Nu5   Реєстр документів за дебетомDEBITu7   Реєстр документів за кредитомCREDIT)r)   _RE_DOC_HDRrG   )r*   r+   r,   curZ
cur_ledgerlnr   r   r   r    x   s6   

z SenseBankPdfParser._split_blocksledger_hintr/   Optional[List[Transaction]]c                 C  s  t |d }t|d }|r|sdS |d}t|dd}d|}t| }r6t|d}	nt	| }rDt| }	ndS | 
|\}
}}| |}|dk}|ru|	 }| j| j| j}}}|
||}}}|d}}n|	}|
||}}}| j| j| j}}}d|}}t|||t|||||||||dg}| |}|dur|s|pt| dd	}|t| d
|| td|dd| j| j| j| j|d	|dd |S )u  
        Возвращает список транзакций:
            • основная (приход / расход)
            • отдельная транзакция‑комиссия (если в назначении есть «комісія … грн»)
        r   N   z%d.%m.%Y rP   )r#   r$   r'   payment_details
payer_name	payer_innpayer_accountrecipient_namerecipient_innrecipient_accountdate_outcomedate_incomeZ	_bank_innr	   z-FEEu(   Комісія за еквайрінг (z.2fu	    грн))rR   rG   _RE_BLOCK_DTgroupr   strptimerE   
_RE_AMOUNTto_float_RE_NUM_grab_counterparty_grab_purposerH   rL   rJ   r   r   _extract_commissiongetattrr)   rM   )r*   rU   r/   Zm_numZm_dtnumdtjoinedmamtrZ   	payer_accr[   Zpurpose_rawis_debitZ
amt_signedZpayer_name_mainZpayer_inn_mainZpayer_acc_mainZrecip_name_mainZrecip_inn_mainZrecip_acc_mainZ
d_out_mainZ	d_in_mainr-   Z
commissionbank_innr   r   r   r"      s\   




zSenseBankPdfParser._parse_blockpurposeOptional[float]c                 C  s^   t dd| }t|}|sdS |ddd}zt|W S  ty.   t	d| Y dS w )uO  
        Ищет в строке конструкцию вида «комісія … грн» и возвращает
        сумму как float, либо **None**, если комиссии нет.

        • нормализует все «нестандартные» пробелы + переносы строк;
        • не чувствителен к регистру/вариантам написания «коміс*»;
        • если в назначении встречается несколько «комісія …», берётся первая.
        z[ \u00A0\u202F\t\r\n]+rX   Nr'   r
   r   zBad commission format: %r)
rF   r   _RE_COMMISSIONrG   rc   r   r   
ValueErrorlogwarning)rt   Zcanonro   rawr   r   r   rj      s   

z&SenseBankPdfParser._extract_commissionTuple[str, str, str]c                 C  s  d }}g }t |D ]6\}}|st| }r| }|s)t| }r)| }d|vr.q
g }|d }	|	dkrpt|dk rpt||	 sp||	 dsp|d||	  |	d8 }	|	dkrpt|dk rpt||	 sp||	 drLd	|}
|
ddd g}|d }|t|k r|| dst|| s|||  |d7 }|t|k r|| dst|| rd	|}|
 d|  }td	| tjd
d|tjd}td|}td|}tdd|d}td| d}t| }r|d}n1t| }r|d }n!tD ]}td| d|tj }r-|| d  } nq|rAt|}|| td| q
t|D ]}td|tjs^td| |||f  S qF|rg|d n| j}td| |||fS )u   
        Возвращает (name, iban, inn) + DEBUG‑лог.
        Анализируем также до 3‑х строк *перед* маркером «Кореспондент».
        r	   u   КореспондентrW   r      )u   Датаu   Док.u   Сума   Призн.   БанкrX   )r~   r}      Проведенийu   [CP] CTX   → %su   Ід\W*код\s+\d{8,10})flagsz\s{2,}u    ,–u   [CP] CLEAN → %sz\bNu   [CP] CAND  → %su   сенс\s+банкu   [CP] PICK  → %su   [CP] PICK  → %s (fallback))	enumeraterI   rG   rc   rK   lenrR   
startswithinsertrE   splitr)   r5   rx   debugrF   r   I
_RE_IBAN_S_RE_LONGDIG_RE_LEGAL_NAME_Q_RE_LEGAL_NAME_ANY_LEGAL_PREFIXESstartr   reversedrM   )r*   r/   accinn
candidatesirT   ro   beforekZ
raw_beforeafterjZ	raw_afterZraw_ctxcleannameZprefpcandZfbr   r   r   rh     s   



z%SenseBankPdfParser._grab_counterpartyc                 C  s   t | D ]Q\}}d|v rU|ddd g}|d }|t| k rL| | dsLt| | sL|| |  |d7 }|t| k rL| | dsLt| | r-d|   S qdS )Nr}   rW   )r   rX   r	   )	r   r   r   r   rR   rG   r)   rE   r5   )r/   r   rT   bufzr   r   r   ri   R  s&   	z SenseBankPdfParser._grab_purposeN)r   r   r   r   )r3   r   r   r4   )rC   r4   )r+   r4   r   rO   )rU   r   r/   r4   r   rV   )rt   r   r   ru   )r/   r4   r   r{   )r/   r4   r   r   )__name__
__module____qualname____doc__r2   staticmethodr   r   r    r"   rj   rh   ri   r   r   r   r   r   U   s    



3D
Jr   )r   r   r   r   ))r   
__future__r   loggingrF   r   typingr   r   r   r9   app.models.transactionr   app.parsers.base_parserr   	getLoggerr   rx   
addHandlerNullHandlercompiler   rR   rb   re   rg   rI   r   rK   r   Xrv   r   rf   r   rE   Sr   r   r   r   r   r   r   r   <module>   sB   









