
    Pkjj_              	         d dl mZ d dlZd dlmZmZ d dlmZmZ d dlm	Z	m
Z
 d dlmZ d dlmZ d dlmZ d d	lmZmZmZmZmZmZmZmZ d d
lmZmZ d dlmZ d dlm Z  ddddddddddddddddddddddddgZ!dIdZ"dJdZ#dKd Z$d!d"dLd,Z%dMd.Z&d!d"dNd/Z'dOd2Z(dPd5Z)dQd6Z*dRd7Z+dSd:Z,dTd<Z-dUd>Z.dVdBZ/dWdEZ0dXdHZ1dS )Y    )annotationsN)UTCdatetime)DecimalInvalidOperation)
ConnectionRow)uuid4)HTTPException)CashSummary)
ActionItemCashPositionCashPositionDetailCashSnapshotCashSnapshotConfirmRequestCashSnapshotPreviewRequestConfirmResponsePreviewResponse)_signed_cash_deltacalculate_cash_balances)decimal_text) latest_official_postfinance_cash
Raiffeisencsv_calculated)platformaccount_namebalance_modezPostFinance CashzPostFinance Cash-KontomanualAKBzAKB ZinsbereitstellungskontozAKB gemeinsames KontozAKB Aargauer-SparkontozAKB Haushaltskontoreturnstrc                     t          j        t                                        d                                                              dd          S )Nr   )microsecondz+00:00Z)r   nowr   replace	isoformat     +src/jarvis_finance/services/cash_service.py_nowr+   $   s@    <$$$33==??GGRUVVVr)   prefixc                B    |  dt                      j        d d          S )N_   )r
   hex)r,   s    r*   _uuidr1   (   s%    ))uww{3B3')))r)   valuefieldr   c                    	 t          t          |                     }n.# t          t          f$ r}t	          dd|           |d }~ww xY w|dk     rt	          d| d          |S )N  zInvalid decimal for status_codedetailr   z must be zero or positive)r   r!   r   
ValueErrorr   )r2   r3   parsedexcs       r*   _decimalr<   ,   s    ]U$$j) ] ] ]4R54R4RSSSY\\]zzu4W4W4WXXXXMs    A
AA
user)
created_byconnr   actionentity_type	entity_idpayloaddictnoter>   c                   t          d          }t                      }|                     d|||||t          j        |d          ||||f
           |S )Naudita/  
        INSERT INTO audit_log(audit_id, timestamp, source, action, entity_type, entity_id, old_values_json, new_values_json, user_text_note, confirmed, confirmation_timestamp, created_by, quality_status, created_at)
        VALUES (?, ?, 'vue_dashboard', ?, ?, ?, NULL, ?, ?, 1, ?, ?, 'ok', ?)
        T)	sort_keys)r1   r+   executejsondumps)	r?   r@   rA   rB   rC   rE   r>   audit_idtss	            r*   _auditrN   6   sf    W~~H	BLL	 
2v{Itz'UY7Z7Z7Z\`bdfprtu   Or)   namec                    |                      d|f                                          }|r|d         S t          d          }|                      d||t                      f           |S )Nz<SELECT platform_id FROM platforms WHERE lower(name)=lower(?)platform_idplatzuINSERT INTO platforms(platform_id, name, platform_type, default_currency, created_at) VALUES (?, ?, 'bank', 'CHF', ?))rI   fetchoner1   r+   )r?   rO   rowrQ   s       r*   _ensure_platformrU   C   ss    
,,UX\W^
_
_
h
h
j
jC
 "=!!--KLL	dDFF#   r)   c          
     F   g }d t           D             }d |                     dd                    d |D                        d|                                          D             }t           D ]}|                     d|d         f                                          }t	          |          d	k    rFt          | |d
                   }|r;|d         d         }|                     d||d         t                      |f           t          d          }|                     d|||d         |d         t                      f           |                    |d                    t          | dd||d|           | 
                                 t	          |          ||dS )Nc                    g | ]
}|d          S r   r(   ).0items     r*   
<listcomp>z2ensure_canonical_cash_accounts.<locals>.<listcomp>Q   s    OOOtd>*OOOr)   c                    g | ]
}|d          S rX   r(   rY   rT   s     r*   r[   z2ensure_canonical_cash_accounts.<locals>.<listcomp>R   s-        	N  r)   z
            SELECT account_name, COUNT(*) AS n
            FROM accounts
            WHERE account_type='cash' AND account_name IN (,c              3     K   | ]}d V  dS )?Nr(   )rY   r.   s     r*   	<genexpr>z1ensure_canonical_cash_accounts.<locals>.<genexpr>X   s"      DaDaQSDaDaDaDaDaDar)   zD)
            GROUP BY account_name HAVING COUNT(*) > 1
            zLSELECT account_id FROM accounts WHERE account_type='cash' AND account_name=?r      r   r   
