mirror of
https://github.com/domfelipe/fraudshield.git
synced 2026-08-07 14:36:55 +00:00
Stack: Python 3.12/FastAPI + React 19/TypeScript + PostgreSQL + Redis. 6 artifacts: plan.md, research.md, data-model.md, api.yaml (OpenAPI), quickstart.md, updated AGENTS.md. Constitution check: 6/6 PASS.
9.2 KiB
9.2 KiB
Data Model: Motor de Detecção de Fraudes Bancárias
Feature: 001-fraud-detection-engine Date: 2026-05-11
Entity Relationship Diagram
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│ Transaction │──────▶│ FraudAlert │◀──────│ DetectionRule│
└─────────────┘ 1:N └──────────────────┘ N:M └─────────────┘
│
│ 1:1
▼
┌──────────────────┐
│InvestigationResult│
└──────────────────┘
│
▼
┌──────────────────┐
│ AuditLog │ (todos os eventos)
└──────────────────┘
Entities
Transaction
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, NOT NULL | Identificador único da transação |
external_id |
VARCHAR(255) | NOT NULL, UNIQUE | ID original do sistema bancário |
amount |
DECIMAL(15,2) | NOT NULL, CHECK > 0 | Valor da transação em reais |
merchant_name |
VARCHAR(255) | NOT NULL | Nome do estabelecimento |
merchant_category |
VARCHAR(100) | NOT NULL | Categoria (ex: "alimentação", "eletrônicos") |
latitude |
DECIMAL(10,7) | NULLABLE | Latitude do estabelecimento |
longitude |
DECIMAL(10,7) | NULLABLE | Longitude do estabelecimento |
customer_id |
VARCHAR(128) | NOT NULL, INDEX | ID do cliente tokenizado |
channel |
VARCHAR(20) | NOT NULL, CHECK (web, mobile, pos) | Canal da transação |
transaction_at |
TIMESTAMPTZ | NOT NULL, INDEX | Timestamp original da transação |
ingested_at |
TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | Timestamp de ingestão no FraudShield |
ruleset_version |
VARCHAR(20) | NOT NULL | Versão do conjunto de regras usado |
raw_payload |
JSONB | NOT NULL | Payload original completo para auditoria |
Indexes:
idx_transactions_customer_idoncustomer_ididx_transactions_transaction_atontransaction_atidx_transactions_ingested_atoningested_at
DetectionRule
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, NOT NULL | Identificador único da regra |
name |
VARCHAR(255) | NOT NULL, UNIQUE | Nome da regra |
description |
TEXT | NOT NULL | Descrição do que a regra detecta |
condition |
TEXT | NOT NULL | Expressão da condição (ex: amount > customer.avg_amount * 3) |
weight |
INTEGER | NOT NULL, CHECK 0-100, DEFAULT 0 | Peso da regra no score composto |
threshold |
INTEGER | NOT NULL, CHECK 0-100, DEFAULT 0 | Score mínimo individual para disparar |
is_active |
BOOLEAN | NOT NULL, DEFAULT FALSE | Se a regra está ativa em produção |
version |
INTEGER | NOT NULL, DEFAULT 1 | Versão incremental |
created_by |
VARCHAR(255) | NOT NULL | Usuário que criou |
created_at |
TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | Data de criação |
updated_at |
TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | Data da última modificação |
FraudAlert
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, NOT NULL | Identificador único do alerta |
transaction_id |
UUID | FK → Transaction.id, NOT NULL | Transação associada |
score |
INTEGER | NOT NULL, CHECK 0-100 | Score de risco composto |
triggered_rules |
JSONB | NOT NULL | Array de objetos: [{rule_id, rule_name, score, explanation}] |
explanation |
TEXT | NOT NULL | Explicação composta em linguagem natural |
status |
VARCHAR(20) | NOT NULL, DEFAULT 'pending', CHECK (pending, confirmed, false_positive, escalated) | Status do alerta |
assigned_to |
VARCHAR(255) | NULLABLE | Analista designado |
decision |
VARCHAR(20) | NULLABLE, CHECK (confirmed, false_positive, escalated) | Decisão tomada |
decided_by |
VARCHAR(255) | NULLABLE | Analista que decidiu |
decided_at |
TIMESTAMPTZ | NULLABLE | Timestamp da decisão |
justification |
TEXT | NULLABLE | Justificativa (obrigatória para false_positive e escalated) |
created_at |
TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | Timestamp de criação do alerta |
ruleset_version |
VARCHAR(20) | NOT NULL | Versão do ruleset que gerou o alerta |
Indexes:
idx_alerts_statusonstatusidx_alerts_created_atoncreated_atidx_alerts_transaction_idontransaction_id
AuditLog
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, NOT NULL | Identificador único |
trace_id |
UUID | NOT NULL, INDEX | Trace ID para correlação |
event_type |
VARCHAR(50) | NOT NULL, INDEX | Tipo de evento (transaction_received, rule_evaluated, alert_created, decision_made, rule_updated) |
event_at |
TIMESTAMPTZ | NOT NULL, INDEX | Timestamp do evento |
actor |
VARCHAR(255) | NULLABLE | Usuário ou sistema que gerou o evento |
payload |
JSONB | NOT NULL | Dados completos do evento |
ruleset_version |
VARCHAR(20) | NULLABLE | Versão do ruleset no momento do evento |
Partitioning: Por mês via event_at para facilitar retenção e performance.
Imutabilidade: Tabela configurada com triggers que rejeitam UPDATE e DELETE. Apenas INSERT permitido.
RuleSnapshot
| Field | Type | Constraints | Description |
|---|---|---|---|
version |
VARCHAR(20) | PK, NOT NULL | Versão do conjunto de regras (ex: "20260511-001") |
rules_json |
JSONB | NOT NULL | Snapshot completo de todas as regras ativas na versão |
created_at |
TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | Quando o snapshot foi criado |
created_by |
VARCHAR(255) | NOT NULL | Quem gerou o snapshot |
CustomerProfile (Cache / Materialized View)
| Field | Type | Source | Description |
|---|---|---|---|
customer_id |
VARCHAR(128) | Derived | ID do cliente tokenizado |
avg_amount_30d |
DECIMAL(15,2) | AVG(amount) 30d | Média de gasto 30 dias |
avg_amount_90d |
DECIMAL(15,2) | AVG(amount) 90d | Média de gasto 90 dias |
common_categories |
JSONB | Top 5 categorias 90d | Categorias mais frequentes |
common_locations |
JSONB | Cluster de coordenadas | Regiões habituais de transação |
night_transactions_pct |
DECIMAL(5,2) | % transações 22h-06h | Percentual noturno |
last_updated_at |
TIMESTAMPTZ | NOW() | Última atualização |
Implementation: Materialized view no PostgreSQL, refresh a cada 5 minutos. Cache em Redis com TTL de 5 minutos para acesso rápido pelo motor de regras.
State Transitions
Alert Lifecycle
┌──────────────┐
│ pending │ ◀── Alerta criado (score > threshold)
└──────┬───────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌───────────┐
│confirmed │ │ false_ │ │ escalated │
│ │ │ positive │ │ │
└──────────┘ └──────────┘ └─────┬─────┘
│
Revisão sênior
│
┌────────┼────────┐
▼ ▼
┌──────────┐ ┌──────────┐
│confirmed │ │ false_ │
│ │ │ positive │
└──────────┘ └──────────┘
Estados finais: confirmed, false_positive (não transicionam mais)
Rule Lifecycle
┌────────┐ activate ┌────────┐
│inactive│───────────────▶│ active │
└────────┘ └────────┘
▲ │
│ deactivate │
└──────────────────────────┘
Validation Rules
Transaction.amount> 0Transaction.channel∈ {web, mobile, pos}DetectionRule.weight∈ [0, 100]DetectionRule.threshold∈ [0, 100]FraudAlert.score∈ [0, 100]FraudAlert.justificationrequired when decision ∈ {false_positive, escalated}FraudAlert.statustransitions only: pending → {confirmed, false_positive, escalated}; escalated → {confirmed, false_positive}