mirror of
https://github.com/domfelipe/fraudshield.git
synced 2026-08-07 09:56:54 +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.
175 lines
9.2 KiB
Markdown
175 lines
9.2 KiB
Markdown
# 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}
|