account_idz{UPDATE accounts SET platform_id=?, currency='CHF', balance_mode=?, portfolio_bucket='cash', updated_at=? WHERE account_id=?r   accta  
                INSERT INTO accounts(account_id, platform_id, account_name, account_type, currency, balance_mode, portfolio_bucket, performance_included, is_active, created_at)
                VALUES (?, ?, ?, 'cash', 'CHF', ?, 'cash', 0, 1, ?)
                cash_account_seed_confirmcash_accountzCanonical cash account ensured)r@   rA   rB   rC   rE   r>   )createdcreated_accounts
duplicates)CANONICAL_CASH_ACCOUNTSrI   joinfetchalllenrU   r+   r1   appendrN   commit)	r?   r>   rg   existing_namesri   rZ   rowsrQ   rc   s	            r*   ensure_canonical_cash_accountsrr   O   s   GOO7NOOON << =@HHDaDaR`DaDaDa<a<a   
 
 (**  J ( { {||jmq  sA  nB  mD  E  E  N  N  P  Pt99q==&tT*-=>> 	{a.JLL Nd>2DFFJG   
 vJLL [$~*>^@TVZV\V\]   NN4/0004 ;cmw{  Cc  pz  {  {  {  {  {KKMMM7||PZ[[[r)   rc   snapshot_typec                V    |                      d||f                                          S )Nz
        SELECT * FROM cash_account_snapshots
        WHERE account_id=? AND snapshot_type=?
        ORDER BY balance_date DESC, created_at DESC
        LIMIT 1
        rI   rS   )r?   rc   rs   s      r*   _latest_snapshotrv   x   s2    <<	 
]#  hjjr)   anchor_date
str | Nonec                    |st          d          S |                     d||f                                          }t          t          |d         pd                    S )N0a	  
        SELECT COALESCE(SUM(CAST(net_amount_chf AS TEXT)), '0') AS movement
        FROM transactions
        WHERE account_id=? AND COALESCE(is_voided,0)=0 AND COALESCE(is_confirmed,1)=1
          AND source_type LIKE '%csv%'
          AND trade_date > ?
        movement)r   rI   rS   r!   )r?   rc   rw   rT   s       r*   _cash_movements_after_anchorr|      sg     s||
,,	 
[!	 	 hjj  3s:-#..///r)   c                   t          | d          }|r:t          t          |d                             t          | |d                   z   S |                     df                                          }|r't          d |D             t          d                    S t          | d          }t          fd	|j        	                                D             t          d                    S )
Ncsv_anchor_balance
amount_chfbalance_datez7SELECT amount_chf FROM cash_balances WHERE account_id=?c              3  j   K   | ].}|d          
t          t          |d                              V  /dS r   Nr   r!   r]   s     r*   ra   z&_calculated_balance.<locals>.<genexpr>   sN        |$0 C-..//0000 r)   rz   Fpersist_alertsc              3  L   K   | ]\  \  }}}|k    |j         |j         V  d S N)r   )rY   ledger_account_id	_currencybalancerc   s       r*   ra   z&_calculated_balance.<locals>.<genexpr>   sO       	
 	
7."I J..73E3Q 3Q3Q3Q3Q	
 	
r)   )
rv   r   r!   r|   rI   rl   sumr   balancesitems)r?   rc   anchorrq   ledgers    `   r*   _calculated_balancer      s%   dJ0DEEF {s6,/00114PQUWacijxcy4z4zzz<<A	  hjj 	  
   
 CLL
 
 	
 %T%@@@F	
 	
 	
 	
;A?;P;P;R;R	
 	
 	

 	  r)   c                l    |                      d|f                                          }|r|d         nd S )NzeSELECT MAX(trade_date) AS last_date FROM transactions WHERE account_id=? AND source_type LIKE '%csv%'	last_dateru   )r?   rc   rT   s      r*   _last_imported_bookingr      sU    
