Ë
    †TKj¨(  ã                  óÌ   — d 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„Zdd„Z	 	 	 	 	 	 	 	 dd	„Zdd
„Zdd„Z	 	 	 	 	 	 	 	 	 	 dd„Z	 	 	 	 	 	 dd„Z	 	 	 	 	 	 dd„Zy)z0Layered matching engine with confidence scoring.é    )Úannotations)Úfuzz)ÚCatalogIndex)Údeduplicate_offersÚexpand_category_synonyms)ÚMatchResultÚOfferÚ	PartQueryc                óþ  — | j                   r0t        | j                   |«      }|rt        ddt        |«      d¬«      S | j                  r1t        | j                  | |«      }|rt        ddt        |«      d¬«      S | j                  s| j                  s| j                  r)t        | |«      \  }}|rt        |dt        |«      d¬«      S t        | |«      \  }}|rt        |dt        |«      d¬«      S t        d	d
g d¬«      S )uÒ   Match a PartQuery against the catalog index using layered strategy.

    Priority: OE (0.95) â†’ engine_code (0.80) â†’ attributes (â‰¤0.90) â†’ fuzzy (â‰¤0.50).
    First layer that produces results wins.
    gffffffî?ÚoeT)Ú
confidenceÚmethodÚoffersÚmatchedgš™™™™™é?Úengine_codeÚatrybutyÚfuzzyç        ÚbrakF)Ú
oe_numbersÚ_match_by_oer   r   r   Ú_match_by_engine_codeÚbrandÚmodelÚcategoryÚ_match_by_attributesÚ_match_fuzzy)ÚqueryÚindexr   Úconfs       úG/var/www/html/agent.samochodowy/src/agent_samochodowy/matcher/engine.pyÚmatchr"      s  € ð ×ÒÜ˜e×.Ñ.°Ó6ˆÙÜØØÜ)¨&Ó1Øô	ð ð ×ÒÜ& u×'8Ñ'8¸%ÀÓGˆÙÜØØ$Ü)¨&Ó1Øô	ð ð ‡{‚{�e—k’k U§^¢^Ü+¨E°5Ó9‰ˆ�ÙÜØØ!Ü)¨&Ó1Øô	ð ô    uÓ-�L€FˆDÙÜØØÜ% fÓ-Øô	
ð 	
ô  #¨f¸RÈÔOÐOó    c                óÎ   — g }t        «       }| D ]S  }|j                  |«      D ]=  }|j                  |vsŒ|j                  |j                  «       |j	                  |«       Œ? ŒU |S )z7Search for offers matching any of the given OE numbers.)ÚsetÚsearch_by_oeÚoffer_idÚaddÚappend)r   r   ÚresultsÚseen_idsr   Úoffers         r!   r   r   A   sf   € à€GÜ›€HØò &ˆØ×'Ñ'¨Ó+ò 	&ˆEØ�~‰~ XÒ-Ø—‘˜UŸ^™^Ô,Ø—‘˜uÕ%ñ	&ð&ð
 €Nr#   c                óþ   — |j                  | «      }|sg S |j                  rV|D �cg c]G  }|j                  sŒ|j                  j                  «       |j                  j                  «       k(  sŒF|‘ŒI }}|r|S |S c c}w )z;Search by engine code, optionally filtering by brand/model.)Úsearch_by_engine_coder   Úlower)r   r   r   r   ÚoÚfiltereds         r!   r   r   M   so   € ð ×(Ñ(¨Ó5€FÙØˆ	ð ‡{‚{Ø%Ö\˜!¨¯«°Q·W±W·]±]³_ÈÏÉ×HYÑHYÓH[Ó5[’AÐ\ˆÐ\ÙØˆOØ€Mùò ]s   ¦A:¸5A:Á.A:c                óî   ‡‡— t        |«      }| j                  r/| j                  j                  «       Št        ˆfd„|D «       «      ry| j                  j                  «       Št        ˆfd„|D «       «      S )zDCheck if an offer plausibly matches the query category via synonyms.c              3  ó&   •K  — | ]  }|‰v –— Œ
 y ­w©N© )Ú.0ÚsynÚ	cat_lowers     €r!   ú	<genexpr>z*_offer_matches_category.<locals>.<genexpr>g   s   øè ø€ Ò4 Cˆs�iÔÑ4ùó   ƒTc              3  ó&   •K  — | ]  }|‰v –— Œ
 y ­wr4   r5   )r6   r7   Útitle_lowers     €r!   r9   z*_offer_matches_category.<locals>.<genexpr>k   s   øè ø€ Ò6 cˆs�kÔ!Ñ6ùr:   )r   r   r/   ÚanyÚtitle)r,   Úquery_categoryÚsynonymsr8   r<   s      @@r!   Ú_offer_matches_categoryrA   a   sZ   ù€ ä'¨Ó7€Hà‡~‚~Ø—N‘N×(Ñ(Ó*ˆ	ÜÓ4¨8Ô4Ô4Øà—+‘+×#Ñ#Ó%€KÜÓ6¨XÔ6Ó6Ð6r#   c                ó¤  — | j                  «       D �cg c]!  }t        |«      dk\  sŒ|j                  «       ‘Œ# }}|sg S g }|D ]…  }|j                  xs dj                  «       }|dz   |j                  z   j                  «       }|D ]@  }||v r|j                  |«        Œ]|sŒ|j                  |«      sŒ/|j                  |«        Œ… Œ‡ |S c c}w )zºReturn offers where the query model appears in offer model/title.

    Supports prefix matching: query "R410" matches offer model "R",
    and query "LF" matches offer model "LF45".
    é   Ú ú )ÚsplitÚlenr/   r   r>   r)   Ú
