Ë
    §‘ˆjˆ  ã                   óÌ   — d Z ddlZddlmZ ddlmZ ddlmZ ddlm	Z	  ej                  e«      ZdZdZ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fd„Zd	ed
efd„Zded
efd„Zy)a&  Decides which products may be compared with which.

218 products compare in 47,000 pairs and would run fine; 20,000 compare in 400
million and would not. Blocking keeps the job's cost tied to catalog shape rather
than catalog size squared, without making cross-category complements impossible.
é    N)Úsql)ÚRealDictCursor)ÚBLOCKING_FIELDS)Úget_db_connectionÚ__uncategorised__é(   ÚproductÚreturnc                 óÂ   — t        | j                  d«      xs g «      t        t        «      z  }t        | j                  d«      xr | xr | j                  d«      «      S )a0  Only a blocking gap disqualifies a product from being paired.

    Requiring an entirely empty missing_fields also excluded anything without a
    converted reference price, which quietly made currency mandatory on sources
    where it is optional -- a whole catalogue would ingest and pair nothing.
    Úmissing_fieldsÚin_stockÚ
attributes)ÚsetÚgetr   Úbool)r	   Úblockings     ú>/var/www/html/strategist-ai/app/services/pairing/candidates.pyÚis_eligibler      s[   € ô �7—;‘;Ð/Ó0Ò6°BÓ7¼#¼oÓ:NÑN€HÜØ�‰�JÓò 	&ØˆLò	&à�K‰K˜Ó%óð ó    Úproductsc                 óˆ   — i }| D ]:  }|j                  |j                  d«      xs t        g «      j                  |«       Œ< |S )NÚcategory)Ú
setdefaultr   ÚUNCATEGORISEDÚappend)r   Úgroupedr	   s      r   Úblocksr   $   sG   € Ø€GØò YˆØ×Ñ˜7Ÿ;™; zÓ2ÒC´mÀRÓH×OÑOÐPWÕXðYà€Nr   c           	      ó²  ‡‡— t        | «      }g }|j                  «       D ]/  }|D ](  }|D ]!  }|d   |d   k7  sŒ|j                  ||f«       Œ# Œ* Œ1 | D �cg c]  }|j                  d«      sŒ|‘Œ }}| D �cg c]  }|j                  d«      rŒ|‘Œ }}|D �cg c]  }t	        |«      sŒ|‘Œ }	}|D ]”  Š‰j                  d«      xs t
        Št        ˆfd„|D «       t        «      }
t	        ‰«      r;t        |
«      t        k  r)|
t        ˆˆfd„|	D «       t        t        |
«      z
  «      z  }
|j                  ˆfd„|
D «       «       Œ– |S c c}w c c}w c c}w )a©  Every ordered pair worth scoring.

    Within a block: everything, both directions -- similar and upsell only ever
    apply here. Across blocks, two rules produce pairs: an accessory partner
    (a phone and a case), and two colour-carrying non-accessories (a shirt and
    trousers -- the outfit case, which has no accessory to key on). Comparing
    every phone against every grocery item buys nothing and is skipped.
    Úproduct_keyÚis_accessoryr   c              3   óZ   •K  — | ]"  }|j                  d «      xs t        ‰k7  r|–— Œ$ y­w)r   N©r   r   )Ú.0ÚpÚanchor_categorys     €r   ú	<genexpr>z"candidate_pairs.<locals>.<genexpr>G   s0   øè ø€ ò I�1Ø—‘�zÓ"Ò3¤m¸ÒGô ñ Iùs   ƒ(+c              3   óp   •K  — | ]-  }|d    ‰d    k7  r |j                  d«      xs t        ‰k7  r|–— Œ/ y­w)r   r   Nr"   )r#   r$   Úanchorr%   s     €€r   r&   z"candidate_pairs.<locals>.<genexpr>M   sB   øè ø€ ò N�qØ�mÑ$¨¨}Ñ(=Ò=Ø—e‘e˜JÓ'Ò8¬=¸_ÒLô ñ Nùs   ƒ36c              3   ó&   •K  — | ]  }‰|f–— Œ
 y ­w©N© )r#   r$   r(   s     €r   r&   z"candidate_pairs.<locals>.<genexpr>R   s   øè ø€ Ò3 Q�f˜a”[Ñ3ùs   ƒ)