,,~  BL  AN  O  O  X  X  Z  ZC",3{,r)   snapshot_date)dict[str, tuple[Decimal, Decimal | None]]c                   |                      d||f                                          }i dfd
}t                      }|D ]v}t          |          \  }}|dk    r|d t	          d          hv r.|                    t          |d                               |t          |d                   ||           w|                      d||f                                          }	|	D ]}
t          |
d         pd          }|                    d          r|                    dd          d         nd }||v rR|dk    rA|                      d||
d         |
d         |
d         f          	                                }|r |t          |
d                   t	          t          |
d                             |
d         "t	          t          |
d                             nd            S )NzSELECT * FROM transactions
           WHERE account_id=? AND trade_date>?
             AND COALESCE(is_confirmed,0)=1 AND COALESCE(is_voided,0)=0
           ORDER BY trade_date,created_at,transaction_idcurrencyr!   originalr   r   Decimal | Noner    Nonec                                         | t          d          t          d          f          \  }}||z   ||d n||z   f| <   d S )Nrz   )getr   )r   r   r   previous_originalprevious_chfdeltass        r*   	add_deltaz-_post_snapshot_cash_deltas.<locals>.add_delta   sk    
 +1**S\\73<<(+
 +
'<
 (#z'9 D
*	
xr)   r   rz   transaction_idcurrency_originalaA  SELECT currency,amount_original,amount_chf,source_type,created_at
             FROM cash_balances
            WHERE account_id=? AND balance_date>?
              AND (source_type LIKE 'vue_equity_sale:%'
                   OR source_type='vue_equity_dividend')
            ORDER BY balance_date,created_at,cash_balance_idsource_type vue_equity_sale::rb   vue_equity_dividenda_  SELECT 1 FROM transactions
                    WHERE account_id=? AND source_type='vue_equity_dividend'
                      AND created_at=? AND currency_original=?
                      AND CAST(net_amount_original AS TEXT)=CAST(? AS TEXT)
                      AND COALESCE(is_confirmed,0)=1 AND COALESCE(is_voided,0)=0
                    LIMIT 1
created_atamount_original)r   r!   r   r   r   r   r    r   )
rI   rl   setr   r   addr!   
startswithsplitrS   )r?   rc   r   rq   r   direct_transaction_idsrT   r   r   postingspostingr   linked_transaction_id	duplicater   s                 @r*   _post_snapshot_cash_deltasr      sn   
 <<	< 
]#  hjj 	 9;F
 
 
 
 
 
  (+uu G G1#66*q==ZD'#,,+???""3s+;'<#=#=>>>	#c-.//:FFFF ||	@ 
]#  hjj    
  
'-06B77 %%&899Kc1%%a(( 	
 !$:::/// L)J'-.	  hjj   	
#$$C 123344|$0 C-..///	
 	
 	
 	
 Mr)   list[CashPosition]c                  # t          | d          }|                     d                                          }g }t          |           }i }|D ]0}|                    |j        g                               |           1|D ]h}|d         ##|v rd |#         D             }t          t          |	                                                    j
        }	t          | #|	          }
t          t          |          t          |
          z            D ]B}|                    |          }|
                    |t          d          t          d          f          \  }}|r|j        nt          d          |z   }|r|j        nt          d          }|||z   n|}|dn|s|rdnd	}|                    t%          d,i d
# d| d#d|d         d|d         d|d         d|dddt'          |d          dt'          |d          dt'          |d          dd dd dt'          |d          ddd|	dt)          | #          d|           Dt+          | #d          }t+          | #d           }|d         pd!}t-          |d                   }|                     d"#f                                          }i }|D ]>}|                    t-          |d                   g                               |           ?#fd#|j        D             }|s|r|hn!t          |          t          |          z  p|h}t          |          D ]}|j                            #|f          }|                    |g           }|d$ |D             n|}|s|r:t1          | #          }|                                d%k    r|nt          d          }n>||j        pt          d          }|j        }nt          d          }t          d          }|sV|sT|t5          d& |D             t          d                    z  }|t5          d' |D             t          d                    z  }|r(||k    r"t          t-          |d                             nd }|d!k    r||n|} |||z
  nt          d          }!|d uo|j        d u pt7          d( |D                       }"|"s||!d)k    rdnd*}|                    t%          d,i d
