Ë
    ÿ�jsÀ  ã                   óf  — d dl mZmZmZmZmZmZmZmZm	Z	 d dl
mZ d dlmZmZ d dlmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZ d dl m!Z! d dl"m#Z#m$Z$m%Z%m&Z& d dl'm(Z( d dl)m*Z* d d	l+m,Z,m-Z- d d
l.m/Z/m0Z0 d dl1m2Z2m3Z3m4Z4m5Z5m6Z6m7Z7m8Z8m9Z9 d dl:m;Z;m<Z<m=Z=m>Z>m?Z?m@Z@mAZA d dlBmCZC d dlDmEZE d dlFmGZGmHZH d dlImJZJmKZK d dlLmMZMmNZN d dlOmPZP d dlQmRZRmSZS d dlTmUZU d dlVZVd dlWZWd dlXZXd dlYZYd dlZZZd dl[Z[d dl\Z\d dl]m]Z]m^Z^m_Z_  eYjÀ                  ea«      Zb e«       ZcecjÉ                  d«      deefd„«       ZfecjÉ                  d«       ed«       ed«       ed«       ed«      fdeedeee   deee   dee   fd„«       ZgecjÉ                  d «       ed«       ed«       ed«      fdeed!eedeee   fd"„«       ZhecjÉ                  d#«       ed«       ed«       ed«      fdeed$eedeee   fd%„«       ZiecjÕ                  d&«       edd'¬(«      fdeed)eed*eefd+„«       ZkecjÙ                  d,«      deed-eefd.„«       ZmecjÙ                  d«      deefd/„«       ZnecjÙ                  d0«      deefd1„«       ZoecjÙ                  d «      deefd2„«       ZpecjÉ                  d3«       ed«       ed«       ed«       ed«      fdeedeee   deee   dee   fd4„«       ZqecjÕ                  d3«      deed)eefd5„«       ZrecjÙ                  d3«      d}deedeee   fd6„«       ZsecjÉ                  d7«       ed«       ed«       ed«      fdeed)eedeefd8„«       ZtecjÙ                  d9«      	 	 d~deed)eed:eefd;„«       ZuecjÙ                  d<«      deefd=„«       Zvd>ewfd?„ZxecjÉ                  d<«      d@efdA„«       ZyecjÉ                  dB«       ed«      fdCezfdD„«       Z{ecjÉ                  dE«       ed«       ed«       edF«       ed«       ed«       edG«       ed«      fdeedHeedIeedeedJeedKeedLeee   fdM„«       Z|ecjÙ                  dN«       ed«      fdeedeefdO„«       Z}ecjÙ                  dP«      dF ed«      fdeedIeedeefdQ„«       Z~ecjÙ                  dR«      deefdS„«       ZecjÉ                  dT«       ed«       edU«       ed«      fdVeedeedWeedXeefdY„«       Z€ecjÙ                  dT«       ed«      fdVeedeefdZ„«       Z�ecjÉ                  d[«       ed«       ed«      fdVeedeed\eefd]„«       Z‚ecjÙ                  d^«       ed«       ed_«      fdeed`eƒfda„«       Z„ecjÙ                  db«      deefdc„«       Z…ecjÙ                  dd«       ededf¬(«       eddg¬(«       eddh¬(«      fdeedieedjeedkeefdl„«       Z†ecjÉ                  dm«       ed«       ed«       ed«       ed«      fdeedIeed\e‡deefdn„«       ZˆdoZ‰dpezdeed>e‡fdq„ZŠddse‹dte‡fdu„ZŒecjÉ                  dv«       ed«       ed«       edr«      dfdeedweee   dxe‡dye	fdz„«       Z�ecjÉ                  d{«       ed«       edr«       edr«      dfdeedte‡dxe‡dye	fd|„«       ZŽy)€é    )	Ú	APIRouterÚBodyÚ
UploadFileÚFileÚFormÚHTTPExceptionÚQueryÚRequestÚResponse)ÚRedirectResponse)ÚListÚOptional)Úbootstrap_tenantÚinsert_vector_dataÚinsert_summaryÚget_latest_summaryÚget_ticketsÚget_ticket_status_countsÚdelete_vector_data_by_sourceÚget_llm_usageÚget_knowledge_sourcesÚadd_ticket_commentÚget_ticket_commentsÚinsert_crawled_urlÚupdate_ticket_statusÚ
get_ticketÚ!delete_vector_data_by_url_subpathÚget_source_ids_by_name)Ústorage_service)Ú
chunk_textÚget_embeddingsÚgenerate_summaryÚgenerate_title)Úextract_text_from_url)Úget_chat_response)Úget_all_threadsÚget_chat_history)Úget_kpisÚget_general_kpis)ÚDEFAULT_CATEGORYÚblob_urlÚdelete_documentÚdownload_linkÚfind_documentÚingest_documentsÚlist_documentsÚset_category)ÚEMAIL_COPY_DEFAULTSÚFEATURE_DEFAULTSÚFEATURE_TOOLSÚPROVIDER_OPTIONSÚget_tool_settingsÚnormalise_tenant_idÚsave_tool_settings)ÚQuotaExceededError©Úenrich_tenant)ÚVALID_EVENTSÚdecide)Úhas_any_productsÚsync_source)Ú
KIND_CRAWLÚget_sources)Újobs)Ú_runÚ	start_job)ÚsettingsN)ÚdatetimeÚ	timedeltaÚtimezonez
/bootstrapÚ	tenant_idc           	   ƒ   ój  K  — t         j                  d| › �«       	 t        | «       dd| › d�dœS # t        $ r>}t         j	                  d| › d|› �d¬	«       t        d
ddt        |«      › �dœ¬«      ‚d }~wt        $ r2}t         j	                  d| › d|› �d¬	«       t        ddddœ¬«      ‚d }~ww xY w­w)Nz'Received bootstrap request for tenant: ÚsuccesszTenant z setup complete.©ÚstatusÚmessagez)Validation error in bootstrap for tenant ú: T©Úexc_infoé�  ÚerrorzValidation failed: ©Ústatus_codeÚdetailz'Error in bootstrap endpoint for tenant éô  z*Internal server error during tenant setup.)ÚloggerÚinfor   Ú
ValueErrorrS   r   ÚstrÚ	Exception)rI   ÚveÚes      ú0/var/www/html/strategist-ai/app/api/endpoints.pyÚ	bootstrapr`   ,   sÔ   è ø€ ä
‡K�KÐ9¸)¸ÐEÔFðBÜ˜Ô#Ø#°'¸)¸ÐDTÐ0UÑVÐVøÜò uÜ�‰Ð@ÀÀÈ2ÈbÈTÐRÐ]aˆÔbÜ¨¸wÐUhÔilÐmoÓipÐhqÐSrÑ4sÔtÐtûÜò BÜ�‰Ð>¸y¸kÈÈAÈ3ÐOÐZ^ˆÔ_Ü¨¸wÐSñ  5Aô  Bð  	BûðBüs0   ‚B3œ0 ¯B3°	B0¹9A2Á2B0Á>-B+Â+B0Â0B3z/ingest/files.Úuser_idÚcategoryÚfilesc           	   ƒ   ó  K  — t         j                  d| › dt        |«      › �«       	 t        | «       |D �cg c]&  }|j                  |j                  «       ƒ d {  –—† f‘Œ( }}t        | |||¬«      }|d   }t        j                  «       }ddt        |«      › d| › d�||d	   |j                  «       j                  «       |j                  «       j                  d
«      |j                  «       dz   dœS 7 Œ˜c c}w # t        $ r ‚ t        $ r>}	t         j                  d| › d|	› �d¬«       t!        dddt#        |	«      › �dœ¬«      ‚d }	~	wt$        $ r2}
t         j                  d| › d|
› �d¬«       t!        ddddœ¬«      ‚d }
~
ww xY w­w)Nz*Received ingest/files request for tenant: z	, count: ©rb   ra   ÚsummaryrK   z	Ingested z files into ú.Ú	documentsú%H:%M:%SÚZ)rM   rN   rf   rh   Úingested_dateÚingested_timeÚ
created_atz'Validation/Extraction error for tenant rO   TrP   rR   rS   zFile ingestion failed: rL   rT   z,Unexpected error in ingest/files for tenant rW   z(An error occurred during file ingestion.)rX   rY   Úlenr   ÚfilenameÚreadr/   rF   ÚutcnowÚdateÚ	isoformatÚtimeÚstrftimer9   rZ   rS   r   r[   r\   )rI   ra   rb   rc   ÚfÚuploadsÚresultrf   Únowr]   r^   s              r_   Úingest_filesrz   9   sŠ  è ø€ ô ‡K�KÐ<¸Y¸KÀyÔQTÐUZÓQ[ÐP\Ð]Ô^ð@Ü˜Ô#à9>Ö?°A�A—J‘J a§f¡f£h§Ò/Ð?ˆÐ?Ü! )¨W¸xÐQXÔYˆØ˜Ñ#ˆä�o‰oÓˆàØ"¤3 u£: ,¨l¸9¸+ÀQÐGØØ Ñ,Ø ŸX™X›Z×1Ñ1Ó3Ø ŸX™X›Z×0Ñ0°Ó<ØŸ-™-›/¨CÑ/ñ
ð 	
ð !/úÒ?øô ò ØÜò yÜ�‰Ð>¸y¸kÈÈBÈ4ÐPÐ[_ˆÔ`Ü¨¸wÐUlÔmpÐqsÓmtÐluÐSvÑ4wÔxÐxûÜò @Ü�‰ÐCÀIÀ;ÈbÐQRÐPSÐTÐ_cˆÔdÜ¨¸wÐS}Ñ4~ÔÐûð@üsX   ‚%F¨C: ·#C5ÁC3ÁC5Á"BC: Ã2FÃ3C5Ã5C: Ã:FÄ9EÅFÅ-E>Å>FÆFz/ingest/textÚtext_contentc           	   ƒ   óä  K  — t         j                  d| › �«       |j                  «       st        dd¬«      ‚	 t	        | «       t        t        j                  «       «      }t        j                  d|t        j                  t        j                  z  «      }|rY|j                  d«      j                  «       }t        j                  dd|«      j                  «       j                  d	d
«      }|sd}nt        || |¬«      }|› d�}t!        | |d«      }|D ],  }t         j                  d|› d|› d| › �«       t#        | |«       Œ. t%        |«      }	||ddœ}
|r||
d<   t         j                  dt'        |	«      › d|› d�«       |	D ]  }t)        || |¬«      }t+        | |||
«       Œ  t-        || |¬«      }t/        | d|«       t1        j2                  «       }dd| › d�||j5                  «       j7                  «       |j9                  «       j;                  d«      |j7                  «       dz   dœS # t<        $ r ‚ t>        $ r2}t         jA                  d| › d|› �d ¬!«       t        d"d#d$d%œ¬«      ‚d }~ww xY w­w)&Nz)Received ingest/text request for tenant: rR   ztext_content cannot be empty.rT   z!^Title:\s*(.+?)\s*Content:\s*(.*)é   z[^\w\s-]Ú ú Ú_ÚDocument©ra   z.txtÚtextzReplacing existing text z (sid: z) in tenant )ÚsourceÚ	source_idÚingestion_typera   zCreated z chunks for text input 'ú'©rI   ra   rK   zIngested text content into rg   ri   rj   )rM   rN   rf   rk   rl   rm   z Error in ingest/text for tenant rO   TrP   rW   rS   z(An error occurred during text ingestion.rL   )!rX   rY   Ústripr   r   r[   ÚuuidÚuuid4ÚreÚsearchÚ
IGNORECASEÚDOTALLÚgroupÚsubÚreplacer#   r   r   r    rn   r!   r   r"   r   rF   rq   rr   rs   rt   ru   r9   r\   rS   )rI   r{   ra   r…   Útitle_matchÚdisplay_titleÚunique_source_nameÚold_source_idsÚsidÚchunksÚmetadataÚchunkÚ	embeddingrf   ry   r^   s                   r_   Úingest_textrœ   [   sr  è ø€ ô ‡K�KÐ;¸I¸;ÐGÔHØ×ÑÔÜ¨Ð4SÔTÐTð1@Ü˜Ô#ÜœŸ
™
›Ó%ˆ	ô —i‘iÐ DÀlÔTV×TaÑTaÔdf×dmÑdmÑTmÓnˆÙØ'×-Ñ-¨aÓ0×6Ñ6Ó8ˆMäŸF™F ;°°MÓB×HÑHÓJ×RÑRÐSVÐX[Ó\ˆMÙ Ø *‘ä*¨<¸ÈGÔTˆMà -˜¨dÐ3Ðô 0°	Ð;MÈvÓVˆØ!ò 	9ˆCÜ�K‰KÐ2Ð3EÐ2FÀgÈcÈUÐR^Ð_hÐ^iÐjÔkÜ(¨°CÕ8ð	9ô ˜LÓ)ˆØ0¸yÐ\bÑcˆÙØ")ˆH�YÑÜ�‰�hœs 6›{˜mÐ+CÐDVÐCWÐWXÐYÔZØò 	FˆEÜ& u¸	È7ÔSˆIÜ˜y¨%°¸HÕEð	Fô
 # <¸9ÈgÔVˆÜ�y &¨'Ô2ä�o‰oÓˆàØ4°Y°K¸qÐAØØ ŸX™X›Z×1Ñ1Ó3Ø ŸX™X›Z×0Ñ0°Ó<ØŸ-™-›/¨CÑ/ñ
