o
    Fj.                    @   s  d dl 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	 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 d dlmZ d dlmZ d dlmZ d d	lm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" d dl#m$Z$ ej%ej&dd e'e(Z)ee(Z*e*j+e ee*ddg dddgdid ee*jZeeZ,ee*jZ-ee*jZee*jZee*_e"e*jZ!i Z.dZ/dd Z0dd Z1dd Z2dd  Z3d!d" Z4dd$d%Z5d&d' Z6d(d) Z7dd+d,Z8e*j9d-d.gd/d0d1 Z:e*j9d2d3gd/e0d4d5 Z;e*j9d6d3gd/e0d7d8 Z<e*j9d9d3gd/e0d:d; Z=e*j9d<d.gd/e0d=d> Z>e*j9d?d.gd/e0d@dA Z?e*j9dBd.gd/e0dCdD Z@e*j9dEd.gd/e0dFdG ZAe*j9dHd.gd/e0dIdJ ZBe*j9dKd.gd/e0dLdM ZCe*j9dNd.gd/e0dOdP ZDe*j9dQd.gd/e0dRdS ZEe*j9dTd.gd/e0dUdV ZFe*j9dWd.gd/e0dXdY ZGe*j9dZd.gd/e0d[d\ ZHe*j9d]d.gd/e0d^d_ ZIe*j9d`d.gd/e0dadb ZJe*j9dcd.gd/e0ddde ZKe*j9dfd.gd/e0dgdh ZLe*j9did.gd/e0djdk ZMe*j9dldmgd/e0dndo ZNe*j9dpdqgd/e0drds ZOe*j9dtd.gd/e0dudv ZPe*j9dwd3gd/e0dxdy ZQe*j9dzd.gd/e0d{d| ZRd}d~ ZSdddZTeUh dZVdZWdd ZXdd ZYdd ZZdddZ[dd Z\eUh dZ]dd Z^dd Z_dd Z`dd ZadddZbdd Zcdd Zddd Zedd Zfe*j9dd.gd/e0dd Zge*j9dd3gd/e0dd Zhe*j9dd.gd/e0dd Zie*j9dd.gd/e0dd Zje*j9dd.gd/e0dd Zke*j9dd.gd/e0dd Zle*j9dd.gd/e0dd Zme*j9dd.gd/e0dd Zne*j9dd.gd/e0dd Zoe*j9dd3gd/e0dd Zpe*j9dd3gd/e0dd Zqe*j9dd.gd/e0ddń Zre*j9dd.gd/e0ddȄ Zsddʄ Zt								#ddd̄Zui ddΓddГddғddԓdd֓ddؓddړddܓddޓdddddddddddddddddddddddddd	ZvdZwi evddiZxi evddiZydd Zzdd Z{dd  Z|dd Z}dd Z~dd Zdd Zd	d
 Zdd Zdd Zdd ZdddZdddZe*j9dd.gd/e0dd Ze*j9dd.gd/e0dd Ze*j9dd3gd/e0dd Ze*j9dd3gd/e0dd  Zeed!d"Zd#Zd$d% Zd&d' Zd(d) Zd*d+ Zd,d- Zdd/d0Zdd1d2Ze*j9d3d.gd/e0d4d5 Ze*j9d6d.gd/e0d7d8 Ze*j9d9d.gd/e0d:d; Ze*j9d<d3gd/e0d=d> Ze*j9d?d3gd/e0d@dA Ze*j9dBd.gd/e0dCdD Ze*j9dEd3gd/e0dFdG Ze*j9dHd3gd/e0dIdJ Ze*j9dKd.gd/e0dLdM Ze*j9dKd3gd/e0dNdO Ze*j9dPd3gd/e0dQdR Ze*j9dSd.gd/e0dTdU Ze*j9dSdmgd/e0dVdW Ze*j9dXd.gd/e0dYdZ Ze*j9d[d.gd/e0d\d] Ze*j9d^d.gd/e0d_d` Ze*j9dad.gd/e0dbdc Ze*j9d^d3gd/e0ddde Ze*j9dadfgd/e0dgdh Ze*j9dadmgd/e0didj Ze*j9dkd3gd/e0dldm Ze*j9dnd.gd/e0dodp Ze*j9dqd.gd/e0drds Ze*j9dtd3gd/e0dudv Ze*j9dwd.gd/e0dxdy Ze*j9dzd3gd/e0ee d{d| Ze*j9d}d3gd/e0ee d~d Ze*j9dd3gd/e0ee dd Ze*j9dd.gd/e0ee dd Ze*j9dd.gd/e0ee dd Ze*j9dd.gd/e0ee dd Ze*j9dd.gd/e0ee dd Ze*j9dd3gd/e0ee dd Ze*j9dd3gd/e0ee dd Ze*j9dd3gd/e0ee dd Ze*j9ddmgd/e0ee dd Zdd Zdd Zdd Zdd Ze*j9dd3gd/e0ee dd Ze*j9dd.gd/e0ee dd Ze*j9dd3gd/e0ee dd Ze*j9ddfgd/e0ee dd Ze*j9ddmgd/e0ee dd Ze*Ðddd Ze*Ðddd Ze*Ðd.dd Ze*j9dd.gd/e0dd Ze*j9dd.gd/e0dd Ze*j9dd3gd/e0dd Ze*j9dd.gd/e0dd Ze*j9dd3gd/e0dÐdĄ Ze*j9dd3gd/e0dƐdǄ Ze*j9dd.gd/e0dɐdʄ Ze*j9dd3gd/e0d̐d̈́ Ze*j9dd.gd/e0dϐdЄ Ze*j9dd3gd/e0dҐdӄ Ze*j9dd.gd/e0dԐdՄ Ze*j9ddmgd/e0dאd؄ Ze(dkre)Ӑdڡ e)Ӑde*jԐdܐdݡ  e)Ӑde*jԐdd#  e*je*jԐdde*jԐdde*jԐdd#d dS dS (      )Flaskrequestjsonify)CORSN)datetime	timedeltawraps)unquote)Config)DatabaseHandler)AnalyticsService)QualityParametersHandler)ObjectionClassificationsHandler)AuthHandlerrequire_authrequire_business_access)
