
    mjp<                    (   d dl mZ d dlZd dlmZ d dlZd dlmZmZ d dl	m
Z
mZmZmZmZmZmZmZmZmZmZmZ d dlmZmZmZmZmZ d!d	Zd"d#dZ G d de          Zd$dZd$dZ d$dZ!d$dZ"d$dZ#d%dZ$d$dZ%d$dZ&d$dZ'd$dZ(d$dZ)d$dZ*d$d Z+dS )&    )annotationsN)Path)apply_migrationsget_schema_version)add_demo_migros_receiptaccept_grocery_product_matchreject_grocery_product_matchcreate_manual_grocery_mappingrefresh_grocery_mapping_priceget_grocery_optimizer_dashboardrun_grocery_optimization$generate_grocery_optimization_reportget_grocery_provider_strategybuild_rappn_link_outpreview_manual_grocery_mappingconfirm_manual_grocery_mapping) search_and_store_product_matchessearch_open_prices_spikeCoopProviderGroceryProductProviderGroceryProviderResultreturnsqlite3.Connectionc                     t          j        d          } t           j        | _        t	          |            t          |           dk    sJ | S )Nz:memory:1   )sqlite3connectRowrow_factoryr   r   )conns    /tests/unit/test_grocery_matching_learning_v2.pydbr"      sG    ?:&&D{DTd##r))))K    Milch 1Lr1r    dictc                8    t          | dd||dddddg          S )	N
2026-05-19Migros1Llz1.50/lz1.50)raw_product_namequantity_textunitunit_price_texttotal_price_text)purchase_date
store_name
receipt_iditems)r   )r    rawr3   s      r!   seed_receiptr6   "   s_    "4|PXeo  NQ  dh  ru  JR  hn  yo  yo  xp  q  q  q  qr#   c                  *     e Zd Zd fdZdddZ xZS )StaticProviderretailerstrresultslist[GroceryProviderResult]r   Nonec                    t                                          d            || _        d|                                                    dd           d| _        || _        d S )Nc                    dS )N  _urls    r!   <lambda>z)StaticProvider.__init__.<locals>.<lambda>(   s    b r#   fetcherzhttps:// -z	.example/)super__init__r9   lowerreplacebase_url_results)selfr9   r;   	__class__s      r!   rJ   zStaticProvider.__init__'   s^    111 P8>>#3#3#;#;C#E#EPPPr#   Nde-CHT   Q r    r   query
str | Nonelocale	use_cacheboolmax_resultsintmax_age_seconds
list[dict]c                T    | xj         dz  c_         d | j        d |         D             S )N   c                6    g | ]}|                                 S rA   )as_dict).0rs     r!   
<listcomp>z2StaticProvider.search_products.<locals>.<listcomp>/   s     AAA		AAAr#   )request_countrN   )rO   r    rT   r9   rV   rW   rY   r[   s           r!   search_productszStaticProvider.search_products-   s5    aAAT]<K<%@AAAAr#   )r9   r:   r;   r<   r   r=   )NrQ   TrR   rS   )r    r   rT   r:   r9   rU   rV   r:   rW   rX   rY   rZ   r[   rZ   r   r\   )__name__
__module____qualname__rJ   re   __classcell__)rP   s   @r!   r8   r8   &   s_                   B B B B B B B B Br#   r8   r=   c                    t                      } t          | d          }t          d           }t          | d|g|d         d         d         gd	          }t	          | |d
         d         d         d          }|d         dk    sJ |d         dk    sJ t          | d          }t          d           }t          | d|g|d         d         d         gdd          }|d         dk    sJ |d         dk    sJ |d
         d         d         dk    sJ t          | ddg|d         d         d         g          }|d         d         dk    sJ d S )Nr%   r3   c                    dS )Nz<article data-product><a href="/milch">Prix Garantie Milch 1L</a><span class="price">CHF 1.20</span><span class="unit-price">1.20/l</span></article>rA   rB   s    r!   rD   zktest_accept_match_creates_preferred_mapping_and_next_receipt_reuses_without_provider_call.<locals>.<lambda>5   s
      2H r#   rE   r4   r   product_item_idF	providersincluded_product_item_idsrW   matchesmatch_idpasst	user_notestatusacceptedtarget_retailerCoopr2c                Z    d dD                                  t          d                    S )Nc              3     K   | ]}|V  d S )NrA   )ra   _s     r!   	<genexpr>z~test_accept_match_creates_preferred_mapping_and_next_receipt_reuses_without_provider_call.<locals>.<lambda>.<locals>.<genexpr><   s      ooQqoooooor#   rA   zprovider must not be called)throwAssertionErrorrB   s    r!   rD   zktest_accept_match_creates_preferred_mapping_and_next_receipt_reuses_without_provider_call.<locals>.<lambda><   s'    oo"ooo6K6KN[xLyLy6z6z r#   Tro   rp   prefer_known_mappingsrW   provider_callsknown_mapping_hitsr^   sourceknown_mappingselected_retailersrp   summaryreplaceable_product_count)r"   r6   r   r   r   r   )	r    firstproviderfoundrw   secondfail_providerreusedruns	            r!   Ytest_accept_match_creates_preferred_mapping_and_next_receipt_reuses_without_provider_callr   2   s   44D$///E  %H  %H  I  I  IH,T4H:jopwjxyzj{  }N  kO  jP  \a  b  b  bE+D%	2B12Ej2Q]deeeHH++++%&&0000$4000F )z)z{{{M-dD]Opvw~p  AB  qC  DU  qV  pW  os  C  D  D  DF"#q((((&'1,,,,)Q)_<<<<
