
    mj?                    z   d dl mZ d dlZd dlmc 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,Z0d0d-Z1dS )2    )annotationsNDecimal)
Connection)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    P/home/agent/.hermes/repos/FinanceManager/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    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|d         d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|d         d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|d         d         }d }||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|d         d!         }d"}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}d# |d$         D             }t          |          }d}t          |          }||k    }|s"t          j        d|fd%||f          d&t          j                    v st          j        t                    rt          j        t                    nd&t          j        |          t          j        |          d't          j                    v st          j        t                    rt          j        t                    nd't          j        |          t          j        |          d(z  }d)d*|iz  }t          t          j        |                    d x}x}x}x}}d+ |d,         D             }t          |          }d}t          |          }||k    }|s"t          j        d|fd%||f          d&t          j                    v st          j        t                    rt          j        t                    nd&t          j        |          t          j        |          d't          j                    v st          j        t                    rt          j        t                    nd't          j        |          t          j        |          d(z  }d)d*|iz  }t          t          j        |                    d x}x}x}x}}|d-         d.         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|d-         d/         }d }||u }|slt          j        d0|fd1||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|d-         d2         }d }||u }|slt          j        d0|fd1||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|d-         d3         }d4}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|d5         d6         }d7}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|d5         d8         }d9}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|d5         d:         }|d5         d;         }||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|d5         d<         }|d5         d=         }||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}d> |d$         D             }t          |          }|sd?d@t          j                    v st          j        t                    rt          j        t                    nd@t          j        |          t          j        |          dAz  }	t          t          j        |	                    d x}}|dB         dC         dD         }|s:dEdFt          j        |          iz  }t          t          j        |                    d }|dG         dC         dH         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|dI         d6         }dJ}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}d S )KN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==z%(py1)s == %(py4)spy1py4assert %(py6)spy6kpis
income_chfexpense_chfz150.00net_chfz850.00savings_rate_percentz85.00c              3  @   K   | ]}t          |d                    V  dS rS   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_categorieszP%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} == %(py10)s
{%(py10)s = %(py6)s(%(py8)s)
}sumr   py0py2rN   rP   py8py10assert %(py12)spy12c              3  @   K   | ]}t          |d                    V  dS rW   r   rX   s     r   r[   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)is)z%(py1)s is %(py4)s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)
startswithrX   s     r   r[   zXtest_cockpit_reconciles_kpis_categories_budget_trend_and_transactions.<locals>.<genexpr>_   s6      ssss?#../IJJssssssr    ,assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}allr`   ra   rN   latest_transactionsr   transaction_tokenzassert %(py1)srM   top_merchantsr(   data_statuspartial)r   r5   r   
@pytest_ar_call_reprcompare	_safereprAssertionError_format_explanationr^   r   @py_builtinslocals_should_repr_global_namer|   )r   	account_a
_account_bfoodsalaryr4   @py_assert0@py_assert3@py_assert2@py_format5@py_format7@py_assert1@py_assert7@py_assert9@py_assert5@py_format11@py_format13@py_format2s                     r   Etest_cockpit_reconciles_kpis_categories_budget_trend_and_transactionsr   @   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(7YK7(K7777777777(K77777(7777K777777777777777777777777777&>,'494'94444444444'944444'44449444444444444444444444444444&>-(4H4(H4444444444(H44444(4444H444444444444444444444444444&>)$00$0000000000$00000$0000000000000000000000000000000&>01<W<1W<<<<<<<<<<1W<<<<<1<<<<W<<<<<<<<<<<<<<<<<<<<<<<<<<<SSf=Q6RSSSh3SSSh_ghW^_gWhWhhSWhhhhhhhhhhhSWhhhhhhhhhhhhh3hhhhh3hhhhhhShhhhShhhhhhhhhhhW^hhhhhW^hhhhhh_ghhhhWhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhFFfWoFFF[3FFF[RZ['RZJ[J[[FJ[[[[[[[[[[[FJ[[[[[[[[[[[[[3[[[[[3[[[[[[F[[[[F[[[[[[[[[[['[[[[['[[[[[[RZ[[[[J[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[(01=X=1X==========1X=====1====X===========================(L)1T1)T1111111111)T11111)1111T111111111111111111111111111(O,44,4444444444,44444,4444444444444444444444444444444(O,O0OO,0OOOOOOOOOOO,0OOOOOO,OOOO0OOOOOOOOOOOOOOOOOOOOOOOOOOOO"#H-77-7777777777-77777-7777777777777777777777777777777"#$89CVC9VCCCCCCCCCC9VCCCCC9CCCCVCCCCCCCCCCCCCCCCCCCCCCCCCCC"#$45e@P9QRd9ee59eeeeeeeeeee59eeeeee5eeee9eeeeeeeeeeeeeeeeeeeeeeeeeeee"#M2_f=M6N6__26___________26______2____6____________________________ssV\]qVrssss3ssssssssssssssss3sssss3sssssssssssssssssssssssssssssss'(+,?@@@@@@@@@@@@@@@@@@@@@@@@@/"1%j1=X=1X==========1X=====1====X===========================- *7i7*i7777777777*i77777*7777i77777777777777777777777777777r    c                 f
   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    }	|	slt	          j        d|	fd||f          t	          j        |          t	          j        |          dz  }
dd|
iz  }t          t	          j        |                    d x}x}	}t          |           }	|	|k    }|st	          j        d|fd|	|f          dt          j
                    v st	          j        t                    rt	          j        t                    nddt          j
                    v st	          j        |           rt	          j        |           ndt	          j        |	          dt          j
                    v st	          j        |          rt	          j        |          nddz  }dd |iz  }t          t	          j        |                    d x}	}t          | dd!|gd"#          }|d$         d%         }d}||k    }	|	slt	          j        d|	fd||f          t	          j        |          t	          j        |          dz  }
dd|
iz  }t          t	          j        |                    d x}x}	}|d$         d&         }d}||k    }	|	slt	          j        d|	fd||f          t	          j        |          t	          j        |          dz  }
dd|
iz  }t          t	          j        |                    d x}x}	}|d$         d'         }d(}||k    }	|	slt	          j        d|	fd||f          t	          j        |          t	          j        |          dz  }
dd|
iz  }t          t	          j        |                    d x}x}	}|d)         d*         }d+}||k    }	|	slt	          j        d,|	fd-||f          t	          j        |          t	          j        |          dz  }
dd|
iz  }t          t	          j        |                    d x}x}	}d. |d/         D             }t          |          }|sd0d1t          j
                    v st	          j        t                    rt	          j        t                    nd1t	          j        |          t	          j        |          d2z  }
