
    3j@&                    p   d Z ddlmZ ddlZddlZddlmZmZ ddlmZ ddl	m
Z
 ddlmZmZ ddlmZ dd	lmZmZ dd
lmZmZmZ ddlmZ ddlmZ e
rddlmZ ddZddZ	 d	 	 	 	 	 	 	 	 	 	 	 ddZ	 	 d	 	 	 	 	 	 	 ddZ 	 	 	 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZ!	 d	 	 	 	 	 	 	 ddZ"ddZ#	 	 	 	 	 	 	 	 d dZ$y)!u  
Staging pipeline — Story 9.1.

Orchestrates: upload → parse → normalize → dedup → stage → review → commit.

Architecture constraints (AR-11):
  - Uses a SEPARATE SQLite engine for staging (instance/import_staging.db)
  - Main DB is touched only to: create ImportBatch, check existing hashes, commit transactions
  - Staging rows are soft-deleted (committed_at set) after successful commit
  - Staging DB is managed by this module — callers never open it directly
    )annotationsN)datetimedate)Decimal)TYPE_CHECKING)create_enginetext)Session)StagingBaseStagedTransactionModel)StagedTransaction
ParseErrorcompute_dedup_hash)merchant_normalizer)flag_duplicatesImportBatchc                d    t        d|  d      }t        j                  j                  |       |S )Nz
sqlite:///F)echo)r   r   metadata
create_all)staging_db_pathengines     9/var/www/html/financials/app/services/staging_pipeline.py_get_staging_enginer   "   s/    Z'89FF##F+M    c            	     f   t         j                  j                  t         j                  j                  t         j                  j                  t         j                  j                  t                          d      } t        j
                  | d       t         j                  j                  | d      S )NinstanceT)exist_okzimport_staging.db)ospathjoindirname__file__makedirs)instance_dirs    r   _default_staging_pathr'   (   sc    77<<PX@Y0Z []ghLKKt,77<<&9::r   c                h   ddl m} ddlm} ddlm} |xs
 t               }t        |      }	|j                  j                  d      j                         D 
cg c]  }
|
j                  |
j                  d }}
|j                  j                  |j                        j                  |j                  j!                  d            j                         D cg c]  }|d   	 }}| D cg c]  }|j                   }}t#        ||      }d}t%        d	 |D              }t'        |	      5 }t)        | |      D ]  \  }}t+        j,                  |j.                  |      }|r|n|j0                  }t3        |j4                  j7                         |j.                  ||j8                  |j:                  ||j<                  |j                  |j>                  ||rd
nd      }|jA                  |       |dz  } |jC                          ddd       ||tE        |      dS c c}
w c c}w c c}w # 1 sw Y   'xY w)a-  
    Stage parsed transactions for user review.

    - Normalizes merchant names
    - Checks for duplicates against existing transaction hashes
    - Writes to staging DB
    - Returns summary dict with counts

    Returns:
        {"staged": int, "duplicates_flagged": int, "parse_errors": int}
    r   dbTransaction)MerchantMappingT)user_confirmed)raw_pattern
normalizedNc              3  &   K   | ]	  }|sd   yw)   N ).0fs     r   	<genexpr>z%stage_transactions.<locals>.<genexpr>Y   s     4!!A4s   	duplicatependingr   merchant_rawmerchant_normalizedamount	is_creditissuerconfidence_score
dedup_hashraw_textimport_batch_idstatusr2   )stagedduplicates_flaggedparse_errors)#app.extensionsr*   app.models.transactionr,   app.models.merchant_mappingr-   r'   r   query	filter_byallr/   r0   sessionr@   filterisnotr   sumr
   zipr   apply_user_mappingsr:   r;   r   r   	isoformatr<   r=   r?   rA   addcommitlen)staged_txnsrF   rB   r>   r   r*   r,   r-   r!   r   muser_mappingsrowexisting_hashest
new_hashesduplicate_flagsstaged_count	dup_countrM   txnis_dup	user_namer0   models                            r   stage_transactionsre   0   s	   $ "2;535D &F
 !&&000EIIK Q\\BM  	