ð 	
øô ò ØÜò @Ü�‰Ð7¸	°{À"ÀQÀCÐHÐSWˆÔXÜ¨¸wÐS}Ñ4~ÔÐûð@üs)   ‚6I0¹G/H) È(I0È)I-È;-I(É(I-É-I0z/ingest/websiteÚurlc              ƒ   ó  K  — t         j                  d| › d|› �«       	 t        j                  ddd¬«      4 ƒd {  –—† }|j	                  |«      ƒ d {  –—† }|j
                  dk\  rQ|j
                  dk7  rBt         j                  d	|› d
|j
                  › �«       t        dd|j
                  › d|› �¬«      ‚d d d «      ƒd {  –—†  	 t        | «       t         j                  d|› d| › �«       t        | |«       t        |dt        j                  | ¬«      ƒ d {  –—† }|d   }|j                  «       s(t         j                  d|› �«       t        dd|› �¬«      ‚t!        t#        j$                  «       «      }||d|d   |d   |d   dœ}	|r||	d<   |d   D ]  }
t'        | ||
«       Œ d}|j)                  dg «      D ]m  }|d   }|d    }t+        |«      }|D ]Q  }d!|› d"|› �}|	j-                  «       }||d#<   ||d$<   d|v r|d   |d%<   t/        || |¬&«      }t1        | |||«       |d'z  }ŒS Œo t         j                  d(|› d)|› d*t3        |d   «      › d+|› �«       t5        || |¬&«      }t7        | d|«       t9        j:                  «       }|j=                  «       j?                  «       |	d,<   |jA                  «       jC                  d-«      |	d.<   |j?                  «       d/z   |	d0<   d1d2|› d3t3        |d   «      › d4| › d5�||	|	d,   |	d.   |	d0   d6œS 7 �ŒÒ7 �Œ¼7 �ŒO# 1 ƒd {  –—†7  sw Y   �Œ`xY w# t        j                  $ r0}t         j                  d|› d|› �«       t        dd|› �¬«      ‚d }~wt        $ r ‚ w xY w7 �Œ_# tD        $ r ‚ tF        $ r>}t         j                  d7| › d|› �d¬8«       t        dd9d:t!        |«      › �d;œ¬«      ‚d }~wtH        $ r2}t         j                  d<| › d|› �d¬8«       t        d=d9d>d;œ¬«      ‚d }~ww xY w­w)?Nz,Received ingest/website request for tenant: z, URL: g      @FT)ÚtimeoutÚverifyÚfollow_redirectsrR   i•  zURL Validation failed for z with status code z+Website validation failed: Received status z from rT   zURL Validation error for rO   z0Website validation failed: Could not connect to zApplying replacement logic for z in tenant é   )Ú	max_depthÚ	max_pagesrI   rƒ   z#No content could be extracted from z5Failed to extract any content from the provided URL: ÚwebsiteÚurls_visitedÚcolorsÚimages)r„   r…   r†   r¦   r§   Ú
top_imagesra   r   Úpagesr�   z	[Source: z]
Ú
source_urlr„   r©   rˆ   r}   zCreated total of z& chunks for website crawl starting at z	 (across z pages) with source_id rk   ri   rl   rj   rm   rK   zIngested website content from z
 (visited z pages) into rg   )rM   rN   rf   r™   rk   rl   rm   z/Website ingestion validation failed for tenant rP   rS   zWebsite ingestion failed: rL   z.Unexpected website ingestion error for tenant rW   z8An unexpected error occurred while crawling the website.)%rX   rY   ÚhttpxÚAsyncClientÚheadrU   Úwarningr   ÚRequestErrorrS   r   r   r$   rE   ÚCRAWL_MAX_PAGESr‰   r[   rŠ   r‹   r   Úgetr    Úcopyr!   r   rn   r"   r   rF   rq   rr   rs   rt   ru   r9   rZ   r\   )rI   r�   ra   ÚclientÚrespr^   Úscrape_resultÚextracted_textr…   Úmetadata_baseÚvisited_urlÚtotal_chunksÚpageÚ	page_textÚpage_urlÚpage_chunksrš   Úreferenced_chunkÚchunk_metadatar›   rf   ry   r]   s                          r_   Úingest_websiterÁ   ˜   s  è ø€ ô ‡K�KÐ>¸y¸kÈÐQTÐPUÐVÔWðÜ×$Ñ$¨S¸ÐQUÔV÷ 	Ið 	IÐZ`ØŸ™ SÓ)×)ˆDà×Ñ 3Ò&¨4×+;Ñ+;¸sÒ+Bä—‘Ð!;¸C¸5Ð@RÐSW×ScÑScÐRdÐeÔfÜ#°Ð>iÐjn×jzÑjzÐi{ð  |Bð  CFð  BGð  =Hô  Ið  I÷	I÷ 	Ið\PÜ˜Ô#ô 	�‰Ð5°c°U¸+ÀiÀ[ÐQÔRÜ)¨)°SÔ9ä3°CÀ2ÔQY×QiÑQiÐu~Ô×ˆà& vÑ.ˆØ×#Ñ#Ô%Ü�L‰LÐ>¸s¸eÐDÔEÜ¨CÐ:oÐpsÐotÐ8uÔvÐväœŸ
™
›Ó%ˆ	àØ"Ø'Ø)¨.Ñ9Ø# HÑ-Ø'¨Ñ1ñ
ˆñ Ø'.ˆM˜)Ñ$ð )¨Ñ8ò 	BˆKÜ˜y¨)°[ÕAð	Bð ˆØ!×%Ñ% g¨rÓ2ò 	"ˆDØ˜V™ˆIØ˜E‘{ˆHô % YÓ/ˆKà$ò "�à%.¨x¨j¸¸E¸7Ð#CÐ ð "/×!3Ñ!3Ó!5�Ø/7�˜|Ñ,Ø+3�˜xÑ(ð ˜tÑ#Ø37¸±>�N <Ñ0ä*Ð+;ÀyÐZaÔb�	Ü" 9Ð.>À	È>ÔZØ Ñ!‘ñ"ð	"ô0 	�‰Ð'¨ ~Ð5[Ð\_Ð[`Ð`iÔjmÐn{ð  }Dñ  oEó  kFð  jGð  G^ð  _hð  ^ið  jô  	kô # >¸YÐPWÔXˆÜ�y )¨WÔ5ô �o‰oÓˆØ),¯©«×)=Ñ)=Ó)?ˆ�oÑ&Ø),¯©«×)<Ñ)<¸ZÓ)Hˆ�oÑ&Ø&)§m¡m£o¸Ñ&;ˆ�lÑ#ð  Ø7¸°u¸JÄsÈ=ÐYgÑKhÓGiÐFjÐjwð  yBð  xCð  CDð  EØØ%Ø*¨?Ñ;Ø*¨?Ñ;Ø'¨Ñ5ñ
ð 	
ðs	IùØ)ùð	Iù÷ 	I÷ 	Iò 	Iûô ×Ñò nÜ�‰Ð0°°°R¸°sÐ;Ô<Ü¨Ð6fÐgjÐfkÐ4lÔmÐmûÜò Øðúð @úô\ ò ØÜò |Ü�‰ÐFÀyÀkÐQSÐTVÐSWÐXÐcgˆÔhÜ¨¸wÐUoÔpsÐtvÓpwÐoxÐSyÑ4zÔ{Ð{ûÜò PÜ�‰ÐEÀiÀ[ÐPRÐSTÐRUÐVÐaeˆÔfÜ¨¸wð  TNñ  5Oô  Pð  	PûðPüs¾   ‚P
ŸL, »L¼L, ¿LÁLÁA$LÂ9L, ÃLÃL, Ã
AM> ÄM;ÄG/M> ÌP
ÌL, ÌLÌL, ÌL)ÌLÌL)Ì$L, Ì,M8Ì?+M*Í*M8Í8P
Í;M> Í>PÎ9O	Ï	PÏ-PÐPÐP
z/ingest/sourcez7The original filename to delete from Azure Blob Storage)Údescriptionr…   Úsource_namec           	   ƒ   ó:  K  — t         j                  d|› d| › �«       	 |r|› d|› �}t        j                  | |«       t	        | |«      }dd|› d|› d| › d�|d	œS # t
        $ r5}t         j                  d
