
    xj8                       U d Z ddl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Zddl	m
Z
 ddlmZ  ej                  d      Z e
e      j!                         j"                  Zedz  Zej)                  dd	       d
Z ej,                  dd      Z e ej,                  dd            Z ej,                  d      xs  ej4                  d      xs dZ e
d      Zdj;                         Zd"dZ e       Z de!d<   d"dZ"d#dZ#d$dZ$d%dZ% ejL                  dejN                  ejP                  z        Z)d&dZ*	 	 	 	 	 	 d'dZ+d(dZ,	 	 	 	 	 	 	 	 	 	 d)dZ-d*d Z.	 	 	 d+	 	 	 	 	 	 	 	 	 	 	 d,d!Z/y)-u:  
Bruno — SDR TORQ Brasil.

Combina persona ALMA + dossie TORQ + campos custom sdr__ + pipeline TESTE RAFA.

ARQUITETURA (importante):
  - NAO usa a API paga da Anthropic (ANTHROPIC_API_KEY).
  - Usa a assinatura Claude Code (Max plan) do Renato via CLI headless.
  - Binario: /usr/bin/claude (npm i -g @anthropic-ai/claude-code).
  - Auth: /home/mia/.claude/.credentials.json (OAuth da assinatura).
  - Prompt caching e aplicado automaticamente pelo CLI no --append-system-prompt.

Estrategia de contexto:
  - --append-system-prompt = dossie ALMA + instrucoes operacionais (imutavel -> CACHE)
  - --print (stdin/arg)    = contexto CRM + historico da conversa + nova msg do lead
  - --output-format json   = retorna {"result": "...", "session_id": "...", "usage": {...}}

O agente devolve:
  - texto natural (msg pro WhatsApp) — usa [[BREAK]] como separador de blocos
  - BLOCO opcional no formato:
      ---SDR-META---
      {"campos": {...}, "stage": "...", "notificar_renato": bool}
      ---FIM---
    O app.py parseia isso, executa as acoes GHL, e envia so o texto natural.
    )annotationsN)Path)Anybruno_agent	historicoT)parentsexist_ok   BRUNO_MODELzclaude-sonnet-4-6BRUNO_CLI_TIMEOUT120
CLAUDE_BINclaudez/usr/bin/claudez>/opt/mia/workspace/clientes/torq-brasil/sdr_alma_torqbrasil.mdu  
## PROTOCOLO DE RESPOSTA (obrigatorio)

Voce e o Bruno. Toda mensagem tua vai direto pro WhatsApp do lead.

### FORMATO da mensagem
- Cada bloco max 3 linhas
- Divida blocos com [[BREAK]] (max 3 blocos)
- 1 pergunta por bloco, no fim
- NUNCA use travessao (- - ou --). Use virgula, ponto, parenteses
- Tom semiformal com pitada mineira leve
- NUNCA revele que voce e IA / agente / bot

### PRIMEIRA MENSAGEM (regra critica)
Quando o HISTORICO DA CONVERSA vier VAZIO (lead esta te falando pela primeira vez):
- Responda APENAS 1 bloco, curto e acolhedor
- Nao entre em catalogo, preco, modelos, uso proprio vs empresa/revenda
- Nao qualifique nada de cara
- Objetivo: apenas cumprimentar e ABRIR ESPACO pro lead falar o que quer
- Ex: "Oi! Aqui e o Bruno da TORQ Brasil, tudo bem? Que bom que voce chegou ate aqui, me conta o que trouxe voce aqui?"
- SO va pra fase L (Levantar) do ALMA quando o lead responder essa segunda msg pedindo algo ou dando contexto
- Nao inclua bloco META na primeira msg (nao ha nada pra atualizar ainda)

### METADADOS (opcional, apenas quando houver mudanca)
Ao fim da mensagem, se voce identificou dados novos ou quer atualizar pipeline,
adicione UM bloco JSON exatamente neste formato:

---SDR-META---
{"campos":{"sdr__nome_lead":"Marcos","sdr__cidade":"BH","sdr__perfil":"b2c_urbano"},"stage":"qualificado","notificar_renato":false}
---FIM---

