
    Vj|g                         d Z ddlZddlZddlZddlZddlmZ  ej                  e      Z	dZ
dZdZdZdZdZd	Zd
ZdZd
ZdddddZdhZdZdZdefdZddefdZdefdZddZ	 ddZ G d d      Z y)uq  
AdsDecisionEngine — nível 1 da escada de autonomia (docs/ads-decision-engine.md).

Gera recomendações persistidas (ads_recommendations) a partir dos dados reais
de ml_ads_item_daily, com consciência de:
  - ciclo de vida do anúncio (lançamento tolera ACOS ≈ margem);
  - significância estatística (não decide com amostra pequena);
  - estoque (não recomenda escalar item com risco de ruptura).

Execução no ML SOMENTE via execute_recommendation, que exige
ADS_WRITE_ENABLED=true (env; default false — regra crítica do projeto).
Feedback loop: measure_outcomes compara 7d antes vs 7d depois da execução.
    Nfunc      <   Z   x   g      >@      g      ?ffffff?g      ?)
lancamentocrescimento
maturidade
liquidacaopausarg       @g333333?returnc           	      d   ddl m} ddlm}m} ddlm} ddlm ddl	m
} dddt        dd	d
}	 t        fd| j                  |      j                  |j                  dk(        j!                         D              }t"        j"                  j%                         t#        j&                  d      z
  }t)        | j                  |j                  |j*                  |j,                  z              j/                  ||j0                  |j0                  k(        j                  |j2                  |k\  |j4                  j7                  g d            j9                         xs d      }		 | j                  |      j;                  d      j=                         }
|
r$|
j>                  rt)        |
j>                        |d<   tC        |d      |d<   tC        |	d      |d<   |dkD  r|	dkD  rtC        ||	z  dz  d      |d<   d|d<   	 ddl"m#}  ||       jI                         }tC        t)        |jK                  d      xs d      d      |d<   |S # t@        $ r Y w xY w# t@        $ r#}tL        jO                  d|        Y d}~|S d}~ww xY w# t@        $ r#}tL        jO                  d|        Y d}~|S d}~ww xY w)u$  
    Fotografia financeira da empresa usada nas decisões de Ads:
      fixed_burden_pct — quanto da receita os custos fixos consomem hoje
      target_net_pct   — meta de margem líquida pós-tudo (crescimento saudável)
      otb_value        — verba disponível para recompra (guard: não escalar Ads
                         de item que não conseguimos reabastecer)
      complete=False   — sem contas fixas cadastradas ou sem receita: o motor
                         cai no modo antigo (bandas sobre a margem de contribuição).
    r   )	FixedCost)MlOrderMlOrderItem)SystemConfig)is_debt_servicer           NF)fixed_monthlyrevenue_30dfixed_burden_pcttarget_net_pct	otb_valuecompletec              3   t   K   | ]/  } |j                         st        |j                  xs d        1 yw)r   N)categoryfloatamount).0costr   s     N/var/www/hypershopcomercio.com.br/hyper-ai/app/services/ads_decision_engine.py	<genexpr>z&get_finance_context.<locals>.<genexpr>O   s3      
"4==1 $++"#
s   58Tr   days)paidshipped	deliveredads_target_net_margin)keyr      r   r   d   r   r   )FinancialServicer   u5   OTB indisponível para contexto de Ads (não-fatal): u0   Contexto financeiro indisponível (não-fatal): )(app.models.financialr   app.models.ml_orderr   r   app.models.system_configr   %app.services.financial_classificationr   
sqlalchemyr   DEFAULT_TARGET_NET_MARGINsumqueryfilteractivealldatetimeutcnow	timedeltar"   quantity
unit_pricejoinml_order_iddate_createdstatusin_scalar	filter_byfirstvalue	Exceptionroundapp.services.financial_servicer1   calculate_otbgetloggerwarning)dbr   r   r   r   sqlfuncctxr   cutoffr   scr1   otber   s                 @r&   get_finance_contextrY   6   s    /85E*  3C#O 
+2293C3Ct3KLPPR
 
 ""))+h.@.@b.IIBHHKK,,{/E/EEF

$w++{/F/FF
G  F*NN?@I
 &(
 	,'116M1NTTVBbhh(-bhh$%  %]A6O";2M1q&+MK,G#,Mq&QC"#"C
O	XG"2&446C$U377;+?+D1%EqIC
 J#  		  	XNNRSTRUVWW J		X OI!MNNJOsc   D3J $AI 9AJ <AI 	IJ IJ 	J I;5J ;J  J 	J/J**J/stagec                     |r|j                  d      r| y|d   }|d   }|dv r| |z
  }n| |z
  |z
  }t        t        d|      d      S )uQ  
    Teto de ACOS que ainda entrega a meta de crescimento:
      teto = margem de contribuição - custos fixos % - meta líquida %
    Fases lançamento/liquidação abrem mão da meta líquida (investem o lucro
    em ranking/giro), mas nunca do break-even pós-fixos.
    Retorna None quando não há dados financeiros completos.
    r   Nr   r   )r   r   r   r/   )rO   rL   max)contribution_pctfinance_ctxrZ   burdentargetceilings         r&   max_acos_forrb   u   sg     kooj9=M=U+,F)*F,,"V+"V+f4S'"A&&    c                  P    t        j                  dd      j                         dk(  S )NADS_WRITE_ENABLEDfalsetrue)osgetenvlower rc   r&   ads_write_enabledrl      s"    99('288:fDDrc   c                     | dkD  r|dk  ry|)|'|r%||k\  ry|	|||z
  k\  ry||k\  ry||t         z  k  ryy| |dkD  r|||k\  ry||dz  k\  ry||dz  k  ryy|y|d	k\  ry|d
