
    pjS5                    p   d dl mZ 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 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mZ d dlmZ d dl m!Z! dZ" eddd          Z#d1dZ$d2dZ%d3dZ&dd d4d&Z' G d' d(          Z(d5d)Z)d5d*Z*d5d+Z+d5d,Z,d5d-Z-d5d.Z.d5d/Z/d5d0Z0dS )6    )annotations)Iterator)date)DecimalN)
Connection)
TestClient)get_db)
create_app)QuoteRefreshRequest)EquityPriceQuote)get_equity_summarylist_equity_positions)_quality_from_errorrefresh_equity_quoterefresh_equity_quotes_batch)confirmed_canonical_positionsrun_daily_market_valuation)get_portfolio_advisor_snapshot)apply_migrationsz2026-07-26T12:00:00Zi        valuedate | str | Nonereturnstrc                    t          | t                    r|                                 n't          | pt                                                    S N)
isinstancer   	isoformatr   TARGET)r   s    2tests/unit/test_sprint11_portfolio_completeness.py
_date_textr"      s?     *5$ 7 7]5??SA\&JZJZJ\J\=]=]]    r   c                    t          j        dd          } t           j        | _        |                     d           t          |            |                     dt          f           |                     dt          f           | S )Nz:memory:F)check_same_threadzPRAGMA foreign_keys=ONzINSERT INTO platforms(platform_id,name,platform_type,country,default_currency,created_at) VALUES('postfinance','PostFinance','broker','CH','CHF',?)zINSERT INTO accounts(account_id,platform_id,account_name,account_type,currency,created_at) VALUES('etrading','postfinance','Manual Portfolio','brokerage','CHF',?))sqlite3connectRowrow_factoryexecuter   NOWconns    r!   _connectr.      s    ?:???D{DLL)***TLL  g  jm  io  p  p  pLL  v  y|  x~      Kr#   r-   r   c                h     t                      }d fd}||j        t          <   t          |          S )Nr   Iterator[Connection]c               3     K    V  d S r    r,   s   r!   overridez_client.<locals>.override)   s      




