
    Qgj<              	         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 ddlmZ ddlmZmZmZ  ee      j)                         j*                  Zedz  Zej1                  d	d	
        e
edz  ddd      Zej5                   ej6                  d              ej8                  ej:                        Zej5                   ej6                  d              ej>                  ej@                  eeg        ejB                  d      Z"	 ddl#m$Z$  e$d        e$ej*                  dz         ddl5m6Z6m7Z7m8Z8 ddl9m:Z:m;Z; ddl<m=Z=  ej|                  dd      j[                         Z? ej|                  dd      j[                         Z@d ZAd!ZBi ZCd"eDd#<   dFd$ZE eE        	 	 dG	 	 	 	 	 	 	 	 	 dHd%ZF eG ej|                  d&d'            ZH eG ej|                  d(d)            ZI eG ej|                  d*d+            ZJe?se"j                  d,        eeL      ZMdId-ZN e=eNe7e8.      ZOi ZPd/eDd0<    ej                         ZRdJd1ZSdd2dKd3ZTdLd4ZUeMj                  d5d6g7      d8        ZWeMj                  d9d:g7      d;        ZXeMj                  d<d6g7      d=        ZYeOj                          eLd>k(  rV e[ ej|                  d?d@            Z\ ej|                  dAdB      Z]e"j                  dCe]e\eH       eMj                  e]e\dDd	E       yy# e%$ r d e&ej*                  dz        fD ]  Z'ejP                  jS                  e'      s e*e'd      5 Z+e+D ]w  Z,e,j[                         Z.e.re.j_                  d      sde.vr+e.ja                  dd      \  Z1Z2ejf                  ji                  e1j[                         e2j[                                y 	 ddd       # 1 sw Y   xY w Y w xY w)Mu  
Sweet Angels SDR Webhook — Fesqua — porta 5056.

Recebe eventos do Linkia (GoHighLevel) quando um lead responde WhatsApp,
processa com o agente Gi (Claude) e devolve a resposta pelo Linkia.

Fluxo:
  1. Webhook chega → responde 200 imediatamente.
  2. Acumula mensagens do mesmo contato num buffer (debounce DEBOUNCE_SECONDS).
  3. Após o debounce, concatena tudo, chama Claude, aguarda TYPING_DELAY_SECONDS
     (simula digitando) e envia os blocos separados por [[BREAK]] com pausa entre eles.
    )annotationsN)RotatingFileHandler)Path)Any)FlaskjsonifyrequestlogsT)parentsexist_okzwebhook.logi  P    utf-8)maxBytesbackupCountencodingz1%(asctime)s [%(levelname)s] %(name)s: %(message)s)levelhandlerssdr_fesqua_webhook)load_dotenvz/opt/mia/.envz
linkia.envr   #=   )gerar_respostagerar_followupis_conversa_encerrada)send_whatsapp_messagemove_opportunity_stage)FollowupManagerLINKIA_TOKEN LINKIA_LOCATION_IDz$fa52b54d-511b-49cf-b27a-b8de4c5575daz$7aea5cd9-b736-417e-986e-9d62cdcfa6a5zdict[str, str]_contact_opp_mapc                    t         j                  dz  } 	 t        j                  | j	                  d            }|j                  dg       D ]3  }|j                  d      }|j                  d      }|s(|s+|t        |<   5 t        j                  dt        t                     y # t        $ r }t        j                  d|       Y d }~y d }~ww xY w)	Nzfesqua_envio_linkia.jsonr   r   sucessos
contact_idopportunity_idz&contact_opp_map carregado: %d entradasz nao carregou contact_opp_map: %s)BASE_DIRparentjsonloads	read_textgetr#   loginfolen	Exceptionwarning)pathdatascidoides         :/opt/mia/workspace/clientes/sweet_angels/sdr_fesqua/app.py_load_contact_opp_mapr:   ]   s    ??77D	;zz$..'.:;*b) 	,A%%%C%%()Cs(+ %		,
 	93?O;PQ ;6::;s$   AB& 4B& 7.B& &	C/C

Cc           
        t         j                  |       }|st        j                  d| |       yt        st        j                  d|        y	 t        ||t        |      \  }}}t        j                  d| ||||||       |s%t        j                  d| |t        |      dd        yy# t        $ r#}t        j                  d| |||       Y d}~yd}~ww xY w)	z<Wrapper seguro pra mover card: nao quebra o fluxo se falhar.z>skip move stage contact=%s (sem opportunity mapeada) motivo=%sNz1skip move stage contact=%s (LINKIA_TOKEN ausente))r'   stage_idtokenstatuszJmove stage contact=%s opp=%s stage=%s status=%s motivo=%s -> ok=%s http=%sz+move stage falhou contact=%s opp=%s body=%s,  z1erro movendo card contact=%s opp=%s motivo=%s: %s)