t          t	          j        |
                    d x}}d S )3Nr8   
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   rp   	confirmedrI   rK   rL   rO   rP   !=z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} != %(py5)sr   r   revision_before_transferr`   rM   py3py5assert %(py7)spy7z
2026-03-31day)rB   rC   rD   rE   rQ   rR   rS   rT   z420.00neutral_activitytransfer_count   >=z%(py1)s >= %(py4)sc              3  .   K   | ]}|d          dk    V  dS )r(   r   N rX   s     r   r[   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    r~   r{   r|   r}   )r   r5   r   r   r   r   r   r   r   r   r   r   r   r|   )r   r   	account_br   r   r   transferr   r   r   r   r   @py_assert4@py_format6@py_format8r4   r   s                    r   Ttest_cockpit_account_filter_and_neutral_transfer_do_not_change_income_expense_or_netr   e   s5   /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3O37OOOOOOOOOOO37OOOOOOOOOOOOO-OOOOO-OOOOOOOOOOOOOdOOOOOdOOOOOO3OOOOOOOOOOO7OOOOOO7OOOOOOOOOOOOOOOOOOOOOOOOOOOO"K  F &>,'383'83333333333'833333'33338333333333333333333333333333&>-(3G3(G3333333333(G33333(3333G333333333333333333333333333&>)$00$0000000000$00000$0000000000000000000000000000000$%&67<1<71<<<<<<<<<<71<<<<<7<<<<1<<<<<<<<<<<<<<<<<<<<<<<<<<<SSV<Q5RSSSS3SSSSSSSSSSSSSSSS3SSSSS3SSSSSSSSSSSSSSSSSSSSSSS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    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|d         d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|d         d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}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 )rS   r   rX   s     r   
<listcomp>zItest_cockpit_zero_buckets_and_comparison_are_explicit.<locals>.<listcomp>   s    :::3C:::r    rg   )rs   r   rs   rI   rK   rL   rO   rP   rF   rS   rQ   expense_change_chf)r   r5   r   r   r   r   r   r   )r   r   r   r   _salaryr4   r   r   r   r   r   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WWWWWWWWW:>WWWWWW:WWWW>WWWWWWWWWWWWWWWWWWWWWWWWWWWW,.9'9.'9999999999.'99999.9999'999999999999999999999999999&>./:7:/7::::::::::/7:::::/::::7:::::::::::::::::::::::::::::r    c            	        t                      \  } }}}}t          | dd          }t          | |ddd|d           t          | dd          }|d         }|d         }||k    }	|	slt          j        d	|	fd
||f          t          j        |          t          j        |          dz  }
dd|
iz  }t          t          j        |                    d x}x}	}|d         d         }d}||k    }	|	slt          j        d|	fd||f          t          j        |          t          j        |          dz  }
dd|
iz  }t          t          j        |                    d x}x}	}|d         d         }d}||k    }	|	slt          j        d|	fd||f          t          j        |          t          j        |          dz  }
dd|
iz  }t          t          j        |                    d x}x}	}|d         d         }d}||k    }	|	slt          j        d|	fd||f          t          j        |          t          j        |          dz  }
dd|
iz  }t          t          j        |                    d x}x}	}d S )Nr   z
2026-05-31rB   rC   r:   z
2026-05-10z25.00r<   data_versionr   )z%(py1)s != %(py4)srL   rO   rP   rQ   rS   rs   rI   rK   ro   rp   rq   )r   r   r5   r   r   r   r   r   )r   r   r   r   r   beforeafterr   r   r   r   r   s               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>%::!%:::::::::::!%::::::!::::%::::::::::::::::::::::::::::&>-(2F2(F2222222222(F22222(2222F222222222222222222222222222='272'72222222222'722222'22227222222222222222222222222222!"8,66,6666666666,66666,666666666666666666666666666666666r    c            	     B   t          d          t          d          g} t          |           }d}t          |          }||k    }|s"t          j        d|fd||f          dt	          j                    v st          j        t                    rt          j        t                    ndt          j        |           t          j        |          dt	          j                    v st          j        t                     rt          j        t                     ndt          j        |          t          j        |          dz  }dd	|iz  }t          t          j	        |                    d x} x}x}x}}t          j        t                    5 }t          t          d
          g           d d d            n# 1 swxY w Y   |j        } | j        }d}||k    }|st          j        d|fd||f          dt	          j                    v st          j        |          rt          j        |          ndt          j        |           t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j	        |
                    d x} x}x}}d S )Nrs   z0.01rI   r]   r   r   r_   rd   re   z0.02i  zN%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.value
}.status_code
} == %(py7)sexcr`   ra   rN   r   assert %(py9)spy9)r   r   r   r   r   r   r   r   r   r   pytestraisesr   valuestatus_code)r   r   r   r   r   r   r   r   @py_assert6r   @py_format10s              r   >test_reconciliation_accepts_one_cent_and_fails_closed_above_itr      s   $+FOOWV__#EY"#EFFYRXY'RX//YF/YYYYYYYYYYF/YYYYYYYYYYYY"YYYYY"YYYYYY#EYYYYFYYYYYYYYYYY'YYYYY'YYYYYYRXYYYY/YYYYYYYYYYYYYYYYYYYYYYYYYYY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'9 'C' C'''''''''' C''''''''''''3'''''3''''''9'''' ''''C'''''''''''''''''''''''''''''''s   F77F;>F;rB   rC   rE   ))z
2026-02-30r   r   )r   r   r   )r   r   yearrB   rC   rE   c                h   t                      ^}}t          j        t                    5 }t	          || ||           d d d            n# 1 swxY w Y   |j        }|j        }d}||k    }	|	st          j        d|	fd||f          dt          j
                    v st          j        |          rt          j        |          ndt          j        |          t          j        |          t          j        |          dz  }
