o
    I)j5                    @   sC  U d dl Z d dlZd dlZd dlZd dlZd dlZd dlZd dlZd dlZd dl	m
Z
m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 d dlmZmZmZmZ d dlmZ d dlZd dlZ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* d dl+m,Z,m-Z-m.Z. d dl/m0Z0 d dl1m2Z2m3Z3 d dl4m5Z5 d dl6m7Z7 d dl8m9Z9 d dl:Z;d dl<m=Z= d dl>m?Z? d dl@mAZA d dlBmCZC d dlDmEZE d dlFmGZGmHZHmIZImJZJ d dlKmLZL d dlMmNZN d dlOmPZPmQZQ d dlRmSZSmTZTmUZUmVZV d dlWmXZX ejYejZdd e[e\Z]dee^ef fdd Z_e_ Z`e5e`Z4e9e`Z8eSe8ZReXe8ZWeLe`ZKe=e8Zae?e`ZbeAe`ZceCe`ZBi Zdee^ee^ef f eed!< d"ZfG d#d$ d$e2ZgG d%d& d&e2ZhG d'd( d(e2ZiG d)d* d*e2Zjd+ZkG d,d- d-e2ZlG d.d/ d/e2ZmG d0d1 d1e2ZnG d2d3 d3e2ZoG d4d5 d5e2ZpG d6d7 d7e2ZqG d8d9 d9e2Zrd:ee^ de^fd;d<Zsd=ee^ef d>e^detfd?d@Zue"ddAfd:ee^ dee^ef fdBdCZve"ddAfd:ee^ deee^ef  fdDdEZwd>e^d=ee^ef dee^ef fdFdGZxd%dIee^ dJetdetfdKdLZyd=ee^ef d>e^detfdMdNZzd>e^d=ee^ef ddfdOdPZ{d>e^d=ee^ef ddfdQdRZ|d>e^d=ee^ef dSe^ddfdTdUZ}d=eee^ef  d>e^de~fdVdWZd=eee^ef  d>e^dXee^ef ddfdYdZZd=ee^ef dee^ef fd[d\Z		d&d=eee^ef  d>e^d]ee^ d^ee^ dee^ef f
d_d`Zdee^ef fdadbZdce^dee^ fdddeZdfe^dee^ef fdgdhZdiedeee^ef  fdjdkZdledee^ fdmdnZdoedeee^ef  fdpdqZdIedefdrdsZdtee^ef duee^ef dee^ef fdvdwZdxdy Zd'dzd{Zd|ee^ef dee^ef fd}d~Zdd Zd>e^fddZd>e^deee^ef  fddZd>e^de^deee^ef  fddZd>e^de^de^fddZdedee^ fddZde^fddZd>e^dee^ dedee^ef fddZd>e^de^dee^ef fddZdee^ef dee^ef fddZd>e^de^dee^ef fddZdee^ef d>e^de^deee^ef  fddZd>e^de^dedee^ef fddZd(d>e^dedee^ef fddZd>e^de^de^dee^ef fddZdoedeee^ef  fddZdedee^ fddZdeee^ef  de^dee fddZdeee^ef  dee^ fddZdoedledeee^ef  fddZd)dedledee detfddZd)deee^ef  dee^ dee^ fddZdee^ef dee^ef fddZdetfddZd>e^deEfddZ			Hd*d>e^d]ee^ dedetdee^ef f
ddÄZG ddń de0Ze dddȍZeje*dgddgdgdˍ ee d d d̜Ze  ed͡de%fddЄZedѡddӄ Zedԡe$ddd׍eevfd>e^d=ee^ef fddلZedڡeevfd>e^d=ee^ef fdd܄Zedݡeevfd>e^d=ee^ef fdd߄Zedeevfd>e^doegd=ee^ef fddZedeevfd>e^doehd=ee^ef fddZedeevfd>e^doeid=ee^ef fddZede$ddddeevfd>e^de^ded=ee^ef fddZede$ddAe$ddddeevfd>e^dee^ ded=ee^ef fddZedeevfd>e^de^d=ee^ef fddZedeevfd>e^doend=ee^ef fddZede$ddd׍e$ddddeevfd>e^de^ded=ee^ef fddZede$dd֐d deevfd>e^ded=ee^ef fddZedeevfd>e^de^doeod=ee^ef fddZede$ddd׍e$dHdAeevfd>e^de^de%de^detd=ee^ef fd	d
Zedeevfd>e^de^d=ee^ef fddZede$ddddeevfd>e^de^ded=ee^ef fddZedeevfd>e^doepd=ee^ef fddZede$ddAeevfd>e^de^de^d=ee^ef fddZeddoee^ef fddZeddoee^ef de%fddZedeeve"ddAfd=ee^ef d:ee^ fddZed eevfd=ee^ef fd!d"Zed#eevfd>e^d=ee^ef fd$d%Zed&eevfd'e^d=ee^ef fd(d)Zed*eevfdoee^ef d=ee^ef fd+d,Zed-eevfdedoee^ef d=ee^ef fd.d/Zed0d1d2 Zed3d>e^fd4d5Zed6eevfdoeqd=ee^ef fd7d8Zed6e$ddAe$ddAe$d d֐d9de$d d d:eevfd;ee^ d<ee^ ded=ed=ee^ef f
d>d?ZeӐd@eevfdAedoerd=ee^ef fdBdCZedDe$dHdAe$ddAe$dHdAfd>e^dEetdFedetfdGdHZedIe$ddAe$dHdAe$ddAe$dHdAfd>e^d]ee^ dEetdFedetf
dJdKZedLe$ddAe$ddAe$ddAe$ddAe$ddAe$dMdAe$ddAeewfd>e^d]ee^ dNee^ dOee^ d^ee^ dPee^ dQedRee^ d=eee^ef  fdSdTZedUe$ddAe$ddAe$ddAe$d dAe$d dAe$ddAe$ddAeewfd>e^d]ee^ dRee^ dVee^ ded=edNee^ dOee^ d=eee^ef  fdWdXZedYeewfd>e^dZe^d=eee^ef  fd[d\Zed]e$ddAfd>e^defd^d_Zed`e$ddAe$ddAe$ddAeewfd>e^d]ee^ dNee^ dOee^ d=eee^ef  f
dadbZedce$dՃe$ddAeewfd>e^dde^d]ee^ d=eee^ef  fdedfZedge$ddAe$ddAe$ddAe$ddAe$d dAe$d dAeewfd>e^d<ee dhee^ dNee^ dOee^ ded=ed=eee^ef  fdidjZedkeewfd>e^dZe^d=eee^ef  fdldmZednd>e^dZe^fdodpZedqd>e^dZe^fdrdsZeӐdtd>e^dZe^duedoee^ef fdvdwZedxe$ddAeewfd>e^ded=eee^ef  fdydzZed{eewfdoee^ef d=eee^ef  fd|d}Zed~e$ddAe$d dAe$d dAe$dHdAe$ddAeewfd>e^d]ee^ ded=edetdRee^ d=eee^ef  fddZedei dAeevfd>e^doee^ef d=ee^ef fddZedei dAeevfd>e^de^doee^ef d=ee^ef fddZde^dee^ fddZd>e^dtee^ef dee^ef fddZedd>e^de^fddZedd>e^de^fddZede$ddAe$ddAeewfd>e^de^d]ee^ detd=eee^ef  f
ddZededՃfd>e^de^dee^ef fddZede$ddAe$ddAfd>e^dNee^ dOee^ fddZede$ddAe$ddAfd>e^dNee^ dOee^ fddZede$ddAe$ddAfd>e^dNee^ dOee^ fddZede$ddAe$ddAfd>e^de^defddZede$ddAe$ddAfd>e^dNee^ dOee^ fddZede$ddAe$ddAe$ddAeewfd>e^d]ee^ dNee^ dOee^ d=eee^ef  f
ddZede$ddAe$ddAe$ddAfd>e^dedNee^ dOee^ fddZedd>e^dZe^fddZedd>e^fddZedd>e^fddZedd>e^fddZede$ddAe$ddAe$ddAe$ddAfd>e^de^d<ee dNee^ dOee^ f
dĐdńZdIee^ dee^ fdƐdǄZ								Hd+d]ee^ dNee^ dOee^ d^ee^ dee^ dPee^ dee dee dRee^ dVee^ detde~e^ee f fd̐d̈́Zi dΐdϓdȐdГdZdѓdҐdӓd]dԓdՐd֓dאdؓdRdٓdVdړdېdܓdݐdޓdߐddddddddddddddddddddd	ZdZi edېdiZi edېdiZ dede^fddZdedede^fddZdIedee fddZd ededee~ fddZdIedee~ fddZd|ee^ef de^fddZdeee^ef  deee^ef  fd	d
Zdeee^ef  deee^ef  fddZd>e^deee^ef  deee^ef  fddZ		d)deee^ef  deee^e^f  deee^ef  fddZ
	d)deee^ef  de^deee^e^f  de-fddZede$ddAe$ddAe$ddAe$ddAe$ddAe$ddAe$ddAe$ddAe$ddAe$ddAf
d>e^d]ee^ dNee^ dOee^ d^ee^ dee^ dPee^ dee dee dRee^ dVee^ fddZede$ddAe$ddAe$ddAe$ddAe$ddAe$ddAe$ddAe$ddAe$ddAe$ddAf
d>e^d]ee^ dNee^ dOee^ d^ee^ dee^ dPee^ dee dee dRee^ dVee^ fddZde%de^fddZ		d&dee^ dee^ dee^ fddZd ee^ef de,fd!d"Zed#d>e^de%fd$d%Zed&edՃe"dd'd(e"dd)d(fd>e^doee^ef dee^ dee^ fd*d+Zed,e$ddAeevfde%d>ee^ d=ee^ef fd-d.Zed/edՃe"dd'd(e"dd)d(fdoee^ef dee^ dee^ fd0d1Zed2doee^ef fd3d4Zed5doee^ef fd6d7Zed8eevfd>e^d=ee^ef fd9d:Zed8eevfd>e^doee^ef d=ee^ef fd;d<Zed=d>e^fd>d?Zed=d>e^doee^ef fd@dAZedBeevfd=ee^ef fdCdDZedBeevfdoee^ef d=ee^ef fdEdFZedGeevfdHed=ee^ef fdIdJZedKd>e^dHefdLdMZedKd>e^dHefdNdOZedPd>e^fdQdRZ edSd>e^fdTdUZ!edVe!dՃfd>e^dWe&fdXdYZ"edZe$dHdAfd>e^d[etfd\d]Z#edZd>e^doee^ef fd^d_Z$ed`d>e^fdadbZ%edcd>e^dHefdddeZ&edfd>e^fdgdhZ'edie!dՃfd>e^dWe&fdjdkZ(edle$ddAe$ddAfd>e^dmee^ dnee^ fdodpZ)edqd>e^drefdsdtZ*edld>e^doee^ef fdudvZ+edqd>e^dredoee^ef fdwdxZ,edqd>e^drefdydzZ-ed{d>e^drefd|d}Z.ed~e$dՃfd>e^de^fddZ/edd>e^de^fddZ0edd>e^doee^ef fddZ1edd>e^fddZ2ede$ddAe$ddAe$dHdAeevfd>e^d]ee^ dFedetd=ee^ef f
ddZ3edeevfd>e^d=ee^ef fddZ4edeevfd>e^doee^ef d=ee^ef fddZ5edeevfd>e^doee^ef d=ee^ef fddZ6ede$d d֐d9de$d d d:e$ddAeevfd>e^ded=ed<ee^ d=ee^ef f
ddZ7edeevfd>e^d=ee^ef fddZ8edeevfd>e^d=ee^ef fddZ9edeevfd>e^doee^ef d=ee^ef fddZ:edeevfd>e^de^d=ee^ef fddZ;edeevfd>e^doee^ef d=ee^ef fddZ<edeevfd>e^de^doee^ef d=ee^ef fddZ=edeevfd>e^de^d=ee^ef fddZ>edd>e^dZe^fddZ?edeevfd=ee^ef fddZ@edeevfdoee^ef de%d=ee^ef fddZAeddd ZBedeevfde%d=ee^ef fddZCde^fddZDd,dede^fddZEde^de^dIe^ddfdĐdńZFd>e^de^detfdǐdȄZGedɡeevfde%d=ee^ef fdʐd˄ZHed̡eevfd>e^de%d=ee^ef fd͐d΄ZIedϡeevfd>e^doee^ef d=ee^ef fdАdфZJde^dee^ fdҐdӄZKedԡeevfd>e^dedoee^ef de%d=ee^ef f
dՐdքZLedסeevfd>e^de%d=ee^ef fdؐdلZMedڡeevfdedoee^ef d=ee^ef fdېd܄ZNedݡe$d dAe$d dAe$ddAe$ddAe$ddAe$ddAe$ddAe$ddAe$ddAe$ddAeevfded=edee dee^ dee^ d>ee^ dee^ dee^ dNee^ dOee^ d=ee^ef fddZOeddoee^ef de%fddZPede$dՃfde^fddZQedeevfdoee^ef de%d=ee^ef fddZRede$ddAeevfd>ee^ d=ee^ef fddZSedeevfdede%d=ee^ef fddZTd>e^deee^ef  fddZUdee^ef dee^ef fddZVde^dee^e^f fddZWddAde^dee^e^f de^dJe^de^f
dd ZXde^dee^e^f de^fddZY					"d-de^de^dee^ef dNee^ dOee^ dee^ dee^ defdd	ZZee[d
dZ\dZ]d>e^de^fddZ^de^de^detfddZ_d|ee^ef dee^ef fddZ`d.d>e^de^dNee^ dOee^ def
ddZad&d>e^de^dNee^ dOee^ def
ddZbedeevfd>e^d=ee^ef fddZcdeee^ef  fddZddee^ef de^deee^ef  fddZededefd d!Zfd>e^detfd"d#Zged$e$ddAe$ddAeevfd>e^dfee^ dee^ d=ee^ef fd%d&Zhedeevfd>e^doejd=ee^ef fd'd(Zid)de^d)e^d*eeee^ef   defd+d,Zjd)e^defd-d.Zkdee^ef de^de^fd/d0Zled1eevfd>e^doeld=ee^ef fd2d3Zmed4eevfd>e^dfe^d=ee^ef fd5d6Zned7e$ddAe$ddAe$ddAe$ddAe$d"dAeevfd>e^dNee^ dOee^ d8ee^ dRee^ ded=ee^ef fd9d:Zode^defd;d<Zped=eevfd>e^doemd=ee^ef fd>d?Zqed@e$d"dAe$dHdAeevfd>e^dedAetd=ee^ef fdBdCZredDe$ddAe$ddAe$d9dAe$dHdAeevfd>e^dNee^ dOee^ dedAetd=ee^ef fdEdFZsedGe$ddAe$ddAeevfd>e^dNee^ dOee^ d=ee^ef fdHdIZtedJeevfd>e^doee^ef d=ee^ef fdKdLZuedMeevfd>e^de%d=ee^ef fdNdOZvedPe$dQdAe$ddAe$ddAe$ddAe$ddAfd>e^d<e^d]ee^ dee^ dRee^ dSee^ fdTdUZwedVd>e^doee^ef fdWdXZxedYd>e^doee^ef fdZd[Zyed\d>e^d]e^fd^d݄Zzed_d>e^doee^ef fd`daZ{G dbdc dce2Z|G ddde dee2Z}edfeevfd>e^de}d=ee^ef fdgdhZ~edieevfd>e^d=ee^ef fdjdkZedleevfd>e^de%d=ee^ef fdmdnZedoeevfd>e^d=ee^ef fdpdqZedieevfd>e^de|d=ee^ef fdrdsZedteevfd>e^d=ee^ef fdudvZG dwdx dxe2Zedyeevfd>e^d=ee^ef fdzd{Zedyeevfd>e^ded=ee^ef fd|d}Zed~eevfd>e^de^d=ee^ef fddZede$dd֐d:e$dddde$ddAe$ddAe$ddAe$ddAe$ddAeevfd>e^deded<ee^ dee^ dee^ dNee^ dOee^ d=ee^ef fddZedeevfd>e^dZe^d=ee^ef fddZG dd de2Zdee^ef dee^ef fddZedeevfd=ee^ef fddZededՃeevfd>e^dee^ef d=ee^ef fddZedeevfd>e^ded=ee^ef fddZedeevfd=ee^ef fddZd=ee^ef ddfddZde^detfddZde^de^fddZd/de^dedee^ fddZde^detfddZd>e^de^fddZde^d>ee^ detfddZd)de^de^d>ee^ de^fddZd)de^d>ee^ deee^ef  fddZ	9	d0de^de^ded>ee^ dee^ f
ddZde^d>ee^ de^fddZdi dZee^ef eed< dZddde^dedee^ef fddZdee^ dee^ee^ef f fdĐdńZdee^ dee^ee^ef f fdǐdȄZde^dee^ fdɐdʄZde^detfdːd̄Zde^dee^ef fd͐d΄Zd>e^detfdϐdЄZdee^ef fdѐd҄Zd>e^dee^ef fdӐdԄZd>e^defdՐdքZd'dאd؄Zed١eevfd=ee^ef fdڐdۄZedܡeeve$ddAe$ddAe$ddAe$ddAe$ddAe$ddݐdލe$ddݐdލe$dd֐dde$dd֐d9de$d d d:fd=ee^ef d>ee^ dee^ dee^ deet dZee^ dNee^ dOee^ dee ded=efddZedeevfd=ee^ef fddZedeevfd>e^d=ee^ef fddZedei dAeevfd>e^doee^ef d=ee^ef fddZedeevfd>e^d=ee^ef fddZede$ddAe$dHdAeevfd>ee^ detd=ee^ef fddZede$ddAe$ddAe$ddAeevfde^dedee^ d>ee^ d=ee^ef f
ddZede$ddAeevfd>ee^ d=ee^ef fddZede$ddAeevfde^d>ee^ d=ee^ef fd dZedeevfd=ee^ef fddZedeevfd=ee^ef fddZedeevfdoee^ef d=ee^ef fd	d
Zedei dAeevfde^doee^ef d=ee^ef fddZedei dAeevfd>e^doee^ef d=ee^ef fddZedeevfd>e^d=ee^ef fddZedeedeevfd>e^doee^ef d=ee^ef fddZede$ddddeevfd>e^ded=ee^ef fddZedeedeevfd>e^doee^ef d=ee^ef fddZedeevfd>e^d=ee^ef fd d!Zed"eevfd>e^d=ee^ef fd#d$ZdS (1      N)ThreadPoolExecutoras_completed)BytesIO)StringIO)datetimetime	timedelta)AnyDictListOptional)unquote)	BodyDependsFastAPIFileHeaderHTTPExceptionQueryRequest
UploadFile)jsonable_encoder)CORSMiddleware)JSONResponsePlainTextResponseStreamingResponse)BaseHTTPMiddleware)	BaseModelField)AuthHandler)Config)DatabaseHandler)AnalyticsService)QualityParametersHandler)PropensityParametersHandler)ObjectionClassificationsHandlerLeadSquaredService)AGENT_PROMPTSget_agent_instructionget_default_catalognormalize_agent)
RAGHandler)build_lead_insights)import_raw_calls_from_csvinsert_lead_recording)CallIngestServiceCANONICAL_INGEST_SCHEMAingest_schema_for_bidshared_ingest_secret)ApiPushServicez4%(asctime)s - %(name)s - %(levelname)s - %(message)s)levelformatreturnc                  C   s,   i } t tD ]}| rtt|| |< q| S N)dirr    isuppergetattr)cfgkey r>   6/home/aiteam/pcaa-dev/dashboard-backend/fastapi_app.py_build_config_dict5   s   r@   _PRESALES_MAP_CACHE,  c                   @   ,   e Zd ZU eedZeeee	f  e
d< dS )IngestDocumentsRequestdefault_factory	documentsN)__name__
__module____qualname__r   listrG   r   r
   strr	   __annotations__r>   r>   r>   r?   rD   K      
 "rD   c                   @   sF   e Zd ZU dZeed< dZeed< dZeed< dZ	e
ee  ed< dS )	IngestTranscriptsRequestTpresales_only  limitFoverwrite_existingNcallids)rH   rI   rJ   rP   boolrM   rR   intrS   rT   r   r   rL   r>   r>   r>   r?   rO   O   s
   
 rO   c                   @   s   e Zd ZU eed< eed< dZeee  ed< dZ	ee ed< dZ
ee ed< dZee ed< dZeeeef  ed< dZeeeef  ed	< d
Zee ed< dS )QueryRequestuser_idmessageNquery_embeddingconversation_idtop_kmin_similaritymetadataprofile_updateslead_quality_agent
agent_type)rH   rI   rJ   rL   rM   rZ   r   r   floatr[   r\   rV   r]   r^   r
   r	   r_   ra   r>   r>   r>   r?   rW   V   s   
 rW   c                   @   sN   e Zd ZU edddZeed< edddZeed< dZe	e
eef  ed< dS )"TelephonyIntegrationConnectRequest.   
min_lengthprovider
source_bidNconfig)rH   rI   rJ   r   rg   rL   rM   rh   ri   r   r
   r	   r>   r>   r>   r?   rc   b   s   
 rc   )r   rd   
      2   c                   @   s   e Zd ZU edddZeed< dZee ed< dZ	eed< dZ
eeeeef   ed	< d
Zeed< dZeee  ed< dZeed< dS )OnboardingProcessingRequest.rd   re   rg   Nrh   defaultanalysis_modecustom_parametersFgroup_filter_enabledallowed_groupnamesr   ingest_lookback_days)rH   rI   rJ   r   rg   rL   rM   rh   r   ro   rp   r   r
   r	   rq   rU   rr   rs   rV   r>   r>   r>   r?   rm   k   s   
 rm   c                   @   rC   )TelephonySyncToDbRequestrE   recordsN)rH   rI   rJ   r   rK   ru   r   r
   rL   r	   rM   r>   r>   r>   r?   rt   u   rN   rt   c                   @   sB   e Zd ZU eed< dZee ed< dZee ed< dZe	ed< dS )	ScoreCallRequestcall_idsystemrX   r`   ra   F	force_llmN)
rH   rI   rJ   rL   rM   rX   r   ra   ry   rU   r>   r>   r>   r?   rv   y   s
   
 rv   c                   @   s   e Zd ZU dZee ed< dZee ed< dZ	ee ed< dZ
ee ed< dZee ed< dZee ed< dZeeeef  ed< dS )	AgentConfigUpsertRequestNdisplay_name
is_enabled	is_lockedsystem_promptrg   
model_nameruntime_config)rH   rI   rJ   r{   r   rL   rM   r|   rU   r}   r~   rg   r   r   r
   r	   r>   r>   r>   r?   rz      s   
 rz   c                   @   s2   e Zd ZU eed< dZee ed< dZeed< dS )AgentAnalyzeCallRequestrw   r`   ra   Fforce_refreshN)	rH   rI   rJ   rL   rM   ra   r   r   rU   r>   r>   r>   r?   r      s   
 r   c                   @   s   e Zd ZU edddZeed< dZee ed< dZ	ee ed< dZ
ee ed< dZee ed	< edddZeed
< dZee ed< dS )ReportedIssueCreateRequest.rd   re   business_idNbusiness_namebusiness_user_name	page_pathrw   
issue_textscope)rH   rI   rJ   r   r   rL   rM   r   r   r   r   rw   r   r   r>   r>   r>   r?   r      s   
 r   c                   @   s"   e Zd ZU edddZeed< dS )ReportedIssueUpdateRequest.rd   re   statusN)rH   rI   rJ   r   r   rL   rM   r>   r>   r>   r?   r      s   
 r   authorizationc                 C   s,   | r|  dstddd| ddd S )NBearer   zMissing authorization headerstatus_codedetail rd   )
startswithr   replacestrip)r   r>   r>   r?   _extract_bearer_token   s   r   userbidc                 C   sP   | sdS |  drdS |  dg pg }|D ]}t| dt|kr% dS qdS )NF	is_masterT
businessesr   getrL   )r   r   r   businessr>   r>   r?   _has_business_access   s   
r   rn   c                 C   s&   t | }t|}|stddd|S )Nr   zInvalid or expired tokenr   )r   auth_handlervalidate_tokenr   )r   tokenr   r>   r>   r?   
_auth_user   s
   
