o
    §jm#  ã                   @  s´   d Z ddlmZ ddlZddlZddlZddlmZ ddlmZm	Z	m
Z
mZ e e¡ZdZdZd+dd„Zd,d-dd„Zd.dd„Zd/dd„Zd0dd „Zd1d"d#„Zdddd$œd2d)d*„ZdS )3zSLLM-backed lead insights generation with lead-specific path-to-conversion guidance.é    )ÚannotationsN)Údatetime)ÚAnyÚDictÚListÚOptional)z#i reviewed our earlier conversationz1wanted to continue from the points you had raisedzEbefore we move ahead, is there any concern about pricing, fit, timingz<is there any concern about pricing, fit, timing, or approvaluò	  You are a sales intelligence analyst. Synthesize ALL provided call data for ONE lead into actionable insights.

Return STRICT JSON only (no markdown). Use this exact schema:
{
  "summary": "<1-2 sentences unique to this lead>",
  "objections": [
    {
      "objection": "<specific objection from calls>",
      "type": "<call purpose or objection category>",
      "how_handled": "<what the agent did, from summaries/transcripts>",
      "customer_satisfied": <true|false>
    }
  ],
  "bant": {
    "budget": "<quote or fact from calls, or 'Not mentioned'>",
    "authority": "<quote or fact from calls, or 'Not mentioned'>",
    "needs": "<quote or fact from calls, or 'Not mentioned'>",
    "timeline": "<quote or fact from calls, or 'Not mentioned'>"
  },
  "data_capture": {
    "competitors_mentioned": "<specific names or 'Not mentioned'>",
    "products_discussed": "<specific products/services or 'Not mentioned'>",
    "customer_sentiment_trend": "<Positive|Neutral|Negative|Needs follow-up with brief reason>",
    "key_concerns": "<semicolon-separated specifics from this lead>"
  },
  "path_to_conversion": {
    "previous_summary": ["<bullet>", "..."],
    "action_steps": [
      {
        "title": "<specific next action>",
        "say": "<speakable script the rep can use on the next call>",
        "rationale": "<why this step fits THIS lead>"
      }
    ]
  }
}

CRITICAL rules for path_to_conversion.action_steps (What to do next):
- Provide 2-4 steps, ordered by priority for closing THIS lead.
- Every "title", "say", and "rationale" MUST reference at least one concrete fact from THIS lead's calls: product/plan name, price or budget figure, location, competitor, person name, promised date, open objection, or explicit customer request.
- "say" must sound natural on a phone call and paraphrase or quote what was actually discussed â€” not a generic sales checklist.
- FORBIDDEN generic templates (never use these or close paraphrases):
  * "I reviewed our earlier conversation and wanted to continue from the points you had raised."
  * "Before we move ahead, is there any concern about pricing, fit, timing, or approval that I should clarify?"
  * Any step that could apply unchanged to a different lead with no edits.
- If a topic was never discussed, do not ask a generic discovery question about it; instead act on what WAS discussed.
- previous_summary bullets must each cite a distinct fact from this lead (dates, amounts, decisions, blockers).

Use only evidence from the provided context. Do not invent facts.ÚtextÚstrÚmax_lenÚintÚreturnc                 C  s4   t | pdƒ ¡ } t| ƒ|kr| S | d |d … d S )NÚ é   z...[truncated])r	   ÚstripÚlen)r   r
   © r   úlead_insights.pyÚ	_truncateG   s   r   é¬  ÚdetailsúDict[str, Any]Úmax_transcript_charsc                 C  s  |   d¡pg }g }|dd… D ]Y}t|  d¡pdƒ ¡ }|r-t|ƒ|kr-d|| d…  }| |  d¡t|  d¡p:dƒ|  d	¡|  d
¡t|  d¡dƒt|  d¡dƒt|  d¡dƒ|  d¡|rct||ƒnddœ	¡ q|   d¡|   d¡|   d¡|   d¡|   d¡|   d¡|   d¡dœ|dœS )z'Compact per-lead context for the model.ÚcallsNé   Ú
transcriptr   z...[earlier omitted]
ÚcallidÚcall_starttimeÚ	directionÚ	agentnameÚcall_purposeéÈ   Úsummaryi   Úobjections_concernsi  Úquality_score)	r   Údater   ÚagentÚpurposer!   Ú
objectionsr#   r   Ú
lead_phoneÚ	lead_nameÚlead_statusÚ
owner_nameÚnext_task_due_dateÚtotal_conversationsÚavg_quality_score)ÚphoneÚnameÚstatusÚownerr,   r-   r.   )Úleadr   )Úgetr	   r   r   Úappendr   )r   r   r   Ú	call_rowsÚcallr   r   r   r   Úbuild_lead_contextN   s:   ÷ÿù	ör8   úOptional[Dict[str, Any]]c                 C  s|   | sd S |   d¡}|  d¡}|dks|dks||krd S zt | ||d … ¡}W n tjy4   Y d S w t|tƒr<|S d S )NÚ{Ú}éÿÿÿÿé   )ÚfindÚrfindÚjsonÚloadsÚJSONDecodeErrorÚ
isinstanceÚdict)r   ÚstartÚendÚparsedr   r   r   Ú_extract_json_objectt   s   

