
    mj?                    `   d dl mZ d dlmZ d dlmZ d dlZd dlmZ d dl	m
Z
 d dl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 d dlmZ d.dZd/dZd0dZd0dZd0dZ d0d Z!d0d!Z"ej#        $                    d"g d#          d1d'            Z%d0d(Z&d0d)Z'd0d*Z(d0d+Z)d0d,Z*d0d-Z+dS )2    )annotationsDecimal)
ConnectionN)HTTPException)
TestClient)get_db)
create_app)confirm_budget_transactionconfirm_transfer)_require_reconciliationget_household_cockpit"get_household_cockpit_data_version)list_household_transactions)connect_memory)apply_migrationsreturn%tuple[Connection, str, str, str, str]c                     t                      } t          |            |                     d           |                     d           |                     d           |                                  | ddddfS )NzINSERT INTO budget_accounts(budget_account_id,name,account_type,currency,created_at) VALUES ('acct_a','Household A','cash','CHF','2026-01-01'),('acct_b','Household B','cash','CHF','2026-01-01')a  INSERT INTO budget_categories(category_id,name,category_type,is_active,sort_order,created_at,updated_at) VALUES ('cat_food','Food','expense',1,1,'2026','2026'),('cat_health','Health','expense',1,2,'2026','2026'),('cat_salary','Salary','income',1,3,'2026','2026')a~  INSERT INTO budget_plan_items(plan_item_id,category_id,name,plan_month,monthly_amount_chf,annual_amount_chf,cadence,is_fixed_cost,source_type,is_active,created_at,updated_at) VALUES ('plan_food','cat_food','Food plan','2026-01','150.00',NULL,'monthly',0,'manual',1,'2026','2026'),('plan_health','cat_health','Health plan','2026-01','75.00',NULL,'monthly',0,'manual',1,'2026','2026')acct_aacct_bcat_food
cat_salary)r   r   executecommitconns    'tests/unit/test_household_cockpit_v1.pydatabaser      s    DTLL	=  
 	LL	=   	LL	q   	KKMMM8Z==    r   r   accountstrtx_typetx_dateamountcategory
str | Nonemerchantc                ^    t          | ||||||d|d          }t          |d                   S )NCHF)
account_idtransaction_typetransaction_datedescriptionpayeeamount_originalcurrency_originalcategory_id	entity_id)r   r"   )r   r!   r#   r$   r%   r&   r(   results           r   add_transactionr5   /   sK    '! ' '#%!&#		
 		
 F vk"###r    Nonec            	        t                      \  } }}}}t          | |ddd|d           t          | |ddd|d           t          | |dd	d
|d           t          | |dddd d           t          | dd|gdd          }|d         d         |gk    sJ |d         d         dk    sJ |d         d         dk    sJ |d         d         dk    sJ |d         d         dk    sJ t          d |d         D                       t	          d          k    sJ t          d  |d!         D                       t	          d          k    sJ |d"         d#         dk    sJ |d"         d$         J |d"         d%         J |d"         d&         d'k    sJ |d(         d)         d*k    sJ |d(         d+         d,k    sJ |d(         d-         |d(         d.         k    sJ |d(         d/         |d(         d0         k    sJ t          d1 |d         D                       sJ |d2         d3         d4         sJ |d5         d3         d6         dk    sJ |d7         d)         d8k    sJ d S )9Nincomez
2026-01-05z1000.00Employerexpensez
2026-01-10100.00Grocerz
2026-02-10z50.00refundz
2026-02-1210.00zRefund without link
2026-01-01z
2026-02-28monthprevious_period)	date_fromdate_toaccount_idsgranularity
comparisonfiltersaccountskpis
income_chfexpense_chfz150.00net_chfz850.00savings_rate_percentz85.00c              3  @   K   | ]}t          |d                    V  dS rK   Nr   .0rows     r   	<genexpr>zXtest_cockpit_reconciles_kpis_categories_budget_trend_and_transactions.<locals>.<genexpr>U   s/      SSsws=)**SSSSSSr    expense_categoriesc              3  @   K   | ]}t          |d                    V  dS rO   r   rP   s     r   rS   zXtest_cockpit_reconciles_kpis_categories_budget_trend_and_transactions.<locals>.<genexpr>V   s/      FFsws=)**FFFFFFr    trendbudgetactual_expense_chf
budget_chfremaining_chfbudget_statusunavailable_for_account_scopereconciliationstatuspassmax_difference_chf0.00kpi_income_chftrend_income_chfkpi_net_chftrend_net_chfc              3  L   K   | ]}|d                               d          V   dS )drilldown_urlz/household/transactions?N)
startswithrP   s     r   rS   zXtest_cockpit_reconciles_kpis_categories_budget_trend_and_transactions.<locals>.<genexpr>_   s6      ssss?#../IJJssssssr    latest_transactionsr   transaction_tokentop_merchantsr(   data_statuspartial)r   r5   r   sumr   all)r   	account_a
_account_bfoodsalaryr4   s         r   Etest_cockpit_reconciles_kpis_categories_budget_trend_and_transactionsrt   @   s   08

-D)ZvD)X|YPZ[[[D)YhhWWWD)YgtXVVVD)X|WdLabbb"K$  F )Z(YK7777&>,'94444&>-(H4444&>)$0000&>01W<<<<SSf=Q6RSSSSSW^_gWhWhhhhhFFfWoFFFFF'RZJ[J[[[[[(01X====(L)111(O,444(O,0OOOOO"#H-7777"#$89VCCCC"#$45@P9QRd9eeeee"#M2f=M6N6_____ssV\]qVrssssssss'(+,?@@@@/"1%j1X====- *i777777r    c                 L   t                      \  } }}}}t          | |ddd|d           t          | |ddd|d           t          | |dd	d
|d           t          |           }t          | dd||dddd          }|d         dk    sJ t          |           |k    sJ t	          | dd|gd          }|d         d         dk    sJ |d         d         dk    sJ |d         d         dk    sJ |d         d         dk    sJ t          d |d          D                       sJ d S )!Nr8   
2026-03-01z500.00r9   r:   
2026-03-02z80.00r<   z
2026-03-03z300.00OtherTz
2026-03-04zOwn transferz200.00r*   )confirmr-   from_account_idto_account_idr.   r0   r1   r^   	confirmedz
2026-03-31day)rB   rC   rD   rE   rI   rJ   rK   rL   z420.00neutral_activitytransfer_count   c              3  .   K   | ]}|d          dk    V  dS )r(   rx   N rP   s     r   rS   zgtest_cockpit_account_filter_and_neutral_transfer_do_not_change_income_expense_or_net.<locals>.<genexpr>   s+      SScs:')SSSSSSr    ri   )r   r5   r   r   r   ro   )r   rp   	account_brr   rs   revision_before_transfertransferr4   s           r   Ttest_cockpit_account_filter_and_neutral_transfer_do_not_change_income_expense_or_netr   e   s   /7zz,D)YfD)X|XvzZZZD)YgtXVVVD)YhgVVVA$GG ,(&)'!&	
 	
 H H,,,,-d337OOOOO"K  F &>,'83333&>-(G3333&>)$0000$%&671<<<<SSV<Q5RSSSSSSSSSSr    c            	     .   t                      \  } }}}}t          | |ddd|d           t          | |ddd|d           t          | ddd	d
          }d |d         D             g dk    sJ |d         d         dk    sJ |d         d         dk    sJ d S )Nr:   z
2026-04-29z30.00r<   z
2026-05-0260.00
2026-05-01z
2026-05-03r}   rA   )rB   rC   rE   rF   c                    g | ]
}|d          S )rK   r   rP   s     r   
<listcomp>zItest_cockpit_zero_buckets_and_comparison_are_explicit.<locals>.<listcomp>   s    :::3C:::r    rV   )ra   r   ra   rF   rK   rI   expense_change_chf)r   r5   r   )r   rp   rq   rr   _salaryr4   s         r   5test_cockpit_zero_buckets_and_comparison_are_explicitr      s    19.D)ZwD)YgtXVVVD)YgtXVVV"$  F ;:&/:::>W>W>WWWWW,.'9999&>./7::::::r    c            	     @   t                      \  } }}}}t          | dd          }t          | |ddd|d           t          | dd          }|d         |d         k    sJ |d	         d
         dk    sJ |d	         d
         dk    sJ |d         d         dk    sJ d S )Nr   z
2026-05-31rB   rC   r:   z
2026-05-10z25.00r<   data_versionrI   rK   ra   r]   r^   r_   )r   r   r5   )r   rp   rq   rr   r   beforeafters          r   Ktest_cockpit_snapshot_and_reconciliation_recompute_after_transaction_changer      s    19.D)Zw"4<VVVFD)YgtXVVV!$,UUUE.!U>%:::::&>-(F2222='72222!"8,666666r    c                 B   t          t          d          t          d          g          t          d          k    sJ t          j        t                    5 } t          t          d          g           d d d            n# 1 swxY w Y   | j        j        dk    sJ d S )Nra   z0.01z0.02i  )r   r   pytestraisesr   valuestatus_code)excs    r   >test_reconciliation_accepts_one_cent_and_fails_closed_above_itr      s    "GFOOWV__#EFF'RX//YYYY	}	%	% 3 12223 3 3 3 3 3 3 3 3 3 3 3 3 3 39 C''''''s   BB	BrB   rC   rE   ))z
2026-02-30rv   r}   )rw   rv   r}   )rv   rw   yearrB   rC   rE   c                    t                      ^}}t          j        t                    5 }t	          || ||           d d d            n# 1 swxY w Y   |j        j        dk    sJ d S )Nr     )r   r   r   r   r   r   r   )rB   rC   rE   r   _r   s         r   ,test_cockpit_rejects_invalid_filter_contractr      s     zzHD1	}	%	% cdiVabbbbc c c c c c c c c c c c c c c9 C''''''s   A

AAc            	       	 t                      \  	} }}}t          	| ddd|d           t          	|ddd|d           t                      }	fd}||j        t          <   t          |                              d	d
d| ddd          }|j        dk    sJ |                                }|d         dk    sJ |d         d         dk    sJ |d         d         dk    sJ |d         d         dk    sJ t          |                              dd
d| d          }|j        dk    sJ |                                d         dk    sJ |                                d         d         d         dk    sJ d S ) Nr:   
2026-06-0240.00r<   
2026-06-0390.00rx   c               3     K    V  d S )Nr   r   s   r   override_dbzKtest_cockpit_api_exposes_the_canonical_filter_contract.<locals>.override_db   s      




r    z/api/budget/household/cockpit
2026-06-01
2026-06-30autoprevious_year)rB   rC   r+   rE   rF   )params   contract_versionhousehold_cockpit_v1rG   rE   r}   rF   rI   rK   z"/api/budget/household/transactions)rB   rC   r+   filtered_totalr   itemsr   merchant_name)	r   r5   r
   dependency_overridesr	   r   getr   json)
rp   r   rr   r   appr   responsebodytransactionsr   s
            @r   6test_cockpit_api_exposes_the_canonical_filter_contractr      s   08

-D)YgD)YgtXVVVD)YgtWUUU
,,C     (3CV$#""'%##!)
 
 # 	 	H 3&&&&==??D"#'=====	?=)U2222	?<(O;;;;<&'1111c??&&,)lR[\\ '  L #s****/0A5555w'*?;xGGGGGGr    c            	     n   t                      \  } }}|                     df           |                                  t          | ddd|d           t          | ddd|d           t	          | d	d
g          }|d         d         dk    sJ d |d         D             hk    sJ t          fd|d         D                       sJ dggdgfD ]X}t          j        t                    5 }t	          | d	d
|           d d d            n# 1 swxY w Y   |j	        j
        dk    sJ Yd S )NzLUPDATE budget_accounts SET name='Same name' WHERE budget_account_id IN (?,?)r:   r   r   Ar   r   Br   r   rB   rC   rD   rI   rK   c                    h | ]
}|d          S )budget_account_idr   )rQ   options     r   	<setcomp>zRtest_account_ids_are_exact_validated_and_names_are_display_only.<locals>.<setcomp>   s    PPPFF&'PPPr    account_optionsc              3  H   K   | ]}|d          hv |d         dk    V  dS )r   account_namez	Same nameNr   )rQ   r   rp   r   s     r   rS   zRtest_account_ids_are_exact_validated_and_names_are_display_only.<locals>.<genexpr>   su        W  W`fgz`{  AJ  LU  @V  aV  aVvn%4  aV  aV  aV  aV  W  Wr    missing r   )r   r   r   r5   r   ro   r   r   r   r   r   )r   rr   r   r4   invalidr   rp   r   s         @@r   ?test_account_ids_are_exact_validated_and_names_are_display_onlyr      s   08

-D)YgLL_bkmvawxxxKKMMMD)YgtSQQQD)YgtSQQQ"l  F &>-(G3333PPf=N6OPPPU^`iTjjjjj  W  W  W  W  W6J[C\  W  W  W  W  W  W  W  WK)Y!7"> , ,]=)) 	S!lPW   	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 y$+++++, ,s   7DD	D	c            	     @  
 t                      \  } }}
}t          | |ddd
d          }t          | |dddd d          }|                     d	||f           |                                  t	          | d
d|g          }t          
fd|d         D                       }|d         dk    sJ d|d         v sJ d|d         v sJ t          | d
d|g
d          }|d         dk    sJ t          d |d         D                       t          d          k    sJ |                     d
f           |                                  t          | d
d|g
d          }	|	d         dk    sJ t          d |	d         D                       t          d          k    sJ d S )Nr:   z
2026-07-02r;   r<   r=   z
2026-07-03r>   zGrocer refundzqUPDATE budget_transactions SET reversal_of_transaction_id=?,updated_at='2026-07-04' WHERE budget_transaction_id=?
2026-07-01
2026-07-31r   c              3  4   K   | ]}|d          k    |V  dS )r2   Nr   )rQ   itemrr   s     r   rS   z]test_category_effect_drilldown_includes_valid_linked_refund_and_reconciles.<locals>.<genexpr>   s3      bbTd=FY]aFaFaDFaFaFaFabbr    rT   rK   r   zcategory_id=cat_foodrg   zfinancial_effect=expenserB   rC   rD   r2   financial_effectr      c              3  @   K   | ]}t          |d                    V  dS expense_effect_chfNr   rQ   r   s     r   rS   z]test_category_effect_drilldown_includes_valid_linked_refund_and_reconciles.<locals>.<genexpr>  0      MMtwt0122MMMMMMr    r   z<UPDATE budget_categories SET is_active=0 WHERE category_id=?c              3  @   K   | ]}t          |d                    V  dS r   r   r   s     r   rS   z]test_category_effect_drilldown_includes_valid_linked_refund_and_reconciles.<locals>.<genexpr>  s0      VVtwt0122VVVVVVr    )	r   r5   r   r   r   nextr   rn   r   )r   rp   rq   r   
expense_id	refund_idcockpitr&   pagearchived_pagerr   s             @r   Jtest_category_effect_drilldown_includes_valid_linked_refund_and_reconcilesr      s%   19.D)Zw y)\8UY[cddJi<RVXghhILL	(	Y  
 	KKMMM#l  G bbbbW-A%BbbbbbHM"g----!Xo%>>>>>%/)BBBBB&K"  D  !Q&&&&MMtG}MMMMMQXY`QaQaaaaaLLORVQXYYYKKMMM/K"  M )*a////VV}W?UVVVVVZabiZjZjjjjjjjr    c            	     v   t                      \  } }}}}t          | |dddd d           t          | dd|g          }t          d |d	         D                       }t	          | dd|gd
d          }|d         dk    sJ t          d |d         D                       t          |d                   k    sJ d S )Nr:   z
2026-07-05z12.00Unsortedr   r   r   c              3  2   K   | ]}|d          dk    |V  dS )r2   uncategorizedNr   r   s     r   rS   zNtest_uncategorized_effect_drilldown_is_exact_and_reconciles.<locals>.<genexpr>"  s2      mmTd=FY]lFlFlDFlFlFlFlmmr    rT   r   r   r   r   c              3  @   K   | ]}t          |d                    V  dS r   r   r   s     r   rS   zNtest_uncategorized_effect_drilldown_is_exact_and_reconciles.<locals>.<genexpr>,  r   r    r   rK   )r   r5   r   r   r   rn   r   )r   rp   rq   _foodr   r   r&   r   s           r   ;test_uncategorized_effect_drilldown_is_exact_and_reconcilesr     s    2:**/D)ZD)YgtZXXX#l  G mmW-A%BmmmmmH&K#"  D  !Q&&&&MMtG}MMMMMQXYaboYpQqQqqqqqqqr    c                   t                      ^}}t          |          }|                    d           |                                 t          |          |k    sJ dd lmc m} d|j        dfd}|                     |d|           t          |d	d
          }dk    sJ |d         	                    d          sJ d S )NzjUPDATE budget_plan_items SET monthly_amount_chf='151.00',updated_at='later' WHERE plan_item_id='plan_food'r   