r   c                 C   s6   | r|  ds	d S | ddd }|sd S t|S )Nr   r   rd   )r   r   r   r   r   )r   r   r>   r>   r?   _optional_auth_user   s   
r   c                 C   s    t || stdd|  d|S )N  zAccess denied to business r   )r   r   r   r   r>   r>   r?   _auth_user_for_bid   s   
r   Fvaluern   c                 C   s    | d u r|S t |   dv S )N)1trueyesyon)rL   r   lower)r   rn   r>   r>   r?   _as_bool   s   r   c                 C   s   t | |rdS | d}|sdS z-t|pg D ]"}t|dt|kr;t|dp-d  }|dv r; W dS qW dS  tyH   Y dS w )NTidFr   roler   )adminbusiness_admin)	rbac_moduleis_business_adminr   r   get_user_businessesrL   r   r   	Exception)r   r   rX   r   r   r>   r>   r?   user_has_business_admin   s$   
r   c                 C   s   t || stdddd S )Nr   zBusiness admin access requiredr   )r   r   r   r>   r>   r?   !_require_business_admin_or_master      
r   c                 C   s(   | ds
t|| rd S t| |d d S )Nr   zsettings.manage)r   r   _require_permissionr   r>   r>   r?   !_require_settings_manage_or_admin   s   r   
permissionc                 C   s*   t j|| |tddstdd| dd S )NTreloadr   zPermission denied: r   )r   has_permissionr   r   )r   r   r   r>   r>   r?   r      s   r   c                 C   sR   | rt | |sdg fS t| |tsdg fS tj| |tdd}t|dp'i S )zPReturn (scope_sql, scope_params) for raw_calls filtering; empty when not scoped.r   Tr   r   )r   r   should_apply_data_scoper   resolve_business_contextbuild_raw_call_scope_sqlr   )r   r   ctxr>   r>   r?   _scope_clause_for_user   s   r   call_rowc                 C   s\   | rt | |s	d S t| |tsd S tj| |tdd}t||dp$i s,tdddd S )NTr   r     Call not foundr   )r   r   r   r   r   call_record_in_scoper   r   )r   r   r   r   r>   r>   r?   _assert_call_in_scope   s   r   c                 C   sB   |  dr| S |  d}|s| S t| }t|pg }||d< |S )z@Reload per-business role/permissions/scope from DB for /auth/me.r   r   r   )r   dictr   r   )r   rX   enrichedr   r>   r>   r?   _enrich_auth_user  s   

r   	groupnameagent_namesc                 C   sZ   t | |\}}|}|}| r"t| |r"t| |t|}t| |t|}|||p'd |p*d dS )N)r   r   scope_wherescope_params)r   r   r   effective_groupname_for_scoper   effective_agent_names_for_scope)r   r   r   r   r   r   effective_groupnameeffective_agent_namesr>   r>   r?   _resolve_list_scope  s   r   c                   C   sf   t dtddtt dtddt dtdd	t d
tddt dtddddS )NSYNC_SOURCE_DB_HOSTDB_HOST	127.0.0.1SYNC_SOURCE_DB_PORTDB_PORT  SYNC_SOURCE_DB_USERDB_USERr   SYNC_SOURCE_DB_PASSWORDDB_PASSWORDr   SYNC_SOURCE_DB_NAMEDB_NAMEvoicebot_clusterutf8mb4hostportr   passworddatabasecharset)osgetenvCONFIGr   rV   r>   r>   r>   r?   get_sync_source_db_config%  s   r   namesc                  G   s(   | D ]}t |}|dvr|  S qd S NNr   )r   r   )r   namer   r>   r>   r?   
_first_env0  s   
r   rg   c                 C   s  t | pd  dd}|dv rh d}dg}d}n|dv r*h d}dg}d	}ntd
dd|d }tdd |D  }tdd |D  }tdd |D  }tdd |D  }	tdd |D  }
|dv r|pktd}|prtd}|pytd}|	ptd}	|
ptd}
dd d|fd|fd|fd|	ffD }|rt|}|r|S td
| dd 	| d|t
|
pd!|||	d"d#S )$Nr   -_>   mcube2mcube_2mcube2_0	mcube_2_0)MCUBE2MCUBE_2	MCUBE_2_0z	Mcube 2.0>   salesclassicmcube_salesmcube_classic)MCUBE_CLASSICMCUBE_SALESSALESzMcube Classic  zUnsupported telephony providerr   r   c                 s       | ]}| d V  qdS )_DB_HOSTNr>   .0pr>   r>   r?   	<genexpr>F      z._provider_env_source_config.<locals>.<genexpr>c                 s   r  )_DB_USERNr>   r  r>   r>   r?   r
  G  r  c                 s   r  )_DB_PASSWORDNr>   r  r>   r>   r?   r
  H  r  c                 s   r  )_DB_NAMENr>   r  r>   r>   r?   r
  I  r  c                 s   r  )_DB_PORTNr>   r  r>   r>   r?   r
  J  r  r   r   r   r   r   c                 S   s   g | ]\}}|s|qS r>   r>   )r  labelr   r>   r>   r?   
<listcomp>S  s    z/_provider_env_source_config.<locals>.<listcomp>r   r   r   r   z default config is missing: , r   r   r   )rL   r   r   r   r   r   r   r    _existing_provider_source_configjoinrV   )rg   provider_keyprovider_aliasesprefix_setsfriendlyprefixesr   r   r   r   r   missingexistingr>   r>   r?   _provider_env_source_config8  sR   

r  r  c              	   C   s<  z5t  (}| }ddgt|  }|d| dt|  | p$g }W d    n1 s/w   Y  W n
 ty?   Y d S w |D ]Y}t	|
dpMg D ]L}t|
dpWd | v rt|
dpdi }|
d	r|
d
r|
dr|
dr|
d	t|
dpd|
d
|
d|
ddd    S qNqBd S )Nr  %sz{
                SELECT bid
                FROM business_telephony_integrations
                WHERE LOWER(provider) IN (z)
                  AND is_active = 1
                ORDER BY updated_at DESC, created_at DESC
                LIMIT 5
                r   rg   r   ri   r   r   r   r   r   r   r   r   )_db_conncursorr  lenexecutetuplefetchallr   
db_handlerlist_telephony_integrationsr   rL   r   r   rV   )r  connr  placeholdersrowsrowitemr<   r>   r>   r?   r  m  s>   (r  phonec                 C   s   d dd t| p	dD }|sg S t|dkr|dd  n|}|d| |h}t|dkr?|d| d| d	| h d
d |D S )Nr   c                 s   s    | ]	}|  r|V  qd S r8   )isdigit)r  chr>   r>   r?   r
        z,_normalize_phone_variants.<locals>.<genexpr>rj   i+91z+910c                 S      g | ]}|r|qS r>   r>   r  vr>   r>   r?   r        z-_normalize_phone_variants.<locals>.<listcomp>)r  rL   r   update)r+  digitscore10variantsr>   r>   r?   _normalize_phone_variants  s   "r:  payloadc                 C   sN   | d u rg S t | tr| S t | tr%dD ]}| |}t |tr$|  S qg S )Nr   DatarK   data
isinstancerK   r   r   r;  r=   r   r>   r>   r?   _extract_lsq_lead_list  s   



rB  c                 C   sJ   t | tr	|  S t | trdd |  D S t | tr#dd | D S | S )Nc                 S   s   i | ]\}}t |t|qS r>   )rL   _to_json_safer  kr4  r>   r>   r?   
<dictcomp>      z!_to_json_safe.<locals>.<dictcomp>c                 S      g | ]}t |qS r>   rC  r3  r>   r>   r?   r    r5  z!_to_json_safe.<locals>.<listcomp>)r@  r   	isoformatr   itemsrK   r   r>   r>   r?   rC    s   


rC  detailscrmc                 C   s  |  dpg }tdd |D }tdd |D }t|tr#| dnd }|  d|p,i  dp4|  dt|  dp;d	t|t|t|  d
pId	|  dpPdt|  dpWd	t|trdt| dndt|trqt| dnd|pui  d|p{i  ddS )Ncallsc                 s   s,    | ]}t |d d dkrdV  qdS )call_statusr   ANSWERrd   N)rL   r   upperr  callr>   r>   r?   r
    s   * z*_build_customer_profile.<locals>.<genexpr>c                 s   s"    | ]}t |d rdV  qdS )has_transcriptrd   N)rU   r   rS  r>   r>   r?   r
         lead
lead_phone
owner_nametotal_conversationsr   avg_quality_scoretalk_listen_ratioN/Atotal_duration_seconds	connectedFmatchedr   next_task_due_date)rX  rY  rZ  answered_callstranscript_callsr[  r\  r^  crm_connectedcrm_matched
crm_statuscrm_next_task_due_date)r   sumr@  r   rV   rb   rU   )rM  rN  rO  rb  rc  crm_leadr>   r>   r?   _build_customer_profile  s"   rj  c                
   C   sJ   t jtddttddtddtddtd	d
dt jjddS )Nr   r   r   r   r   r   r   r   r   r   r   Tr   r   r   r   r   r   cursorclass
autocommit)pymysqlconnectr   r   rV   cursors
DictCursorr>   r>   r>   r?   r    s   



r  c                  C   sn   t  *} |  }|d |d | d u r%|d W d    d S W d    d S 1 s0w   Y  d S )NaV  
            CREATE TABLE IF NOT EXISTS reported_issues (
                id BIGINT AUTO_INCREMENT PRIMARY KEY,
                business_id VARCHAR(50) NOT NULL,
                business_name VARCHAR(255) DEFAULT NULL,
                business_user_name VARCHAR(255) DEFAULT NULL,
                user_id BIGINT DEFAULT NULL,
                username VARCHAR(100) DEFAULT NULL,
                user_email VARCHAR(255) DEFAULT NULL,
                page_path VARCHAR(500) DEFAULT NULL,
                call_id VARCHAR(100) DEFAULT NULL,
                scope VARCHAR(100) DEFAULT NULL,
                issue_text TEXT NOT NULL,
                status ENUM('open','in_progress','resolved','dismissed') DEFAULT 'open',
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                INDEX idx_business_status_created (business_id, status, created_at),
                INDEX idx_status_created (status, created_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            z
            SELECT 1
            FROM INFORMATION_SCHEMA.COLUMNS
            WHERE TABLE_SCHEMA = DATABASE()
              AND TABLE_NAME = 'reported_issues'
              AND COLUMN_NAME = 'business_user_name'
            LIMIT 1
            z
                ALTER TABLE reported_issues
                ADD COLUMN business_user_name VARCHAR(255) DEFAULT NULL AFTER business_name
                r  r  r!  fetchoner&  r  r>   r>   r?   _ensure_reported_issues_table  s   
""ru  r)  c                 C   s   t | pi S r8   rI  r)  r>   r>   r?   _serialize_reported_issue  s   rw  c                  C   sF   t  } z|  }|d |d |d W |   d S |   w )Na  
            CREATE TABLE IF NOT EXISTS rag_agent_configs (
                id BIGINT AUTO_INCREMENT PRIMARY KEY,
                bid VARCHAR(50) NOT NULL,
                agent_type VARCHAR(100) NOT NULL,
                display_name VARCHAR(150) NOT NULL,
                is_enabled BOOLEAN DEFAULT FALSE,
                is_locked BOOLEAN DEFAULT TRUE,
                system_prompt LONGTEXT NOT NULL,
                provider VARCHAR(100) DEFAULT 'auto',
                model_name VARCHAR(150) DEFAULT '',
                runtime_config JSON,
                updated_by VARCHAR(100) DEFAULT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uniq_bid_agent (bid, agent_type),
                INDEX idx_bid_enabled (bid, is_enabled)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            a8  
            CREATE TABLE IF NOT EXISTS rag_agent_call_analysis (
                id BIGINT AUTO_INCREMENT PRIMARY KEY,
                bid VARCHAR(50) NOT NULL,
                call_id VARCHAR(100) NOT NULL,
                agent_type VARCHAR(100) NOT NULL,
                status ENUM('completed','failed') DEFAULT 'completed',
                model_name VARCHAR(150) DEFAULT '',
                summary TEXT,
                result_json JSON,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uniq_bid_call_agent (bid, call_id, agent_type),
                INDEX idx_bid_created (bid, created_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            a@  
            CREATE TABLE IF NOT EXISTS rag_agent_knowledge_uploads (
                id BIGINT AUTO_INCREMENT PRIMARY KEY,
                bid VARCHAR(50) NOT NULL,
                agent_type VARCHAR(100) NOT NULL,
                version_no INT NOT NULL,
                filename VARCHAR(255) NOT NULL,
                page_count INT NOT NULL,
                char_count INT NOT NULL,
                knowledge_bands JSON,
                compiled_prompt LONGTEXT,
                uploaded_by VARCHAR(100) DEFAULT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                UNIQUE KEY uniq_bid_agent_version (bid, agent_type, version_no),
                INDEX idx_bid_agent_created (bid, agent_type, created_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            )r  r  r!  closert  r>   r>   r?   _ensure_agent_tables  s   ry  c                 C   s   t  }t }zF| }|D ]8}|dt| t|d t|d |dr&dnd|dr.dndt|d tj|d	p?d
ddddf qW |  d S |  w )NaQ  
                INSERT INTO rag_agent_configs (
                    bid, agent_type, display_name, is_enabled, is_locked, system_prompt, provider, model_name, runtime_config
                )
                VALUES (%s, %s, %s, %s, %s, %s, 'auto', '', %s)
                ON DUPLICATE KEY UPDATE agent_type = agent_type
                ra   r{   r|   rd   r   r}   instructionr   g?g?)temperaturetop_pTensure_ascii)	r*   r  r  r!  rL   r   jsondumpsrx  )r   catalogr&  r  r*  r>   r>   r?   _ensure_default_agent_configsG  s$   


r  c              	   C   s   t |  t }z| }|dt| f | pg }W |  n|  w |D ]#}t|dtrKzt	
|d |d< W q( tyJ   i |d< Y q(w q(t|S )Nz
            SELECT bid, agent_type, display_name, is_enabled, is_locked, system_prompt, provider, model_name, runtime_config, updated_at
            FROM rag_agent_configs
            WHERE bid = %s
            ORDER BY id ASC
            r   )r  r  r  r!  rL   r#  rx  r@  r   r  loadsr   rC  )r   r&  r  r(  r)  r>   r>   r?   _get_agent_configsc  s&   	r  ra   c                 C   s0   t | D ]}t|dt|kr|  S qd S )Nra   )r  rL   r   )r   ra   r)  r>   r>   r?   _get_agent_config}  s
   r  c                 C   s.   t | |}|r|drt|dS t|S )Nr~   )r  r   rL   r)   )r   ra   r<   r>   r>   r?   "_resolve_agent_instruction_for_bid  s   
r  	pdf_bytesc                 C   sh  d }dD ]9}z#t |}|t| }g }|jD ]}|| p d  q|W   S  ty= } z|}W Y d }~qd }~ww z]tt	
d| }|dkrOtd| jddd}	t	d	|	}
t|
d
krh|
d
d  n|	g}g }t|D ]'}|t|k r}|| nd}t	
d|}ddd |D  }||pd qq|W S  ty } ztdd| d| dd }~ww )N)pypdfPyPDF2r   s   /Type\s*/Page\br   zNo /Type /Page markers foundlatin1ignoreerrorsz/Type\s*/Page\brd   z\(([^()]*)\) c                 S   s   g | ]	}t d d|qS )z\\[nrtbf()\\]r  )resub)r  litr>   r>   r?   r        z&_extract_pdf_pages.<locals>.<listcomp>  zPDF parser is unavailable on the server and fallback extraction failed. Install 'pypdf' inside backend venv for reliable parsing. Last parser error: z; fallback error: r   )
__import__	PdfReaderr   pagesappendextract_textr   r   r   r  findall
ValueErrordecodesplitranger  r   )r  parser_errormodule_namemodulereaderr  pageexc
page_count	text_blobpage_splitscandidate_sectionsidxsegmentliteralscleanedfallback_excr>   r>   r?   _extract_pdf_pages  sN   

r  c                 C   s   | dkrt dd|  dd S )Npresales_lead_agentr  zaThis endpoint is currently enabled only for Raj Mehta (presales_lead_agent). Received agent_type=r   )r   )ra   r>   r>   r?   _assert_raj_presales_agent  s   r  rX   rR   c              	   C   s   t  }z.| }|r|dt| t|t|f n|dt| t|f | p+g }W |  n|  w |D ]#}t|dtr\zt	
|d |d< W q9 ty[   i |d< Y q9w q9dt|iS )Na_  
                SELECT c.conversation_id, c.user_id, c.metadata, c.updated_at,
                    (SELECT content FROM rag_messages m WHERE m.bid = c.bid AND m.conversation_id = c.conversation_id AND m.role = 'user' ORDER BY m.id ASC LIMIT 1) AS first_user_message,
                    (SELECT content FROM rag_messages m WHERE m.bid = c.bid AND m.conversation_id = c.conversation_id ORDER BY m.id DESC LIMIT 1) AS last_message
                FROM rag_conversations c
                WHERE c.bid = %s AND c.user_id = %s
                ORDER BY c.updated_at DESC
                LIMIT %s
                aL  
                SELECT c.conversation_id, c.user_id, c.metadata, c.updated_at,
                    (SELECT content FROM rag_messages m WHERE m.bid = c.bid AND m.conversation_id = c.conversation_id AND m.role = 'user' ORDER BY m.id ASC LIMIT 1) AS first_user_message,
                    (SELECT content FROM rag_messages m WHERE m.bid = c.bid AND m.conversation_id = c.conversation_id ORDER BY m.id DESC LIMIT 1) AS last_message
                FROM rag_conversations c
                WHERE c.bid = %s
                ORDER BY c.updated_at DESC
                LIMIT %s
                r^   conversations)r  r  r!  rL   rV   r#  rx  r@  r   r  r  r   rC  )r   rX   rR   r&  r  r(  r)  r>   r>   r?   _list_conversations  s.   		r  rw   c                 C   s^   t | |pi }t | |pi }t | |pi }t | |pi }t|t|t|t|dS )N)rT  
transcript	analyticsbant)r$  get_call_by_idget_call_transcriptget_call_analyticsget_bant_analysisrC  )r   rw   rT  r  r  r  r>   r>   r?   _collect_call_context  s   r  contextc           	   	   C   s   |  dpi }|  dpi }|  dpi }| d}| d}| dp%d}| dp,i }| dp3d	}||d ur;|nd
|p?d|d urI|dk rIdnd||| ddg ddS )Nr  r  r  quality_score	sentimentsummaryz+Call analyzed using existing call metadata.profiler   r   unknown<   mediumlowlanguage)r  r  transcript_language)zMReview objection handling moments and add specific rebuttal playbook entries.z5Track repeat intent and follow-up commitments in CRM.z9Coach agent using this call transcript and quality notes.)r  r  r  compliance_riskcustomer_profilerecommended_actionsr   )	r  r  r  r  qualityr  r  r  profile_summaryr>   r>   r?   _score_call_fallback  s$   

r  c                 C   sV   t | |pi }|dpi }t|tsi }t|dpdt|dp$d |dS )Nr   rg   autor   r   rg   r   r   )r  r   r@  r   rL   r   )r   ra   r<   runtime_cfgr>   r>   r?   _resolve_agent_model_settings  s   
r  c           
      C   s   |  dpi  d}|sd S t||}t|| dtj| dd }tj||d |d p-d |d d}|s7d S |d	}|d
}|dksM|dksM||krOd S zt	|||d  }	W n
 t
yf   Y d S w t|	tsnd S |	S )Nr  z
Return STRICT JSON with keys: summary, quality_score, sentiment, compliance_risk, customer_profile, recommended_actions.
quality_score must be 0-100 numeric.

CALL CONTEXT:
Tr}  rg   r   r   r  {}rd   )r   r  r  r  r  rag_handler_invoke_chat_modelfindrfindr  r   r@  r   )
r  r   ra   transcript_textsettingspromptanswerstartendparsedr>   r>   r?   _score_call_with_llm   s8   




r  agent_iddaysc           	   
   C   s   |  d}|  d}t jtddttddtddtd	d
tdddt jjdd}z#| }d| d| d}|||t|f |	 pLi }W |
  n|
  w |t|t|t  d dS )N
_raw_calls_callanalyticsr   r   r   r   r   r   r   r   r   r   r   Trk  a>  
            SELECT
                COUNT(*) AS total_calls,
                AVG(a.quality_score) AS avg_quality_score,
                AVG(a.agent_speak_percentage) AS avg_agent_speak_percentage,
                AVG(a.customer_speak_percentage) AS avg_customer_speak_percentage,
                SUM(CASE WHEN a.sentiment = 'positive' THEN 1 ELSE 0 END) AS positive_calls,
                SUM(CASE WHEN a.sentiment = 'neutral' THEN 1 ELSE 0 END) AS neutral_calls,
                SUM(CASE WHEN a.sentiment = 'negative' THEN 1 ELSE 0 END) AS negative_calls
            FROM `z` r
            LEFT JOIN `z` a ON r.callid = a.callid
            WHERE COALESCE(NULLIF(r.agentname, ''), NULLIF(r.agent_callinfo, ''), 'Unknown') = %s
              AND r.call_starttime >= DATE_SUB(NOW(), INTERVAL %s DAY)
        Z)r  window_daysmetricsgenerated_at)rn  ro  r   r   rV   rp  rq  r  r!  rs  rx  rC  r   utcnowrJ  )	r   r  r  table_callstable_analyticsr&  r  queryr)  r>   r>   r?   _agent_reportD  s6   






	
r  rj   
limit_runsc              
   C   s   t jtddttddtddtddtd	d
dt jjdd}z+| }|dt	| f |
 p8i }|dt	| t|f | pJg }W |  n|  w t	| t|t|dS )Nr   r   r   r   r   r   r   r   r   r   r   Trk  a*  
            SELECT bid, status, last_run_started_at, last_run_finished_at, last_duration_ms,
                   processed_calls, ingested_documents, ingested_chunks, skipped, last_error, updated_at
            FROM rag_ingestion_progress
            WHERE bid = %s
            LIMIT 1
            a,  
            SELECT id, bid, started_at, finished_at, duration_ms, status,
                   processed_calls, ingested_documents, ingested_chunks, skipped, error, created_at
            FROM rag_ingestion_runs
            WHERE bid = %s
            ORDER BY id DESC
            LIMIT %s
            )r   currentrecent_runs)rn  ro  r   r   rV   rp  rq  r  r!  rL   rs  r#  rx  rC  )r   r  r&  r  r  runsr>   r>   r?   _ingestion_progressm  s0   





r  c                 C   s  t | |}|dpi }|dstdd| dt| |}| dtj|dd }t| |}tj||d |d	 p;d |d
 dpBd}|	d}	|
d}
|	dksY|
dksY|
|	kriddg g g d|d d id}n&zt||	|
d  }W n ty   ddg g g d|d d id}Y nw t }z2| }|dt| t|t|t|d	 ptddt|dpdtj|ddf W |  n|  w t| t|t|t|t  d dS )Nr  r   !Transcript not found for call_id=r   z
Return STRICT JSON with keys: summary, score, risks, opportunities, actions, model_data.
score must be 0-100 numeric.

CALL CONTEXT:
Tr}  rg   r   r   r  r   r  r  r  zModel returned non-JSON output.r   raw_response  )r  scorerisksopportunitiesactions
model_datard   zModel output parse failed.a  
            INSERT INTO rag_agent_call_analysis (bid, call_id, agent_type, status, model_name, summary, result_json)
            VALUES (%s, %s, %s, 'completed', %s, %s, %s)
            ON DUPLICATE KEY UPDATE
                status='completed',
                model_name=VALUES(model_name),
                summary=VALUES(summary),
                result_json=VALUES(result_json),
                updated_at=CURRENT_TIMESTAMP
            RAG_CHAT_MODELr  r  )r   rw   ra   resultr  )r  r   r   r  r  r  r  r  r  r  r  r  r   r  r  r!  rL   r   rx  rC  r   r  rJ  )r   rw   ra   r  transcript_objagent_instructionr  r  response_textr  r  
result_objr&  r  r>   r>   r?   _run_agent_call_analysis  sv   





	
	

r  c                 C   sf   | d u rd S t | tr| r| d S d S t | tr1dD ]}| |}t |tr.|r.|d   S q| S d S )Nr   r<  r?  rA  r>   r>   r?   _extract_lsq_lead_record  s   


r   valuesc                  G   s0   | D ]}|d u r	qt | }|r|  S qd S r8   rL   r   )r  r   textr>   r>   r?   _first_present_str  s   r  recordkeysc                 G   s   t | tsd S |D ]}|| v r| |dvr| |  S q	|D ]!}|  D ]\}}t| t| kr@|dvr@|    S q&q d S r   )r@  r   r   rK  rL   r   )r  r  r=   
record_keyr   r>   r>   r?   
_lsq_field  s   
 r  c                 C   sn   t | tsd S tt| dd}tt| dd}ddd ||fD  p&d }tt| dd	d
d|t| ddS )N	FirstName
first_nameLastName	last_namer  c                 S   r2  r>   r>   )r  partr>   r>   r?   r    r5  z%_lsq_display_name.<locals>.<listcomp>ProspectNameprospect_nameContactNamecontact_nameNamer   )r@  r   r  r  r  r   )r  firstlast	full_namer>   r>   r?   _lsq_display_name  s   
 
r  c                 C   s   t t|}|st| S t| }|s t| }t|tr|gng }|D ]}t|ddddd}|s0q"t t|}||r?|  S q"d S )NPhoneMobilePhoneNumberr+  mobile)setr:  r   rB  r@  r   r  intersection)r;  r+  target_variantsleadssinglerW  rX  lead_variantsr>   r>   r?   _select_lsq_record_for_phone  s    
r!  fallback_phonec                 C   sl   t t|}|s
dS t| trt| dddddnd }|r't|t t|S |r4t|t t|S dS )NFr  r  r  r+  r  )r  r:  r@  r   r  rU   r  )r  r+  r"  r  rX  r>   r>   r?   _record_matches_phone-  s    r#  c                 C   s   t | tsd S tt| ddddd}|r|S tt| ddd|p!d	}tt| d
dp+d	}t| p1d	}| d| d| }t|d S )N
ProspectIDLeadIdIdlead_id
ProspectIdr  r  r  r   EmailAddressemail|utf-8)	r@  r   r  r  r  hashlibsha256encode	hexdigest)r  r"  r'  r+  r*  r   digest_sourcer>   r>   r?   _lsq_external_id<  s   
r2  c              
   C   sF   | t | t| ddt| dddt| dddt| d	d