startswith)	Úquery_modelr   ÚtÚtokensr   r0   Úoffer_modelÚhaystackÚtoks	            r!   Ú_model_in_resultsrO   n   sÍ   € ð "-×!2Ñ!2Ó!4ÖD˜A¼¸A»À!»ˆa�g‰g�iÐD€FÐDÙØˆ	Ø€GØò ˆØ—w‘w’} "×+Ñ+Ó-ˆØ #Ñ%¨¯©Ñ/×6Ñ6Ó8ˆØò 	ˆCà�h‰Ø—‘˜qÔ!Ùâ˜sŸ~™~¨kÕ:Ø—‘˜qÔ!Ùñ	ðð €Nùò! Es
   “C§Cc                ó  — i }|r||d<   |r||d<    | j                   dd|i|¤Ž}|r|S |j                  «       d   }|j                  «       |j                  «       k7  r | j                   dd|i|¤Ž}|r|S ddl}|j	                  d|«      }|ru|j                  d«      }	|	j                  «       |j                  «       k7  rC|	j                  «       |j                  «       k7  r"t        |	«      dk\  r | j                   dd|	i|¤Ž}|S )	u'  Try exact model, then base token, then series prefix.

    Examples:
    - "Sprinter w904" â†’ try "Sprinter w904", then "Sprinter"
    - "R410" â†’ try "R410", then "R410" (prefix matching in index handles Râ†’R410)
    - "LF" â†’ try "LF" (prefix matching in index handles LFâ†’LF45/LF55)
    r   r   r   r   Nz^([A-Za-z]+)é   r5   )Úsearch_by_attributesrF   r/   Úrer"   ÚgrouprG   )
r   r   r   r   Úkwargsr   ÚbaserS   Úprefix_matchÚprefixs
             r!   Ú_try_model_variantsrY   ‡   s  € ð €FÙØˆˆw‰ÙØ%ˆˆzÑð (ˆU×'Ñ'Ñ>¨eÐ>°vÑ>€FÙØˆð �;‰;‹=˜Ñ€DØ‡z�zƒ|�u—{‘{“}Ò$Ø+�×+Ñ+ÑA°$ÐA¸&ÑAˆÙØˆMó Ø—8‘8˜O¨UÓ3€LÙØ×#Ñ# AÓ&ˆØ�<‰<‹>˜UŸ[™[›]Ò*¨v¯|©|«~ÀÇÁÃÒ/MÔRUÐV\ÓR]ÐabÒRbØ/�U×/Ñ/ÑG°fÐGÀÑGˆFà€Mr#   c                ó  — | j                   rJ| j                  r>| j                  r2t        || j                   | j                  | j                  «      }|r|dfS | j                   re| j                  rY|j	                  | j                   | j                  ¬«      }|r0| j                  r t        | j                  |«      }|r|dfS |dfS |dfS | j                  r4| j                  r(t        |d| j                  | j                  «      }|r|dfS | j                   rp| j                  rdt        || j                   | j                  d«      }|r@| j                  r0|D �cg c]  }t        || j                  «      sŒ|‘Œ }}|r|dfS g dfS |d	fS g dfS c c}w )
uŠ  Attribute-based matching with category validation.

    Category validation: offers whose category/title doesn't match the
    query category are filtered out. This prevents "rozrusznik Iveco" from
    returning "zbiornik paliwa Iveco".

    Confidence tiers:
      0.90 â€” brand + model + category all confirmed
      0.85 â€” brand + category confirmed, model found in results
      0.65 â€” brand + model confirmed, category confirmed via post-filter
      0.55 â€” brand + category (no model to check) or model + category
      0.45 â€” brand + model only (no category in query)
      0.35 â€” brand + category but model absent from results
    gÍÌÌÌÌÌì?)r   r   g333333ë?gffffffÖ?gš™™™™™á?NgÍÌÌÌÌÌä?r   çÍÌÌÌÌÌÜ?)r   r   r   rY   rR   rO   rA   )r   r   r   Úmodel_matchedr0   r1   s         r!   r   r   ¯   s]  € ð& ‡{‚{�u—{’{ u§~¢~Ü$ U¨E¯K©K¸¿¹ÀeÇnÁnÓUˆÙØ˜4�<Ðð ‡{‚{�u—~’~Ø×+Ñ+°%·+±+ÈÏÉÐ+ÓWˆÙØ�{Š{Ü 1°%·+±+¸vÓ F�Ù Ø(¨$Ð.Ð.Ø˜t�|Ð#Ø˜4�<Ðð ‡{‚{�u—~’~Ü$ U¨D°%·+±+¸u¿~¹~ÓNˆÙØ˜4�<Ðð ‡{‚{�u—{’{Ü$ U¨E¯K©K¸¿¹ÀdÓKˆÙØ�~Š~à'-Ö\ !Ô1HÈÈEÏNÉNÕ1[šAÐ\�Ð\ÙØ# T˜>Ð)à˜3�w�Ø˜4�<Ðàˆsˆ7€Nùò ]s   ÅFÅ0Fc                óÀ  ‡— | j                   }|j                  |«      }|sg dfS t        «       }| j                  r0t	        | j                  «      D �ch c]  }|j                  «       ’Œ }}| j                  xs dj                  «       }g }|D ]¿  }t        j                  |j                  «       |j                  j                  «       «      }	|	dk  rŒE|xr+ |j                  xr |j                  j                  «       |k(  }
|j                  j                  «       Š|rt        ˆfd„|D «       «      nd}|
xr |}|j                  ||	|f«       ŒÁ |sg dfS |j                  d„ d¬«       |d	   d
   }|d	   d   }|rd|dz
  dz  dz  z   }t        |d«      }nd|dz
  dz  dz  z   }t        |d«      }|dd D ��cg c]  \  }}}|‘Œ
 c}}t        |d«      fS c c}w c c}}w )z¼FTS5 search + rapidfuzz re-scoring.

    If the query has a known brand and/or category, offers that match those
    attributes are scored higher and the confidence ceiling is raised.
    r   rD   é(   c              3  ó&   •K  — | ]  }|‰v –— Œ
 y ­wr4   r5   )r6   r7   Úoffer_title_lowers     €r!   r9   z_match_fuzzy.<locals>.<genexpr>  s   øè ø€ ÒO¸3˜cÐ%6Ô6ÑOùr:   Fc                ó   — | d   | d   fS )NrC   rQ   r5   )Úxs    r!   ú<lambda>z_match_fuzzy.<locals>.<lambda>  s   € ˜q ™t Q q¡T˜l€ r#   T)ÚkeyÚreverser   rQ   rC   r[   é<   g333333Ã?g333333ã?g333333Ó?Né   )Úraw_textÚsearch_fuzzyr%   r   r   r/   r   r   Útoken_set_ratior>   r=   r)   ÚsortÚminÚround)r   r   Úsearch_textÚfts_resultsÚcat_synonymsÚsÚq_brandÚscoredr,   ÚratioÚoffer_brand_matchÚoffer_cat_matchÚbrand_cat_confirmedÚ
