mirror of
https://github.com/domfelipe/fraudshield.git
synced 2026-08-07 12:36:52 +00:00
plan: add implementation plan for fraud detection engine
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.
This commit is contained in:
parent
e31cba2847
commit
7f9832bf4b
6 changed files with 1487 additions and 1 deletions
175
specs/001-fraud-detection-engine/data-model.md
Normal file
175
specs/001-fraud-detection-engine/data-model.md
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
# 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}
|
||||
Loading…
Add table
Add a link
Reference in a new issue