"46(gmnugvwxgy  {L  hM  gN  O  O  OCy>56!;;;;;;r#   c                 r   t                      } t          |           }t          d           }t          | d|g|d         d         d         gd          }t	          | |d	         d         d
         d          }|d         dk    sJ t          | d          }t          | d|g|d         d         d         gdd          }t          d |d	         D                       sJ t          | ddg|d         d         d         g          }|d         d         dk    sJ d|d         d         d         v sJ d S )Nc                    dS )Nz<article data-product><a href="/milch">Coop Milch 1L</a><span class="price">CHF 1.20</span><span class="unit-price">1.20/l</span></article>rA   rB   s    r!   rD   zLtest_rejected_match_is_persisted_and_not_recommended_again.<locals>.<lambda>H   s
      2 r#   rE   r%   r4   r   rm   Frn   rq   rr   falschrt   rv   rejectedrz   rk   Tr   c              3  H   K   | ]}|                     d           dk    V  dS )rv   	suggestedN)getra   ms     r!   r~   zMtest_rejected_match_is_persisted_and_not_recommended_again.<locals>.<genexpr>O   s1      II!quuX+-IIIIIIr#   ry   r   r   r   r$   optimized_listsrejected_products)r"   r6   r   r   r	   allr   )r    receiptr   r   r   r   r   r   s           r!   :test_rejected_match_is_persisted_and_not_recommended_againr   E   s   44D4  G  %  %  @  @  @H,T4H:jqryjz{|j}  P  kQ  jR  ^c  d  d  dE+D%	2B12Ej2Q]efffHH++++$4000F-dDXJkqrykz{|k}  P  lQ  kR  jn  z~      FIIvi7HIIIIIIII
"46(gmnugvwxgy  {L  hM  gN  O  O  OCy>56!;;;;./23FGGGGGGGr#   c                 8   t                      } t          |           }t          | |d         d         d         ddddddd	d
dd          }|d         dk    sJ |d         d	k    sJ |d         J t          | ddg|d         d         d         g          }|d         d         dk    sJ |                     d           t          | |d                   }|d         dk    sJ t          | ddg|d         d         d         g          }|d         d
k    sJ d S )Nr4   r   rm   DennerDenner Milch 1Lhttps://www.denner.ch/milchr*   r+   manual_match1.101.10/lzmanuell notiert)rm   rx   target_product_nametarget_product_urltarget_brandtarget_package_sizetarget_unit
match_typecandidate_price_textcandidate_unit_price_textru   rv   rw   r   last_price_checked_atr%   r   r   r   aT  INSERT INTO grocery_product_details_cache(detail_id,retailer,product_url,product_name,price_text,unit_price_text,package_size,ingredients_text,nutrition_json,fetched_at,cache_status,source_hash) VALUES ('d1','Denner','https://www.denner.ch/milch','Denner Milch 1L','1.10','1.10/l','1L',NULL,'{}','2026-05-19T10:00:00+00:00','cached','hash')
mapping_idz2026-05-19T10:00:00+00:00optimized_total_text)r"   r6   r
   r   executer   )r    r   mapping