Regras do bloco META:
- `campos`: obj com keys sdr__* que voce quer setar/atualizar (use APENAS keys do Bloco 7 do dossie)
- `stage`: um de [novo_lead, qualificado, test_drive_agendado, proposta, perdido] — omitir se nao mudou
- `notificar_renato`: true SOMENTE quando lead virou QUENTE + handoff (ver Bloco 8.3)
- OMITA o bloco META inteiro se nao ha nada pra atualizar
- O lead NUNCA ve esse bloco (ele fica so nos logs)

### QUANDO NOTIFICAR RENATO (notificar_renato: true)
- B2C ticket >R$ 15k
- B2B com CNPJ + volume >= 5 unidades
- B2B revenda 26+ unidades (Revenda Oficial)
- Lead pediu explicitamente falar com humano
- Marcou test drive presencial
- Pediu proposta formal / contrato
- Situacao fora do escopo do dossie

### CAMPOS OBRIGATORIOS ATE FIM DA FASE L (Levantar)
sdr__nome_lead, sdr__cidade, sdr__uf, sdr__perfil, sdr__uso_pretendido,
sdr__prazo_compra, sdr__categoria_interesse. CNH apenas se scooter/triciclo.

### STAGES DISPONIVEIS (nao pule etapas)
- novo_lead → qualificado → test_drive_agendado / proposta → ganhamos / perdemos

### FORMATO DE ENTRADA
A tua "user message" vem estruturada assim:
  [CONTEXTO CRM]
  ...campos do lead...

  [HISTORICO DA CONVERSA]
  lead: ...
  bruno: ...
  (mais turnos)

  [NOVA MENSAGEM DO LEAD]
  ...texto que ele acabou de mandar...

Responda APENAS com a mensagem pro lead + (opcional) bloco META. Nada mais.
Nada de "Ola! Como posso ajudar?" — voce ja esta no meio de uma conversa real.
strc                     t         j                         st        j                  dt                yt         j	                  d      S )Nz dossie ALMA nao encontrado em %s utf-8encoding)DOSSIE_PATHexistslogerror	read_text     H/opt/mia/workspace/clientes/torq-brasil/sdr_bruno_webhook/bruno_agent.py_load_dossier      s4    		4kB  ' 22r   _DOSSIE_CACHEc                      t          dt         S )z
    System prompt completo: dossie ALMA + instrucoes operacionais.
    Passado via --append-system-prompt. O CLI aplica prompt caching automaticamente.
    z

---

)r   INSTRUCOES_OPERACIONAISr   r   r   _build_system_promptr"      s    
 _K(?'@AAr   c                R    dj                  d | D              }t        |xs d dz  S )Nr   c              3  J   K   | ]  }|j                         s|d v s|  yw))-_N)isalnum).0cs     r   	<genexpr>z _history_path.<locals>.<genexpr>   s     KAIIK1
?1Ks   ##unknownz.json)joinHISTORICO_DIR)
contact_idsafes     r   _history_pathr0      s.    77KjKKDd/i0666r   c                .   t        |       }|j                         sg S 	 t        j                  |j	                  d            }t        |t              r|S 	 g S # t        j                  t        f$ r"}t        j                  d| |       Y d }~g S d }~ww xY w)Nr   r   z&historico corrompido contact=%s err=%s)r0   r   jsonloadsr   
isinstancelistJSONDecodeErrorOSErrorr   warning)r.   pdataes       r   load_historyr<      s    j!A88:	Mzz!++w+78dD!K " I   '* M<j!LLIMs   6A B2BBc                    |t          d  }	 t        |       j                  t        j                  |dd      d       y # t
        $ r!}t        j                  d| |       Y d }~y d }~ww xY w)NF   )ensure_asciiindentr   r   z&nao salvei historico contact=%s err=%s)MAX_HISTORY_MESSAGESr0   