k  ryy)u   
    margin_percent = margem de CONTRIBUIÇÃO (pós comissão/frete/imposto/custo,
    ANTES de Ads e custos fixos). Com dados financeiros (max_acos), as bandas
    são ancoradas na margem LÍQUIDA real; sem eles, cai nas bandas antigas.
    r   	queimandoprejuizoatencaoescalarsaudavelr   g?   
   )SCALE_HEADROOM_FACTOR)spendads_revenueacosmargin_percentmax_acosr   s         r&   	_classifyr{      s     qy[A%  0^>!'DNEU4U,U88333 !nq&8T=M>!>C''>C''|rzrzrc   c
                    |dkD  r||z  dz  nd}
|duxr |du}d}|r||t        ||z
  |z
  d      }| dk(  rddd	|d
d| dd|
d
ddS | dk(  rc|r||dz  z  |z
  n| }|||xs dk  rd|dd|dd|dd|dd	}nd|dd|dd|d
d}dd|dt        |      t        |d      z  dz  d
ddS | dk(  r4|r|d|dd |dd!|dd"|	dd#	}n|d|dd$|dd%}nd|dd&}d'd(|d)dS | d*k(  rN|r|d|dd+|dd,|dd-|dd.|	dd/}n#|r|||z
  nd}|d|dd0|dd1|dd2nd|dd3}d4d5|d6|d
d| d7dS d8d9|rd:ddS d;ddS )<uP   Sugestão read-only por item (nível 0 — usada pela página Ads Intelligence).r   r   r   Nr/   rn   r   zPausar Ads deste itemz	Gastou R$.2fz em u.    dia(s) sem NENHUMA venda via Ads no período.zEconomia estimada de R$u   /mês)codelabelreasonimpactro   g      Y@u   Margem líquida REAL .1fu   % negativa: contribuição u   % − ACOS u   % − custos fixos u-   % — a venda existe mas não paga as contas.zACOS z% >= margem uA   % — cada venda via Ads dá prejuízo (resultado no período: R$z).zPausar Product Ads deste itemzEstancar perda de ~R$   rp   z% acima do teto de u   % — sobra líquida de z%, abaixo da meta de u   % para crescer com saúde.z!% consome mais de 70% da margem (u"   %) — lucro via Ads quase zerado.uM   % elevado e margem do produto desconhecida — cadastre o custo para avaliar.	monitorarz"Monitorar de perto / reduzir lanceu1   Risco de virar prejuízo com pequena piora de CPCrq   z% com teto de u   % — líquida real de u    % já descontando custos fixos (z%) e acima da meta (z%).z% bem abaixo da margem u   % — folga de zpp para escalar.z % baixo com vendas consistentes.aumentarzAumentar investimentozReceita via Ads atual R$u   d com espaço para crescermanteru   Manter como estáu   Líquida real dentro da meta.u#   ACOS confortável dentro da margem.)rL   absr\   )classificationrv   rw   rx   ry   days_activeperiod_daysrz   r   r   monthly_spendfinance_awarenet_pctlossr   headrooms                   r&   _suggest_actionr      s"    3>/U[(2-sMD(I-=T-IM G38H-0@@!D$,!%D=kl/c/B%H	
 	
 #AO{nu45=V[U[4>+>Q#?-gc]:UVdehUi j""&s+>?OPS>T  UBCF T#Jl>#2F  GH  IM  NQ  HR  RT  UF
 4-c$i#k1:M.MPR.RSV-WW\]
 	
 "W0d3Z':8C.H`ahil`m n++9#*>>XZF'T#J&GWZG[[}~FT#J&stF9I	
 	
 "W0d3Z~hs^CZ[bcfZg h77G6LL`aops`ttwyF 4BdFV-]aH!- d3Z'>~c>RRabjknaoo  A5:4*Dd3e  ,0S0Ak]Rlm	
 	
 $6C2	  Jo	 rc   c                   \    e Zd Zd ZdedefdZ	 ddZddedefdZ	de
fd	Zdd
edefdZy)AdsDecisionEnginec                     || _         y )N)rR   )selfrR   s     r&   __init__zAdsDecisionEngine.__init__   s	    rc   units_ads_alltimer   c                    t         j                   j                         }d }|Q|j                  E|j                  }t        |d      r|j                  |j                  d       }||z
  j                  }|