dd|
iz  }t          t          j        |                    d x}x}x}	}d S )	Nr     rI   r   r   r   r   r   )r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   )rB   rC   rE   r   _r   r   r   r   r   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'9 'C' C'''''''''' C''''''''''''3'''''3''''''9'''' ''''C'''''''''''''''''''''''''''''''s   A

AAc            	     L   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    }	|	st          j	        d|	fd||f          dt          j                    v st          j        |          rt          j        |          ndt          j        |          t          j        |          dz  }
dd|
iz  }t          t          j        |                    d x}x}	}|                                }|d         }d}	||	k    }|slt          j	        d|fd||	f          t          j        |          t          j        |	          dz  }dd|iz  }t          t          j        |                    d x}x}}	|d         d         }d}	||	k    }|slt          j	        d|fd||	f          t          j        |          t          j        |	          dz  }dd|iz  }t          t          j        |                    d x}x}}	|d         d          }d}	||	k    }|slt          j	        d|fd||	f          t          j        |          t          j        |	          dz  }dd|iz  }t          t          j        |                    d x}x}}	|d!         d"         }d}	||	k    }|slt          j	        d|fd||	f          t          j        |          t          j        |	          dz  }dd|iz  }t          t          j        |                    d x}x}}	t          |                              d#d
d| d$          }|j        }d}||k    }	|	st          j	        d|	fd||f          d%t          j                    v st          j        |          rt          j        |          nd%t          j        |          t          j        |          dz  }
dd|
iz  }t          t          j        |                    d x}x}	}|                                d&         }d'}	||	k    }|slt          j	        d|fd||	f          t          j        |          t          j        |	          dz  }dd|iz  }t          t          j        |                    d x}x}}	|                                d(         d)         d*         }d}	||	k    }|slt          j	        d|fd||	f          t          j        |          t          j        |	          dz  }dd|iz  }t          t          j        |                    d x}x}}	d S )+Nr:   
2026-06-0240.00r<   
2026-06-0390.00r   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   rI   )z3%(py2)s
{%(py2)s = %(py0)s.status_code
} == %(py5)sresponse)r`   ra   r   r   r   contract_versionhousehold_cockpit_v1rK   rL   rO   rP   rG   rE   r   rF   rQ   rS   z"/api/budget/household/transactions)rB   rC   r+   transactionsfiltered_totalr   itemsr   merchant_name)r   r5   r
   dependency_overridesr	   r   getr   r   r   r   r   r   r   r   r   json)r   r   r   r   appr   r   r   r   r   r   r   bodyr   r   r   r   r   r   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&3&&&&&&&&&&3&&&&&&&&&&&&8&&&&&8&&&&&&&&&&3&&&&&&&&&&&&&&&&&&&&&&&&&&&==??D"#='==#'===========#'======#===='============================	?=)2U2)U2222222222)U22222)2222U222222222222222222222222222	?<(;O;(O;;;;;;;;;;(O;;;;;(;;;;O;;;;;;;;;;;;;;;;;;;;;;;;;;;<&1'1&'1111111111&'11111&1111'111111111111111111111111111c??&&,)lR[\\ '  L #*s*#s**********#s************<*****<******#****s***************************/05A50A55555555550A5555505555A555555555555555555555555555w'*?;GxG;xGGGGGGGGGG;xGGGGG;GGGGxGGGGGGGGGGGGGGGGGGGGGGGGGGGGGr    c            	        t                      \  } }}|                     df           |                                  t          | ddd|d           t          | ddd|d           t	          | d	d
g          }|d         d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t          t          j	        |                    d x}x}}d |d         D             }h}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t          t          j	        |                    d x}x}}fd|d         D             }	t          |	          }|sddt          j                    v st          j        t                    rt          j        t                    ndt          j        |	          t          j        |          dz  }t          t          j	        |                    d x}	}dggdgfD ]$}
t          j        t                     5 }t	          | d	d
|
           d d d            n# 1 swxY w Y   |j        }	|	j        }d}||k    }|st          j        d|fd||f          dt          j                    v st          j        |          rt          j        |          ndt          j        |	          t          j        |          t          j        |          d z  }d!d"|iz  }t          t          j	        |                    d x}	x}x}}&d S )#NzLUPDATE budget_accounts SET name='Same name' WHERE budget_account_id IN (?,?)r:   r   r   Ar   r   Br   r   rB   rC   rD   rQ   rS   rI   rK   rL   rO   rP   c                    h | ]
}|d          S )budget_account_idr   )rY   options     r   	<setcomp>zRtest_account_ids_are_exact_validated_and_names_are_display_only.<locals>.<setcomp>   s    PPPFF&'PPPr    account_optionsr   r   c              3  H   K   | ]}|d          hv |d         dk    V  dS )r   account_namez	Same nameNr   )rY   r   r   r   s     r   r[   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    r{   r|   r}   missing r   r   r   r   r   r   )r   r   r   r5   r   r   r   r   r   r   r|   r   r   r   r   r   r   r   r   )r   r   r   r4   r   r   r   r   r   r   invalidr   r   r   r   r   r   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 &>-(3G3(G3333333333(G33333(3333G333333333333333333333333333PPf=N6OPPPjU^`iTjjPTjjjjjjjjjjjPTjjjjjjPjjjjTjjjjjjjjjjjjjjjjjjjjjjjjjjjj W  W  W  W  W6J[C\  W  W  W  W3  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W3  W  W  W  W  W3  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  WK)Y!7"> , ,]=)) 	S!lPW   	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 y+y$++$++++++++++$++++++++++++s+++++s++++++y++++$++++++++++++++++++++++++++++++++++, ,s   ?JJ#	&J#	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    }
|
slt          j        d|
fd||	f          t          j        |          t          j        |	          dz  }dd|iz  }t          t          j
        |                    d x}x}
}	d}|d         }	||	v }
|
slt          j        d|
fd||	f          t          j        |          t          j        |	          dz  }dd|iz  }t          t          j
        |                    d x}x}
}	d}|d         }	||	v }
|
slt          j        d|
fd||	f          t          j        |          t          j        |	          dz  }dd|iz  }t          t          j
        |                    d x}x}
}	t          | d
d|gd          }|d         }d}	||	k    }
|
slt          j        d|
fd||	f          t          j        |          t          j        |	          dz  }dd|iz  }t          t          j
        |                    d x}x}
}	d |d         D             }t          |          }	d}t          |          }|	|k    }|s"t          j        d|fd |	|f          d!t          j                    v st          j        t                    rt          j        t                    nd!t          j        |          t          j        |	          d"t          j                    v st          j        t                    rt          j        t                    nd"t          j        |          t          j        |          d#z  }d$d%|iz  }t          t          j
        |                    d x}x}	x}x}}|                     d&f           |                                  t          | d