|› d| › d|› �d¬«       t        ddddœ¬«      ‚d}~ww xY w­w)zX
    Deletes all data associated with a specific source_id from the knowledge base.
    z&Received request to delete source_id 'z' for tenant r€   rK   zDeleted z chunks for source_id 'z' from z and Azure storage.)rM   rN   Údeleted_countzError deleting source_id ú for tenant rO   TrP   rW   rS   z'An error occurred during data deletion.rL   rT   N)rX   rY   r   Údelete_filer   r\   rS   r   )rI   r…   rÃ   Úazure_blob_namerÅ   r^   s         r_   Údelete_ingested_sourcerÉ     sÔ   è ø€ ô ‡K�KÐ8¸¸À=ÐQZÐP[Ð\Ô]ðáØ!* ¨1¨[¨MÐ:ˆOÜ×'Ñ'¨	°?ÔCä4°YÀ	ÓJˆàØ! - Ð0GÈ	À{ÐRYÐZcÐYdÐdwÐxØ*ñ
ð 	
øô
 ò Ü�‰Ð0°°¸<È	À{ÐRTÐUVÐTWÐXÐcgˆÔhÜ¨¸wÐS|Ñ4}Ô~Ð~ûðüs(   ‚BŸ:A ÁBÁ	BÁ#0BÂBÂBz/summary/{type}Útypec           	   ƒ   ó0  K  — t         j                  d| › d|› �«       |dvrt        dd¬«      ‚	 t        | |«      \  }}|s(t         j	                  d| › d|› �«       t        d	d
¬«      ‚| |||r|j                  «       j                  «       nd |r|j                  «       j                  d«      nd |r|j                  «       dœS d dœS # t        $ r}|‚d }~wt        $ r5}t         j                  d| › d|› d|› �d¬«       t        ddddœ¬«      ‚d }~ww xY w­w)Nz+Received summary fetch request for tenant: z, type: )rc   rƒ   r¥   rR   z8Invalid summary type. Use 'files', 'text', or 'website'.rT   zNo summary found for z type é”  z*No summary found for this type and tenant.ri   )rI   rÊ   rf   rk   rl   rm   z"Error fetching summary for tenant z, type rO   TrP   rW   rS   z Internal error fetching summary.rL   )rX   rY   r   r   r¯   rr   rs   rt   ru   r\   rS   )rI   rÊ   rf   ÚdtÚher^   s         r_   Úget_summaryrÏ   '  s3  è ø€ ä
‡K�KÐ=¸i¸[ÈÐQUÐPVÐWÔXØÐ/Ñ/Ü¨Ð4nÔoÐoðxÜ(¨°DÓ9‰ˆ�ÙÜ�N‰NÐ2°9°+¸VÀDÀ6ÐJÔKÜ¨CÐ8dÔeÐeð #ØØÙ68˜RŸW™W›Y×0Ñ0Ô2¸dÙ?A˜RŸW™W›Y×/Ñ/°
Ô;ÀtÙ,.˜"Ÿ,™,›.ñ
ð 	
ð 59ñ
ð 	
øô ò ØˆûÜò xÜ�‰Ð9¸)¸ÀGÈDÈ6ÐQSÐTUÐSVÐWÐbfˆÔgÜ¨¸wÐSuÑ4vÔwÐwûðxüsA   ‚-D°BC ÃDÃC ÃDÃ	DÃCÃDÃ0DÄDÄDc              ƒ   óð   K  — t         j                  d| › �«       	 t        | d«      }d| t        |«      |dœS # t        $ r2}t         j                  d| › d|› �d¬«       t        d	d
ddœ¬«      ‚d }~ww xY w­w)Nz#Listing ingested files for tenant: ÚfilerK   )rM   rI   Úcountrc   zError listing files for tenant rO   TrP   rW   rS   zError listing ingested files.rL   rT   ©rX   rY   r   rn   r\   rS   r   ©rI   Úsourcesr^   s      r_   Úlist_ingested_filesrÖ   A  sƒ   è ø€ ä
‡K�KÐ5°i°[ÐAÔBðuÜ'¨	°6Ó:ˆØ#°)ÄcÈ'ÃlÐ]dÑeÐeøÜò uÜ�‰Ð6°y°kÀÀAÀ3ÐGÐRVˆÔWÜ¨¸wÐSrÑ4sÔtÐtûðuüó%   ‚A6œ8 ·A6¸	A3Á-A.Á.A3Á3A6z/ingest/websitesc              ƒ   óð   K  — t         j                  d| › �«       	 t        | d«      }d| t        |«      |dœS # t        $ r2}t         j                  d| › d|› �d¬«       t        d	d
ddœ¬«      ‚d }~ww xY w­w)Nz&Listing ingested websites for tenant: r¥   rK   )rM   rI   rÒ   Úwebsitesz"Error listing websites for tenant rO   TrP   rW   rS   z Error listing ingested websites.rL   rT   rÓ   rÔ   s      r_   Úlist_ingested_websitesrÚ   K  sƒ   è ø€ ä
‡K�KÐ8¸¸ÐDÔEðxÜ'¨	°9Ó=ˆØ#°)ÄcÈ'ÃlÐ`gÑhÐhøÜò xÜ�‰Ð9¸)¸ÀBÀqÀcÐJÐUYˆÔZÜ¨¸wÐSuÑ4vÔwÐwûðxür×   c              ƒ   óð   K  — t         j                  d| › �«       	 t        | d«      }d| t        |«      |dœS # t        $ r2}t         j                  d| › d|› �d¬«       t        d	d
ddœ¬«      ‚d }~ww xY w­w)Nz"Listing ingested text for tenant: rƒ   rK   )rM   rI   rÒ   Útext_sourceszError listing text for tenant rO   TrP   rW   rS   zError listing ingested text.rL   rT   rÓ   rÔ   s      r_   Úlist_ingested_textrÝ   U  sƒ   è ø€ ä
‡K�KÐ4°Y°KÐ@ÔAðtÜ'¨	°6Ó:ˆØ#°)ÄcÈ'ÃlÐdkÑlÐløÜò tÜ�‰Ð5°i°[ÀÀ1À#ÐFÐQUˆÔVÜ¨¸wÐSqÑ4rÔsÐsûðtür×   z
/documentsc              ƒ   óú  K  — 	 t        | «      } t        | «       |D �cg c]&  }|j                  |j                  «       ƒ d{  –—† f‘Œ( }}t	        | |||¬«      }ddt        |d   «      › d�dœ|¥S 7 Œ/c c}w # t        $ r ‚ t        $ r5}t        j                  d| › d	|› �d
¬«       t        ddd|› �dœ¬«      ‚d}~wt        $ r2}t        j                  d| › d	|› �d
¬«       t        ddddœ¬«      ‚d}~ww xY w­w)z÷Upload documents under a category.

    Same indexing as /ingest/files, but the response carries each stored
    document's source_id, so the settings page can offer delete and
    re-categorise straight away without a second listing request.
    Nre   rK   zStored rh   z document(s).rL   zExtraction error for tenant rO   TrP   rR   rS   zUpload failed: rT   zError uploading documents for rW   zCould not store the documents.)r7   r   ro   rp   r/   rn   r9   rZ   rX   rS   r   r\   )	rI   rb   ra   rc   rv   rw   rx   r]   r^   s	            r_   Úupload_documents_endpointrß   _  s.  è ø€ ðcÜ'¨	Ó2ˆ	Ü˜Ô#Ø9>Ö?°A�A—J‘J a§f¡f£h§Ò/Ð?ˆÐ?Ü! )¨W¸xÐQXÔYˆØ#Ø$¤S¨°Ñ)<Ó%=Ð$>¸mÐLñàðð 	ð !/úÒ?øô
 ò ØÜò YÜ�‰Ð3°I°;¸bÀÀÐEÐPTˆÔUÜ¨¸wØBQÐRTÐQUÐ@Vñ5Xô Yð 	Yûäò cÜ�‰Ð5°i°[ÀÀ1À#ÐFÐQUˆÔVÜ¨¸wØ@`ñ5bô cð 	cûðcüsW   ‚C;„A8 ž#A3ÁA1ÁA3Á	'A8 Á0C;Á1A3Á3A8 Á8C8Â
