Ë
    §‘ˆj}  ã                   ó¬   — d Z ddlZddlmZ ddlmZ ddlmZ  ej                  e	«      Z
dZddh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y)aŒ  Merchant approve/reject decisions on candidate product pairs.

The only writer of strategist_pairing_decisions. The pairing job rewrites its
own table (strategist_product_neighbors) on every run; if decisions lived
there, a merchant who rejected a pair would see it come back after the next
catalogue sync. Keeping decisions in a separate table the job never touches
is what makes a re-run safe.
é    N)Úsql)Úexecute_values)Úget_db_connectiongffffffæ?ÚapprovedÚrejectedÚ	tenant_idÚ	decisionsÚreturnc           
      óø  — |D ]F  }|j                  d«      t        vsŒt        d|j                  d«      ›dt        t        «      › �«      ‚ |D �cg c]$  }|d   |d   |d   |d   |j                  d«      f‘Œ& }}t	        «       }	 |j                  «       5 }t        |t        j                  d«      j                  t        j                  | «      «      |«       |j                  «        t        |«      cd	d	d	«       |j                  «        S c c}w # 1 sw Y   nxY wn6# t        $ r* |j                  «        t         j#                  d
| d¬«       ‚ w xY w	 |j                  «        y	# |j                  «        w xY w)zCUpserts a batch of merchant decisions. Returns the number recorded.ÚdecisionzInvalid decision z; must be one of Ú
anchor_keyÚneighbor_keyÚ	pair_typeÚ
decided_byaš  
                INSERT INTO {}.strategist_pairing_decisions
                    (anchor_key, neighbor_key, pair_type, decision, decided_by)
                VALUES %s
                ON CONFLICT (anchor_key, neighbor_key, pair_type) DO UPDATE
                SET decision = EXCLUDED.decision,
                    decided_by = EXCLUDED.decided_by,
                    decided_at = CURRENT_TIMESTAMP
            Nz)Failed to record pairing decisions for %sT)Úexc_info)ÚgetÚ_VALID_DECISIONSÚ
ValueErrorÚsortedr   Úcursorr   r   ÚSQLÚformatÚ
IdentifierÚcommitÚlenÚcloseÚ	ExceptionÚrollbackÚloggerÚerror)r   r	   ÚentryÚrowsÚconnÚcurs         ú=/var/www/html/strategist-ai/app/services/pairing/decisions.pyÚrecordr&   #   sx  € àò /ˆØ�9‰9�ZÓ Ô(8Ò8ÜØ# E§I¡I¨jÓ$9Ð#<Ð<MÜÔ*Ó+Ð,ð.ó/ð /ð/ð öð ð 
ˆ|Ñ	˜e NÑ3°U¸;Ñ5GØ	ˆzÑ	˜EŸI™I lÓ3ò	5ð€Dð ô Ó€DðØ�[‰[‹]ð 	˜cÜ˜3¤§¡ð )ó !÷ ‘œŸ™ yÓ1Ó2°Dô:ð �K‰KŒMÜ�t“9÷	ð 	ð" 	�
‰
�ùò3÷	ð 	úð 	øô ò Ø�‰ŒÜ�‰Ð@À)ÐVZˆÔ[Øðúð	ð" 	�
‰
�øˆ�
‰
�ús=   Á)DÂD ÂADÃ3	D ÄDÄD ÄE' Ä3EÅE' Å'E9r   c                 óD  — |j                  «       5 }|j                  t        j                  d«      j	                  t        j
                  | «      «      |f«       |j                  «       D �ci c]  }|d   |d   |d   f|d   “Œ c}cddd«       S c c}w # 1 sw Y   yxY w)a  One anchor's decisions, on a caller-supplied connection.

    The serving path runs on every page view and opening a connection costs
    ~200ms here, so it cannot afford its own; nor can it afford to read every
    decision the tenant has ever made to answer about one product.
    zoSELECT anchor_key, neighbor_key, pair_type, decision FROM {}.strategist_pairing_decisions WHERE anchor_key = %sr   é   é   é   N)r   Úexecuter   r   r   r   Úfetchall)r   r   r#   r$   Úrows        r%   Úload_for_anchorr.   G   s�   € ð 
�‰‹ð L˜#Ø�‰”C—G‘GðIó
÷ ‰&”—‘ 	Ó*Ó
+¨j¨]ô	<ð =@¿L¹L»NÖK°S��Q‘˜˜Q™  Q¡Ð(¨#¨a©&Ñ0ÒK÷Lñ Lùò
 L÷Lð Lús   ‘ABÁ,BÂBÂBÂBc                 óÄ  — t        «       }	 |j                  «       5 }|j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  «       D �ci c]  }|d   |d   |d   f|d   “Œ c}cddd«       |j                  «        S c c}w # 1 sw Y   nxY w	 |j                  «        y# |j                  «        w xY w)z×All of a tenant's decisions, keyed (anchor_key, neighbor_key, pair_type).

    A pairing run needs the full set to decide servability for every
    candidate it produces, so one query beats one lookup per pair.
    zYSELECT anchor_key, neighbor_key, pair_type, decision FROM {}.strategist_pairing_decisionsr   r(   r)   r*   N)	r   r   r+   r   r   r   r   r,   r   )r   r#   r$   r-   s       r%   Úloadr0   V   sÌ   € ô Ó€DðØ�[‰[‹]ð 	P˜cØ�K‰KœŸ™ð7ó÷ ‰f”S—^‘^ IÓ.Ó/ô1ð ADÇÁÃÖO¸�S˜‘V˜S ™V S¨¡VÐ,¨c°!©fÑ4ÒO÷	Pð 	Pð 	�
‰
�ùò P÷	Pð 	Púð 	Pð 	�
‰
�øˆ�
‰
�ús5   ŒC œAB/Á5B*ÂB/Â	C Â*B/Â/B8Â4C ÃCÚpairc                 ó|   — |dk(  ry|dk(  ry| j                  d«      dk(  ry| j                  d«      xs dt        k\  S )	uÊ   The Â§5 servability rule: a merchant's decision always wins; absent one,
    a merchant-declared pair is trusted outright and everything else needs
    enough confidence to skip the approval queue.
    r   Fr   TÚsourceÚmerchantÚ
confidenceg        )r   ÚAPPROVAL_THRESHOLD)r1   r   s     r%   Úis_servabler7   h   sH   € ð
 �:ÒØØ�:ÒØà‡x�x�Ó˜ZÒ'ØØ�H‰H�\Ó"Ò) cÔ.@Ñ@Ð@ó    )Ú__doc__ÚloggingÚpsycopg2r   Úpsycopg2.extrasr   Úapp.services.infra.databaser   Ú	getLoggerÚ__name__r   r6   r   ÚstrÚlistÚintr&   Údictr.   r0   Úboolr7   © r8   r%   ú<module>rF      sŸ   ðñó å Ý *å 9à	ˆ×	Ñ	˜8Ó	$€ð Ð à 
Ð+Ð ð!�cð ! dð !¨só !ðHL˜sð L°ð L¸dó Lð�Cð ˜Dó ð$A�dð A¨ô Ar8   