Ë
    ÿ�jÖ:  ã                   óâ  — d Z ddlZddlZddlmZ ddlmZmZ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
d
d
d
d
d
d
d
ddœ	ZddddddœZddddddœZddddddœZddiZdZddlZ ej8                  d«      Zddd d!œZd"ed#efd$„Z d#e!fd%„Z"d"efd&„Z#d"efd'„Z$d#e!fd(„Z%d"ed#e!fd)„Z&d"ed*e!d+e!d,e!d-e'd#e!fd.„Z(	 	 d6d"ed*e!d+e!d,e!d-e'd#e!fd/„Z)d"ed#e*fd0„Z+d"ed#e!fd1„Z,d2ed3ed4ed#efd5„Z-y)7aà  Per-tenant feature toggles, and the email copy that goes with one of them.

Storage is one row per key in the tenant's strategist_settings table, under a
row lock so two concurrent saves merge instead of one silently overwriting
the other. Reads fall back to the older strategist_summaries rows for
tenants the migration (scripts/migrate_settings.py) hasn't reached yet.

Only some switches have a tool behind them (see FEATURE_TOOLS); the rest are
stored so the dashboard works now. Provider settings (PROVIDER_OPTIONS/
PROVIDER_DEFAULTS) pick one option instead of on/off, under a "providers"
key -- chat_conversation and send_email are wired into real behavior;
ticket_management and product_recommendation are stored only, for now.
é    N)Úpartial)Úget_latest_summaryÚget_settingÚupdate_setting)Úget_google_calendar_connection)Úget_mailchimp_connection)Úget_teams_connectionÚtool_settingsÚemail_settingsTF)	Ú
send_emailÚ
voice_modeÚbook_meetingÚorder_trackingÚattach_documentsÚticket_managementÚcrm_integrationÚproduct_recommendationÚchat_conversationr   Úcreate_or_update_ticketÚsend_documentÚrecommend_productsr   )r   r   r   r   r   )ÚwhatsappÚslackÚgalaxiq_chat)Úgalaxiq_ticketmanagement)ÚsmtpÚ	mailchimp)Úshopify)Úgoogle_calendarÚteams)r   r   r   r   r   r   r   r   )zGeneral enquiriesÚSalesÚBusinessz^[^@\s]+@[^@\s]+\.[a-zA-Z]{2,}$z&Thanks for chatting with {{brandName}}u6   Â© {{currentYear}} {{brandName}}. All rights reserved.zzResponses are powered by AI and may contain errors or omissions. Please verify important information before relying on it.)ÚheaderÚfooterÚaiDisclaimerÚ	tenant_idÚreturnc                 ó^   — | xs dj                  «       } | r| j                  d«      sd| › �S | S )zýAccept a tenant id with or without the org_ prefix.

    Schemas are named org_<uuid>. Callers sometimes pass the bare uuid, and
    without this a bootstrap would silently create a second, empty schema whose
    settings the chat would never read.
    Ú Úorg_)ÚstripÚ
startswith©r&   s    ú?/var/www/html/strategist-ai/app/services/chat/tools_settings.pyÚnormalise_tenant_idr/   m   s9   € ð ’˜b×'Ñ'Ó)€IÙ˜×-Ñ-¨fÔ5Ø�i�[Ð!Ð!ØÐó    c                  ó^   — t        t        «      t        t        «      t        t        «      g dœS )N)ÚfeaturesÚemailÚ	providersÚticket_emails)ÚdictÚFEATURE_DEFAULTSÚEMAIL_COPY_DEFAULTSÚPROVIDER_DEFAULTS© r0   r.   Ú	_defaultsr;   z   s*   € ÜÔ-Ó.¼Ô>QÓ9RÜÔ/Ó0À2ñGð Gr0   c           
      óŒ  — t         t        fD ]E  }	 t        | |«      }|r|d   nd}|sŒ	 t        j                  |«      }t        |t        «      sŒC|c S  y# t        $ r)}t        j                  d|› d| › d|› �«       Y d}~ yd}~ww xY w# t        j                  t        f$ r t        j                  d|› d| › d�«       Y Œµw xY w)a  The tenant's settings as they were stored in strategist_summaries.

    Used as a read fallback for tenants the migration has not reached, and by
    the migration itself as its source. Prefers the newer 'tool_settings' row
    over the older flat 'email_settings' one.
    r   NzCould not read z for ú: zStored z  is not valid JSON; ignoring it.)ÚSETTINGS_KEYÚLEGACY_SETTINGS_KEYr   Ú	ExceptionÚloggerÚerrorÚjsonÚloadsÚJSONDecodeErrorÚ	TypeErrorÚ