|t        k  ry|t        k  r|	|t        k  ryd }|!|j                  t        |j                        }|
|t        k\  ry|
|t        k  ryy)Ntzinfo)r   r   r   r   r   )r=   r>   
start_timehasattrr   replacer)   LAUNCH_MAX_AGE_DAYSLAUNCH_MAX_UNITSLAUNCH_UNITS_MAX_AGE_DAYSdays_of_stockr"   LIQUIDATION_MIN_DAYS_OF_STOCKGROWTH_MAX_AGE_DAYS)r   adr   nowage_daysstartr   s          r&   infer_lifecyclez!AdsDecisionEngine.infer_lifecycle  s    &&(>bmm7MMEuh'ELL,DT2e))HH0C$C//X5EUnIn>b..:!""2"23M$:W)WH0C$C rc   Nc                    |sy|xs i }|j                  d      r|j                  d      nd}|j                  d      r|j                  d      nd}|dk(  r|dk(  ryt        |||||||	|||
      S |dk(  r'|dk(  r|r|	||d	z  k  ryt        |||||||	|||
      S |d
k(  rc|

|
t        k  ry|j                  d      }||dk  rt        j	                  d       yt        |||||||	|||
      }|dk(  r|dxx   dz  cc<   |S y)u   
        Converte o diagnóstico em UMA recomendação acionável (ou None), aplicando
        os guardrails de fase, significância, estoque e caixa de recompra (OTB).
        Nr   r   r   rn   r   )rz   r   r   ro   g333333?rq   r   r   z5Escalar bloqueado por OTB<=0 (sem verba de recompra).r   r   u7    Item em liquidação (estoque alto) — acelerar giro.)rO   r   STOCK_GUARD_MIN_DAYSrP   info)r   r   rZ   significantrv   rw   rx   ry   r   r   r   rz   r^   fcr_   r`   rW   actions                     r&   _decide_actionz AdsDecisionEngine._decide_action  sc    B/1vvj/A*+t-/VVJ-?()T[($">5+t#1;,4v^df f Z'$D<LQUXfilXlQl">5+t#1;,4v^df f Y&(]=Q-Q &&%C3!8SU$^UK%3[+.6`fhF $x $]] M rc   r)   c                    ddl m} ddlm} ddlm} t        j                  j                         }|t        j                  |      z
  }| j                  j                  |j                  t        j                  |j                        j!                  d      t        j                  |j"                        j!                  d      t        j                  |j$                        j!                  d      t        j&                  |j(                        j!                  d	            j+                  |j                  |k\        j-                  |j                        j/                         }|sg ddd
dS |D cg c]  }|j                   }	}| j                  j                  |      j+                  |j(                  j1                  |	            j/                         D 
ci c]  }
|
j(                  |
 }}