0B:Â:C8Ã-C3Ã3C8Ã8C;c           	   ƒ   óþ   K  — 	 t        | «      } t        | |«      }|st        ddddœ¬«      ‚ddi|¥S # t        $ r ‚ t        $ r5}t        j                  d|› d	| › d
|› �d¬«       t        ddddœ¬«      ‚d}~ww xY w­w)z?Delete a document: its knowledge base rows and its stored file.rÌ   rS   úDocument not found.rL   rT   rM   rK   zError deleting document ú for rO   TrP   rW   zCould not delete the document.N)r7   r,   r   r\   rX   rS   )rI   r…   Úremovedr^   s       r_   Údelete_document_endpointrä   €  s®   è ø€ ðcÜ'¨	Ó2ˆ	Ü! )¨YÓ7ˆÙÜ¨CÀ7ØDYñ9[ô \ð \à˜)Ð/ wÐ/Ð/øÜò ØÜò cÜ�‰Ð/°	¨{¸%À	¸{È"ÈQÈCÐPÐ[_ˆÔ`Ü¨¸wØ@`ñ5bô cð 	cûðcüs%   ‚A=„.3 ²A=³A:Á0A5Á5A:Á:A=c              ƒ   óÔ   K  — 	 t        | «      } t        | |«      }dt        |«      |dœS # t        $ r2}t        j                  d| › d|› �d¬«       t        ddd	d
œ¬«      ‚d}~ww xY w­w)zHThe tenant's uploaded documents, for the Attach Documents settings card.rK   )rM   rÒ   rh   zError listing documents for rO   TrP   rW   rS   zCould not list documents.rL   rT   N)r7   r0   rn   r\   rX   rS   r   )rI   rb   rh   r^   s       r_   Úlist_documents_endpointræ   ’  s~   è ø€ ð^Ü'¨	Ó2ˆ	Ü" 9¨hÓ7ˆ	Ø#¬c°)«nÈ9ÑUÐUøÜò ^Ü�‰Ð3°I°;¸bÀÀÐDÈtˆÔTÜ¨¸wØ@[ñ5]ô ^ð 	^ûð^üs$   ‚A(„%* ©A(ª	A%³-A Á A%Á%A(z/documents/categoryc              ƒ   ó"  K  — 	 t        | «      } t        | ||«      st        ddddœ¬«      ‚d||j                  «       xs t        dœS # t        $ r ‚ t
        $ r2}t        j                  d|› d	|› �d
¬«       t        ddddœ¬«      ‚d}~ww xY w­w)z+Re-categorise an already uploaded document.rÌ   rS   rá   rL   rT   rK   )rM   r…   rb   zError setting category for rO   TrP   rW   zCould not update the category.N)r7   r1   r   r‰   r*   r\   rX   rS   )rI   r…   rb   r^   s       r_   Úset_document_category_endpointrè   Ÿ  sµ   è ø€ ðcÜ'¨	Ó2ˆ	Ü˜I y°(Ô;Ü¨CÀ7ØDYñ9[ô \ð \à#°)Ø$ŸN™NÓ,Ò@Ô0@ñBð 	Bøäò ØÜò cÜ�‰Ð2°9°+¸RÀ¸sÐCÈdˆÔSÜ¨¸wØ@`ñ5bô cð 	cûðcüs)   ‚B„AA ÁBÁBÁ-BÂBÂBz/documents/downloadÚ	file_namec              ƒ   ó¤  K  — |s|st        ddddœ¬«      ‚	 t        | «      } |rt        | |«      n(t        | t        | |«      xs i j	                  d«      «      }t        | |«      }|st        ddddœ¬«      ‚t        |d	¬
«      S # t         $ r ‚ t        $ r6}t        j                  d|xs |› d|› �d¬«       t        ddddœ¬«      ‚d}~ww xY w­w)a/  Download a document. This is the URL handed to visitors in chat.

    It redirects to a storage link generated right now, so the link in a chat
    transcript never goes stale and the storage account never appears in a chat
    payload. Identify the document by `source_id`; `file_name` also works.
    rR   rS   z#source_id or file_name is required.rL   rT   r…   rÌ   rá   i.  )rU   zError serving document rO   TrP   rW   zCould not serve the document.N)
r   r7   r.   r-   r²   r+   r   r\   rX   rS   )rI   r…   ré   Údocumentr�   r^   s         r_   Údocument_download_endpointrì   µ  s  è ø€ ñ ™Ü¨¸wØ@eñ5gô hð 	hðbÜ'¨	Ó2ˆ	Ù;D”M )¨YÔ7Ü& y´=ÀÈIÓ3VÒ3\ÐZ\ß(+©¨KÓ(8ó:ð 	ô �y (Ó+ˆÙÜ¨CÀ7ØDYñ9[ô \ð \ô   °Ô5Ð5øÜò ØÜò bÜ�‰Ð.¨yÒ/E¸IÐ.FÀbÈÈÐLÐW[ˆÔ\Ü¨¸wØ@_ñ5aô bð 	bûðbüs)   ‚C˜A,B ÂCÂCÂ1CÃCÃCz/tools-settingsc              ƒ   óÄ   K  — 	 t        | «      }d|t        t        «      dœS # t        $ r2}t        j                  d| › d|› �d¬«       t        ddd	d
œ¬«      ‚d}~ww xY w­w)aL  Every capability switch for a tenant, plus the email copy.

    `features` carries all eight switches so the settings page can render them
    without knowing which ones the backend implements. `wired` names the subset
    that currently changes chat behaviour; the rest are stored preferences whose
    tools do not exist yet.
    rK   ©rM   rE   Úwiredz!Error fetching tool settings for rO   TrP   rW   rS   zCould not load tool settings.rL   rT   N)r6   Úsortedr4   r\   rX   rS   r   )rI   rE   r^   s      r_   Úget_tools_settings_endpointrñ   Õ  s|   è ø€ ðbÜ$ YÓ/ˆØ#Ø$Ü¤Ó.ñ0ð 	0øô ò bÜ�‰Ð8¸¸À2ÀaÀSÐIÐTXˆÔYÜ¨¸wØ@_ñ5aô bð 	bûðbüs$   ‚A „" ¡A ¢	A«-AÁAÁA Úreturnc                 ó,  — d„ }t         D �ci c]  }|| v sŒ| || |   «      “Œ }}d| v r|j                  d || d   «      «       t        D �ci c]  }|| v sŒ|| |   “Œ }}t        D �ci c]  }|› d�| v r	|| |› d�   “Œ }}|||fS c c}w c c}w c c}w )zÌRead the flat form shape the settings page posts.

    Fields are named after the switches themselves (voice_mode=false), plus the
    three email strings. `enabled` is the old name for `send_email`.
    c                 óT   — t        | «      j                  «       j                  «       dv S )N)Ú1ÚtrueÚyesÚon)r[   r‰   Úlower)Úraws    r_   Úas_boolz%_parse_settings_form.<locals>.as_boolï  s"   € Ü�3‹x�~‰~Ó×%Ñ%Ó'Ð+EÐEÐEó    ÚenabledÚ
send_emailÚ	_provider)r3   Ú
setdefaultr2   r5   )Úformrû   ÚnameÚfeaturesÚemailÚ	providerss         r_   Ú_parse_settings_formr  é  sÓ   € òFô 7GÖW¨dÈ$ÐRVÊ,�‘g˜d 4™jÓ)Ñ)ÐW€HÐWØ�DÑØ×Ñ˜L©'°$°y±/Ó*BÔCÜ*=ÖN $ÀÈÂˆT�4˜‘:ÑÐN€EÐNô =Mö /°DØ�V˜9Ð%¨Ñ-ð �t˜t˜f IÐ.Ñ/Ñ/ð /€Ið /à�U˜IÐ%Ð%ùò Xùò Oùò/s   Œ	B–BÁ	BÁBÁ)BÚrequestc              ƒ   ó¬  K  — | j                   j                  dd«      j                  d«      r¬| j                  «       ƒ d{  –—† }t	        |t
        «      st        ddddœ¬	«      ‚|j                  d
«      xs dj                  «       }|j                  d«      xs i |j                  d«      xs i }}|j                  d«      xs i }|j                  d«      }nL| j                  «       ƒ d{  –—† }|j                  d
«      xs dj                  «       }t        |«      \  }}}d}|st        ddddœ¬	«      ‚|j                  «       D ��	ci c]  \  }}	|	€Œ	||	“Œ }}}	|j                  «       D ��	ci c]  \  }}	|	€Œ	||	“Œ }}}	|s|s|s|€t        ddddœ¬	«      ‚	 t        t        |«      «       t        ||xs d|xs d|xs d|¬«      }
d|
t        t        «      dœS 7 �ŒŸ7 Œõc c}	}w c c}	}w # t         $ r}t        ddt#        |«      dœ¬	«      ‚d}~wt$        $ r2}t&        j)                  d|› d|› �d¬«       t        ddddœ¬	«      ‚d}~ww xY w­w)až  Update capability switches and/or email copy. Only what is sent changes.

    Accepts either JSON:
        {"tenant_id": "...", "features": {"voice_mode": false},
         "email": {"header": "..."}, "ticket_emails": [{"email": "...", "category": "..."}]}
    or form fields, which is what the settings page already posts:
        tenant_id=...&voice_mode=false&header=...

    Both are supported so moving off the old /email-settings URL is a one-line
    change on the caller rather than a rewrite of how it posts. `ticket_emails`
    is JSON-only: a list of objects has no natural flat-form encoding and
    nothing in this codebase's form-based caller needs it.
    zcontent-typer~   zapplication/jsonNrR   rS   zBody must be an object.rL   rT   rI   r  r  r  Úticket_emailsztenant_id is required.zNo settings supplied.)r  r  r  r	  rK   rî   zError saving tool settings for rO   TrP   rW   zCould not save tool settings.)Úheadersr²   Ú
