Ë
    qýuj*  ã                   óº   — d Z ddlZddlmZmZ ddlmZmZmZm	Z	 ddl
mZ  ej                  e«      Zh d£ZdZdd	d
œZdZdedefd„Zdedefd„Zdedededefd„Zy)a¼  Deciding whether a visitor's activity is worth a suggestion.

Deterministic rules and a database lookup, deliberately. This runs on every page
view of every visitor, so it must answer in tens of milliseconds; a model call
would take seconds and would spend the tenant's ai_tokens quota -- the same
budget the chatbot itself needs -- on visitors who never open the widget.

Nothing reachable from `decide()` calls a model. The `search` branch matches on
text (`match_products`, a plain ILIKE lookup) rather than on meaning -- it is
deliberately not `search_products`, which embeds the query and spends
ai_tokens quota. That embedding search is fine inside the chat tool, where a
model call is already in the loop and the visitor is waiting on a reply
anyway; it is not fine here, where the caller is an unauthenticated widget
firing on every page view. Do not swap this back to `search_products` to
"improve" match quality -- that would reintroduce a model call, and therefore
cost and latency, into a path that must stay a cheap lookup.

There is no cooldown in this version. Every qualifying event returns a
recommendation, which means a visitor moving through several pages is suggested
something on each one.
é    N)Ú	parse_qslÚurlparse)Úfind_by_urlÚmatch_productsÚrelated_productsÚto_card)Úget_tool_settings>   ÚdwellÚsearchÚ	page_viewÚadd_to_cartÚcart_abandonÚcheckout_starté   z*Still deciding? You might also like these.z Here's what matched your search.)r
   r   )ÚqÚsÚqueryr   ÚkeywordÚkÚurlÚreturnc                 óØ   — 	 t        t        t        | «      j                  d¬«      «      }t
        D ](  }|j                  |«      sŒ||   j                  «       c S  y# t        $ r Y yw xY w)z=The search term a search results page carries in its own URL.F)Úkeep_blank_valuesÚ )Údictr   r   r   Ú	ExceptionÚ_SEARCH_PARAMSÚgetÚstrip)r   ÚparamsÚnames      ú;/var/www/html/strategist-ai/app/services/recommendations.pyÚsearch_query_from_urlr#   /   sk   € ðÜ”i¤¨£× 3Ñ 3ÀuÔMÓNˆô ò (ˆØ�:‰:�dÕØ˜$‘<×%Ñ%Ó'Ò'ð(ð øô ò Ùðús   ‚)A Á	A)Á(A)Úreasonc                 ó   — d| dœS )NF)Ú	recommendr$   © )r$   s    r"   Ú_nothingr(   ;   s   € Ø¨&Ñ1Ð1ó    Ú	tenant_idÚeventÚpagec                 óJ  — 	 t        | «      d   }|j                  dd«      st        d«      S t        | |j                  d«      xs d«      }|st        d«      S |dk(  r'|j                  d	«      xs d
t        k  rt        d«      S |dk7  rt        d«      S |dk(  r:t        |j                  d«      xs d«      }|st        d«      S t        | |d¬«      }nt        | |d¬«      }|D �cg c]'  }|j                  d«      |j                  d«      k7  r|‘Œ) c}dd }|st        d«      S dt        j                  |t        d   «      |D �cg c]  }t        |«      ‘Œ c}dœS c c}w c c}w # t        $ r2}	t        j                  d| › d|	› �d¬«       t        d«      cY d}	~	S d}	~	ww xY w)z4Whether to suggest anything, and what. Never raises.ÚfeaturesÚproduct_recommendationTÚfeature_offr   r   Úpage_unknownr
   Údwell_secondsr   Úno_rule_matchedr   é   )ÚlimitÚproduct_keyNé   Úno_match)r&   ÚmessageÚproductsz#Recommendation decision failed for z: )Úexc_info)r	   r   r(   r   ÚDWELL_THRESHOLD_SECONDSr#   r   r   ÚMESSAGESr   r   ÚloggerÚerror)
r*   r+   r,   r.   Úcurrentr   Ú
candidatesÚrÚothersÚexs
             r"   ÚdeciderE   ?   s©  € ð'$Ü$ YÓ/°
Ñ;ˆØ�|‰|Ð4°dÔ;Ü˜MÓ*Ð*ä˜i¨¯©°%«Ò)>¸BÓ?ˆÙÜ˜NÓ+Ð+à�GÒØ—‘˜Ó)Ò.¨QÔ2IÒIÜÐ 1Ó2Ð2Ø�hÒô Ð-Ó.Ð.à�HÒÜ)¨$¯(©(°5«/Ò*?¸RÓ@ˆEÙÜÐ 1Ó2Ð2Ü'¨	°5ÀÔB‰Jô *¨)°WÀAÔFˆJð (ö I˜Ø—U‘U˜=Ó)¨W¯[©[¸Ó-GÒGò ò IØIKÈ!ðMˆáÜ˜JÓ'Ð'à!Ü#Ÿ<™<¨¬x¸Ñ/@ÓAØ17Ö8¨AœW Q�ZÒ8ñ:ð 	:ùòIùò 9øÜò $ä�‰Ð:¸9¸+ÀRÈÀtÐLÐW[ˆÔ\Ü˜
Ó#Õ#ûð$ús^   ‚*E' ­+E' Á+E' ÂE' Â/E' Ã!E' Ã&,EÄE' Ä%!E' ÅE"ÅE' Å
E' Å'	F"Å0'FÆF"ÆF")Ú__doc__ÚloggingÚurllib.parser   r   Úapp.services.productsr   r   r   r   Úapp.services.tools_settingsr	   Ú	getLoggerÚ__name__r>   ÚVALID_EVENTSr<   r=   r   Ústrr#   r   r(   rE   r'   r)   r"   ú<module>rO      sš   ðñó* ß ,ç XÓ XÝ 9à	ˆ×	Ñ	˜8Ó	$€òA€ð Ð ð :Ø0ñ€ð ?€ð	˜sð 	 só 	ð2�Sð 2˜Tó 2ð)$�cð )$ #ð )$¨Tð )$°dô )$r)   