dt| dddS )Nr)  r*  r  r  r  
LeadStatusStatuslead_status	OwnerNameOwnerrY  NextTaskDueDatera  rawr   r*  r+  r   rY  ra  )r  r  )r  r>   r>   r?   _crm_payload_from_lsqK  s   

r;  c                   C   s   t tdd  dkS )NALLOW_CRM_WRITEfalser   )rL   r   r   r   r   r>   r>   r>   r?   _crm_write_allowedW     r>  c                 C   sV   t | d}|r|dr|dr|dstdddt|d |d |dd	S )
Nleadsquared
access_key
secret_key	is_activer  2LeadSquared integration not configured or inactiver   api_hostrA  rB  rE  )r$  get_crm_credentialsr   r   r'   )r   credsr>   r>   r?   !_get_lsq_service_for_bid_or_error[  s   "rI     	row_countr   c                  C   s  t dtt|pdd}|  d|pd d| }tt }t|}|r9|s9|t|dd tkr9|di S t| d	}|rP|d
rP|drP|ds^dddg g g dddddS t	|d
 |d |dd}	|	
dd|di}
|
dsdd|
dpdg g g dddddS t|
d}i }t }|D ] }t|ddd}|sqt|D ]}|| ||vr|||< qqtj| t||d}i }i }g }|D ]}|dpd}|dpd}|d pd}d }t|D ]}||}|r nq||vr|dt t d!||< ||vr|t dt d"||< t|d#p d}|| d$  |7  < || d% | || d& | || d$  |7  < || d' | || d& | ||||||d(t|t|d)d*d+t|d,d-d.d/ qd0d1 | D }|jd2d3 dd4 g }| D ]%}t|d' }||d ||r|d nd t|d$ t|d& d5 q|jd6d3 dd4 ddd7|||t|td8d9 |D t|dd}||d:t|< |S );Nrj   rJ  r  :r   tsr   r>  r@  rA  rB  rC  FrD  )lsq_leads_fetchedmatched_customersmapped_rows)successr_  rY   groupsagents	customersstatsrE  rF  Paging)OffsetRowCountrQ  TrY   z&Failed to fetch leads from LeadSquaredr  r  r  )r   customer_numbersr   r   r   	agentnamecustomer_callinfo)r   
totalCallsmatchedCustomersrS  )rZ  
groupnamesr\  r]  total_callsr\  rS  r]  r^  	last_callr6  r7  rY  r3  r4  r5  )r[  r   rZ  r_  r`  lsq_namelsq_owner_name
lsq_statusc                 S   s6   g | ]}|d  t |d t|d t|d dqS )r   r\  r]  rS  )r   r\  r]  agentsCount)rV   r   )r  valr>   r>   r?   r    s    


z:_get_presales_mapping_from_leadsquared.<locals>.<listcomp>c                 S      | d S Nr\  r>   xr>   r>   r?   <lambda>      z8_get_presales_mapping_from_leadsquared.<locals>.<lambda>r=   reverse)rZ  r^  r   r\  r]  c                 S   rf  rg  r>   rh  r>   r>   r?   rj    rk  z2Pre-sales mapping generated from LeadSquared leadsc                 S   s   h | ]}|d  qS )r[  r>   r  r)  r>   r>   r?   	<setcomp>  r5  z9_get_presales_mapping_from_leadsquared.<locals>.<setcomp>)rM  r>  )maxminrV   r   rA   r   _PRESALES_MAP_CACHE_TTL_SECONDSr$  rG  r'   search_leadsrB  r  r  r:  addget_group_agent_customer_rowsrK   r  r  r  sortsortedr   ) r   r   rK  r   safe_row_count	cache_keynowcachedrH  service
lsq_searchr  phone_to_leadall_phone_variantsrW  r+  variantr(  group_indexagent_indexcustomer_rowsr)  groupagentcustomer_phonematched_leadr_  rR  rS  re  r^  responser>   r>   r?   &_get_presales_mapping_from_leadsquaredf  s   
 "








	

	r  c                   @   s"   e Zd ZdZdZdefddZdS )StripApiPrefixMiddlewarezVMap public URLs under /api/* to app routes (/*) when the proxy forwards the full path.z/apirequestc                    sp   |j dpd}|| jd r |t| jd  pd|j d< n|| jks,|| jd kr1d|j d< ||I d H S )Npathr   /)r   r   r   _prefixr   )selfr  	call_nextr  r>   r>   r?   dispatch  s   
z!StripApiPrefixMiddleware.dispatchN)rH   rI   rJ   __doc__r  r   r  r>   r>   r>   r?   r    s    r  zMCube AI FastAPIz1.0.0)titleversion*T)allow_originsallow_credentialsallow_methodsallow_headers)requests_totalerrors_totalhttpr  c                    s   t d  d7  < t }z	|| I d H }W n ty&   t d  d7  <  w tt | d d}td| jj|j	| t
||jd< |S )Nr  rd   r  rQ      zpath=%s status=%s latency_ms=%szX-Process-Time-Ms)_metricsr   perf_counterr   roundloggerinfourlr  r   rL   headers)r  r  startedr  
latency_msr>   r>   r?   metrics_and_latency  s   r  z/healthc                   C   s   ddt   d tdS )Nhealthyzmcube-ai-fastapir  )r   r|  	timestampr  )r   r  rJ  r  r>   r>   r>   r?   health"  s
   r  z/rag/agents.rd   re   c                 C   s   t | | dt| iS )NrS  )r   r  r   r>   r>   r?   list_agents,  s   
r  z/rag/{bid}/agents/catalogc              	   C   s:   t | | t| t g dg dttddgddS )N)r  ollamabedrock)z
qwen2.5:7bz	gemma2:9bzdeepseek-r1:8br  r   )r  r  )r   r  	providerssuggested_models)r   rL   r*   r   r   r   r>   r>   r?   rag_agent_catalog5  s   
r  z/rag/{bid}/ollama/modelsc              
   C   s   t | | ttddd}ztj| ddd}|  |jr&| ni }W n t	yA } zt
dd| d	| d
d }~ww |dpHg }dd |D }|||dS )NOLLAMA_BASE_URLzhttp://127.0.0.1:11434r  z	/api/tags   timeout  z$Failed to fetch models from Ollama (z): r   modelsc                 S   s$   g | ]}| d rt| d qS r   r   r  r*  r>   r>   r?   r  T  s   $ z%rag_ollama_models.<locals>.<listcomp>)ollama_base_urlr  r:  )r   rL   r   r   rstriprequestsraise_for_statuscontentr  r   r   )r   r   base_urlr  r;  r  r  r   r>   r>   r?   rag_ollama_modelsF  s   
r  z/rag/{bid}/documentsc                 C   s4   t | | |jstdddt| |j}d|dS )Nr  zdocuments array is requiredr   rQ  )r   	ingestion)r   rG   r   r  ingest_documents)r   r;  r   r  r>   r>   r?   rag_ingest_documentsX  s
   

r  z/rag/{bid}/ingest-transcriptsc                 C   s*   t | | tj| |jt|j|j|jdS )Nr   rP   rR   rS   rT   )r   r  backfill_transcriptsrP   rV   rR   rS   rT   )r   r;  r   r>   r>   r?   rag_ingest_transcriptsa  s   
r  z/rag/{bid}/queryc           	      C   s`  t | | |jstddd|jr|j stdddt|j}t| |p'i }|dr6td| dd|dd	u rFtd| d
dt	| |}t
|jpPi }||d< t|dp]d|d< t|dphd|d< |dpri |d< ttdd}t|pdd | |d< tt|pd|k|d< tj| |j|j|j|j|j|j||jd	}||d< |S )Nr  zuser_id is requiredr   zmessage is requiredr}   r    is locked for this businessr|   F! is not enabled for this businessra   rg   r  llm_providerr   r   llm_model_namer   llm_runtime_configRAG_AGENT_INSTRUCTION_MAX_CHARSi.  r  agent_instruction_truncated)	r   rX   rY   rZ   r[   r\   r]   r^   r_   )r   rX   r   rY   r   r+   ra   r  r   r  r   r^   rL   rV   r   r   r  r  rZ   r[   r\   r]   r_   )	r   r;  r   ra   	agent_cfgr  r^   max_instruction_charsr  r>   r>   r?   	rag_querym  sB   



r  z*/rag/{bid}/conversations/{conversation_id}   rQ   )rn   geler[   c                 C   s$   t | | tj| ||d}||dS )NrR   )r[   messages)r   r  get_conversation_messages)r   r[   rR   r   r  r>   r>   r?   rag_get_conversation  s   

r  z/rag/{bid}/conversationsrl   c                 C   s   t | | t| ||dS )N)r   rX   rR   )r   r  )r   rX   rR   r   r>   r>   r?   rag_list_conversations  s   
r  z/rag/{bid}/profiles/{user_id}c                 C   s   t | | t| |S r8   )r   r  get_user_profile)r   rX   r   r>   r>   r?   rag_get_profile  s   
r  z/rag/{bid}/score-callc                 C   s   t | | t| |j}|dpi }|ds!tdd|j dt|| t|j}|jr5|s5tddd|p:t	|}t
| |jt|j|t  d dS )	Nr  r   r  r   r  zLLM scoring failedr  )r   rw   ra   	scorecardr  )r   r  rw   r   r   r  r+   ra   ry   r  rL   r   r  rJ  )r   r;  r   r  r  scoredr  r>   r>   r?   rag_score_call  s   


r  z/rag/{bid}/agent-reportr  im  c                 C   s   t | | t| ||S r8   )r   r  )r   r  r  r   r>   r>   r?   rag_agent_report  s   
r  z/rag/{bid}/ingestion-progressd   c                 C   s   t | | t| |dS )N)r  )r   r  )r   r  r   r>   r>   r?   rag_ingestion_progress  s   
r  z%/rag/{bid}/agents/config/{agent_type}c                 C   s  t | | t|}t|  t| |pi }|stdd| d|jd ur'|jn|d|jd ur5t|jntt	|d|j
d urGt|j
ntt	|d|jd urW|jn|d|jd urc|jn|d|jd uro|jn|d	|jd ur{|jn|d
pi d}t }zM| }|dt|d t|d t|d t|d t|d pdt|d	 pdtj|d
 ddt|dp|dpdt| t|f
 W |  n|  w dt| |dS )Nr   Unknown agent type: r   r{   r|   r}   r~   rg   r   r   )r{   r|   r}   r~   rg   r   r   an  
            UPDATE rag_agent_configs
            SET display_name = %s,
                is_enabled = %s,
                is_locked = %s,
                system_prompt = %s,
                provider = %s,
                model_name = %s,
                runtime_config = %s,
                updated_by = %s
            WHERE bid = %s AND agent_type = %s
            r  r   Tr}  usernamer   rx   ok)r   r  )r   r+   r  r  r   r{   r   r|   rV   rU   r}   r~   rg   r   r   r  r  r!  rL   r  r  rx  )r   ra   r;  r   r  
update_objr&  r  r>   r>   r?   rag_upsert_agent_config  sB   
$$	



r  z,/rag/{bid}/agents/{agent_type}/knowledge-pdffilenameappend_to_existing_promptc                    sB  t | | t|}t| |rt| dstdddt|  t| |p)i }|s5tdd| dt|j	
dp=d }d	|vrKtdd
d| I dH }|sZtdddt|dkrftdddt|}	t|	}
|
dk rxtddddd |	D }t|stddddd t|D }ddd |D }|stdddt|}d| }|r|
dr|
d d| }n| d| }t|
dpi }t|t|
dp|
dpdt  d |
t|d|d < ||d!< ||d"< t }d}z| }|d#t| t|f | pi }t|
d$pd%d }||d  d&< |d't|tj|d(d)t|
dp@|
dp@dt| t|f |d*t| t|t|t|t|
tt|tj|d(d)t|t|
dpx|
dpxdf	 W |  n|  w d+t| t|t|t||
t|d,t| |d-S ).z
    Upload a knowledge PDF containing prompt + knowledge bands for an agent.
    For current rollout, this endpoint is restricted to the pre-sales lead agent.
    z.pdfr  zOnly .pdf files are supportedr   r   r  zcontent-typer   zapplication/pdfz,Request content-type must be application/pdfNzUploaded PDF is emptyi   zPDF exceeds 50MB limitrd   z*No pages were detected in the uploaded PDFc                 S   s   g | ]
}t |pd  qS r   r  )r  r  r>   r>   r?   r  ;      z2rag_upload_agent_knowledge_pdf.<locals>.<listcomp>z*Could not extract usable text from the PDFc                 S   s   g | ]\}}|d  |dqS )rd   )r  r  r>   )r  r  r  r>   r>   r?   r  ?  rG  z

c                 S   s,   g | ]}|d  rd|d  d|d   qS )r  z[Knowledge Band Page r  z]
r>   r  r>   r>   r?   r  A  s   , z0No valid knowledge-band content found in the PDFz]Use the following uploaded prompt and knowledge bands as the primary rubric for evaluation.

r~   r   r  r   rx   r  )r  uploaded_byuploaded_atr  
char_countknowledge_pdf_metaknowledge_bandsknowledge_compiled_promptz
            SELECT COALESCE(MAX(version_no), 0) AS max_version
            FROM rag_agent_knowledge_uploads
            WHERE bid = %s AND agent_type = %s
            max_versionr   
version_noz
            UPDATE rag_agent_configs
            SET system_prompt = %s,
                runtime_config = %s,
                updated_by = %s
            WHERE bid = %s AND agent_type = %s
            Tr}  a  
            INSERT INTO rag_agent_knowledge_uploads (
                bid, agent_type, version_no, filename, page_count, char_count,
                knowledge_bands, compiled_prompt, uploaded_by
            )
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
            r  )r  r  r  )r   r   ra   r  filer  )r   r+   r  rL   r   endswithr   r  r  r  r   bodyr   r  any	enumerater  r)   r   r   r  rJ  r  r  r!  rs  rV   r  r  rx  )r   ra   r  r  r  r   r  content_type
file_bytes
page_textsr  cleaned_pagesr  compiled_knowledgebase_instructionknowledge_instructionr~   r   r&  r  r  r)  r>   r>   r?   rag_upload_agent_knowledge_pdf  s   


r  c              	   C   s   t | | t|}t| t| |pi }|dpi }|dp!i }|dp(g }|dp/d}|s>dt| t|ddd	S dt| t|d
||t|dd dd	S )zS
    Preview latest uploaded knowledge PDF extraction for agent configuration.
    r   r  r  r  r   r  FN)r   r   ra   has_uploaded_knowledgeknowledge_pdfTi  )metar  compiled_prompt_preview)r   r+   r  r  r   rL   )r   ra   r   r<   r   r   r  compiledr>   r>   r?   rag_get_agent_knowledge_pdf  s0   
		r  z4/rag/{bid}/agents/{agent_type}/knowledge-pdf/historyc                 C   s   t | | t|}t| t }z| }|dt| t|t|f | p)g }W |	  n|	  w dt| t|t
|dS )zI
    List previous uploaded versions for knowledge PDF on the agent.
    z
            SELECT version_no, filename, page_count, char_count, uploaded_by, created_at
            FROM rag_agent_knowledge_uploads
            WHERE bid = %s AND agent_type = %s
            ORDER BY version_no DESC
            LIMIT %s
            r  )r   r   ra   versions)r   r+   r  r  r  r!  rL   rV   r#  rx  rC  )r   ra   rR   r   r&  r  r(  r>   r>   r?   #rag_get_agent_knowledge_pdf_history  s"   


r  z/rag/{bid}/agents/analyze-callc                 C   s  t | | t|j}t| |pi }|drtd| dd|ddu r/td| dd|jst }z| }|	dt
| t
|jt
|f | }W |  n|  w |rt|d	t
rxzt|d	 |d	< W n	 tyw   Y nw d
t|dS dt| |j|dS )Nr}   r   r  r   r|   Fr  z
                SELECT bid, call_id, agent_type, model_name, summary, result_json, updated_at
                FROM rag_agent_call_analysis
                WHERE bid = %s AND call_id = %s AND agent_type = %s
                LIMIT 1
                result_jsonr{  )r   analysis	generated)r   r+   ra   r  r   r   r   r  r  r!  rL   rw   rs  rx  r@  r  r  r   rC  r  )r   r;  r   ra   r  r&  r  r{  r>   r>   r?   rag_agent_analyze_call  s4   



	r	  z$/rag/{bid}/agents/analysis/{call_id}r`   c                 C   s   t | | t|}t }z| }|dt| t|t|f | }W |  n|  w |s7tdddt	|
dtrTzt|d |d< W n	 tyS   Y nw dt|iS )Nz
            SELECT bid, call_id, agent_type, model_name, summary, result_json, updated_at
            FROM rag_agent_call_analysis
            WHERE bid = %s AND call_id = %s AND agent_type = %s
            LIMIT 1
            r   zNo analysis foundr   r  r  )r   r+   r  r  r!  rL   rs  rx  r   r@  r   r  r  r   rC  )r   rw   ra   r   r&  r  r)  r>   r>   r?   rag_get_agent_analysis  s(   

	r
  z/auth/registerc              	      s   g d}|D ]}|  |std| ddqtj| d | d | d |  d|  d	d
d\}}|dkrStj|d t| d |  d	d
d\}}|dkrSt||dS t||dS )Nr   r  r*  r   r   is requiredr   r  r*  r   r  r   r   )r  r*  r   r  r      r   r   rX   r   r   r   r  )r   r   r   create_userassign_business_accessrL   r   )r;  required_fieldsfieldr  r   assign_resultassign_statusr>   r>   r?   auth_register8  s,   





r  z/auth/loginc                    sb   |  dr|  dstdddtj| d | d |jr |jjnd |j dd\}}t||dS )	Nr  r   r  z"Username and password are requiredr   
User-Agent)
ip_address
user_agentr  )r   r   r   loginclientr   r  r   )r;  r  r  r   r>   r>   r?   
auth_loginP  s   

r  z/auth/logoutc                 C   s"   t |}t|\}}t||dS )Nr  )r   r   logoutr   )r   r   r   r  r   r>   r>   r?   auth_logout]  s   r  z/auth/mec                 C   s   dt | iS )Nr   )r   r   r>   r>   r?   auth_med     r   z/auth/users/{bid}c                 C   sL   t | | |dst|| st| |d t| \}}t|td|idS )Nr   z
users.viewusersr  )r   r   r   r   r   get_users_by_businessr   r   )r   r   r"  r   r>   r>   r?   auth_users_for_businessi  s
   
r$  z/auth/role-permissions/{role}r   c                 C   s   t | }t|}||dS )N)r   permissions)r   normalize_roler   get_role_base_permissions)r   r   
normalizedr%  r>   r>   r?   auth_role_permissionsr  s   


r)  z/business-admin/users/createc                    s<  dD ]}|  |std| ddqt|  d }| ds-t||s-t||d |  dd	}tj| d
 | d | d |  d|dd\}}|dkrSt||dS tj	|d ||d\}}|dkrit||dS |  d}	|	d ur| dst||st||d t
|d |||	\}
}|dkrt||
dS td|dS )Nr  r  r  r   r   r   zusers.creater   r   r  r*  r   r  Fr  r*  r   r  r   r   r  r  r   r  r%  roles.assign)r   r   rL   r   r   r   r   r  r   r  sync_permission_overrides)r;  r   r  r   r   r  r   r  r  desired_permissionsperm_resultperm_statusr>   r>   r?   business_admin_users_createy  sH   



r0  z&/business-admin/users/{user_id}/accessc                    s
  t |dpd }|stddd|dpt||}|d}|d urB|s/t||d t ||\}}|dkrBt||d	S d
|v sJd|v rR|sRt||d d
|v rnt	 ||d
p`g \}}|dkrnt||d	S d|v rt
 ||dp|g \}}|dkrt||d	S d|v r|st||d t|\}}	|	dkrt|	|d	S t fdd|D d }
t|p|
pi dpd}t |||dpg \}}|dkrt||d	S t|\}}|dkrt||d	S t fdd|D d }|stdddtd|iS )Nr   r   r  bid is requiredr   r   r   r+  r  r^  agent_mappingszusers.updater%  c                 3   .    | ]}t |d pdt  kr|V  qdS r   r   NrV   r   r  urX   r>   r?   r
       , z5business_admin_users_update_access.<locals>.<genexpr>r   c                 3   r3  r4  r5  r6  r8  r>   r?   r
    r9  r   zUser not found in business)rL   r   r   r   r   r   r   r  r   replace_group_mappingsreplace_agent_mappingsr#  nextr   r&  r,  r   )rX   r;  r   r   is_privilegedr   r  r   users_previewpreview_statustarget_previeweffective_roler.  r/  r"  targetr>   r8  r?   "business_admin_users_update_access  sV   
rC  z/list-businessesc                   C   s   t  S r8   )r$  get_all_businessesr>   r>   r>   r?   list_businesses  s   rE  z/businesses/{bid}/infoc                 C   s   t | }|stddd|S )Nr   Business not foundr   )r$  get_business_infor   )r   r  r>   r>   r?   business_info  s   
rH  z/reported-issuesc                 C   s  t | jpd }t | jpd }|stddd|s"tdddt|| | jp+d p/d }|sXzt|p9i }|	dpJ|	dpJ|	dpJd }W n t
yW   d }Y nw |	d	}t |pad rit|nd }t  t A}| }	|	d
||| jp|	dpd ||	d|	d| jpd | jpd | jpd |f
 |	j}
|	d|
f |	 }W d    n1 sw   Y  dt|dS )Nr   r  zbusiness_id is requiredr   zissue_text is requiredr   r   businessNamer   a  
            INSERT INTO reported_issues (
                business_id, business_name, business_user_name, user_id, username, user_email,
                page_path, call_id, scope, issue_text, status
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'open')
            r  r*  +SELECT * FROM reported_issues WHERE id = %sTrQ  issue)rL   r   r   r   r   r   r   r$  rG  r   r   r,  rV   ru  r  r  r!  r   r   rw   r   	lastrowidrs  rw  )r;  r   r   r   r   r  raw_user_idrX   r&  r  issue_idrL  r>   r>   r?   create_reported_issue  sZ   



rP  r  )rn   r  r   r   offsetc                 C   s  t |pd }|r|dvrtdddt | pd }|dr"n)|r*t|| n!|dp0g }|s9tdd	dt |d
 dpBd }t|| t  g }g }	|r^|d |	| |rj|d |	| |rtdd| nd}
t 6}|	 }|
d|
 |	 t| pi dpd
}|
d|
 d|	||g  | pg }W d    n1 sw   Y  |D ]}|dd p|d|d< qt||||dS )Nr   >   openresolved	dismissedin_progressr  Invalid issue statusr   r   r   r   zNo business accessr   r   zbusiness_id = %sstatus = %szWHERE  AND z.SELECT COUNT(*) AS total FROM reported_issues totala  
            SELECT
                ri.*,
                COALESCE(NULLIF(ri.business_user_name, ''), NULLIF(bu.full_name, ''), ri.username) AS business_user_name_resolved
            FROM reported_issues ri
            LEFT JOIN business_users bu ON bu.id = ri.user_id
            z`
            ORDER BY ri.created_at DESC, ri.id DESC
            LIMIT %s OFFSET %s
            business_user_name_resolvedr   )issuesrY  rR   rQ  )rL   r   r   r   r   ru  r  r  r  r  r!  rV   rs  r#  poprC  )r   r   rR   rQ  r   status_valuer   r   whereparams	where_sqlr&  r  rY  r[  rL  r>   r>   r?   list_reported_issues!  sR   







ra  z/reported-issues/{issue_id}rO  c                 C   s   t | t|jp	d }|dvrtdddt  t *}| }|d|| f |j	dkr5tdd	d|d
| f |
 }W d    n1 sJw   Y  dt|dS )Nr   >   rR  rS  rT  rU  r  rV  r   z4UPDATE reported_issues SET status = %s WHERE id = %sr   r   zReported issue not foundrJ  TrK  )_require_masterrL   r   r   r   ru  r  r  r!  rowcountrs  rw  )rO  r;  r   r]  r&  r  rL  r>   r>   r?   update_reported_issue`  s"   

rd  z/groupnames/{bid}rP   lsq_row_countc                 C   s8   |rt | d ||d}|dg }|pt| S t| S )Nr   r   rK  r   rR  )r  r   r$  get_all_groupnames)r   rP   re  r   mappingrR  r>   r>   r?   r^  z  s   
r^  z/agentnames/{bid}c                 C   sF   |rt | |||d}dd |dg D }|pt| |S t| |S )Nrf  c                 S   s    g | ]}| d r| d qS )rZ  r  r  r>   r>   r?   r    s     zagentnames.<locals>.<listcomp>rS  )r  r   r$  get_agent_names)r   r   rP   re  r   rh  rS  r>   r>   r?   
agentnames  s   rj  z/location-stats/{bid}x   	date_fromdate_todetailed_callsdetailed_threshold_seconds	directionc	           
      C   s<   t || ||}	tj| |	d |||	d ||||	d |	d d
S )Nr   r   r   r   )r   rn  ro  rp  r   r   )r   r$  get_location_stats)
r   r   rl  rm  r   rn  ro  rp  r   r   r>   r>   r?   location_stats  s   rr  z/location-calls/{bid}rP  c	           
      C   s8   t || |d }	tj| |	d |||||||	d |	d d