t3        | j                  j                  |j                  t        j                  |j4                              j+                  |j                  j1                  |	            j-                  |j                        j/                               }t7        | j                        }|d   r,t8        j;                  d|d   dd|d   dd|d   dd       nt8        j;                  d       | j                  j                  |      j+                  |j<                  dk(        j/                         }|D ci c]  }|j                  |j>                  f| }}t        j                  jA                         t        j                  tB              z
  }| j                  j                  |      j+                  |j<                  j1                  g d      |jD                  |k\        j/                         }|D ch c]  }|j                  |j>                  f }}g }tG               }|D ]T  }tI        |jJ                  xs d      }tI        |jL                  xs d      }tO        |j$                  xs d      }tO        |jP                  xs d      }|dk  r|dk  rs|jS                  |j                        }|r!|jT                  tI        |jT                        nd}|r!|jV                  tI        |jV                        nd}|dkD  rtY        ||z  dz  d      nd}| j[                  |tO        |jS                  |j                        xs d            }t]        |||      } |d   r|d   nd}!t_        ||||| |!      }"|t`        k\  xs	 |tb        k\  }#| je                  |"||#|||||||| |      }$|$s|j                  |$d   f}%|%|v r|jg                  |%       |%|v r ||j                  |$d   |"||$d   |$jS                  d       |||||||||r|jh                  nd|r|jj                  nd| |!|d   r|d   nd|jS                  d!      d"d#      }&| j                  jg                  |&       |jm                  |&       W d}'t        j                  jA                         t        j                  tn              z
  }(|D ]v  }|jp                  duxr |jp                  |(k  })|j                  |j>                  f|v}*|)s|*sAd$|_        t        j                  jA                         |_"        d%|_9        |'d&z  }'x | j                  ju                          t8        j;                  d'tw        |       d(|' d)       |D +cg c]O  }+|+j(                  |+j                  |+j>                  |+jx                  xs i jS                  d*      |+jz                  d+Q c}+|'tw        |      d,dS c c}w c c}
w c c}w c c}w c c}+w )-u   
        Roda o diagnóstico e materializa recomendações em ads_recommendations.
        Dedup: não recria pendente igual (item+ação). Expira pendentes obsoletas
        (TTL ou diagnóstico mudou). Retorna resumo para notificação.
        r   MlAdsItemDailyAdsRecommendation)Adr(   rv   rw   clicksr   T)createdexpiredskippedno_datar   z.Motor de Ads com contexto financeiro: fixos R$r   r}   u   /mês = r   r   u    % da receita 30d; meta líquida r   z%.ua   Motor de Ads SEM contexto financeiro (cadastre custos fixos) — usando bandas de contribuição.pending)executedacceptedrejectedfailedNr0   r/   )rz   r   )rz   r^   r~   r   r   r   )r   rv   rw   r   rx   ry   r   r   titleskurz   r   r   r   )item_idaction_coder   lifecycle_stager   r   metrics_snapshotrE   r   autor   zAdsDecisionEngine: u    recomendações criadas, z expiradas.r   )idr   r   r   r   F)>app.models.ml_ads_item_dailyr   app.models.ads_recommendationr   app.models.adr   r=   datetodayr?   rR   r9   r   r   r8   r%   r   revenuer   countr   r:   group_byr<   rF   dictunits_quantityrY   rP   r   rE   r   r>   DECISION_COOLDOWN_DAYS
decided_atsetr"   rv   rw   intr   rO   ry   r   rL   r   rb   r{   MIN_CLICKS_SIGNIFICANCEMIN_SPEND_SIGNIFICANCEr   addr   r   appendRECOMMENDATION_TTL_DAYS
created_at
decided_bycommitlenr   r   ),r   r)   r   r   r   r   
start_dateaggritem_idsaads_mapunits_alltimer^   r   ppending_by_keycooldown_cutoffrecent_decideddcooldown_keysr   current_keysrv   rw   r   r   r   ry   r   rx   rZ   m_acosr_   r   r   r   r.   recr   
ttl_cutoff	stale_ttlno_longer_validcs,                                               r&   generate_recommendationsz*AdsDecisionEngine.generate_recommendationsL  s    	@C$##%X//T::
ggmm""HH^(()//8HH^++,22=AHH^**+11(;JJ~(()//>
 &:-

(>))
*335 	 !aA$OO'*+!AII++$(GGMM"$5$<$<RUUYYx=P$Q$U$U$WXq1447XX TWW]]""DHH^-J-J$K

&''++H5
6xx@V@V7WX[X[X]_ *$''2z"KKHUdIefiHj k();<SAAabmn~b  AD  bE  EGH I KK{|''-- 1299$$	1

#% 	 BIIA199amm4a7II
 #++224x7I7IOe7ff'89@@$$(()WX((O;
 #% 	 >LL!))Q]]3LLu 6	 A!'',Q'E 23KQ'Famm0q1KzkQ.QYY'B:<ARARA^U2#4#45eiN8:r?O?O?[E""2"23bfM:E/5,s2A6tD((S1B1B1991M1RQR-STE!.+uEF8CJ8O[!34UYF&uk406QN $;;^uH^?^K((U)4dN)4dM28k ) SF 99fVn-Cm#S!n$#		"6N- %h'zz(+#'%$dn#.)+RXXbffPT &(.GRS]G^k2B&Cdh!,!=	" !#C& GGKKNN3m6	 r &&--/(2D2DJa2bb
 	AD0NQ\\J5NI yy!--8LOO$'00779%1	 	)#g,7QRYQZZefg 	   ddqyy,,277@((  
 >*	
 		
O ,X" J MX s   3__0 _0_$0A_)c                    ddl m} |j                  t        vrdd|j                   dfS t	               sy || j
                        }|j                  |j                  d      }|j                  d      rut        j                  j                         |_        t        j                  |d	      d
