o
    GY)jw                     @   s   d Z ddlZddlZddlZddlmZmZmZ ddlm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mZ eeZe	d	d
Ze	dZe	dZe	ddZdZG dd dZddededefddZdS )zv
Call Analysis with Quality Parameters
Uses AWS Nova AI to analyze calls against business-specific quality parameters
    N)DictListOptional)datetime)QualityParametersHandler)calculate_talk_listen_ratio)DatabaseHandler)load_summary_pipeline_configresolve_summary_prompt_parts
AWS_REGIONz	us-east-1AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_NOVA_MODELzamazon.nova-lite-v1:0   c                   @   sB  e Zd ZdZdd Zdee defddZdd	d
e	e de
defddZd&dedee d
e	e defddZd&ded
e	e defddZdee de	e de	e fddZdedefddZdedefddZd
edededee fdd Zd&d
ed!ededee dedefd"d#Zd&d
ed!ededee dedefd$d%ZdS )'CallAnalyzerzFAnalyze calls using AWS Nova with business-specific quality parametersc                 C   s2   || _ t|| _t|| _tjdttt	d| _
d S )Nzbedrock-runtime)service_nameregion_nameaws_access_key_idaws_secret_access_key)configr   quality_params_handlerr   
db_handlerboto3clientr   r   r   bedrock_runtime)selfr    r   H/home/aiteam/pcaa-dev/dashboard-backend/analyze_calls_with_parameters.py__init__   s   

zCallAnalyzer.__init__
parametersreturnc                 C   s|   g }|D ]4}d|d  d|d  d|d  d|d  d	|d
  d|d p%d d|d  d|d  d}| | qd|S )z'Format quality parameters for AI promptz
**Parameter:** parameter_namez
- **Category:** parameter_groupz
- **Max Score:** 	max_scorez
- **What to check:** check_descriptionz
- **Description:** detailed_descriptionz
- **Sample utterances:** sample_utteranceszN/Az
- **Auto-detect N/A:** auto_detect_naz
- **Type:** parameter_type
)appendjoin)r   r   	formattedparam	param_strr   r   r   format_parameters_for_prompt,   s*   


z)CallAnalyzer.format_parameters_for_promptThas_quality_parametersbidr1   c                C   s    |rt | j|nd }t||dS )Nr0   )r	   r   r
   )r   r2   r1   pipeline_cfgr   r   r   _summary_parts_for_bid?   s   z#CallAnalyzer._summary_parts_for_bidN
transcriptc           	   	   C   sP   |  |}| |\}}|rd| d}nd}d| d| d| d| d	}|S )z/Build comprehensive AI prompt for call analysisr)   

zYou are an expert call quality analyst. Analyze the following call transcript against specific quality parameters.

CALL TRANSCRIPT:
z"

QUALITY PARAMETERS TO EVALUATE:
a  

INSTRUCTIONS:
For EACH parameter above:
1. **Determine Applicability:** Decide if this parameter applies to this specific call. Be generous - only mark as "not applicable" if the parameter truly cannot be evaluated for this call type. Not-applicable parameters count as 0 points toward the overall quality score (their max_score still counts in the denominator).

2. **Score (if applicable):** If the parameter is applicable, score it as an integer from 0 up to max_score (inclusive).
   - The score must be on the same scale as max_score (e.g. if max_score is 10, valid scores are 0-10, NOT 0-100).
   - **DEFAULT: Give FULL max_score unless there are clear problems**
   - If the agent made partial effort, deduct proportionally on the 0..max_score scale (e.g. 7/10, not 70/10).
   - Only deduct points for serious, obvious failures or complete absence of the behavior
   - When in doubt, give the higher score - be generous and supportive

3. **Provide Evidence:**
   - Exact transcript segment where you detected this (quote the relevant lines)
   - Approximate speaker turn number (counting from the start of the conversation)
   - Approximate timestamp in format "MM:SS"