r#   )r   r0   )r
   dependency_overridesr	   r   )r-   appr3   s   `  r!   _clientr6   '   sC    
,,C     '/CV$c??r#   Ftransactionscountintr8   boolNonec               v   t          |          D ]}d|d}d|d}d|d}|                     d|dd|d||| d	t          f           |                     d
d|d|||d| d	t          f           |r,|                     dd|d|d|dd|dt          f           |                                  d S )Nzinst-02dTCH010dzINSERT INTO instruments(instrument_id,asset_class,name,ticker,isin,exchange,currency,provider_symbol,data_provider_primary,created_at) VALUES(?,?,?,?,?,'SIX','CHF',?,'fmp',?)stockz	Position z.SWzINSERT INTO instrument_price_mappings(mapping_id,instrument_id,isin,ticker,exchange,currency,provider,provider_symbol,provider_market,trading_currency,mapping_status,confidence,created_at) VALUES(?,?,?,?,?,'CHF','fmp',?,'SIX','CHF','mapped','1',?)zmap-SIXa"  INSERT INTO transactions(transaction_id,transaction_type,account_id,instrument_id,trade_date,quantity,currency_original,source_type,source_id,row_hash,is_confirmed,quality_status,created_at) VALUES(?, 'initial_position_snapshot','etrading',?,'2026-07-01','1','CHF','confirmed',?,?,1,'ok',?)ztx-zsource-zhash-)ranger*   r+   commit)r-   r9   r8   indexinstrument_idtickerisins          r!   _seed_positionsrJ   /   s   u T T++++ U E     F  IV  X_  ax  mr  ax  ax  ax  z@  BF  KQ  HV  HV  HV  X[  H\  	]  	]  	]  O  Rd  Y^  Rd  Rd  Rd  fs  uy  {A  CH  MS  JX  JX  JX  Z]  Q^  	_  	_  	_ 	TLL  ~  AR  GL  AR  AR  AR  Ta  cx  mr  cx  cx  cx  zM  BG  zM  zM  zM  OR  @S  T  T  TKKMMMMMr#   c                  (    e Zd ZdZddZddddZdS )FreshProvidermockr   r<   c                    g | _         d S r   )calls)selfs    r!   __init__zFreshProvider.__init__=   s     "


r#   N
price_dateprovider_symbolr   rS   date | Noner   c          
         | j                             |           t          |dt          d          ddt	          |           dd          S )NCHF10fmprC   
T12:00:00ZfreshrT   currencycloseproviderprovider_marketprice_timestampquality_status)rO   appendr   r   r"   rP   rT   rS   s      r!   	get_pricezFreshProvider.get_price?   s    
/***%W^_cWdWdot  GL  ak  lv  aw  aw  ^C  ^C  ^C  T[  \  \  \  	\r#   r   r<   rT   r   rS   rU   r   r   )__name__
__module____qualname__namerQ   re   r2   r#   r!   rL   rL   ;   sV        D# # # #KO \ \ \ \ \ \ \ \r#   rL   c           
        t                      }t          |d           t                      |                     dfd           t	          |t          ddt                                          dd                    }|j        |j	        cxk    r|j
        cxk    r|j        cxk    rdk    sn J |j        du sJ t          j                  dk    sJ |                    d	                                          d         dk    sJ |                     dd
            t	          |t          ddt                                          dd                    }|j        dk    sJ |j	        dk    sJ |j        du sJ d S )N   Djarvis_finance.services.market_service.equity_price_provider_by_namec                    S r   r2   _namer_   s    r!   <lambda>zJtest_batch_processes_all_22_and_resume_skips_fresh_cache.<locals>.<lambda>H       nv r#   autod   r   r_   limitrS   pacing_secondsmax_retriesTzVSELECT COUNT(*) FROM market_prices WHERE price_date='2026-07-24' AND close IS NOT NULLc                Z    d dD                                  t          d                    S )Nc              3     K   | ]}|V  d S r   r2   .0_s     r!   	<genexpr>z]test_batch_processes_all_22_and_resume_skips_fresh_cache.<locals>.<lambda>.<locals>.<genexpr>O   s$      n}n}uvopn}n}n}n}n}n}r#   r2   zfresh prices must be skippedthrowAssertionError)rq   s    r!   rr   zJtest_batch_processes_all_22_and_resume_skips_fresh_cache.<locals>.<lambda>O   sN    n}n}z|n}n}n}  oD  oD  ES  Tr  Es  Es  ot  ot r#   )r.   rJ   rL   setattrr   r   r    r   totalupdatedvaluedcoverage_totalcompletelenrO   r*   fetchonecached)monkeypatchr-   resultresumedr_   s       @r!   8test_batch_processes_all_22_and_resume_skips_fresh_cacher   D   s    ::DD"H^`v`v`v`vwww(/BFZ]jpjzjzj|j|  NO  ]^  0_  0_  0_  `  `F<6>YYYYV]YYYYf>SYYYYWYYYYYYY?d""""x~"$$$$<<pqqzz||}~  DF  F  F  F  F^  at  at  u  u  u)$0CV[^kqk{k{k}k}  OP  ^_  1`  1`  1`  a  aG>R?at######r#   c           
        t                      }t          |d           |                    dt                                          t
          f           |                                 t                      |                     dfd           t          |t          ddt                                          dd                    }|j        dk    r|j        dk    rj        d	gk    sJ t                      }t          |d           |                    d
t                                          t
          f           |                                 t                      |                     dfd           t          |t          ddt                                          dd                    }|j        dk    r|j        dk    rj        d	gk    sJ d S )N   zINSERT INTO market_prices(market_price_id,instrument_id,price_date,close,currency,provider,provider_symbol,provider_market,quality_status,created_at)
                    VALUES('wrong-cache','inst-00',?,'99','USD','fmp','T00.SW','SIX','fresh',?)rn   c                    S r   r2   rp   s    r!   rr   zLtest_batch_does_not_treat_wrong_currency_cache_as_coverage.<locals>.<lambda>]   rs   r#   rt   ru   r   rv   T00.SWa  INSERT INTO market_prices(market_price_id,instrument_id,price_date,close,currency,provider,provider_symbol,provider_market,quality_status,created_at)
                    VALUES('wrong-exchange-cache','inst-00',?,'99','CHF','fmp','T00.SW','NASDAQ','fresh',?)c                    S r   r2   )rq   exchange_providers    r!   rr   zLtest_batch_does_not_treat_wrong_currency_cache_as_coverage.<locals>.<lambda>g   s    n r#   )r.   rJ   r*   r    r   r+   rE   rL   r   r   r   r   r   rO   )r   r-   r   wrong_exchange_connexchange_resultr   r_   s        @@r!   :test_batch_does_not_treat_wrong_currency_cache_as_coverager   V   sJ   ::DD!LL cekeueuewewy|d}  KKMMMH^`v`v`v`vwww(/BFZ]jpjzjzj|j|  NO  ]^  0_  0_  0_  `  `F=A&.A"5"5(.XJ:V:V:VV"**'+++ !oqw  rB  rB  rD  rD  FI  qJK K K   %^````  A  A  A12EGZdjru  CI  CS  CS  CU  CU  fg  uv  Hw  Hw  Hw  x  xO!Q&&?+Ba+G+GL]LchpgqLqLqLqqLqLqr#   c           
        t                      }t          |d            G d dt                    } |            |                     dfd           t	          |t          ddt                                          dd	                    }|j        dk    r|j	        d         d
         dk    sJ |
                    d                                          d         dk    sJ d S )Nr   c                      e Zd Zddd