best_ratioÚbest_confirmedr   r0   Ú_r`   s                     @r!   r   r   è   sê  ø€ ð —.‘.€KØ×$Ñ$ [Ó1€KÙØ�3ˆwˆô !›U€LØ‡~‚~Ü+CÀEÇNÁNÓ+SÖT a˜Ÿ™�	ÐTˆÐTà�{‰{Ò ˜b×'Ñ'Ó)€Gð /1€FØò ;ˆÜ×$Ñ$ [×%6Ñ%6Ó%8¸%¿+¹+×:KÑ:KÓ:MÓNˆØ�2Š:Øð $ÒV¨¯©ÒV¸¿¹×8IÑ8IÓ8KÈwÑ8VÐØ!ŸK™K×-Ñ-Ó/ÐÙS_œ#ÓOÀ,ÔOÔOÐejˆØ/ÒC°OÐà�‰�u˜eÐ%8Ð9Õ:ð;ñ Ø�3ˆwˆð ‡K�KÑ*°D€KÔ9Ø˜‘˜1‘€JØ˜A‘Y˜q‘\€Nñ à˜Z¨"™_°Ñ2°TÑ9Ñ9ˆ
Ü˜ TÓ*‰
ð ˜Z¨"™_°Ñ2°TÑ9Ñ9ˆ
Ü˜ TÓ*ˆ
à# B Q˜Z×(‘'�!�Q˜ŠAÓ(¬%°
¸AÓ*>Ð>Ð>ùòK UùóJ )s   ÁGÆ8GN)r   r
   r   r   Úreturnr   )r   z	list[str]r   r   r{   úlist[Offer])r   Ústrr   r
   r   r   r{   r|   )r,   r	   r?   r}   r{   Úbool)rI   r}   r   r|   r{   r|   )
r   r   r   ú
str | Noner   r}   r   r   r{   r|   )r   r
   r   r   r{   ztuple[list[Offer], float])Ú__doc__Ú
__future__r   Ú	rapidfuzzr   Úagent_samochodowy.matcher.indexr   Ú#agent_samochodowy.matcher.normalizer   r   Úagent_samochodowy.modelsr   r	   r
   r"   r   r   rA   rO   rY   r   r   r5   r#   r!   ú<module>r†      s¾   ðÙ 6å "å å 8ß \ß BÑ Bó2Pój	ðØðØ&ðØ/;ðàóó(
7óð2%Øð%Ø *ð%Ø36ð%ØBLð%àó%ðP6Øð6Ø)ð6àó6ðr5?Øð5?Ø)ð5?àô5?r#   