ÿrH   ÚsayÚboolc                   s2   t | pdƒ ¡  ¡ ‰ ˆ sdS t‡ fdd„tD ƒƒS )Nr   Tc                 3  s    | ]	}t  |ˆ ¡V  qd S )N)ÚreÚsearch)Ú.0Úpattern©Úloweredr   r   Ú	<genexpr>†   s   € z"_is_generic_say.<locals>.<genexpr>)r	   r   ÚlowerÚanyÚ_GENERIC_SAY_PATTERNS)rI   r   rO   r   Ú_is_generic_say‚   s   rU   Úaction_stepsr   úList[Dict[str, str]]c                 C  s¤   t | tƒsg S g }| D ]@}t |tƒsqt| d¡pdƒ ¡ }t| d¡p%dƒ ¡ }t| d¡p5| d¡p5dƒ ¡ }|rA|rAt|ƒrBq| |||dœ¡ q|d d… S )NÚtitler   rI   Ú	rationaleÚworks_because)rX   rI   rY   é   )rC   ÚlistrD   r	   r4   r   rU   r5   )rV   Ú
normalizedÚsteprX   rI   rY   r   r   r   Ú_normalize_action_steps‰   s   

 r_   Úrawc              
   C  sf  t |  d¡tƒr|  d¡ni }| d¡}t |tƒr$dd„ | d¡D ƒ}nt |tƒs+g }dd„ |D ƒd d… }t| d¡ƒ}|  d	¡}t |tƒsIg }t |  d
¡tƒrV|  d
¡ni }t |  d¡tƒre|  d¡ni }t ¡  	¡ d t|  d¡pudƒ 
¡ |t| d¡p€dƒ 
¡ p…dt| d¡pŒdƒ 
¡ p‘dt| d¡p˜dƒ 
¡ pdt| d¡p¤dƒ 
¡ p©ddœ|||dœdœS )NÚpath_to_conversionÚprevious_summaryc                 S  s   g | ]
}|  ¡ r|  ¡ ‘qS r   )r   )rM   Úliner   r   r   Ú
<listcomp>   s    z/_normalize_insights_payload.<locals>.<listcomp>Ú
c                 S  s$   g | ]}t |ƒ ¡ rt |ƒ ¡ ‘qS r   )r	   r   )rM   Úpr   r   r   rd       s   $ é   rV   r'   ÚbantÚdata_captureÚZr!   r   ÚbudgetzNot mentionedÚ	authorityÚneedsÚtimeline)rk   rl   rm   rn   )rb   rV   )Úgenerated_atr!   r'   rh   ri   ra   )rC   r4   rD   r	   Úsplitr\   r_   r   ÚutcnowÚ	isoformatr   )r`   ÚpathÚpreviousrV   r'   rh   ri   r   r   r   Ú_normalize_insights_payload™   s4   




üþõru   ©ÚproviderÚ
model_nameÚruntime_configrw   úOptional[str]rx   ry   c          
      C  s–   t | ƒ}| d¡sdS t› dtj|dd› d}|||||pi d}|s+t d¡ dS t|ƒ}|s8t d	¡ dS t|ƒ}	|	d
 d sIt d¡ dS |	S )z=Generate full insights JSON via LLM. Returns None on failure.r   Nz/

LEAD AND CALL DATA (use only this evidence):
T)Úensure_asciire   rv   z)Lead insights LLM returned empty responsez-Lead insights LLM response was not valid JSONra   rV   z>Lead insights LLM produced no valid lead-specific action_steps)	r8   r4   ÚLEAD_INSIGHTS_SYSTEMr@   ÚdumpsÚloggerÚwarningrH   ru   )
r   Úinvoke_chat_modelrw   rx   ry   ÚcontextÚpromptÚanswerrG   r]   r   r   r   Úgenerate_lead_insights_with_llm½   s2   	
þÿü


r„   )r   r	   r
   r   r   r	   )r   )r   r   r   r   r   r   )r   r	   r   r9   )rI   r	   r   rJ   )rV   r   r   rW   )r`   r   r   r   )
r   r   rw   rz   rx   rz   ry   r9   r   r9   )Ú__doc__Ú
__future__r   r@   ÚloggingrK   r   Útypingr   r   r   r   Ú	getLoggerÚ__name__r~   rT   r|   r   r8   rH   rU   r_   ru   r„   r   r   r   r   Ú<module>   s(    

3
&


(ú