d	ZdS )Rtest_rate_limit_retries_without_marking_instrument_inactive.<locals>.RetryProviderNrR   rT   r   rS   rU   r   r   c          
        | j                             |           t          | j                   dk    r't          |dd ddt	          |           ddd          S t          |dt          d	          ddt	          |           dd
          S )Nr   rW   rY   rC   rZ   rate_limitedfmp_rate_limitedrT   r]   r^   r_   r`   ra   rb   error_messagerX   r[   r\   )rO   rc   r   r   r"   r   rd   s      r!   re   z\test_rate_limit_retries_without_marking_instrument_inactive.<locals>.RetryProvider.get_pricep   s   Jo...4:!##'RW_cns  FK  `j  ku  `v  `v  ]B  ]B  ]B  Sa  qC  D  D  D  D#Oe[bcg[h[hsx  KP  eo  pz  e{  e{  bG  bG  bG  X_  `  `  `  `r#   rg   )rh   ri   rj   re   r2   r#   r!   RetryProviderr   o   s=        OS 	` 	` 	` 	` 	` 	` 	` 	`r#   r   rn   c                    S r   r2   rp   s    r!   rr   zMtest_rate_limit_retries_without_marking_instrument_inactive.<locals>.<lambda>v   rs   r#   rt   ru   r   rv   attempts   zGSELECT instrument_status FROM instruments WHERE instrument_id='inst-00'suspected_inactive)r.   rJ   rL   r   r   r   r    r   r   resultsr*   r   )r   r-   r   r   r_   s       @r!   ;test_rate_limit_retries_without_marking_instrument_inactiver   l   s<   ::DD!` ` ` ` ` ` ` ` }H^`v`v`v`vwww(/BFZ]jpjzjzj|j|  NO  ]^  0_  0_  0_  `  `F>Q6>!#4Z#@A#E#E#EE<<abbkkmmnop  uI  I  I  I  I  I  Ir#   c           	     >   t                      }t          |dd           |                    d           |                                 |                     dd            t          |dt          dt                                          	                    }t          |t          dd
t                                          d                    }|j
        |j        dk    sJ |j        dk    r,t          |t                                                    g k    sJ d S )Nr   Tr7   zfUPDATE instruments SET valuation_policy='exclude_from_auto_price_update' WHERE instrument_id='inst-00'rn   c                 Z    d dD                                  t          d                    S )Nc              3     K   | ]}|V  d S r   r2   r|   s     r!   r   zVtest_excluded_instrument_is_not_fetched_or_valued.<locals>.<lambda>.<locals>.<genexpr>   s$      o~o~vwpqo~o~o~o~o~o~r#   r2   z&excluded instrument contacted providerr   )_argss    r!   rr   zCtest_excluded_instrument_is_not_fetched_or_valued.<locals>.<lambda>   sN    o~o~{}o~o~o~  pE  pE  FT  U}  F~  F~  p  p r#   inst-00rt   r_   rS   ru   r   )r_   rw   rS   rx   missing)as_of)r.   rJ   r*   rE   r   r   r   r    r   r   r^   rb   r   r   )r   r-   directbatchs       r!   1test_excluded_instrument_is_not_fetched_or_valuedr   |   s5   ::DD!$////LLyzzzKKMMM^  a  a  @  @  @!$	3FPVcicscscucu3v3v3vwwF'.A6Y\ioiyiyi{i{  MN  /O  /O  /O  P  PE<F$9Y$F$F$FF;! =d&JZJZJ\J\ ] ] ]ac c c cc c cr#   c           	        t          d          dk    sJ t          d          dk    sJ t          d          dk    sJ t                      }t          |d           t          dd	t	          d
          dddd          |                     dfd           t          |dt          dt          	                                                    }|j
        dk    sJ |                    d                                          d         dk    sJ t          ddt	          d
          dddd          |                     dfd           t          |dt          dt          	                                                    }|j
        dk    sJ |                    d                                          d         dk    sJ d S )Nfmp_endpoint_restrictedplan_restrictedfmp_auth_error