isinstancer6   )r&   ÚkeyÚrowÚstoredÚexÚsaveds         r.   Úread_legacy_blobrM      sÖ   € ô Ô1Ð2ò ˆð	ä$ Y°Ó4ˆCÙ"�S˜’V¨ˆFñ Øð	Ü—J‘J˜vÓ&ˆEô �eœTÕ"ØŠLð!ð" øô ò 	Ü�L‰L˜?¨3¨%¨u°Y°K¸rÀ"ÀÐFÔGÝûð	ûô ×$Ñ$¤iÐ0ò 	Ü�L‰L˜7 3 % u¨Y¨KÐ7WÐXÔYÙð	ús(   ‘AªBÁ	BÁBÂBÂ5CÃCc                 óÈ   — 	 t        | t        «      }t        |t        «      r|r|S t        | «      S # t        $ r'}t        j	                  d| › d|› �«       d}Y d}~ŒJd}~ww xY w)zIThe tenant's saved settings: the settings table first, then the old rows.zCould not read settings for r=   N)r   r>   r@   rA   rB   rG   r6   rM   )r&   rJ   rK   s      r.   Ú_read_storedrO   š   sb   € ðÜ˜Y¬Ó5ˆô �&œ$Ô¡FØˆÜ˜IÓ&Ð&øô ò Ü�‰Ð3°I°;¸bÀÀÐEÔFØ�ûðús   ‚1 ±	A!ºAÁA!c                 óŠ  — t        «       }| s|S | j                  d«      xs i j                  «       D ]  \  }}|t        v sŒt	        |«      |d   |<   Œ! | j                  d«      xs i j                  «       D ]  \  }}|t
        v sŒ|sŒ||d   |<   Œ | j                  d«      xs i j                  «       D ]$  \  }}|t        v sŒ|�|t        |   v sŒ||d   |<   Œ& | j                  d«      }t        |t        «      r||d<   d| v rt	        | d   «      |d   d<   t
        D ]  }| j                  |«      sŒ| |   |d   |<   Œ! |S )zDNormalise a stored blob of either shape into the full settings dict.r2   r3   r4   r5   Úenabledr   )	r;   ÚgetÚitemsr7   Úboolr8   ÚPROVIDER_OPTIONSrG   Úlist)rL   ÚsettingsÚnameÚvalueÚstored_ticket_emailss        r.   Úapply_over_defaultsr[   ¦   st  € ä‹{€HÙØˆð Ÿ	™	 *Ó-Ò3°×:Ñ:Ó<ò 5‰ˆˆeØÔ#Ò#Ü)-¨e«ˆH�ZÑ  Ò&ð5ð Ÿ	™	 'Ó*Ò0¨b×7Ñ7Ó9ò ,‰ˆˆeØÔ&Ò&ª5Ø&+ˆH�WÑ˜dÒ#ð,ð Ÿ	™	 +Ó.Ò4°"×;Ñ;Ó=ò 0‰ˆˆeð Ô#Ò#¨¨¸%ÔCSÐTXÑCYÒ:YØ*/ˆH�[Ñ! $Ò'ð0ð !Ÿ9™9 _Ó5ÐÜÐ&¬Ô-Ø$8ˆ�Ñ!ð �EÑÜ-1°%¸	Ñ2BÓ-Cˆ�Ñ˜\Ñ*Ü#ò 2ˆØ�9‰9�T�?Ø&+¨D¡kˆH�WÑ˜dÒ#ð2ð €Or0   c                 ó<   — t        t        t        | «      «      «      S )zEEvery switch plus the email copy, merged over defaults. Never raises.)r[   rO   r/   r-   s    r.   Úget_tool_settingsr]   É   s   € äœ|Ô,?À	Ó,JÓKÓLÐLr0   r2   r3   r4   r5   c                 óh  — t        |xs t        | «      «      }|xs i j                  «       D ]  \  }}t        |«      |d   |<   Œ |xs i j                  «       D ]*  \  }}|xs dj	                  «       xs	 t
        |   |d   |<   Œ, |xs i j                  «       D ]  \  }}||d   |<   Œ |�||d<   |S )a‘  Apply an update on top of whatever is already stored.

    Runs inside update_setting's row lock, so `current` is the value nobody else
    can be writing at the same moment. An empty `current` means this tenant has
    not been migrated yet, so the old summaries blob is the starting point rather
    than bare defaults -- otherwise the first save after deployment would wipe
    their settings.
    r2   r)   r3   r4   r5   )r[   rM   rS   rT   r+   r8   )	r&   r2   r3   r4   r5   ÚcurrentÚbaserX   rY   s	            r.   Ú_merge_settingsra   Î   sÞ   € ô ˜wÒEÔ*:¸9Ó*EÓF€DØ š B×-Ñ-Ó/ò -‰ˆˆeÜ!% e£ˆˆZÑ˜Òð-àš ×*Ñ*Ó,ò Q‰ˆˆeà$š{¨×1Ñ1Ó3ÒPÔ7JÈ4Ñ7PˆˆW‰�dÒðQð "š R×.Ñ.Ó0ò (‰ˆˆeð #(ˆˆ[Ñ˜$Òð(ð Ð Ø -ˆˆ_ÑØ€Kr0   c                 ó:  — t        | «      } |rG|j                  «       D ��ci c],  \  }}|t        |t        «      r|j	                  «       xs dn|“Œ. }}}t        |xs i «      t        t        «      z
  }|r&t        ddj                  t        |«      «      › �«      ‚t        |xs i «      t        t        «      z
  }|r&t        ddj                  t        |«      «      › �«      ‚t        |xs i «      t        t        «      z
  }|r&t        ddj                  t        |«      «      › �«      ‚|xs i j                  «       D ]-  \  }}|€Œ	|t        |   vsŒt        |› dt        |   › d|›�«      ‚ |� |D ]›  }t        |t        «      rd|vsd	|vrt        d