# d| d#d|d         d|d         d|d         d|d|dt'          |d          dt'          | d          dt'          |d          d|t'          |d          nd d|r||k    rt'          |d         d          nd dt'          | d          dt'          |!d          d|r||k    r|d+         nd dt)          | #          d|           j|S )-NFr   a  
        SELECT a.account_id, a.account_name, a.account_type, a.currency, COALESCE(a.balance_mode,'manual') AS balance_mode, p.name AS platform
        FROM accounts a JOIN platforms p ON p.platform_id=a.platform_id
        WHERE a.is_active=1
          AND (
              a.account_type='cash'
              OR a.account_id IN (
                  SELECT account_id FROM postfinance_account_roles
                  WHERE role='etrading_cash'
              )
          )
        ORDER BY p.name, a.account_name
        rc   c                    i | ]
}|j         |S r(   )r   )rY   	components     r*   
<dictcomp>z'list_cash_positions.<locals>.<dictcomp>&  s-        "I  r)   rz   Abgleich offenz"Offiziell abgeglichen + BewegungenzOffiziell abgeglichenidr   r   account_labelr   account_typer   r   official_componentsamount   r   calculated_balance_chfmanual_balance_chfcsv_anchor_balance_chfused_value_chfdifference_chfz0.00last_manual_reconciliationlast_imported_bookingstatusmanual_balancer~   r   zSELECT currency,amount_original,amount_chf,source_type
                 FROM cash_balances WHERE account_id=?
                 ORDER BY balance_date,created_at,cash_balance_idc                &    h | ]\  }}|k    |S r(   r(   )rY   r   r   rc   s      r*   	<setcomp>z&list_cash_positions.<locals>.<setcomp>e  s3     
 
 
+!8 J.. ...r)   c                f    g | ].}t          |d          pd                              d          ,|/S )r   r   )r   r   )r!   r   rY   balance_rows     r*   r[   z'list_cash_positions.<locals>.<listcomp>y  sS       #;}5;<<GGC   r)   CHFc              3  Z   K   | ]&}t          t          |d                              V  'dS )r   Nr   r   s     r*   ra   z&list_cash_positions.<locals>.<genexpr>  sL        '  K0A$B C CDD     r)   c              3  j   K   | ].}|d          
t          t          |d                              V  /dS r   r   r   s     r*   ra   z&list_cash_positions.<locals>.<genexpr>  sN        '&|4@  K$= > >??@@@@ r)   c              3  *   K   | ]}|d          du V  dS r   r(   r   s     r*   ra   z&list_cash_positions.<locals>.<genexpr>  s=        6AL)T1     r)   r   Aktuellr   r(   )r   rI   rl   r   
setdefaultrc   rn   nextitervaluesr   r   sortedr   r   r   r   r   r   r   r   rv   r!   r   r   upperr   any)$r?   r   rq   	positionsr   official_by_accountr   rT   
componentsr   r   r   delta_original	delta_chfr   baseline_chfr   r   r   r   modedefault_currencymaterialized_rowsmaterialized_by_currencyr   ledger_currencies
currenciesledger_balancecurrency_rowsadditional_rows
calculatedmanual_amountused
difference
missing_fxrc   s$                                      @r*   list_cash_positionsr     s   $T%@@@F<<	  hjj 	 %'I:4@@+-( S S	&&y';R@@GG	RRRR t t&
,,, !4Z!@  J !j&7&7&9&9!:!:;;IM/j-PPF"3z??S[[#@AA ( (&NN844	,2JJS\\73<<0- -)	
 2;LI--"## 8ARy33gcll090EL9,,< 
 !( %$ &1)21==0        (558555#-: "%Z '*.&9&9	
 &)%8%8 "* &;%:  ,OQ??? $0