r   Úvaluesr   r   Ú_has_colourr   Ú_takeÚMAX_CROSS_CATEGORY_PARTNERSÚlenÚextend)r   r   ÚpairsÚmembersÚaÚbr$   ÚaccessoriesÚnon_accessoriesÚcolouredÚpartnersr(   r%   s              @@r   Úcandidate_pairsr:   +   sj  ù€ ô �XÓ€GØ€Eà—>‘>Ó#ò )ˆØò 	)ˆAØò )�Ø�]Ñ# q¨Ñ'7Ó7Ø—L‘L ! Q Õ(ñ)ñ	)ð)ð 'Ö@˜¨!¯%©%°Õ*?’1Ð@€KÐ@Ø"*ÖH˜Q°!·%±%¸Õ2G’qÐH€OÐHØ*Ö=�a¬k¸!­n’Ð=€HÐ=à!ò 4ˆØ Ÿ*™* ZÓ0ÒA´Mˆô óI˜ô Iä'ó)ˆô
 �vÔ¤3 x£=Ô3NÒ#NØœôN˜Hô Nô ,¬c°(«mÑ;ó	=ñ =ˆHð 	�‰Ó3¨(Ô3Õ3ð#4ð& €Lùò/ AùÚHùÚ=s$   ÁE
Á-E
Á7EÂEÂEÂ)EÚlimitc                 ó\   — g }| D ]$  }t        |«      |k\  r |S |j                  |«       Œ& |S r*   )r0   r   )Ú
candidatesr;   ÚtakenÚ	candidates       r   r.   r.   W   s?   € Ø€EØò  ˆ	Üˆu‹:˜ÒØà€Lð 	�‰�YÕð ð €Lr   c                 óL   — t        d„ | j                  d«      xs g D «       «      S )Nc              3   ój   K  — | ]+  }|j                  d «      dk(  xr |j                  d«      –— Œ- y­w)ÚkeyÚcolorÚvalueN)r   )r#   r4   s     r   r&   z_has_colour.<locals>.<genexpr>a   s5   è ø€ ò ;Øð �u‰u�U‹|˜wÑ&Ò9¨1¯5©5°«>Ó9ñ ;ùs   ‚13r   )Úanyr   )r	   s    r   r-   r-   `   s*   € Üñ ;Ø Ÿ™ \Ó2Ò8°bô;ó ;ð ;r   Ú	tenant_idc                 ó`  — t        «       }	 |j                  t        ¬«      5 }|j                  t	        j
                  d«      j                  t	        j                  | «      «      «       |j                  «       D �cg c]  }t        |«      ‘Œ }}d d d «       |j                  «        D �cg c]  }t        |«      sŒ|‘Œ }}t        |«      t        |«      k  r*t        j                  d| t        |«      t        |«      «       |S c c}w # 1 sw Y   Œ|xY w# |j                  «        w xY wc c}w )N)Úcursor_factoryzÞSELECT product_key, name, description, category, brand, attributes, price_cents, price_reference_cents, price_tier, is_accessory, rating, in_stock, missing_fields, content_hash, tenant_relations FROM {}.strategist_productszX%s: pairing %d of %d products; the rest are out of stock, flagged, or have no attributes)r   Úcursorr   Úexecuter   ÚSQLÚformatÚ
IdentifierÚfetchallÚdictÚcloser   r0   ÚloggerÚinfo)rF   ÚconnÚcurÚrÚrowsÚeligibles         r   Úload_eligiblerX   e   sò   € ÜÓ€DðØ�[‰[¬ˆ[Ó7ð 	5¸3Ø�K‰KœŸ™ð.ó÷ ‰f”S—^‘^ IÓ.Ó/ô1ð &)§\¡\£^Ö4 ”D˜•GÐ4ˆDÐ4÷	5ð 	�
‰
ŒàÖ2�a¤;¨q¥>’Ð2€HÐ2Ü
ˆ8ƒ}”s˜4“yÒ Ü�‰ð 5àœs 8›}¬c°$«iô	9ð €Oùò 5÷	5ð 	5ûð 	�
‰
�üâ2sA   ŒD ¢AD
Á;DÂD
ÂD Â+D+Â<D+ÄD
Ä
DÄD ÄD()Ú__doc__ÚloggingÚpsycopg2r   Úpsycopg2.extrasr   Úapp.services.catalog.normaliser   Úapp.services.infra.databaser   Ú	getLoggerÚ__name__rQ   r   r/   rO   r   r   Úlistr   r:   Úintr.   r-   ÚstrrX   r+   r   r   ú<module>rd      s«   ðñó å Ý *å :Ý 9à	ˆ×	Ñ	˜8Ó	$€à#€Ø Ð ð˜ð  $ó ð�Tð ˜dó ð)˜dð ) tó )ðX˜Sð  Tó ð;˜ð ; $ó ;ð
˜Sð  Tô r   