# 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_id` on `customer_id` - `idx_transactions_transaction_at` on `transaction_at` - `idx_transactions_ingested_at` on `ingested_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_status` on `status` - `idx_alerts_created_at` on `created_at` - `idx_alerts_transaction_id` on `transaction_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` > 0 - `Transaction.channel` ∈ {web, mobile, pos} - `DetectionRule.weight` ∈ [0, 100] - `DetectionRule.threshold` ∈ [0, 100] - `FraudAlert.score` ∈ [0, 100] - `FraudAlert.justification` required when decision ∈ {false_positive, escalated} - `FraudAlert.status` transitions only: pending → {confirmed, false_positive, escalated}; escalated → {confirmed, false_positive}