Ë
    §‘ˆj#%  ã                   óˆ  — d Z ddlZ ej                  e«      ZdZdZdZdZdZ	dZ
d	Zd
ZdZh d£Z eddh«       eddh«       eddh«       eddh«      hZdedefd„Zdededefd„Zdededefd„Zdedefd„Zdedefd„Zdededefd„Zdededefd„Zdededefd„Zdedefd „Zd!ed"edefd#„Z dededefd$„Z!dedefd%„Z"y)&aƒ  The three pairing rules.

Deterministic and dependency-free on purpose: these rules are the part of pairing
worth testing, and a rule that needs a database or a model to exercise stops
being tested honestly.

score and confidence stay separate throughout. A pair can be a strong
relationship derived by a weak method; the approval queue gates on the
derivation, not on the relationship.
é    N)ÚsimilarÚ
complementÚupsellgš™™™™™á?g      @g)\�Âõ(Ì?)NÚunisexÚkidsÚaccessory_forgffffffÖ?gffffffò?ç333333Ó?>   ÚgrayÚgreyÚnavyÚbeigeÚblackÚcreamÚwhiteÚorangeÚredÚpinkÚbrownr   ÚproductÚreturnc           	      ó  — | j                  d«      xs g D �ch c]]  }|j                  d«      rJ|j                  d«      �9|j                  d«      t        |j                  d«      «      j                  «       f’Œ_ c}S c c}w ©NÚ
attributesÚkeyÚvalue©ÚgetÚstrÚlower©r   Úas     ú9/var/www/html/strategist-ai/app/services/pairing/rules.pyÚ_pairsr#   '   sk   € à—k‘k ,Ó/Ò5°2ö<ØØ�u‰u�UŒ| §¡ g£Ð :ð �U‰U�5‹\œ3˜qŸu™u W›~Ó.×4Ñ4Ó6Ò7ò <ð <ùò <s   ˜A"A=r!   Úbc                 ór   — t        | «      t        |«      }}|r|syt        ||z  «      t        ||z  «      z  S )Nç        )r#   Úlen©r!   r$   ÚleftÚrights       r"   Úattribute_jaccardr+   -   s8   € Ü˜“)œV A›Yˆ%€DÙ‘uØÜˆt�e‰|Óœs 4¨%¡<Ó0Ñ0Ð0ó    c                 ór   — | j                  d«      d uxr# | j                  d«      |j                  d«      k(  S )NÚcategory)r   )r!   r$   s     r"   Ú_same_categoryr/   4   s2   € Ø�5‰5�Ó DÐ(ÒS¨Q¯U©U°:Ó->À!Ç%Á%È
ÓBSÑ-SÐSr,   c                 ó‚   — | j                  d«      |j                  d«      }}|r|sy t        ||«      t        ||«      z  S )NÚprice_cents)r   ÚmaxÚmin)r!   r$   ÚlowÚhighs       r"   Ú_price_ratior6   8   s<   € Ø—‘�mÓ$ a§e¡e¨MÓ&:ˆ€CÙ‘dØÜˆs�D‹>œC  T›NÑ*Ð*r,   r   c                 óÀ   — | j                  d«      xs g D ]F  }|j                  d«      |k(  sŒ|j                  d«      €Œ*t        |d   «      j                  «       c S  y r   r   )r   r   r!   s      r"   Ú_attrr8   ?   sX   € Ø�[‰[˜Ó&Ò,¨"ò +ˆØ�5‰5�‹<˜3Ó 1§5¡5¨£>Ñ#=Ü�q˜‘z“?×(Ñ(Ó*Ò*ð+ð r,   c                 ó–   — t        | d«      t        |d«      }}|r|sy||k(  s|t        v r	|t        v ryt        ||h«      t        v rdS dS )aL  1.0 matching, 0.7 unremarkable, 0.5 unknown, 0.0 clashing.

    0.5 rather than 0.0 for unknown: only 52 of 218 live products carry a colour,
    and treating absent data as a clash would suppress most valid pairs. Unknown
    sits below unremarkable so a pair we can actually see is preferred to one we
    are guessing about.
    Úcolorç      à?ç      ð?r&   çffffffæ?)r8   ÚNEUTRAL_COLORSÚ	frozensetÚCLASHINGr(   s       r"   Ú_colour_compatibilityrA   F   sX   € ô ˜˜7Ó#¤U¨1¨gÓ%6ˆ%€DÙ‘uØð ˆu‚}˜¤Ñ/°E¼^Ñ4KØÜ˜T 5˜MÓ*¬hÑ6ˆ3Ð?¸CÐ?r,   Úcosinec                 ó  — t        | |«      r	|t        k  ry t        | |«      }|�
|t        kD  ry t	        | |«      }d|z  d|z  z   }d| j                  d«      › d�|d›d�|d›d�g}t        |d	«      t        |d	«      d
|dœS )Nr=   r	   úsame category (r.   ú)ú.2fú text similarityz attribute overlapé   Ú	embedding©ÚscoreÚ
confidenceÚsourceÚreasons)r/   ÚSIMILAR_MIN_COSINEr6   ÚSIMILAR_MAX_PRICE_RATIOr+   r   Úround)r!   r$   rB   ÚratioÚoverlaprK   rN   s          r"   Úscore_similarrT   Y   s¬   € Ü˜!˜QÔ 6Ô,>Ò#>ØÜ˜˜AÓ€EØÐ˜UÔ%<Ò<Øä  1Ó%€GØ�&‰L˜3 ™=Ñ(€EØ  §¡ zÓ!2Ð 3°1Ð5Ø˜�Ð-Ð.Ø˜#�Ð0Ð1ð3€Gô ˜5 !“_´E¸&À!Ó4DØ!¨gñ7ð 7r,   c                 ó^   — t        | d«      t        |d«      }}|t        v s|t        v ry||k7  S )zñA men's shirt must not suggest a women's handbag.

    Only a definite disagreement blocks: unisex, kids and unknown all stay
    compatible, so nothing legitimate is lost. Phase 1 has extracted gender all
    along and nothing read it.
    ÚgenderF)r8   ÚGENDER_NEUTRALr(   s       r"   Ú_genders_clashrX   i   s7   € ô ˜˜8Ó$¤e¨A¨xÓ&8ˆ%€DØŒ~Ñ ¬.Ñ!8ØØ�5‰=Ðr,   c                 óÂ   — | j                  d«      xs g D �cg c]<  }|j                  d«      t        k(  r"|j                  d«      r|j                  d«      ‘Œ> c}S c c}w r   )r   ÚACCESSORY_FOR_KEYr    s     r"   Ú_accessory_targetsr[   v   sV   € Ø%,§[¡[°Ó%>Ò%DÀ"ö E˜qØ�u‰u�U‹|Ô0Ò0°Q·U±U¸7´^ð �E‰E�'�Nò Eð Eùò Es   ˜AAÚ	candidateÚanchorc                 ó\  — t        | «      }|sydj                  d„ |j                  d«      |j                  d«      fD «       «      }|D ]a  }t        |«      j	                  «       j                  «       j                  d«      }|sŒ<||v s!|j                  d«      j                  |«      sŒa y y)	aà  Is the candidate an accessory FOR this anchor, not merely an accessory?

    Returns "declared" when the accessory names this anchor's type, "unknown"
    when it names nothing, and "no" when it names something else.

    is_accessory is a boolean, so without this a basketball rim and a pair of
    earbuds both "complement" a shirt. Matching is loose on purpose: the model
    writes "phones" where the category is "smartphones", and "shirts" where it
    is "mens-shirts".
    ÚunknownÚ c              3   óR   K  — | ]  }|rt        |«      j                  «       –— Œ! y ­w)N)r   r   )Ú.0Úvs     r"   ú	<genexpr>z _accessorises.<locals>.<genexpr>Œ   s&   è ø€ ò S¨1ÙPQô ˜A›Ÿ™Ÿñ Sùs   ‚%'r.   Úproduct_typeÚsÚdeclaredÚno)r[   Újoinr   r   r   ÚstripÚrstripÚendswith)r\   r]   ÚtargetsÚhaystackÚtargetÚwords         r"   Ú_accessorisesrq   {   s¨   € ô ! Ó+€GÙð à�x‰xñ SØŸ™ JÓ/°·±¸NÓ1KÐLôSó S€Hàò ˆÜ�6‹{× Ñ Ó"×(Ñ(Ó*×1Ñ1°#Ó6ˆÚ�T˜XÑ%¨¯©¸Ó)=×)FÑ)FÀtÕ)LÙðð r,   c           	      óÆ  — t        | |«      ry| j                  d«      ryt        | |«      ryt        | |«      }|j                  d«      rit	        || «      }|dk(  ry|dk(  r
|t
        k  ry|t
        k  rydd|z  z   d|z  z   }d}|j                  d	«      › d