A#>#>#> 0<J/J/J/J ,04 04t (4J'B'B'B (.v 4A=  /ET:.V.V.V!"  &v#   * !$
4DEE!$
4HII>".hs:// LLE M	
 

 (** 	 :< , 	" 	"K$//K
+,,b f[!!!!
 
 
 
/5
 
 
 ""&''#.F*G*GG " !	 	 z** e	 e	H#_00*h1GHHN4882FFM "- '4    #   
/ 
/0zBB
"*.."2"2e";";JJ    ++6F'#,,
"0"@$S\\
")#,, f 3 +:   CLL$ $  c +:  
 CLL  
 &*::: F<011222  8##(A   !, 
**S\\  d*P~/HD/P  ET      !.!:zQ !  
    $11x111)z !__ #&n"5"5	
 "%^!4!4 &X "& (;;;  ,D!444 ,8
A+F+F+F )4 %]A666!$ ""&.2B&B&B %VL%91===!'* $0a#8#8#8+, $0
A#>#>#>-2 ""&.2B&B&B ~..!58 +Az*R*R*R9: "6;       Ke	L r)   r   c           
     ^   t          |           }t          d |D                       }d |D             }t          dt          |d          t          d |D                       t          d |D                       t          d |D                       |rt	          |          nd |          S )	Nc              3  >   K   | ]}t          |j                  V  d S r   )r   r   rY   ps     r*   ra   z#get_cash_summary.<locals>.<genexpr>  s-      ==a())======r)   c                *    g | ]}|j         	|j         S r(   )r   r   s     r*   r[   z$get_cash_summary.<locals>.<listcomp>  s#    bbb1QEab!.bbbr)   r   r   c              3  0   K   | ]}|j         d k    dV  dS )r   rb   Nr   r   s     r*   ra   z#get_cash_summary.<locals>.<genexpr>  s.      !U!U!.H:T:T!:T:T:T:T!U!Ur)   c              3  0   K   | ]}|j         d k    dV  dS )r   rb   Nr   r   s     r*   ra   z#get_cash_summary.<locals>.<genexpr>  s/      )e)e!.TdBdBd!BdBdBdBd)e)er)   c              3  0   K   | ]}|j         d k    dV  dS )r   rb   N)r   r   s     r*   ra   z#get_cash_summary.<locals>.<genexpr>  s/      %[%[AahJZ>Z>Za>Z>Z>Z>Z%[%[r)   )base_currencycash_chfmanual_accounts_countcsv_calculated_accounts_countopen_reconciliation_countlast_reconciliation_atr   )r   r   r   r   max)r?   r   total
last_datess       r*   get_cash_summaryr    s    #D))I==9=====Ebb	bbbJeQ''!!U!UY!U!U!UUU&))e)eY)e)e)e&e&e"%%[%[%[%[%["["[2<Fs:$   r)   requestr   r   c           	     x   |                      d|j        f                                          }|r|d         dk    rt          dd          |j        dvrt          dd	          t          |j        d
          }t          t          d          d|j         d|j	         t          |d          dg           S )Nz@SELECT account_id, account_type FROM accounts WHERE account_id=?r   cash  zCash account not foundr6   >   r   reconciliationcalculated_balancer~   r5   zInvalid snapshot_typer   previewz	 CHF per r   
not_needed)
preview_idasset_classsummaryr   	fx_statuswarnings)rI   rc   rS   r   rs   r<   r   r   r1   r   r   )r?   r  rT   r   s       r*   preview_cash_snapshotr    s   
,,Y\c\n[p
q
q
z
z
|
|C N#n%//4LMMMM$ttt4KLLLLg(,77FeI&6&6FW^Wl  UN  UNw~  xL  UN  UN  [g  hn  pq  [r  [r  ~J  UW  X  X  X  Xr)   r   r   c                L   |j         st          dd          t          | |           t          |j        d          }t          d          }t                      }|j        dv rdn|j        dk    rd	nd
}t          | d|j         dd|j	        |
                                |j                  }|                     d||j	        |j        |j        t          |d          t          |d          ||j        ||f
           |                                  t!          d||d          S )Ni  zExplicit confirm requiredr6   r   cashsnap>   r   r  r   r~   
csv_importsystem_calculatedcash__confirmrf   )r@   rA   rB   rC   rE   z
        INSERT INTO cash_account_snapshots(snapshot_id, account_id, snapshot_type, balance_date, amount_original, currency, amount_chf, source, note, created_at, created_by, audit_id)
        VALUES (?, ?, ?, ?, ?, 'CHF', ?, ?, ?, ?, 'user', ?)
        r   	confirmedu+   Kontostand gespeichert · Audit geschrieben)r   rB   rL   message)confirmr   r  r<   r   r1   r+   rs   rN   rc   