startswithÚjsonÚ
isinstanceÚdictr   r‰   r  r  Úitemsr   r7   r8   rð   r4   rZ   r[   r\   rX   rS   )r  ÚpayloadrI   r  r  r  r	  r  ÚkÚvrE   r]   r^   s                r_   Úsave_tools_settings_endpointr  þ  sk  è ø€ ð ‡�×Ñ˜>¨2Ó.×9Ñ9Ð:LÔMØŸ™›×&ˆÜ˜'¤4Ô(Ü¨CÀ7ØD]ñ9_ô `ð `à—[‘[ Ó-Ò3°×:Ñ:Ó<ˆ	Ø!Ÿ+™+ jÓ1Ò7°R¸¿¹ÀWÓ9MÒ9SÐQS�%ˆØ—K‘K Ó,Ò2°ˆ	ØŸ™ OÓ4‰à—\‘\“^×#ˆØ—X‘X˜kÓ*Ò0¨b×7Ñ7Ó9ˆ	Ü%9¸$Ó%?Ñ"ˆ�%˜ØˆáÜ¨¸wØ@Xñ5Zô [ð 	[ð "*§¡Ó!1×C™˜˜A°Q±]��1‘ÐC€HÑCØ#Ÿk™k›m×=‘d�a˜¨q©}ˆQ�‰TÐ=€EÑ=Ù™E©)¸Ð8MÜ¨¸wØ@Wñ5Yô Zð 	Zðbô 	Ô,¨YÓ7Ô8ô
 & i¸(Ò:JÀdØ,1ªM°TØ09Ò0A¸TØ4AôCˆð $°ÄFÌ=ÓDYÑZÐZðM 'ùð $úó DùÛ=øô" ò ]Ü¨¸wÔSVÐWYÓSZÑ4[Ô\Ð\ûÜò bÜ�‰Ð6°y°kÀÀAÀ3ÐGÐRVˆÔWÜ¨¸wØ@_ñ5aô bð 	bûðbüs€   ‚?IÁG ÁB+IÃ-G#Ã.AIÅ
G%ÅG%ÅIÅ1
G+Å<G+ÆIÆAG1 ÇIÇ#IÇ%IÇ1	IÇ:HÈIÈ-IÉIÉIz/recommendations/eventr  c              ƒ   óP  ‡‡‡K  — | j                  d«      xs dj                  «       Š| j                  d«      xs dj                  «       }| j                  d«      xs dj                  «       Š| j                  d«      xs i Š‰r|r‰j                  d«      st        ddd	d
œ¬«      ‚‰t        vr/t        ddddj	                  t        t        «      «      › �d
œ¬«      ‚	 t        j                  j                  ˆˆˆfd„«      ƒ d{  –—† }|j                  d«      sUt        j                  d|› d‰j                  d«      › d|j                  d«      › d‰j                  d«      ›d�	«       ddi|¥S d t        j                  «       j                  dd! › �}t        j                  |› dt!        |d"   «      › d#|› d‰j                  d«      › �«       d|d$œ|¥S 7 ŒÐ# t        $ r?}t        j                  d|› d‰j                  d«      › d|› �d¬«       ddddœcY d}~S d}~ww xY w­w)%a  The widget reports page activity; we decide whether to suggest anything.

    Answers HTTP 200 with `recommend: false` rather than an error whenever there
    is nothing to show, because a suggestion the visitor never asked for must
    never surface as a failure in their browser.
    rI   r~   Ú
visitor_idÚeventr»   r�   rR   rS   z0tenant_id, visitor_id and page.url are required.rL   rT   z Unknown event. Expected one of: z, c                  ó0   •— t        t        ‰«      ‰ ‰«      S ©N)r=   r7   )r  r»   rI   s   €€€r_   ú<lambda>z0recommendations_event_endpoint.<locals>.<lambda>U  s   ø€ ”FÔ.¨yÓ9¸5À$ÓG€ rü   Nz#Recommendation endpoint failed for z on rO   TrP   rK   FÚno_match)rM   Ú	recommendÚreasonr  zNo recommendation for r  z (title=Útitleú)rM   Úrec_é
   Úproductsz products for )rM   Úrecommendation_id)r²   r‰   r   r<   Újoinrð   ÚanyioÚ	to_threadÚrun_syncr\   rX   rS   rY   rŠ   r‹   Úhexrn   )r  r  ÚoutcomeÚexr"  r  r»   rI   s        @@@r_   Úrecommendations_event_endpointr*  =  s3  úè ø€ ð —‘˜[Ó)Ò/¨R×6Ñ6Ó8€IØ—+‘+˜lÓ+Ò1¨r×8Ñ8Ó:€JØ�[‰[˜Ó!Ò' R×.Ñ.Ó0€EØ�;‰;�vÓÒ$ "€Dá™J¨d¯h©h°u¬oÜ¨ØØIñ5Kô Lð 	Lð ”LÑ Ü¨ØØ9¸$¿)¹)ÄFÌ<ÓDXÓ:YÐ9ZÐ[ñ5]ô ^ð 	^ð	OÜŸ™×0Ñ0ÝGóI÷ Iˆð �;‰;�{Ô#Ü�‰Ð,¨Z¨L¸¸T¿X¹XÀe»_Ð<MÈRØ—{‘{ 8Ó,Ð-¨X°d·h±h¸wÓ6GÐ5JÈ!ðMô 	Nà˜)Ð/ wÐ/Ð/àœtŸz™z›|×/Ñ/°°Ð4Ð5Ð6ÐÜ
‡K�KÐ$Ð% R¬¨G°JÑ,?Ó(@Ð'AÀØ�,˜d 4§8¡8¨E£?Ð"3ð5ô 6àÐ6GÑSÈ7ÐSÐSð%Iùäò Oô 	�‰Ð:¸:¸,ÀdØ—x‘x “Ð' r¨"¨ð/Ø9=ð 	ô 	?à#°%À:ÑNÕNûðOüsI   …CH&Ã!'G ÄGÄ	G ÄCH&ÇG Ç	H#Ç$4HÈH#ÈH&ÈH#È#H&z/chatÚdefaultÚuserÚqueryÚ	thread_idÚ	user_nameÚroleÚlocationc           
   ƒ   óâ  K  — t         j                  d| › d|› d|› d|› �«       	 t        | ||||||¬«      ƒ d {  –—† }d|j                  dd«      |j                  d	g «      |j                  d
g «      |j                  dg «      ||dœS 7 ŒN# t        $ r}d|j
                  g g dœ||dœcY d }~S d }~wt        $ r2}	t         j                  d| › d|	› �d¬«       t        ddddœ¬«      ‚d }	~	ww xY w­w)Nz!Received chat request for tenant ú, user_id: z, role: z, location: )ra   r1  rK   rƒ   r~   Ú	resourcesÚtoolsr!  )rM   Úresponser4  r5  r!  r.  ra   rS   )Úurlsr¨   )rM   r6  r4  r.  ra   z"Chat generation failed for tenant rO   TrP   rW   z9Failed to generate chat response. Please try again later.rL   rT   )	rX   rY   r%   r²   r9   rN   r\   rS   r   )
rI   r-  r.  ra   r/  r0  r1  Úfull_responseÚqer^   s
             r_   Úchatr:  i  s8  è ø€ ô ‡K�KÐ3°I°;¸kÈ'ÈÐRZÐ[_ÐZ`Ð`lÐmuÐlvÐwÔxðQÜ/°	¸5À)ÈYÐX\Ðfmð  yAô  B÷  BˆàØ%×)Ñ)¨&°"Ó5Ø&×*Ñ*¨;¸Ó;Ø"×&Ñ& w°Ó3Ø%×)Ñ)¨*°bÓ9Ø"Øñ
ð 	
ð Bùô ò 
àØŸ
™
Ø"$°Ñ3Ø"Øñ
õ 	
ûô ò QÜ�‰Ð9¸)¸ÀBÀqÀcÐJÐUYˆÔZÜ¨¸wð  TOñ  5Pô  Qð  	QûðQüsQ   ‚"C/¥B ºB	»AB ÂC/Â	B Â	C,ÂB.Â(C,Â)C/Â.C,Â:-C'Ã'C,Ã,C/z/chat/threadsc              ƒ   óö   K  — t         j                  d| › d|› �«       	 t        | |¬«      }dt        |«      |dœS # t        $ r2}t         j                  d| › d|› �d¬	«       t        d
dddœ¬«      ‚d }~ww xY w­w)NzListing threads for tenant: r3  r‚   rK   )rM   rÒ   Úthreadsz*Error in list_threads endpoint for tenant rO   TrP   rW   rS   zError retrieving threads.rL   rT   )rX   rY   r&   rn   r\   rS   r   )rI   ra   r<  r^   s       r_   Úlist_threadsr=  ‹  sŽ   è ø€ ä
‡K�KÐ.¨y¨k¸ÀWÀIÐNÔOð	qÜ! )°WÔ=ˆàÜ˜“\Øñ
ð 	
øô
 ò qÜ�‰ÐAÀ)ÀÈBÈqÈcÐRÐ]aˆÔbÜ¨¸wÐSnÑ4oÔpÐpûðqüs%   ‚A9Ÿ; ºA9»	A6Á-A1Á1A6Á6A9z/chat/historyc              ƒ   óî   K  — t         j                  d| › d|› d|› �«       	 t        | ||¬«      }d|||dœS # t        $ r2}t         j	                  d| › d|› �d	¬