4. **Reasoning:** Brief explanation of why you gave this score or why it's not applicable.
a  CUSTOMER PROFILING (BANT):
Also extract sales qualification details for Budget, Authority, Need, and Timeline.
- Provide a clear value if mentioned (amount, range, or statement).
- If not mentioned, set value to "Not mentioned" and evidence to "N/A".
- Include evidence and reasoning for each field.

OUTPUT FORMAT (JSON only, no additional text):
{
  "parameters": {
    "Parameter Name": {
      "applicable": true/false,
      "score": <score_value or null if N/A>,
      "max_score": <max_score_value>,
      "detected_in_segment": "<exact quote from transcript>",
      "speaker_turn": <turn_number>,
      "timestamp": "MM:SS",
      "reasoning": "<explanation>"
    }
  },
  "customer_profile": {
    "budget": {
      "value": "<amount/range/statement or 'Not mentioned'>",
      "evidence": "<exact quote from transcript or 'N/A'>",
      "reasoning": "<brief explanation>",
      "confidence": "high/medium/low"
    },
    "authority": {
      "value": "<role/decision maker status or 'Not mentioned'>",
      "evidence": "<exact quote from transcript or 'N/A'>",
      "reasoning": "<brief explanation>",
      "confidence": "high/medium/low"
    },
    "need": {
      "value": "<stated need/pain point or 'Not mentioned'>",
      "evidence": "<exact quote from transcript or 'N/A'>",
      "reasoning": "<brief explanation>",
      "confidence": "high/medium/low"
    },
    "timeline": {
      "value": "<timeframe/urgency or 'Not mentioned'>",
      "evidence": "<exact quote from transcript or 'N/A'>",
      "reasoning": "<brief explanation>",
      "confidence": "high/medium/low"
    }
  },
  "customer_profile_summary": "<1-2 sentence summary of BANT status>",
  "overall_summary": "a  ",
  "call_purpose": "<main reason for the call>",
  "objections_concerns": "<any issues raised by customer>",
  "objection_type": "<2-3 word classification of the objection/concern type, e.g., 'Delivery Issue', 'Food Quality', 'Payment Problem', 'Order Delay', 'Wrong Items', 'Bad Service', 'None' if no objection>",
  "sentiment": "<positive/negative/neutral>"
}

IMPORTANT:
- Return ONLY valid JSON, no markdown formatting
- Use exact parameter names as keys in the "parameters" object
- For N/A parameters, set "applicable": false and provide a reason
- Provide actual transcript quotes for "detected_in_segment"
- Be precise with timestamps and speaker turns
- For customer_profile fields, always include value, evidence, reasoning, and confidence
)r/   r4   )	r   r5   r   r2   params_formattedsummary_config_blockoverall_summary_hintsummary_sectionpromptr   r   r   build_analysis_promptG   s   
GWz"CallAnalyzer.build_analysis_promptc                 C   s>   | j |dd\}}|rd| dnd}d| d| d| dS )zFBuild a prompt for short calls where quality scoring is not applicableFr0   r)   r6   zcAnalyze the following short call transcript for sales qualification and summary.

CALL TRANSCRIPT:
zGOUTPUT FORMAT (JSON only, no additional text):
{
  "overall_summary": "a  ",
  "call_purpose": "<main reason for the call>",
  "sentiment": "<positive/negative/neutral>",
  "customer_profile": {
    "budget": {
      "value": "<amount/range/statement or 'Not mentioned'>",
      "evidence": "<exact quote from transcript or 'N/A'>",
      "reasoning": "<brief explanation>",
      "confidence": "high/medium/low"
    },
    "authority": {
      "value": "<role/decision maker status or 'Not mentioned'>",
      "evidence": "<exact quote from transcript or 'N/A'>",
      "reasoning": "<brief explanation>",
      "confidence": "high/medium/low"
    },
    "need": {
      "value": "<stated need/pain point or 'Not mentioned'>",
      "evidence": "<exact quote from transcript or 'N/A'>",
      "reasoning": "<brief explanation>",
      "confidence": "high/medium/low"
    },
    "timeline": {
      "value": "<timeframe/urgency or 'Not mentioned'>",
      "evidence": "<exact quote from transcript or 'N/A'>",
      "reasoning": "<brief explanation>",
      "confidence": "high/medium/low"
    }
  },
  "customer_profile_summary": "<1-2 sentence summary of BANT status>"
}