write_textr2   dumpsr7   r   r8   )r.   historytrimmedr;   s       r   save_historyrF      sl    ++,-GMj!,,JJwU1= 	- 	
  M<j!LLMs   2? 	A)A$$A)z&---SDR-META---\s*(\{.*?\})\s*---FIM---c                   | sdi fS i }t         j                  |       }|rM	 t        j                  |j	                  d            }t         j                  d|       j                         }||fS | j                         }||fS # t        j
                  $ r5}t        j                  d||j	                  d      dd        i }Y d}~d}~ww xY w)ze
    Separa a resposta em (texto_lead, meta_dict).
    Se nao houver bloco META, meta_dict = {}.
    r      zmeta JSON invalido: %s | raw=%sN   )

META_REGEXsearchr2   r3   groupr6   r   r8   substrip)rawmetamatchr;   textos        r   parse_responserS      s    
 2vDc"E	::ekk!n-D
 r3'--/ $; 		$; ## 	KK91ekk!nTc>RSD	s   $B C	+CC	c                F   dg}dD ]-  }| j                  |      }|s|j                  d| d|        / |rI|j                  d       |j                         D ]$  \  }}|ddg fvs|j                  d| d|        & n|j                  d       d	j                  |      S )
u@   Contexto do CRM em texto plano — vai no comeco do user prompt.z[CONTEXTO CRM])
first_name	last_namephoner.   conversation_idz- : zCampos sdr__ ja preenchidos:Nr   z&Campos sdr__: nenhum preenchido ainda.
)getappenditemsr,   )contact_metacustom_fieldslinhaskvs        r   _build_context_hintrc      s    
 FR )QMMBqcA3-() 45!'') 	-DAqr2&1#Rsm,	- 	>?99Vr   c                   | sydg}| t          d D ]\  }|j                  d      }|j                  d      xs dj                         }|r|dvr>|dk(  rd	nd
}|j                  | d|        ^ dj	                  |      S )z0Historico serializado em texto (lead: / bruno:).u6   [HISTORICO DA CONVERSA]
(vazio — primeira interacao)z[HISTORICO DA CONVERSA]Nrolecontentr   )user	assistantrg   leadbrunorY   rZ   )rA   r[   rN   r\   r,   )rD   r`   msgre   rf   prefixs         r   _build_history_blockrm      s    H'(F,,-. .wwv779%+224$&;;6>w7),-. 99Vr   c                <    t        | |       dt        |       d| S )Nz

z

[NOVA MENSAGEM DO LEAD]
)rc   rm   r^   r_   rD   mensagem_leads       r   _build_user_promptrq      s2     |];
<D(
) *$$1?	4r   c                B   t        t              j                         s+t        j                  t              st        dt               t        dddd| dt        ddd	d
g}t        j                  j                         }|j                  dt        j                  j                  d             |j                  dd       	 t        j                  ||ddt         |      }|j$                  dk7  r(t        d|j$                   d|j&                  dd       |j(                  xs dj+                         }|st        d|j&                  dd       	 t-        j.                  |      }|j3                  d      s|j3                  d      dk7  rMt        d |j3                  d       d!|j3                  d"       d#t5        |j3                  d$            dd%       |j3                  d$      xs d}|j3                  d&      xs i }	|j3                  d'      |	d(<   |j3                  d)      |	d*<   |j3                  d+      |	d,<   ||	fS # t        j"                  $ r}t        dt          d      |d}~ww xY w# t,        j0                  $ r}t        d| d|dd       |d}~ww xY w)-z
    Chama o CLI Claude Code em modo headless com output JSON.

    Retorna (texto_bruto, usage_dict).
    Levanta RuntimeError em qualquer falha.
    zCLI claude nao encontrado em z--printz--output-formatr2   z--append-system-promptz--modelz--permission-modedefaultz--disallowedToolsz,Bash,Edit,Write,Read,Task,WebFetch,WebSearchHOME~ANTHROPIC_API_KEYNT)inputcapture_outputtexttimeoutenvzCLI timeout apos sr   zCLI retornou exit=z stderr=i  r   z!CLI retornou stdout vazio stderr=rI   zCLI retornou nao-JSON: z
 | stdout=i  is_errorsubtypesuccesszCLI status error subtype=z	 api_err=api_error_statusz result=resulti,  usage
session_id_session_idduration_ms_duration_mstotal_cost_usd	_cost_usd)r   r   r   shutilwhichRuntimeErrorMODEL_IDosenvironcopy
setdefaultpath
expanduserpop
subprocessrunCLI_TIMEOUTTimeoutExpired
returncodestderrstdoutrN   r2   r3   r6   r[   r   )
system_promptuser_promptcmdr{   procr;   r   payloadraw_textr   s
             r   _call_claude_clir     s    
""$V\\*-E::,GHH 	6 -8YK	C **//
CNN6277--c23GG&
F~~
 !  1$++ds:K9NO
 	
 kkR&&(F>t{{4C?P>STUU**V$ {{:'++i"8I"E'I(>'? @{{#567 8'++h/0#69;
 	
 {{8$*HKK &BE";;|4E-#KK6E. %56E+U?A $$ F.{m1=>AEF  %aS
6$3<2BC
	s0   ?I I1 I.I))I.1JJJc                2   t         st        j                  d       di fS |rt        |      }nt	        |       }t               }t        |xs i |||      }	 t        ||      \  }}	t        j                  d|	j                  dd      |	j                  d	d      |	j                  d
d      |	j                  dd      |	j                  d      |	j                  d      |	j                  d             |st        j                  d       di fS t        |      \  }}|st        j                  d       d}|j                  d|d       |j                  d|d       t        | |       ||fS # t        $ r$}
t        j                  d|
       di fcY d}
~
S d}
~
ww xY w)zu
    Gera resposta do Bruno via CLI Claude Code (assinatura Max plan).

    Retorna (texto_pro_lead, meta_dict).
    u/   dossie ALMA vazio — impossivel gerar respostazBOi, deu um probleminha aqui do meu lado. Ja te chamo em instantes.ro   zCLI Claude falhou: %sNziclaude_cli usage input=%s cache_read=%s cache_creation=%s output=%s cost_usd=%s duration_ms=%s session=%sinput_tokensr   cache_read_input_tokenscache_creation_input_tokensoutput_tokensr   r   r   zClaude CLI retornou texto vazioz(Deixa eu conferir aqui e ja te respondo.z+Bruno so respondeu META, sem texto pro leadrg   )re   rf   rh   )r   r   r   r5   r<   r"   rq   r   	Exception	exceptioninfor[   r8   rS   r\   rF   )r.   rp   r^   r_   historico_ghlrD   r   r   rO   r   r;   rR   rP   s                r   gerar_respostar   V  s    		CDSUWWW }%z*(*M$!'R##	KX%m[A
U HH	0		.!$		+Q/		/3		/1%		+		.!		- 
 5692== %KE4AB: NNF}=>NNKE:;W%$;C  X-q1SUWWWXs   E) )	F2FFF)returnr   )r.   r   r   r   )r.   r   r   list[dict[str, str]])r.   r   rD   r   r   None)rO   r   r   tuple[str, dict[str, Any]])r^   dict[str, Any]r_   dict[str, Any] | Noner   r   )rD   r   r   r   )
r^   r   r_   r   rD   r   rp   r   r   r   )r   r   r   r   r   r   )NNN)r.   r   rp   r   r^   r   r_   r   r   zlist[dict[str, str]] | Noner   r   )0__doc__
__future__r   r2   loggingr   rer   r   pathlibr   typingr   	getLoggerr   __file__resolveparentBASE_DIRr-   mkdirrA   getenvr   intr   r   r   r   rN   r!   r   r   __annotations__r"   r0   r<   rF   compileDOTALL
IGNORECASErJ   rS   rc   rm   rq   r   r   r   r   r   <module>r      s  4 #   	 	    g&>!!#**;&   D4  0 299]$78 )"))/78 RYY|$SX(>SBS
STEJ 
EGK P3 "^s #B7

M RZZ-II
8 ( 	(
 
(
 "
 	

 	
"EV +/+/15AAA (A )	A
 /A  Ar   