«       t        ddddœ¬«      ‚d }~ww xY w­w)NzFetching history for tenant: z, user: z
, thread: r‚   rK   )rM   r.  ra   Úmessagesz0Error in fetch_chat_history endpoint for tenant rO   TrP   rW   rS   zError retrieving chat history.rL   rT   )rX   rY   r'   r\   rS   r   )rI   r.  ra   r?  r^   s        r_   Úfetch_chat_historyr@  ™  sš   è ø€ ä
‡K�KÐ/°	¨{¸(À7À)È:ÐV_ÐU`ÐaÔbð
vÜ# I¨yÀ'ÔJˆàØ"ØØ ñ	
ð 	
øô ò vÜ�‰ÐGÈ	À{ÐRTÐUVÐTWÐXÐcgˆÔhÜ¨¸wÐSsÑ4tÔuÐuûðvüs%   ‚A5¢7 ¶A5·	A2Á -A-Á-A2Á2A5z/ticketsc              ƒ   ó  K  — t         j                  d| › �«       	 t        | «      }t        | «      }dt	        |«      ||dœS # t
        $ r2}t         j                  d| › d|› �d¬«       t        dd	d
dœ¬«      ‚d }~ww xY w­w)Nz.Received request to fetch tickets for tenant: rK   )rM   rÒ   Ústatus_countsÚticketsz+Error in fetch_tickets endpoint for tenant rO   TrP   rW   rS   zError retrieving tickets.rL   rT   )rX   rY   r   r   rn   r\   rS   r   )rI   rC  rB  r^   s       r_   Úfetch_ticketsrD  ¨  s”   è ø€ ä
‡K�KÐ@ÀÀÐLÔMðqÜ˜iÓ(ˆÜ0°Ó;ˆàÜ˜“\Ø*Øñ	
ð 	
øô ò qÜ�‰ÐBÀ9À+ÈRÐPQÈsÐSÐ^bˆÔcÜ¨¸wÐSnÑ4oÔpÐpûðqüs(   ‚B œ%A ÁB Á	A=Á-A8Á8A=Á=B z/tickets/{ticket_id}/commentsÚAdminÚ	ticket_idÚ
admin_nameÚcommentc           	   ƒ   óž  K  — t         j                  d| › d|› �«       	 t        || ||«      }	 ddlm} ddd| › d|› �|d	œ} |d
|› d�|«      ƒ d {  –—†  dd|dœS 7 Œ
# t
        $ r"}t         j                  d|› �«       Y d }~Œ.d }~ww xY w# t
        $ r5}t         j                  d| › d|› d|› �d¬«       t        ddddœ¬«      ‚d }~ww xY w­w)Nz+Received request to post comment on ticket ú for tenant: r   ©Úpublish_eventÚsystemÚticket_comment_addedzNew comment added to ticket z by ©rÊ   r  rN   ÚdataÚtenant_ú:ticketsz+Failed to publish ticket comment to redis: rK   zComment added successfully)rM   rN   rH  z Error posting comment to ticket rÆ   rO   TrP   rW   rS   zFailed to add comment.rL   rT   )	rX   rY   r   Úapp.services.infra.redisrL  r\   r¯   rS   r   )	rF  rI   rG  rH  Únew_commentrL  r  Úws_errr^   s	            r_   Úpost_ticket_commentrV  ¸  s  è ø€ ô ‡K�KÐ=¸i¸[ÈÐV_ÐU`ÐaÔbðnÜ(¨°I¸zÈ7ÓSˆð
	SÝ>à Ø/Ø9¸)¸ÀDÈÈÐUØ#ñ	ˆGñ   '¨)¨°HÐ =¸wÓG×GÐGð
  Ø3Ø"ñ
ð 	
ð	 HùÜò 	SÜ�N‰NÐHÈÈÐQ×RÑRûð	Sûô ò nÜ�‰Ð7¸	°{À,ÈyÈkÐY[Ð\]Ð[^Ð_ÐjnˆÔoÜ¨¸wÐSkÑ4lÔmÐmûðnüsc   ‚CŸB ®#A ÁAÁA ÁB ÁCÁA Á	B	Á'BÁ?B ÂB	Â	B Â	C
Â0CÃC
Ã
Cc           	   ƒ   óú   K  — t         j                  d| › d|› �«       	 t        || «      }dt        |«      |dœS # t        $ r5}t         j                  d| › d|› d|› �d¬	«       t        d
dddœ¬«      ‚d }~ww xY w­w)Nz.Received request to fetch comments for ticket rJ  rK   )rM   rÒ   Úcommentsz#Error fetching comments for ticket rÆ   rO   TrP   rW   rS   zError retrieving comments.rL   rT   )rX   rY   r   rn   r\   rS   r   )rF  rI   rX  r^   s       r_   Úfetch_ticket_commentsrY  Ù  s›   è ø€ ä
‡K�KÐ@ÀÀÈ=ÐYbÐXcÐdÔeð	rÜ& y°)Ó<ˆàÜ˜“]Ø ñ
ð 	
øô
 ò rÜ�‰Ð:¸9¸+À\ÐR[ÐQ\Ð\^Ð_`Ð^aÐbÐmqˆÔrÜ¨¸wÐSoÑ4pÔqÐqûðrüs%   ‚A;Ÿ: ¹A;º	A8Á0A3Á3A8Á8A;z/tickets/{ticket_id}/statusrM   c           	   ƒ   ó*  K  — t         j                  d| › d|› d|› �«       g d¢}||vrt        ddd|› �dœ¬	«      ‚	 t        || |«      }|st        d
dddœ¬	«      ‚	 ddlm} t        || «      }ddd| › d|› �|xs | |dœdœ} |d|› d�|«      ƒ d {  –—†  dd|› �| |dœS 7 Œ# t        $ r"}t         j                  d|› �«       Y d }~Œ2d }~ww xY w# t        $ r5}	t         j                  d| › d|› d|	› �d¬«       t        ddd dœ¬	«      ‚d }	~	ww xY w­w)!Nz,Received request to update status of ticket z to rJ  )zIn ProgressÚResolvedÚOngoingÚOpenrR   rS   zInvalid status. Must be one of rL   rT   rÌ   z"Ticket not found or update failed.r   rK  rM  Úticket_status_updatedzTicket z status updated to )rF  rM   rO  rQ  rR  z1Failed to publish ticket status update to redis: rK   zTicket status updated to )rM   rN   rF  Ú
new_statusz!Error updating status for ticket rÆ   rO   TrP   rW   zFailed to update ticket status.)
rX   rY   r   r   rS  rL  r   r\   r¯   rS   )
rF  rI   rM   Úallowed_statusesrK   rL  Úticket_datar  rU  r^   s
             r_   Úupdate_ticket_status_endpointrb  ç  sŒ  è ø€ ô ‡K�KÐ>¸y¸kÈÈfÈXÐUbÐclÐbmÐnÔoâEÐØÐ%Ñ%Ü¨¸wÐUtð  vFð  uGð  THñ  5Iô  Jð  	JðwÜ& y°)¸VÓDˆÙÜ¨CÀ7ÐW{Ñ8|Ô}Ð}ð	YÝ>Ü$ Y°	Ó:ˆKà Ø0Ø$ Y KÐ/BÀ6À(ÐKØ#ÒQ°YÈ&Ñ'Qñ	ˆGñ   '¨)¨°HÐ =¸wÓG×GÐGð
  Ø2°6°(Ð;Ø"Ø ñ	
ð 	
ð	 HùÜò 	YÜ�N‰NÐNÈvÈhÐW×XÑXûð	Yûô ò wÜ�‰Ð8¸¸À<ÐPYÈ{ÐZ\Ð]^Ð\_Ð`ÐkoˆÔpÜ¨¸wÐStÑ4uÔvÐvûðwüsd   ‚:D½C Á6B$ ÂB"ÂB$ Â	C Â!DÂ"B$ Â$	CÂ-C
ÃC Ã
CÃC Ã	DÃ0DÄDÄDz/analytics/usageéd   Úlimitc              ƒ   óÀ   K  — 	 t        | |«      }d| t        |«      |dœS # t        $ r2}t        j	                  d| › d|› �d¬«       t        ddd	d
œ¬«      ‚d}~ww xY w­w)z7
    Returns LLM token usage history for a tenant.
    rK   )rM   rI   rÒ   Úusagez-Error in fetch_llm_usage endpoint for tenant rO   TrP   rW   rS   z!Error retrieving usage analytics.rL   rT   N)r   rn   r\   rX   rS   r   )rI   rd  Úusage_historyr^   s       r_   Úfetch_llm_usagerh    sz   è ø€ ð

