Ë
    §‘ˆj  ã                   ó  — d Z ddlZddlmZ ddlmZmZ ddlmZm	Z	 ddl
mZmZ ddlmZmZ ddlmZmZ dd	lmZmZmZmZ  ej2                  e«      Zd
Zde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dfd„Z#de$de"ddfd„Z%dde"defd„Z&y)zæBuilds a tenant's pairing graph.

The job owns strategist_product_neighbors and rewrites it freely. It never writes
to strategist_pairing_decisions -- a merchant who rejects a pair and then
re-syncs must not see that pair return.
é    N)Úsql)ÚJsonÚexecute_values)Úget_db_connectionÚmigrate_pairing_tables)Úcandidate_pairsÚload_eligible)Úis_servableÚload)ÚcosineÚvectors_for)Ú
PAIR_TYPESÚscore_complementÚscore_similarÚscore_upsellé   ÚaÚbÚvectorsÚreturnc                 ó$  — |j                  | d   «      |j                  |d   «      }}|r|rt        ||«      nd}g }dt        | ||«      fdt        | ||«      fdt	        | |«      ffD ]%  \  }}|sŒ	|j                  | d   |d   |dœ|¥«       Œ' |S )NÚproduct_keyg        ÚsimilarÚ
complementÚupsell)Ú
anchor_keyÚneighbor_keyÚ	pair_type)Úgetr   r   r   r   Úappend)	r   r   r   ÚleftÚrightÚ
similarityÚresultsr   Úscoreds	            ú7/var/www/html/strategist-ai/app/services/pairing/job.pyÚ
score_pairr'      s¿   € Ø—+‘+˜a Ñ.Ó/°·±¸Q¸}Ñ=MÓ1Nˆ%€Dñ )-±”˜˜eÔ$¸C€Jà€Gà	”M ! Q¨
Ó3Ð4Ø	Ô'¨¨1¨jÓ9Ð:Ø	”<  1Ó%Ð&ðò ?Ñˆ	�6ò
 Ø�N‰N¨!¨MÑ*:Ø,-¨mÑ,<Ø)2ñ>à6<ð>õ ?ð?ð €Nó    Úproductsc                 óè   — | D �ch c]  }|d   ’Œ	 }}g }| D ]S  }|j                  d«      xs i j                  d«      xs g }|D ]$  }||vrŒ|j                  |d   |dddddgdœ«       Œ& ŒU |S c c}w )	zEA merchant's own declarations outrank every inference in this module.r   Útenant_relationsÚcomplementaryr   g      ð?Úmerchantzdeclared by the merchant)r   r   r   ÚscoreÚ
confidenceÚsourceÚreasons)r   r    )r)   ÚpÚknownÚpairsÚproductÚdeclaredÚneighbors          r&   Úmerchant_pairsr8   ,   s©   € à'/Ö0 !ˆQˆ}ÓÐ0€EÐ0Ø€EØò DˆØ—K‘KÐ 2Ó3Ò9°r×>Ñ>¸ÓOÒUÐSUˆØ ò 	DˆHØ˜uÑ$ØØ�L‰L¨°Ñ(>Ø*2ÀØ#&°cÀZØ&@Ð%AñCõ Dñ	DðDð €Lùò 1s   …A/r4   c                 óð   — i }| D ]+  }|j                  |d   |d   fg «      j                  |«       Œ- g }|j                  «       D ].  }|j                  d„ d¬«       |j	                  |d t
         «       Œ0 |S )Nr   r   c                 ó   — | d   S )Nr.   © )r2   s    r&   ú<lambda>z*_top_per_anchor_and_type.<locals>.<lambda>C   s
   €  1 W¡:€ r(   T)ÚkeyÚreverse)Ú