S )Nr   r   r   )r   r   )r   r$  get_filtered_raw_calls)
r   r   rp  rP  rR   rQ  rl  rm  r   r   r>   r>   r?   location_calls  s   rt  z/raw-calls/{bid}/{callid}callidc                 C   s,   t | |}|stdddt|| | |S )Nr   r   r   )r$  get_raw_call_detailsr   r   )r   ru  r   rT  r>   r>   r?   raw_call_details  s
   rw  z/analytics/{bid}/pendingc                 C   s   t | |}t||dS )N)countrO  )r$  get_calls_for_analysisr   )r   rR   rO  r>   r>   r?   pending_analytics  s   rz  z/analytics/{bid}/dashboardc                 C   s   t || |d }t| |d |||d |d t| |d |||d |d t| |d |||d |d t| |d |||d |d t| |d |||d |d t| |d |||d |d t| |d |||d |d dS )Nr   r   r   )overviewsentiment_by_locationquality_by_locationquality_by_agentcall_purposesconcerns_frequencybusy_locations)	r   r$  get_analytics_overviewget_sentiment_by_locationget_quality_by_locationget_quality_by_agentget_call_purpose_frequencyget_concerns_frequencyget_busy_locations)r   r   rl  rm  r   r   r>   r>   r?   analytics_dashboard  s.   r  z#/analytics/{bid}/calls-by-objection	objectionc                 C   s,   t || |d }t| ||d |d |d S )Nr   r   r   )r   r$  get_calls_by_objection)r   r  r   r   r   r>   r>   r?   analytics_calls_by_objection	  s   r  z/calls/{bid}sales_intentc              	   C   s   t || }i }	|d ur||	d< |r||	d< |r||	d< |r!||	d< t| |	|||d |d }
t| |	|d |d }|
|||dS )Nr   r  rl  rm  r   r   )rO  rY  rR   rQ  )r   r$  	get_callsget_calls_count)r   r   r  rl  rm  rR   rQ  r   r   filtersrO  total_countr>   r>   r?   
calls_list	  s"   
r  z/calls/{bid}/{callid}c                 C   s  t | |}|stdddt|| | t | |}|r^|dd|d< |dd|d< |d}|rLt|trLzt	|}W n tj
yK   g }Y nw ||d< |d	|d	< |d
|d
< t | |}|r|dd|d< |dd|d< |dd|d< |dd|d< |d|d< |d|d< |d|d< |d|d< |d|d< |dd|d< |d|d< |d|d< |d|d< |d|d< |d|d< t | |}|r|d|d< |d|d< |S )Nr   r   r   r  r   transcriptsr  speaker_segmentsnum_speakersdurationr  call_purposeobjections_concernsobjection_typer  propensity_scorepropensity_bandpropensity_parameter_scorespropensity_parameter_detectionsr  
sentimentsparameter_scoresr\  agent_speak_percentagecustomer_speak_percentagedead_air_percentager  bant_profilebant_summary)r$  r  r   r   r  r   r@  rL   r  r  JSONDecodeErrorr  r  )r   ru  r   rT  transcript_datar  analytics_data	bant_datar>   r>   r?   call_details7	  sP   
r  z/bant/{bid}/{callid}c                 C       t | |}|stddd|S )Nr   zBANT not foundr   )r$  r  r   )r   ru  r  r>   r>   r?   bant_detailsi	     r  z /calls/{bid}/{callid}/transcriptc                 C   s"   t | | t | | d|dS )NzTranscript deleted successfully)rY   ru  )r$  delete_transcriptreset_transcription_status)r   ru  r>   r>   r?   delete_call_transcriptq	  s   
r  z-/calls/{bid}/{callid}/segment/{segment_index}segment_indexc                 C   s@   t |dd }|stdddt| ||| d||dS )Nr  r   r  zText cannot be emptyr   zSegment updated successfully)rY   ru  r  )rL   r   r   r   r$  update_speaker_segment_text)r   ru  r  r;  new_textr>   r>   r?   patch_call_segmentx	  s
   r  z/calls/{bid}/recentc                 C   s"   t || }t| ||d |d S )Nr   r   )r   r$  get_recent_calls)r   rR   r   r   r>   r>   r?   recent_calls	  s   
r  z/calls/searchc                 C   sb   |  d}|  dd}t|  dd}|stdddt|t|}tt||||d	 |d
 S )Nr   r  r   rR   rl   r  zBusiness ID is requiredr   r   r   )r   rV   r   r   rL   r$  search_calls)r;  r   r   r  rR   r   r>   r>   r?   calls_search	  s   
r  z/leads/{bid}transcripts_onlyc           	   
   C   sV   t || |d }tj| |d |||||d |d d}|dg t|dd||dS )	Nr   r   r   )r   r   rR   rQ  r  rp  r   r   r  rY  r   )r  rY  rR   rQ  )r   r$  get_leads_listr   rV   )	r   r   rR   rQ  r  rp  r   r   r  r>   r>   r?   
leads_list	  s   
"
r  z/leads/{bid}/bulk-uploadc           	   
   C   sH  t | | t|p	i dpd }|stdddz	tj|dd}W n ty6 } ztddd|d	}~ww |s?tdd
dz|d}W n t	yV   |jddd}Y nw |pZi d}zt
tt| ||rmt| nd	d}W |S  ty } z	tdt|d|d	}~w ty } ztd|  tdd| d|d	}~ww )zMImport calls from CSV (base64) into {bid}_raw_calls for bulk lead recordings.file_data_base64r   r  zfile_data_base64 is requiredr   F)validatezInvalid file_data_base64NzCSV file is emptyz	utf-8-sigzlatin-1r   r  r   )r   zBulk upload failed for bid %sr  zBulk upload failed: )r   rL   r   r   r   base64	b64decoder   r  UnicodeDecodeErrorr.   r$  r  r  	exception)	r   r;  r   file_b64	csv_bytesr  csv_textr   r  r>   r>   r?   leads_bulk_upload	  sD   
r  z)/leads/{bid}/{lead_phone:path}/recordingsrX  c              
   C   s   t | | t|}ztttt| ||pi W S  ty+ } z	tdt|d|d}~w tyF } zt	
d|  tdd| d|d}~ww )z:Upload a single recording (URL or base64 file) for a lead.r  r   Nz'Lead recording upload failed for bid %sr  zUpload failed: )r   r   r/   r$  r   rL   r  r   r   r  r  )r   rX  r;  r   decoded_phoner  r>   r>   r?   leads_upload_recording	  s$   
r  r  c                 C   
   t | S r8   )r  r  )r  r>   r>   r?   _invoke_lead_insights_chat	  s   
r  c                 C   sb   | dpg D ]'}| d}|r| drqt| |}|r.| d|d< | dp+d|d< q|S )NrO  ru  r  r  r  r   r  )r   r$  r  )r   rM  rT  ru  r  r>   r>   r?   _enrich_lead_details_with_bant	  s   
r  z'/leads/{bid}/{lead_phone:path}/insightsc                 C   sJ   t |}tj| |d}|stdddt| |}t| }dt||diS )Nr   rX  r   Lead not foundr   insights)data_capture_fields)r   r$  get_lead_detailsr   r  list_data_capture_fieldsr-   )r   rX  r  rM  r  r>   r>   r?   lead_insights
  s   

r  z0/leads/{bid}/{lead_phone:path}/insights/generatec                 C   st   t |}tj| |d}|stdddt| |}dd |dg D }|s+tdd	dt| }d
t|td|diS )Nr  r   r  r   c                 S   s   g | ]	}| d r|qS )r  r  rS  r>   r>   r?   r  
  r  z*generate_lead_insights.<locals>.<listcomp>rO  r  z#No transcripts found for this lead.r  T)invoke_chatuse_llmr  )	r   r$  r  r   r  r   r  r-   r  )r   rX  r  rM  calls_with_transcriptsr  r>   r>   r?   generate_lead_insights
  s    

r  z/leads/{bid}/{lead_phone:path}include_crmc                 C   s  t |}t|| |d }tj| ||d |d |d d}|s#tdddddd	d d
}|rAt| d	}	|	rA|	drA|	drA|	drAd|d< tj| d	|d}
|
rbt|
d|d dsbd }
|
rd|d< |
dpni |
d|
d|
d|
d|
d|
dd|d< nzddl	m
} ||	d |	d |	dpdd }||}|d!r-|d"r-|d" d }|d#pd$ }|d%pd$ }|d& |  p|d'pd }|d(p|d)p|}dd l}|d*p||  }tj| d	|||d+|d,||d-|d.	 d|d< |||d,||d-|d+|d/d|d< nd0|d1< W n ty@   d0|d1< Y nw ||d2< t|||d3< |S )4Nr   r   r   )r   rX  r   r   r   r   r  r   Fr@  )r_  r`  rg   rW  rA  rB  rC  Tr_  r   rg   r+  lead_payload)r  r+  r"  r`  	lead_namer*  phone_primaryr5  rY  ra  r9  rW  r   r&   rE  $https://api-in21.leadsquared.com/v2/rF  rQ  r>  r	  r   r  r  r  r  r  r$  r6  r)  r3  )	r   rg   external_lead_idr  rY  r*  r  r5  r  r8  z"No CRM record found for this lead.rY   rN  r  )r   r   r$  r  r   rG  r   get_cached_crm_lead_by_phoner#  leadsquared_servicer'   search_lead_by_phoner   r-  r.  r/  r0  upsert_crm_lead_cacher   rj  )r   rX  r   r  r   r  r   rM  rN  rH  r{  r'   lsqlivelead_recr  r  r   	phone_val_hleidr>   r>   r?   lead_detail)
  s   *
	
r  z&/leads/{bid}/{lead_phone:path}/profiler  c           
      C   s4  ddl }t|}tj| d|d}|pi dpi }t|tr5zddl}||}W n t	y4   i }Y nw i |dd |
 D }|pEi dpT|| d	  }	tj| d|	|d
pe|pbi d|dpp|pmi d|dp{|pxi d||dp|pi d|dp|pi d|d
 ddiS )z@Persist manually-entered lead profile data into crm_leads_cache.r   Nr@  r  r  c                 S   &   i | ]\}}|d ur|dkr||qS r   r>   rD  r>   r>   r?   rF  
     & z'update_lead_profile.<locals>.<dictcomp>r  z|manualr  r  rY  r*  r5  ra  )
r   rg   r  r  rY  r*  r  r5  ra  r  rQ  T)r-  r   r$  r  r   r@  rL   r  r  r   rK  r.  r/  r0  r  )
r   rX  r  r  r  r  existing_payload_jsonmerged_payloadr  r>   r>   r?   update_lead_profile
  s4   
&r  z/analytics/{bid}/statsc                 C      t | ||S r8   )analytics_serviceget_call_statisticsr   rl  rm  r>   r>   r?   analytics_stats
     r  z/analytics/{bid}/sentimentc                 C   r  r8   )r  get_sentiment_distributionr  r>   r>   r?   analytics_sentiment
  r  r  z/analytics/{bid}/intentc                 C   r  r8   )r  get_intent_distributionr  r>   r>   r?   analytics_intent
  r  r  z/analytics/{bid}/trendsday   periodc                 C   r  r8   )r  
get_trends)r   r  r  r>   r>   r?   analytics_trends
  r  r  z/analytics/{bid}/agentsc                 C   r  r8   )r  get_agent_performancer  r>   r>   r?   analytics_agents
  r  r  z/analytics/{bid}/leaderboardc              	   C   s<   t || |d}t| |d |||d |d }|t|dS )z7Agent leaderboard ranked by average call quality score.Nr   r   r   )leaderboardtotal_agents)r   r$  get_agent_leaderboardr   )r   r   rl  rm  r   r   r>  r>   r>   r?   analytics_leaderboard
  s   	r  z/analytics/{bid}/keywordsrk   c                 C   s   t | |||S r8   )r  get_top_keywords)r   rR   rl  rm  r>   r>   r?   analytics_keywords
  s   r  z/analytics/{bid}/{callid}c                 C   r  )Nr   z!Analytics not found for this callr   )r$  r  r   )r   ru  r  r>   r>   r?   call_analytics
  r  r  z/queue-calls/{bid}c                 C   s0   t j| ddidd}dt| dt|| dS )Nr   r   rQ   r  zQueued z calls for processing)rY   rx  r   )r$  r  r   )r   rO  r>   r>   r?   queue_calls
  s   r  z/process-calls/{bid}c                 C   s   t dd| ddS )N   zProcessing started)rY   r   r  )r   r   r>   r>   r?   process_calls
  s   r  z/transcripts/{bid}c                 C   r  r8   )r$  get_transcriptsr  r>   r>   r?   r	  
     
r	  z/export/{bid}/callsr  r6   c           
      C   s   dd |||d  D }tj| |dd}|dkrKt }|r2tj||d  d}|  || d	d
|  dt	
 d di}	t| d|	dS |S )Nc                 S      i | ]\}}|d ur||qS r8   r>   rD  r>   r>   r?   rF     rG  z export_calls.<locals>.<dictcomp>)r   rl  rm  i'  r  csvr   )
fieldnamesContent-Dispositionzattachment; filename=calls_r   %Y%m%d.csvtext/csvr  
media_typer  )rK  r$  r  r   r  
DictWriterr  writeheader	writerowsr   rz  strftimer   getvalue)
r   r6   r   rl  rm  r  rO  outputwriterr  r>   r>   r?   export_calls
  s   
 r  c                 C   s   dd t | pddD S )Nc                 S      g | ]
}|  r|  qS r>   r   r  r>   r>   r?   r    r  z%_split_csv_filter.<locals>.<listcomp>r   ,)rL   r  rL  r>   r>   r?   _split_csv_filter  r?  r  customer_namequality_minquality_maxinclude_quality_filtersc                 C   sp  g }g }| r| d | |  |r| d | | |r(| d | | t|}|rFddgt| }| d| d || |rV| d | d	| d	 |rb| d
 | | |	rn| d | |	 t|prd  }|dkr| d n	|dkr| d |
r|d ur| d | | |d ur| d | | |rdd| |fS d|fS )Nzr.groupname = %szDATE(r.call_starttime) >= %szDATE(r.call_starttime) <= %sr  r  zr.agentname IN ()z)CAST(r.customer_callinfo AS CHAR) LIKE %s%zLOWER(r.direction) = LOWER(%s)z UPPER(r.call_status) = UPPER(%s)r   r   z=TIMESTAMPDIFF(SECOND, r.call_starttime, r.call_endtime) > 120nozZ(TIMESTAMPDIFF(SECOND, r.call_starttime, r.call_endtime) <= 120 OR r.call_endtime IS NULL)za.quality_score >= %sza.quality_score <= %sz WHERE rX  )r  r  r  r   extendrL   r   r   )r   rl  rm  r   r   rn  r!  r"  rp  rP  r#  where_clausesr_  rS  r'  detailedr>   r>   r?   _export_filters  sL   
















r*  customer_numberzCUSTOMER NUMBERzCUSTOMER NAMEzCALL IDrZ  z
AGENT NAMEz
GROUP NAMEcall_starttimezCALL START TIMEcall_endtimezCALL END TIME	DIRECTIONzCALL STATUSduration_secondszDURATION SECONDStranscription_statuszTRANSCRIPTION STATUSr  LANGUAGEr  zNUM SPEAKERStranscript_durationzTRANSCRIPT DURATIONr  
TRANSCRIPTr  zQUALITY SCOREr  	SENTIMENTzCALL PURPOSEzOBJECTIONS CONCERNSzOBJECTION TYPEzTALK LISTEN RATIOzAGENT SPEAK PERCENTAGEzCUSTOMER SPEAK PERCENTAGEzDEAD AIR PERCENTAGESUMMARYzPARAMETER SCORES)	r  r  r  r\  r  r  r  r  r  )r+  r   ru  DURATIONsecondsc              	   C   sp   z
t tt| }W n ttfy   Y dS w |dk rd}|d }|d d }|d }|dd|dd|dS )Nr   r     r  02drL  )rV   r  rb   	TypeErrorr  )r7  rY  hoursminutessecsr>   r>   r?   _format_seconds_as_hhmmssg  s   r>  leftrightc                 C   s   dt |  dt | dS )zNTwo-part ratio for export (e.g. 49:51). Excel formula avoids 49:51:00 display.z="rL  ")rV   )r?  r@  r>   r>   r?   _export_ratio_mm_sst  s   rB  c              	   C   sH   | d u st |  dkrd S z	ttt| W S  ttfy#   Y d S w r   )rL   r   rV   r  rb   r:  r  rL  r>   r>   r?   _parse_speak_percentagey  s   rC  agent_talk_timecustomer_talk_timec              	   C   s~   zt dt| pd}t dt|pd}W n ttfy    Y d S w || }|dkr+d S ttd| | ttd| | fS )N        r   r  )rp  rb   r:  r  rV   r  )rD  rE  agent_secondscustomer_secondsrY  r>   r>   r?   _ratio_from_talk_times  s   (rI  c                 C   s  | du rdS t | tr2tdt|  }t|d\}}t|d\}}|dkr*||fS |d | |fS t | trM| jdkrB| j| j	fS | jd | j | j	fS t | tt
frt | tst
| }d|  k rgdk rn dS tt|d }t|d\}}t|d\}}|dkr||fS |d | |fS dS t|  }|r| dv rdS tdd	|}	d
d |	d	D }
t|
dk rdS dtdtfdd}t|
dkr'|
d }|dv s|ddd rtt
|dkr||
d ||
d fS |
d dv r|
d ddd r|
d ddd r||
d ||
d fS ||
d ||
d fS t|
dkrDtdd |
D rD||
d ||
d fS dS )z?Normalize mixed inputs to (left, right) for MM:SS-style export.Nr   r8  r  rd   iQ r]  NAz\s*:\s*rL  c                 S   s   g | ]}|d kr|qS r  r>   r  r>   r>   r?   r        z2_parse_talk_listen_ratio_parts.<locals>.<listcomp>r  r  r7   c                 S   s   t tt| S r8   )rV   r  rb   )r  r>   r>   r?   _to_int  s   z/_parse_talk_listen_ratio_parts.<locals>._to_int   )r1  00z0.0z0.00.r   )r1  rO  c                 s   s"    | ]}| d dd V  qdS )rP  r   rd   N)r   r,  r  r>   r>   r?   r
    rV  z1_parse_talk_listen_ratio_parts.<locals>.<genexpr>)r@  r   rp  rV   total_secondsdivmoddt_timehourminutesecondrb   rU   r  rL   r   rR  r  r  r  r   r   r,  all)r   rY  r;  remr<  r7  numericrQ  r  r(  partsrM  thirdr>   r>   r?   _parse_talk_listen_ratio_parts  sT   


*>"r\  c                 C   s   t | d}t | d}|dur#|dur#|dks|dkr#t||S t| d| d}|r9t|d |d S t| d}|rKt|d |d S t| dpRd	 }| d
v r^dS |S )zIStandardize Talk Listen Ratio to MM:SS-style strings (e.g. 49:51, 81:19).r  r  Nr   rD  rE  rd   r\  r   rJ  rK  )rC  r   rB  rI  r\  rL   r   rR  )r)  	agent_pctcustomer_pctfrom_talk_timesr  r:  r>   r>   r?   !_quality_export_talk_listen_ratio  s    
r`  r(  c                 C   sB   g }| pg D ]}t |}d|v rt|d|d< || q|S )Nr/  )r   r>  r   r  r(  preparedr)  new_rowr>   r>   r?   "_prepare_transcription_export_rows  s   rd  c                 C   sN   g }| pg D ]}t |}d|v rt|d|d< t||d< || q|S )Nr/  r\  )r   r>  r   r`  r  ra  r>   r>   r?   _prepare_quality_export_rows  s   re  c           
   
   C   s   |s|S dd |D }dd |D }i }|r;zt | d|pi }W n ty: } ztd| | W Y d }~nd }~ww g }|D ]5}t|}t|dd pR|dpRd	 }||p\i }	||d< |d	pl|	d
pld|d	< |
| q?|S )Nc                 S   s,   g | ]}t |d p|dpd qS )r+  r[  r   rL   r   r   rn  r>   r>   r?   r    s    z/_add_customer_export_fields.<locals>.<listcomp>c                 S   r2  r>   r>   )r  r+  r>   r>   r?   r    r5  r@  z2Customer name enrichment skipped for export %s: %sr[  r+  r   r   r  )r$  get_crm_enrichment_for_phonesr   r  warningr   rL   r\  r   r   r  )
r   r(  phonescrm_by_phoner  r   r)  rc  r+  crm_infor>   r>   r?   _add_customer_export_fields  s.   "rl  header_labelsc                    sX   |pt  g }| p	g D ]tt}|dd  D  | fdd|D  q
|S )Nc                 s   s    | ]	}|t vr|V  qd S r8   )EXPORT_FIRST_COLUMNSr  r=   r>   r>   r?   r
    r.  z*_format_export_csv_rows.<locals>.<genexpr>c              	      s2   i | ]}  |t|d d  |dqS )r   r  r   )r   rL   r   rR  ro  labelsr)  r>   r?   rF    s    $z+_format_export_csv_rows.<locals>.<dictcomp>)EXPORT_HEADER_LABELSrK   rn  r'  r  r  )r(  rm  	formattedordered_keysr>   rp  r?   _format_export_csv_rows  s   
ru  c                 C   sd   t | |d} t }| r"tj|t| d  dd}|  ||  dd| i}t|	 d|dS )	Nrm  r   r  )r  extrasactionr  zattachment; filename=r  r  )
ru  r   r  r  rK   r  r  r  r   r  )r(  r  rm  r  r  r  r>   r>   r?   _csv_response  s   
rx  z/export/{bid}/transcriptionsc                 C   s  |  d}|  d}|  d}t  }| }t ||s!g }nt ||}|d up.|d u}t ||}|rQ|sQtg d|  dt d dW  d    S t|||||||||	|
|d\}}t 	||}|rpd	| d
nd}|
d|d  d|d  d|d  d|d  d| d| d| d| t | pg }t| |}t|}W d    n1 sw   Y  d|  dt d d}t||tdS )Nr  _sarvamresponser  transcriptions_r   r  r  r   r   rn  r!  r"  rp  rP  r#  zINNER JOIN `z` a ON r.callid = a.callidr   a  
                SELECT
                    r.customer_callinfo AS customer_number,
                    r.callid,
                    r.agentname,
                    r.groupname,
                    r.call_starttime,
                    r.call_endtime,
                    r.direction,
                    r.call_status,
                    TIMESTAMPDIFF(SECOND, r.call_starttime, r.call_endtime) AS duration_seconds,
                    r.transcription_status,
                    r  z& AS num_speakers,
                    r2  z- AS transcript_duration,
                    r  z$ AS transcript,
                    r  z' AS raw_response
                FROM `z` r
                z
                \
                ORDER BY r.call_starttime DESC
                LIMIT 10000
                rv  )r$  get_connectionr  _table_existsrx  r   rz  r  r*  sarvam_export_select_exprsr!  %prepare_transcription_csv_export_rowsr#  rl  rd  "TRANSCRIPTION_EXPORT_HEADER_LABELS)r   r   rl  rm  r   r   rn  r!  r"  rp  rP  	raw_tablesarvam_tableanalytics_tabler&  r  r(  
has_sarvamneeds_quality_joinhas_analyticsr`  r_  sarvam_colsquality_join_sqlr  r>   r>   r?   export_transcriptions$  sj   



 


5r  z/export/{bid}/qualityc                 C   s   |  d}|  d}t  K}| }t ||rt ||s"g }n0t|||||||||	|
dd\}}|d| d| d| d| | pHg }t| |}t|}W d    n1 s\w   Y  d	|  d
t	
 d d}t||tdS )Nr  r  Tr{  a  
                SELECT
                    r.customer_callinfo AS customer_number,
                    r.callid,
                    r.agentname,
                    r.groupname,
                    r.call_starttime,
                    r.call_endtime,
                    r.direction,
                    r.call_status,
                    TIMESTAMPDIFF(SECOND, r.call_starttime, r.call_endtime) AS duration_seconds,
                    a.quality_score,
                    a.sentiment,
                    a.call_purpose,
                    a.objections_concerns,
                    a.objection_type,
                    a.talk_listen_ratio,
                    a.agent_talk_time,
                    a.customer_talk_time,
                    a.agent_speak_percentage,
                    a.customer_speak_percentage,
                    a.dead_air_percentage,
                    a.summary,
                    a.parameter_scores
                FROM `z ` r
                INNER JOIN `z+` a ON r.callid = a.callid
                r|  quality_r   r  r  rv  )r$  r}  r  r~  r*  r!  r#  rl  re  r   rz  r  rx  QUALITY_EXPORT_HEADER_LABELS)r   r   rl  rm  r   r   rn  r!  r"  rp  rP  r  r  r&  r  r(  r`  r_  r  r>   r>   r?   export_qualityn  sH   