yÜ% i°Ó7ˆàØ"Ü˜Ó'Ø"ñ	
ð 	
øô ò yÜ�‰ÐDÀYÀKÈrÐRSÐQTÐUÐ`dˆÔeÜ¨¸wÐSvÑ4wÔxÐxûðyüs$   ‚A„  ŸA 	A©-AÁAÁAz/analytics/refreshc              ƒ   óþ   K  — t         j                  d| › �«       	 t        | «      ƒ d{  –—† }d|dœS 7 Œ	# t        $ r ‚ t        $ r2}t         j                  d| › d|› �d¬«       t        d	d
ddœ¬«      ‚d}~ww xY w­w)zF
    Triggers the KPI calculation and returns the latest metrics.
    z/Received analytics refresh request for tenant: NrK   )rM   rP  z$Analytics refresh failed for tenant rO   TrP   rW   rS   z!Failed to compute analytics KPIs.rL   rT   )rX   rY   r(   r9   r\   rS   r   )rI   Úkpisr^   s      r_   Úrefresh_analyticsrk  !  s”   è ø€ ô
 ‡K�KÐAÀ)ÀÐMÔNð
yÜ˜iÓ(×(ˆàØñ
ð 	
ð )ùô
 ò ØÜò yÜ�‰Ð;¸I¸;ÀbÈÈÐLÐW[ˆÔ\Ü¨¸wÐSvÑ4wÔxÐxûðyüs1   ‚A=œ6 ª4«6 ³A=´6 ¶A:Á-A5Á5A:Á:A=z/analytics/generalÚallz(Time range (e.g., 1h, 24h, 7d, 30d, all)zCustom start date (YYYY-MM-DD)zCustom end date (YYYY-MM-DD)Ú
time_rangeÚ
start_dateÚend_datec           
   ƒ   óT  K  — t         j                  d| › d|› d|› d|› �«       	 t        | |||«      ƒ d{  –—† }|€d| i g g dœg g dœg g dœdœd	œd
dœS d| |dœS 7 Œ## t        $ r}|‚d}~wt        $ r2}t         j                  d| › d|› �d¬«       t        ddddœ¬«      ‚d}~ww xY w­w)zY
    Returns a subset of KPIs optimized for the UI dashboard with dynamic filtering.
    z%Received request for metrics: tenant z, range z	, custom ú-NrK   )ÚlabelsÚdatasets)Útotal_usersÚconversation_channelsÚresponse_time)Útop_kpisÚchartsz1No metrics available for the selected time range.)rM   rI   rP  rN   )rM   rI   rP  z*Error fetching general metrics for tenant rO   TrP   rW   rS   z"Internal error retrieving metrics.rL   rT   )rX   rY   r)   r   r\   rS   )rI   rm  rn  ro  ÚmetricsrÎ   r^   s          r_   Úfetch_general_analyticsrz  3  sý   è ø€ ô ‡K�KÐ7¸	°{À(È:È,ÐV_Ð`jÐ_kÐklÐmuÐlvÐwÔxðzÜ(¨°JÀ
ÈHÓU×UˆØˆ?ð $Ø&à "à24À"Ñ'EØ<>ÈBÑ1OØ46ÀBÑ)Gññð Oñð ð  Ø"Øñ
ð 	
ð# Vùô, ò ØˆûÜò zÜ�‰ÐAÀ)ÀÈBÈqÈcÐRÐ]aˆÔbÜ¨¸wÐSwÑ4xÔyÐyûðzüsP   ‚"B(¥A ¶A·A ÁB(ÁA ÁB(ÁA Á	B%Á%A'Á'B%Á3-B Â B%Â%B(z/chat/takeoverc           
   ƒ   ó`  K  — ddl m} t        j                  d| › d|› d|› d|› �«       	  || |||¬«      }|st	        dd¬	«      ‚	 dd
lm} |rdnd}|rdnd}d|rdnd|||dœdœ}	 |d| › d|› �|	«      ƒ d{  –—†  d|rdndd|rdnd› d|› �||dœdœ}
 |d| › d�|
«      ƒ d{  –—†  dd|rdnd› d|› �|d œS 7 ŒA7 Œ# t        $ r"}t        j                  d|› �«       Y d}~Œ:d}~ww xY w# t        $ r ‚ t        $ r,}t        j                  d!|› �d"¬#«       t	        d$d%¬	«      ‚d}~ww xY w­w)&zD
    Toggles human takeover for a specific conversation thread.
    r   )Úset_takeover_statuszTakeover toggle: ú/z -> r‚   rÌ   zConversation thread not found.rT   rK  Útakeover_startedÚtakeover_endedzHuman agent has joined the chatz AI assistant has resumed controlrM  )Útakeover_activera   rO  rQ  z:chat_Nz	Takeover ÚstartedÚendedrâ   )r.  r€  z:conversation_listz,Could not publish takeover status to redis: rK   zHuman takeover Ú	activatedÚdeactivated)rM   rN   r€  zTakeover toggle failed: TrP   rW   z'Failed to toggle human takeover status.)
Ú#app.services.integrations.firestorer|  rX   rY   r   rS  rL  r\   r¯   rS   )rI   r.  rM   ra   r|  rK   rL  Ú
event_typeÚmsgÚthread_payloadÚlist_payloadrU  r^   s                r_   Úchat_takeoverrŠ  [  s¨  è ø€ õ HÜ
‡K�KÐ# I ;¨a°¨y¸¸)¸ÀDÈÈÐQÔRð(_Ù% i°¸FÈGÔTˆÙÜ¨CÐ8XÔYÐYð	TÝ>Ù/5Ñ+Ð;KˆJÙ7=Ñ3ÐCeˆCð !Ù/5Ñ+Ð;KØØ,2¸wÑGñ	ˆNñ   '¨)¨°F¸9¸+Ð FÈÓW×WÐWð !Ù/5Ñ+Ð;KØ&±F¡yÀÐ&HÈÈiÈ[ÐYØ&/ÀFÑKñ	ˆLñ   '¨)¨Ð4FÐ GÈÓV×VÐVð
  Ø(¹©ÀMÐ(RÐRWÐXaÐWbÐcØ%ñ
ð 	
ð Xøð WùÜò 	TÜ�N‰NÐIÈ&ÈÐR×SÑSûð	Tûô ò ØÜò _Ü�‰Ð/°¨sÐ3¸dˆÔCÜ¨Ð4]Ô^Ð^ûð_üsv   ‚(D.«C- Á2B? Á9B;Á:,B? Â&B=Â'B? Â+C- Â:D.Â;B? Â=B? Â?	C*ÃC%Ã C- Ã%C*Ã*C- Ã-D+Ã?'D&Ä&D+Ä+D.é   r„   c                 ó2  — | d   t         k7  ry| j                  d«      }|sy| d   j                  d«      }|€t        n
t        |«      }|dk  ryt	        j
                  t        j                  «      |z
  }|t        |¬«      k\  ryt        |t         | d   «      S )	a}  Whether this website source was read recently enough to skip.

    Only websites are throttled, and only when the config does not override the
    window -- a merchant who wants their site read on every press sets
    min_interval_hours to 0 without a deploy.

    Also never skips a source with zero product rows, regardless of
    last_synced_at -- that timestamp only means the source was crawled
    recently, not that the products from that crawl still exist. A cleanup
    that deletes product rows without also clearing this timestamp would
    otherwise leave a build that looks successful but serves an empty
    catalogue.
    ÚkindFÚlast_synced_atÚconfigÚmin_interval_hoursr   )ÚhoursÚexternal_ref)
r@   r²   ÚCRAWL_MIN_INTERVAL_HOURSÚfloatrF   ry   rH   ÚutcrG   r>   )r„   rI   Úlast_syncedr‘  Úages        r_   Ú_crawled_recentlyr˜  ”  s“   € ð ˆf�~œÒ#Øà—*‘*Ð-Ó.€KÙØà�8Ñ× Ñ Ð!5Ó6€EØ(-¨Õ$¼5À»<€EØ�‚zØä
�,‰,”x—|‘|Ó
$ {Ñ
2€CØ
Œi˜eÔ$Ò$Øä˜I¤z°6¸.Ñ3IÓJÐJrü   FrÕ   Úforcec                 ó2   ‡ ‡— ddt         dt        fˆˆ fd„}|S )a  A blocking, job-shaped wrapper around sync_source for all of a tenant's
    connected sources. One source's failure (e.g. a revoked token) must not
    hide the results of the others, so each is caught and reported inline
    rather than aborting the run.
    rI   rò   c           	      ó  •— g }t        ‰	«      }t        ‰	d¬«      D ]�  \  }}‰s<t        || «      r0|j                  |d   ddœ«       |� ||dz  |z  d|› d|› d	�«       ŒD	 t	        j
                  t        | |«      }|j                  d
|d   i|¥«       |€Œ{ ||dz  |z  d|› d|› d	�«       Œ’ d|iS # t        $ rO}t        j                  d|d   d¬«       |j                  |d   t        |«      j                  dœ«       Y d }~Œrd }~ww xY w)Nr}   )Ústartr’  zcrawled recently)r„   Úskippedrc  zsynced z of z sourcesr„   zCatalog sync failed for %sTrP   )r„   rS   Úresults)rn   Ú	enumerater˜  Úappendr$  Úrunr?   r\   rX   rS   rÊ   Ú__name__)
rI   Úprogressrž  ÚtotalÚir„   r(  r)  r™  rÕ   s
           €€r_   r¡  z_sync_sources.<locals>.run»  sG  ø€ ØˆÜ�G“ˆÜ" 7°!Ô4ò 	M‰IˆAˆvÙÔ.¨v°yÔAð
 —‘¨&°Ñ*@Ø+=ñ ?ô @àÐ'Ù˜Q ™W¨Ñ-°¸¸¸4À¸wÀhÐ/OÔPØð_ô  Ÿ)™)¤K°¸FÓC�Ø—‘ ¨&°Ñ*@ÐLÀGÐLÔMð Ñ#Ù˜˜S™ EÑ)¨W°Q°C°t¸E¸7À(Ð+KÕLð9	Mð: ˜7Ð#Ð#øô ò _ô
 —‘Ð9¸6À.Ñ;QØ&*ð ô ,à—‘¨&°Ñ*@Ì4ÐPRË8×K\ÑK\Ñ]×^Ñ^ûð_ús   Á!3B3Â3	DÂ<ADÄDr  )r[   r  )rÕ   r™  r¡  s   `` r_   Ú_sync_sourcesr¦  µ  s   ù€ ñ $”sð  $¬dö  $ðD €Jrü   z/catalog/syncÚ
source_refÚwaitr6  c              ƒ   ó¦  ‡ ‡K  — t        ‰ «      }|r|D �cg c]  }|d   |k(  sŒ|‘Œ }}|st        dd¬«      ‚t        |«      Š|r+t        j                  j                  ˆˆ fd„«      ƒ d {  –—† S 	 t        ‰ d‰«      ƒ d {  –—† }d
|_
        |ddœS c c}w 7 Œ+7 Œ# t        j                  $ r}t        dd|j                  d	œ¬«      ‚d }~ww xY w­w)Nr’  rÌ   zNo connected source foundrT   c                  ó   •—  ‰ ‰«      S r  © )r¡  rI   s   €€r_   r  z'catalog_sync_endpoint.<locals>.<lambda>ò  s   ø€ ±c¸)³n€ rü   Úsyncé™  Úalready_running©rS   Újob_idéÊ   Úqueued©r°  rM   )rA   r   r¦  r$  r%  r&  rD   rB   ÚJobAlreadyRunningr°  rU   )	rI   r§  r¨  r6  rÕ   Úsr°  Úrunningr¡  s	   `       @r_   Úcatalog_sync_endpointr·  à  sã   ùè ø€ ô ˜)Ó$€GÙØ%ÖI˜¨¨>Ñ):¸jÓ)H’1ÐIˆÐIÙÜ¨Ð4OÔPÐPä