r#   r-   r.   r/   r    r2   r   strr1   	exception)	r&   r<   r>   motivoopp_idokstatus_codebodyr8   s	            r9   _move_stage_saferG   n   s     !!*-FL	
 	GT
 6!	!
K 	X&&"k	
 KK=FCIdsO 
  
?	
 	

s   AB) )	C2CCSDR_DEBOUNCE_SECONDSz5.0SDR_TYPING_DELAY_SECONDSz3.0SDR_BLOCK_DELAY_SECONDSz4.0uD   LINKIA_TOKEN nao configurado — respostas nao serao enviadas ao GHLc                (    t        | |t        |      S )Nr&   messager=   conversation_id)r   r    )r&   rM   rN   s      r9   _followup_sendrO      s     '	     )send_fngenerate_followup_fnis_encerrada_fnzdict[str, dict]_pendingc                |   t         5  t        j                  | d      }ddd       sy|d   }|d   }|d   }dj                  |      }t        j                  d| t        |      |dd        	 t        | ||      }t        st        j                  d
       y|j                         }d|v rt        | t        d       nd|v rt        | ddd       |j                  d      D 	cg c]#  }	|	j!                         s|	j!                         % }
}	|
st        j#                  d|        yt%        j&                  t(               d}t+        |
      D ]h  \  }}|dkD  rt%        j&                  t,               t/        | |t        |      \  }}}|rd}t        j                  d|dz   t        |
      ||| |dd        j |rt0        j3                  | |       |j                         }d|v sd|v rt0        j5                  |        yy# 1 sw Y   xY w# t        $ r }t        j                  d	|       Y d}~yd}~ww xY wc c}	w )uF   Chamado após o debounce. Concatena msgs, chama Claude e envia blocos.NmessagesrN   contact_meta
z+processando contact=%s msgs=%d combinado=%rr?   )r&   mensagem_leadrW   zerro gerando resposta: %su,   LINKIA_TOKEN ausente — nao enviando ao GHLz[[qualificado]]qualificado)rB   z@sweetangelsgastronomialostencerramento_sem_qualificar)r>   rB   z	[[BREAK]]z$resposta vazia apos split contact=%sFr   rL   Tz9bloco %d/%d enviado ok=%s status=%s contact=%s preview=%rr   P   )_pending_lockrT   popjoinr.   r/   r0   r   r1   rA   r    errorlowerrG   STAGE_QUALIFICADOsplitstripr2   timesleepTYPING_DELAY_SECONDS	enumerateBLOCK_DELAY_SECONDSr   followupregistrar_mensagem_enviadamarcar_encerrado)r&   r4   rV   rN   rW   mensagem_combinadarespostar8   resposta_lower_prebblocosalgum_okiblocorD   r>   rF   resposta_lowers                     r9   _fire_contactrw      s,   	 .||J-.JH,-O'L8,HH5CM#5ds#;
!!,%
 		@A ")..%6}M	"&8	8T&A^_ "*!<JA	aggiJFJ:JG 	JJ#$Hf% 
5q5JJ*+0!+	
FD HGE3v;FJcr
	

" ++JH ^^%N N26G>6Y!!*- 7ZK. .*  115" Ks/   H .H %H9;H9 H
	H6H11H6defaultc                   |D ]e  }|sd|v rF| }d}|j                  d      D ]  }t        |t              r