!
6r  c                 C   sp   | j dpddd  }| j dp| j dpddd  }|r0| d| d	S t| jd	S )
Nzx-forwarded-protohttpsr  r   zx-forwarded-hostr   r   z://r  )r  r   r  r   r  rL   r  )r  	forwardedr   r>   r>   r?   _public_api_base  s
   *r  x_ingest_secretx_webhook_secretc                 C   s   | p|pd  p	d S r   r  )r  r  r>   r>   r?   _resolve_ingest_secret  s   r  r  c                 C   s   t | dd}t|| dS )Nhttp_statusr  r  )rV   r\  r   )r  r   r>   r>   r?   _call_ingest_response  s   r  z"/v1/bids/{bid}/calls/ingest/schemac                 C   s   t | t|S )z>Public JSON schema + URL for Mcube or any external integrator.)r2   r  )r   r  r>   r>   r?   call_ingest_schema_v1  s   r  z/v1/bids/{bid}/calls/ingestX-Ingest-Secret)rn   aliaszX-Webhook-Secretc                 C   s   t j| |t||d}t|S )zOUniversal per-BID call ingest. Opt-in via webhook_ingest_enabled (default off).ingest_secret)call_ingest_serviceprocessr  r  )r   r;  r  r  r  r>   r>   r?   call_ingest_v1  s   r  z/webhook/call-ingest/schemac                 C   s,   |rt || t|t| S i tddiS )z@Schema for settings UI; optional bid returns per-BID ingest URL.notez3Pass ?bid=6004 for per-BID ingest URL and settings.)r   r2   r  r1   )r  r   r   r>   r>   r?   webhook_call_ingest_schema  s   
r  z/webhook/call-ingestc                 C   sV   t | dpd }|stddddd |  D }tj||t||d}t|S )	uC   Alias for /v1/bids/{bid}/calls/ingest — bid must be in JSON body.r   r   r  z.bid is required in JSON body for this endpointr   c                 S   s   i | ]\}}|d kr||qS r  r>   rD  r>   r>   r?   rF    rG  z-webhook_call_ingest_alias.<locals>.<dictcomp>r  )	rL   r   r   r   rK  r  r  r  r  )r;  r  r  r   r  r  r>   r>   r?   webhook_call_ingest_alias  s   r  z/webhook/call-updatec              
   C   s   |  d}|  d}|r|stdddt|||  di }|rOztj|ddd	|gd
 W ddiS  tyN } ztd||| W Y d }~ddiS d }~ww tddd)Nr   ru  r  zbid and callid are requiredr   r>  Frd   Tr  z%RAG auto-ingest skipped for %s/%s: %srY   zCall updated successfullyr  zFailed to update call)	r   r   r$  update_callr  r  r   r  rh  )r;  r   ru  rQ  rag_excr>   r>   r?   webhook_call_update  s   

r  z/webhook/conversation-summaryc                 C   sX   |  d}|  d}|  d}|r|stdddt|||}|s(tdddd	d
iS )Nr   ru  transfer_reasonr  z#business_id and callid are requiredr   r  zFailed to save summaryrY   zSummary saved successfully)r   r   r$  save_conversation_summary)r;  r   ru  r  rQ  r>   r>   r?   webhook_conversation_summary  s   


r  z/data-capture-fields/{bid}c                 C      t | | t| }d|iS Nfields)r   r$  r  )r   r   r  r>   r>   r?   data_capture_fields_list"     

r  c                 C   s6   t | | t| | t| |pi dpg }d|iS r  )r   r   r$  replace_data_capture_fieldsr   )r   r;  r   r  r>   r>   r?   data_capture_fields_save)  s   

r  z/quality-parameters/{bid}c                 C   r  r8   )quality_params_handlerget_parametersr  r>   r>   r?   quality_parameters_list1  r
  r  c                 C   s   t | |pi }d|dS NzParameter saved successfullyrY   parameter_id)r  save_parameterr   r;  r  r>   r>   r?   quality_parameters_save6  s   
r  z/admin/default-parametersc                 C   s   t |  tjdddS )Nrn   8329seed_from_bid)rb  r  get_template_parametersr  r>   r>   r?   admin_default_parameters_list<  s   r  c                 C   s"   t | td| p
i }d|dS )Nrn   z$Default parameter saved successfullyr  )rb  r  save_template_parameter)r;  r   r  r>   r>   r?   admin_default_parameters_saveB     
r  z$/admin/default-parameters/{param_id}param_idc                 C   s,   t | td| }|stdddddiS )Nrn   r   zDefault parameter not foundr   rY   z&Default parameter deleted successfully)rb  r  delete_template_parameterr   )r  r   deletedr>   r>   r?   admin_default_parameters_deleteI  s
   r  z$/quality-parameters/{bid}/{param_id}c                 C   r  )Nr   zParameter not foundr   )r  get_parameter_by_idr   )r   r  	parameterr>   r>   r?   quality_parameter_getR  r  r  c                 C   $   t | |}|stdddddiS Nr   z+Parameter not found or could not be deletedr   rY   zParameter deleted successfully)r  delete_parameterr   r   r  rQ  r>   r>   r?   quality_parameter_deleteZ     r  z /quality-parameters/{bid}/groupsc                 C   r  r8   )r  get_parameter_groupsr  r>   r>   r?   quality_groupsb  r
  r  z%/quality-parameters/{bid}/total-scorec                 C      dt | iS Ntotal_score)r  calculate_total_possible_scorer  r>   r>   r?   quality_total_scoreg  r  r  z /quality-parameters/{bid}/uploadr  c              
      R   |  I d H }zt| |jpd|W S  ty( } z	tdt|d|d }~ww Nz
upload.csvr  r   )readr  import_parameters_filer  r  r   rL   r   r  r  r  r>   r>   r?   quality_parameters_uploadl     r  z/propensity-parameters/{bid}seedc                 C   s$   t   |rt |  t j| ddS )NF)active_only)propensity_params_handlerensure_tableseed_defaults_if_emptyr  )r   r  r>   r>   r?   propensity_parameters_listw  s   
r  c                 C   s"   t   t | |p
i }d|dS r  )r  r  r  r  r>   r>   r?   propensity_parameters_save  r  r  z!/propensity-parameters/{bid}/seedc                 C   s   t   t | }d|dS )NzDefaults seeded)rY   inserted)r  r  r  )r   r  r>   r>   r?   propensity_parameters_seed  s   

r  z'/propensity-parameters/{bid}/{param_id}c                 C   r  r  )r  r  r   r  r>   r>   r?   propensity_parameter_delete  r  r  z(/propensity-parameters/{bid}/total-scorec                 C   r  r  )r  r  r  r>   r>   r?   propensity_total_score  r  r  z#/propensity-parameters/{bid}/uploadc              
      r  r  )r  r  r  r  r  r   rL   r  r>   r>   r?   propensity_parameters_upload  r  r  z /objection-classifications/{bid}business_typerC  c                 C   s*   |d u rd nt | dk}t| ||S )Nr   )rL   r   objection_handlerget_all_classifications)r   r  rC  activer>   r>   r?   objection_classifications  s   r  z4/objection-classifications/{bid}/{classification_id}classification_idc                 C   r  )Nr   Classification not foundr   )r  get_classification_by_idr   )r   r  classificationr>   r>   r?   objection_get_by_id  r  r  c                 C   sB   |r| dstdddt| || dd}td|dd	d
S )Ncategory_namer  zcategory_name is requiredr   
created_byr   r  z#Classification created successfully)r   rY   r  )r   r   r  create_classificationr   )r   r;  r  r>   r>   r?   objection_create  s   r  c              	   C   s8   t | ||pi |p
i dd}|stdddddiS )N
updated_byr   r   z+Classification not found or no changes mader   rY   z#Classification updated successfully)r  update_classificationr   r   )r   r  r;  rQ  r>   r>   r?   objection_update  s    r  c                 C   r  )Nr   r  r   rY   z#Classification deleted successfully)r  delete_classificationr   r   r  rQ  r>   r>   r?   objection_delete  r  r  z;/objection-classifications/{bid}/{classification_id}/togglec                 C   r  )Nr   r  r   rY   z*Classification status toggled successfully)r  toggle_active_statusr   r  r>   r>   r?   objection_toggle  r  r  z'/objection-classifications/{bid}/searchqc                 C   s   t | |S r8   )r  search_classifications)r   r  r>   r>   r?   objection_search  r!  r  z7/objection-classifications/{bid}/by-severity/{severity}severityc                 C   s    |dvr
t dddt| |S )N)r  r  highcriticalr  zInvalid severity levelr   )r   r  get_classifications_by_severity)r   r  r>   r>   r?   objection_by_severity  s   r  z)/objection-classifications/{bid}/classifyc                 C   s*   |pi  d}|stdddt| |S )Nobjection_textr  zobjection_text is requiredr   )r   r   r  classify_objection)r   r;  r  r>   r>   r?   objection_classify  s   r  z+/objection-classifications/{bid}/statisticsc                 C   r  r8   )r  get_statisticsr  r>   r>   r?   objection_statistics  r
  r  z'/crm/{bid}/leadsquared/presales-mappingc                 C   s:   t | | t| |||d}t|drd|dS d|dS )N)r   rK  r   rQ  r  r  r  )r   r  r   r   )r   r   re  r   r   rh  r>   r>   r?   crm_presales_mapping  s   
 r	  z"/crm/{bid}/leadsquared/integrationc                 C   s8   t | | t| d}|stdddd ddS d|dS )	Nr@  r   Fz!LeadSquared integration not found)rQ  rY   r>  r  TrQ  r>  )r   r$  get_crm_integrationr   )r   r   integrationr>   r>   r?   crm_lsq_get_integration  s
   

r  c           	   
   C   s   t | | t| | t|dd }t|dd }t|dd }t|dd}t| d}|r>t| dni }|sH|dd}|sP|d	d}|rT|sZt	d
dd|se|p_i dpdd}tj
| d|||||dpsi d dddS )Nlsq_access_keyr   lsq_secret_keylsq_api_hostrC  Tr@  rA  rB  r  z.lsq_access_key and lsq_secret_key are requiredr   rE  r  ri   )r   rg   rA  rB  rE  rC  ri   z*LeadSquared integration saved successfullyrQ  rY   )r   r   rL   r   r   rU   r$  r  rG  r   upsert_crm_integration)	r   r;  r   rA  rB  rE  rC  existing_integrationexisting_credsr>   r>   r?   crm_lsq_save_integration  s4   


	r  z0/crm/{bid}/leadsquared/integration/push-activityc                 C   s   t | | t stddd|stdddt| }||}z)tj| d|dr+dnd	|d
|d|dr;dn|d||dd W n tyW   t	
d|  Y nw t|drcd|dS d|dS )Nr   !CRM write operations are disabledr   r  zActivity payload is requiredr@  rQ  pushedfailedRelatedProspectIdActivityEventz Manual LeadSquared activity pushrY   r>  )r   rg   r   crm_lead_idactivity_eventrY   payload_previewresponse_previewz6Failed to write manual LeadSquared push log for bid=%sr  r  )r   r>  r   rI  create_activityr$  log_crm_pushr   r   r  r  r   r   r;  r   r|  r  r>   r>   r?   crm_lsq_push_activity$  s,   



 r"  z /crm/{bid}/leadsquared/push-logsc           	      C   s   t | | t| d}|pi dpi }t|o%|do%|do%|d}|s3dddg d	||d
S tj| d|||d}ddd|S )Nr@  ri   rC  has_credentialsenabledTFz7LeadSquared integration is not active for this businessr   )rQ  r$  rY   logsrY  rR   rQ  )rg   rR   rQ  r   )rQ  r$  )r   r$  r  r   rU   get_crm_push_logs)	r   rR   rQ  r   r   r  ri   r  r>  r>   r>   r?   crm_lsq_push_logsB  s   
&	r'  z'/crm/{bid}/leadsquared/integration/testc                 C   s   t | | t| d}|r|dr|ds tdddddS t|d |d |d	d
}| }|dr?t| d |S td|dS )Nr@  rA  rB  r   Fz&LeadSquared integration not configuredr  r  rE  rF  rQ  r  )r   r$  rG  r   r   r'   test_connectionmark_crm_integration_tested)r   r   rH  r|  r  r>   r>   r?   crm_lsq_test_integration\  s   

r*  c                 C   s6   t | | t| | t| d}d|rddS ddS )Nr@  Tz,LeadSquared integration deleted successfullyz%LeadSquared integration did not existr  )r   r   r$  delete_crm_integration)r   r   removedr>   r>   r?   crm_lsq_delete_integrationj  s   

r-  z#/crm/{bid}/leadsquared/leads/searchc                 C   s    t | | t| }||pi S r8   )r   rI  rs  )r   r;  r   r|  r>   r>   r?   crm_lsq_search_leadsr  s   
r.  z&/crm/{bid}/leadsquared/leads/{lead_id}r'  c                 C   s<   t | | t| }||}t|drd|dS d|dS )NrQ  r  r  r  )r   rI  get_leadr   r   r   r'  r   r|  r  r>   r>   r?   crm_lsq_get_leady  s   

 r1  z/crm/{bid}/leadsquared/leadsc                 C   sR   t | | t stdddt| }||pi }t|dr$d|dS d|dS Nr   r  r   rQ  r  r  r  )r   r>  r   rI  create_leadr   r   r!  r>   r>   r?   crm_lsq_create_lead  s   
 r4  c                 C   sT   t | | t stdddt| }|||pi }t|dr%d|dS d|dS r2  )r   r>  r   rI  update_leadr   r   )r   r'  r;  r   r|  r  r>   r>   r?   crm_lsq_update_lead  s   
 r6  c                 C   sN   t | | t stdddt| }||}t|dr"d|dS d|dS r2  )r   r>  r   rI  delete_leadr   r   r0  r>   r>   r?   crm_lsq_delete_lead  s   

 r8  z/audio/proxy/{bid}/{callid}c                    s|   t | |}|p	i d}|stdddtj|ddd  jdkr(td	d
d fdd}t|  jddddddS )NfileUrlr   zAudio file not foundr   Tr  )streamr  r  r  zFailed to fetch audio filec                  3   s"     j ddD ]} | r| V  qd S )Ni    )
chunk_size)iter_content)chunkr  r>   r?   _iter  s   zaudio_proxy.<locals>._iterzContent-Typez
audio/mpegbyteszpublic, max-age=3600)zAccept-RangeszCache-Control)r  r  )r$  r  r   r   r  r   r   r  )r   ru  rT  file_urlr?  r>   r>  r?   audio_proxy  s   
rB  z/admin/usersc                 C   s6   |  dstdddt \}}t|td|idS )Nr   r   Master admin access requiredr   r"  r  )r   r   r   get_all_usersr   r   )r   r"  r   r>   r>   r?   admin_users  s   
rE  z/admin/users/createc              
      s<  | dstddddD ]}|  |std| ddqtj| d | d	 | d
 |  d|  dd|  ddd\}}|dkr|d }|  dd}|  drw| d D ]"}t|tr`| dn|}	t|trm| d|n|}
t||	|
 qTtj| d| ddd| d  |jr|jj	nd |j
 dd t||dS )Nr   r   rC  r   )r  r*  r   r  r  r  r*  r   r  r   r   Fr*  r  r   r   r   r  zCreated new user: r  rX   r  activity_typedescriptionr  r  r  )r   r   r   r  r@  r   r  log_activityr  r   r  r   )r;  r  r   r  r  r   rX   default_roler   r   r   r>   r>   r?   admin_users_create  s@   






rK  z/admin/businessesc                  C   s   t  \} }t|d| idS )Nr   r  )r   rD  r   )r   r   r>   r>   r?   admin_businesses  s   rL  z/admin/businesses/createc              	      s   | dstddd|  I d H }| dr| ds#tdddtj|d |d | d	d
\}}|dkr_tj| d| ddd|d  d|d  d| jrV| jjnd | j dd t	||dS )Nr   r   rC  r   r   r   r  zbid and name are requiredrH  r   r   rH  r  r   r  create_businesszCreated new business:  (ID: r$  r  rF  r  )
r   r   r  r   rN  rI  r  r   r  r   )r  r   r>  r  r   r>   r>   r?   admin_businesses_create  s"   
"
rP  c                  C   sd   t  } |  }|d | pi }W d    n1 sw   Y  ttt|dp+dd dS )Nz
            SELECT MAX(CAST(bid AS UNSIGNED)) AS max_bid
            FROM businesses
            WHERE bid REGEXP '^[0-9]+$'
            max_bidr   rd   rQ   )r  r  r!  rs  rL   rp  rV   r   )r&  r  r)  r>   r>   r?   _generate_business_bid  s    
rR     lengthc                    s(   t jt j  d fddt| D S )Nr   c                 3   s    | ]}t  V  qd S r8   )secretschoice)r  r   alphabetr>   r?   r
  
  r  z0_generate_onboarding_password.<locals>.<genexpr>)stringascii_lettersr7  r  r  )rT  r>   rW  r?   _generate_onboarding_password  s   r[  
table_namecolumn_namec                 C   s<   |  d||f |  sd S |  d| d| d|f d S )Nz
        SELECT 1
        FROM information_schema.columns
        WHERE table_schema = DATABASE()
          AND table_name = %s
          AND column_name = %s
        LIMIT 1
        zDELETE FROM `z	` WHERE `` = %sr!  rs  )r  r\  r]  r   r>   r>   r?   #_delete_from_table_if_column_exists  s    r`  r   c                 C   s   t  }| }|d| f | pg }W d    n1 sw   Y  |D ]}|dr7||dkr7 dS t||drC dS q&dS )Na  
            SELECT bu.password_hash, bu.plain_password
            FROM business_users bu
            JOIN user_business_access uba ON uba.user_id = bu.id
            WHERE uba.bid = %s
              AND bu.is_active = 1
            ORDER BY bu.is_master ASC, bu.id ASC
            plain_passwordTpassword_hashF)r  r  r!  r#  r   r   verify_password)r   r   r&  r  r(  r)  r>   r>   r?   _validate_business_password  s   rd  z/admin/onboard-businessc              	      s  t | |  I d H }t|dpd }|stdddt|dp&d p,t }td|s9tdddt|d	p@d pEd }t	j
|||d
\}}|dkrft|tr^|dnd}t||dtdd|d pvd| }	|	 d| dd d }
t|dp|
 d  }t }t	j|
||| dddd\}}|dkrt|tr|dnd}t||dt	|d |d t	j|d|ddd| d| d| jr| jjnd | jd d! ||||d |
||d"| d#| d$| d%gd&S )'Nr   r   r  zBusiness name is requiredr   r   [A-Za-z0-9_-]{2,20}z:BID must be 2-20 letters, numbers, underscores, or hyphensrH  rM  r  errorzFailed to create businessz[^a-zA-Z0-9]+r   	business__adminr  r*  z@mcube.localz Adminr   Fr*  z0Business created, but admin user creation failedr   r  rN  zOnboarded new business: rO  r$  r  rF  )r  r   r*  r  ry  r  )r   r   rH  business_user_idcredentialstables_created)rb  r  rL   r   r   r   rR  r  	fullmatchr   rN  r@  r   r  r   r[  r  r  rI  r  r   r  )r  r   r>  r   r   rH  createdr   r   username_baser  r*  r   new_useruser_statusr>   r>   r?   admin_onboard_business6  sf   " 


rq  z#/admin/businesses/{bid}/credentialsc              	      s>  t | t|  } td| stddd| I d H }t|dp%d }t|dp0d }t|dp;d}|d	}|d ur\zt|}W n t	t
fy[   tdd
dw tj| ||||d\}}	|	dkr~t|trv|dnd}
t|	|
dtj|d|ddd|  |jr|jjnd |jdd |S )Nre  r  Invalid business IDr   r  r   r*  r   ri  zInvalid business_user_id)r   r  r*  r   rX   r  rf  zFailed to update credentialsr   update_business_credentialsz'Updated login credentials for business r  rF  )rb  rL   r   r  rl  r   r  r   rV   r:  r  r    update_business_user_credentialsr@  r   rI  r  r   r  )r   r  r   r>  r  r*  r   ri  r  r   r   r>   r>   r?   !admin_update_business_credentialsv  sF   


ru  z!/business/{bid}/validate-passwordc                    sR   t | t|p	i dpd}|stdddtt| |s%tddddd	iS )
Nr   r   r  zPassword is requiredr   r   z%Incorrect password. Please try again.rQ  T)rb  rL   r   r   rd  )r   r;  r   r   r>   r>   r?   !validate_business_delete_password  s   rv  c                 C   s\   | sdS t | dk rdS td| sdS td| sdS td| s$d	S td
| s,dS d S )NzNew password is required   z/New password must be at least 5 characters longz[A-Z]z-New password must include an uppercase letterz[a-z]z,New password must include a lowercase letterz\dz"New password must include a numberz[^A-Za-z0-9]z-New password must include a special character)r   r  search)r   r>   r>   r?   $_business_user_password_policy_error  s   ry  z5/pca/businesses/{bid}/users/{user_id}/change-passwordc              
   C   s  t | t|  } td| stdddt|pi dpd}t|p%i dp*d}t|p0i dp5d}|s?tdd	dt|}|rKtd|d|sStdd
d||kr]tdddt }	|		 }
|

d| |f |
 }W d    n1 s{w   Y  |stdddt|||\}}|dkrtj|d|ddd|  d|dp| |jr|jjnd |jdd t||dS )Nre  r  rr  r   previous_passwordr   new_passwordconfirm_passwordzPrevious password is requiredz!Re-enter new password is requiredzNew passwords do not matchzSELECT bu.username, bu.full_name
            FROM user_business_access uba
            JOIN business_users bu ON bu.id = uba.user_id
            WHERE uba.bid = %s AND uba.user_id = %s AND bu.is_active = TRUE
            LIMIT 1r   z)Login user is not linked to this businessr  r   r  change_passwordzChanged password for business z user: r  rF  r  )rb  rL   r   r  rl  r   r   ry  r  r  r!  rs  r   change_user_passwordrI  r  r   r  r   )r   rX   r;  r  r   rz  r{  r|  policy_errorr&  r  target_userr  r   r>   r>   r?   !pca_change_business_user_password  sJ   

r  z/business/{bid}c                 C   s"  t | t|  } td| stdddg d}g d}t }| }z|d| f |	 }|s:tdd	d|d
| | f dd |
 pJg D }|D ]	}	t||	d|  qO|d| f d}