˜Ó
 €Cáô —_‘_×-Ñ-Ô.DÓE×EÐEðCÜ  ¨F°CÓ8×8ˆð
 €HÔØ¨Ñ1Ð1ùò% Jð Føð 9ùÜ×!Ñ!ò CÜ¨Ø&°'·.±.ñ5Bô Cð 	CûðCüsW   „C–B¤B¨ACÁ,BÁ-CÁ2B ÂBÂB ÂCÂB ÂCÂ/C	Ã	CÃCz/catalog/enrichc              ƒ   óº  ‡ ‡K  — |r,	 t         j                  j                  ˆˆ fd„«      ƒ d{  –—† S 	 t        ‰ ddˆfd	„	«      ƒ d{  –—† }d|_        |ddœS 7 Œ*# t        $ r=}t        j                  d‰ d¬«       t        dt        |«      j                  ¬«      ‚d}~ww xY w7 Œ[# t        j                  $ r}t        d
d|j                  dœ¬«      ‚d}~ww xY w­w)z©Run attribute extraction over a tenant's catalog.

    Separate from /catalog/sync because it costs money and must be re-runnable
    without re-fetching a catalog.
    c                  ó   •— t        ‰‰ «      S r  r:   )r™  rI   s   €€r_   r  z)catalog_enrich_endpoint.<locals>.<lambda>  s   ø€ ¼-È	ÐSXÓ:Y€ rü   NzEnrichment failed for %sTrP   iö  rT   Úenrichc                 ó    •— t        | ‰|¬«      S )N)r£  r:   )Útr£  r™  s     €r_   r  z)catalog_enrich_endpoint.<locals>.<lambda>  s   ø€ ¤]°1°eÀhÔ%O€ rü   r­  r®  r¯  r±  r²  r³  r  )r$  r%  r&  r\   rX   rS   r   rÊ   r¢  rD   rB   r´  r°  rU   )rI   r™  r¨  r6  r)  r°  r¶  s   ``     r_   Úcatalog_enrich_endpointr½  ý  sà   ùè ø€ ñ ð	KÜŸ™×1Ñ1Ô2YÓZ×ZÐZð
CÜ Ø�xÜOóQ÷ Qˆð €HÔØ¨Ñ1Ð1ð [ùÜò 	KÜ�L‰LÐ3°YÈˆLÔNÜ¨C¼¸R»×8IÑ8IÔJÐJûð	Kúð
Qùô ×!Ñ!ò CÜ¨Ø&°'·.±.ñ5Bô Cð 	CûðCüsl   „Cˆ&A ®A¯A ²C´B& ÁB$Á	B& ÁCÁA Á	B!Á$8BÂB!Â!CÂ$B& Â&CÂ9CÃCÃCr  )NN)F)�Úfastapir   r   r   r   r   r   r	   r
   r   Úfastapi.responsesr   Útypingr   r   Úapp.services.infra.databaser   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   Úapp.services.infra.storager   Úapp.services.infra.embeddingr    r!   r"   r#   Úapp.services.content.websiter$   Úapp.services.chat.chatr%   r…  r&   r'   Úapp.services.chat.analyticsr(   r)   Úapp.services.content.documentsr*   r+   r,   r-   r.   r/   r0   r1   Ú app.services.chat.tools_settingsr2   r3   r4   r5   r6   r7   r8   Úapp.services.infra.quotasr9   Úapp.services.enrichment.jobr;   Ú$app.services.pairing.recommendationsr<   r=   Úapp.services.catalog.syncr>   r?   Úapp.services.catalog.sourcesr@   rA   Úapp.services.infrarB   Úapp.api.catalogrC   rD   Úapp.core.configrE   r$  Úasyncior  ÚloggingrŠ   rŒ   r¬   rF   rG   rH   Ú	getLoggerr¢  rX   ÚrouterÚpostr[   r`   rz   rœ   rÁ   ÚdeleterÉ   r²   rÏ   rÖ   rÚ   rÝ   rß   rä   ræ   rè   rì   rñ   Útupler  r  r  r*  r:  r=  r@  rD  rV  rY  rb  Úintrh  rk  rz  ÚboolrŠ  r“  r˜  Úlistr¦  r·  r½  r«  rü   r_   ú<module>rÛ     s%	  ðß d× dÕ dÝ .ß !÷÷ ÷ ÷ ó õ
 7ß eÓ eÝ >Ý 4ß Qß B÷÷ ó ÷÷ ñ õ 9Ý 5ß Eß Cß @Ý #ß +Ý $Û Û Û Û Û Û 	Û ß 2Ñ 2à	ˆ×	Ñ	˜8Ó	$€á	‹€à‡�ˆ\Óð
B˜sò 
Bó ð
Bð ‡�ˆ_Óá˜#“YÙ! $›ZÙ" 4›jÙ" 3›iñ	@Øð@à�c‰]ð@ð �s‰mð@ð �
Ñò	@ó ð@ðB ‡�ˆ^Óá˜#“YÙ˜S›	Ù! $›Zñ:@Øð:@àð:@ð �c‰]ò:@ó ð:@ðx ‡�ÐÓá˜#“YÙ�C‹yÙ! $›ZñrPØðrPà	ðrPð �c‰]òrPó  ðrPðh ‡�ÐÓ ñ ˜TÐ/hÔiñØðàðð òó !ðð2 ‡�ÐÓðx ð x¨Cò xó ðxð2 ‡�ˆOÓðu¨ò uó ðuð ‡�ÐÓðx¨Cò xó  ðxð ‡�ˆNÓðt¨ò tó ðtð ‡�ˆ\Óá˜#“YÙ" 4›jÙ! $›ZÙ" 3›iñ	cØðcà�s‰mðcð �c‰]ðcð �
Ñò	có ðcð@ ‡�ˆ|Óðc¨cð c¸cò có ðcð" ‡�ˆLÓñ	^¨Sð 	^¸HÀS¹Mò 	^ó ð	^ð ‡�Ð"Ó#á˜#“YÙ˜#“YÙ˜“IñcØðcàðcð òcó $ðcð* ‡�Ð!Ó"ØFJØ6:ñb°ð bÀð bØ03òbó #ðbð> ‡�ÐÓðb°ò bó ðbð&& %ó &ð* ‡�ÐÓð;b°ò ;bó  ð;bð| ‡�Ð%Ó&Ù9=¸c»ñ (T°$ò (Tó 'ð(TðV ‡�ˆWÓá˜#“YÙ�c“Ù˜)“_Ù˜“:Ù˜$“ZÙ�V“Ù" 4›jñQØðQàðQð ðQð ð	Qð
 ðQð ðQð �s‰mòQó ðQðB ‡�ˆOÓÙ6;¸D³kñ q #ð q°ò qó ðqð ‡�ˆOÓØ>GÑX]Ð^bÓXcñ v¨ð v¸ð vÐRUò vó ðvð ‡�ˆJÓðq 3ò qó ðqð ‡�Ð,Ó-ñ ˜#“YÙ˜7“mÙ˜“9ñ	nØðnàðnð ðnð ò	nó .ðnð@ ‡�Ð+Ó,ÙAFÀsÃñ r¨3ð r¸3ò ró -ðrð ‡�Ð*Ó+ñ ˜#“YÙ�s“)ñ&wØð&wàð&wð ò&wó ,ð&wðP ‡�ÐÓÙ+0°«:ÁEÈ#ÃJñ y Sð y¸cò yó  ðyð  ‡�Ð Ó!ðy sò yó "ðyð" ‡�Ð Ó!ñ ˜EÐ/YÔZÙ˜DÐ.NÔOÙ˜$Ð,JÔKñ	%zØð%zàð%zð ð%zð ò	%zó "ð%zðN ‡�ÐÓá˜#“YÙ˜#“YÙ˜“9Ù˜“:ñ	3_Øð3_àð3_ð ð3_ð ò	3_ó ð3_ðj Ð ðK˜dð K¨sð K°tó KñB(˜4ð (¨ó (ðV ‡�ˆ_Óá˜#“YÙ $ T£
Ù�e“Øñ	2Øð2à˜‘ð2ð ð2ð ò	2ó ð2ð8 ‡�ÐÓá˜#“YÙ�u“+Ù�e“Øñ	2Øð2àð2ð ð2ð ò	2ó  ñ2rü   