IMPORTANT:
- Return ONLY valid JSON, no markdown formatting
)r4   )r   r5   r2   r8   r9   r:   r   r   r   build_short_call_prompt   s   
z$CallAnalyzer.build_short_call_promptspeaker_segmentsactual_durationc              	   C   s   |durzt |W S  ttfy   Y dS w |sdS z*g }|D ]}|d}|du r/|d}|dur:|t | q|rBt|W S dW S  ttfyP   Y dS w )zAGet duration in seconds from actual duration or speaker segments.Nend_timeend)float	TypeError
ValueErrorgetr*   max)r   r>   r?   	end_timessegend_valr   r   r   _get_duration_seconds   s*   


z"CallAnalyzer._get_duration_secondssentiment_valuec                 C   sz   |du rdS t |tsdS | sdS |  }d|v r#| d }d|v s+d|v r-dS d|v s9d|v s9d	|v r;dS dS )
z:Normalize sentiment to one of: positive, negative, neutralNneutral r   positivegoodnegativebadpoor)
isinstancestrstriplowersplit)r   rK   sentiment_lowerr   r   r   _normalize_sentiment   s   
z!CallAnalyzer._normalize_sentimentr;   c              
      s  zdd|igdgdddd}t dt  | jjtd	d	t|d
}|d  d}t	|}|
di 
di 
dg }|rSt|trS|d 
dd }ntdt d|dd  d ddl  fdd}||}	zt	|	W W S  tjy }
 zt d|
  t d|	   d}
~
ww  tjy } zt d|  t d|   d}~w ty } z	t d|   d}~ww )zCall AWS Nova Bedrock APIusertext)rolecontentg333333?i  )temperaturemax_new_tokens)messagesinferenceConfigzCalling AWS Nova model: zapplication/json)modelIdcontentTypeacceptbodyre   zutf-8outputmessager]   r    z,Empty content received from Bedrock responsezReceived response from Nova: N   z...c                    s     d|  j}|r|d}n!| d}| d}|dkr/|dkr/||kr/| ||d  }n| } dd|} dd	|} d
d|}|S )Nz```(?:json)?\s*(\{.*?\})\s*```   {}z,\s*([\]}])z\1z+("speaker_turn"\s*:\s*)(\d+(?:\s*,\s*\d+)+)z\1[\2]z&("speaker_turn"\s*:\s*)(\d+\s*-\s*\d+)z\1"\2")searchDOTALLgroupfindrfindsub)r[   matchjson_str	start_idxend_idxrer   r   extract_json_from_text6  s(   

z:CallAnalyzer.call_aws_nova.<locals>.extract_json_from_textzFailed to parse cleaned JSON: zCleaned string attempts: z+Failed to parse AWS Nova response as JSON: zResponse text: zError calling AWS Nova: )loggerinfor   r   invoke_modeljsondumpsreaddecodeloadsrE   rS   listrU   rD   ry   JSONDecodeErrorerror	Exception)r   r;   request_bodyresponseraw_responseresponse_bodycontent_listresponse_textrz   clean_json_strnested_eer   rx   r   call_aws_nova  sX   
zCallAnalyzer.call_aws_novaobjections_textc              
   C   s   |r|  dkr
g S zS| j }| }|d|f | }W d   n1 s*w   Y  |s<td|  g W S ddd |D }d| d	| d
| d}| 	|}	|	
dg W S  tyy }
 ztd|
  g W  Y d}