setdefaultr    ÚvaluesÚsortÚextendÚMAX_NEIGHBORS_PER_TYPE)r4   ÚgroupedÚpairÚkeptÚmemberss        r&   Ú_top_per_anchor_and_typerH   <   sŒ   € Ø€GØò UˆØ×Ñ˜D Ñ.°°[Ñ0AÐBÀBÓG×NÑNÈtÕTðUð €DØ—>‘>Ó#ò 6ˆØ�‰Ñ-°tˆÔ<Ø�‰�GÐ3Ô3Ð4Õ5ð6ð €Kr(   Ú	tenant_idc                 óÀ  — t        «       }	 |j                  «       5 }|j                  t        j                  d«      j                  t        j                  | «      «      «       |rut        |t        j                  d«      j                  t        j                  | «      «      |D �cg c])  }|d   |d   |d   |d   |d   |d   t        |d	   «      f‘Œ+ c}«       d
d
d
«       |j                  «        	 |j                  «        y
c c}w # 1 sw Y   Œ0xY w# t        $ r* |j                  «        t        j                  d| d¬«       ‚ w xY w# |j                  «        w xY w)zÓReplaces this tenant's whole graph in one transaction.

    Delete-then-insert rather than upsert: a pair that no longer scores must
    disappear, and an upsert would leave last run's edges behind forever.
    z+DELETE FROM {}.strategist_product_neighborszINSERT INTO {}.strategist_product_neighbors (anchor_key, neighbor_key, pair_type, score, confidence, source, reasons) VALUES %sr   r   r   r.   r/   r0   r1   Nz(Could not write the pairing graph for %sT©Úexc_info)r   ÚcursorÚexecuter   ÚSQLÚformatÚ
Identifierr   r   ÚcommitÚ	ExceptionÚrollbackÚloggerÚerrorÚclose)rI   r4   ÚconnÚcurr2   s        r&   Úreplace_neighborsrZ   H   s=  € ô Ó€DðØ�[‰[‹]ð 	;˜cØ�K‰KœŸ™Ø=óç‰f”S—^‘^ IÓ.Ó/ô1ñ ô
 ˜s¤C§G¡Gð1ó%÷ ‘&œŸ™¨	Ó2Ó3ð 49ö:à./ð ˜‘o q¨Ñ'8¸!¸K¹.Ø˜‘j ! L¡/°1°X±;Ü˜1˜Y™<Ó(ò*ò :ô;÷	;ð" 	�‰�ð 	�
‰
�ùò:÷	;ð 	;ûô$ ò Ø�‰ŒÜ�‰Ð?ÀØ"ð 	ô 	$àð	ûð 	�
‰
�úsA   ŒD œB	D	Â%.DÃD	ÃD ÄD	Ä	DÄD Ä3EÅE ÅEÚpercentÚstepc                 ó|   — | €y	  | t        |«      |«       y# t        $ r t        j                  dd¬«       Y yw xY w)ztProgress is best-effort. The job is spending real money on model calls;
    a reporting failure must never abort it.NzProgress callback failedTrK   )ÚintrS   rU   Úwarning)Úprogressr[   r\   s      r&   Ú_reportra   k   sC   € ð ÐØðBÙ”�W“˜tÕ$øÜò BÜ�‰Ð1¸DˆÖAðBús   … ˜ ;º;c           	      óÞ  ‡‡— t        | «       t        | «      }t        |dd«       t        |«      dk  rt        |dd«       t        |«      dddi dœS t	        | |«      }t        |dd	«       t        |«      }t        |«      }g }d}t        |d