«      ‚t        j                  |j                  d«      xs dj	                  «       «      st        |j                  d«      ›d�«      ‚|d	   t         vsŒ…t        dt         › d|d	   ›�«      ‚ |xs i j                  «       D ��cg c]  \  }}|�
|t"        v r|‘Œ }	}}|	r^t        t%        | «      d   «      }
|
j'                  |xs i «       |	D ]-  }t"        |   }|
j                  |«      rŒt        |› d|› d�«      ‚ |xs i j                  d«      dk(  rt)        | «      st        d«      ‚|xs i j                  d«      dk(  rt+        | «      st        d«      ‚|xs i j                  d«      dk(  rt-        | «      st        d«      ‚t/        | t0        t3        t4        | ||||«      «      }t        d„ |d   j                  «       D «       «      }t6        j9                  d| › d|xs d› �«       |S c c}}w c c}}w )zUMerge the supplied switches, copy, provider choices and business emails, and persist.NzUnknown feature(s): z, zUnknown email setting(s): zUnknown provider setting(s): z must be one of z or null, got r3   Úcategoryz7Each ticket_emails entry needs both email and category.r)   z is not a valid email address.zcategory must be one of z, got r2   z cannot be set while z/ is off; enable it first or in the same requestr   r   zPConnect Mailchimp under Integrations before selecting it as your email provider.r   r   zXConnect Google Calendar under Integrations before selecting it as your meeting provider.r    zXConnect Microsoft Teams under Integrations before selecting it as your meeting provider.c              3   ó,   K  — | ]  \  }}|rŒ	|–— Œ y ­w)Nr:   )Ú.0ÚnÚons      r.   ú	<genexpr>z%save_tool_settings.<locals>.<genexpr>4  s   è ø€ ÒE‘u�q˜"Â"”ÑEùs   ‚
�zSaved tool settings for z; off: Únone)r/   rS   rG   Ústrr+   Úsetr7   Ú
ValueErrorÚjoinÚsortedr8   rU   r6   Ú_TICKET_EMAIL_REÚmatchrR   ÚTICKET_CATEGORIESÚPROVIDER_REQUIRES_FEATUREr]   Úupdater   r   r	   r   r>   r   ra   rA   Úinfo)r&   r2   r3   r4   r5   rX   rY   ÚunknownÚentryÚneeding_gateÚeffective_featuresÚgateÚmergedÚoffs                 r.   Úsave_tool_settingsr|   é   sí  € ô $ IÓ.€Iñ à'0§¡Ó'8÷:Ù#˜˜eð ´jÀÌÔ6L˜EŸK™K›MÒ1©TÐRWÑWð :ˆ	ñ :ô �(’.˜bÓ!¤CÔ(8Ó$9Ñ9€GÙÜÐ/°·	±	¼&À»/Ó0JÐ/KÐLÓMÐMÜ�%’+˜2Ó¤Ô%8Ó!9Ñ9€GÙÜÐ5°d·i±iÄÀwÃÓ6PÐ5QÐRÓSÐSÜ�)’/˜rÓ"¤SÔ)9Ó%:Ñ:€GÙÜÐ8¸¿¹Ä6È'Ã?Ó9SÐ8TÐUÓVÐVØ!š R×.Ñ.Ó0ò Z‰ˆˆeØÑ Ô.>¸tÑ.DÒ!DÜØ�&Ð(Ô)9¸$Ñ)?Ð(@ÀÈuÈiÐXóZð ZðZð
 Ð Ø"ò 	_ˆEÜ˜e¤TÔ*¨g¸UÑ.BÀjÐX]ÑF]Ü ØMóOð Oä#×)Ñ)¨5¯9©9°WÓ+=Ò+CÀ×*JÑ*JÓ*LÔMÜ  E§I¡I¨gÓ$6Ð#9Ð9WÐ!XÓYÐYØ�ZÑ Ô(9Ò9Ü Ø.Ô/@Ð.AÀÈÈjÑHYÐG\Ð]ó_ð _ð	_ð .7ª_¸"×,CÑ,CÓ,E÷ P™[˜T 5ØÐ(¨TÔ5NÑ-Nò ð P€Lñ PáÜ!Ô"3°IÓ">¸zÑ"JÓKÐØ×!Ñ! (¢.¨bÔ1Ø ò 	/ˆDÜ,¨TÑ2ˆDØ%×)Ñ)¨$Õ/Ü Ø�fÐ1°$°ð 8-ð .ó/ð /ð	/ð 	Š�R×Ñ˜\Ó*¨kÒ9Ü'¨	Ô2Üð"ó#ð #ð 	Š�R×Ñ˜^Ó,Ð0AÒAÜ-¨iÔ8Üð,ó-ð -ð 	Š�R×Ñ˜^Ó,°Ò7Ü# IÔ.Üð,ó-ð -ô Ø”<Ü” ¨H°e¸YÈÓVóX€Fô ÑE  zÑ 2× 8Ñ 8Ó :ÔEÓ
E€CÜ
‡K�KÐ*¨9¨+°W¸Sº]ÀF¸OÐLÔMØ€MùóK:ùó>Ps   ¡1NÈ'Nc                 óž   — t        | «      d   }t        j                  «       D ��ch c]  \  }}|j                  |d«      rŒ|’Œ c}}S c c}}w )zÏTool names this tenant has switched off.

    The chat loop strips these from the tool list AND from the system prompt, so
    a disabled capability is invisible to the model rather than merely refused.
    r2   T)r]   ÚFEATURE_TOOLSrS   rR   )r&   r2   ÚfeatureÚtools       r.   Údisabled_tools_forr�   9  sC   € ô ! Ó+¨JÑ7€HÜ&3×&9Ñ&9Ó&;×_‘]�W˜dÀ8Ç<Á<ÐPWÐY]ÕC^ŠDÓ_Ð_ùÓ_s   ¦A	ÁA	c                 ó6   — t        | «      }d|d   d   i|d   ¥S )zG{'enabled', 'header', 'footer', 'aiDisclaimer'} for the email pipeline.rQ   r2   r   r3   )r]   )r&   rW   s     r.   Úget_email_settingsrƒ   G  s+   € ä  Ó+€HØ�x 