~
S d}
~
ww )z7Classify objections into predefined categories using AIrh   z
                    SELECT id, description
                    FROM objection_classifications
                    WHERE bid = %s
                    ORDER BY id
                Nz+No objection classifications found for BID r)   c                 S   s"   g | ]}|d   d|d  qS )idz. descriptionr   ).0cr   r   r   
<listcomp>|  s    z5CallAnalyzer._classify_objections.<locals>.<listcomp>z_You are analyzing customer objections from a call transcript.

OBJECTIONS/CONCERNS IDENTIFIED:
z!

CALL TRANSCRIPT (for context):
z"

AVAILABLE OBJECTION CATEGORIES:
aa  

TASK:
Match each objection/concern to the most appropriate category ID from the list above. An objection can match multiple categories if applicable.

OUTPUT FORMAT (JSON only, no additional text):
{
  "classified_objections": [
    {
      "classification_id": <number>,
      "objection_text": "<brief description of this specific objection>",
      "confidence": "<high/medium/low>"
    }
  ]
}

IMPORTANT:
- Return ONLY valid JSON
- Only include objections that clearly match a category
- If no objections match any category, return empty array
- Multiple objections can have the same classification_id
classified_objectionszError classifying objections: )rU   r   get_connectioncursorexecutefetchallr{   warningr+   r   rE   r   r   )r   r2   r   r5   connr   classificationsclassifications_textr;   ai_responser   r   r   r   _classify_objectionsf  s<   


	
 z!CallAnalyzer._classify_objectionscallidc                 C   s  zL|  ||}|duo|tk }|rtd||t | | j||d}d|d< t||d}	i d|d|d	|d
dd|ddddddd| |dpRddddddddddt	
dgd|	d d|	d d|	d d|	d d|	d |	d |	d t|d}
| j|||
 | j|||d|d d |
W S | j|}|std!| d" | |||||W S | j|||d}td#| d$| d%t| d& | |}i }i }g }d'}d'}|D ]x}|d( }|d) |i }|d*d+rG|d,d'}|du rd'}|d- pd'}| j||}||d+d.||< d+|d/d|d0d'|d1d2|d3dd4||< ||7 }||7 }q|d- pMd'}d'|d5|d3d6d7||< || ||7 }qt||d}	| j|\}}|dur}|}|}n|d'krt|| d8 d9}nd:}|dd}g }|r| rtd;|  | |||}td<t| d= i d|d|d	|d
dd|ddd|d|ddd| |dpdd|dt	
|dt	
|d|dt	
|d|	d d|	d d|	d d|	d d|	d |	d |	d t||d>}
| j|||
 | j|||d|d d td?| d@| dA| dB| dC	 |
W S  tyi } ztdD| d$| dE|   d}~ww )Fa  
        Analyze a call with quality parameters and talk-to-listen ratio

        Args:
            bid: Business ID
            callid: Call ID
            transcript: Full call transcript
            speaker_segments: List of speaker segments with timing info
            actual_duration: Actual call duration in seconds

        Returns:
            Dict containing all analysis results
        Nz>Call %s duration %.2fs is below %ss; skipping quality scoring.)r2   z%Call duration too low to assign scorequality_score_noter?   r   r2   summaryoverall_summaryrh   call_purposeobjections_concernsobjection_typeNone	sentimentrL   quality_scoreparameter_scoresparameter_detectionstotal_possible_scoreparameters_not_applicabletalk_listen_ratioagent_talk_timecustomer_talk_timedead_air_percentageagent_speak_percentagecustomer_speak_percentage
