Ë
    éƒtj;S  ã                   ó¸  — d dl Z d dlZd dlZd dlZd dlmZ d dlmZ d dlm	Z	m
Z
mZmZmZmZmZ d dlmZ d dlmZ d dlmZ d dl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!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/m0Z0m1Z1 d dl2m3Z3m4Z4m5Z5m6Z6m7Z7m8Z8 d dl9m:Z: d dl;m<Z< d dl=m>Z>  e"«       Z?e!dz  dz  Z@ e
ddd¬«      Ze?j‚                  j…                  d«      D � cg c]#  } | j‡                  «       sŒ| j‡                  «       ‘Œ% c} ZDej‹                  eeDdgk7  reDndgddgdg¬«       ej�                  e<«        eeG«      j‘                  «       j’                  j’                  dz  ZJej—                  d«      d€d„«       ZLej›                  de-d g¬!«      de-fd"„«       ZNejŸ                  d#e&d$g¬!«      d%e%de&fd&„«       ZPejŸ                  d'e&d$g¬!«       ed(«       ed¬)«       ed¬)«      fd*ed+eQdz  d,eQdz  de&fd-„«       ZRej§                  d.d$g¬/«       ed(«      fd+eQdeTfd0„«       ZUej›                  d1eVe)   d$g¬!«       ed2d3d4¬5«      fd6eWdeVe)   fd7„«       ZXej›                  d8e'd$g¬!«      d+eQde'fd9„«       ZYej§                  d8d$g¬/«      d+eQdeTfd:„«       ZZd;eQdeQfd<„Z[ejŸ                  d=e,d$g¬!«      d>e+de,fd?„«       Z\ G d@„ dAe«      Z]ej½                  dBd$g¬/«      dCeWd>e]deTfdD„«       Z_ G dE„ dFe«      Z` G dG„ dHe«      ZaejŸ                  dIeadJg¬!«      d>e`deafdK„«       Zbej›                  dLeVe*   dJg¬!«       eddM¬N«       edd dO¬5«       ed¬)«       ed¬)«       edPd3dQ¬5«       ed d ¬R«      fdSeeQ e	e«      f   dTeQdz  dUeWdz  dVeQdz  dWeQdz  d6eWdXeWdeVe*   fdY„«       Zcej›                  dZdJg¬/«       ed¬)«       ed¬)«       ed[d3d\¬5«      fdSeeQ e	e«      f   dVeQdz  dWeQdz  d6eWdeTf
d]„«       Zdej½                  d^dJg¬/«      dCeWd>e1dSeeQ e	e«      f   deTfd_„«       Zeej›                  d`eVe$   dJg¬!«       edPd3dQ¬5«       ed d ¬R«      fdSeeQ e	e«      f   d6eWdXeWdeVe$   fda„«       Zfd�dbeTd,eQde/fdc„ZgejŸ                  dde/deg¬!«       ed(«      fd*ede/fdf„«       Zhej›                  dge/deg¬!«      dheQde/fdi„«       ZiejŸ                  dje/deg¬!«      dheQd>e.de/fdk„«       ZjejŸ                  dle/deg¬!«      dheQde/fdm„«       ZkeJdnz  Zldefdo„Zmej›                  dpdqg¬/«      dr„ «       Znej›                  dsdqg¬/«      dt„ «       Zoej›                  dudqg¬/«      dv„ «       Zpej›                  dwdqg¬/«      dx„ «       Zqej›                  dydqg¬/«      dz„ «       Zrej›                  d{dqg¬/«      d|„ «       Zseljé                  «       r#ejë                  d} e eQel«      d¬~«      dn¬«       yyc c} w )‚é    N)ÚPath)Ú	Annotated)ÚDependsÚFastAPIÚFileÚFormÚHTTPExceptionÚQueryÚ
UploadFile)ÚCORSMiddleware)ÚFileResponse)ÚStaticFiles)Ú	BaseModelÚField)Údatabase)ÚADMIN_PASSWORDÚADMIN_USERNAMEÚcreate_access_tokenÚverify_admin_token)ÚBASE_DIRÚget_settings)Ú	AuditItemÚChatRequestÚChatResponseÚConversationDetailÚConversationMessageÚConversationSummaryÚFeedbackItemÚFeedbackRequestÚFeedbackResponseÚHealthResponseÚPdfConfirmRequestÚPdfJobResponseÚPdfRowÚReviewRequest)Úcalculate_jobÚclear_session_jobÚconfirm_jobÚcreate_job_from_uploadÚget_jobÚingest_pdf_for_chat)Úget_rag_service)Úsaas_router)Úinit_saas_tablesÚdataÚpdf_uploadszAccountants SaaS ChatbotzMMulti-tenant RAG chatbot agent for accounting and finance (Ollama + ChromaDB)z2.0.0)ÚtitleÚdescriptionÚversionÚ,Ú*T)Úallow_originsÚallow_credentialsÚallow_methodsÚallow_headersÚstaticÚstartupÚreturnc               ƒ   óô  K  — t        j                  t        j                  rt         j                  nt         j
                  d¬«       t        j                  d«      j                  t        j                  rt         j                  nt         j
                  «       t        j                  r$t        j                  d«      j                  d«       t        j                  «        t        «        	 ddlm}   | «        	 t        j"                  «        t        j$                  slt&        j)                  «       rXt&        j+                  «       D ]A  }|j-                  «       sŒ|j.                  j1                  «       dv sŒ1	 |j3                  «        ŒC t7        «       }t9        j:                  t9        j<                  |j>                  «      «       dd„}t9        j:                  t9        j<                  |«      «       y # t        $ r. t        j                  t        «      j!                  dd¬	«       Y �Œ0w xY w# t        $ r. t        j                  t        «      j!                  d
d¬	«       Y �ŒUw xY w# t4        $ r Y �Œ-w xY w­w)Nz0%(asctime)s %(levelname)s [%(name)s] %(message)s)ÚlevelÚformatz
app.rag.kbzRAG_DEBUG enabledr   )Úensure_admin_accountzFailed to ensure admin accountT©Úexc_infozPostgres pool warm-up failed>   ú.jsonú.pdfc                  óä   — 	 ddl m}   | «       }t        j                  t        «      j                  d|«       y # t        $ r- t        j                  t        «      j                  dd¬«       Y y w xY w)Nr   )Úwarm_local_statement_cachesz Warmed %s statement PDF cache(s)zStatement cache warm-up failedTrA   )Úapp.saas.kb_answerrF   ÚloggingÚ	getLoggerÚ__name__ÚinfoÚ	ExceptionÚwarning)rF   Úns     ú$/var/www/html/CA-Chatbot/app/main.pyÚ_warm_statementsz!startup.<locals>._warm_statementsg   sa   € ð	aÝFá+Ó-ˆAÜ×ÑœhÓ'×,Ñ,Ð-OÐQRÕSøÜò 	aÜ×ÑœhÓ'×/Ñ/Ð0PÐ[_Ð/Ö`ð	aús   ‚69 ¹3A/Á.A/©r<   N) rH   ÚbasicConfigÚsettingsÚ	rag_debugÚDEBUGÚINFOrI   ÚsetLevelrK   r   Úinit_dbr.   Úapp.saas.dbr@   rL   rJ   rM   Úwarm_pg_poolÚpersist_user_dataÚ
UPLOAD_DIRÚexistsÚiterdirÚis_fileÚsuffixÚlowerÚunlinkÚOSErrorr,   ÚasyncioÚcreate_taskÚ	to_threadÚwarmup)r@   ÚpathÚservicerP   s       rO   r;   r;   D   sÉ  è ø€ ä×ÑÜ'×1Ò1Œg�mŠm´w·|±|ØAõô ×Ñ�lÓ#×,Ñ,Ü!×+Ò+Œ�Š´·±ôô ×ÒÜ×Ñ˜,Ó'×,Ñ,Ð-@ÔAä×ÑÔÜÔð]Ý4áÔð[Ü×ÑÔô ×%Ò%¬*×*;Ñ*;Ô*=Ü×&Ñ&Ö(ˆDØ�|‰|�~ $§+¡+×"3Ñ"3Ó"5Ð9JÒ"JðØ—K‘K•Mð )ô Ó€GÜ×Ñœ×)Ñ)¨'¯.©.Ó9Ô:óaô ×Ñœ×)Ñ)Ð*:Ó;Õ<øô5 ò ]Ü×Ñœ(Ó#×+Ñ+Ð,LÐW[Ð+×\ð]ûô ò [Ü×Ñœ(Ó#×+Ñ+Ð,JÐUYÐ+×Zð[ûô ò Úðüst   ‚C*I8Ã-G4 Ã;H. ÄAI8ÅI8Å9I(Æ	A+I8Ç43H+È'I8È*H+È+I8È.3I%É!I8É$I%É%I8É(	I5É1I8É4I5É5I8z/healthÚsystem)Úresponse_modelÚtagsc               ƒ   óÂ   K  — t        «       } t        d| j                  «       ƒ d {  –—† t        j                  | j                  «       | j                  «       ¬«      S 7 Œ6­w)NÚok)ÚstatusÚollama_reachableÚollama_modelÚvector_store_readyÚdocument_count)r,   r!   rp   rS   rq   rr   rs   )ri   s    rO   Úhealthrt   u   sQ   è ø€ äÓ€GÜØØ&×7Ñ7Ó9×9Ü×*Ñ*Ø"×5Ñ5Ó7Ø×-Ñ-Ó/ôð à9ús   ‚$A¦A
§7Az/chatÚchatÚrequestc              ƒ   ó¼  K  — t        «       }|j                  «       st        dd¬«      ‚|j                  «       ƒ d {  –—† s0t        ddt        j
                  › dt        j                  › d�¬«      ‚	 t        j                  |j                  | j                  | j                  «      ƒ d {  –—† S 7 Œr7 Œ# t        $ r}t        dt        |«      ¬«      |‚d }~ww xY w­w)Né÷  z4Vector store is empty. Run: python scripts/ingest.py©Ústatus_codeÚdetailzOllama is not reachable at z. Start Ollama and pull Ú.)r,   rr   r	   rp   rS   Úollama_base_urlrq   rd   rf   ru   ÚquestionÚ
session_idÚRuntimeErrorÚstr)rv   ri   Úexcs      rO   ru   ru   ƒ   s×   è ø€ äÓ€Gà×%Ñ%Ô'ÜØØIô
ð 	
ð
 ×)Ñ)Ó+×+Ð+ÜØØ0´×1IÑ1IÐ0Jð K,Ü,4×,AÑ,AÐ+BÀ!ðEô