d |_        d|_        | j
                  j!                          dd|j                   dfS y)u.  
        Executa uma recomendação ACEITA. Retorna (executed: bool, message: str).

        NOTA (2026-07-09): o MLB não tem endpoint documentado de escrita para
        status de Product Ads. Toda ação é encaminhada para execução MANUAL pelo
        usuário (Plano B — WhatsApp orienta). A recomendação aceita fica com
        status 'accepted' (NÃO 'failed', para não poluir o feedback loop). O
        flag ADS_WRITE_ENABLED e o caminho de escrita ficam prontos para o dia
        em que o ML publicar/autorizar um endpoint oficial.
        r   )MeliApiServiceFu   Ação 'u3   ' — execute manualmente no painel do Mercado Ads.)Fuf   Recomendação aceita. Execute a ação no painel do Mercado Ads (o WhatsApp traz o link e o código).pausedok)ensure_asciiNi  r   Tu	   Anúncio z pausado no Product Ads.)app.services.meli_apir   r   EXECUTABLE_ACTIONSrl   rR   update_product_ad_statusr   rO   r=   r>   executed_atjsondumpsexecution_resultrE   r   )r   r   r   meliresults        r&   execute_recommendationz(AdsDecisionEngine.execute_recommendation  s     	9??"44HS__$55hiii " C dgg&..s{{HE::d&//668CO#'::f5#I%4#PC #CJGGNN9S[[M1IJJJ rc   window_daysc                 d    ddl m ddlm} t        j                  j                         t	        j                  |      z
  } j                  j                  |      j                  |j                  dk(  |j                  j                  d      |j                  |k        j                         }d}|D ]  j                  j                         } fd} ||t	        j                  |      z
  |      } |||t	        j                  |      z         }	t!        |d   |	d   z
  d	      }
t!        |d
   |	d
   z
  d	      }|||	|
|d_        t        j                  j                         _        |dz  } |r3 j                  j%                          t&        j)                  d| d       |S )u   
        Para recomendações executadas há >= window_days sem outcome medido,
        compara gasto/receita de Ads na janela ANTES vs DEPOIS da execução.
        É o que permite calibrar os thresholds com dados próprios.
        r   r   r   r(   r   Nc                    j                   j                  t        j                  j                        t        j                  j
                              j                  j                  j                  k(  j                  | k\  j                  |k        j                         }t        t        |d   xs d      d      t        t        |d   xs d      d      dS )Nr   r/   r   )rv   rw   )rR   r9   r   r8   r%   r   r:   r   r   rI   rL   r"   )d_fromd_torowr   r   r   s      r&   
window_sumz6AdsDecisionEngine.measure_outcomes.<locals>.window_sum  s    ggmmHH^001488N<R<R3S&"**ckk9"''61"''$. %'  "'uSV[q'91!=eTYZ]^_Z`ZedeTfhiNjkkrc   rv   r/   rw   )r  beforeafterspend_savedads_revenue_lostr   z'AdsDecisionEngine: outcome medido para u    recomendações.)r   r   r   r   r=   r>   r?   rR   r9   r:   rE   outcome_measured_atis_r   r<   r   rL   outcomer   rP   r   )r   r  r   rU   recsmeasured	exec_dater  r  r	  r
  revenue_lostr   r   s   `           @@r&   measure_outcomesz"AdsDecisionEngine.measure_outcomes  s    	@C""))+h.@.@k.RRww}}./66$$
21155d;))V3
 #%	 	  	C,,.Il  	H,>,>K,P PR[\Fy)h6H6Hk6Z*Z[Ew%. @!DK !6}9M!MqQL* *$0CK '/&7&7&>&>&@C#MH5	8 GGNNKKA(K\]^rc   NN)r   )r   )__name__
__module____qualname__r   r   strr   r   r   r   tupler   r  rk   rc   r&   r   r      s^    
S S 8 37/bJ
S J
$ J
\ U  H0C 0 0rc   r   )r   r  )NNN)!__doc__rh   r   loggingr=   r6   r   	getLoggerr  rP   r   r   r   r   r   r   r   r   r   r   ACOS_TARGET_BY_STAGEr   r7   ru   r   rY   r  rb   boolrl   r{   r   r   rk   rc   r&   <module>r     s    
    			8	$      #       	  Z     <t <~'s '&E4 E%R JNCLt trc   