RAGHandler)LeadSquaredServicez4%(asctime)s - %(name)s - %(levelname)s - %(message)s)levelformatz/**)GETPOSTPUTPATCHDELETEOPTIONSContent-TypeAuthorization)originsmethodsallow_headers)	resources,  c                    s   t   fdd}|S )Nc               
      sl   z | i |W S  t y5 } z!tjd j dt| dd tt|dddfW  Y d }~S d }~ww )Nz	Error in : T)exc_infoz)An error occurred processing your request)errormessage  )	Exceptionloggerr'   __name__strr   )argskwargsef ./home/aiteam/pcaa-dev/dashboard-backend/app.pydecorated_functionA   s    z)handle_errors.<locals>.decorated_functionr   )r2   r5   r3   r1   r4   handle_errors@   s   	r6   c                 C   s`   |  drdS |  dg D ] }t| dd  }t| dt|kr-|dkr- dS qdS )	N	is_masterT
businessesrole bidadminF)getr-   striplower)userr;   businessr9   r3   r3   r4   user_has_business_adminM   s   
rB   c                 C   s.   t tdd pi }t|| stddidfS d S )Ncurrent_userr'   Business admin access required  )getattrr   rB   r   )r;   r@   r3   r3   r4   !_require_business_admin_or_masterW   s   
rG   c                   C   sp   t dtjddtt dtjddt dtjdd	t d
tjddt dtj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appconfigr=   intr3   r3   r3   r4   get_sync_source_db_config^   s   ra   c                   C   sD   t jddt jddt jddt jddt jd	d
ddS )NrI   rJ   rL   rM   rO   r<   rQ   r:   rS   rT   rU   rV   )r^   r_   r=   r3   r3   r3   r4   get_primary_db_configi   s   rb   Fc                 C   s    | d u r|S t |   dv S )N)1trueyesyon)r-   r>   r?   )valuedefaultr3   r3   r4   _as_boolt   s   rj   c                 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 N)isdigit).0chr3   r3   r4   	<genexpr>{       z,_normalize_phone_variants.<locals>.<genexpr>
   i+91z+910c                 S      g | ]}|r|qS r3   r3   )rm   vr3   r3   r4   
<listcomp>       z-_normalize_phone_variants.<locals>.<listcomp>)joinr-   lenupdate)phonedigitscore10variantsr3   r3   r4   _normalize_phone_variantsz   s   r   c                 C   sN   | d u rg S t | tr| S t | tr%dD ]}| |}t |tr$|  S qg S )NListDatalistdata
isinstancer   dictr=   payloadkeyrh   r3   r3   r4   _extract_lsq_lead_list   s   



r      c           !      C   s  t dtt|pdd}|  d|pd d| }tt }t|}|r6|s6||dd tkr6|dS t| d	}|rM|d
rM|drM|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}t|}d }|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|d)d*d+t|d,d-d.t|d/d0d1d2 qg }| D ]}||d |d$ t|d& t|d% d3 q|jd4d5 dd6 g }| D ]#}t|d' }||d ||r|d nd |d$ t|d& d7 q|jd8d5 dd6 ddd9|||t|td:d; |D t|dd} || d<t|< | S )=Nrq   r   r)   :r:   tsr   r   leadsquared
access_key
secret_key	is_activeF2LeadSquared integration not configured or inactive)lsq_leads_fetchedmatched_customersmapped_rows)success	connectedr(   groupsagents	customersstatsapi_hostr   r   r   Paging)OffsetRowCountr   Tr(   z&Failed to fetch leads from LeadSquaredPhoneMobilePhoneNumber)r;   customer_numbers	groupnamer   -	agentnamecustomer_callinfo)r   
totalCallsmatchedCustomersr   )r   
groupnamesr   r   total_callsr   r   r   r   	last_call	FirstNameNameProspectName	OwnerNameOwner
owner_name
LeadStatusStatuslead_status)r   r   r   r   r   lsq_namelsq_owner_name
lsq_status)r   r   r   agentsCountc                 S      | d S Nr   r3   xr3   r3   r4   <lambda>      z8_get_presales_mapping_from_leadsquared.<locals>.<lambda>)r   reverse)r   r   r   r   r   c                 S   r   r   r3   r   r3   r3   r4   r   #  r   z2Pre-sales mapping generated from LeadSquared leadsc                 S   s   h | ]}|d  qS )r   r3   rm   rowr3   r3   r4   	<setcomp>.  rx   z9_get_presales_mapping_from_leadsquared.<locals>.<setcomp>)r   r   )maxminr`   time_PRESALES_MAP_CACHEr=   _PRESALES_MAP_CACHE_TTL_SECONDS
db_handlerget_crm_credentialsr   search_leadsr   set
_lsq_fieldr   addget_group_agent_customer_rowsr   appendvaluesrz   sortsorted)!r;   r   	row_countforce_refreshsafe_row_count	cache_keynowcachedcredsservice
lsq_searchleadsphone_to_leadall_phone_variantsleadr|   r   variantrowsgroup_indexagent_indexcustomer_rowsr   groupagentcustomer_phonematched_leadr   r   rh   r   groups_for_agentresponser3   r3   r4   &_get_presales_mapping_from_leadsquared   s  

"











r   z/healthr   )r!   c                   C   s   t dt  dddfS )zHealth check endpointhealthyzCall Analytics Dashboard API)status	timestampr      )r   r   r   	isoformatr3   r3   r3   r4   health_check=  s   
r   z/auth/registerr   c               
   C   s   t j} g d}|D ]}| |std| didf  S q	tj| d | d | d | d | d	| d
dd\}}t||fS )zRegister a new user)r;   usernameemailrY   r'    is required  r;   r   r   rY   	full_namer9   r@   )r;   r   r   rY   r   r9   )r   jsonr=   r   auth_handlercreate_user)r   required_fieldsfieldresultstatus_coder3   r3   r4   registerK  s   


	r  z/auth/loginc                  C   sf   t j} | dr| dstddidfS t j}t jd}tj| d | d ||d\}}t||fS )z
Login userr   rY   r'   z"Username and password are requiredr   
User-Agent)
ip_address
user_agent)r   r   r=   r   remote_addrheadersr   login)r   r  r  r   r  r3   r3   r4   r  b  s   
r  z/auth/logoutc                  C   sP   t jd} | r| dstddidfS | dd}t|\}}t||fS )zLogout userr   Bearer r'   Missing authorization header  r:   )r   r  r=   
startswithr   replacer   logout)auth_headertokenr   r  r3   r3   r4   r  y  s   r  z/auth/mec                  C   sd   t jd} | r| dstddidfS | dd}t|}|s*tddidfS td|id	fS )
zGet current logged-in user infor   r	  r'   r
  r  r:   zInvalid or expired sessionr@   r   )r   r  r=   r  r   r  r   validate_session)r  r  r@   r3   r3   r4   get_current_user  s   
r  z/auth/users/<bid>c                 C   s   t | \}}td|i|fS )z)Get all users for a business (admin only)users)r   get_users_by_businessr   )r;   r  r  r3   r3   r4   get_business_users  s   r  z/list-businessesc                  C   s   t  } t| dfS )z;Get list of all available businesses with their call countsr   )r   get_all_businessesr   )r8   r3   r3   r4   list_businesses  s   r  z/businesses/<bid>/infoc                 C   s*   t | }|stddidfS t|dfS )z0Get detailed information for a specific businessr'   zBusiness not found  r   )r   get_business_infor   )r;   infor3   r3   r4   r    s   
r  z/groupnames/<bid>c                 C   s   t tjddd}|r5tjjddtd}t tjddd}t| d||d	}|d
g }|s4t| }nt| }t|dfS )zOGet list of all groupnames for Jubilant Foods (bid 7987) with their call countspresales_onlyFri   lsq_row_countr   typer   Nr;   r   r   r   r   r   )	rj   r   r.   r=   r`   r   r   get_all_groupnamesr   )r;   r  r   r   mappingr   r3   r3   r4   get_groupnames  s    

r#  z/agentnames/<bid>c                 C   s   t jd}tt jddd}|rAt jjddtd}tt jddd}t| |||d	}d
d |dg D }|s@t| |}nt| |}t|dfS )z6Get list of agent names filtered by location/groupnamer   r  Fr  r  r   r  r   r   c                 S       g | ]}| d r| d qS )r   r=   )rm   r   r3   r3   r4   rw          z"get_agentnames.<locals>.<listcomp>r   r   )	r   r.   r=   rj   r`   r   r   get_agent_namesr   )r;   r   r  r   r   r"  r   r3   r3   r4   get_agentnames  s"   r(  z/location-stats/<bid>c           	   
   C   s|   t jd}t jd}t jd}t jd}t jd}t jdd}t jd}tj| |||||||d	}t|d
fS )z
    Get call statistics for a specific location (groupname) for Jubilant Foods
    Query params:
    - groupname: Filter by location/groupname (optional)
    r   	date_fromdate_toagent_namesdetailed_callsdetailed_threshold_secondsx   	direction)r+  r,  r-  r/  r   )r   r.   r=   r   get_location_statsr   )	r;   r   r)  r*  r+  r,  r-  r/  r   r3   r3   r4   r0    s$   
r0  z/location-calls/<bid>c           	   
   C   s   t jd}t jd}t jd}t jd}t jd}t jjddtd}t jjd	d
td}t| |||||||}t|dfS )a  
    Get filtered raw calls for Jubilant Foods
    Query params:
    - groupname: Filter by location/groupname (optional)
    - direction: Filter by direction (inbound/outbound) (optional)
    - call_status: Filter by call_status (ANSWER/BUSY/CANCEL/NOANSWER) (optional)
    - date_from: Start date (YYYY-MM-DD) (optional)
    - date_to: End date (YYYY-MM-DD) (optional)
    - limit: Number of records (default: 100)
    - offset: Offset for pagination (default: 0)
    r   r/  call_statusr)  r*  limitd   r  offsetr   r   )r   r.   r=   r`   r   get_filtered_raw_callsr   )	r;   r   r/  r1  r)  r*  r2  r4  callsr3   r3   r4   get_location_calls  s$   
r7  z/raw-calls/<bid>/<callid>c                 C   ,   t | |}|stddidfS t|dfS )zMGet call details from 7987_raw_calls with transcript from 7987_sarvamresponser'   Call not foundr  r   )r   get_raw_call_detailsr   )r;   callidcallr3   r3   r4   get_raw_call_details_route"     r=  z/analytics/<bid>/<callid>c                 C   r8  )z!Get analytics for a specific callr'   z!Analytics not found for this callr  r   )r   get_call_analyticsr   )r;   r;  	analyticsr3   r3   r4   r?  0  r>  r?  z/analytics/<bid>/pendingc                 C   s4   t jjddtd}t| |}tt||ddfS )zGet calls that need analyticsr2  rq   r  )countr6  r   )r   r.   r=   r`   r   get_calls_for_analysisr   rz   r;   r2  r6  r3   r3   r4   get_pending_analytics:  s   rD  z/analytics/<bid>/dashboardc              	   C   s   t jd}t jd}t jd}t| |||}t| |||}t| |||}t| |||}t| |||}t	| |||}	t
| |||}
t||||||	|
ddfS )z*Get comprehensive analytics dashboard datar   r)  r*  )overviewsentiment_by_locationquality_by_locationquality_by_agentcall_purposesconcerns_frequencybusy_locationsr   )r   r.   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_locationsr   )r;   r   r)  r*  rE  rF  rG  rH  rI  rJ  rK  r3   r3   r4   get_analytics_dashboardF  s*   rS  z#/analytics/<bid>/calls-by-objectionc                 C   sF   t jd}t jd}|stddidfS t| ||}t|dfS )z0Get calls filtered by specific objection/concern	objectionr   r'   zObjection parameter is requiredr   r   )r   r.   r=   r   r   get_calls_by_objection)r;   rT  r   r6  r3   r3   r4   rU  n  s   rU  z/calls/<bid>c           
      C   s   t jjdtd}t jd}t jd}t jd}t jjddtd}t jjdd	td}i }|d
ur6||d< |r<||d< |rB||d< |rH||d< t| |||}t| |}	t||	||ddfS )aY  
    Get all calls for a business with optional filtering
    Query params:
    - status: Filter by status (0-3)
    - sales_intent: Filter by intent (High/Medium/Low)
    - date_from: Start date (YYYY-MM-DD)
    - date_to: End date (YYYY-MM-DD)
    - limit: Number of records (default: 100)
    - offset: Offset for pagination (default: 0)
    r   r  sales_intentr)  r*  r2  r3  r4  r   N)r6  totalr2  r4  r   )r   r.   r=   r`   r   	get_callsget_calls_countr   )
r;   r   rV  r)  r*  r2  r4  filtersr6  total_countr3   r3   r4   rX    s2   rX  z/calls/<bid>/<callid>c                 C   s  t | |}|stddidfS t | |}|rZ|dd|d< |dd|d< |d}|rHt|trHzt|}W n tj	yG   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< t | |}|r|d|d< |d|d< t|dfS )z,Get detailed information for a specific callr'   r9  r  
transcriptr:   transcriptslanguagespeaker_segmentsnum_speakersdurationsummarycall_purposeobjections_concernsobjection_typequality_score	sentiment
sentimentsparameter_scorestalk_listen_ratioagent_speak_percentagecustomer_speak_percentagedead_air_percentageprofilebant_profilebant_summaryr   )r   get_call_by_idr   get_call_transcriptr=   r   r-   r   loadsJSONDecodeErrorr?  get_bant_analysis)r;   r;  r<  transcript_datar_  analytics_data	bant_datar3   r3   r4   get_call_details  sF   
ry  z/bant/<bid>/<callid>c                 C   r8  )z$Get BANT profile for a specific callr'   zBANT not foundr  r   )r   ru  r   )r;   r;  bantr3   r3   r4   get_bant_details  r>  r{  z /calls/<bid>/<callid>/transcriptr   c              
   C   s   zt | | t | | td|ddfW S  ty? } ztd| dt|  tdt|idfW  Y d}~S d}~ww )	z4Delete transcript and mark call for re-transcriptionzTranscript deleted successfully)r(   r;  r   zError deleting transcript for r%   r'   r)   N)r   delete_transcriptreset_transcription_statusr   r*   r+   r'   r-   )r;   r;  r0   r3   r3   r4   r|    s    r|  z1/calls/<bid>/<callid>/segment/<int:segment_index>r   c                 C   s   z*t  }|dd }|stddidfW S t| ||| td||ddfW S  tyV } z t	d	| d
| dt
|  tdt
|idfW  Y d}~S d}~ww )z-Update the text of a specific speaker segmenttextr:   r'   zText cannot be emptyr   zSegment updated successfully)r(   r;  segment_indexr   zError updating segment z for r%   r)   N)r   get_jsonr=   r>   r   r   update_speaker_segment_textr*   r+   r'   r-   )r;   r;  r  r   new_textr0   r3   r3   r4   update_segment_text  s$     r  z/calls/<bid>/recentc                 C   s*   t jjddtd}t| |}t|dfS )zGet recent calls for a businessr2  rq   r  r   )r   r.   r=   r`   r   get_recent_callsr   rC  r3   r3   r4   r    s   r  z/calls/searchc                  C   sX   t  } | d}| dd}| dd}|stddidfS t|||}t|d	fS )
z
    Search calls across all fields
    Body: {
        "bid": "6840",
        "query": "search term",
        "limit": 50
    }
    r;   queryr:   r2  2   r'   zBusiness ID is requiredr   r   )r   r  r=   r   r   search_calls)r   r;   r  r2  resultsr3   r3   r4   r    s   
r  z/leads/<bid>c                 C   s   t jd}t jjddtd}t jjddtd}t jdd d	k}tj| ||||d
}t|dg t|dd||ddfS )z8Get customer-level lead aggregates built from raw calls.r   r2  r3  r  r4  r   transcripts_onlyfalserd   )r;   r   r2  r4  r  r   rW  )r   rW  r2  r4  r   )r   r.   r=   r`   r?   r   get_leads_listr   )r;   r   r2  r4  r  r   r3   r3   r4   	get_leads,  s&   
r  c                 C   s2   t | pd }|sg S td|}dd |D S )Nr:   z[\n;,|]+c                 S   r$  )z -	r>   rm   itemr3   r3   r4   rw   I  r&  z(_split_insight_items.<locals>.<listcomp>)r-   r>   resplit)rh   r~  itemsr3   r3   r4   _split_insight_itemsD  s
   r  c                    s   | sdS t d| }|D ]7}| }|sqt dd|  |d ur' |v r'qt fdd|D rC|d ur;|  |d d   S qdS )NNot mentionedz(?<=[.!?])\s+|\n+\s+ c                 3   s    | ]}| v V  qd S rk   r3   )rm   keywordr   r3   r4   ro   W      z _find_snippet.<locals>.<genexpr>   )r  r  r>   subr?   anyr   )r~  keywordsused_sentences	sentencessentencecleanedr3   r  r4   _find_snippetL  s    
r  >   r:   nanoner   n/anot mentioned))budgetr  )	authorityr  )needsneed)timeliner  c                 C   s(   t t| pd ot|   tvS Nr:   )boolr-   r>   r?   _BANT_NOT_MENTIONEDr~  r3   r3   r4   _is_bant_mentionedg  s   (r  c                 C   s   t | tr1t| dpd }t| dpd }t|r%|d d S t|r/|d d S d S | d u r7d S t|  }t|rG|d d S d S )Nrh   r:   evidence@  )r   r   r-   r=   r>   r  )
field_datarh   r  r~  r3   r3   r4   _bant_field_displayk  s   
r  c                 C   s   |  d}t|tr|r|S |  d}t|tr/| r/zt|}W n ty.   d }Y nw t|trB| d}t|trB|rB|S d S )Nro  raw_responsecustomer_profiler=   r   r   r-   r>   r   rs  r*   )r<  rn  rawr  r3   r3   r4   _call_customer_profilez  s   



r  r:   c                 C   s   dd t D }| D ]$}t|}|sq	t D ]\}}|| dkrqt||}|r,|||< qq	t }g dg dg dg dd}	|	 D ]\}}
|| dkrOqDt||
|d	}|dkr^|||< qD|S )
Nc                 S   s   i | ]\}}|d qS )r  r3   )rm   out_key_r3   r3   r4   
<dictcomp>  s    z(_aggregate_lead_bant.<locals>.<dictcomp>r  )r  pricepricingcostpay	expensiveafford)zdecision makerdecisionapprovalapprovemanager
managementzsign off)requirementr  zlooking forzinterested inz
pain pointzuse case)r  	timeframedeadlinezgo live	implementquarterurgentasap)r  r  r  r  r  )_LEAD_BANT_FIELD_MAPr  r  r=   r   r  r  )r6  transcript_textr   r<  rn  r  profile_keydisplayusedfallback_keywordsr  snippetr3   r3   r4   _aggregate_lead_bant  s6   r  c                 C   s   g }t  }| D ]_}t|tsqt|dpd }t|dp!d }t|dp,d }|s5|s5qtdd| d|   }||v rJq|	| |r]|r]| | kr]d}|
|||d q|S )Ntitler:   say	rationaler  r  r  r  r  )r   r   r   r-   r=   r>   r  r  r?   r   r   )stepsoutseenstepr  r  r  
dedupe_keyr3   r3   r4   _dedupe_action_steps  s$   
 
r  >   none.no objectionno objectionsnone identifiedr  r  r  r  c                 C   s   t ddt| pd  S )Nr  r  r:   )r  r  r-   r>   r?   r  r3   r3   r4   _normalize_insight_text  s   r  c                 C   s*   t | }|r
|tv rdS |drdS dS )NFznone T)r  _OBJECTION_EMPTYr  )r~  
normalizedr3   r3   r4   _is_meaningful_objection  s   
r  c                 C   sd   |  d}t|tr|S t|tr0| r0zt|}t|tr#|W S d W S  ty/   Y d S w d S )Nr  r  )r<  r  parsedr3   r3   r4   _parse_call_raw_response  s   


r  c                 C   sP  |  d}t|tr!| r!zt|}W n ty    i }Y nw t|tr]| D ]2\}}dt|	 vr7q*t|tr\t| dpH| dpHd }|r\t
|tvr\|d d   S q*t| }|r| dpii }t|tr| D ]2\}}dt|	 vrqst|trt| dp| dpd }|rt
|tvr|d d   S qsdS )	Nparameter_detectionsrT  transcript_segment	reasoningr:   r  
parametersdetected_in_segment)r=   r   r-   r>   r   rs  r*   r   r  r?   r  r  r  )r<  
detectionsr   detailsegmentr  paramsr3   r3   r4   _objection_handling_from_call  sJ   




r  r   c                 C   s   t | pd } | sdS td| }|r|d  n| }t| |kr$| S t||kr7|t|dkr4d S d S |d |d   d S )Nr:   z(?<=[.!?])\s+r      u   …)r-   r>   r  r  rz   rstrip)rb  max_lenpartsfirstr3   r3   r4   _first_summary_sentence  s   r  c                 C   sr   t | pd }|sdS t|dkr|d d n|}zt|ddd d }|dW S  ty8   | Y S w )Nr:   zUnknown daterq   Zz+00:00z%d %b %Y)r-   r>   rz   r   fromisoformatr  strftimer*   )
start_timestart	date_partr  r3   r3   r4   _format_call_date_label  s   r  c                 C   s   g }t  }t  }| D ]}t|d}|r|| q
| D ]_}t|dp+|dp+d }t|}t|dp:d}t|dD ]8}	t	|	sJqCt|	}
|
|v sV|
|v rWqC||
 |	||dkd	}t|}|rv||
krv||vrv||d
< |
| qCq|S )Nrb  re  rc  r:   rf  r   rd  <   )rT  r  customer_satisfiedhow_handled)r   r  r=   r   r-   r>   r  floatr  r  r   )r6  
objectionsseen_objectionssummary_normsr<  summary_normre  r  quality_numrT  r   entryhandled_normr3   r3   r4   _build_lead_objections  s:   
 
r  c                 C   s  g }t  }t| d d D ]x}t|dpd pd}t|dp#d }t|dp.d  }t|dp;d }t|d	 d
| d| }	|rW|	d| 7 }	d}
|rgt|}||vrgt|}
|
rp|	 d|
 n|	}t|}||v r{q|	| |
| q|S )N   r   r   rc  r:   r/  r<  rb  call_starttime    —  call with u    · r%   )r   reversedr-   r=   r>   r  r  r  r  r   r   )r6  objection_textslines
seen_linesr<  r   purposer/  rb  headerbodyr
  lineline_keyr3   r3   r4   $_build_previous_conversation_summary@  s,   
r  c                 C   s  |  dpg }g }|D ],}t| dpd }|s0| dp g }t|tr0ddd |D }|r7|| qd|}dd	 |D }t|}d
d |D }	t||	}
|
se|redd	 t	|d d D }
|  d}ddddddddg}|r|dkr|dd| ddd t
 }t  d d|
d d pd|t||t|g d|d t|g d!|d t|  d"pd#d$krd%nd&d'd(d |d d) D pd*d+|
t|d,d-S ).Nr6  r\  r:   r_  
c                 s   sZ    | ](}t |trt|d p|dpd rt|d p%|dp%d V  qdS )r~  r\  r:   N)r   r   r-   r=   r>   )rm   segr3   r3   r4   ro   e  s    
z'_build_lead_insights.<locals>.<genexpr>c                 S   s8   g | ]}t |d pd rt |d pd qS )rb  r:   r-   r=   r>   rm   r<  r3   r3   r4   rw   m  s   8 z(_build_lead_insights.<locals>.<listcomp>c                 S   s   h | ]}t |d  qS )rT  )r  r  r3   r3   r4   r   p      z'_build_lead_insights.<locals>.<setcomp>c              	   S   sJ   g | ]!}t |d  dt|dpd   d|dp d qS )r  r  r/  Callr  r   r   )r  r=   r-   r>   r  r   r3   r3   r4   rw   s  s    r  next_task_due_datezFollow up with contextzZI reviewed our earlier conversation and wanted to continue from the points you had raised.zYReferencing prior conversations makes the follow-up feel specific and reduces repetition.r  zResolve open concernsziBefore we move ahead, is there any concern about pricing, fit, timing, or approval that I should clarify?zWThe fastest path to conversion is to surface and handle the remaining blocker directly.r   zUse the scheduled next taskz%I have the next follow-up marked for z. Does that still work for you?z=Aligning with the CRM task keeps the sales motion consistent.r  r     z5Insights generated from available lead conversations.)
competitorzother optionalternativer  )productr   planpackagepropertyprojectavg_quality_scorer   r  PositivezNeeds follow-upz; c                 s   s    | ]}|d  V  qdS )rT  Nr3   r  r3   r3   r4   ro     r     r  )competitors_mentionedproducts_discussedcustomer_sentiment_trendkey_concerns)previous_summaryaction_steps)generated_atrb  r  rz  data_capturepath_to_conversion)r=   r-   r>   r   r   ry   r   r  r  r  r   r   utcnowr   r  r  r  r  )detailsr6  transcript_partsr<  r~  segmentsr  	summariesr  r  r3  next_duer4  data_capture_usedr3   r3   r4   _build_lead_insights]  sx   





	
r?  z'/leads/<bid>/<path:lead_phone>/insightsc                 C   s>   t |}tj| |d}|stddidfS tdt|idfS )Nr;   
lead_phoner'   Lead not foundr  insightsr   )r
   r   get_lead_detailsr   r?  )r;   rA  decoded_phoner9  r3   r3   r4   get_lead_insights  s
   rF  z0/leads/<bid>/<path:lead_phone>/insights/generatec                 C   sh   t |}tj| |d}|stddidfS dd |dg D }|s*tddid	fS td
t|idfS )Nr@  r'   rB  r  c                 S   s   g | ]	}| d r|qS )r\  r%  r   r3   r3   r4   rw     s    z*generate_lead_insights.<locals>.<listcomp>r6  z#No transcripts found for this lead.r   rC  r   )r
   r   rD  r   r=   r?  )r;   rA  rE  r9  calls_with_transcriptsr3   r3   r4   generate_lead_insights  s   rH  z/leads/<bid>/<path:lead_phone>c              
   C   s  t |}tjd}tjdd dk}tj| ||d}|s'tddidfS ddd	d
d}|rt| d	}|r|dr|dr|drd|d< t	| d	|}|r|dr|d }	d|d< d|d< |	|dprt
|	dddd|dp|t
|	dd|dpt
|	ddd|d pt
|	d!d"d |d#pt
|	d$d%d#|d&pt
|	d'd&d(|d)< nRt|d |d |d*d+}
|
|}|d,rt|d-}	|	rd|d< d.|d< |	t
|	ddddt
|	ddt
|	dddt
|	d!d"d t
|	d$d%d#t
|	d'd&d(|d)< ||d/< t|d0fS )1zFGet lead detail timeline and optional CRM enrichment for one customer.r   include_crmrd   )r;   rA  r   r'   rB  r  Fr   N)r   matchedproviderr   r   r   r   Tr   lead_payloadrJ  cachesource	lead_namer   
first_namer   r   r   EmailAddressphone_primaryr   r   r   r   r   r   r   r   r   r#  NextTaskDueDate)r  namer   r|   r   r   r#  r   r   r   r   r   livecrmr   )r
   r   r.   r=   r?   r   rD  r   r   get_cached_crm_lead_by_phoner   r   search_lead_by_phone_extract_lsq_lead_record)r;   rA  rE  r   rI  r9  rV  r   r   
lsq_recordr   search_resultr3   r3   r4   get_lead_detail  sj   "





r\  z/analytics/<bid>/statsc                 C   2   t jd}t jd}t| ||}t|dfS )z
    Get overall statistics for a business
    Query params:
    - date_from: Start date (YYYY-MM-DD)
    - date_to: End date (YYYY-MM-DD)
    r)  r*  r   )r   r.   r=   analytics_serviceget_call_statisticsr   )r;   r)  r*  r   r3   r3   r4   get_call_stats  s   	r`  z/analytics/<bid>/sentimentc                 C   r]  )z)Get sentiment distribution for a businessr)  r*  r   )r   r.   r=   r^  get_sentiment_distributionr   )r;   r)  r*  rg  r3   r3   r4   get_sentiment_analysis     rb  z/analytics/<bid>/intentc                 C   r]  )z,Get sales intent distribution for a businessr)  r*  r   )r   r.   r=   r^  get_intent_distributionr   )r;   r)  r*  intentr3   r3   r4   rd  '  rc  rd  z/analytics/<bid>/trendsc                 C   s:   t jdd}t jjddtd}t| ||}t|dfS )z
    Get trends over time
    Query params:
    - period: day, week, month (default: day)
    - days: Number of days to look back (default: 7)
    perioddaydays   r  r   )r   r.   r=   r`   r^  
get_trendsr   )r;   rf  rh  trendsr3   r3   r4   rj  2  s   	rj  z/analytics/<bid>/agentsc                 C   r]  )z&Get performance metrics for all agentsr)  r*  r   )r   r.   r=   r^  get_agent_performancer   )r;   r)  r*  performancer3   r3   r4   rl  B  rc  rl  z/analytics/<bid>/keywordsc                 C   sF   t jjddtd}t jd}t jd}t| |||}t|dfS )z)Get most common keywords across all callsr2     r  r)  r*  r   )r   r.   r=   r`   r^  get_top_keywordsr   )r;   r2  r)  r*  r  r3   r3   r4   ro  M  s
   ro  z/queue-calls/<bid>c                 C   s8   t j| ddidd}tdt| dt|| ddfS )	z&Queue unprocessed calls for processingr   r     r2  zQueued z calls for processing)r(   rA  business_idr   )r   rX  r   rz   )r;   r6  r3   r3   r4   queue_calls]  s   rs  z/process-calls/<bid>c                 C   s   t d| ddfS )z%Directly process calls (bypass queue)zProcessing started)r(   rr     r   r;   r3   r3   r4   process_callsn  s   rw  z/transcripts/<bid>c                 C      t | }t|dfS )z"Get all transcripts for a businessr   )r   get_transcriptsr   )r;   r]  r3   r3   r4   ry       
ry  z/export/<bid>/callsc              	   C   s   t jdd}t jjdtdt jdt jdd}dd	 | D }tj| |d
d}|dkriddl}ddlm	} | }|rS|j
||d  d}|  || | ddd|  dt d ddfS t|dfS )zExport calls data as JSONr   r   r   r  r)  r*  )r   r)  r*  c                 S   s   i | ]\}}|d ur||qS rk   r3   )rm   krv   r3   r3   r4   r    s    z export_calls.<locals>.<dictcomp>i'  rq  csvr   NStringIO)
fieldnamesr   text/csvzattachment; filename=calls_r  %Y%m%d.csvr   zContent-Disposition)r   r.   r=   r`   r  r   rX  r|  ior~  
DictWriterkeyswriteheader	writerowsgetvaluer   r   r  r   )r;   format_typerZ  r6  r|  r~  outputwriterr3   r3   r4   export_calls  s(   


r  c                 C   s   dd t | pddD S )Nc                 S   s   g | ]
}|  r|  qS r3   r  r  r3   r3   r4   rw     s    z%_split_csv_filter.<locals>.<listcomp>r:   ,)r-   r  rh   r3   r3   r4   _split_csv_filter     r  c                 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) <= %s, %sz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:   re   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 z AND )r   r  ry   rz   extendr-   r>   r?   )r   r)  r*  r+  customer_namer,  quality_minquality_maxr/  r1  include_quality_filterswhere_clausesr  r   placeholdersdetailedr3   r3   r4   _export_filters  sL   
















r  customer_numberzCUSTOMER NUMBERr  zCUSTOMER NAMEr;  zCALL IDr   z
AGENT NAMEr   z
GROUP NAMEr  zCALL START TIMEcall_endtimezCALL END TIMEr/  	DIRECTIONr1  zCALL STATUSduration_secondszDURATION SECONDStranscription_statuszTRANSCRIPTION STATUSr^  LANGUAGEr`  zNUM SPEAKERStranscript_durationzTRANSCRIPT DURATIONr\  
TRANSCRIPTrf  zQUALITY SCORErg  	SENTIMENTzCALL PURPOSEzOBJECTIONS CONCERNSzOBJECTION TYPEzTALK LISTEN RATIOzAGENT SPEAK PERCENTAGEzCUSTOMER SPEAK PERCENTAGEzDEAD AIR PERCENTAGESUMMARYzPARAMETER SCORES)	rc  rd  re  rj  rk  rl  rm  rb  ri  )r  r  r;  DURATIONc              	   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  02dr   )r`   roundr  	TypeError
ValueError)secondsrW  hoursminutessecsr3   r3   r4   _format_seconds_as_hhmmss  s   r  c                 C   s   dt |  dt | dS )Nz="r   ")r`   )leftrightr3   r3   r4   _export_ratio_mm_ss  s   r  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  )r-   r>   r`   r  r  r  r  r  r3   r3   r4   _parse_speak_percentage  s   r  c              	   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 )Ng        r   r3  )r   r  r  r  r`   r  )agent_talk_timecustomer_talk_timeagent_secondscustomer_secondsrW  r3   r3   r4   _ratio_from_talk_times!  s   (r  c                 C   s  dd l }ddlm}m} | d u rd S t| |r>tdt|  }t|d\}}t|d\}}|dkr6||fS |d | |fS t| |rY| j	dkrN| j
| jfS | j	d | j
 | jfS t| ttfrt| tst| }	d|	  k rsd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 |dd	|}d
d |d	D }t|dk rd S 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rKtdd |D rK||d ||d fS d S )Nr   )r   r   r  r  r  iQ zN/ANAz\s*:\s*r   c                 S   s   g | ]}|d kr|qS r:   r3   rm   pr3   r3   r4   rw   N  r!  z2_parse_talk_listen_ratio_parts.<locals>.<listcomp>r$  c                 S   s   t tt| S rk   )r`   r  r  )partr3   r3   r4   _to_intR  s   z/_parse_talk_listen_ratio_parts.<locals>._to_int   )rt   00z0.0z0.00.r:   )rt   r  c                 s   s"    | ]}| d dd V  qdS )r  r:   r  N)r  rl   r  r3   r3   r4   ro   ]  s     z1_parse_talk_listen_ratio_parts.<locals>.<genexpr>)r  r   r   r   r   r   r`   total_secondsdivmodhourminutesecondr  r  r  r-   r>   upperr  r  rz   r  rl   all)rh   r  r   r   rW  r  remr  r  numericr  r~  r  r  r  thirdr3   r3   r4   _parse_talk_listen_ratio_parts-  sX   


*>"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 )Nrk  rl  r   r  r  r  rj  r:   r  r  )r  r=   r  r  r  r-   r>   r  )r   	agent_pctcustomer_pctfrom_talk_timesr  r  r3   r3   r4   !_quality_export_talk_listen_ratiob  s    
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_rowr3   r3   r4   "_prepare_transcription_export_rowsv  s   r  c                 C   s&   | d u rdS t | tr|  dkS dS )NFr:   T)r   r-   r>   r  r3   r3   r4   _parameter_score_has_value  s
   
r  c              	   C   s   | sdS t | trzt| }W n ttfy   |  Y S w | }t |ts1t | tr,| S t| S i }| D ]\}}t |trLt	|
drK|||< q7t	|rT|||< q7|r^tj|ddS dS )Nr:   scoreFensure_ascii)r   r-   r   rs  r  r  r   dumpsr  r  r=   )ri  scoresfilteredrT  r   r3   r3   r4   #_filter_parameter_scores_for_export  s*   


r  c                 C   sh   g }| pg D ]+}t |}d|v rt|d|d< t||d< d|v r,t|d|d< || q|S )Nr  rj  ri  )r   r  r=   r  r  r   r  r3   r3   r4   _prepare_quality_export_rows  s   r  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:   r  r   r3   r3   r4   rw     s    z/_add_customer_export_fields.<locals>.<listcomp>c                 S   ru   r3   r3   )rm   r|   r3   r3   r4   rw     rx   r   z2Customer name enrichment skipped for export %s: %sr   r  r:   r  rO  )r   get_crm_enrichment_for_phonesr*   r+   warningr   r-   popr=   r>   r   )
r;   r   phonescrm_by_phoneexcenrichedr   r  r  crm_infor3   r3   r4   _add_customer_export_fields  s.   "r  c                    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 rk   )EXPORT_FIRST_COLUMNSrm   r   r3   r3   r4   ro     rp   z*_format_export_csv_rows.<locals>.<genexpr>c              	      s2   i | ]}  |t|d d  |dqS )r  r  r:   )r=   r-   r  r  r  labelsr   r3   r4   r    s    $z+_format_export_csv_rows.<locals>.<dictcomp>)EXPORT_HEADER_LABELSr   r  r  r  r   )r   header_labels	formattedordered_keysr3   r  r4   _format_export_csv_rows  s   
r  c                 C   sr   dd l }ddlm} t| |d} | }| r,|j|t| d  dd}|  ||  |	 ddd| d	fS )
Nr   r}  r  ignore)r  extrasactionr   r  zattachment; filename=r  )
r|  r  r~  r  r  r   r  r  r  r  )r   filenamer  r|  r~  r  r  r3   r3   r4   _csv_download  s   
r  z/export/<bid>/transcriptionsc                 C   s  |  d}|  d}|  d}t jd}t jd}t jd}t jd}t jd}t jd	}	t jjd
td}
t jjdtd}t jd}t jd}t }| }t||sag }nt||}|
d upn|d u}t||}|r|stg d|  dt	
 d dW  d    S t||||||	|
||||d\}}t||}|rd| 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 )$N
_raw_calls_sarvamresponse_callanalyticsr   r)  r*  r+  r  r,  r  r  r  r/  r1  transcriptions_r  r  r  r+  r  r,  r  r  r/  r1  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,
                    r  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
                r  )r   r.   r=   r  r   get_connectioncursor_table_existsr  r   r   r  r  sarvam_export_select_exprsexecute%prepare_transcription_csv_export_rowsfetchallr  r  "TRANSCRIPTION_EXPORT_HEADER_LABELS)r;   	raw_tablesarvam_tableanalytics_tabler   r)  r*  r+  r  r,  r  r  r/  r1  connr  r   
has_sarvamneeds_quality_joinhas_analytics	where_sqlr  sarvam_colsquality_join_sqlr  r3   r3   r4   export_transcriptions  s~   



 


5r  z/export/<bid>/qualityc                 C   sl  |  d}|  d}t jd}t jd}t jd}t jd}t jd}t jd}t jjd	td
}	t jjdtd
}
t jd}t jd}t K}| }t||r_t||sbg }n0t|||||||	|
||dd\}}|	d| d| d| d| |
 pg }t| |}t|}W d    n1 sw   Y  d|  dt d d}t||tdS )Nr  r  r   r)  r*  r+  r  r,  r  r  r  r/  r1  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  r  )r   r.   r=   r  r   r  r  r  r  r  r  r  r  r   r   r  r  QUALITY_EXPORT_HEADER_LABELS)r;   r
  r  r   r)  r*  r+  r  r,  r  r  r/  r1  r  r  r   r  r  r  r3   r3   r4   export_quality/  s\   



!
6r  z/webhook/call-updatec               
   C   s   t  } | d}| d}|r|stddidfS t||| di }|rWztj|ddd	|gd
 W n tyN } zt	
d||| W Y d}~nd}~ww tddidfS tddidfS )z
    Receive call updates from processing system
    Body: {
        "bid": "6840",
        "callid": "CALL-001",
        "status": 2,
        "data": {...}
    }
    r;   r;  r'   zbid and callid are requiredr   r   Fr  Tr;   r  r2  overwrite_existingcallidsz%RAG auto-ingest skipped for %s/%s: %sNr(   zCall updated successfullyr   zFailed to update callr)   )r   r  r=   r   r   update_callrag_handlerbackfill_transcriptsr*   r+   r  )r   r;   r;  r   rag_excr3   r3   r4   receive_call_update}  s*   


r  z/webhook/conversation-summaryc                  C   sp   t  } | d}| d}| d}|r|stddidfS t|||}|r0tddid	fS tdd
idfS )z
    Receive conversation summary
    Body: {
        "business_id": "6840",
        "callid": "CALL-001",
        "transfer_reason": {...}
    }
    rr  r;  transfer_reasonr'   z#business_id and callid are requiredr   r(   zSummary saved successfullyr   zFailed to save summaryr)   )r   r  r=   r   r   save_conversation_summary)r   r;   r;  r   r   r3   r3   r4   receive_conversation_summary  s   


r"  CALL_SYNC_CACHE_TTL_SECONDS3600v5c               
   C   sr   t dptjdpd} ztj| dddd}|  |W S  ty8 } zt	
dt| W Y d }~d S d }~ww )N	REDIS_URLRAG_REDIS_URLzredis://127.0.0.1:6379/0Tr  )decode_responsessocket_timeoutsocket_connect_timeoutzCall sync Redis unavailable: %s)r\   r]   r^   r_   r=   redisfrom_urlpingr*   r+   r  r-   )	redis_urlclientr0   r3   r3   r4   _get_call_sync_redis  s   r0  c                 C   s   t jd}|r|dsd tddidffS |dd}t|}|s.d tddidffS t|| s=d tddid	ffS |d fS )
Nr   r	  r'   r
  r  r:   Invalid or expired tokenrD   rE   )	r   r  r=   r  r   r  r   validate_tokenrB   )r;   r  r  r@   r3   r3   r4   _sync_auth_user  s   

r3  c                 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_archivezSHOW TABLES LIKE %sr  fetchone)r  r;   
table_kind
candidates	candidater3   r3   r4   _resolve_source_call_table  s   r>  c                 C   s"   |  d| d|f |  d uS )NzSHOW COLUMNS FROM `z	` LIKE %sr9  )r  
table_namecolumn_namer3   r3   r4   _table_has_column  s   rA  c                 C   s:   i }|   D ]\}}t|tr| ||< q|||< q|S rk   )r  r   r   r   )r   
serializedr   rh   r3   r3   r4   _serialize_call_sync_row  s   

rC  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 )Nr/  r   callfromcalltoclicktocalldidz1NULLIF(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)), '')z	COALESCE(r  r  NULLa  
            SELECT
                callid,
                %s as bid,
                agentname,
                groupname,
                starttime,
                endtime,
                dialstatus,
                direction,
                filename,
                z as customer_callinfo,
                countrycode,
                emp_phone,
                clicktocalldid
            FROM `"`
            WHERE 1 = 1
        & AND DATE(starttime) BETWEEN %s AND %sz! ORDER BY starttime DESC LIMIT %sc                 S   s   g | ]}t |qS r3   )rC  r   r3   r3   r4   rw   B  rx   z'_fetch_source_calls.<locals>.<listcomp>r3   )ra   pymysqlconnectr  cursors
DictCursorr>  closerA  r   ry   r-   r  r`   r  tupler  )r;   r;  r)  r*  r2  source_configr  r  r?  has_directionhas_customer_callinfohas_callfrom
has_calltohas_clicktocallcustomer_candidatescustomer_exprr  r  r   r3   r3   r4   _fetch_source_calls  sP   
<
	


rX  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   z8
            SELECT COUNT(*) as total
            FROM `rH  rI  rW  r3   )ra   rJ  rK  r  rL  rM  r>  rN  r  r  rO  r:  r`   r=   )r;   r;  r)  r*  rP  r  r  r?  r  r  r   r3   r3   r4   _fetch_source_countG  s&   rY  z/sync/cache/<bid>/historyc                 C   s2  t | \}}|r
|S tjjddtd}tjdddv }dt d|  d	| }t }|rD|sD||}|rDt|}d
|d< t	|dfS t
| d|d\}	}
|
svt	dd dg t  dd|  d|  dgt dt ddd	dfS d|
t|	|	t  dd}|r||ttj|d
d t	|dfS )Nr2  r$   r  refreshrt   rc   rd   True	callsync:r   :history:limit:T	cache_hitr   r4  rq  call_historyr   Fz5Source call history table not found for this businessr6  r5  rW   rZ   rW   rZ   )	rN  tablerA  records	cached_atr_  r(   expected_tablesconfigured_source_db)rN  rb  rA  rc  rd  r_  r  r3  r   r.   r=   r`   CALL_SYNC_CACHE_SCHEMA_VERSIONr0  r   rs  r   rX  r   r   r   ra   rz   setexr#  r  )r;   r  
auth_errorr2  rZ  r   redis_clientr   r   rc  r?  r3   r3   r4   get_cached_history_calls_  sP   





rl  z/sync/cache/<bid>/archivec                 C   sv  t | \}}|r
|S tjd}tjd}tjjddtd}tjdddv }|r3|r3| d	| nd
}dt d	|  d| d| }t }	|	r`|s`|	|}
|
r`t|
}d|d< t	|dfS t
| d|||d\}}|st	dd dg ||t  dd|  d|  dgt dt ddddfS d|t||||t  dd}|	r|	|ttj|dd t	|dfS )Nr)  r*  r2  r)   r  rZ  rt   r[  r   recentr]  	:archive::limit:Tr_  r   archiver)  r*  r2  call_archiver   Fz5Source call archive table not found for this businessr7  r8  rW   rZ   ra  )rN  rb  rA  rc  r)  r*  rd  r_  r(   re  rf  )rN  rb  rA  rc  r)  r*  rd  r_  r  rg  )r;   r  rj  r)  r*  r2  rZ  	range_keyr   rk  r   r   rc  r?  r3   r3   r4   get_cached_archive_calls  sj   







rt  z/sync/check-count/<bid>c              
   C   s   t | \}}|r
|S tjd}tjd}z(t| d}|r(|r(t| d||dnd}td|id|it|| ||dd	fW S  tyg } zt	d
t
|  tddt
| idfW  Y d }~S d }~ww )Nr)  r*  r4  rp  )r)  r*  r   rW  )callhistorycallarchiver[  r)  r*  r   zError checking call count: r'   zFailed to check call count: r)   )r3  r   r.   r=   rY  r   r`   r*   r+   r'   r-   )r;   r  rj  r)  r*  history_totalarchive_totalr0   r3   r3   r4   check_call_count  s,   

&ry  z/sync/calls/<bid>c              
   C   s  t | \}}|r
|S t pi }|d}|d}zt }t|dd}|rI|rIt| d|||d\}}	d}
dt d	|  d
| d	| d| 
}nt| d|d\}}	d}
dt d	|  d| }|
|	t||||t	
  d}|	std|
 d|
d |dddfW S |r||ttj|dd tdt| d|
 t||
|	|dddfW S  ty } ztdt|  tddt| idfW  Y d }~S d }~ww )Nr)  r*  r2  r)   rp  rq  rr  r]  r   rn  ro  r4  rq  r`  r^  )rN  rb  rA  rc  r)  r*  rd  zSource table not found for r   r+  )r(   cached_countrN  rb  r   	stored_inr   Tr  zSuccessfully cached z calls from zError caching calls: r'   zFailed to cache calls: )r3  r   r  r=   r0  r`   rX  rh  rz   r   r   r   r   ri  r#  r   r  r*   r+   r'   r-   )r;   r  rj  r   r)  r*  rk  r2  rc  r?  rN  r   r   r0   r3   r3   r4   
sync_calls  s|   


$

		&r|  z/sync/upload/<bid>c                 C   s   t jd}|r|dstddidfS |dd}t|}|s*tddidfS t|| s7tddid	fS t j	d
}|sGtddidfS t
| |}t|dfS )z=Upload raw calls Excel file into {bid}_raw_calls (admin only)r   r	  r'   r
  r  r:   r1  rD   rE   filezExcel file is requiredr   r   )r   r  r=   r  r   r  r   r2  rB   filesr   import_raw_calls_from_excel)r;   r  r  r@   upload_filer   r3   r3   r4   upload_raw_calls+	  s   

r  z/transcription/calls/<bid>c              
   C   s   t jdd}t }zDtjdi |}|tjj}|dkr*d|  d}|	| nd|  d}|	||f |
 }|  |  t|t|ddfW S  tyw } ztd	t|  td
dt| idfW  Y d}~S d}~ww )z
    Get calls from raw_calls table filtered by status for transcription management
    Query params:
    - status: Filter by status (0, 1, 2, or 'all')
    r   rt   r  z
                SELECT callid, bid, agentname, groupname, call_starttime, call_endtime,
                       call_status, status, transcription_status, transcription_requested,
                       selected_for_processing
                FROM z_raw_calls
                WHERE call_status = 'ANSWER'
                ORDER BY call_starttime DESC
                LIMIT 500
            z_raw_calls
                WHERE call_status = 'ANSWER'
                AND status = %s
                ORDER BY call_starttime DESC
                LIMIT 500
            )r6  rA  r   z#Error getting transcription calls: r'   zFailed to get calls: r)   Nr3   )r   r.   r=   rb   rJ  rK  r  rL  rM  r  r  rN  r   rz   r*   r+   r'   r-   )r;   r   dest_configr  r  r  r6  r0   r3   r3   r4   get_transcription_callsD	  s8   	
&r  z/transcription/trigger/<bid>c           
   
   C   s  t  }|dg }|stddidfS t }zBtjdi |}| }ddgt	| }d|  d| d	}|
|| |  |j}|  |  td
| d|ddfW S  ty }	 ztdt|	  tddt|	 idfW  Y d}	~	S d}	~	ww )zo
    Trigger transcription for selected calls
    Body: {
        "callids": ["call1", "call2", ...]
    }
    r  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)r(   queued_countr   z Error triggering transcription: z!Failed to trigger transcription: r)   Nr3   )r   r  r=   r   rb   rJ  rK  r  ry   rz   r  commitrowcountrN  r*   r+   r'   r-   )
r;   r   r  r  r  r  r  r  affected_rowsr0   r3   r3   r4   trigger_batch_transcription{	  s<   	
&r  z/analysis/trigger/<bid>c                 C   sD  t  }|dg }|stddidfS zddlm} |tj}d}d}g }|D ]}zt	| |}	|	sA|
| d |d7 }W q(|	d	d
krV|
| d |d7 }W q(|	d}
|
sk|
| d |d7 }W q(|	d}|r|t|tr|t|}|	dp|	d}td| d|   |j| ||
|pg |d}|d7 }td|  W q( ty } z#td| dt|  |
| dt|  |d7 }W Y d}~q(d}~ww d| d| d||d}|r|dd |d< t|dfW S  ty! } ztdt|  tdd t| id!fW  Y d}~S d}~ww )"z
    Trigger AI analysis for selected answered calls with transcripts
    Body: {
        "callids": ["call1", "call2", ...]
    }
    r  r'   r  r   r   )CallAnalyzerz: Call not foundr  r1  ANSWERz: Call not answeredr]  z: No transcript availabler_  ra  r  zAnalyzing call z	 for BID )r;   r;  r\  r_  actual_durationzSuccessfully analyzed call zError analyzing call r%   NzAnalysis completed: z successful, z failed)r(   success_counterror_countrq   errorsr   zError in batch analysis: zFailed to trigger analysis: r)   )r   r  r=   r   analyze_calls_with_parametersr  r^   r_   r   r:  r   r   r-   r   rs  r+   r  analyze_callr*   r'   )r;   r   r  r  analyzerr  r  r  r;  	call_datar\  r_  r  analysis_resultr0   response_datar3   r3   r4   trigger_batch_analysis	  sv   	



&r  z/quality-parameters/<bid>c                 C   rx  )z)Get all quality parameters for a businessr   )quality_params_handlerget_parametersr   )r;   r  r3   r3   r4   get_quality_parameters
  rz  r  c                 C   s:   t  }|stddidfS t| |}td|ddfS )z"Save or update a quality parameterr'   No data providedr   zParameter saved successfully)r(   parameter_idr   )r   r  r   r  save_parameter)r;   r   r  r3   r3   r4   save_quality_parameter
  s   r  z /quality-parameters/<bid>/uploadc              
   C   s   t jd}|r|jstddidfS |j}| ds%tddidfS zt| ||	 }W n t
yL } ztdt|idfW  Y d}~S d}~ww tdd	i|d
fS )z1Upload quality parameters from a CSV or XLSX filer}  r'   zNo file uploadedr   )r  z.xlsxz%Only CSV and XLSX files are supportedNr(   z(Quality parameters uploaded successfullyr   )r   r~  r=   r  r   r?   endswithr  import_parameters_filereadr  r-   )r;   r  r  r   r  r3   r3   r4   upload_quality_parameters 
  s&   
 r  z(/quality-parameters/<bid>/<int:param_id>c                 C   r8  )z Get a specific quality parameterr'   zParameter not foundr  r   )r  get_parameter_by_idr   )r;   param_id	parameterr3   r3   r4   get_quality_parameter7
     r  c                 C   0   t | |}|stddidfS tddidfS )zDelete a quality parameterr'   z+Parameter not found or could not be deletedr  r(   zParameter deleted successfullyr   )r  delete_parameterr   )r;   r  r   r3   r3   r4   delete_quality_parameterC
  s   r  z /quality-parameters/<bid>/groupsc                 C   rx  )z/Get list of all parameter groups for a businessr   )r  get_parameter_groupsr   )r;   r   r3   r3   r4   r  O
  rz  r  z%/quality-parameters/<bid>/total-scorec                 C   s   t | }td|idfS )z+Get total possible score for all parameterstotal_scorer   )r  calculate_total_possible_scorer   )r;   r  r3   r3   r4   get_total_possible_scoreW
  s   
r  z /objection-classifications/<bid>c                 C   sF   t jd}t jd}|dur| dk}t| ||}t|dfS )z
    Get all objection classifications for a business
    Query params:
    - business_type: Filter by business type (optional)
    - is_active: Filter by active status (optional)
    business_typer   Nrd   r   )r   r.   r=   r?   objection_handlerget_all_classificationsr   )r;   r  r   classificationsr3   r3   r4   get_objection_classificationsc
  s   	r  z8/objection-classifications/<bid>/<int:classification_id>c                 C   r8  )z-Get a specific objection classification by IDr'   Classification not foundr  r   )r  get_classification_by_idr   )r;   classification_idclassificationr3   r3   r4   "get_objection_classification_by_idv
  r  r  c                 C   sb   t  }|stddidfS |dstddidfS |dd}t| ||}t|dd	d
fS )z%Create a new objection classificationr'   r  r   category_namezcategory_name is required
created_byr<   z#Classification created successfully)idr(      )r   r  r   r=   r  create_classification)r;   r   r  r  r3   r3   r4   create_objection_classification
  s   
r  r   c                 C   s\   t  }|stddidfS |dd}t| |||}|s&tddidfS tdd	id
fS )z+Update an existing objection classificationr'   r  r   
updated_byr<   z+Classification not found or no changes mader  r(   z#Classification updated successfullyr   )r   r  r   r=   r  update_classification)r;   r  r   r  r   r3   r3   r4   update_objection_classification
  s   r  c                 C   r  )z"Delete an objection classificationr'   r  r  r(   z#Classification deleted successfullyr   )r  delete_classificationr   r;   r  r   r3   r3   r4   delete_objection_classification
     r  z?/objection-classifications/<bid>/<int:classification_id>/togglec                 C   r  )z,Toggle the active status of a classificationr'   r  r  r(   z*Classification status toggled successfullyr   )r  toggle_active_statusr   r  r3   r3   r4   toggle_objection_classification
  r  r  z'/objection-classifications/<bid>/searchc                 C   s8   t jd}|stddidfS t| |}t|dfS )z\
    Search objection classifications
    Query params:
    - q: Search term (required)
    qr'   zSearch term (q) is requiredr   r   )r   r.   r=   r   r  search_classifications)r;   search_termr  r3   r3   r4    search_objection_classifications
  s
   r  z7/objection-classifications/<bid>/by-severity/<severity>c                 C   s0   |dvrt ddidfS t| |}t |dfS )z4Get all classifications of a specific severity level)lowmediumhighcriticalr'   zInvalid severity levelr   r   )r   r  get_classifications_by_severity)r;   severityr  r3   r3   r4   r  
  s   r  z)/objection-classifications/<bid>/classifyc                 C   sB   t  }|r|dstddidfS t| |d }t|dfS )z~
    Auto-classify an objection text based on keywords
    Body: {
        "objection_text": "The text to classify"
    }
    objection_textr'   zobjection_text is requiredr   r   )r   r  r=   r   r  classify_objectionr;   r   r   r3   r3   r4   classify_objection_text
  s
   	r  z+/objection-classifications/<bid>/statisticsc                 C   rx  )z=Get statistics about objection classifications for a businessr   )r  get_statisticsr   )r;   r   r3   r3   r4   get_objection_statistics
  rz  r  z/rag/<bid>/documentsc                 C   sN   t jddpi }|dg }|stddidfS t| |}td|dd	fS )
a  
    Upsert RAG documents and chunks for a business.
    Body:
    {
      "documents": [
        {
          "source_id": "doc-1",
          "title": "FAQ",
          "source_type": "kb",
          "source_uri": "https://...",
          "metadata": {},
          "chunks": [
            {"chunk_id": "doc-1-1", "text": "...", "embedding": [0.1, ...], "metadata": {}}
          ]
        }
      ]
    }
    Tsilent	documentsr'   zdocuments array is requiredr   r   )r   	ingestionr   )r   r  r=   r   r  ingest_documents)r;   r   r  r   r3   r3   r4   rag_ingest_documents  s   r  z/rag/<bid>/ingest-transcriptsc              	   C   sZ   t jddpi }tj| t|ddt|ddt|dd|dd	}t|d
fS )z
    Backfill transcripts from DB into RAG chunks/documents.
    Body:
    {
      "presales_only": true,
      "limit": 1000,
      "overwrite_existing": false,
      "callids": ["optional", "callid-list"]
    }
    Tr  r  r2  rp  r  Fr  r  r   )r   r  r  r  r  r=   r`   r   r  r3   r3   r4   rag_ingest_transcripts%  s   r  z/rag/<bid>/queryc                 C   s   t jddpi }|d}|d}|stddidfS |s&tddidfS tj| |||d	|d
|d|d|d|dd	}t|dfS )aS  
    Query RAG with memory and customer profile updates.
    Body:
    {
      "user_id": "cust-123",
      "message": "user question",
      "query_embedding": [optional vector],
      "conversation_id": "optional",
      "top_k": 8,
      "min_similarity": 0.2,
      "metadata": {},
      "profile_updates": {"traits": {...}}
    }
    Tr  user_idr(   r'   zuser_id is requiredr   zmessage is requiredquery_embeddingconversation_idtop_kmin_similaritymetadataprofile_updates)	r;   r  r(   r  r  r  r  r  r  r   )r   r  r=   r   r  r  )r;   r   r  r(   r   r3   r3   r4   	rag_query?  s&   

r  z*/rag/<bid>/conversations/<conversation_id>c                 C   s4   t jjddtd}tj| ||d}t||ddfS )zGet conversation history.r2  r   )ri   r  rq  )r  messages)r   r.   r=   r`   r  get_conversation_messagesr   )r;   r  r2  r  r3   r3   r4   rag_get_conversationi  s   r  z/rag/<bid>/profiles/<user_id>c                 C   s   t | |}t|dfS )z2Get current user profile learned from prior chats.r   )r  get_user_profiler   )r;   r  rn  r3   r3   r4   rag_get_profilew  s   r  z'/crm/<bid>/leadsquared/presales-mappingc                 C   s`   t jjddtd}t jd}tt jddd}t| |||d}|d	r(d
nd}t||fS )Nr  r   r  r   r   Fr  r   r   r   r   )r   r.   r=   r`   rj   r   r   )r;   r   r   r   r"  r   r3   r3   r4   leadsquared_presales_mapping  s   r  z"/crm/<bid>/leadsquared/integrationc                 C   s6   t | d}|stddd ddfS td|ddfS )	Nr   Fz!LeadSquared integration not found)r   r(   r   r  T)r   r   r   )r   get_crm_integrationr   )r;   integrationr3   r3   r4   get_leadsquared_integration  s   r  c           	   
   C   s  t | }|r|S tjddpi }t|dd }t|dd }t|dd }t|dd}t| d}|rDt	| dni }|sN|d	d}|sV|d
d}|rZ|sct
ddddfS |sn|phi dpmd}tj| d|||||dp|i d t
ddddfS )NTr  lsq_access_keyr:   lsq_secret_keylsq_api_hostr   r   r   r   Fz.lsq_access_key and lsq_secret_key are requiredr   r(   r   r   z$https://api-in21.leadsquared.com/v2/r_   )r;   rK  r   r   r   r   r_   z*LeadSquared integration saved successfullyr   )rG   r   r  r-   r=   r>   r  r   r  r   r   upsert_crm_integration)	r;   	admin_errr   r   r   r   r   existing_integrationexisting_credsr3   r3   r4   save_leadsquared_integration  s8   	r  z0/crm/<bid>/leadsquared/integration/push-activityc                 C   s   t  stddddfS tjddpi }|stddddfS t| d	}|r1|d
r1|ds:tddddfS t|d
 |d |dd}||}|drTdnd}t||fS )NF!CRM write operations are disabledr  rE   Tr  zActivity payload is requiredr   r   r   r   r   r   r   r   r   )	_crm_write_allowedr   r   r  r   r   r=   r   create_activity)r;   r   r   r   r   r  r3   r3   r4   push_leadsquared_activity  s    
r  z'/crm/<bid>/leadsquared/integration/testc                 C   s   t | d}|r|dr|dstddddfS t|d |d |dd	}| }|d
r>t | d t|dfS t|dfS )Nr   r   r   Fz&LeadSquared integration not configuredr  r  r   r   r   r   r   )r   r   r=   r   r   test_connectionmark_crm_integration_tested)r;   r   r   r   r3   r3   r4   test_leadsquared_integration  s   
r  c                 C   s6   t | }|r|S t| d}td|rdndddfS )Nr   Tz,LeadSquared integration deleted successfullyz%LeadSquared integration did not existr  r   )rG   r   delete_crm_integrationr   )r;   r  removedr3   r3   r4   delete_leadsquared_integration  s   
r  c                 C   sh   t | d}|r|dr|dr|ds"d tddddffS t|d |d |d	d
}|d fS )Nr   r   r   r   Fr   r  r   r   r   )r   r   r=   r   r   )r;   r   r   r3   r3   r4   !_get_lsq_service_for_bid_or_error  s   "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   r   r3   r3   r4   rY    s   


rY  c                 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 )Nr  )r   r   r=   r  r-   r?   )recordr  r   
record_keyrh   r3   r3   r4   r   &  s   
 r   c                   C   s   t tdd  dkS )NALLOW_CRM_WRITEr  rd   )r-   r\   r]   r>   r?   r3   r3   r3   r4   r  3  r  r  z#/crm/<bid>/leadsquared/leads/searchc                 C   s6   t | \}}|r
|S tjddpi }t||dfS )NTr  r   )r  r   r  r   r   )r;   r   errr   r3   r3   r4   leadsquared_search_leads7  s
   r  z&/crm/<bid>/leadsquared/leads/<lead_id>c                 C   s:   t | \}}|r
|S ||}t||drdfS dfS )Nr   r   r   )r  get_leadr   r=   r;   lead_idr   r  r   r3   r3   r4   leadsquared_get_leadC  s
   
r  z/crm/<bid>/leadsquared/leadsc                 C   sb   t  stddddfS t| \}}|r|S tjddpi }||}t||dr.dfS d	fS 
NFr  r  rE   Tr  r   r   r   )r  r   r  r   r  create_leadr=   )r;   r   r  r   r   r3   r3   r4   leadsquared_create_leadO  s   
r  c                 C   sd   t  stddddfS t| \}}|r|S tjddpi }|||}t||dr/dfS d	fS r  )r  r   r  r   r  update_leadr=   )r;   r
  r   r  r   r   r3   r3   r4   leadsquared_update_lead^  s   r  c                 C   sR   t  stddddfS t| \}}|r|S ||}t||dr&dfS dfS )NFr  r  rE   r   r   r   )r  r   r  delete_leadr=   r	  r3   r3   r4   leadsquared_delete_leadm  s   
r  r  c                 C      t ddidfS )Nr'   zEndpoint not foundr  ru  r'   r3   r3   r4   	not_found     r    c                 C   r  )Nr'   zMethod not allowedr  ru  r  r3   r3   r4   method_not_allowed  r  r  c                 C   r  )Nr'   Internal server errorr)   ru  r  r3   r3   r4   internal_error  r  r  z/audio/proxy/<bid>/<callid>c           
   
      s  ddl }ddlm}m} zWt| |}|r|ds#tddidfW S |d }|j|dd	d
  jdkrIt	
d| d j  tddidfW S  jdd} fdd}||| |ddddW S  ty }	 zt	
d| dt|	  tddidfW  Y d}	~	S d}	~	ww )z>Proxy audio file from Google Drive to bypass CORS restrictionsr   N)Responsestream_with_contextfileUrlr'   zAudio file not foundr  Tr  )streamtimeoutr   zFailed to fetch audio from r%   zFailed to fetch audio filei  r   z
audio/mpegc                  3   s"     j ddD ]} | r| V  qd S )Ni    )
chunk_size)iter_content)chunkr   r3   r4   generate  s   zproxy_audio.<locals>.generatebyteszpublic, max-age=3600)zAccept-RangeszCache-Control)content_typer  zError proxying audio for r  r)   )requestsflaskr  r  r   rq  r=   r   r  r+   r'   r  r*   r-   )
r;   r;  r'  r  r  r<  file_urlr&  r$  r0   r3   r#  r4   proxy_audio  s2   
	r*  z/admin/usersc                  C   sz   t jd} | r| dstddidfS | dd}t|}|r'|ds/tddid	fS t \}}td
|i|fS )z!Get all users (master admin only)r   r	  r'   r
  r  r:   r7   Master admin access requiredrE   r  )	r   r  r=   r  r   r  r   r2  get_all_users)r  r  r@   r  r  r3   r3   r4   admin_get_all_users  s   
r-  z/admin/users/createc               
   C   s  t jd} | r| dstddidfS | dd}t|}|r'|ds/tddid	fS t j}g d
}|D ]}||sLtd| didf  S q8tj	|d |d |d |d|dd|ddd\}}|dkr|d }|dr|dd}	|d D ](}
t
|
tr|
dn|
}t
|
tr|
d}|du r|	}n|	}t||| qtj|d|ddd|d  t jt jdd t||fS )z%Create a new user (master admin only)r   r	  r'   r
  r  r:   r7   r+  rE   )r   r   rY   r   r   r   r   rY   r   r9   r@   F)r   r   rY   r   r9   r7   r  r  r8   r;   Nr   zCreated new user: r  r  r   activity_typedescriptionr  r  )r   r  r=   r  r   r  r   r2  r   r   r   r   assign_business_accesslog_activityr  )r  r  r@   r   r   r   r   r  r  default_rolerA   r;   r9   r3   r3   r4   admin_create_user  sV   









	r4  z/admin/businessesc                  C   s   t  \} }td| i|fS )zGet all businessesr8   )r   r  r   )r8   r  r3   r3   r4   admin_get_all_businesses  s   r5  z/admin/businesses/createc            	   
   C   s  t jd} | r| dstddidfS | dd}t|}|r'|ds/tddid	fS d
}t jrDt jdrDt j	}t j
d}nt  }|pKi }|drV|ds^tddidfS tj|d |d |dd\}}|dkrtj|d|ddd|d  d|d  dt jt jdd |r|jrzt|d |}||d< W n' ty } ztdt|  tt|ddd fW  Y d
}~S d
}~ww t||fS )!z)Create a new business (master admin only)r   r	  r'   r
  r  r:   r7   r+  rE   Nzmultipart/form-dataraw_calls_filer;   rT  zbid and name are requiredr   r0  )r;   rT  r0  r  r  r   create_businesszCreated new business: z (ID: r  r  r.  uploadzError importing raw calls: T)r'   business_createdr)   )r   r  r=   r  r   r  r   r2  r&  formr~  r  r7  r2  r  r  r   r  r*   r+   r'   r-   )	r  r  r@   r  r   r   r  upload_infor0   r3   r3   r4   admin_create_business  s^   



	r<  z%/admin/users/<int:user_id>/businessesc                 C   s   t jd}|r|dstddidfS |dd}t|}|r'|ds/tddid	fS t j}|d
s?tddidfS tj	| |d
 |ddd\}}t||fS )z4Assign business access to a user (master admin only)r   r	  r'   r
  r  r:   r7   r+  rE   r;   zbid is requiredr   r9   r@   )r  r;   r9   )
r   r  r=   r  r   r  r   r2  r   r1  )r  r  r  r@   r   r   r  r3   r3   r4   admin_assign_business_accessV  s    



r=  z/admin/activity-logc                  C   s  t jd} | r| dstddidfS | dd}t|}|r'|ds/tddid	fS t jjd
dt	d}t jjddt	d}t jjdt	d}t jd}t jd}t jd}t jd}	t jd}
t jd}t jd}tj
|||||||	|
||d
\}}t||fS )z$Get activity log (master admin only)r   r	  r'   r
  r  r:   r7   r+  rE   r2  r3  r  r4  r   r  r/  action_coder;   username_containsdescription_containsr)  r*  )
r2  r4  r  r/  r>  r;   r?  r@  r)  r*  )r   r  r=   r  r   r  r   r2  r.   r`   get_activity_log)r  r  r@   r2  r4  r  r/  r>  r;   r?  r@  r)  r*  r   r  r3   r3   r4   admin_get_activity_logs  s<   