ð 	
ðGÜ×&Ñ& w§|¡|°W×5EÑ5EÀw×GYÑGYÓZ×ZÐZð ,øð [ùÜò GÜ¨´C¸³HÔ=À3ÐFûðGüsE   ‚;C½B0¾4CÁ38B4 Â+B2Â,B4 Â/CÂ2B4 Â4	CÂ=CÃCÃCz/chat/upload-pdf.)ÚdefaultÚfiler   Úmessagec           
   ƒ   óÒ  K  — | j                   r)| j                   j                  «       j                  d«      st        dd¬«      ‚| j	                  «       ƒ d{  –—† }|st        dd¬«      ‚t        |«      dkD  rt        dd¬«      ‚|xs t        t        j                  «       «      }d}	 t        j                  rSt        j                  d	d	¬
«       t        t        j                  «       › d| j                   › �z  }|j                  |«       n^t        j                  dd¬«      }	 |j!                  |«       |j#                  «        t%        |j&                  «      }|j)                  «        t+        j,                  t.        || j                   |«      ƒ d{  –—† }	 |�#t        j                  s	 |j3                  d	¬«       |xs dj7                  «       }	|j9                  d«      xs i j9                  d«      }
t;        |j9                  d«      xs7 |j9                  d«      xs$ |j9                  d«      xs |j9                  d«      «      }|so|j9                  d«      xs d| j                   › d�}t=        j>                  d| j                   › �|dd|¬«      }tA        |g ||xs dd|j9                  d«      |
¬ «      S |	xs d!}tC        «       }	 t+        j,                  |jD                  ||«      ƒ d{  –—† }tA        |jF                  |jH                  ||jJ                  d|j9                  d«      |
¬ «      S 7 �ŒÝ# |j)                  «        w xY w7 �Œ¸# t0        $ r}t        dd|› �¬«      |‚d}~ww xY w# t4        $ r Y �Œ¾w xY w# |�4t        j                  s#	 |j3                  d	¬«       w # t4        $ r Y w w xY ww w xY w7 ŒË# tL        $ r}t        d"t        |«      ¬«      |‚d}~ww xY w­w)#a  Upload a PDF into the chat session: extract, calculate, and answer-ready.

    Optional ``message`` is answered in the same turn using the uploaded statement.
    By default the PDF is processed from a temp file and deleted immediately (no disk retention).
    rD   é�  úPlease upload a .pdf filery   Nú