d|gd          }|d         }d}	||	k    }
|
slt          j        d|
fd||	f          t          j        |          t          j        |	          dz  }dd|iz  }t          t          j
        |                    d x}x}
}	d' |d         D             }t          |          }	d}t          |          }|	|k    }|s"t          j        d|fd |	|f          d!t          j                    v st          j        t                    rt          j        t                    nd!t          j        |          t          j        |	          d"t          j                    v st          j        t                    rt          j        t                    nd"t          j        |          t          j        |          d#z  }d$d%|iz  }t          t          j
        |                    d x}x}	x}x}}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   )rY   itemr   s     r   r[   z]test_category_effect_drilldown_includes_valid_linked_refund_and_reconciles.<locals>.<genexpr>   s3      bbTd=FY]aFaFaDFaFaFaFabbr    r\   rS   r   rI   rK   rL   rO   rP   zcategory_id=cat_foodry   )in)z%(py1)s in %(py4)szfinancial_effect=expenserB   rC   rD   r2   financial_effectr      c              3  @   K   | ]}t          |d                    V  dS expense_effect_chfNr   rY   r
  s     r   r[   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   r]   r^   r   r_   rd   re   z<UPDATE budget_categories SET is_active=0 WHERE category_id=?c              3  @   K   | ]}t          |d                    V  dS r  r   r  s     r   r[   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   r   r   r   r   r   r^   r   r   r   r   )r   r   r   r   