run_before	refreshed	run_afters         r!   Etest_manual_mapping_can_be_saved_and_used_after_sourced_cache_refreshr   U   s   44D4  G+D''BRSTBUVgBh  {C  Yj  \  ks  IM  [^  ky  PV  rz  FW  X  X  XG8
****< N2222*+333)$(pwx  qA  BC  qD  EV  qW  pX  Y  Y  YJi !<=BBBBLL  h  i  i  i-dGL4IJJI,-1LLLLL(t
ovw~o  AB  pC  DU  pV  oW  X  X  XI+,666666r#   c            	        t                      } t          |           }dD ]Y}t          j        t                    5  t          | |d         d         d         dd|           d d d            n# 1 swxY w Y   Zt          j        t                    5  t          | |d         d         d         dddd	
           d d d            d S # 1 swxY w Y   d S )N)zjavascript:alert(1)zhttp://127.1/xzhttp://[::1]/xzhttp://169.254.169.254/latestzhttp://172.31.0.1/xzhttp://localhost/xr4   r   rm   ry   Bad)rm   rx   r   r   zhttps://coop.example/bad
evil_match)rm   rx   r   r   r   )r"   r6   pytestraises
ValueErrorr
   )r    r   
unsafe_urls      r!   5test_manual_mapping_rejects_unsafe_url_and_match_typer   e   s   44D4  G ` z z
]:&& 	z 	z)$@PQR@STe@fx~  UZ  oy  z  z  z  z	z 	z 	z 	z 	z 	z 	z 	z 	z 	z 	z 	z 	z 	z 	z	z	"	" _ _%dGG<LQ<OPa<btz  QV  kE  R^  	_  	_  	_  	__ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _s#   'A..A2	5A2	(CCCc            	     R   t                      } t          |           }t          | |d         d         d         ddddd	          }t          |           }|d
         d         d         d         }|d         d         d         |d         k    sJ |d         d         d         dv sJ d S )Nr4   r   rm   ry   Coop Milch 1Lhttps://coop.example/milchr   1.20rm   rx   r   r   r   r   receiptsknown_mappingsr   cache_status>   fehltfehlerfrischveraltet)r"   r6   r
   r   )r    r   r   dashitems        r!   7test_dashboard_surfaces_known_mappings_and_cache_statusr   o   s    44D4  G+D''BRSTBUVgBh  {A  Wf  {W  dr  IO  P  P  PG*400D
Aw'*D !!$\2gl6KKKKK !!$^48aaaaaaar#   tmp_pathr   c           	        t                      }t          |          }t          ||d         d         d         ddddd	           t          |d
dg|d         d         d         g          }t	          ||d         | dz  | dz            }t          |d                                                   }|d         du sJ d|v sJ d|v sJ d|v sJ d|v sJ d|v sJ d|v sJ d|v sJ d|v sJ d|v sJ d|v sJ d S )Nr4   r   rm   ry   r   r   r   r   r   r%   r   run_idzruntime-reportsrepo)reports_dir	repo_rootreport_pathruntime_onlyTzBon-AuswahlzAnalysierte ProduktezAkzeptierte AlternativenzAbgelehnte AlternativenzUnsichere ProduktezNicht gefundene Produkteu.   Geschätzte Ersparnis nur aus sicheren MatcheszBekannte Mappingszkeine medizinische Empfehlung)r"   r6   r
   r   r   r   	read_text)r   r    r   r   reporttexts         r!   Gtest_report_contains_known_mappings_sources_and_disclaimer_runtime_onlyr   y   s   44D4  G!$8H8KL]8^pv  M\  qM  Zh  E  F  F  F  F
"46(gnovgwxygz  |M  hN  gO  P  P  PC1$HS[^oSo  |D  GM  |M  N  N  NF}%&&0022D.!T))))D    !T))))%----$,,,,4''''%----;tCCCC$&&&&'4////*d222222r#   c                    t                      } t          |           }t          dt          dddd d d          g          }t	          | d|g|d         d         d	         gd
          }|d         d         d         dk    sJ |d         d         d         sJ t          | |d         d         d         d
          }|d         dk    sJ |d         dk    sJ t          | |d         d         d                   }|d         dk    sJ d S )Nry   zCoop Milch ohne Preiszhttps://coop.example/no-pricez0.50)r9   product_nameproduct_url
price_textr/   
confidencer%   r4   r   rm   Frn   rq   rv   needs_reviewrr   )save_as_preferredr   r   r   )r"   r6   r8   r   r   r   r	   )r    r   r   r   rw   r   s         r!   <test_no_price_provider_candidate_is_persisted_and_rejectabler      sv   44D4  Gf'<f[r  A`  mq  CG  TZ  ([  ([  ([  '\  ]  ]H,T4H:jqryjz{|j}  P  kQ  jR  ^c  d  d  dEAx(N::::Az****+D%	2B12Ej2QejkkkHH////L!^3333+D%	2B12Ej2QRRHH++++++r#   c                 "   t                      } t          |           }t          dt          ddddddgd          g          }t	          | d	|g|d
         d         d         gd          }t          | |d         d         d                    t          dt          ddddddgd          g          }t          | d          }t	          | d|g|d
         d         d         gdd          }t          d |d         D                       sJ d S )Nry   r   r   r   z1.20/lclose_matchz0.80)r9   r   r   r   r/   quality_flagsr   r%   r4   r   rm   Frn   rq   rr   r   r   zhttps://denner.example/milchr   r   rz   rk   Tr   c              3  F   K   | ]}|d          dk    o|d         dk    V  dS )r9   r   rv   r   NrA   r   s     r!   r~   zNtest_rejected_exact_match_does_not_suppress_other_retailers.<locals>.<genexpr>   s9      hhAq}(GQx[K-Ghhhhhhr#   )r"   r6   r8   r   r   r	   any)r    r   coopr   dennerr   found_agains          r!   ;test_rejected_exact_match_does_not_suppress_other_retailersr      s   44D4  G&#8&Wf  uQ  ^d  v~  O\  N]  jp  $q  $q  $q  #r  s  sD,T4D6fmnufvwxfy  {L  gM  fN  Z_  `  `  `E uY'7'::'FGGGH'<h]n  }[  hn  @H  Yf  Xg  tz  ({  ({  ({  '|  }  }F$4000F24&ntu|n}~  oA  BS  oT  nU  mq  }B  C  C  CKhhQ\]fQghhhhhhhhhhr#   c                    t                      } t          d | d         D                       }|d         du sJ d|d         v sd| d         d         v sJ | d         d         d	k    sJ t          d
 | d         d         D                       sJ t          d | d         d         D                       sJ t          d          }|d         d	k    sJ |d         dk    sJ d|d         vsJ d S )Nc              3  2   K   | ]}|d          dk    |V  dS )r   zRappn.chNrA   ra   rows     r!   r~   zPtest_provider_strategy_documents_rappn_linkout_and_llm_limits.<locals>.<genexpr>   s1      ]]s:R\?\?\?\?\?\?\]]r#   provider_matrixofficial_apiFzLink-Outrecommendationrappnmodelink_out_onlyc              3     K   | ]}d |v V  	dS )zPreise erfindenNrA   ra   r   s     r!   r~   zPtest_provider_strategy_documents_rappn_linkout_and_llm_limits.<locals>.<genexpr>   s(      YYT D(YYYYYYr#   