Empty fileé  @úPDF too large (max 20 MB)T©ÚparentsÚexist_okÚ_F©r`   Údeleteéô  zPDF ingest failed: ©Ú
missing_okÚ ÚcalculationÚsummaryÚrowsÚdocument_textÚtext_previewÚerrorzCould not read content from r|   z[uploaded PDF] z[]r   )r~   ÚanswerÚcitations_jsonÚduration_msr   ÚpdfÚjob_id)rœ   Ú	citationsr   Úlog_idÚmodeÚ
pdf_job_idÚpdf_summaryz=What is this PDF about? Summarize what the document contains.rx   )'Úfilenamera   Úendswithr	   ÚreadÚlenr�   ÚuuidÚuuid4rS   r[   r\   ÚmkdirÚwrite_bytesÚtempfileÚNamedTemporaryFileÚwriteÚflushr   ÚnameÚcloserd   rf   r+   rL   rb   rc   ÚstripÚgetÚboolr   Ú	log_queryr   r,   ru   rœ   r¡   r¢   r€   )r„   r   r…   ÚrawÚsidÚsavedÚtmpÚjobr‚   Ú	user_noter—   Úhas_contentrœ   r¢   r~   ri   Úresps                    rO   Úchat_upload_pdfrÀ   š   s…  è ø€ ð �=Š= §¡× 3Ñ 3Ó 5× >Ñ >¸vÔ FÜ¨Ð4OÔPÐPà—	‘	“×
€CÙÜ¨°LÔAÐAÜ
ˆ3ƒxÐ"Ò"Ü¨Ð4OÔPÐPà
Ò
)œœDŸJ™J›LÓ)€CØ€EðÜ×%Ò%Ü×Ñ T°DÐÔ9Ü¤D§J¡J£L >°°4·=±=°/Ð!BÑBˆEØ×Ñ˜cÕ"ä×-Ñ-°VÀEÔJˆCðØ—	‘	˜#”Ø—	‘	”Ü˜SŸX™X›�à—	‘	”ä×%Ñ%Ô&9¸5À$Ç-Á-ÐQTÓU×U‰ð
 Ð¤X×%?Ò%?ðØ—‘¨�Ô-ð ’˜B×%Ñ%Ó'€IØ�w‰w�}Ó%Ò+¨×0Ñ0°Ó;€GÜØ�‰�‹ò 	#Ø�7‰7�=Ó!ò	#à�7‰7�?Ó#ò	#ð �7‰7�>Ó"ó	€Kñ Ø—‘˜Ó!ÒTÐ'CÀDÇMÁMÀ?ÐRSÐ%TˆÜ×#Ñ#Ø& t§}¡} oÐ6ØØØØô
ˆô ØØØØ’>˜TØØ—w‘w˜xÓ(Øô
ð 	
ð Ò[Ð[€HÜÓ€GðGÜ×&Ñ& w§|¡|°X¸sÓC×CˆÜØ—;‘;Ø—n‘nØØ—;‘;ØØ—w‘w˜xÓ(Øô
ð 	
ðE úð( —	‘	•úàUúÜò ZÜ¨Ð6IÈ#ÈÐ4OÔPÐVYÐYûðZûô ò Úðûð Ð¤X×%?Ò%?ðØ—‘¨�Õ-øÜò Ùðúð &@ÐúðL Dùô ò GÜ¨´C¸³HÔ=À3ÐFûðGüsè   ‚AO'ÁL5ÁAO'Â+A:M Ä&6L8 Å9M ÆMÆM ÆO'Æ.M2 Ç DO'Ë$N? Ë1N=Ë2AN? Ì4O'Ì8M
Í
M Í	M/ÍM*Í*M/Í/N Í2	M?Í;O'Í>M?Í?O'ÎN:ÎN)Î(N:Î)	N5Î2N:Î4N5Î5N:Î:O'Î=N? Î?	O$ÏOÏO$Ï$O'z	/chat/pdf)rl   c              ƒ   ó*   K  — t        | «       d| dœS ­w)zIDetach the active uploaded PDF from a chat session so normal RAG resumes.T©rn   r   )r'   ©r   s    rO   Úchat_clear_pdfrÄ   ÷   s   è ø€ ô �jÔ!Ø jÑ1Ð1ùs   ‚z/conversationsé(   é   éd   )rƒ   ÚgeÚleÚlimitc              ƒ   ór   K  — t        j                  | ¬«      }|D �cg c]  }t        di |¤Ž‘Œ c}S c c}w ­w)N©rÊ   © )r   Úlist_conversationsr   )rÊ   r˜   Úrs      rO   ÚconversationsrÐ      s7   è ø€ ä×&Ñ&¨UÔ3€DÙ.2Ó3©d¨ÔÑ$ !Ó$¨dÑ3Ð3ùÒ3ùs   ‚7�2¯7z/conversations/{session_id}c              ƒ   óD  K  — t        j                  | «      }|st        dd¬«      ‚d}t        j                  d¬«      }|D ]!  }|d   | k(  sŒ|j	                  d«      xs |} n t        |d	   d
   «      }t        | ||D �cg c]  }t        di |¤Ž‘Œ c}¬«      S c c}w ­w)Né”  zConversation not foundry   ÚChatéÈ   rÌ   Úidr1   r   r~   )rÕ   r1   ÚmessagesrÍ   )r   Úget_session_messagesr	   rÎ   rµ   Ú_title_fallbackr   r   )r   rÖ   r1   ÚconvosÚcÚms         rO   Úconversation_detailrÜ     s¯   è ø€ ä×,Ñ,¨ZÓ8€HÙÜ¨Ð4LÔMÐMØ€EÜ×(Ñ(¨sÔ3€FÛˆØˆT‰7�jÓ Ø—E‘E˜'“NÒ+ eˆEÙð ô
   ¨¡¨JÑ 7Ó8ˆÜØØÙ4<Ó=±H¨qÔ%Ñ*¨Ó*°HÑ=ôð ùò >ùs   ‚A
B Á4B ÂBÂB c              ƒ   ó>   K  — t        j                  | «       d| dœS ­w)NTrÂ   )r   Údelete_conversationrÃ   s    rO   Úconversation_deleterß     s   è ø€ ä× Ñ  Ô,Ø jÑ1Ð1ùs   ‚r~   c                 ó
  — | xs dj                  «       }|j                  d«      r$|j                  dd«      j                  «       xs d}dj                  |j	                  «       «      }t        |«      dkD  r|d d dz   S |xs dS )	Nr•   z[uploaded PDF]z
PDF uploadÚ é0   é-   u   â€¦rÓ   )r´   Ú
startswithÚreplaceÚjoinÚsplitr©   )r~   Útexts     rO   rØ   rØ      sx   € ØŠN˜×!Ñ!Ó#€DØ‡�Ð'Ô(Ø�|‰|Ð,¨bÓ1×7Ñ7Ó9ÒI¸\ˆØ�8‰8�D—J‘J“LÓ!€DÜ"% d£)¨b¢.ˆD��"ˆI˜ÑÐF°t²~¸vÐFó    z	/feedbackÚbodyc           
   ƒ   ó  K  — t        j                  | j                  | j                  | j                  | j
                  | j                  | j                  | j                  | j                  ¬«      }t        |d| j                  ¬«      S ­w)zMSave thumbs up/down into SQLite for later model training / preference tuning.)r¢   r   r~   rœ   ÚratingÚ
correctionÚ
message_idr£   zThank you for your feedback!)Úfeedback_idr…   rì   )r   Úsave_feedbackr¢   r   r~   rœ   rì   rí   rî   r£   r    )rê   rï   s     rO   Úsubmit_feedbackrñ   *  sl   è ø€ ô ×(Ñ(Ø�{‰{Ø—?‘?Ø—‘Ø�{‰{Ø�{‰{Ø—?‘?Ø—?‘?Ø�Y‰Yô	€Kô ØØ.Ø�{‰{ôð ùs   ‚BBc                   ó2   — e Zd ZU  edd¬«      Zedz  ed<   y)ÚFeedbackCorrectionRequestNi   )rƒ   Ú
max_lengthrí   )rJ   Ú
__module__Ú__qualname__r   rí   r�   Ú__annotations__rÍ   ré   rO   ró   ró   =  s   … Ù"¨4¸DÔA€J��d‘
ÔAré   ró   z/feedback/{feedback_id}rï   c              ƒ   óT   K  — t        j                  | |j                  «       d| dœS ­w)NT)rn   rï   )r   Úupdate_feedback_correctionrí   )rï   rê   s     rO   Úpatch_feedback_correctionrú   A  s'   è ø€ ô
 ×'Ñ'¨°T·_±_ÔEØ {Ñ3Ð3ùó   ‚&(c                   ó"   — e Zd ZU eed<   eed<   y)ÚLoginRequestÚusernameÚpasswordN)rJ   rõ   rö   r�   r÷   rÍ   ré   rO   rý   rý   K  s   … ØƒMØ„Mré   rý   c                   ó&   — e Zd ZU eed<   dZeed<   y)ÚTokenResponseÚaccess_tokenÚbearerÚ
token_typeN)rJ   rõ   rö   r�   r÷   r  rÍ   ré   rO   r  r  P  s   … ØÓØ€J�Ôré   r  z/admin/loginÚadminc              ƒ   ó¶   K  — | j                   t        k7  s| j                  t        k7  rt	        dd¬«      ‚t        d| j                   i«      }t        |¬«      S ­w)Ni‘  zInvalid credentialsry   Úsub)r  )rþ   r   rÿ   r   r	   r   r  )rê   Útokens     rO   Úadmin_loginr	  U  sI   è ø€ à‡}�}œÒ&¨$¯-©-¼>Ò*IÜ¨Ð4IÔJÐJÜ ¨¯©Ð 6Ó7€EÜ eÔ,Ð,ùs   ‚AAz/admin/feedbackz^(up|down)$)rƒ   Úpatterné   é2   r’   )rƒ   rÈ   Ú_adminrì   ÚreviewedÚorg_idÚagent_idÚoffsetc              ƒ   ó|   K  — t        j                  ||||||¬«      }|D �cg c]  }t        di |¤Ž‘Œ c}S c c}w ­w)N)rÊ   r  rì   r  r  r  rÍ   )r   Úget_feedbackr   )	r  rì   r  r  r  rÊ   r  r˜   rÏ   s	            rO   Úlist_feedbackr  _  sL   è ø€ ô × Ñ ØØØØØØô€Dñ (,Ó,¡t !ŒLÑ˜1Ó tÑ,Ð,ùÒ,ùs   ‚ <¢7´<z/admin/feedback/trainingiˆ  i N  c              ƒ   ó¤  K  — t        j                  |||¬«      }|D �cg c]u  }|d   |d   |d   |d   |j                  d«      |j                  d«      |j                  d«      |j                  d	«      |j                  d
«      |j                  d«      dœ
‘Œw }}t        |«      t	        d„ |D «       «      t	        d„ |D «       «      |dœS c c}w ­w)z;Export rated Q/A pairs for preference tuning / fine-tuning.)r  r  rÊ   rÕ   r~   rœ   rì   rí   r£   Ú
model_namer  r  Ú
created_at)
rÕ   r~   rœ   rì   rí   r£   r  r  r  r  c              3   ó2   K  — | ]  }|d    dk(  sŒd–— Œ y­w)rì   ÚuprÆ   NrÍ   ©Ú.0Úps     rO   Ú	<genexpr>z+export_training_feedback.<locals>.<genexpr>Ž  s   è ø€ Ð:™U˜ a¨¡k°TÓ&9”!™Uùó   ‚�c              3   ó2   K  — | ]  }|d    dk(  sŒd–— Œ y­w)rì   ÚdownrÆ   NrÍ   r  s     rO   r  z+export_training_feedback.<locals>.<genexpr>�  s   è ø€ Ð>™u˜!¨¨(©°vÓ(=”A™uùr  )Úcountr  r   Úpairs)r   Úget_training_pairsrµ   r©   Úsum)r  r  r  rÊ   r˜   rÏ   r"  s          rO   Úexport_training_feedbackr%  t  sÝ   è ø€ ô ×&Ñ&¨f¸xÈuÔU€Dñ óñ ˆAð �D‘'Ø˜*™Ø˜‘kØ˜‘kØŸ%™% Ó-Ø—E‘E˜&“MØŸ%™% Ó-Ø—e‘e˜H“oØŸ™˜jÓ)ØŸ%™% Ó-ó	
ð ð 
ð ô  �U“ÜÑ:™UÓ:Ó:ÜÑ>™uÓ>Ó>Øñ	ð ùòùs   ‚CŸA:CÂ7Cz/admin/feedback/{feedback_id}c              ƒ   óz   K  — t        j                  | |j                  «       ddddœ}| ||j                     dœS ­w)NÚpendingÚapprovedÚ	dismissed)r   rÆ   r  )rï   r  )r   Úupdate_feedback_reviewr  )rï   rê   r  Úlabelss       rO   Úreview_feedbackr,  “  s<   è ø€ ô ×#Ñ# K°·±Ô?Ø˜z¨kÑ:€FØ&°F¸4¿=¹=Ñ4IÑJÐJùs   ‚9;z/admin/auditc              ƒ   ót   K  — t        j                  ||¬«      }|D �cg c]  }t        di |¤Ž‘Œ c}S c c}w ­w)N)rÊ   r  rÍ   )r   Úget_audit_logr   )r  rÊ   r  r˜   rÏ   s        rO   Ú	audit_logr/     s8   è ø€ ô ×!Ñ!¨°fÔ=€DÙ$(Ó)¡D˜qŒI‰N˜‹N DÑ)Ð)ùÒ)ùs   ‚8ž3°8r¼   c                 ó†  — t        | d   | d   | d   | d   | d   | j                  dd«      | d   | j                  d	«      xs g D �cg c]  }t        di |¤Ž‘Œ c}| j                  d
«      xs d| j                  d«      xs g | j                  d«      xs i | j                  dd«      | j                  d«      |¬«      S c c}w )Nr    r¦   Úsource_typeÚtrustedÚneeds_confirmÚ	confirmedFro   r˜   rš   r•   ÚwarningsÚ
validationÚocr_usedr–   )r    r¦   r1  r2  r3  r4  ro   r˜   rš   r5  r6  r7  r–   r…   rÍ   )r#   rµ   r$   )r¼   r…   rÏ   s      rO   Ú_job_responser8  ¬  sÈ   € ÜØ�8‰}Ø�Z‘Ø˜Ñ&Ø�I‘Ø˜/Ñ*Ø—'‘'˜+ uÓ-Ø�8‰}Ø#&§7¡7¨6£?Ò#8°bÐ#8Ó9Ñ#8˜aŒf‰k�q‹kÐ#8Ñ9Ø—W‘W˜^Ó,Ò2°Ø—‘˜Ó$Ò*¨Ø—7‘7˜<Ó(Ò.¨BØ—‘˜ UÓ+Ø—G‘G˜MÓ*Øôð ùò :s   ÁB>z/pdf/extractrŸ   c              ƒ   ó‚  K  — | j                   r)| j                   j                  «       j                  d«      st        dd¬«      ‚| j	                  «       ƒ d {  –—† }|st        dd¬«      ‚t        |«      dkD  rt        dd¬«      ‚d }	 t        j                  rSt        j                  dd¬	«       t        t        j                  «       › d
| j                   › �z  }|j                  |«       n^t        j                  dd¬«      }	 |j                  |«       |j!                  «        t#        |j$                  «      }|j'                  «        t)        j*                  t,        || j                   «      ƒ d {  –—† }	 |�#t        j                  s	 |j1                  d¬«       |d   r|d   sdnd}t5        ||¬«      S 7 �Œa# |j'                  «        w xY w7 Œ]# t.        $ r}t        dd|› �¬«      |‚d }~ww xY w# t2        $ r Y Œbw xY w# |�4t        j                  s#	 |j1                  d¬«       w # t2        $ r Y w w xY ww w xY w­w)NrD   r‡   rˆ   ry   r‰   rŠ   r‹   TrŒ   r�   Fr�   r’   zExtraction failed: r“   r2  r3  u;   Digital PDF extracted and validated â€” ready to calculate.zSReview and confirm amounts before calculating (required for OCR / unverified data).©r…   )r¦   ra   r§   r	   r¨   r©   rS   r[   r\   r¬   rª   r«   r­   r®   r¯   r°   r±   r   r²   r³   rd   rf   r)   rL   rb   rc   r8  )r„   r¸   rº   r»   r¼   r‚   Úmsgs          rO   Úpdf_extractr<  ¿  sÿ  è ø€ à�=Š= §¡× 3Ñ 3Ó 5× >Ñ >¸vÔ FÜ¨Ð4OÔPÐPà—	‘	“×
€CÙÜ¨°LÔAÐAÜ
ˆ3ƒxÐ"Ò"Ü¨Ð4OÔPÐPà€EðÜ×%Ò%Ü×Ñ T°DÐÔ9Ü¤D§J¡J£L >°°4·=±=°/Ð!BÑBˆEØ×Ñ˜cÕ"ä×-Ñ-°VÀEÔJˆCðØ—	‘	˜#”Ø—	‘	”Ü˜SŸX™X›�à—	‘	”ä×%Ñ%Ô&<¸eÀTÇ]Á]ÓS×S‰ð Ð¤X×%?Ò%?ðØ—‘¨�Ô-ð ˆyŠ> # oÒ"6ñ 	Fàbð ô
 ˜ cÔ*Ð*ðI úð& —	‘	•úàSùÜò ZÜ¨Ð6IÈ#ÈÐ4OÔPÐVYÐYûðZûô ò Ùðûð Ð¤X×%?Ò%?ðØ—‘¨�Õ-øÜò Ùðúð &@Ðüs¯   ‚AH?ÁF9Á0H?Â
A:G Ä6F< Ä;8G Å3GÅ4G Å9H?ÆG5 ÆH?Æ<GÇG Ç	G2ÇG-Ç-G2Ç2H Ç5	HÇ>H?È HÈH?ÈH<ÈH+È*H<È+	H7È4H<È6H7È7H<È<H?z/pdf/{job_id}r    c              ƒ   óT   K  — t        | «      }|st        dd¬«      ‚t        |«      S ­w)NrÒ   úJob not foundry   )r*   r	   r8  )r    r¼   s     rO   Úpdf_getr?  ë  s*   è ø€ ä
�&‹/€CÙÜ¨°OÔDÐDÜ˜ÓÐùrû   z/pdf/{job_id}/confirmc              ƒ   óô   K  — 	 t        | |j                  D �cg c]  }|j                  «       ‘Œ c}«      }|j                  d«      rdnd}t        ||¬«      S c c}w # t        $ r t	        dd¬«      d ‚w xY w­w)NrÒ   r>  ry   r2  z)Amounts confirmed. You can calculate now.uD   Confirmation saved but validation failed â€” fix highlighted issues.r:  )r(   r˜   Ú
model_dumpÚKeyErrorr	   rµ   r8  )r    rê   rÏ   r¼   r;  s        rO   Úpdf_confirmrC  ó  s€   è ø€ ðOÜ˜&¸4¿9º9Ó"E¹9°a 1§<¡<¥>¸9Ñ"EÓFˆð
 �7‰7�9Ôñ 	4àSð ô
 ˜ cÔ*Ð*ùò #FøÜò OÜ¨°OÔDÈ$ÐNðOüs+   ‚A8„A ˜A
¯A ¶"A8ÁA ÁA5Á5A8z/pdf/{job_id}/calculatec              ƒ   ó0  K  — 	 t        j                  t        | «      ƒ d {  –—† }t        |d¬«      S 7 Œ# t        $ r t	        dd¬«      d ‚t
        $ r}t	        dt        |«      ¬«      |‚d }~wt        $ r}t	        dt        |«      ¬«      |‚d }~ww xY w­w)NrÒ   r>  ry   i“  r‡   z,Calculation complete (deterministic totals).r:  )	rd   rf   r&   rB  r	   ÚPermissionErrorr�   Ú
ValueErrorr8  )r    r¼   r‚   s      rO   Úpdf_calculaterG    s�   è ø€ ðGÜ×%Ñ%¤m°VÓ<×<ˆô ˜Ð&TÔUÐUð =ùÜò OÜ¨°OÔDÈ$ÐNÜò GÜ¨´C¸³HÔ=À3ÐFûÜò GÜ¨´C¸³HÔ=À3ÐFûðGüs=   ‚B„5 ¡3¢5 ¦B³5 µBÁA+Á+BÁ7BÂBÂBÚstudioc                  ód   — t         dz  } | j                  «       st        dd¬«      ‚t        | «      S )Nz
index.htmlrÒ   zEStudio UI not built. Run: cd frontend && npm install && npm run buildry   )Ú
STUDIO_DIRr]   r	   r   ©Úindexs    rO   Ú_studio_indexrM    s5   € Ü˜Ñ%€EØ�<‰<Œ>ÜØØZô
ð 	
ô ˜ÓÐré   z/adminÚuic               ƒ   ól   K  — t         dz  } | j                  «       st        dd¬«      ‚t        | «      S ­w)Nz
admin.htmlrÒ   zAdmin UI not foundry   ©Ú
STATIC_DIRr]   r	   r   ©Úpages    rO   Úadmin_uirT    s2   è ø€ ä˜Ñ$€DØ�;‰;Œ=Ü¨Ð4HÔIÐIÜ˜ÓÐùó   ‚24z/pdfc               ƒ   ól   K  — t         dz  } | j                  «       st        dd¬«      ‚t        | «      S ­w)Nzpdf.htmlrÒ   zPDF UI not foundry   rP  rR  s    rO   Úpdf_uirW  %  s2   è ø€ ä˜
Ñ"€DØ�;‰;Œ=Ü¨Ð4FÔGÐGÜ˜ÓÐùrU  Ú/c               ƒ   ó   K  — t        «       S ­w©N©rM  rÍ   ré   rO   Úchat_uir\  -  ó   è ø€ ä‹?Ðùó   ‚z/appc               ƒ   ó   K  — t        «       S ­wrZ  r[  rÍ   ré   rO   Úapp_uir`  2  r]  r^  z/chat-classicc               ƒ   ól   K  — t         dz  } | j                  «       st        dd¬«      ‚t        | «      S ­w)Nz	chat.htmlrÒ   zChat UI not foundry   rP  rK  s    rO   Úclassic_chat_uirb  7  s2   è ø€ ä˜Ñ$€EØ�<‰<Œ>Ü¨Ð4GÔHÐHÜ˜ÓÐùrU  z/legacy-studioc               ƒ   ól   K  — t         dz  } | j                  «       st        dd¬«      ‚t        | «      S ­w)Nz	saas.htmlrÒ   zLegacy SaaS UI not foundry   rP  rR  s    rO   Úlegacy_saas_uird  ?  s2   è ø€ ä˜Ñ#€DØ�;‰;Œ=Ü¨Ð4NÔOÐOÜ˜ÓÐùrU  z/studio)Ú	directoryÚhtml)r²   rQ   )r•   )vrd   rH   r®   rª   Úpathlibr   Útypingr   Úfastapir   r   r   r   r	   r
   r   Úfastapi.middleware.corsr   Úfastapi.responsesr   Úfastapi.staticfilesr   Úpydanticr   r   Úappr   Úapp.authr   r   r   r   Ú
