Este é um problema real e recorrente em e-commerce e fintech: o motor antifraude é calibrado para reduzir chargeback e perdas. No começo, melhora os números. Com o tempo, começa a reprovar ou segurar pedidos legítimos de forma concentrada em certos CEPs e padrões de compra. A operação vê “fraude cair” na média — e, sem perceber, cria dano por coorte: um subconjunto de clientes paga com mais fricção, mais recusas e mais tempo de espera.
Aqui, Code Map é um diagnóstico: mapear onde o atrito e os falsos positivos se concentram, quem paga por isso e quais mecanismos estão produzindo a concentração.
AS-IS — decisão binária, pouca evidência, nenhum freio de coorte
No AS-IS, o antifraude costuma virar um classificador com saída binária (aprova/nega) ou ternária (aprova/revisa/nega), otimizado por taxa global. O código “funciona”, mas não carrega governança: não deixa recibo contestável, não mede impacto por coorte, e trata “nega” como ação barata (quando, na prática, é quase irreversível).
# AS-IS (exemplo simplificado): decisão binária por score.# Problemas típicos:# - features de localização entram como proxies fortes (CEP/geo)# - decisão final não deixa recibo auditável# - não mede falsos positivos por coorte# - "deny" é tratado como reversível, mas é perda real (abandono)from dataclasses import dataclassfrom typing import Dict, Any@dataclassclass Order:order_id: stramount: floatis_first_purchase: boolpayment_method: strip_country: strdelivery_zip: strdevice_risk: floatchargeback_history_score: float # do usuário ou do cartão (ex.: 0..1)def model_score(features: Dict[str, Any]) -> float:# Placeholder: modelo treinado para risco (0..1)# (em AS-IS, proxies como ZIP/geo podem dominar silenciosamente)base = 0.15base += 0.35 * features["device_risk"]base += 0.30 * features["chargeback_history_score"]base += 0.10 if features["is_first_purchase"] else 0.0base += 0.10 if features["payment_method"] in {"prepaid", "virtual_card"} else 0.0base += 0.20 if features["delivery_zip"].startswith(("0", "1")) else 0.0 # proxy bruto (exemplo)return min(1.0, base)def fraud_decision(order: Order) -> Dict[str, Any]:features = {"amount": order.amount,"is_first_purchase": order.is_first_purchase,"payment_method": order.payment_method,"ip_country": order.ip_country,"delivery_zip": order.delivery_zip,"device_risk": order.device_risk,"chargeback_history_score": order.chargeback_history_score,}score = model_score(features)# Regra simples: acima do limiar, nega.decision = "deny" if score >= 0.65 else "approve"# Quase nenhum recibo: apenas um score agregado.return {"order_id": order.order_id, "decision": decision, "risk_score": score}
Depois de algum tempo, esse desenho costuma produzir exatamente o que o Code Map detecta: melhora agregada com dano concentrado. O time sente isso como “alguns bairros dão problema”, “clientes novos reclamam mais”, “cai conversão em certas regiões”. Mas sem instrumentação por coorte e sem recibos, a operação não consegue provar se está reduzindo fraude ou apenas empurrando custo para um segmento específico.
TO-BE — decisão governável: medir coortes, degradar com baixo dano, e registrar recibos
No TO-BE, o Code Map recomenda transformar antifraude em um sistema de decisão com quatro travas: observabilidade por coorte, saídas de baixo dano quando a confiança cai, limites sobre proxies e recibos contestáveis. O objetivo deixa de ser “negar melhor” e passa a ser “controlar risco sem concentrar dano”.
# TO-BE (exemplo simplificado): decisão com governança no runtime.# Melhorias:# - mede e registra coorte (para diagnóstico CM)# - troca "deny" por degradação controlada (step-up) quando possível# - impõe limite: proxies (ex.: ZIP) não podem ser motivo único para decisão irreversível# - gera recibo replayável (evidence + reasons + policy_version)from dataclasses import dataclassfrom typing import Dict, Any, List, Tupleimport timeimport uuid@dataclassclass Order:order_id: stramount: floatis_first_purchase: boolpayment_method: strip_country: strdelivery_zip: strdevice_risk: floatchargeback_history_score: floataccount_age_days: intdelivery_confirmable: bool # ex.: endereço validado / ponto de retirada disponívelPOLICY_VERSION = "fraud_policy_v3.2"def cohort_key(order: Order) -> str:# Exemplo: coorte operacional (não “atributo sensível”; foco em onde o atrito se concentra)zip_prefix = order.delivery_zip[:2] if order.delivery_zip else "??"return f"zip:{zip_prefix}|pm:{order.payment_method}|first:{int(order.is_first_purchase)}"def model_score(features: Dict[str, Any]) -> float:# Placeholder: modelo de risco (0..1)base = 0.12base += 0.35 * features["device_risk"]base += 0.30 * features["chargeback_history_score"]base += 0.08 if features["is_first_purchase"] else 0.0base += 0.08 if features["payment_method"] in {"prepaid", "virtual_card"} else 0.0base += 0.05 if features["account_age_days"] < 7 else 0.0# ZIP entra, mas com peso menor e nunca como motivo único para decisão irreversível:base += 0.06 if features["delivery_zip"].startswith(("0", "1")) else 0.0return min(1.0, base)def policy_constraints(order: Order, score: float, reasons: List[str]) -> Tuple[str, List[str]]:"""Retorna (decision, reasons) aplicando limites:- "deny" só quando houver evidência forte além de proxies- preferir step-up quando há alternativa de baixo dano"""has_strong_non_proxy = any(r in reasons for r in ["high_device_risk", "high_chargeback_signal"])proxy_only = (not has_strong_non_proxy) and any(r.startswith("zip_proxy") for r in reasons)# 1) Se risco alto mas sem evidência forte além de proxy, NÃO negar; pedir step-up.if score >= 0.70 and proxy_only:return "step_up", reasons + ["policy:no_irreversible_action_on_proxy_only"]# 2) Se risco alto e há evidência forte, negar ou revisar.if score >= 0.80 and has_strong_non_proxy:return "deny", reasons + ["policy:irreversible_allowed_with_strong_evidence"]# 3) Zona cinza: revisão/step-upif score >= 0.60:return ("step_up" if order.delivery_confirmable else "review"), reasons + ["policy:degrade_when_uncertain"]return "approve", reasons + ["policy:low_risk"]def derive_reasons(order: Order) -> List[str]:reasons = []if order.device_risk >= 0.8:reasons.append("high_device_risk")if order.chargeback_history_score >= 0.7:reasons.append("high_chargeback_signal")if order.is_first_purchase:reasons.append("first_purchase")if order.payment_method in {"prepaid", "virtual_card"}:reasons.append("payment_risk_method")if order.account_age_days < 7:reasons.append("new_account")if order.delivery_zip.startswith(("0", "1")):reasons.append("zip_proxy:prefix_01") # marcado explicitamente como proxyreturn reasonsdef write_receipt(receipt: Dict[str, Any]) -> None:# Placeholder: log/audit store + métricas agregadas por coorteprint("[FRAUD_RECEIPT]", receipt)def fraud_decision(order: Order) -> Dict[str, Any]:request_id = f"fr_{uuid.uuid4().hex}"features = {"amount": order.amount,"is_first_purchase": order.is_first_purchase,"payment_method": order.payment_method,"ip_country": order.ip_country,"delivery_zip": order.delivery_zip,"device_risk": order.device_risk,"chargeback_history_score": order.chargeback_history_score,"account_age_days": order.account_age_days,}score = model_score(features)reasons = derive_reasons(order)decision, reasons = policy_constraints(order, score, reasons)receipt = {"ts": int(time.time()),"request_id": request_id,"policy_version": POLICY_VERSION,"order_id": order.order_id,"cohort": cohort_key(order),"risk_score": score,"decision": decision,"reasons": reasons,"evidence": {# evidência mínima para replay (sem “vazar” dado desnecessário)"device_risk_bucket": "high" if order.device_risk >= 0.8 else "ok","chargeback_signal_bucket": "high" if order.chargeback_history_score >= 0.7 else "ok","account_age_days": order.account_age_days,"delivery_confirmable": order.delivery_confirmable,},"next_step": ("3ds_or_otp" if decision == "step_up" else"manual_review_queue" if decision == "review" else"deny_with_appeal_path" if decision == "deny" else"fulfill"),}write_receipt(receipt)return {"order_id": order.order_id,"decision": decision,"risk_score": score,"request_id": request_id,"next_step": receipt["next_step"],}
Antes, o sistema “comprava segurança” com uma moeda invisível: fricção concentrada em coortes específicas. Agora, o sistema passa a ter três propriedades que o diagnóstico Code Map considera essenciais:
- Visibilidade por coorte: toda decisão carrega um rótulo operacional (cohort) e vira mensurável.
- Saída de baixo dano: “nega” deixa de ser default; entra step-up/review quando há incerteza.
- Limite sobre proxy: o sistema impede decisões irreversíveis quando a justificativa é “proxy-only”, forçando degradação segura.
Análise — o que Code Map com código muda de verdade
A diferença não está em “usar um modelo melhor”. Está em tornar a decisão antifraude governável: medir onde dói, evitar irreversibilidade quando a evidência é fraca, e produzir recibos contestáveis. No AS-IS, a empresa otimiza média e descobre tarde o dano concentrado. No TO-BE, a empresa cria mecanismos para enxergar e corrigir concentração cedo — antes que vire perda de confiança e receita.
Se o sistema precisasse escolher uma única prioridade imediata, a pergunta Code Map não seria “como negar mais fraude?”. Seria: em quais cohorts os falsos positivos estão se acumulando, e qual alternativa de baixo dano existe quando a confiança cai?