llm_policy	forbiddenc              3     K   | ]}d |v V  	dS )zProduktnamen normalisierenNrA   r   s     r!   r~   zPtest_provider_strategy_documents_rappn_linkout_and_llm_limits.<locals>.<genexpr>   s(      bb+t3bbbbbbr#   allowedr$   manual_sourcemanual/rappn_user_checkedz	api.rappnurl)r   nextr   r   )strategyr   links      r!   =test_provider_strategy_documents_rappn_linkout_and_llm_limitsr     s;   ,..H]]): ;]]]]]E E))))/000J(7BSTZB[4[4[4[[GV$7777YYXl5KK5XYYYYYYYYbb@VW`@abbbbbbbb
++D<?**** $?????d5k))))))r#   c                     ddddddddid	d
ddgit          dfd          } | d         d         }|d                             d          sJ |d         J |d         du sJ d|d         v sJ d S )Nr4      123Milch1coder   product_quantityosm_namez	Test Shopr(   CHF)datecurrency)idproductlocationproofc                H    t          d                                        S Njson
__import__dumpsrC   payloads    r!   rD   z@test_open_prices_without_price_is_never_secure.<locals>.<lambda>       *U[J\J\JbJbcjJkJk r#   )rT   rF   
candidatesr   
source_urlz/7r   can_count_as_secure_savingFno_pricer   r   endswithresult	candidater  s     @r!   .test_open_prices_without_price_is_never_securer&     s    ugkn.o.o  I  KV  ~W  kw  EJ  bK  bK  L  L  M  NG%G=k=k=k=klllF|$Q'I\"++D11111\"***12e;;;;?3333333r#   c            	        ddddddddd	d
diddidgit          dfd          } | d         d         }|d         dk    sJ |d         dk    sJ |d                             d          sJ |d         du sJ |d         sJ d S )Nr4      z1.25r  r(   r  r  r*   r	  r  z	Open Shopr  z
2026-05-18)r  pricer  r  r  r  r  c                H    t          d                                        S r  r  r  s    r!   rD   zRtest_open_prices_with_price_still_candidate_with_source_and_date.<locals>.<lambda>   r  r#   )barcoderF   r  r   r   
price_dater  z/8r  Fattributionr!  r#  s     @r!   @test_open_prices_with_price_still_candidate_with_source_and_dater.     s   F|qv  IP  fj  ik  ik  zD  FQ  yR  ^d  fr  ]s  t  t  u  vG%e=k=k=k=klllF|$Q'I\"f,,,,\"l2222\"++D1111112e;;;;]######r#   c                    t                      } t          |           }|d         d         d         }t          | |dddddd	d
ddd          }|d         du sJ |d         du sJ t          | |          }|d         dk    sJ |d         dk    sJ |                     d|f                                          }d |D             ddgk    sJ t          | ddg|g          }|d         d         dk    sJ |d         d         dk    sJ d S )Nr4   r   rm   r   r   r   r   r   r*   r+   r(   r   u   manuell geprüft)rm   rx   r   r   r   r   r   r   target_price_datetarget_price_sourceru   requires_confirmTr  r0  r1  z[SELECT action FROM grocery_mapping_audit_events WHERE product_item_id=? ORDER BY created_atc                    g | ]
}|d          S )actionrA   r   s     r!   rc   z[test_manual_price_preview_confirm_audit_and_secure_saving_when_complete.<locals>.<listcomp>   s    ,,,cCM,,,r#   manual_mapping_previewmapping_insertedr%   r   r   r   r^   estimated_savings_textz0.40)r"   r6   r   r   r   fetchallr   )r    r   item_idpreviewr   auditsr   s          r!   Gtest_manual_price_preview_confirm_audit_and_secure_saving_when_completer<     s   44D4  Ggq!"34G,T7\d  {L  a~  U[  w  UY  gj  ~J  `{  GY  Z  Z  ZG%&$..../0D8888,T7;;G&'<7777()-HHHHH\\w  {B  zD  E  E  N  N  P  PF,,V,,,1IK]0^^^^^
"48*iphq
r
r
rCy>56!;;;;y>23v======r#   c            	     ^   t                      } t          |           }|d         d         d         }t          | |ddddd	          }|d
         du sJ t          | |          }d|d         v sJ t	          | ddg|g          }|d         d         dk    sJ |d         d         dk    sJ d S )Nr4   r   rm   r   r   r   r   r   )rm   rx   r   r   r   r   r  Fr   quality_flags_jsonr%   r   r   r   r7  z0.00)r"   r6   r   r   r   )r    r   r9  r:  r   r   s         r!   Etest_manual_price_without_date_source_is_review_only_no_secure_savingr?     s    44D4  Ggq!"34G,T7\d  {L  a~  U[  w  @  @  @G/0E9999,T7;;GW%9:::::
"48*iphq
r
r
rCy>56!;;;;y>23v======r#   )r   r   )r$   r%   )r    r   r   r&   )r   r=   )r   r   r   r=   ),
__future__r   r   pathlibr   r   !jarvis_finance.storage.migrationsr   r   )jarvis_finance.services.grocery_optimizerr   r   r	   r
   r   r   r   r   r   r   r   r   /jarvis_finance.services.grocery_price_providersr   r   r   r   r   r"   r6   r8   r   r   r   r   r   r   r   r   r  r&  r.  r<  r?  rA   r#   r!   <module>rE     s   " " " " " "         R R R R R R R R                            t  t  t  t  t  t  t  t  t  t  t  t  t  t   q q q q q	B 	B 	B 	B 	B+ 	B 	B 	B< < < <&H H H H 7 7 7 7 _ _ _ _b b b b3 3 3 3(, , , ,	i 	i 	i 	i* * * *4 4 4 4$ $ $ $> > > >"
> 
> 
> 
> 
> 
>r#   