rB  z/api/embed/tokenc            	      C   s  t j} | stddidfS | d}| d}|r|s#tddidfS t|t|}|s5tddidfS t jd	}|d
rZ|rZdd |d
 dD }||vrZtddidfS tj	t||d |d d}t
jdd}| d| }t||dt||d ddfS )zrGenerate a short-lived embed token for iframe integration.
    Called by external app backends with their API key.r'   Request body is requiredr   api_keyr;   zapi_key and bid are requiredz1Invalid API key or unauthorized for this businessr  Originallowed_originsc                 S   s   g | ]}|  qS r3   r  )rm   or3   r3   r4   rw     rx   z#get_embed_token.<locals>.<listcomp>r  zOrigin not allowedrE   partner_namer  )r;   rH  
api_key_idEMBED_BASE_URLzhttp://localhost:6174z/#/embed?token=r  )r  	embed_url
expires_inr;   rH  r   )r   r   r   r=   r   validate_api_keyr-   r  r  generate_embed_tokenr^   r_   )	r   rD  r;   
key_recordoriginallowedr  embed_base_urlrK  r3   r3   r4   get_embed_token  s>   

rS  z/api/embed/validatec                  C   sZ   t jd} | stddidfS t| }|stddidfS td|d |d	 d
dfS )zCValidate an embed token. Called by the frontend embed page on load.r  r'   zToken is requiredr   zInvalid or expired embed tokenr  Tr;   rH  )validr;   rH  r   )r   r.   r=   r   r   validate_embed_token)r  r   r3   r3   r4   validate_embed_token_route  s   
rV  z/admin/embed-keysc            
   	   C   s  t jd} | r| dstddidfS | dd}t|}|r'|ds/tddid	fS t j}|s<tdd