expense_id	refund_idcockpitr&   r   r   r   r   r   pager   r   r   r   r   r   archived_pager   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-"g----------"g-----"----g---------------------------!>Xo%>>!%>>>>>>>>>>>!%>>>>>>!>>>>%>>>>>>>>>>>>>>>>>>>>>>>>>>>>%B/)BB%)BBBBBBBBBBB%)BBBBBB%BBBB)BBBBBBBBBBBBBBBBBBBBBBBBBBBB&K"  D  !&Q&!Q&&&&&&&&&&!Q&&&&&!&&&&Q&&&&&&&&&&&&&&&&&&&&&&&&&&&MMtG}MMMa3MMMaY`aQXY`QaQaaMQaaaaaaaaaaaMQaaaaaaaaaaaaa3aaaaa3aaaaaaMaaaaMaaaaaaaaaaaQXaaaaaQXaaaaaaY`aaaaQaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaLLORVQXYYYKKMMM/K"  M )*/a/*a//////////*a/////*////a///////////////////////////VV}W?UVVVj3VVVjbijZabiZjZjjVZjjjjjjjjjjjVZjjjjjjjjjjjjj3jjjjj3jjjjjjVjjjjVjjjjjjjjjjjZajjjjjZajjjjjjbijjjjZjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjr    c            	        t                      \  } }}}}t          | |dddd d           t          | dd|g          }t          d |d	         D                       }t	          | dd|gd
d          }|d         }d}	||	k    }
|
slt          j        d|
fd||	f          t          j        |          t          j        |	          dz  }dd|iz  }t          t          j	        |                    d x}x}
}	d |d         D             }t          |          }	|d         }t          |          }|	|k    }|s"t          j        d|fd|	|f          dt          j                    v st          j        t                    rt          j        t                    ndt          j        |          t          j        |	          dt          j                    v st          j        t                    rt          j        t                    ndt          j        |          t          j        |          dz  }dd|iz  }t          t          j	        |                    d x}x}	x}x}}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   r[   zNtest_uncategorized_effect_drilldown_is_exact_and_reconciles.<locals>.<genexpr>"  s2      mmTd=FY]lFlFlDFlFlFlFlmmr    r\   r  r  r   r   rI   rK   rL   rO   rP   c              3  @   K   | ]}t          |d                    V  dS r  r   r  s     r   r[   zNtest_uncategorized_effect_drilldown_is_exact_and_reconciles.<locals>.<genexpr>,  r  r    r   rS   r]   r^   r   r_   rd   re   )r   r5   r   r  r   r   r   r   r   r   r^   r   r   r   r   )r   r   r   _foodr   r  r&   r  r   r   r   r   r   r   r   r   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&!Q&&&&&&&&&&!Q&&&&&!&&&&Q&&&&&&&&&&&&&&&&&&&&&&&&&&&MMtG}MMMq3MMMqYaboYpqQXYpQqQqqMQqqqqqqqqqqqMQqqqqqqqqqqqqq3qqqqq3qqqqqqMqqqqMqqqqqqqqqqqQXqqqqqQXqqqqqqYpqqqqQqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqr    c                   t                      ^}}t          |          }|                    d           |                                 t          |          }||k    }|st	          j        d|fd||f          dt          j                    v st	          j        t                    rt	          j	        t                    nddt          j                    v st	          j        |          rt	          j	        |          ndt	          j	        |          dt          j                    v st	          j        |          rt	          j	        |          nddz  }dd	|iz  }t          t	          j        |                    d x}}d
d lmc m} d
|j        dfd}	|                     |d|	           t!          |dd          }
d}|k    }|st	          j        d|fd|f          dt          j                    v st	          j                  rt	          j	                  ndt	          j	        |          dz  }dd|iz  }t          t	          j        |                    d x}}|
d         }|j        }d} ||          }|ssdt	          j	        |          t	          j	        |          t	          j	        |          t	          j	        |          dz  }t          t	          j        |                    d x}x}x}}d S )NzjUPDATE budget_plan_items SET monthly_amount_chf='151.00',updated_at='later' WHERE plan_item_id='plan_food'r   r   r   r   r   r   r   r   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?   z
2026-01-31r   r   rI   )z%(py0)s == %(py3)sr&  )r`   r   zassert %(py5)sr   r   1zJassert %(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.endswith
}(%(py5)s)
})rM   r   r   r   )r$  r   r   r"   )r   r   r   r   r   r   r   r   r   r   r   r   )jarvis_finance.services.household_cockpitserviceshousehold_cockpitsetattrr   endswith)monkeypatchr   r   r   r   r   r   r   cockpit_moduler(  r4   r   @py_format4r   r   r&  r'  s                  @@r   Ltest_cockpit_revision_covers_plans_and_is_computed_once_inside_read_snapshotr2  /  sp   zzHD1/55FLL}~~~KKMMM-d33=3v==========3v============-=====-=============d=====d======3===========v=====v===========================FFFFFFFFFE"EM6 6 6 6 6 6 6
 (LN_```"4<VVVF5A:5A55A.!/!*/3/**3////////!////*////3//////////////////////////////r    c                 4	   t                      ^} }}d)fd}t                    } |d	d
|             |dd
|            |dd|                                             t                    }||k    }|st          j        d|fd||f          dt          j                    v st          j        t                    rt          j        t                    nddt          j                    v st          j                  rt          j                  ndt          j        |          dt          j                    v st          j        |          rt          j        |          nddz  }dd|iz  }t          t          j
        |                    d x}}t          dd| g          }	|	d         d         }
d}|
|k    }|slt          j        d|fd|
|f          t          j        |
          t          j        |          dz  }dd |iz  }t          t          j
        |                    d x}
x}}|	d         d!         }
d"}|
|k    }|slt          j        d|fd|
|f          t          j        |
          t          j        |          dz  }dd |iz  }t          t          j
        |                    d x}
x}}|	d#         d$         }
d%}|
|k    }|slt          j        d|fd|
|f          t          j        |
          t          j        |          dz  }dd |iz  }t          t          j
        |                    d x}
x}} |d&d'd                                             t          dd| g          }|d         d!         }
d(}|
|k    }|slt          j        d|fd|
|f          t          j        |
          t          j        |          dz  }dd |iz  }t          t          j
        |                    d x}
x}}|d#         d$         }
d%}|
|k    }|slt          j        d|fd|
|f          t          j        |
          t          j        |          dz  }dd |iz  }t          t          j
        |                    d x}
x}}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   )r4  r$   r5  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-03r   r   r   r   version_beforer   r   r   z
2026-08-01z
2026-08-31r   rQ   open_review_countr   rI   rK   rL   rO   rP   review_scopedate_and_accountsr   rp   r   review_unboundz
2026-08-04unavailable_global)r4  r"   r$   r"   r5  r'   r   r6   )r   r   r   r   r   r   r   r   r   r   r   r   )r   r   r   r7  r;  r   r   r   r   scopedr   r   r   r   unavailabler   s                  @r   Mtest_review_quality_is_date_and_account_scoped_and_missing_binding_is_partialrC  F  s   %-ZZ"D)Y	
 	
 	
 	
 	
 	
 8==NIj,	222Ij,	222IlL)444KKMMM-d33E3~EEEEEEEEEE3~EEEEEEEEEEEE-EEEEE-EEEEEEEEEEEEEdEEEEEdEEEEEE3EEEEEEEEEEE~EEEEE~EEEEEEEEEEEEEEEEEEEEEEEEEEE"l  F &>-.3!3.!3333333333.!33333.3333!333333333333333333333333333&>.)@-@@)-@@@@@@@@@@@)-@@@@@@)@@@@-@@@@@@@@@@@@@@@@@@@@@@@@@@@@- *7i7*i7777777777*i77777*7777i777777777777777777777777777Id333KKMMM'l  K v~.F2FF.2FFFFFFFFFFF.2FFFFFF.FFFF2FFFFFFFFFFFFFFFFFFFFFFFFFFFF}%h/<9</9<<<<<<<<<</9<<<<</<<<<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   )2
__future__r   builtinsr   _pytest.assertion.rewrite	assertionrewriter   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   r   r   r   r   r   markparametrizer   r   r  r  r"  r2  rC  r   r    r   <module>rU     s   " " " " " "                            ! ! ! ! ! ! ) ) ) ) ) ) 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    