auth_errorr   r   r   r   rW   11yfinanceXSWXz2026-07-25T12:00:00Zr[   r\   rn   c                @     t          dddfdi                      S )NPr2   re   c                    S r   r2   )rP   r   _kwargsfutures      r!   rr   zZtest_provider_error_classes_future_and_currency_guards.<locals>.<lambda>.<locals>.<lambda>   s      kq r#   type)rq   r   s    r!   rr   zHtest_provider_error_classes_future_and_currency_guards.<locals>.<lambda>   s[      osnrsvxz  ~I  Kq  Kq  Kq  Kq  }r  os  os  ou  ou r#   r   rt   r   future_price_rejectedz"SELECT COUNT(*) FROM market_pricesr   USDz2026-07-24T12:00:00Zc                @     t          dddfdi                      S )Nr   r2   re   c                    S r   r2   )rP   r   r   mismatchs      r!   rr   zZtest_provider_error_classes_future_and_currency_guards.<locals>.<lambda>.<locals>.<lambda>   s      ks r#   r   )rq   r   s    r!   rr   zHtest_provider_error_classes_future_and_currency_guards.<locals>.<lambda>   s[      ounrsvxz  ~I  Ks  Ks  Ks  Ks  }t  ou  ou  ow  ow r#   currency_mismatch)r   r.   rJ   r   r   r   r   r   r    r   rb   r*   r   )r   r-   rejectedr   r   s      @@r!   6test_provider_error_classes_future_and_currency_guardsr      sC   899=NNNNN/00L@@@@122nDDDD::DD!hgVZmmfp  CI  [q  BI  J  J  JF^  au  au  au  au  v  v  v#D)5HRXekeueuewew5x5x5xyyH"&=====<<<==FFHHKqPPPP5PWX\P]P]hr  EK  ]s  DK  L  L  LH^  aw  aw  aw  aw  x  x  x#D)5HRXekeueuewew5x5x5xyyH"&99999<<<==FFHHKqPPPPPPr#   c                    t                      } t          |           }|j        dk    r|j        du r|j        J t          | dd            G d d          }t          | t                                          d |            i	          }|j        d
k    sJ t          |           }t          |          dk    sJ t          d |D                       dk    sJ t          |           }|j        du sJ |j        J |j        dk    sJ |j        dk    sJ |j        ddgk    sJ t          d |D                       sJ d S )NunavailableF   Tr7   c                       e Zd ZdZdddd
ZdS )^test_unknown_values_remain_null_and_partial_summary_lists_all_missing.<locals>.PartialProviderrY   NrR   rT   r   rS   rU   r   r   c          
         |dk    rt          d          nd }t          |d|ddt          |           d|rdnd|rd nd	
          S )Nr   rX   rW   rY   rC   rZ   r[   endpoint_restrictedr   r   )r   r   r"   )rP   rT   rS   r^   s       r!   re   zhtest_unknown_values_remain_null_and_partial_summary_lists_all_missing.<locals>.PartialProvider.get_price   s    %4%@%@GDMMMdE#Oe[`kp  CH  ]g  hr  ]s  ]s  Z  Z  Z  [`  P{  PW  PW  f{  SX  Kw  KO  KO  ^w  x  x  x  xr#   rg   rh   ri   rj   rk   re   r2   r#   r!   PartialProviderr      sB        OS 	x 	x 	x 	x 	x 	x 	x 	xr#   r   rY   r   price_providerspartialc              3  (   K   | ]}|j         d u V  d S r   )market_value_chfr}   rows     r!   r   zXtest_unknown_values_remain_null_and_partial_summary_lists_all_missing.<locals>.<genexpr>   s*      <<s#t+<<<<<<r#   r   z10.00T01T02c              3  (   K   | ]}|j         d u V  d S r   )portfolio_share_pctr   s     r!   r   zXtest_unknown_values_remain_null_and_partial_summary_lists_all_missing.<locals>.<genexpr>   s*      ??3s&$.??????r#   )r.   r   statuscoverage_completeequity_value_chfrJ   r   r    r   r   r   sumvalued_partial_chfunvalued_positionsunvalued_tickersall)r-   emptyr   r   rowssummarys         r!   Etest_unknown_values_remain_null_and_partial_summary_lists_all_missingr      s   ::Dt$$E<=((U-D-M-MRWRhRpRppD!$////x x x x x x x x
 (F4D4D4F4FY^`o`o`q`qXrsssF=I%%%% &&Dt99>>>><<t<<<<<AAAA &&G$----#+++%0000%****#u~5555??$??????????r#   c                    t                      } t          | dd            G d d          }t          | t                                          d |            i          }|j        dk    r	d	|j        v sJ t          |           d
         }t          |           }|j	        |j
        d	k    sJ |j        du r|j        J t          |           j        g k    sJ d S )Nr   Tr7   c                       e Zd ZdZdddd