|r{ddgt| }|d| d| |j}
|d| f g }|D ]}|  d| }	t||	r|d|	 d ||	 q|  W n+ ty   |    ty } z|  td| | tdd| dd }~ww W d    n1 sw   Y  tj|d|ddd|d d |  d|jr|jjnd |jd!d" d#d$||
d%S )&Nre  r  rr  r   )	raw_callssarvamresponsecallanalyticsr  
lead_notes
lead_tasksr  )business_crm_integrationsbusiness_telephony_integrationsbusiness_pipeline_configpca_business_allocationsbusiness_agent_configembed_api_keysz*SELECT name FROM businesses WHERE bid = %sr   rF  a  
                SELECT bu.id
                FROM business_users bu
                JOIN user_business_access uba ON uba.user_id = bu.id
                WHERE uba.bid = %s
                  AND bu.is_master = 0
                  AND NOT EXISTS (
                    SELECT 1
                    FROM user_business_access other_uba
                    WHERE other_uba.user_id = bu.id
                      AND other_uba.bid <> %s
                  )
                c                 S   s   g | ]}|d  qS )r   r>   rn  r>   r>   r?   r  (  r5  z#delete_business.<locals>.<listcomp>r   z/DELETE FROM user_business_access WHERE bid = %sr   r  r  z:DELETE FROM business_users WHERE is_master = 0 AND id IN (r$  z%DELETE FROM businesses WHERE bid = %sr   zDROP TABLE ``zError deleting business %s: %sr  zFailed to delete business: r   r  deletezDeleted business: r   rO  r  rF  TzBusiness deleted successfully)rQ  rY   dropped_tablesdeleted_users)rb  rL   r   r  rl  r   r  r  r!  rs  r#  r`  r  r   rc  r~  r  commitrollbackr   r  rf  r   rI  r   r  r   r  )r   r  r   suffixesrelated_tablesr&  r  r   business_only_user_idsr\  r  r'  r  suffixr  r>   r>   r?   delete_business  s|   		


:
	r  z!/admin/users/{user_id}/businessesc                 C   sX   | dstddd| dstdddtj| |d | dd	d
\}}t||dS )Nr   r   rC  r   r   r  r1  r   r   r  r  )r   r   r   r  r   )rX   r;  r   r  r   r>   r>   r?   admin_assign_businessZ  s   

 r  z/admin/activity-logrG  action_codeusername_containsdescription_containsc                 C   s`   |
 dstdddtj| |||||||||	d
\}}|dkr(t|t|dS t|t|dS )Nr   r   rC  r   )
rR   rQ  rX   rG  r  r   r  r  rl  rm  r  r  )r   r   r   get_activity_logr   r   )rR   rQ  rX   rG  r  r   r  r  rl  rm  r   r  r   r>   r>   r?   admin_activity_logd  s"   

r  z/api/embed/tokenc           	      C   s   | pi  d}| p
i  d}|r|stdddt|t|}|s(tddd|j d}| d	rM|rMd
d t|d	 dD }||vrMtdddtjt||d |d d}t dd}|| d| dt||d dS )Napi_keyr   r  zapi_key and bid are requiredr   r   z1Invalid API key or unauthorized for this businessOriginallowed_originsc                 S   s   g | ]}|  qS r>   r  )r  or>   r>   r?   r    r5  zembed_token.<locals>.<listcomp>r  r   zOrigin not allowedpartner_namer   )r   r  
api_key_idEMBED_BASE_URLzhttp://localhost:6174z/#/embed?token=r8  )r   	embed_url
expires_inr   r  )	r   r   r   validate_api_keyrL   r  r  generate_embed_tokenr   )	r;  r  r  r   
key_recordoriginallowedr   embed_base_urlr>   r>   r?   embed_token  s   "r  z/api/embed/validater   c                 C   s.   t | }|stdddd|d |d dS )Nr   zInvalid or expired embed tokenr   Tr   r  )validr   r  )r   validate_embed_tokenr   )r   r  r>   r>   r?   embed_validate  s   
r  z/admin/embed-keysc              	   C   s   | dstddd|  d}|  d}|r|stdddtjt|||  d	|  d
d\}}tj|d |d dd| d| d|jrJ|jjnd |j dd t	||dS )Nr   r   rC  r   r   r  r  z!bid and partner_name are requiredr  
expires_at)r   r  r  r  r   r  create_embed_keyz#Created embed API key for business z (partner: r$  r  rF  r  )
r   r   r   create_embed_api_keyrL   rI  r  r   r  r   )r;  r  r   r   r  r  r   r>   r>   r?   admin_embed_keys_create  s*   




r  c                 C   s6   | dstdddtj| d\}}t|d|idS )Nr   r   rC  r   r  r  r  )r   r   r   list_embed_api_keysr   )r   r   r  r   r>   r>   r?   admin_embed_keys_list  s   
r  z/admin/embed-keys/{key_id}key_idc              	   C   sj   | dstdddt| \}}tj|d |d dd|  |jr&|jjnd |j d	d
 t||dS )Nr   r   rC  r   r   r  revoke_embed_keyzRevoked embed API key r  rF  r  )	r   r   r   revoke_embed_api_keyrI  r  r   r  r   )r  r  r   r  r   r>   r>   r?   admin_embed_keys_revoke  s   

r  c                 C   s\   zt | }W n
 ty   Y d S w |sd S |D ]}t|dp!ddkr)|  S q|d S )NrC  r   rd   )r$  r%  r   rV   r   )r   rK  r*  r>   r>   r?   )_get_active_telephony_integration_or_none  s   r  r  c                 C   sz   | pi  dpi }| d}| d}| d}| d}t| dp$d}|r.|r.|r.|s4tdd	d
|||||ddS )Nri   r   r   r   r   r   r   r  z6Telephony integration is missing DB connection detailsr   r   r   )r   rV   r   )r  r<   r   r   r   r   r   r>   r>   r?   ,_source_db_config_from_telephony_integration  s   



r  c                 C   s   dd t | |D S )Nc                 S   s   i | ]}t | t |qS r>   rL   r   r  cr>   r>   r?   rF    rG  z$_source_cols_map.<locals>.<dictcomp>)_existing_table_columnsr  r\  r>   r>   r?   _source_cols_map  s   r  NULLr  source_cols
candidatesc                G   s:   |D ]}t | }||v r|  d||  d  S q|S )N.`r  r  )r  r  rn   r  r   r=   r>   r>   r?   _source_sql_col  s   r  c                 C   s.  d|v }g }d|v r|d }| d|  d| d |rNd|v rNd|v rN|d }|d }|d }| d|  d| d	|  d| d
|  d| d|  d| d d|v rb|d }| d|  d| d d|v rv|d }| d|  d| d d|v r|d }| d|  d| d |sdS dd| dS )Nrp  r[  zNULLIF(TRIM(CAST(r  z` AS CHAR)), '')calltocallfromzCASE WHEN LOWER(TRIM(CAST(z0` AS CHAR))) = 'outbound' THEN NULLIF(TRIM(CAST(z&` AS CHAR)), '') WHEN LOWER(TRIM(CAST(z/` AS CHAR))) = 'inbound' THEN NULLIF(TRIM(CAST(z` AS CHAR)), '') ELSE NULL ENDclicktocalldidr  	COALESCE(r  r$  )r  r  )r  r  has_directioncustomer_candidatescolr  r  rp  r>   r>   r?   _customer_callinfo_expr  sP   r  rh   
table_kindsource_configdialstatus_csvdirection_csvc           %      C   s  ddl m} tjdBi |}	z|	tjj}
t|
| |}|s(g d fW |	  S d}t	|
|}t
||dd}t
||dd}t
||dd	}t
||d
dd}t
||ddd}t
||dd}t
||ddddd}| d}t
||dddd d}t
||ddd d}t
||ddd d}||
| |||d\}}}}t||}d g}g }d!|v r|| d"|d!  d# |t|  |r|r|dkr|d$| d% |||g |r|dkrd&d' t|d(D } | r|| d)d*d+gt|   d,  ||  |r8d
|v r8d-d' t|d(D } | r8|| d"|d
  d.d*d+gt|   d,  ||  |dkr?|n| d"|dd d/}!d0g d1| d2| d3| d3| d4| d5| d6| d7| d8| d9| d3| d3| d:| d;| d<| d=d>| d?|! d@}"t| gt| | t|g }#|
|"t|# |
 pg }$dAd' |$D |fW |	  S |	  w )CNr   )resolve_mcube_group_sqlr  	starttimer,  endtimer-  
dialstatusrP  rp  z	'inbound'r   r  fileurlrA  ru  rw   rZ  
callername
agent_namez''z AS agentname	emp_phoner  r  z AS emp_phoner  z AS clicktocalldidcountrycodez AS countrycode)
call_aliasz1 = 1r   r  r^  zDATE(z) BETWEEN %s AND %sc                 S   r  r>   r  r3  r>   r>   r?   r  b  r  z3_fetch_source_calls_from_config.<locals>.<listcomp>r  z IN (r  r  r$  c                 S   r  r>   r  r3  r>   r>   r?   r  j  r  z` IN (r  r   z$
            SELECT
                z7 AS callid,
                %s AS bid,
                z,
                z AS starttime,
                z AS endtime,
                z  AS dialstatus,
                z AS direction,
                z AS filename,
                z' AS customer_callinfo,
                z
            FROM `z` z
            z
            WHERE rX  z
            ORDER BY z# DESC
            LIMIT %s
        c                 S   rH  r>   _serialize_call_sync_rowr  rr>   r>   r?   r    r5  r>   )mcube_group_utilr  rn  ro  r  rp  rq  _resolve_source_call_tablerx  r  r  r  r  rL   r'  r  r  r   r   rK   rV   r!  r"  r#  )%rh   r  r  rl  rm  r  r  rR   r  r&  r  r\  r  r  	start_colend_coldialstatus_coldirection_colfilename_col
callid_col	agent_col	agent_sqlemp_phone_sqlclicktocall_sqlcountrycode_sqlgroup_join_sqlgroup_join_paramsgroupname_sqlr   customer_exprr^  where_paramsvals	order_colr  r_  r(  r>   r>   r?   _fetch_source_calls_from_config)  s   

T



 

&
	
 r  CALL_SYNC_CACHE_TTL_SECONDS3600v5c                 C   s\   |dkr| d| dgn	| d| dg}|D ]}|  d|f |  r+|  S qd S )Nhistory_callhistory_call_history_callarchive_call_archiveSHOW TABLES LIKE %sr_  )r  r   r  r  	candidater>   r>   r?   r    s   0r  c                 C   s"   |  d| d|f |  d uS )NSHOW COLUMNS FROM `z	` LIKE %sr_  )r  r\  r]  r>   r>   r?   _table_has_column  s   r  c                 C   s4   i }|   D ]\}}t|tr| n|||< q|S r8   )rK  r@  r   rJ  )r)  r  r=   r   r>   r>   r?   r    s   r  c                 C   st  t  }tjdi |}z|tjj}t|| |}|s$g d fW |  S t||d}	t||d}
t||d}t||d}t||d}g }|
rK|	d |	rV|rV|rV|	d |r]|	d |rd|	d	 |rod
d
| dnd}d| d| d}t| g}|r|r|d7 }|||g |d7 }|	t| ||t| | pg }dd |D |fW |  S |  w )Nrp  r[  r  r  r  z1NULLIF(TRIM(CAST(customer_callinfo AS CHAR)), '')zCASE WHEN LOWER(TRIM(CAST(direction AS CHAR))) = 'outbound' THEN NULLIF(TRIM(CAST(callto AS CHAR)), '') WHEN LOWER(TRIM(CAST(direction AS CHAR))) = 'inbound' THEN NULLIF(TRIM(CAST(callfrom AS CHAR)), '') ELSE NULL ENDz&NULLIF(TRIM(CAST(callto AS CHAR)), '')z.NULLIF(TRIM(CAST(clicktocalldid AS CHAR)), '')r  r  r$  r  z
            SELECT
                callid, %s as bid, agentname, groupname, starttime, endtime, dialstatus, direction, filename,
                zP as customer_callinfo, countrycode, emp_phone, clicktocalldid
            FROM `z"`
            WHERE 1 = 1
        & AND DATE(starttime) BETWEEN %s AND %sz! ORDER BY starttime DESC LIMIT %sc                 S   rH  r>   r  r  r>   r>   r?   r    r5  z'_fetch_source_calls.<locals>.<listcomp>r>   )r   rn  ro  r  rp  rq  r  rx  r  r  r  rL   r'  rV   r!  r"  r#  )r   r  rl  rm  rR   r  r&  r  r\  r  has_customer_callinfohas_callfrom
has_calltohas_clicktocallr  r  r  r_  r(  r>   r>   r?   _fetch_source_calls  sP   
(



r  c                 C   s   t  }tjdi |}zI|tjj}t|| |}|s"W |  dS d| d}g }	|r9|r9|d7 }|	||g |	|t
|	 | pFi }
t|
dpNdW |  S |  w )Nr   zSELECT COUNT(*) as total FROM `z` WHERE 1 = 1r  rY  r>   )r   rn  ro  r  rp  rq  r  rx  r'  r!  r"  rs  rV   r   )r   r  rl  rm  r  r&  r  r\  r  r_  r)  r>   r>   r?   _fetch_source_count  s"   
r	  z/telephony/{bid}/integrationsc                 C   r  )Nintegrations)r   r$  r%  )r   r   r
  r>   r>   r?   telephony_list_integrations  r  r  c                 C   s   g }| pg D ]E}t |tr| }|r||dd qt |ts"qt|dp.|dp.d }|s5q|d}|du rC|d}|||d q|S )zAReturn [{groupname, call_count}, ...] for telephony UI consumers.N)r   
call_countr   r   r   r  r\  )r@  rL   r   r  r   r   )rR  r(  r*  r   rx  r>   r>   r?   "_normalize_telephony_source_groups  s"   

 

r  src_bidc                 C   sd   ddl m} tjdi | }z| }t||d}|s"g W |  S ||||W |  S |  w )z?Load distinct groupnames from a Mcube source callhistory table.r   )fetch_mcube_source_groupsr  Nr>   )r  r  rn  ro  r  r  rx  )r  r  r  r&  r  r\  r>   r>   r?   _fetch_telephony_source_groups  s   
r  rs   c                 C   s<   t  jdd}| dkr|jdddddS |tt| d S )zCStart time for first ingest on a new business (0 = start of today).r   )microsecond)rT  rU  rV  r  )r  )r   rz  r   r   rV   )rs   rz  r>   r>   r?   $_compute_onboarding_ingest_watermark  s   r  c                 C   s   d|  }t | |rdS |  d}z@t 2}| }|d| d | p)i }t|dp1ddkr?	 W d   W dS W d   W d	S 1 sKw   Y  W d	S  ty\   Y d	S w )
zQOnly seed ingest watermark for brand-new pipelines (do not disturb running BIDs).orchestrator_ingest_Fr  zSELECT COUNT(*) AS n FROM `r  nr   NT)	r$  get_sync_watermarkr  r  r!  rs  rV   r   r   )r   wm_keyr  r&  r  r)  r>   r>   r?   "_should_bootstrap_ingest_watermark#  s*   

r  z/telephony/{bid}/source-groupsc           
   
   C   s  t | | t| }t|p|pi dp|  }g }t|p%|p i dp%d  }d}|rOzt|}W nB tyN }	 zt	d| |	 W Y d}	~	n.d}	~	ww |rxzt
|}W n  ty_     tyw }	 zt	d| |	 W Y d}	~	nd}	~	ww |rzt||}W n  ty     ty }	 zt	d| |	 W Y d}	~	nd}	~	ww |sztt| pg }W n ty   g }Y nw | |t|dS )	zODistinct groupnames from the telephony source DB (or local raw_calls fallback).rh   rg   r   Nz@telephony source-groups integration config failed for bid=%s: %sz=telephony source-groups provider config failed for bid=%s: %sz3telephony source-groups query failed for bid=%s: %s)r   rh   rR  )r   r  rL   r   r   r   r  r   r  rh  r  r   r  r  r$  rg  )
r   rg   rh   r   r  r  rR  r  r  r  r>   r>   r?   telephony_source_groups5  sL   
"r  c                 C   s   t | | t| | t|jpd  }t|jpd }|r"|s(tdddt|j	p-i }|sKt
 }|d|d|d|d|d	d
}tj| |||dd tj| |d dddS )Nr   r  z$provider and source_bid are requiredr   r   r   r   r   r   )r   r   r   r   r   Tr   rg   rh   ri   rC  r   rg   z(Telephony integration saved successfullyr  )r   r   rL   rg   r   r   rh   r   r   ri   r   r   r$  upsert_telephony_integrationset_active_telephony_provider)r   r;  r   rg   rh   r<   env_cfgr>   r>   r?   telephony_connect_integrationb  s$   


r  
target_bidrp   c           	      C   s   |d ur|nt | }t }| }|dt|f W d    n1 s&w   Y  d}|p0g D ]$}t|}|dd  |dd  |dd  t t|| |d7 }q1|S )N-DELETE FROM quality_parameters WHERE bid = %sr   r   
created_at
updated_atrd   )	r  r  r  r  r!  rL   r   r\  r  )	rh   r  rp   r_  r&  r  rx  paramr*  r>   r>   r?   _copy_quality_parameters{  s   
r$  c                 C   s   t jddd}t }| }|dt| f W d    n1 s"w   Y  d}|p,g D ]*}t|}|dd  |dd  |dd  |d	d  t t| | |d
7 }q-|S )Nrn   r  r  r   r   r   template_keyr!  r"  rd   )	r  r  r  r  r!  rL   r   r\  r  )r  r_  r&  r  rx  r#  r*  r>   r>   r?   !_copy_default_template_parameters  s   
r&  c                 C   sb   t jdi | }z#|t jj}t||d}|s%tdd| d| dd|W |  S |  w )Nr  r  zSource table not found for BID z. Expected z_callhistory.r   r>   )rn  ro  r  rp  rq  r  r   rx  )r  rh   r&  r  r\  r>   r>   r?   _verify_source_table_exists  s   r'  z-/admin/businesses/{bid}/onboarding-processingc                 C   s  t | t|  } t|jp|  }t|jpd  }t|jp#d  }|dvr3tdddt	  |dk}d }d }|dkr]t
|}t||}tj| |||d	d
 tj| |d t|jpbd}	|	tvrytddddd tD  ddd |jpg D }
|jr|
stdddd}|dkrt| }n|dkrtd| |jpg }d	|||pd|||d|	|jrdnd|
d}|r||d|d|d|d|dd t| | d}|dkrt| rt|	}t| d|  |  d	}d	| |||||||	|t|j|
d S )!Nr   rn   >   customrn   transcription_onlyr  zInvalid analysis moder   r)  r  Tr  r  r   z%ingest_lookback_days must be one of: r  c                 s   s    | ]}t |V  qd S r8   rL   r3  r>   r>   r?   r
        z7admin_business_onboarding_processing.<locals>.<genexpr>c                 S   s$   g | ]}t | rt | qS r>   r  )r  gr>   r>   r?   r    s
    
z8admin_business_onboarding_processing.<locals>.<listcomp>z3Select at least one group, or disable group filter.r(  sarvamrd   )pipeline_enabledsource_typerh   source_tablero   analytics_enabledprocessing_modestt_providerlookback_daysrq   rr   r   r   r   r   r   )source_db_hostsource_db_portsource_db_usersource_db_passwordsource_db_nameFr  )rQ  r   rg   rh   r0  ro   r1  parameters_savedrs   ingest_watermark_setrq   rr   )rb  rL   r   rh   rg   r   ro   r   r$  %ensure_business_pipeline_config_tabler  r'  r  r  rV   rs   ONBOARDING_INGEST_LOOKBACK_DAYSr  rr   rq   r&  r$  rp   r6  r   save_pipeline_configr  r  set_sync_watermarkrJ  rU   )r   r;  r   rh   rg   ro   r1  provider_configr0  rs   allowed_groupsparams_savedcfg_payloadr;  wmr>   r>   r?   $admin_business_onboarding_processing  s   


	rE  z(/telephony/{bid}/integrations/{provider}c                 C   s8   t | | t| | tj| |d}d|rddS ddS )Nr  TzDisconnected successfullyzIntegration did not existr  )r   r   r$  delete_telephony_integration)r   rg   r   r,  r>   r>   r?    telephony_disconnect_integration  s   

rG  z/telephony/{bid}/previewr  c              
   C   s4  t | | t| }|stdddt|}t|dp| }	|r|rt|	d||||||d\}
}t|	d||||||d\}}t }g }g |
|D ]}t|dpRd	}|r[||v r[qI|rb|| |	| qI|j
d
d dd |d t| }d||dt||dS t|	d||||d\}}d|t||dS )Nr  zNo telephony provider connectedr   rh   archive)rh   r  r  rl  rm  r  r  rR   r  ru  r   c                 S      t | dpdS )Nr  r   rL   r   rv  r>   r>   r?   rj  G      z)telephony_preview_calls.<locals>.<lambda>Trl  zarchive+history)rH  r  )sourcetablerx  ru   )rh   r  r  r  r  rR   )r   r  r   r  rL   r   r  r  rt  r  rv  rV   r   )r   rl  rm  r  rp  rR   r   r  r  rh   archive_recordsarchive_tablehistory_recordshistory_tableseen_callidsru   r)  ru  rM  r>   r>   r?   telephony_preview_calls  sh   







rS  c                 C   sf   |  d| d |  pg }t }|D ]}t|tr$||d q||d  qdd |D S )Nr  r  r   r   c                 S   s   h | ]}|r|qS r>   r>   r  r>   r>   r?   ro  e  r5  z*_existing_table_columns.<locals>.<setcomp>)r!  r#  r  r@  r   rt  r   )r  r\  r(  colsr)  r>   r>   r?   r  [  s   
r  z/telephony/{bid}/sync-to-dbc                    sd  t | | t| | |jpg }|stddd|  d}t }z| }|d|f | s:tdd| ddt||g }|D ]p}t	|pIi }	t
| t
|	dpUd	|	d
|	d|	dpg|	d|	dpp|	d|	dpy|	d|	d|	dp|	d|	dp|	d|	dp|	dp|	dd}
|
d sqC|fdd|
 D  qC|stdddtdd |D  d vrtdddd d!d"  D }d d#gt  }d$d%  D }|rd d&d" |D nd	}d'| d(| d)| d*}|r|d+| 7 } fd,d%|D }||| |  d-t|d.W |  S |  w )/Nr  zrecords array is requiredr   r  r  zTable z does not existru  r   rZ  r   r  r,  r  r-  r  rP  rp  r  r  r  agent_callinfor[  r  )r   ru  rZ  r   r,  r-  rP  rp  r  rU  r[  c                    s&   i | ]\}}| v r|d ur||qS r8   r>   rD  )existing_colsr>   r?   rF    r  z(telephony_sync_to_db.<locals>.<dictcomp>z!No valid records (missing callid)c                 S   s   h | ]}|  D ]}|qqS r>   )r  )r  r)  rE  r>   r>   r?   ro    rG  z'telephony_sync_to_db.<locals>.<setcomp>callid is requiredr  c                 s   s    | ]	}d | d V  qdS )r  Nr>   r  r>   r>   r?   r
    r.  z'telephony_sync_to_db.<locals>.<genexpr>r  c                 S   s   g | ]}|d vr|qS ))ru  r>   r  r>   r>   r?   r    rL  z(telephony_sync_to_db.<locals>.<listcomp>c                 s   s"    | ]}d | d| dV  qdS )r  z` = VALUES(`z`)Nr>   r  r>   r>   r?   r
    rV  zINSERT INTO `z` (z
) VALUES (r$  z ON DUPLICATE KEY UPDATE c                    s   g | ]  fd dD qS )c                    s   g | ]}  |qS r>   r  r  rv  r>   r?   r        z3telephony_sync_to_db.<locals>.<listcomp>.<listcomp>r>   )r  )columnsrv  r?   r    rG  T)rQ  r  )r   r   ru   r   r  r  r!  rs  r  r   rL   r   r  rK  rw  r  r   executemanyr  rx  )r   r;  r   ru   r\  r&  r  rP  r  r)  mappedcols_sqlr'  update_cols
update_sqlsqlr  r>   )rY  rV  r?   telephony_sync_to_dbh  s`   




r`  z/sync/cache/{bid}/historyrefreshc           	      C   s   t | | t| | dt d|  d| }|s$t|}|r$d|d< |S t| d|d\}}|sRdd d	g t  d
d|  d|  dgt	 
dt	 
ddd	S d|t||t  d
d}tj|| d|td |S )N	callsync:rL  :history:limit:T	cache_hitr  r  call_historyr   Fz5Source call history table not found for this businessr  r  r   r   r   r   )	rL  rM  rx  ru   	cached_atrd  rY   expected_tablesconfigured_source_db)rL  rM  rx  ru   rg  rd  ry  r   rL  r;  ttl_secondsr   r   CALL_SYNC_CACHE_SCHEMA_VERSIONr$  get_call_sync_cacher  r   rz  rJ  r   r   r   upsert_call_sync_cacher  )	r   rR   ra  r   ry  r{  ru   r\  r;  r>   r>   r?   sync_cache_history  s:   



rp  z/sync/cache/{bid}/archivec                 C   s  t | | t| | |r|r| d| nd}dt d|  d| d| }|s4t|}|r4d|d< |S t| d|||d	\}	}
|
sfd
d dg ||t  dd|  d|  dgt	 
dt	 
dddS d
|
t|	|	||t  dd}tj|| d
|td |S )NrL  recentrb  	:archive::limit:Trd  rH  rl  rm  rR   call_archiver   Fz5Source call archive table not found for this businessr  r  r   r   rf  )rL  rM  rx  ru   rl  rm  rg  rd  rY   rh  ri  )rL  rM  rx  ru   rl  rm  rg  rd  rj  rl  )r   rl  rm  rR   ra  r   	range_keyry  r{  ru   r\  r;  r>   r>   r?   sync_cache_archive  sP   
	




rw  z/sync/check-count/{bid}c                 C   sZ   t | | t| | t| d}|r|rt| d||dnd}d|id|it|| ||dS )Nr  rH  )rl  rm  r   rY  )callhistorycallarchiver  rl  rm  )r   r   r	  rV   )r   rl  rm  r   history_totalarchive_totalr>   r>   r?   sync_check_count  s   



r|  z/sync/calls/{bid}c              
   C   s$  t | | t| | |d}|d}t|dd}|r?|r?t| d|||d\}}d}dt d	|  d
| d	| d| 
}	nt| d|d\}}d}dt d	|  d| }	||t||||t 	 d}
|rqt
j|	| ||
td |sd| d|d |	ddS dt| d| t||||	ddS )Nrl  rm  rR   r  rH  rt  ru  rb  rL  rr  rs  r  r  re  rc  )rL  rM  rx  ru   rl  rm  rg  rj  zSource table not found for r   mysql)rY   cached_countrL  rM  ry  	stored_inzSuccessfully cached z calls from )r   r   r   rV   r  rm  r   r   rz  rJ  r$  ro  r  )r   r;  r   rl  rm  rR   ru   r\  rL  ry  payload_objr>   r>   r?   
sync_calls  s>   



$
	&r  z/sync/upload/{bid}c                    s"   t | | t| | tddd)Ni  zExcel multipart upload requires python-multipart in this runtime. Use admin JSON business create path or install python-multipart.r   )r   r   r   )r   r  r   r>   r>   r?   sync_upload_raw_callsC  s   

r  z/transcription/calls/{bid}r1  
start_dateend_datec                 C   s   t  }zo| }dg}g }	|dkr|d |	| |r'|d |	| |r3|d |	| |r?|d |	| |rK|d |	| d|  d	d
| d}
||
t|	 | pdg }t|t|dW |	  S |	  w )Nzcall_status = 'ANSWER'rW  rW  zgroupname = %szagentname = %szDATE(call_starttime) >= %szDATE(call_starttime) <= %sz
            SELECT callid, bid, agentname, groupname, call_starttime, call_endtime, call_status, status,
                   transcription_status, transcription_requested, selected_for_processing
            FROM `z_raw_calls`
            WHERE rX  zH
            ORDER BY call_starttime DESC
            LIMIT 500
        )rO  rx  )
r  r  r  r  r!  r"  r#  rC  r   rx  )r   r   r   rZ  r  r  r&  r  r^  r_  r  rO  r>   r>   r?   transcription_callsR  s:   	