�d| j                  d	«      › �|d›d�g}nM|dk  s	|t        k  rydd|z  z   d|z  z   }d}d| j                  d	«      › d|j                  d	«      › d�d|d›d�g}|dk  rCt        | d«      r7t        |d«      r+|j                  dt        | d«      › dt        |d«      › �«       t        t        |d«      d«      |d|dœS )zFDirectional: a suggests b. Never the reverse unless scored separately.NÚis_accessoryrh   r_   r;   gš™™™™™¹?gš™™™™™Ù?g      è?r.   z is an accessoryzdifferent category from rF   rG   r=   r	   gš™™™™™É?zdifferent categories (z / rE   zcompatible attributesr<   r:   zcolour z with rH   Ú	attributerJ   )r/   r   rX   rA   rq   ÚACCESSORY_COMPLEMENT_MIN_COSINEÚWEAK_COMPLEMENT_MIN_COSINEr8   ÚappendrQ   r3   )r!   r$   rB   ÚcompatibilityÚ	targetingrK   rL   rN   s           r"   Úscore_complementrz   •   s­  € ä�a˜ÔØØ‡u�uˆ^ÔØÜ�a˜ÔØä)¨!¨QÓ/€MØ‡u�uˆ^ÔÜ! ! QÓ'ˆ	Ø˜ÒØð
 ˜	Ò! fÔ/NÒ&NØð Ô3Ò3ØØ�c˜MÑ)Ñ)¨C°&©LÑ8ˆØˆ