connectionr   r   r"   c                D    dz   |           t                    z   S )Nr   )r"   )r   callsreal_revisions    r   drifting_revisionzgtest_cockpit_revision_covers_plans_and_is_computed_once_inside_read_snapshot.<locals>.drifting_revision;  s'    
}Z((3u::55r    r   r?   z
2026-01-31r   r   r   1)r   r   r   r"   )
r   r   r   r   )jarvis_finance.services.household_cockpitserviceshousehold_cockpitsetattrr   endswith)	monkeypatchr   r   r   cockpit_moduler   r4   r   r   s	          @@r   Ltest_cockpit_revision_covers_plans_and_is_computed_once_inside_read_snapshotr   /  s   zzHD1/55FLL}~~~KKMMM-d33v====FFFFFFFFFE"EM6 6 6 6 6 6 6
 (LN_```"4<VVVFA::::.!**3///////r    c                 P   t                      ^} }}dfd}t                    } |d	d
|             |dd
|            |dd|                                             t                    |k    sJ t          dd| g          }|d         d         dk    sJ |d         d         dk    sJ |d         d         dk    sJ  |ddd                                             t          dd| g          }|d         d         dk    sJ |d         d         dk    sJ d S )Ncandidate_idr"   r$   account_sourcer'   r   r6   c                <                         d| d||f           d S )Na  INSERT INTO budget_transaction_candidates(
                   transaction_candidate_id,source_file_label,source_type,transaction_date,
                   description,amount_original,currency_original,status,created_at,
                   household_batch_id,account_source,signed_amount_original,classification)
               VALUES (?,?, 'akb_bank', ?, 'Review', '20.00', 'CHF', 'needs_review',
                       '2026-01-01', 'batch', ?, '-20.00', 'expense_candidate')zsynthetic.csv)r   )r   r$   r   r   s      r   	candidatez`test_review_quality_is_date_and_account_scoped_and_missing_binding_is_partial.<locals>.candidateI  s7    S ?G^D	
 	
 	
 	
 	
r    review_az
2026-08-03review_b
review_oldz
2025-08-03z
2026-08-01z
2026-08-31r   rI   open_review_countr   review_scopedate_and_accountsrl   r^   rm   review_unboundz
2026-08-04unavailable_global)r   r"   r$   r"   r   r'   r   r6   )r   r   r   r   )rp   r   r   r   version_beforescopedunavailabler   s          @r   Mtest_review_quality_is_date_and_account_scoped_and_missing_binding_is_partialr  F  s   %-ZZ"D)Y	
 	
 	
 	
 	
 	
 8==NIj,	222Ij,	222IlL)444KKMMM-d33~EEEE"l  F &>-.!3333&>.)-@@@@@- *i7777Id333KKMMM'l  K v~.2FFFFF}%h/9<<<<<<r    )r   r   )r   r   r!   r"   r#   r"   r$   r"   r%   r"   r&   r'   r(   r"   r   r"   )r   r6   )rB   r"   rC   r"   rE   r"   r   r6   ),
__future__r   decimalr   sqlite3r   r   fastapir   fastapi.testclientr   jarvis_finance.api.dependenciesr	   jarvis_finance.api.mainr
   +jarvis_finance.services.budget_transactionsr   r   r   r   r   r   (jarvis_finance.services.household_importr   jarvis_finance.storage.databaser   !jarvis_finance.storage.migrationsr   r   r5   rt   r   r   r   r   markparametrizer   r   r   r   r   r   r  r   r    r   <module>r     sx   " " " " " "              ! ! ! ! ! ! ) ) ) ) ) ) 2 2 2 2 2 2 . . . . . . d d d d d d d d         
 Q P P P P P : : : : : : > > > > > >> > > >0$ $ $ $""8 "8 "8 "8J T  T  T  TF; ; ; ; 7 7 7 7( ( ( ( +   ( ( ( ( H  H  H  HF, , , ,*(k (k (k (kVr r r r&0 0 0 0.!= != != != != !=r    