r  z/transcription/trigger/{bid}c                 C   s   | dg }|stdddt }z&| }ddgt| }d|  d| d	}||| |j}W |  n|  w d
| d|dS )NrT   r  No call IDs providedr   r  r  z
            UPDATE `z_raw_calls`
            SET transcription_requested = 1, transcription_status = 'pending', selected_for_processing = 1
            WHERE callid IN (z
)
        zSuccessfully queued z calls for transcription)rY   queued_count)	r   r   r  r  r  r   r!  rc  rx  )r   r;  rT   r&  r  r'  r  affectedr>   r>   r?   transcription_trigger~  s    r  z%/transcription/trigger-combined/{bid}c                 C   s4   | dg }t| d|i}t| d|i}d||dS )NrT   zCombined trigger executed)rY   transcriptionr  )r   r  analysis_trigger)r   r;  rT   tar>   r>   r?   transcription_trigger_combined  s   r  z&/transcription/status/{bid}/{batch_id}batch_idc                 C   s   t | t |dddS )N	completedz-Legacy batch status endpoint is now immediate)r   r  r   rY   r*  )r   r  r>   r>   r?   r0    s   z/analysis/trigger/{bid}c                 C   s  | dg }|stdddzddlm} |t}W n ty/ } z	tdd| dd }~ww d}d}g }|D ]}	zmt| |	}
|
sQ||	 d	 |d
7 }W q8|
 ddkrf||	 d |d
7 }W q8|
 d}|s{||	 d |d
7 }W q8|
 d}|rt	|t
rt|}|
 dp|
 d}|j| |	||pg |d |d
7 }W q8 ty } z||	 d|  |d
7 }W Y d }~q8d }~ww d| d| d||d}|r|d d |d< |S )NrT   r  r  r   r   )CallAnalyzerr  zFailed to initialize analyzer: z: Call not foundrd   rP  rQ  z: Call not answeredr  z: No transcript availabler  r  r/  )r   ru  r  r  actual_durationz: zAnalysis completed: z successful, z failed)rY   success_counterror_countrj   r  )r   r   analyze_calls_with_parametersr  r   r   r$  rv  r  r@  rL   r  r  analyze_call)r   r;  rT   r  analyzerer  r  r  ru  	call_datar  r  r  response_datar>   r>   r?   r    sX   


r  c                   @   s  e Zd ZU dZee ed< dZee ed< dZ	ee
 ed< dZee ed< dZee ed< dZee ed< dZee ed< dZee ed	< dZee
 ed
< dZee
 ed< dZee
 ed< dZee
 ed< dZee ed< dZee ed< dZee
 ed< dZee ed< dZee ed< dZee ed< dZee ed< dZee ed< dZeee  ed< dZee ed< dZee ed< dZ ee ed< dZ!ee ed< dZ"ee ed< dS )PipelineConfigRequestNr.  r5  r6  r7  r8  r9  r3  stt_api_keymin_call_duration_s
sync_batchtranscribe_batchsync_interval_slead_filter_enabledcrm_providerr4  r/  rh   ro   r1  rq   rr   propensity_enabledsummary_modesummary_instructionswebhook_ingest_enabledr  )#rH   rI   rJ   r.  r   rU   rM   r5  rL   r6  rV   r7  r8  r9  r3  r  r  r  r  r  r  r  r4  r/  rh   ro   r1  rq   rr   r   r  r  r  r  r  r>   r>   r>   r?   r    s6   
 r  c                   @   s&   e Zd ZU eed< dZee ed< dS )SummaryConfigRequestr  Nr  )rH   rI   rJ   rL   rM   r  r   r>   r>   r>   r?   r    s   
 r  z/pipeline/{bid}/summary-configc              
   C   s  t | | ddlm}m} ||j}|jpd pd}|dkr8|s'tdddt||kr7tdd	| d
dnd}zt	
  t	| || W n1 ty\ } z	tdt|d|d}~w tyx } ztd| | tdd| d|d}~ww d| ||pddS )zPSave only call summary style fields (isolated from full pipeline config upsert).r   MAX_SUMMARY_INSTRUCTIONS_LENnormalize_summary_moder   Nr(  r  <summary_instructions is required when summary_mode is customr   %summary_instructions must be at most  charactersz,Failed to save summary config for bid %s: %sr  zFailed to save summary config: T)rQ  r   r  r  )r   summary_configr  r  r  r  r   r   r   r$  r<  save_summary_configr  rL   r   r  r  )r   r  r   r  r  modeinstructionsr  r>   r>   r?   r    sL   


r  z/pipeline/{bid}/configc                 C   s>  t | | t  t| }|s| ddS t|}dD ]}||r&d||< qdD ]}t||dr;||  ||< q)t|d|d< t	|d|d< t	t
|d	pWd
|d	< d
dlm} ||d|d< |dpqd|d< t	t
|dp|d
|d< |drd|d< d|d< n
d|d< |dd  d|d< |S )NF)r   exists)source_db_password_encstt_api_key_encz***r!  r"  rJ  rr   rq   r  r   )r  r  r  r   r  r  Tingest_secret_configuredr  )r   r$  r<  get_pipeline_configr   r   hasattrrJ  _decode_allowed_groupnamesrU   rV   r  r  r\  )r   r   r<   saferE  r  r  r>   r>   r?   r  "  s8   





r  z/pipeline/{bid}/ingest/enablec                 C   s|   t | | t stdddt  t| ddd t|}d| dd| d| d	|  d
| d	|  ddd|  dd	S )zXEnable per-BID webhook ingest using the shared INGEST_SECRET (does not disable polling).r  zTSet INGEST_SECRET (or WEBHOOK_SECRET) in server .env before enabling webhook ingest.r   rd   r   )r  r  T/api/webhook/call-ingestz/api/v1/bids/z/calls/ingestz/calls/ingest/schemar  zWebhook enabled for BID z. Mcube uses the same shared secret for all BIDs (INGEST_SECRET in server .env). Orchestrator polling remains active as backup.)	rQ  r   r  r  
ingest_urlper_bid_url
schema_urlheaderr  )r   r3   r   r$  r<  r>  r  )r   r  r   baser>   r>   r?   enable_call_ingest_webhookG  s0   

r  z/pipeline/{bid}/ingest/disablec                 C   s8   t | | t  t| ddi d| dd|  ddS )zJDisable webhook ingest for a BID (Mcube may still POST; PCAA returns 403).r  r   TFz Webhook ingest disabled for BID z. Polling unchanged.)rQ  r   r  rY   )r   r$  r<  r>  r   r>   r>   r?   disable_call_ingest_webhookm  s   

r  c              
   C   s  t | | t  t|dr| n| }dd | D }d|v rdddlm}m	} ||
d}||d< |dkr`|
dp@d	 }|sKtd
ddt||kr[td
d| dd||d< nd |d< d|v r|
dd urddlm} t|d  }	t|	|krtd
d| dd|	pd |d< zt| | W n ty }
 ztd| |
 tdd|
 d|
d }
~
ww dd|  dS )N
model_dumpc                 S   r  r   r>   rD  r>   r>   r?   rF    r  z(save_pipeline_config.<locals>.<dictcomp>r  r   r  r(  r  r   r  r  r   r  r  )r  z-Failed to save pipeline config for bid %s: %sr  z Failed to save pipeline config: TzPipeline config saved for bid r  )r   r$  r<  r  r  r   rK  r  r  r  r   r   r   r   rL   r>  r   r  r  )r   r  r   r:  r>  r  r  r  r  r  r  r>   r>   r?   r>  ~  sX   



r>  z/pipeline/{bid}/statusc           
      C   s  t | | t  t  t| }t| d|  }t| d}i }z>t|  |  d}t &}| }|	d| d |
 pEg D ]
}	|	d ||	d < qFW d   n1 s[w   Y  W n	 tyj   Y nw | |rvt|d	d
nd
|r}t|nd|rt|ndd|dS )zLReturn pipeline operational status: watermarks, pending counts, last errors.
call_sync_	lsq_leads_call_recordsz9SELECT status, COUNT(*) AS cnt
                    FROM `z` GROUP BY statuscntr   Nr.  F)	call_syncr  )r   r.  
watermarkscall_record_counts)r   r$  r<  ensure_sync_watermarks_tabler  r  ensure_call_records_tabler}  r  r!  r#  r   rU   r   rL   )
r   r   r<   call_sync_wmlsq_leads_wmcountsrM  r&  r  r)  r>   r>   r?   get_pipeline_status  s@   




r  c                   @   s   e Zd ZU eed< dZee ed< dZee ed< dZ	ee ed< dZ
ee ed	< dZee ed
< dZee ed< dZee ed< dZee ed< dS )AgentConfigRequestr  Tagent_enabledr  model_provideramazon.nova-lite-v1:0model_idNr~   user_prompt_templateoutput_schemag?r{  i   
max_tokens)rH   rI   rJ   rL   rM   r  r   rU   r  r  r~   r  r  r{  rb   r  rV   r>   r>   r>   r?   r    s   
 r  z/agents/{bid}c                 C   sZ   t | | t  t| }|D ]}dD ]}t||dr&||  ||< qq| |dS )Nr  rJ  )r   rS  )r   r$  "ensure_business_agent_config_tableget_agent_configsr  r   rJ  )r   r   configsr<   r  r>   r>   r?   list_agent_configs  s   


r  c                 C   sB   t | | t  dd |  D }t| |}d||jdS )Nc                 S   r  r8   r>   rD  r>   r>   r?   rF    rG  z1create_or_update_agent_config.<locals>.<dictcomp>T)rQ  r   r  )r   r$  r  r   rK  save_agent_configr  )r   r  r   r>  row_idr>   r>   r?   create_or_update_agent_config  s
   
r  z/agents/{bid}/{agent_name}r  c                 C   s<   t | | t| |}|stdd| d|  dd|dS )Nr   zAgent 'z' not found for bid r   T)rQ  r  )r   r$  delete_agent_configr   )r   r  r   r  r>   r>   r?   r    s
   

r  z/call-records/{bid}r  	page_sizerx  c	              
   C   sl   t | | zt|  tj| |||||||d}	|	W S  ty5 }
 ztjd| |
dd tdddd }
~
ww )N)r   r  r  r   r  rx  rl  rm  z"list_call_records error bid=%s: %sTexc_infor  zFailed to fetch call recordsr   )r   r$  r  get_call_records_listr   r  rf  r   )r   r  r  r   r  rx  rl  rm  r   r  r  r>   r>   r?   list_call_records  s&   


r  z!/call-records/{bid}/{callid:path}c              
   C   s   t | | zt|  t| |}|stdd| dd|W S  ty'     tyB } ztjd| ||dd tdd	dd }~ww )
Nr   zCall record z
 not foundr   z1get_call_record_detail error bid=%s callid=%s: %sTr  r  zFailed to fetch call record)r   r$  r  get_call_record_detailr   r   r  rf  )r   ru  r   r  r  r>   r>   r?   r  8  s   

r  c                   @   sn   e Zd ZU dZee ed< dZee ed< dZ	ee ed< dZ
ee ed< dZee ed< dZee ed< dS )SttPipelineConfigUpdateNr$  raw_calls_id_colraw_calls_url_col
batch_sizepoll_interval_snotes)rH   rI   rJ   r$  r   rU   rM   r  rL   r  r  rV   r  r  r>   r>   r>   r?   r  P  s   
 r  r<   c              	   C   sZ   | d }t j|d}i | t| d|dd|dd|dd|ddd	d
S )Nr   r  r$  pendingr   
processingdoner  )r  r  r  r  )r$  	job_stats)r$  get_stt_job_statsrU   r   )r<   r   rU  r>   r>   r?   _stt_bid_row_with_statsY  s   



r  z/stt-pipeline/bidsc                 C   s   |  dstdddz;dd t D }t }|D ]}||vr/|ddd	d
dd d d d	||< qdd t| dd dD }|t|dW S  ty` } zt	j
d|dd tdddd }~ww )Nr   r   rC  r   c                 S   s   i | ]}|d  |qS r  r>   r  r>   r>   r?   rF  n  rX  z*stt_pipeline_list_bids.<locals>.<dictcomp>Fr   recording_urlrj   r  )	r   r$  r  r  r  r  r  r!  r"  c                 S   rH  r>   )r  r3  r>   r>   r?   r  }  r5  z*stt_pipeline_list_bids.<locals>.<listcomp>c                 S   rf  Nr   r>   rh  r>   r>   r?   rj  }  rk  z(stt_pipeline_list_bids.<locals>.<lambda>r=   )bidsrY  z stt_pipeline_list_bids error: %sTr  r  z Failed to list STT pipeline bids)r   r   r$  get_stt_pipeline_bid_configsdiscover_stt_raw_call_bidsrw  r  r   r   r  rf  )r   r  
discoveredr   r  r  r>   r>   r?   stt_pipeline_list_bidsh  s2   

r  z/stt-pipeline/bids/{bid}/togglec              
   C   s   | dst|| stdddt| dd}zt| | | |dW S  ty? } ztjd| |d	d
 tdddd }~ww )Nr   r   (Business admin or master access requiredr   r$  F)r   r$  z(stt_pipeline_toggle_bid error bid=%s: %sTr  r  z!Failed to toggle STT pipeline bid)	r   r   r   rU   r$  toggle_stt_pipeline_bidr   r  rf  )r   r  r   r$  r  r>   r>   r?   stt_pipeline_toggle_bid  s   r  z/stt-pipeline/bids/{bid}/configc              
   C   s   | dst|| stddddd |  D }zt| | t| p,d| i|}t|W S  t	yL } zt
jd| |d	d
 tdddd }~ww )Nr   r   r  r   c                 S   r  r8   r>   rD  r>   r>   r?   rF    rG  z.stt_pipeline_update_config.<locals>.<dictcomp>r   z+stt_pipeline_update_config error bid=%s: %sTr  r  z$Failed to update STT pipeline config)r   r   r   r   rK  r$  upsert_stt_pipeline_bid_configget_stt_pipeline_bid_configr  r   r  rf  )r   r  r   r>  r<   r  r>   r>   r?   stt_pipeline_update_config  s   
r   z/stt-pipeline/statsc              
   C   s   |  dstdddz!t }| dd| dd| dd| d	dt| d
W S  tyF } ztjd|dd tdddd }~ww )Nr   r   rC  r   r  r   r  r  r  )r  r  r  r  rY  zstt_pipeline_stats error: %sTr  r  z Failed to get STT pipeline stats)	r   r   r$  r  rh  r  r   r  rf  )r   rU  r  r>   r>   r?   stt_pipeline_stats  s   





r  c                 C   s   |  dstdddd S )Nr   r   rC  r   )r   r   r  r>   r>   r?   rb    r   rb  c                 C   s   |  d|f |  d uS )Nz
        SELECT 1
        FROM information_schema.tables
        WHERE table_schema = DATABASE() AND table_name = %s
        LIMIT 1
        r_  r  r>   r>   r?   r~    s
   	r~  r=   c                 C   sB   t | pd} t| dkr| rdS dS | d d  d| dd   S )Nr      z****   z...)rL   r   r  r>   r>   r?   	_mask_key  s   r  r  c                 C   s   t j| sg S z/t| dddd}| }W d    n1 s w   Y  dd |tdt|d d  D W S  tyB   g  Y S w )	Nr  r,  r  encodingr  c                 S   s   g | ]}| d qS )
)r  r  liner>   r>   r?   r    rX  z$_read_last_lines.<locals>.<listcomp>rd   r  )r   r  r  rR  	readlinesrp  rq  r   )r  rR   fhlinesr>   r>   r?   _read_last_lines  s   
&r  log_keyc                 C   s   t tdt| pdS )Nz^[a-zA-Z0-9_.-]+$r   )rU   r  matchrL   )r  r>   r>   r?   _pca_log_key_safe  s   r  c                 C   s    d dd t| p	d D S )Nr   c                 s   s$    | ]}|  s|d v r|V  qdS )z_-N)isalnumr  r>   r>   r?   r
    s   " z _pca_bid_safe.<locals>.<genexpr>)r  rL   r   r  r>   r>   r?   _pca_bid_safe  s    r  r
  c                    s   t |pd}|s
dS t| pd  sdS | dd| d| d| d| f}t fdd	|D r7dS td
 }|rF|d|kS dS )Nr   Tr   zbid=zbid:zBID zBusiness ID: c                 3       | ]}| v V  qd S r8   r>   )r  r   r  r>   r?   r
    r+  z,_pca_log_line_matches_bid.<locals>.<genexpr>zQ\b([A-Za-z0-9_-]+)_(?:raw_calls|callanalytics|sarvamresponse|callhistory|calls)\brd   )r  rL   r  r  rx  r  )r
  r   bid_safematching_tokenstable_matchr>   r  r?   _pca_log_line_matches_bid  s*   r  backend_dirc                 C   sZ  t j| }t|p
d}|r|dv rtdd| dd|rA|dr+|d| r8|d	rA|d| sAtdd
| d|dkrZt j|d}t j|rT|S tddd|dkrst j|d}t j|rm|S tdddt	|s}tddd| d}t jt j||}||kr||t j
 stdddt j|r|S tddd)zLMap API log_key to a path under backend_dir (per-BID stems or legacy names).r   >   orchestrationanalytics_updatesr   z0Shared legacy logs are not available inside BID z Manage viewr   orchestration_r   analytics_updates_zlog_key does not belong to BID r  orchestration.logr   zTLegacy orchestration.log not found; use orchestration_<bid> log key from PCA status.r  analytics_updates.logzLLegacy analytics_updates.log not found; use analytics_updates_<bid> log key.r  zInvalid log_key.logzInvalid log pathzLog file not found)r   r  realpathr  r   r   r  r  isfiler  sep)r  r  r   backend_realr  r   r  r>   r>   r?   _resolve_pca_log_file  sT   

r&  c              
      s  g t  t|p	d}dtdtdtdtddf
 fdd	}|r&d
| dnd}tttj |}|D ](}tj|}|	drI|dd n|}|t
d
d }	|||d|	 dd q6|rgd| dnd}
tttj |
}|D ](}tj|}|	dr|dd n|}|t
dd }	|||d|	 dd qwtj d}|stj|r|dddd tj d}|stj|r|dddd S )zUList per-BID orchestration / analytics log files plus legacy shared files if present.r   r  r  r   rH  r7   Nc              
      sv   | v rd S t j |}t j|}|rt j|nd}| |||||r/t|d  dndd |  d S )Nr   i   z KBz0 KB)r  r   rH  r  r  size)r   r  r  r#  getsizer  rV   rt  )r  r  r   rH  fullr  size_br  cardsseenr>   r?   add_card4  s   z(_pca_collect_log_cards.<locals>.add_cardr  r!  zorchestration_*.logr  zOrchestration (r$  z4Ingest / queue / orchestration log for this businessr  zanalytics_updates_*.logzAnalytics updates (z.Analytics success/update log for this businessr  r  zOrchestration (legacy shared)zELegacy log before per-BID files; new runs use orchestration_<bid>.logr   r  z!Analytics updates (legacy shared)zLegacy log before per-BID files)r  r  rL   rw  globr   r  r  basenamer  r   r#  )r  r   r  r.  	orch_glob
orch_pathsr  r  stembid_partanalytics_globanalytics_pathslegacy_olegacy_ar>   r+  r?   _pca_collect_log_cards.  sZ   &

r9  pattern
tail_linesc           
      C   s   g }t ttj| |D ]6}t||}tj|}|D ]&}| }	d|	v s3d|	v s3d|	v s3d|	v rCt||rC|	d| d|  qq|dd  S )NERRORFAILWARNWARNING[z] i)
rw  r/  r   r  r  r  r0  rR  r  r  )
r  r:  r;  r   outr  tailtaglnulr>   r>   r?   _pca_aggregate_log_errorsr  s   
 rF  r  c                 C   s*   t |pd}|r|  d| dS |  dS )Nr   r   r!  z*.log)r  )r  r   r  r>   r>   r?   _pca_log_pattern_for_bid  s   
rG  rF  )atqueues_RABBITMQ_CACHEgffffff?)timeout_sec
queue_namerK  c          
   
      sr  t   tdp
i  ttdpd } r.|tk r. }|d ur.i |ddS dtttf dtttf f fdd	}zYtj	g d
tj
d|d}i }| D ]!}| d}t|dkrwt|d t|d d d||d < qVtd< dd | D td< |v rdi| W S |ddddW S  ty }	 z|d d t|	dW  Y d }	~	S d }	~	ww )NrI  rH  r   T)r   r{  r;  r7   c                    s0   t  }| |< td< |td< i | diS )NrH  rI  r   )r   rJ  )r;  mergedcached_queuesrz  rL  r>   r?   _cache_and_return  s
   z0_rabbitmq_queue_stats.<locals>._cache_and_return)rabbitmqctllist_queuesr   r  	consumersz-q)stderrr  r  	rN  rd   r  )r  rS  rf  c                 S   s   i | ]\}}|d |i|qS r  r>   )r  r   rU  r>   r>   r?   rF    s    z)_rabbitmq_queue_stats.<locals>.<dictcomp>r   zQueue not found)r   rJ  r   rb   _RABBITMQ_CACHE_TTL_SECr
   rL   r	   
subprocesscheck_outputSTDOUT
splitlinesr   r  r   rV   rK  r   )
rL  rK  	cache_ager{  rP  rA  r  r
  rZ  r  r>   rN  r?   _rabbitmq_queue_stats  sN   
*



r\  selected_bidc              
      s   i }rfdd|d< fdd|d< ndd |d< dd |d< t |d< i }td	d
7  fdd| D }t|D ]}|| }z| ||< W q< tyZ   ddd||< Y q<w W d   |S 1 sfw   Y  |S )zAResolve orchestrator / pipeline / STT process state concurrently.c                      s   t d  dS )Norchestrator_loop_.sh_process_runningr>   r]  r>   r?   rj    s    z+_pca_job_process_snapshot.<locals>.<lambda>loopc                      s   t d  S )Norchestrate_pipeline.py --bid r`  r>   rb  r>   r?   rj        pipelinec                   S   s   t dS )Nr^  r`  r>   r>   r>   r?   rj    rk  c                   S   s
   dd dS )NFrunningpidr>   r>   r>   r>   r?   rj    s   
 sttrN  )max_workersc                    s   i | ]
\}}  ||qS r>   )submit)r  r=   fn)poolr>   r?   rF    r  z-_pca_job_process_snapshot.<locals>.<dictcomp>FNrg  )_stt_worker_processr   rK  r   r  r   )r]  tasksresultsfuturesfuturer=   r>   )rn  r]  r?   _pca_job_process_snapshot  s,   
rt  r  c                 C   s"  dd |D }|si S d dgt| }| d| d| |  p$g }i }|D ]e}t|dp2d }t|d	p=d }t|d
pHd }	|rX|ddd  nd }
|rf|ddd  nd }|	rt|	ddd  nd }|
|||||	t|dpdd|t|d< q)|S )Nc                 S   s(   g | ]}t |pd  rt | qS r  r  )r  r   r>   r>   r?   r       ( z*_pca_business_user_map.<locals>.<listcomp>r  r  aE  
        SELECT
            uba.bid,
            GROUP_CONCAT(
                bu.id
                ORDER BY CASE WHEN uba.role = 'admin' THEN 0 ELSE 1 END, bu.id
                SEPARATOR ', '
            ) AS business_user_ids,
            GROUP_CONCAT(
                bu.username
                ORDER BY CASE WHEN uba.role = 'admin' THEN 0 ELSE 1 END, bu.id
                SEPARATOR ', '
            ) AS business_login_user_ids,
            GROUP_CONCAT(
                COALESCE(NULLIF(bu.full_name, ''), bu.username)
                ORDER BY CASE WHEN uba.role = 'admin' THEN 0 ELSE 1 END, bu.id
                SEPARATOR ', '
            ) AS business_user_names,
            COUNT(*) AS business_user_count
        FROM user_business_access uba
        JOIN business_users bu ON bu.id = uba.user_id
        WHERE uba.bid IN (zE)
          AND bu.is_active = TRUE
        GROUP BY uba.bid
        business_user_idsr   business_login_user_idsbusiness_user_namesr  rd   r   business_user_count)ri  rv  business_login_user_idrw  r   rx  ry  r   )	r  r   r!  r#  rL   r   r   r  rV   )r  r  
clean_bidsr'  r(  r  r)  user_idslogin_user_idsr   first_user_idfirst_login_user_idr
  r>   r>   r?   _pca_business_user_map  s8   	r  c                 C   sJ   zt jdd| gdddd}dd |jpd D W S  ty$   g  Y S w )	Npgrepz-afTr  )capture_outputr  r  c                 S   r  r>   r  r	  r>   r>   r?   r    r  z(_pgrep_process_lines.<locals>.<listcomp>r   )rW  runstdoutrZ  r   )r:  r  r>   r>   r?   _pgrep_process_lines  s   r  c                    s"   |    d}t fdd|D S )N)grepr  z
python3 -cextglobr  dump_bash_statezsnap=$(command catc                 3   r  r8   r>   )r  markerloweredr>   r?   r
  &  r+  z)_is_noise_process_line.<locals>.<genexpr>)r   r  )r
  noise_markersr>   r  r?   _is_noise_process_line  s   	r  c                 C   sn   t | D ]-}t|rq| dr|ddkrq|dd}|r1|d  r1dt|d d  S qd	ddS )
zUReturn the first real matching process PID, ignoring shell wrappers and pgrep itself.r_  r  rk   Nrd   r   Trg  F)r  r  r  rx  r  r,  rV   )r:  r
  rZ  r>   r>   r?   ra  )  s   