Ø—e‘e˜JÓ'Ð(Ð(8Ð9Ø-¨a¯e©e°JÓ.?Ð-@ÐAØ˜S�\Ð!1Ð2ð4‰ð ˜3Ò &Ô+EÒ"EØØ�c˜MÑ)Ñ)¨C°&©LÑ8ˆØˆ
Ø+¨A¯E©E°*Ó,=Ð+>¸cÀ!Ç%Á%È
ÓBSÐATÐTUÐVØ*Ø˜S�\Ð!1Ð2ð4ˆð �sÒœu Q¨Ô0´U¸1¸gÔ5FØ�‰˜¤ q¨'Ó!2Ð 3°6¼%ÀÀ7Ó:KÐ9LÐMÔNÜœ3˜u c›?¨AÓ.¸jØ!¨gñ7ð 7r,   c                 ó¨  — t        | |«      sy| j                  d«      |j                  d«      }}|r|r||z  t        k  ry| j                  d«      |j                  d«      }}|�|�||t        z
  k  ryt	        ||z  dz
  dz  d«      }||z  d›d�d	| j                  d
«      › d�g}|�|j                  d|› �«       t        dd|z  z   d«      dd|dœS )zEDirectional: b is the trade-up from a. Arithmetic, never a judgement.Nr1   Úratingé   é   r<   z.1fzx the pricerD   r.   rE   zrated r;   rH   gffffffî?Ú
arithmeticrJ   )r/   r   ÚUPSELL_MIN_PRICE_RATIOÚUPSELL_MAX_RATING_DROPr3   rw   rQ   )r!   r$   r4   r5   Úa_ratingÚb_ratingÚliftrN   s           r"   Úscore_upsellr…   Ê   s÷   € ä˜!˜QÔØØ—‘�mÓ$ a§e¡e¨MÓ&:ˆ€CÙ‘d˜d S™jÔ+AÒAØàŸ™˜x›¨!¯%©%°«/ˆh€HØÐ Ð 4Ø�hÔ!7Ñ7Ò7Øä��s‘
˜Q‘ !Ñ# SÓ)€DØ˜‘*˜SÐ! Ð-°ÀÇÁÀzÓARÐ@SÐSTÐ/UÐV€GØÐØ�‰˜ ˜zÐ*Ô+ô ˜3  t¡Ñ+¨QÓ/¸tØ"¨wñ8ð 8r,   )#Ú__doc__ÚloggingÚ	getLoggerÚ__name__ÚloggerÚ
PAIR_TYPESrO   rP   ru   rW   rZ   rv   r€   r�   r>   r?   r@   ÚdictÚsetr#   Úfloatr+   Úboolr/   r6   r   r8   rA   rT   rX   Úlistr[   rq   rz   r…   © r,   r"   ú<module>r’      s¹  ðñ	ó à	ˆ×	Ñ	˜8Ó	$€ð 1€
àÐ ØÐ Ø"&Ð Ø)€Ø#Ð Ø!Ð ØÐ ØÐ ò N€Ù�x Ð'Ó(©)°X¸vÐ4FÓ*GÙ�u˜f�oÓ&©	°7¸GÐ2DÓ(EðG€ð<�Dð <˜Só <ð1˜ð 1 $ð 1¨5ó 1ðT�dð T˜tð T¨ó Tð+�Dð +˜Tó +ð�4ð ˜có ð@˜Tð @ dð @¨uó @ð&7�Tð 7˜dð 7¨Eó 7ð 
�dð 
˜tð 
¨ó 
ðE ð E¨ó Eð
˜Tð ¨4ð °Có ð427˜ð 27 ð 27¨uó 27ðj8�Dð 8˜Tô 8r,   