Ñ+¨LÑ9ÐO¸XÀgÑ=NÐOÐOr0   ÚtextÚ
brand_nameÚcurrent_yearc                 óN   — | xs dj                  d|«      j                  d|«      S )zMFill the {{brandName}} / {{currentYear}} placeholders the settings UI offers.r)   z{{brandName}}z{{currentYear}})Úreplace)r„   r…   r†   s      r.   Úrender_placeholdersr‰   M  s+   € àŠJ�B×Ñ °Ó<×DÑDØ˜<ó)ð )r0   )NNNN).Ú__doc__rC   ÚloggingÚ	functoolsr   Úapp.services.infra.databaser   r   r   Ú)app.services.integrations.google_calendarr   Ú#app.services.integrations.mailchimpr   Úapp.services.integrations.teamsr	   Ú	getLoggerÚ__name__rA   r>   r?   r7   r~   rU   r9   rr   rq   ÚreÚ_reÚcompilero   r8   rj   r/   r6   r;   rM   rO   r[   r]   rV   ra   r|   rk   r�   rƒ   r‰   r:   r0   r.   ú<module>r–      s  ðñó Û Ý ç WÑ WÝ TÝ HÝ @à	ˆ×	Ñ	˜8Ó	$€à€Ø&Ð ð ØØØØØØØ"Øñ
Ð ð  Ø2Ø'Ø2Ø"ñ€ð ?Ø6Ø'Ø*Ø0ñÐ ð Ø3ØØ"ØñÐ ð Ð,ðÐ ð ?Ð ó Ø�3—;‘;ÐAÓBÐ ð 7ØFð	Dñ	Ð ð
 3ð 
¨3ó 
ðG�4ó Gð
 ó ð6	'˜Có 	'ð  $ó  ðFM ð M¨ó Mð
˜sð ¨dð ¸4ð ÈDð Ø#'ðØ59óð6 MQØEIñM #ð M°ð MÀTð MØ"&ðMØ>BðMØNRóMð`` #ð `¨#ó `ðP #ð P¨$ó Pð)˜cð )¨sð )À#ð )È#ô )r0   