ra  c                 C   sJ   t | pd } | sdS td|  ddrdS ttd|  dS )zOTrue when this BID's orchestrator loop or an active orchestrate run is running.r   Fr^  r_  rh  Trd  )rL   r   ra  r   rU   r  r>   r>   r?   _bid_loop_running6  s   r  c                  C   s   t d} | dr| S t dS )Nz$call-proccessing/stt_pipeline/run.pyrh  zrun.py --worker)ra  r   )procr>   r>   r?   ro  @  s   
ro  c           	      C   s@  | d}t | |s|ddi dg g dS | d| d t|  p#i dp(d}| d| d d	d
 |  p;g D }| d| d t|  pMi dpRd}| d| d dd |  peg D }| d| d dd |  pyg D }| d| d t|  pi dpd}||||t|d ||dS )Nr  r   )r   r_  calls_today	by_statustotal_minutescalls_per_daycalls_per_hourSELECT COUNT(*) AS c FROM `r  r  z_
        SELECT COALESCE(CAST(status AS CHAR), '0') AS status_key, COUNT(*) AS c
        FROM `z&`
        GROUP BY status_key
        c                 S   "   i | ]}t |d  t|d qS 
status_keyr  rL   rV   r  r>   r>   r?   rF  ^     " z&_collect_bid_stats.<locals>.<dictcomp>z(` WHERE DATE(call_starttime) = CURDATE()zG
        SELECT DATE(call_starttime) AS d, COUNT(*) AS c
        FROM `z`
        WHERE call_starttime >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
        GROUP BY DATE(call_starttime)
        ORDER BY d ASC
        c                 S   s0   g | ]}| d rt|d  t|d dqS )dr  )daterx  )r   rL   rV   r  r>   r>   r?   r  n  s    z&_collect_bid_stats.<locals>.<listcomp>zG
        SELECT HOUR(call_starttime) AS h, COUNT(*) AS c
        FROM `zv`
        WHERE DATE(call_starttime) = CURDATE()
        GROUP BY HOUR(call_starttime)
        ORDER BY h ASC
        c                 S   s<   g | ]}| d durt|d  ddt|d dqS )hNr9  z:00r  )rT  rx  )r   rV   r  r>   r>   r?   r  }  s
     zk
        SELECT COALESCE(SUM(TIMESTAMPDIFF(SECOND, call_starttime, call_endtime)), 0) AS sec
        FROM `zP`
        WHERE call_starttime IS NOT NULL AND call_endtime IS NOT NULL
        secr  )r~  r!  rV   rs  r   r#  )	r  r   rM  r_  r  r  r  r  rQ  r>   r>   r?   _collect_bid_statsG  sh   



	
	

r  c                 C   sn   | d}| d}t | |sdS t | |sdS | d| d| d t|  p*i dp/d}t|d S )	Nr  ry  r   ak  
        SELECT COALESCE(
            SUM(
                CASE
                    WHEN s.transcript IS NOT NULL AND s.transcript != ''
                    THEN COALESCE(s.duration, TIMESTAMPDIFF(SECOND, r.call_starttime, r.call_endtime))
                    ELSE 0
                END
            ),
            0
        ) AS transcribed_seconds
        FROM `z` r
        LEFT JOIN `z#` s ON r.callid = s.callid
        transcribed_secondsr  )r~  r!  rb   rs  r   rV   )r  r   r  r  r7  r>   r>   r?    _get_transcribed_minutes_for_bid  s   



r  c                  C   sr   t  ,} |  }|d |d | d u}|s'|d W d    d S W d    d S 1 s2w   Y  d S )Na  
            CREATE TABLE IF NOT EXISTS pca_business_allocations (
                bid VARCHAR(64) PRIMARY KEY,
                monthly_call_limit INT NOT NULL DEFAULT 0,
                monthly_minute_limit INT NOT NULL DEFAULT 0,
                notes TEXT NULL,
                updated_by VARCHAR(255) NULL,
                updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
            z
            SELECT 1
            FROM information_schema.columns
            WHERE table_schema = DATABASE()
              AND table_name = 'pca_business_allocations'
              AND column_name = 'monthly_minute_limit'
            LIMIT 1
            z
                ALTER TABLE pca_business_allocations
                ADD COLUMN monthly_minute_limit INT NOT NULL DEFAULT 0
                rr  )r&  r  has_minute_colr>   r>   r?   _ensure_usage_allocation_table  s    
"r  z/pca/businesses-overviewc                 C   s*  t |  t \}}tt }t }i }t }| }dd |p#g D }t	|t
t||B }	|p4g D ]V}
t|
dp>d }|sEq5t||}|	|i }||
dpZd| |d|d|d	|d
|d|d|ddt|
dd||v t|d|||< q5|D ]@}||v rqt||}|	|i }|d| |d|d|d	|d
|d|d|ddddt|d|||< qW d    n1 sw   Y  tdd | D }td}|pt|d}t  i }t 2}| }|d | pg D ]}tt|dpd|t|dp&d< qW d    n	1 s5w   Y  | D ]\}}||d|d< q>tdd | D }tdd phtddphdd}d}t
| dd d||r| | n|d tt |||t|dd!d"S )#Nc                 S   s8   g | ]}t |d pd rt |d pd qS )r   r   rf  )r  br>   r>   r?   r    s   8 z+pca_businesses_overview.<locals>.<listcomp>r   r   r   	Business ri  rv  rz  rw  r   rx  ry  r   rC  T)r   r   ri  rv  rz  rw  r   rx  ry  rC  has_pipelineloop_runningc                 s   s    | ]}| d V  qdS )r  Nr  rn  r>   r>   r?   r
    r  z*pca_businesses_overview.<locals>.<genexpr>zorchestrator_loop.shrh  z@SELECT bid, webhook_ingest_enabled FROM business_pipeline_configr  Fc                 s   s    | ]}|rd V  qdS )rd   Nr>   r3  r>   r>   r?   r
    r+  PUBLIC_BASE_URLr  r  c                 S      t | dS r  rJ  rh  r>   r>   r?   rj  (  re  z)pca_businesses_overview.<locals>.<lambda>r  r  )r  orch_runningstt_running)r   webhook_ingest_urlwebhook_ingest_full_urlwebhook_ingest_header webhook_ingest_secret_configuredwebhook_ingest_enabled_countglobal_jobs)rb  r   rD  r  r$  r  ro  r  r  r  rw  rL   r   r   r  rU   r  r  r  ra  r<  r!  r#  rV   rK  rh  r   r   r   r  r3   )r   r   r   r  stt_procby_bidr&  r  
known_bidsuser_by_bidr  r   s	user_infoany_loop_runninggeneric_loopr  webhook_by_bidr)  webhook_enabled_countpublic_basewebhook_pathr>   r>   r?   pca_businesses_overview  s   





/	
r  z/pca/webhook-ingest-logsz
YYYY-MM-DD)rn   rH     i8"  actionskip_reasonsignature_validr;  c                 C   s2   t |  |s|r
d}tj|||||||||	|
d
S )z@Master-only audit log for Mcube webhook POSTs (pcaa_ingest_log).N)
r   r  r  r  ru  rl  rm  r;  rR   rQ  )rb  r$  list_webhook_ingest_logs)r   r   r  r  r  ru  rl  rm  r;  rR   rQ  r>   r>   r?   pca_webhook_ingest_logs6  s   r  z
/pca/usagec                 C   sr  t |  t  t \}}g }t }| }|d dd | p$g D }|p*g D ]t}t|	dp4d
 }|s;q+t||}	t||}
|	|i }t|	dpW|	dpWd}|dkritt|
| d	 d
dnd }|||	dpwd| t|		dp~dt|		dpd|
||t|dko|
|k|	d|	dd
 q+W d    n1 sw   Y  dt|dd diS )Nz&SELECT * FROM pca_business_allocationsc                 S   s   i | ]	}t |d  |qS r  r*  r  r>   r>   r?   rF  `  r  zpca_usage.<locals>.<dictcomp>r   r   monthly_minute_limitmonthly_call_limitr   r  r        Y@r   r  r  r_  r  r"  )
r   r   r  r_  used_minutesr  usage_percentlimit_exhaustedr  r"  r   c                 S   r  r  rJ  rh  r>   r>   r?   rj    re  zpca_usage.<locals>.<lambda>r  )rb  r  r   rD  r  r  r!  r#  rL   r   r   r  r  rV   rq  r  r  rU   rw  )r   r   r   
usage_rowsr&  r  	alloc_mapr  r   rU  r  
allocationmonthly_limit	usage_pctr>   r>   r?   	pca_usageV  sP   




#r  z/pca/usage/{bid}c           	      C   s  t | | t  t )}| }t|t| }|dt| f | p%i }t|t| }W d    n1 s7w   Y  t	|
dpH|
dpHd}|dkrZtt|| d ddnd }t| t	|
dpfdt	|
d	pnd|||t|dkoz||k|
d
|
dd	S )Nz5SELECT * FROM pca_business_allocations WHERE bid = %sr  r  r   r  r  r  r  r_  r  r"  )	r   r  r_  r  r  r  r  r  r"  )r   r  r  r  r  rL   r!  rs  r  rV   r   rq  r  rU   )	r   r   r&  r  rU  r  r  r  r  r>   r>   r?   pca_usage_for_bid  s:   

r  z/pca/usage/{bid}/allocationc                 C   s   t | t  t|dp|dpd}|dpd }|d}t }| }|dt| |||f W d    n1 s?w   Y  dt| ||dS )	Nr  r  r   r  r  a  
            INSERT INTO pca_business_allocations (bid, monthly_call_limit, notes, updated_by)
            VALUES (%s, %s, %s, %s)
            ON DUPLICATE KEY UPDATE
                monthly_call_limit = VALUES(monthly_call_limit),
                monthly_minute_limit = VALUES(monthly_call_limit),
                notes = VALUES(notes),
                updated_by = VALUES(updated_by)
            T)updatedr   r  r  )rb  r  rV   r   r  r  r!  rL   )r   r;  r   r  r  r  r&  r  r>   r>   r?   pca_update_allocation  s$   

	r  z/pca/bid/{bid}/statsc                 C   sH   t | t }| }t|t| W  d    S 1 sw   Y  d S r8   )rb  r  r  r  rL   )r   r   r&  r  r>   r>   r?   pca_bid_stats  s
   $r  z/pca/statuslitec                 C   s  t | t| pd pd }t|pt|}t|}|dp#dd d}|dp-dd d}|dp7dd d}td}	di |d	}
|sz<|}|sUt }|rS|d nd
}t	 }|
 }t||}|dd|di |d	}
W d    n1 szw   Y  W n	 ty   Y nw tjtjt}t||}t|d}ddd||d|ddddddt|ddd|ddddddt|ddd|dddgdd|	d|	d|	d d!g|
|d"S )#Nr   rc  Frg  rf  rj  stt_jobsr   rY  r  r   1713r_  r  rh  orchestrator-loopzOrchestrator Loopz&Runs orchestrate pipeline on schedule.Tri  z1bash dashboard-backend/orchestrator_loop_<bid>.sh)job_keyr   rH  rh  	scheduledrequiredri  command
stt-workerz
STT Workerz.Consumes stt_jobs queue and transcribes calls.z=call-proccessing/stt_pipeline/venv/bin/python run.py --workerorchestrate-pipelinezOne-time Orchestrate Pipelinez#Manual on-demand orchestration run.z=python3 dashboard-backend/orchestrate_pipeline.py --bid <bid>z!Speech-to-text transcription jobsr  rS  rf  )r   rH  r  rS  rf  )jobsrI  	db_statusr%  )rb  rL   r   rU   rt  r   r\  r$  r  r  r  r  r   r   r  dirnameabspath__file__r9  )r   r  r   r]  skip_db_statsproc_snapshot	loop_procpipeline_procr  queuer  default_bidr  r&  r  rU  r  r%  r  r>   r>   r?   
pca_status  s   



"	r  z/pca/logs/{log_key}r  filterc                    s   t | tjtjt}t||  d}t||d} fdd|D }|dkr3dd |D }d	|iS |dkr>dd |D }d	|iS )
Nr  r  c                    s   g | ]	}t | r|qS r>   )r  r  rD  r  r>   r?   r  /  r  zpca_logs.<locals>.<listcomp>r  c                 S   (   g | ]}d |  v sd|  v r|qS )r<  r=  rR  r  r>   r>   r?   r  1  ru  warningsc                 S   r  )r>  r?  r  r  r>   r>   r?   r  3  ru  r  )rb  r   r  r  r  r  r&  r  )r  r  r  r   r   r  r)  	out_linesr>   r  r?   pca_logs#  s   r  z/pca/failuresc                 C   s  t | t| pd pd }g }g }g }t v}| }|r!|gnt }|D ]^}	|	 d}
t||
s4q'|d|
 d |	 pBg D ]}|	|d< |
| qC|d|
 d |	 p]g D ]}|	|d< |
| q^|d|
 d |	 pxg D ]}|	|d< |
| qyq'W d    n1 sw   Y  tjtjt}t|td||d	}t|td
||d	}t|dd ddd d }t|dd ddd d }t|dd ddd d }|||t|t|t|d||ddS )Nr   r  zd
                SELECT callid, agentname, groupname, call_starttime, fileurl
                FROM `z[` WHERE status = -2
                ORDER BY call_starttime DESC LIMIT 100
                r   z[` WHERE status = -1
                ORDER BY call_starttime DESC LIMIT 100
                z[` WHERE status = -3
                ORDER BY call_starttime DESC LIMIT 100
                r  r  r  c                 S   rI  Nr,  r   rJ  r  r>   r>   r?   rj  s  rK  zpca_failures.<locals>.<lambda>Trl  rB   c                 S   rI  r  rJ  r  r>   r>   r?   rj  t  rK  c                 S   rI  r  rJ  r  r>   r>   r?   rj  u  rK  )stt_failed_countinvalid_url_countno_transcript_count)r  r  )
stt_failedinvalid_urlanalytics_no_transcriptr  
log_errors)rb  rL   r   r  r  r$  r  r~  r!  r#  r  r   r  r  r  r  rF  rG  rw  r   )r   r   r]  r  r  r  r&  r  r  bid_itemrM  r)  r  orchestration_erranalytics_errr>   r>   r?   pca_failures7  s   

(r  z/pca/queue/{queue_name}/detailsc                 C   s4  t | t|pd pd }t| }di |d}g }zpt }|p(|r'|d nd}| d}	t P}
|
 }t||	rz|	d|	 d dd	 |
 pLg D }|	d
|	 d t| p^i dpcd}|||d}|	d|	 d |
 pyg }W d    n1 sw   Y  W n	 ty   Y nw |||dS )Nr   r   r  r  r  zNSELECT COALESCE(CAST(status AS CHAR), '0') AS status_key, COUNT(*) AS c FROM `z` GROUP BY status_keyc                 S   r  r  r  r  r>   r>   r?   rF    r  z%pca_queue_details.<locals>.<dictcomp>r  r  r  zc
                    SELECT callid, agentname, groupname, call_starttime
                    FROM `zb` WHERE status = 1
                    ORDER BY call_starttime DESC LIMIT 200
                    )r  db_breakdownqueued_calls)rb  rL   r   r\  r$  r  r  r  r~  r!  r#  rV   rs  r   r   )rL  r   r   r]  r  r   r  r  r  rM  r&  r  r  rY  r>   r>   r?   pca_queue_details  s@   


r  z/pca/api-creditsc              
   C   s   t |  tdtdd}tdtdd}tdtdd}|r&dnd}d	d
||r/dndt|t|ddddddd||ddgiS )NSARVAM_SUBSCRIPTION_KEYr   AWS_NOVA_MODELr  
AWS_REGIONz
ap-south-1r  not_configuredr  z	Sarvam AIActiveNot configuredzspeech-to-text-translate/job/v1https://dashboard.sarvam.ai/)rg   r   status_labelkey_set
key_maskedmodeldashboard_urlzAWS Bedrockpay_per_usez3https://console.aws.amazon.com/cost-management/home)rg   r   r
  billing_typer  regionr  rb  r   r   r   r   rU   r  )r   
sarvam_key	aws_model
aws_regionsarvam_statusr>   r>   r?   pca_api_credits  s0   

r  z/pca/stt/configc                 C   s^   t |  tdtdd}tdtdd}|rdnd}||r#dndt|t||d	d
S )Nr  r   TRANSCRIPTION_PROVIDERr-  r  r  r  r  r	  )r   r
  r  r  r  r  r  )r   r=   r  r   r>   r>   r?   pca_stt_config  s   
r  z/pca/stt/update-keyc           
   
   C   sp  t | t| pi dpd }|stdddtjtjtj	t
d}zzg }tj|rMt|ddd	d
}| }W d    n1 sHw   Y  d}t|D ]\}}| drjd| d||< d} nqS|sv|d| d t|ddd}|| W d    n1 sw   Y  |td< |tjd< dt|dW S  ty }	 z	tdd|	 dd }	~	ww )Nr=   r   r  zkey is requiredr   z.envr  r,  r  r  FzSARVAM_SUBSCRIPTION_KEY=r  Twr  r  )r  r  r  zFailed to update key: )rb  rL   r   r   r   r   r  r  r  r  r  r  rR  r  r  r   r  
writelinesr   environr  r   )
r;  r   r=   env_pathr  r  r  r  r
  r  r>   r>   r?   pca_stt_update_key  s<   

r  z/pca/jobs/{job_key}/startr  c              
   C   s  t | tjtjt}tjtj|d}| dkrxt }|dr.dd|ddS tj|dd	}tj|d
dd}tj	|sNt
dd| dtdddd}ztj|ddg|tj|tjddd}	W |  n|  w d|	jdS | dkrt|pi dpd }
|
st
dddt|pi dpd}d | d!|
 d"| }n| d#kr$t|pi dpd }
|
st
dddtj|d$|
 d%}tj	|st|d&d'd(3}|d) |d* |d+| d, |d- |d.|
 d/ |d0 |d1 W d    n	1 sw   Y  t|d2 d3| d4}nt
d5d6dtjd7d8d+| d9| d:|  d;gtjtjdd<}	|	jd=d>\}}|pNd r_t|pWd?  d@ nd }d|dS )ANz..r  rh  Falready_runningri  r  reasonri  zcall-proccessingstt_pipelinevenvbinpythonr  zSTT venv python not found: r   z/tmp/pca_stt-worker.logabr   )	bufferingzrun.pyz--workerT)cwdstdinr  rT  start_new_session	close_fdsr  ri  r  r   r   r  r1  rR   rl   z	python3 "z /orchestrate_pipeline.py" --bid z	 --limit r  r^  r_  r  r,  r  z#!/usr/bin/env bash
zset -euo pipefail
cd ""
while true; do
(  python3 orchestrate_pipeline.py --bid  --limit 50
  sleep 300
done
  zbash "rA  r   zUnknown job keybash-lcz" && nohup z >/tmp/pca_.log 2>&1 & echo $!r  rT  r  rw  r  r1  r  )rb  r   r  r  r  r  r  ro  r   r  r   rR  rW  PopenDEVNULLrY  rx  ri  rL   r   rV   writechmodPIPEcommunicaterZ  )r  r;  r   base_dirproject_rootr  stt_dir
python_binlog_fhr  r   rR   cmdscriptr  rA  r   ri  r>   r>   r?   pca_start_job  sr   






.
rG  z+/admin/businesses/{bid}/orchestration/startc                 C   s\  t | td|  d}|drdd|ddS tjtjt}tj|d|  d}tj	|sxt
|dd	d
.}|d |d| d |d |d|  d |d |d W d    n1 smw   Y  t|d tjddd| d|  dgtjtjdd}|jdd\}}	|pd rt|pd  d nd }
d|
d S )!Nr^  r_  rh  Fr   ri  r!  r  r,  r  z&#!/usr/bin/env bash
set -euo pipefail
r.  r/  r0  r1  r2  r3  r4  r5  r6  r7  znohup bash "z" >/tmp/orch_r8  Tr9  rw  r  r   r1  r  r-  )rb  ra  r   r   r  r  r  r  r  r  rR  r<  r=  rW  r:  r>  r?  r   rV   rZ  )r   r;  r   r  r@  rF  r  r  rA  r   ri  r>   r>   r?   admin_orchestration_start:  s.   




(
rH  z/api-push/{bid}/configc                 C   sF   t | t  t| }|si t t| ddd d}d|dS )NF)r   has_auth_tokenhas_api_key_valueapi_push_effective_atTr
  )rb  r$  ensure_api_push_tablesget_api_push_config_api_push_config_defaultsrL   )r   r   r<   r>   r>   r?   rM  T  s   

rM  rE   c                 C   s$   t | t| |p
i }d|ddS )NTzAPI push settings saved)rQ  r>  rY   )rb  r$  save_api_push_config)r   r;  r   savedr>   r>   r?   rO  d  s   rO  z/api-push/{bid}/logs   c                 C   s    t | tj| |d}d|dS )Nr  T)rQ  r%  )rb  r$  get_api_push_logs)r   rR   r   r%  r>   r>   r?   rR  o  s   
rR  z/api-push/{bid}/testc                 C   sD   t | t|dpd }|stdddtj| |ddd}|S )	Nru  r   r  rW  r   manual_testT)trigger_eventforce)rb  rL   r   r   r   api_push_servicepush_call_update)r   r;  r   ru  r  r>   r>   r?   test_api_pushz  s   rX  z,/admin/businesses/{bid}/orchestration/statusc                 C   sf   t | td|  d}tjtjt}tjtj|d|  d}t	|
d|
d|dS )Nr^  r_  rh  ri  )rh  ri  script_exists)rb  ra  r   r  r  r  r  r  r  rU   r   )r   r   r	  r@  rF  r>   r>   r?   admin_orchestration_status  s
   rZ  z*/admin/businesses/{bid}/orchestration/stopc                 C   sD   t | ztddd|  dg W ddiS  ty!   Y ddiS w )Nr6  r7  zpkill -f 'orchestrator_loop_z.sh'stoppedT)rb  rW  rT  r   r   r>   r>   r?   admin_orchestration_stop  s   r\  )F)NN)r7   N)rj   r8   )NrJ  F)NNNNNNNF)rS  )NNNNrB   )NNr  )r  )r  N(  r/  r  loggingr   r  r   r  r-  rW  concurrent.futuresr   r   rU  rY  ior   r   r   rS  r   typingr	   r
   r   r   urllib.parser   rn  r  fastapir   r   r   r   r   r   r   r   r   fastapi.encodersr   fastapi.middleware.corsr   fastapi.responsesr   r   r   starlette.middleware.baser   pydanticr   r   r   r   ri   r    r$  r!   rbacr   r  r"   quality_parameters_handlerr#   propensity_parameters_handlerr$   r  r%   r  r'   mcube_ai_agentsr(   r)   r*   r+   r  r,   lead_insights_servicer-   lead_upload_servicer.   r/   r  r0   r1   r2   r3   rV  r4   basicConfigINFO	getLoggerrH   r  rL   r@   r   r  r  r  rA   rM   rr  rD   rO   rW   rc   r=  rm   rt   rv   rz   r   r   r   r   rU   r   r   r   r   r   r   r   r   r   r"  r   r   r   r   r   r   r  r  r  r:  rB  rC  rj  r  ru  rw  ry  r  r  r  r  r@  r  r  rV   r  r  r  r  r  r  r  r  r   r  r  r  r!  r#  r2  r;  r>  rI  r  r  appadd_middlewarer  
middlewarer  r   r  r  r  r  postr  r  r  r  r  r  r  r  r  putr  r  r  r  r	  r
  r  r  r  r   r$  r)  r0  rC  rE  rH  rP  ra  patchrd  r^  rj  rr  rt  rw  rz  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r	  r  r  rb   r*  rr  rn  r  r  r>  rB  rC  rI  r\  r`  rd  re  rl  ru  rx  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r	  r  r  r"  r'  r*  r-  r.  r1  r4  r6  r8  rB  rE  rK  rL  rP  rR  r[  r`  rd  rq  ru  rv  ry  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r   r  rm  r  r  r  r  r	  r  r  r  r  r  r  r  r$  r&  r'  rE  rG  rS  r  r`  rp  rw  r|  r  r  r  r  r  r0  r  r  r  r  r  r  r  r>  r  r  r  r  r  r  r  r  r  r  r  r   r  rb  r~  r  r  r  r  r  r&  r9  rF  rG  rJ  rV  r\  rt  r  r  r  ra  r  ro  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rG  rH  rM  r   rO  rR  rX  rZ  r\  r>   r>   r>   r?   <module>   s  
 ,



(,&	"".
"


5$.
",B",&*"."$")")Q&
"""."

 


	



((('


((




	

8

 



(

&

&


!
 
4
$
(
*
0
*


7

(
7

>




	

	

"

!



1

,








%

*



W

&:
:
:
4
:



	




	
5	

 $	 322
6
	

I	
I	"









*
6

"
&
2
,




$

&
"



$

<

"
(


$

"



*
6
!




*
*
6
0
6
	<
	0
	
&
6
!
*$
*
?

+





5.
b6
	
	


 "
 
6
6
0$*"

'
h 	(662
* 4


,.:(


c.

A
.
?

&


4




*



+"
"

"1


-

$

%



4
,







	

*	
&





& &",2)G""2.0.6"
 P
#&
_
	

&
,*
"


 *


S

6
O

%&
&
2
@
@>
*










*
.