ZdS )btest_stale_snapshot_values_remain_unknown_and_global_signals_are_suppressed.<locals>.StaleProviderrY   NrR   rT   r   rS   rU   r   r   c          	     H    t          |dt          d          dddd          S )NrW   rX   rY   rC   z2026-07-20T12:00:00Zr[   r\   )r   r   rd   s      r!   re   zltest_stale_snapshot_values_remain_unknown_and_global_signals_are_suppressed.<locals>.StaleProvider.get_price   sE    #Oe[bcg[h[hsx  KP  bx  IP  Q  Q  Q  Qr#   rg   r   r2   r#   r!   StaleProviderr      sB        OS 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Qr#   r   rY   r   r   stale_pricer   F)r.   rJ   r   r    r   r   reason_codesr   r   r   status_coder   r   r   investment_signals)r-   r   r   positionr   s        r!   Ktest_stale_snapshot_values_remain_unknown_and_global_signals_are_suppressedr      s   ::DD!$////Q Q Q Q Q Q Q Q (F4D4D4F4FY^`m`m`o`oXpqqqF=I%%-6;N*N*N*NN$T**1-H &&G$,1E1V1V1VV$--'2J2R2RR)$//BbHHHHHHr#   c                   t                      }t          |dd           |                     dd            t          |          }|                    d          j        dk    sJ |                    d          j        dk    sJ d S )	Nr   Tr7   rn   c                 Z    d dD                                  t          d                    S )Nc              3     K   | ]}|V  d S r   r2   r|   s     r!   r   zUtest_equity_get_endpoints_do_not_invoke_provider.<locals>.<lambda>.<locals>.<genexpr>   sH        {J  {J  BC{|  {J  {J  {J  {J  {J  {Jr#   r2   zGET render called providerr   )r   r   s     r!   rr   zBtest_equity_get_endpoints_do_not_invoke_provider.<locals>.<lambda>   sf      {J  {J  GI  {J  {J  {J  {P  {P  Q_  `|  Q}  Q}  {~  {~ r#   z/api/equity/positions   z/api/equity/summary)r.   rJ   r   r6   getr   )r   r-   clients      r!   0test_equity_get_endpoints_do_not_invoke_providerr      s    ::DD!$////^  a~  a~      T]]F::-..:cAAAA::+,,8C??????r#   )r   r   r   r   )r   r   )r-   r   r   r   )r-   r   r9   r:   r8   r;   r   r<   rf   )1
__future__r   collections.abcr   datetimer   decimalr   r&   r   fastapi.testclientr   jarvis_finance.api.dependenciesr	   jarvis_finance.api.mainr
   !jarvis_finance.api.schemas.marketr   !jarvis_finance.market_data.pricesr   &jarvis_finance.services.equity_servicer   r   &jarvis_finance.services.market_servicer   r   r   +jarvis_finance.services.portfolio_analyticsr   r   )jarvis_finance.services.portfolio_advisorr   !jarvis_finance.storage.migrationsr   r+   r    r"   r.   r6   rJ   rL   r   r   r   r   r   r   r   r   r2   r#   r!   <module>r
     s   " " " " " " $ $ $ $ $ $                    ) ) ) ) ) ) 2 2 2 2 2 2 . . . . . . A A A A A A > > > > > > \ \ \ \ \ \ \ \ y y y y y y y y y y q q q q q q q q T T T T T T > > > > > >	dAr		^ ^ ^ ^       KP 	 	 	 	 	 	\ \ \ \ \ \ \ \$ $ $ $$r r r r,I I I I 	d 	d 	d 	dQ Q Q Q(@ @ @ @0I I I I&@ @ @ @ @ @r#   