assessmentr   talk_listen_assessmentanalysis_modelr   customer_profilecustomer_profile_summaryz$No quality parameters found for BID z. Using fallback analysis.zAnalyzing call z	 for BID z with z parametersr   r!   r   
applicableTscorer#   )r   r#   r   detected_in_segmentspeaker_turn	timestampz00:00	reasoning)detectedtranscript_segmentr   r   r   FzNot applicable to this call)r   r#   r   reasond      g        z Classifying objections for call Found z classified objections)r   r   r   r   r   u	   ✓ Call z analyzed: Score /z	 (Total: )zError analyzing call : )rJ    MIN_DURATION_FOR_SCORING_SECONDSr{   r|   r   r=   r   rE   rY   r~   r   r   r   save_call_analyticssave_bant_analysisr   get_parametersr   _fallback_analysisr<   lennormalize_parameter_scorer*   +compute_quality_score_from_parameter_scoresroundrU   r   r   r   )r   r2   r   r5   r>   r?   duration_secondsis_short_callr   
ratio_dataanalytics_datar   r;   r   r   r   total_scorer   r-   
param_nameparam_resultr   r#   computed_qscomputed_tpsr   r   r   r   r   r   r   analyze_call  sf  	

"















	



$zCallAnalyzer.analyze_callc              	   C   s  t d|  t||d}| j|dd\}}|rd| dnd}	|r%dnd	}
d
| d|	 d|
 d}z| |}W n   ddddd}Y i 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$ d%|d% |d& |d' t|d(}| j	||| | j
|||d)|d*d |S )+z;Fallback analysis when no quality parameters are configuredz!Using fallback analysis for call r   Fr0   r)   r6   rh   z0- overall_summary: (follow SUMMARY CONFIG above)z'- overall_summary: 5-6 sentence summaryz7Analyze this call transcript and provide:

TRANSCRIPT:
zProvide a JSON response with:
a  
- call_purpose: main reason for call
- objections_concerns: any issues raised
- objection_type: 2-3 word classification (e.g., 'Delivery Issue', 'Food Quality', 'None')
- sentiment: positive/negative/neutral
- customer_profile: object with budget, authority, need, timeline (value/evidence/reasoning/confidence)
- customer_profile_summary: 1-2 sentence summary of BANT status

Return ONLY valid JSON.zCall analysis unavailableUnknownrL   )r   r   r   r   r   r2   r   r   r   r   r   r   r   r   2   r   Nr   r   r   r   r   r   r   r   r   r   r   r   r   )r{   r|   r   r4   r   rE   rY   r   r   r   r   )r   r2   r   r5   r>   r?   r   r8   r9   r:   summary_linebasic_promptr   r   r   r   r   r   o  s   
	

zCallAnalyzer._fallback_analysis)N)__name__
__module____qualname____doc__r   r   r   rT   r/   r   booltupler4   r<   r=   rB   rJ   rY   r   r   r   r   r   r   r   r   r      s*    
$c"2Z(B ,Hr   
   r2   r   limitc                 C   sd  t |}t|}|| |}tdt| d|   g g d}|D ]}z^|| |d }|r4|dsAtd|d  d W q"|d }	|dg }
|
rht	|
t
rhzt|
}
W n tjtfyg   g }
Y nw |d	}|| |d |	|
|}|d
 |d  W q" ty } z!td|d  d|  |d |d t
|d W Y d}~q"d}~ww |S )z(Batch analyze calls that need processingr   z calls to analyze for BID )successfailedr   transcriptszNo transcript found for call z
, skippingr>   durationr   zFailed to analyze call r   r   )r   r   N)r   r   get_calls_for_analysisr{   r|   r   get_raw_call_detailsrE   r   rS   rT   r~   r   r   rC   r   r*   r   r   )r2   r   r   analyzerr   callsresultscallcall_detailsr5   r>   r?   resultr   r   r   r   batch_analyze_calls  s<   
(r   )r   ) r   r   r~   loggingtypingr   r   r   r   osquality_parameters_handlerr   talk_listen_calculatorr   r   r   summary_configr	   r
   	getLoggerr   r{   getenvr   r   r   r   r   r   rT   intr   r   r   r   r   <module>   s0    


     (