||v r||   }d} n |sD|d dg fvsL|c S || v sU| |   d dg fvs`| |   c S  |S )N.TFr!   )rd   
isinstancedict)dry   keyskcurrD   parts          r9   
_get_firstr   
  s     !8CB c4(TS[d)CB c$B/
!V!T2rN2Q4K  NrP   c           	         t        | dddd      }t        | ddd      }t        | dd	d
ddd      }t        | ddd      }t        | ddd      }t        | dd      }t        | dd      }t        | ddd      }||||||||dS )N	contactIdr&   z
contact.ididconversationIdrN   zconversation.idrF   rM   messageBodytextzmessage.bodyzlastMessage.body	firstName
first_namezcontact.firstNamelastName	last_namezcontact.lastNamephonezcontact.phone	directioninboundrx   typemessageTypeWhatsApp)r&   rN   mensagemr   r   r   r   r   )r   )	payloadr&   rN   r   r   r   r   r   msg_types	            r9   extrair_payloadr     s    \<J !+->O 	=&*H
 G[,@STJ7J=OPIw9E7KCI'6=*MH !* 	 	rP   z/healthGET)methodsc                     t        dddt        t              t        t        t        j
                  xr t        j
                  j                               t        t        j                        d      S )NTsweet_angels_sdr_fesquai  )rD   serviceportlinkia_token_ok
debounce_sfollowup_loopfollowup_tracked)	r   boolr    DEBOUNCE_SECONDSrk   _threadis_aliver0   _state rP   r9   healthr   B  sV    ,-&h..N83C3C3L3L3NO0  rP   z/webhook/fesquaPOSTc            	        	 t        j                  dd      xs i } t        j                  dt        j                  | d	      d d
        t        |       }|d   s>t        j	                  dt        j                  | d	      d d        t        ddd      dfS |d   s%t        j                  d       t        ddd      dfS t        |d         j                         dk(  r%t        j                  d       t        ddd      dfS t        |d         }|d   rt        |d         nd }t        |d         j                         }|j                  d      |j                  d      |j                  d      ||d}t        j                  d||j                  d      |d d        t        j!                  |       |rt        j#                  ||       t$        j                  |      r2t'        j(                  t*        |t,        d dfd      j/                          t0        5  |t2        v rot2        |   }|j                  d      r|d   j5                          |d   j7                  |       ||d<   ||d <   t        j                  d!|t9        |d                n|gd ||d"t2        |<   t'        j:                  t<        t>        |f#      }d|_         |t2        |   d<   |j/                          d d d        t        ddt<        d$      dfS # t        $ rB}t        j	                  d|t         j
                  d d        t        ddd      dfcY d }~S d }~ww xY w# 1 sw Y   lxY w)%NTF)forcesilentzpayload nao-JSON: %s | raw=%si  invalid_json)rD   ra   zwebhook recebido: %s)ensure_asciii   r&   z#webhook sem contact_id | payload=%smissing_contact_id   r   u'   webhook sem mensagem util — ignorando
no_message)rD   skippedr   outboundu,   mensagem outbound — ignorando (evita loop)rN   r   r   r   )r   r   r   r&   rN   z"recebido contact=%s nome=%s msg=%rr   )targetargsdaemontimerrV   rW   z'debounce reset contact=%s total_msgs=%d)rV   r   rN   rW   )r   )rD   queuedr   )!r	   get_jsonr1   r.   r2   r4   r   r/   r*   dumpsr   r@   rb   re   r-   rk   registrar_resposta_recebidaset_conversation_idr#   	threadingThreadrG   STAGE_LEAD_RESPONDEUstartr^   rT   cancelappendr0   Timerr   rw   r   )	r   r8   dadosr&   rN   rY   rW   existingr   s	            r9   webhook_fesquar   O  s2   D""e<B
 HH#TZZe%LTc%RSG$E 9JJwU;DSA	Ce.BCDcII:;d|<=sBB
5$$&*4?@dz:;S@@U<()J7<=N7Oc% 123UYOj)*002Mii-YY{+7# *L HH,EIIl+]4C-@ ((4$$ZA J'#2D)D	
 %' 
 !
+H||G$!((*Z ''6*9H&''3H^$HH>
CPXYcPdLef +O#2 ,	$HZ   0-zmT(-W%), $$>NOPRUUUU  D3QTc8JKen=>CCDh s*   L CM	M7MMMM/c                 $    t        ddddd      S )Nu#   Sweet Angels SDR Webhook — FesquazGET /healthzPOST /webhook/fesqua)r   webhook)r   	endpoints)r   r   rP   r9   indexr     s"    8#-
  rP   __main__PORT5056HOSTz0.0.0.0z'subindo Flask em %s:%s (debounce=%.1fs)F)hostr   debugthreaded)returnNone)Nr!   )
r&   r@   r<   
str | Noner>   r   rB   r@   r   r   )r&   r@   rM   r@   rN   r   )r&   r@   r   r   )r~   dict[str, Any]r   r@   ry   r   r   r   )r   r   r   r   )`__doc__
__future__r   r*   loggingossysr   rf   logging.handlersr   pathlibr   typingr   flaskr   r   r	   __file__resolver)   r(   LOG_DIRmkdirlog_handlersetFormatter	FormatterStreamHandlerstdoutstream_handlerbasicConfigINFO	getLoggerr.   dotenvr   ImportErrorr@   envfiler3   existsopenfrawre   line
startswithrd   r   venviron
setdefaultgi_agentr   r   r   
ghl_clientr   r   followup_managerr   getenvr    r"   r   rc   r#   __annotations__r:   rG   floatr   rh   rj   r2   __name__apprO   rk   rT   Lockr^   rw   r   r   router   r   r   r   intr   r   r/   runr   rP   r9   <module>r     s7   #   	 
   0   ) )>!!#**
V
 dT *
 "m	   GIJ '&&szz2   GIJ   ',,+~1N Og,-
@" ,./ 
 E ,ryy,224RYY3R8>>@ 
 > : 
 $& . %;   	%
%
%
 %
 	%

 
%
P #95AB YRYY'A5IJ IBII&?GH KKVWHo ') / 	 H.^ >B (H 9ug&	 '	 vh/NV 0NVb 3  !  	 zyryy()D299VY'DHH6dDTUGGDG=	 u  	@#S<)G%HI @77>>'"g0 @A @C99;D4??3#73d? ::c1-DAqJJ))!'')QWWY?@@ @ @@	@s1   #L :O,

O,A=O	O,O$ O,+O,