//0778N8N8T8TUY8Z[__aAO 
 )441!,,4J4%j/BOL444I	 G{O< 	KC+??@P@PR_`I&/S5L5LJ*XX'') --$.zz--!$!5!5>> /&,{)E KKAL'	( 	+0 'L) Q
 5 s    HH)H#)CH((H1c                   |xs
 t               }t        |      }t        |      5 }|j                  t              j                  t        j                  | k(  t        j                  j                  d            }|s"|j                  t        j                  dk7        }|j                  t        j                  j                               j                         }|j                          |cddd       S # 1 sw Y   yxY w)z9Load staged transactions for a batch from the staging DB.Nrejected)r'   r   r
   rJ   r   rN   rB   committed_atis_rC   order_byr   descrL   expunge_all)rB   r   include_duplicatesr!   r   rM   qrowss           r   get_staged_transactionsrp   y   s     535D &F	 
GMM0188"22oE"//33D9
 "/66*DEAzz055::<=AAC
 
 
s   CC33C<c                >   |xs
 t               }t        |      }t        |      5 }	|	j                  t        |       }
|
t        d|  d      |||
_        |||
_        |||
_        |||
_	        |||
_
        |	j                          ddd       y# 1 sw Y   yxY w)z'Update a single staged transaction row.NzStaged transaction z
 not found)r'   r   r
   getr   
ValueErrorr;   r<   category_id
account_idrC   rU   )	staged_idr;   r<   rt   ru   rC   r   r!   r   rM   rZ   s              r   update_staged_transactionrw      s     535D &F	 Gkk0)<;29+ZHII*&9C#CJ")CO!'CNCJ  s   A%BBc                   ddl m} ddlm} ddlm} |xs
 t               }t        |      }d}t        |      5 }	|	j                  t              j                  t        j                  | k(  t        j                  j                  d      t        j                  j!                  ddg            j#                         }
t%        j&                         }|
D ]  }|j(                  xs |} ||j*                  |j,                  |j.                  |j0                  |j2                  d|j4                  |j6                  |j8                  ||j:                  | 	      }|j<                  j?                  |       ||_        d
|_        |dz  } |	jA                          ddd       |j                  jC                  |       }|r'd
|_        t%        j&                         |_        ||_"        |j<                  jA                          |S # 1 sw Y   ixY w)a:  
    Promote accepted staged transactions to the main Transaction table.

    - Only commits rows with status='pending' or 'accepted' (not rejected/duplicate)
    - Sets committed_at on each staging row after commit
    - Updates ImportBatch.status to 'committed'
    - Returns count of committed transactions
    r   r)   r+   r   Nr8   acceptedF)r   r;   r:   r<   r=   	is_manualr>   r@   r?   ru   rt   rB   	committedr2   )#rG   r*   rH   r,   app.models.import_batchr   r'   r   r
   rJ   r   rN   rB   rh   ri   rC   in_rL   r   utcnowru   r   r;   r:   r<   r=   r>   r@   r?   rt   rM   rT   rU   rr   	row_count)rB   default_account_idr   r*   TxnModelr   r!   r   r{   staging_sessionro   nowrZ   acct_idnew_txnbatchs                   r   commit_stagedr      s    ">3535D &FI	 !O$$%;<CC"22oE"//33D9"))--y*.EF
 #%	 	 oo 	Cnn:(:GXX$'$;$; --zz--zz>>!$!5!5"OO /G JJNN7#"C$CJNI'	* 	 ;!> !!/2E"%__.#JJM! !s   EG::Hc                    t        | j                  j                         | j                  | j                  | j
                  | j                  | j                  | j                  | j                  | j                  |d      S )zMConvert a StagedTransaction dataclass to a StagedTransactionModel ORM object.r8   r9   )r   r   rS   r:   r;   r<   r=   r>   r?   r@   rA   )ra   rB   s     r   staged_txn_to_modelr      sc    !XX!%%33zz--zz-->>' r   c                    |D ]L  }|j                  dd      j                         | j                         k(  s5|j                  d      sG|d   c S  |j                  |       S )a  
    Story 9.5: Resolve category_id for a merchant from user mappings.

    user_mappings: list of dicts with 'normalized', 'category_id'
    default_category_map: {merchant_normalized: category_id} for built-in rules
    Returns category_id or None if no match.
    r0    rt   )rr   lower)r;   rY   default_category_maprX   s       r   auto_categorizer      sd      $55r"((*.A.G.G.IIaeeTaNb]##$
  ##$788r   )r   str)returnr   )N)rW   zlist[StagedTransaction]rF   zlist[ParseError]rB   intr>   r   r   
str | Noner   dict)NF)rB   r   r   r   rm   boolr   zlist[StagedTransactionModel])NNNNNN)rv   r   r;   r   r<   zDecimal | Nonert   
int | Noneru   r   rC   r   r   r   r   None)rB   r   r   r   r   r   r   r   )ra   r   rB   r   r   r   )r;   r   rY   z
list[dict]r   zdict[str, int]r   r   )%__doc__
__future__r   jsonr    r   r   decimalr   typingr   
sqlalchemyr   r	   sqlalchemy.ormr
   app.models.staged_transactionr   r   app.services.pdf_parsers.baser   r   r   app.servicesr   app.services.duplicate_detectorr   r|   r   r   r'   re   rp   rw   r   r   r   r3   r   r   <module>r      s  
 #  	 #    * " M [ [ , ;3
; #'F(F"F F 	F
  F 
FV #'$  "	. '+!"!"&#  	
     
@ #';;;  ; 		;|"999 )9 	9r   