model_dumprE   rI   r   r   ro   r   )r?   r  r   snapshot_idr%   sourcerL   s          r*   confirm_cash_snapshotr    s   ? Q4OPPPP$(((g(,77F
##K
&&C .2VVVXXls  mB  FZ  mZ  mZ\h\h  `sFd#J7+@#J#J#JXfry  sE  OV  Oa  Oa  Oc  Oc  jq  jv  w  w  wHLL	 
g('*?AUWcdjlmWnWnp|  ~D  FG  qH  qH  JP  RY  R^  `c  em  	n   	KKMMM+x  bO  P  P  P  Pr)   position_idr   c                   t          fdt          |           j        D             d           }|st          dd          |j        }|                     d|f                                          }g }d }t          |          D ]}t          t          |d                             }|d nt          ||z
  d          }	|}|                    t          |d         |d	         |d
         t          |d          |d         |d         |	|d         rdnd                      t          t          |                    }t          di |                                |j        |t#          dd          t#          dd          t#          dd          t#          dd          t#          dd          t#          dd          gdS )Nc              3  2   K   | ]}|j         k    |V  d S r   )r   )rY   r   r  s     r*   ra   z+get_cash_position_detail.<locals>.<genexpr>
  s/      XX1ADKDWDWQDWDWDWDWXXr)   r  zCash position not foundr6   z
        SELECT snapshot_id, snapshot_type, balance_date, amount_chf, source, COALESCE(note,'') AS note, audit_id
        FROM cash_account_snapshots
        WHERE account_id=?
        ORDER BY balance_date DESC, created_at DESC
        LIMIT 30
        r   r   r  rs   r   r  rE   rL   zAudit vorhanden)r  rs   r   r   r  rE   difference_to_previous_chf
audit_hint
EinzahlungT)labelenabled
AuszahlungzKontostand aktualisierenzCSV-Anfangsstand setzenu   Abgleich bestätigenzVerlauf anzeigen)last_changehistoryavailable_actionsr(   )r   r  r   r   rc   rI   rl   reversedr   r!   r   rn   r   listr   r  r   r   )
r?   r  positionrc   rq   r'  previousrT   currentdiffs
    `        r*   get_cash_position_detailr/  	  s   XXXX 0 6 6 @XXXZ^__H O4MNNNN$J<<	 
	 	 hjj 	 #%G#H~~ t t#c,/0011'tt\'H:La-P-P|M0BRUVeRfux  zH  vI  Vb  cj  lm  Vn  Vn  wz  {C  wD  KN  OU  KV  sw  Y\  ]g  Yh  Dr  DU  DU  nr  s  s  s  	t  	t  	t  	t8G$$%%G  L  L 3 3 5 5  L8Cfpw  MW  ^j  tx  My  My  My  {E  LX  bf  {g  {g  {g  is  zT  ^b  ic  ic  ic  eo  vO  Y]  e^  e^  e^  `j  qG  QU  `V  `V  `V  Xb  i{  EI  XJ  XJ  XJ  LK  L  L  L  L  Lr)   )r    r!   )r,   r!   r    r!   )r2   r!   r3   r!   r    r   )r?   r   r@   r!   rA   r!   rB   r!   rC   rD   rE   r!   r>   r!   r    r!   )r?   r   rO   r!   r    r!   )r?   r   r>   r!   r    rD   )r?   r   rc   r!   rs   r!   )r?   r   rc   r!   rw   rx   r    r   )r?   r   rc   r!   r    r   )r?   r   rc   r!   r    rx   )r?   r   rc   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   )2
__future__r   rJ   r   r   decimalr   r   sqlite3r   r	   uuidr
   fastapir   #jarvis_finance.api.schemas.overviewr   $jarvis_finance.api.schemas.positionsr   r   r   r   r   r   r   r   jarvis_finance.ledger.cashr   r   #jarvis_finance.services.api_helpersr   -jarvis_finance.services.portfolio_aggregationr   rj   r+   r1   r<   rN   rU   rr   rv   r|   r   r   r   r   r  r  r  r/  r(   r)   r*   <module>r:     s3   " " " " " "  " " " " " " " " - - - - - - - - # # # # # # # #       ! ! ! ! ! ! ; ; ; ; ; ;	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 S R R R R R R R < < < < < < Z Z Z Z Z Z |M]^^#5M_ghh(FX`aa(?QYZZ(@RZ[[(<N^__ W W W W* * * *    |B 
 
 
 
 
 
	 	 	 	 KQ &\ &\ &\ &\ &\ &\R	 	 	 	0 0 0 0    >- - - -
R R R RjK K K K\    X X X XP P P P(L L L L L Lr)   