Ë
    qýujã!  ã            	       óN  — d Z ddlZddlZddlmZ ddlmZmZmZ  ej                  e
«      ZdZdZdddddddddœZd	d
dd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fd„Zd$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"edefd#„Zy)%a“  Per-tenant feature toggles, and the email copy that goes with one of them.

The settings dashboard shows a card per capability with an on/off switch. This
module is the single source of truth for those switches: which capabilities a
tenant has turned on, and -- for emailing -- the header, footer and disclaimer
text they authored.

Storage is one row per key in the tenant's strategist_settings table, updated in
place. Settings used to live in strategist_summaries, which is append-only and
had no locking: two admins toggling different switches at the same time each
read the old state and the second silently overwrote the first. Writes now
happen under a row lock, so the second save merges on top of the first.

Reads fall back to the old summaries rows (first 'tool_settings', then the even
older flat 'email_settings') for tenants the migration has not reached, so
nothing breaks before or during it. See scripts/migrate_settings.py.

Only some switches have a tool behind them today (see FEATURE_TOOLS). The rest
are stored so the dashboard works now; wiring one up later means adding a line
to FEATURE_TOOLS and marking its prompt section with <<<TOOL:name>>>.
é    N)Úpartial)Úget_latest_summaryÚget_settingÚupdate_settingÚtool_settingsÚemail_settingsT)Ú
send_emailÚ
voice_modeÚbook_meetingÚorder_trackingÚattach_documentsÚticket_managementÚcrm_integrationÚproduct_recommendationr	   Úcreate_or_update_ticketÚsend_documentÚrecommend_products)r	   r   r   r   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/tools_settings.pyÚnormalise_tenant_idr    C   s9   € ð ’˜b×'Ñ'Ó)€IÙ˜×-Ñ-¨fÔ5Ø�i�[Ð!Ð!ØÐó    c                  ó@   — t        t        «      t        t        «      dœS )N)ÚfeaturesÚemail)ÚdictÚFEATURE_DEFAULTSÚEMAIL_COPY_DEFAULTS© r!   r   Ú	_defaultsr)   P   s   € ÜÔ-Ó.¼Ô>QÓ9RÑSÐSr!   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Ú
isinstancer%   )r   ÚkeyÚrowÚstoredÚexÚsaveds         r   Úread_legacy_blobr;   T   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.   r/   r0   r5   r%   r;   )r   r8   r9   s      r   Ú_read_storedr=   o   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   |<   Œ 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.r#   r$   Úenabledr	   )r)   ÚgetÚitemsr&   Úboolr'   )r:   ÚsettingsÚnameÚvalues       r   Úapply_over_defaultsrF   {   sö   € ä‹{€HÙØˆð Ÿ	™	 *Ó-Ò3°×:Ñ:Ó<ò 5‰ˆˆeØÔ#Ò#Ü)-¨e«ˆH�ZÑ  Ò&ð5ð Ÿ	™	 'Ó*Ò0¨b×7Ñ7Ó9ò ,‰ˆˆeØÔ&Ò&ª5Ø&+ˆH�WÑ˜dÒ#ð,ð �EÑÜ-1°%¸	Ñ2BÓ-Cˆ�Ñ˜\Ñ*Ü#ò 2ˆØ�9‰9�T�?Ø&+¨D¡kˆH�WÑ˜dÒ#ð2ð €Or!   c                 ó<   — t        t        t        | «      «      «      S )zEEvery switch plus the email copy, merged over defaults. Never raises.)rF   r=   r    r   s    r   Úget_tool_settingsrH   ”   s   € äœ|Ô,?À	Ó,JÓKÓLÐLr!   r#   r$   c                 ó  — t        |xs t        | «      «      }|xs i j                  «       D ]  \  }}t        |«      |d   |<   Œ |xs i j                  «       D ]*  \  }}|xs dj	                  «       xs	 t
        |   |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.
    r#   r   r$   )rF   r;   rA   rB   r   r'   )r   r#   r$   ÚcurrentÚbaserD   rE   s          r   Ú_merge_settingsrL   ™   s›   € ô ˜wÒEÔ*:¸9Ó*EÓF€DØ š B×-Ñ-Ó/ò -‰ˆˆeÜ!% e£ˆˆZÑ˜Òð-àš ×*Ñ*Ó,ò Q‰ˆˆeà$š{¨×1Ñ1Ó3ÒPÔ7JÈ4Ñ7PˆˆW‰�dÒðQð €Kr!   c           
      óþ  — 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        |«      «      › �«      ‚t        | t        t        t        | ||«      «      }t        d„ |d   j                  «       D «       «      }t        j                  d| › d|xs d› �«       |S )	zMMerge the supplied switches and copy into the tenant's settings, and persist.zUnknown feature(s): z, zUnknown email setting(s): c              3   ó,   K  — | ]  \  }}|rŒ	|–— Œ y ­w)Nr(   )Ú.0ÚnÚons      r   ú	<genexpr>z%save_tool_settings.<locals>.<genexpr>¹   s   è ø€ ÒE‘u�q˜"Â"”ÑEùs   ‚
�r#   zSaved tool settings for z; off: Únone)r    Úsetr&   Ú
ValueErrorÚjoinÚsortedr'   r   r,   r   rL   rA   r/   Úinfo)r   r#   r$   ÚunknownÚmergedÚoffs         r   Úsave_tool_settingsr\   «   sæ   € ä# IÓ.€Iä�(’.˜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äØ”<Ü” ¨H°eÓ<ó>€Fô ÑE  zÑ 2× 8Ñ 8Ó :ÔEÓ
E€CÜ
‡K�KÐ*¨9¨+°W¸Sº]ÀF¸OÐLÔMØ€Mr!   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.
    r#   T)rH   ÚFEATURE_TOOLSrA   r@   )r   r#   ÚfeatureÚtools       r   Údisabled_tools_forra   ¾   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.r?   r#   r	   r$   )rH   )r   rC   s     r   Úget_email_settingsrc   Ì   s+   € ä  Ó+€HØ�x 
Ñ+¨LÑ9ÐO¸XÀgÑ=NÐOÐOr!   Ú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)rd   re   rf   s      r   Úrender_placeholdersri   Ò   s+   € àŠJ�B×Ñ °Ó<×DÑDØ˜<ó)ð )r!   )NN)Ú__doc__r1   ÚloggingÚ	functoolsr   Úapp.services.databaser   r   r   Ú	getLoggerÚ__name__r/   r,   r-   r&   r^   r'   Ústrr    r%   r)   r;   r=   rF   rH   rL   r\   rT   ra   rc   ri   r(   r!   r   ú<module>rq      sp  ðñó* Û Ý ç QÑ Qà	ˆ×	Ñ	˜8Ó	$€à€Ø&Ð ð ØØØØØØØ"ñ	Ð ð Ø2Ø'Ø2ñ	€ð 7ØFð	Dñ	Ð ð
 3ð 
¨3ó 
ðT�4ó Tð ó ð6	'˜Có 	'ð $ó ð2M ð M¨ó Mð
˜sð ¨dð ¸4ð ÈTó ñ$ #ð °ð ÀTð ÐUYó ð&` #ð `¨#ó `ðP #ð P¨$ó Pð)˜cð )¨sð )À#ð )È#ô )r!   