app.configr   r   Úapp.models.schemasr   r   r   r   r   r   r   r   r    r!   r"   r#   r$   r%   Úapp.pdfr&   r'   r(   r)   r*   r+   Úapp.rag.chainr,   Úapp.saasr-   rY   r.   rS   r\   Úcors_originsrç   r´   ÚoriginsÚadd_middlewareÚinclude_routerÚ__file__ÚresolveÚparentrQ  Úon_eventr;   rµ   rt   Úpostru   r�   rÀ   r‘   ÚdictrÄ   ÚlistÚintrÐ   rÜ   rß   rØ   rñ   ró   Úpatchrú   rý   r  r	  r  r%  r,  r/  r8  r<  r?  rC  rG  rJ  rM  rT  rW  r\  r`  rb  rd  r]   Úmount)Úos   0rO   Ú<module>r„     sR  ðÛ Û Û Û Ý Ý ç R× RÑ RÝ 2Ý *Ý +ß %å ß \Ó \ß -÷÷ ÷ ÷ ÷ ÷ õ *Ý  Ý (á‹>€Ø˜Ñ Ñ.€
áØ
$Ø_Øô€ð '×3Ñ3×9Ñ9¸#Ô>Ó
LÑ>˜À!Ç'Á'Å)ˆ1�7‰7�9Ð>Ñ
L€Ø × Ñ ØØ$¨¨Ò-‘'°C°5ØØ�%Ø�%ð ô ð × Ñ �;Ô á�(‹^×#Ñ#Ó%×,Ñ,×3Ñ3°hÑ>€
ð ‡�ˆiÓò+=ó ð+=ð` ‡�ˆ >¸¸
€ÓCð�nò ó Dðð ‡�ˆ' ,°f°X€Ó>ðG˜ð G¨ò Gó ?ðGð, ‡�Ð
¨\ÀÀ€ÓIá˜C“yÙ!¨$Ô/Ù tÔ,ñYGØ
ðYGà�d‘
ðYGð �4‰ZðYGð ò	YGó JðYGðx ‡�ˆK˜v˜h€Ó'Ù+0°«:ñ 2 Sð 2¸$ò 2ó (ð2ð ‡�Ð	¨$Ð/BÑ*CÈ6È(€ÓSÙ%*°2¸!ÀÔ%Dñ 4˜sð 4ÈÐNaÑIbò 4ó Tð4ð
 ‡�Ð	&Ð7IÐQWÐPX€ÓYð¨#ð Ð2Dò ó Zðð& ‡�Ð)°°€Ó9ð2¨#ð 2°$ò 2ó :ð2ð
G˜cð G có Gð ‡�ˆ+Ð&6¸f¸X€ÓFð ð Ð4Dò ó Gðô$B 	ô Bð ‡�Ð$¨F¨8€Ó4ð4Øð4à
#ð4ð 
ò4ó 5ð4ô�9ô ô
�Iô ð
 ‡�ˆ.¨¸g¸Y€ÓGð-˜Lð -¨]ò -ó Hð-ð ‡�Ð	¨4°Ñ+=ÀWÀI€ÓNñ  t°]ÔCÙ ¨°!¸Ô:Ù tÔ,Ù ¨Ô.Ù˜r a¨CÔ0Ù  aÔ(ñ-Ø�c™7Ð#5Ó6Ð6Ñ7ð-à�$‰Jð-ð �D‰jð-ð �$‰Jð	-ð
 �D‰jð-ð ð-ð ð-ð 
ˆ,Ñò-ó Oð-ð( ‡�Ð	#¨7¨)€Ó4ñ  tÔ,Ù ¨Ô.Ù˜t¨¨eÔ4ñ	Ø�c™7Ð#5Ó6Ð6Ñ7ðà�$‰Jðð �D‰jðð ð	ð
 
òó 5ðð< ‡�Ð*°'°€Ó;ðKØðKà
ðKð �c™7Ð#5Ó6Ð6Ñ7ðKð 
ò	Kó <ðKð ‡�ˆ¨¨Y©¸w¸i€ÓHñ ˜r a¨CÔ0Ù  aÔ(ñ*Ø�c™7Ð#5Ó6Ð6Ñ7ð*àð*ð ð*ð 
ˆ)�_ò	*ó Ið*ñ�tð  cð °>ó ð& ‡�ˆ.¨¸u¸g€ÓFÙ)-¨c«ñ (+˜Jð (+°~ò (+ó Gð(+ðV ‡�ˆ¨¸u¸g€ÓFð˜#ð  .ò ó Gðð ‡�Ð
!°.ÈÀw€ÓOð
+˜cð 
+Ð):ð 
+¸~ò 
+ó Pð
+ð ‡�Ð
#°NÈ%È€ÓQð	V ð 	V¨ò 	Vó Rð	Vð ˜(Ñ"€
ð�|ó ð ‡�ˆ˜˜€Óñó  ðð ‡�ˆ�t�f€Óñó ðð ‡�ˆ�D�6€Óñó ðð ‡�ˆ�t�f€Óñó ðð ‡�ˆ ˜v€Ó&ñó 'ðð ‡�Ð	  €Ó'ñó (ðð ×ÑÔØ‡I�Iˆi™©s°:«ÀTÔJÐQY€IÕZð ùòe Ms   ÃWÃ2W