idfS |d}|d}|rJ|sRtddidfS |d}|d}tj	t
||||d\}}	tj|d |d dd| d| dt jt jdd t||	fS )z.Create a new embed API key (master admin only)r   r	  r'   r
  r  r:   r7   r+  rE   rC  r   r;   rH  z!bid and partner_name are requiredrF  
expires_at)r;   rH  rF  rW  r  r   create_embed_keyz#Created embed API key for business z (partner: r  r  r.  )r   r  r=   r  r   r  r   r2  r   create_embed_api_keyr-   r2  r  )
r  r  r@   r   r;   rH  rF  rW  r   r  r3   r3   r4   admin_create_embed_key  s>   






	rZ  c                  C   s   t jd} | r| dstddidfS | dd}t|}|r'|ds/tddid	fS t jd
}tj	|d\}}td|i|fS )z+List all embed API keys (master admin only)r   r	  r'   r
  r  r:   r7   r+  rE   r;   rv  r  )
r   r  r=   r  r   r  r   r2  r.   list_embed_api_keys)r  r  r@   r;   r  r  r3   r3   r4   admin_list_embed_keys  s   
r\  z/admin/embed-keys/<int:key_id>c              	   C   s   t jd}|r|dstddidfS |dd}t|}|r'|ds/tddid	fS t| \}}tj	|d
 |d dd|  t j
