Em marketplaces, a pressão para reduzir fraude, dropshipping irregular, violações de política e risco reputacional costuma levar a um motor de “trust & safety” que suspende contas automaticamente. No começo, isso limpa a plataforma. Depois, começam a surgir sinais de assimetria: sellers legítimos sendo suspensos em certas localidades, em certas categorias, ou em rotas logísticas específicas. Na média, o sistema parece “eficiente”. Na distribuição, ele vira uma máquina de dano concentrado.
Este diagnóstico (no formato Code Map) busca tornar visível onde os falsos positivos se acumulam, por que a localidade vira proxy dominante e como redesenhar o enforcement para reduzir irreversibilidade, aumentar contestabilidade e melhorar correção.
AS-IS — punição binária, proxy forte e pouco recibo
No AS-IS, o sistema tende a usar um score único e regras simples para suspender rapidamente. A localidade entra como sinal “correlacionado” com abuso histórico e acaba virando proxy dominante. O resultado é que um corredor inteiro (uma região, um tipo de rota) passa a pagar o custo do risco — inclusive quando o seller é legítimo.
# AS-IS: suspensão direta por score agregado.# Problemas típicos:# - localidade entra como proxy forte (ex.: "hotspots")# - decisão irreversível (suspender) é disparada por evidência fraca# - pouco recibo contestável (difícil explicar por que foi suspenso)# - não mede concentração de falsos positivos por segmento operacionalfrom dataclasses import dataclassfrom typing import Dict, Any@dataclassclass SellerSignal:seller_id: strcategory: strregion_code: str # ex.: estado/UF ou cluster internocomplaint_rate_30d: float # 0..1cancel_rate_30d: float # 0..1late_ship_rate_30d: float # 0..1kyc_ok: boolaccount_age_days: intpayout_anomalies: float # 0..1def risk_score_as_is(s: SellerSignal) -> float:base = 0.10base += 0.35 * s.payout_anomaliesbase += 0.20 * s.complaint_rate_30dbase += 0.15 * s.cancel_rate_30dbase += 0.10 * s.late_ship_rate_30dbase += 0.10 if not s.kyc_ok else 0.0base += 0.10 if s.account_age_days < 14 else 0.0# Proxy local bruto (exemplo): regiões tratadas como “hotspot”if s.region_code in {"R-07", "R-09"}:base += 0.25return min(1.0, base)def enforce_as_is(s: SellerSignal) -> Dict[str, Any]:score = risk_score_as_is(s)decision = "suspend" if score >= 0.70 else "allow"# Recibo fraco: apenas score (pouco acionável para apelação)return {"seller_id": s.seller_id, "decision": decision, "risk_score": score}
O efeito típico desse desenho é “justiça na média”: o sistema parece bom globalmente, mas falha de forma concentrada. Isso aparece quando suspensões são revertidas em massa em certos segmentos (localidade/categoria), sinalizando falso positivo recorrente.
TO-BE — enforcement graduado, limites de proxy e recibos contestáveis
No TO-BE, a recomendação é trocar “suspender” como primeira resposta por um modelo de degradação controlada: restringir impacto enquanto se coleta evidência melhor. Ao mesmo tempo, localidade deve ser tratada explicitamente como proxy e nunca ser motivo único para punição irreversível. Por fim, a decisão precisa deixar um recibo replayável para auditoria e apelação.
# TO-BE: enforcement por estágios + recibo + visibilidade por segmento operacional.# Melhorias:# - rotula segmentos (região x categoria x idade) para medir concentração de reversões# - impede "suspender" quando a evidência é proxy-only (localidade sem sinais fortes)# - aplica degradação controlada: step-up / hold payout / limites antes de suspender# - gera recibo replayável e caminho de apelaçãofrom dataclasses import dataclassfrom typing import Dict, Any, List, Tupleimport timeimport uuid@dataclassclass SellerSignal:seller_id: strcategory: strregion_code: strcomplaint_rate_30d: floatcancel_rate_30d: floatlate_ship_rate_30d: floatkyc_ok: boolaccount_age_days: intpayout_anomalies: floatPOLICY_VERSION = "seller_enforcement_v4.1"def segment_key(s: SellerSignal) -> str:age_bucket = "new" if s.account_age_days < 30 else "est"return f"reg:{s.region_code}|cat:{s.category}|age:{age_bucket}"def derive_reasons(s: SellerSignal) -> List[str]:reasons = []if s.payout_anomalies >= 0.8:reasons.append("high_payout_anomaly")if s.complaint_rate_30d >= 0.12:reasons.append("high_complaints")if s.cancel_rate_30d >= 0.10:reasons.append("high_cancels")if s.late_ship_rate_30d >= 0.15:reasons.append("high_late_ship")if not s.kyc_ok:reasons.append("kyc_incomplete")if s.account_age_days < 14:reasons.append("very_new_account")# Marcar explicitamente o sinal de localidade como proxyif s.region_code in {"R-07", "R-09"}:reasons.append("region_proxy:hotspot")return reasonsdef score_to_be(s: SellerSignal) -> float:base = 0.10base += 0.35 * s.payout_anomaliesbase += 0.22 * s.complaint_rate_30dbase += 0.15 * s.cancel_rate_30dbase += 0.12 * s.late_ship_rate_30dbase += 0.08 if not s.kyc_ok else 0.0base += 0.06 if s.account_age_days < 14 else 0.0# Proxy local com peso menorbase += 0.05 if s.region_code in {"R-07", "R-09"} else 0.0return min(1.0, base)def policy_enforcement(s: SellerSignal, score: float, reasons: List[str]) -> Tuple[str, List[str], str]:"""Retorna (decision, reasons, next_step).decision: allow | step_up | limit | hold_payout | suspend | review"""has_strong = any(r in reasons for r in ["high_payout_anomaly", "high_complaints", "high_cancels", "high_late_ship", "kyc_incomplete"])proxy_only = (not has_strong) and any(r.startswith("region_proxy") for r in reasons)# Nunca suspender com proxy-onlyif score >= 0.75 and proxy_only:return ("step_up",reasons + ["policy:no_suspend_on_proxy_only"],"request_additional_verification")# Alto risco com evidência forte: restringe primeiro, revisa rápidoif score >= 0.85 and has_strong:return ("hold_payout",reasons + ["policy:high_risk_hold_then_review"],"fast_manual_review_queue")# Zona cinza: limitações reversíveisif score >= 0.65:return ("limit",reasons + ["policy:degrade_when_uncertain"],"rate_limit_listings_and_orders")return ("allow", reasons + ["policy:low_risk"], "normal_operations")def write_receipt(receipt: Dict[str, Any]) -> None:print("[SELLER_RECEIPT]", receipt)def enforce_to_be(s: SellerSignal) -> Dict[str, Any]:request_id = f"se_{uuid.uuid4().hex}"score = score_to_be(s)reasons = derive_reasons(s)decision, reasons, next_step = policy_enforcement(s, score, reasons)receipt = {"ts": int(time.time()),"request_id": request_id,"policy_version": POLICY_VERSION,"seller_id": s.seller_id,"segment": segment_key(s),"risk_score": score,"decision": decision,"reasons": reasons,"evidence": {"complaint_rate_30d": s.complaint_rate_30d,"cancel_rate_30d": s.cancel_rate_30d,"late_ship_rate_30d": s.late_ship_rate_30d,"payout_anomalies_bucket": "high" if s.payout_anomalies >= 0.8 else "ok","kyc_ok": s.kyc_ok,"account_age_days": s.account_age_days,},"next_step": next_step,"appeal_path": "seller_portal_appeal_with_receipt_id","reversibility": "graduated_enforcement_before_suspend",}write_receipt(receipt)return {"seller_id": s.seller_id,"decision": decision,"risk_score": score,"request_id": request_id,"next_step": next_step,}
Análise — o que muda quando a decisão deixa de ser binária
O ganho não vem de “relaxar” segurança, e sim de trocar irreversibilidade por governabilidade: medir concentração por segmento operacional, impedir punição dura quando só há proxy, aplicar degradação controlada e registrar recibos contestáveis. Isso reduz falsos positivos locais, preserva a base de sellers legítimos e evita que a plataforma troque risco por destruição silenciosa de oferta em regiões específicas.