¬«      D ]J  \  }\  }	}
|j                  t        |	|
|«      «       dd|z  |z  z   }||k7  sŒ6|}t        ||d|› d|› �«       ŒL |st        |dd«       t        |«      }|D �ch c]  }|d   |d   |d   f’Œ }}|D �cg c]  }|d   |d   |d   f|vr|‘Œ c}|z   }t        |«      }t        | |«       t        |dd«       t        | «      Št        ˆfd„|D «       «      }t        D �‡ci c]  Š‰t        ˆfd„|D «       «      “Œ }}t        |«      t        |«      |t        |«      |z
  |dœ}t        j!                  d| |«       |S c c}w c c}w c c}w )Né   zloading eligible productsé   éd   zno eligible pairs to scorer   )r)   r4   ÚservableÚqueuedÚby_typeé-   zembedding productsé   )Ústartu   scoring candidate pairs â€” z of éZ   zscoring candidate pairsr   r   r   zwriting pairing graphc           
   3   ór   •K  — | ].  }t        |‰j                  |d    |d   |d   f«      «      sŒ+d–— Œ0 y­w)r   r   r   rj   N)r
   r   )Ú.0r2   Ú	decisionss     €r&   ú	<genexpr>zpair_tenant.<locals>.<genexpr>¢   sA   øè ø€ ò Q˜¤kØ	ˆ9�=‰=˜!˜L™/¨1¨^Ñ+<¸aÀ¹nÐMÓNõ'P”1ñ Qùs   ƒ,7°7c              3   ó4   •K  — | ]  }|d    ‰k(  sŒd–— Œ y­w)r   rj   Nr;   )rn   r2   Úts     €r&   rp   zpair_tenant.<locals>.<genexpr>¥   s   øè ø€ Ò=˜A¨¨;©¸1Ó)<”aÑ=ùs   ƒ‘zPaired %s: %s)r   r	   ra   Úlenr   r   Ú	enumeraterB   r'   r8   rH   rZ   Úload_decisionsÚsumr   rU   Úinfo)rI   r`   r)   r   Ú
candidatesÚtotal_candidatesr%   Úlast_percentÚir   r   r[   r6   r2   Údeclared_keysrF   rf   rr   rh   Úreportro   s                    `  @r&   Úpair_tenantr~   v   sK  ù€ Ü˜9Ô%Ü˜YÓ'€HÜˆH�aÐ4Ô5Ü
ˆ8ƒ}�qÒÜ�˜#Ð;Ô<Ü ›M°AÀ1Ø¨ñ,ð 	,ô ˜) XÓ.€GÜˆH�bÐ.Ô/ä  Ó*€JÜ˜:“ÐØ€Fð €LÜ˜z°Ô3ò N‰	ˆ‰6ˆAˆqØ�‰”j  A wÓ/Ô0Ø˜˜Q™Ð"2Ñ2Ñ3ˆØ�lÓ"Ø"ˆLÜ�H˜gØ2°1°#°TÐ:JÐ9KÐLõNðNñ Ü�˜"Ð7Ô8ô
 ˜hÓ'€Hà&ö(Øð ˜‘o q¨Ñ'8¸!¸K¹.ÒIð (€Mð (àö $�AØ�L‘/ 1 ^Ñ#4°a¸±nÐEØ"ñ#ò ò $à&.ñ/€Fô $ FÓ+€DÜ�i Ô&ÜˆH�cÐ2Ô3ä˜yÓ)€IÜó Q˜dô Qó Q€Hô HR×RÀ!ˆq”#Ó= Ô=Ó=Ñ=ÐR€GÐRÜ˜h›-´#°d³)Ø"¬c°$«i¸(Ñ.BØ ñ"€Fô ‡K�K� ¨FÔ3Ø€Mùò'(ùò$ùò Ss   Ã=G ÄG%ÆG*)N)'Ú__doc__ÚloggingÚpsycopg2r   Úpsycopg2.extrasr   r   Úapp.services.infra.databaser   r   Úapp.services.pairing.candidatesr   r	   Úapp.services.pairing.decisionsr
   r   ru   Úapp.services.pairing.embeddingsr   r   Úapp.services.pairing.rulesr   r   r   r   Ú	getLoggerÚ__name__rU   rC   ÚdictÚlistr'   r8   rH   ÚstrrZ   r^   ra   r~   r;   r(   r&   ú<module>r�      sØ   ðñó å ß 0ç Qß Jß Nß ?÷ó ð 
ˆ×	Ñ	˜8Ó	$€àÐ ð�$ð ˜4ð ¨$ð °4ó ð&˜Tð  dó ð 	 Dð 	¨Tó 	ð  ð  ¨Tð  °dó  ðFB˜sð B¨#ð B°$ó Bñ4˜3ð 4°$ô 4r(   