t jdd t||fS )z+Revoke an embed API key (master admin only)r   r	  r'   r
  r  r:   r7   r+  rE   r  r   revoke_embed_keyzRevoked embed API key r  r.  )r   r  r=   r  r   r  r   r2  revoke_embed_api_keyr2  r  )key_idr  r  r@   r   r  r3   r3   r4   admin_revoke_embed_key)  s"   

	r`  __main__z%Starting Call Analytics Dashboard APIzEnvironment: ENVdevelopmentzDebug mode: DEBUGHOSTz0.0.0.0PORTi  )rW   rX   debug)F)Nr   Frk   r  )r   )NNNNNNNF)NNr)   )NN)r(  r   r   r   
flask_corsr   rJ  r\   r   r   r   r   loggingr+  r  	functoolsr	   urllib.parser
   r_   r   r   r   r@  r   quality_parameters_handlerr   r  r   r   r   r   r   r  r   leadsquared_servicer   basicConfigINFO	getLoggerr,   r+   r^   from_objectr^  r  r   r   r6   rB   rG   ra   rb   rj   r   r   r   router   r  r  r  r  r  r  r  r#  r(  r0  r7  r=  r?  rD  rS  rU  rX  ry  r{  r|  r  r  r  r  r  r  	frozensetr  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r?  rF  rH  r\  r`  rb  rd  rj  rl  ro  rs  rw  ry  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#  rh  r0  r3  r>  rA  rC  rX  rY  rl  rt  ry  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  rY  r   r  r  r  r  r  r  errorhandlerr  r  r  r*  r-  r4  r5  r<  r=  rB  rS  rV  rZ  r\  r`  r  r=   runr3   r3   r3   r4   <module>   s   

	




 '

!
&),	""NH			
 5	

	5
HL%
F.;B5.Z



	
 &

$



/:9,.- 