Code: Suspensão de sellers por falso positivo local no marketplace

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 operacional

from dataclasses import dataclass
from typing import Dict, Any

@dataclass
class SellerSignal:
    seller_id: str
    category: str
    region_code: str          # ex.: estado/UF ou cluster interno
    complaint_rate_30d: float # 0..1
    cancel_rate_30d: float    # 0..1
    late_ship_rate_30d: float # 0..1
    kyc_ok: bool
    account_age_days: int
    payout_anomalies: float   # 0..1

def risk_score_as_is(s: SellerSignal) -> float:
    base = 0.10
    base += 0.35 * s.payout_anomalies
    base += 0.20 * s.complaint_rate_30d
    base += 0.15 * s.cancel_rate_30d
    base += 0.10 * s.late_ship_rate_30d
    base += 0.10 if not s.kyc_ok else 0.0
    base += 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.25

    return 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ção

from dataclasses import dataclass
from typing import Dict, Any, List, Tuple
import time
import uuid

@dataclass
class SellerSignal:
    seller_id: str
    category: str
    region_code: str
    complaint_rate_30d: float
    cancel_rate_30d: float
    late_ship_rate_30d: float
    kyc_ok: bool
    account_age_days: int
    payout_anomalies: float

POLICY_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 proxy
    if s.region_code in {"R-07", "R-09"}:
        reasons.append("region_proxy:hotspot")

    return reasons

def score_to_be(s: SellerSignal) -> float:
    base = 0.10
    base += 0.35 * s.payout_anomalies
    base += 0.22 * s.complaint_rate_30d
    base += 0.15 * s.cancel_rate_30d
    base += 0.12 * s.late_ship_rate_30d
    base += 0.08 if not s.kyc_ok else 0.0
    base += 0.06 if s.account_age_days < 14 else 0.0
    # Proxy local com peso menor
    base += 0.05 if s.region_code in {"R-07", "R-09"} else 0.0
    return 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-only
    if 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ápido
    if 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íveis
    if 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.

Veja também: