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:
Felipe Domingues 2026-05-12 14:58:21 -03:00
parent e31cba2847
commit 7f9832bf4b
6 changed files with 1487 additions and 1 deletions

View file

@ -1,4 +1,18 @@
<!-- SPECKIT START --> <!-- SPECKIT START -->
For additional context about technologies to be used, project structure, For additional context about technologies to be used, project structure,
shell commands, and other important information, read the current plan shell commands, and other important information, read the current plan:
specs/001-fraud-detection-engine/plan.md
## Tech Stack
- **Backend**: Python 3.12, FastAPI, SQLAlchemy 2.0, Pydantic v2, Alembic
- **Frontend**: React 19, TypeScript, Tailwind CSS, Recharts, Vite
- **Storage**: PostgreSQL 16, Redis 7
- **Testing**: pytest, Vitest, Playwright
## Quick Reference
- Start backend: `cd backend && uv run uvicorn fraudshield.main:app --reload`
- Start frontend: `cd frontend && npm run dev`
- Run tests: `cd backend && uv run pytest`, `cd frontend && npm test`
- API docs: http://localhost:8000/docs (auto-generated OpenAPI)
- Quickstart: specs/001-fraud-detection-engine/quickstart.md
<!-- SPECKIT END --> <!-- SPECKIT END -->

View file

@ -0,0 +1,803 @@
openapi: "3.0.3"
info:
title: FraudShield API
version: "1.0.0"
description: API do motor de detecção de fraudes bancárias.
servers:
- url: http://localhost:8000/api/v1
description: Local development
paths:
/transactions/ingest:
post:
summary: Ingest transaction for fraud evaluation
operationId: ingestTransaction
tags: [Transactions]
security:
- ApiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TransactionIngestRequest"
responses:
"202":
description: Transaction accepted for processing
content:
application/json:
schema:
$ref: "#/components/schemas/TransactionAccepted"
"400":
$ref: "#/components/responses/ValidationError"
"429":
description: Rate limit exceeded
"503":
description: Service unavailable (backpressure)
/transactions/{transaction_id}:
get:
summary: Get transaction details with evaluation result
operationId: getTransaction
tags: [Transactions]
security:
- BearerAuth: []
parameters:
- name: transaction_id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Transaction details
content:
application/json:
schema:
$ref: "#/components/schemas/TransactionDetail"
"404":
description: Transaction not found
/alerts:
get:
summary: List fraud alerts with filters
operationId: listAlerts
tags: [Alerts]
security:
- BearerAuth: []
parameters:
- name: status
in: query
schema:
type: string
enum: [pending, confirmed, false_positive, escalated]
- name: min_score
in: query
schema:
type: integer
minimum: 0
maximum: 100
- name: from_date
in: query
schema:
type: string
format: date-time
- name: to_date
in: query
schema:
type: string
format: date-time
- name: triggered_rule
in: query
schema:
type: string
- name: limit
in: query
schema:
type: integer
default: 50
maximum: 200
- name: offset
in: query
schema:
type: integer
default: 0
responses:
"200":
description: Paginated alert list
content:
application/json:
schema:
$ref: "#/components/schemas/AlertListResponse"
/alerts/{alert_id}:
get:
summary: Get alert details
operationId: getAlert
tags: [Alerts]
security:
- BearerAuth: []
parameters:
- name: alert_id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Alert details with full explanation
content:
application/json:
schema:
$ref: "#/components/schemas/AlertDetail"
"404":
description: Alert not found
/alerts/{alert_id}/decide:
post:
summary: Submit investigation decision
operationId: decideAlert
tags: [Alerts]
security:
- BearerAuth: []
parameters:
- name: alert_id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AlertDecisionRequest"
responses:
"200":
description: Decision recorded
content:
application/json:
schema:
$ref: "#/components/schemas/AlertDetail"
"400":
$ref: "#/components/responses/ValidationError"
"409":
description: Alert already decided
/rules:
get:
summary: List detection rules
operationId: listRules
tags: [Rules]
security:
- BearerAuth: []
- AdminAuth: []
parameters:
- name: is_active
in: query
schema:
type: boolean
responses:
"200":
description: Rule list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/DetectionRule"
post:
summary: Create new detection rule
operationId: createRule
tags: [Rules]
security:
- BearerAuth: []
- AdminAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateRuleRequest"
responses:
"201":
description: Rule created (inactive by default)
content:
application/json:
schema:
$ref: "#/components/schemas/DetectionRule"
"400":
$ref: "#/components/responses/ValidationError"
/rules/{rule_id}:
get:
summary: Get rule details
operationId: getRule
tags: [Rules]
security:
- BearerAuth: []
- AdminAuth: []
parameters:
- name: rule_id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Rule details
content:
application/json:
schema:
$ref: "#/components/schemas/DetectionRule"
put:
summary: Update detection rule
operationId: updateRule
tags: [Rules]
security:
- BearerAuth: []
- AdminAuth: []
parameters:
- name: rule_id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateRuleRequest"
responses:
"200":
description: Rule updated
content:
application/json:
schema:
$ref: "#/components/schemas/DetectionRule"
/rules/{rule_id}/activate:
post:
summary: Activate a rule
operationId: activateRule
tags: [Rules]
security:
- BearerAuth: []
- AdminAuth: []
parameters:
- name: rule_id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Rule activated
"400":
description: Rule has no weight or threshold set
/rules/{rule_id}/deactivate:
post:
summary: Deactivate a rule
operationId: deactivateRule
tags: [Rules]
security:
- BearerAuth: []
- AdminAuth: []
parameters:
- name: rule_id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Rule deactivated
/rules/{rule_id}/test:
post:
summary: Test rule against historical data
operationId: testRule
tags: [Rules]
security:
- BearerAuth: []
- AdminAuth: []
parameters:
- name: rule_id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/TestRuleRequest"
responses:
"200":
description: Test results
content:
application/json:
schema:
$ref: "#/components/schemas/TestRuleResponse"
/dashboard/metrics:
get:
summary: Get real-time dashboard metrics
operationId: getDashboardMetrics
tags: [Dashboard]
security:
- BearerAuth: []
parameters:
- name: period
in: query
schema:
type: string
enum: [24h, 7d, 30d, 90d]
default: 24h
responses:
"200":
description: Dashboard metrics
content:
application/json:
schema:
$ref: "#/components/schemas/DashboardMetrics"
/dashboard/report:
get:
summary: Export compliance report
operationId: exportReport
tags: [Dashboard]
security:
- BearerAuth: []
parameters:
- name: from_date
in: query
required: true
schema:
type: string
format: date
- name: to_date
in: query
required: true
schema:
type: string
format: date
- name: format
in: query
schema:
type: string
enum: [pdf, csv]
default: pdf
responses:
"200":
description: Report file
content:
application/pdf:
schema:
type: string
format: binary
text/csv:
schema:
type: string
format: binary
/health:
get:
summary: Health check
operationId: healthCheck
tags: [System]
responses:
"200":
description: Service healthy
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: healthy
version:
type: string
example: "1.0.0"
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
AdminAuth: {}
schemas:
TransactionIngestRequest:
type: object
required: [external_id, amount, merchant_name, merchant_category, customer_id, channel, transaction_at]
properties:
external_id:
type: string
description: ID original do sistema bancário
amount:
type: number
format: decimal
minimum: 0.01
example: 5000.00
merchant_name:
type: string
example: "Loja Exemplo Ltda"
merchant_category:
type: string
example: "eletrônicos"
latitude:
type: number
format: float
nullable: true
longitude:
type: number
format: float
nullable: true
customer_id:
type: string
description: Token do cliente
example: "tok_abc123xyz"
channel:
type: string
enum: [web, mobile, pos]
transaction_at:
type: string
format: date-time
TransactionAccepted:
type: object
properties:
transaction_id:
type: string
format: uuid
status:
type: string
example: accepted
trace_id:
type: string
format: uuid
TransactionDetail:
type: object
properties:
id:
type: string
format: uuid
external_id:
type: string
amount:
type: number
merchant_name:
type: string
merchant_category:
type: string
latitude:
type: number
nullable: true
longitude:
type: number
nullable: true
customer_id:
type: string
channel:
type: string
transaction_at:
type: string
format: date-time
ingested_at:
type: string
format: date-time
alert:
$ref: "#/components/schemas/AlertDetail"
nullable: true
AlertListResponse:
type: object
properties:
items:
type: array
items:
$ref: "#/components/schemas/AlertSummary"
total:
type: integer
limit:
type: integer
offset:
type: integer
AlertSummary:
type: object
properties:
id:
type: string
format: uuid
score:
type: integer
status:
type: string
amount:
type: number
merchant_name:
type: string
top_rule:
type: string
created_at:
type: string
format: date-time
AlertDetail:
type: object
properties:
id:
type: string
format: uuid
transaction_id:
type: string
format: uuid
score:
type: integer
triggered_rules:
type: array
items:
type: object
properties:
rule_id:
type: string
format: uuid
rule_name:
type: string
score:
type: integer
explanation:
type: string
explanation:
type: string
description: Explicação composta em linguagem natural
status:
type: string
enum: [pending, confirmed, false_positive, escalated]
assigned_to:
type: string
nullable: true
decision:
type: string
nullable: true
decided_by:
type: string
nullable: true
decided_at:
type: string
format: date-time
nullable: true
justification:
type: string
nullable: true
created_at:
type: string
format: date-time
AlertDecisionRequest:
type: object
required: [decision]
properties:
decision:
type: string
enum: [confirmed, false_positive, escalated]
justification:
type: string
description: Required for false_positive and escalated
notes:
type: string
DetectionRule:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
description:
type: string
condition:
type: string
weight:
type: integer
minimum: 0
maximum: 100
threshold:
type: integer
minimum: 0
maximum: 100
is_active:
type: boolean
version:
type: integer
created_by:
type: string
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
CreateRuleRequest:
type: object
required: [name, description, condition]
properties:
name:
type: string
example: "Valor atípico"
description:
type: string
example: "Detecta transações com valor muito acima da média do cliente"
condition:
type: string
example: "amount > customer.avg_amount_30d * 3"
weight:
type: integer
minimum: 0
maximum: 100
default: 0
threshold:
type: integer
minimum: 0
maximum: 100
default: 0
UpdateRuleRequest:
type: object
properties:
name:
type: string
description:
type: string
condition:
type: string
weight:
type: integer
minimum: 0
maximum: 100
threshold:
type: integer
minimum: 0
maximum: 100
TestRuleRequest:
type: object
properties:
days_back:
type: integer
default: 90
maximum: 90
TestRuleResponse:
type: object
properties:
rule_id:
type: string
format: uuid
total_transactions_evaluated:
type: integer
example: 50000
would_trigger_count:
type: integer
example: 1250
would_trigger_pct:
type: number
example: 2.5
score_distribution:
type: object
properties:
p50:
type: number
p90:
type: number
p95:
type: number
max:
type: number
estimated_false_positives:
type: integer
description: Based on analyst decisions on similar patterns
sample_alerts:
type: array
items:
$ref: "#/components/schemas/AlertSummary"
DashboardMetrics:
type: object
properties:
period:
type: string
total_transactions:
type: integer
total_alerts:
type: integer
fraud_rate_pct:
type: number
description: Alertas confirmados / total de transações
false_positive_rate_pct:
type: number
avg_decision_time_seconds:
type: number
alerts_by_status:
type: object
properties:
pending:
type: integer
confirmed:
type: integer
false_positive:
type: integer
escalated:
type: integer
alerts_by_hour:
type: array
items:
type: object
properties:
hour:
type: integer
count:
type: integer
top_triggering_rules:
type: array
items:
type: object
properties:
rule_name:
type: string
alert_count:
type: integer
score_distribution:
type: object
properties:
low:
type: integer
medium:
type: integer
high:
type: integer
critical:
type: integer
responses:
ValidationError:
description: Validation error
content:
application/json:
schema:
type: object
properties:
detail:
type: string
errors:
type: array
items:
type: object
properties:
field:
type: string
message:
type: string

View 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}

View file

@ -0,0 +1,177 @@
# Implementation Plan: Motor de Detecção de Fraudes Bancárias
**Branch**: `001-fraud-detection-engine` | **Date**: 2026-05-11 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/001-fraud-detection-engine/spec.md`
## Summary
Sistema web para detecção de fraudes bancárias com motor de regras pontuável, explicações auditáveis em linguagem natural, fila de investigação para analistas, gestão de regras por administradores, e dashboard de métricas em tempo real. Backend Python/FastAPI com pipeline de ingestão assíncrono via Redis Streams, frontend React/TypeScript com dashboard interativo, PostgreSQL para dados transacionais e audit log imutável.
## Technical Context
**Language/Version**: Python 3.12 (backend), TypeScript 5.x (frontend)
**Primary Dependencies**: FastAPI, SQLAlchemy 2.0, Pydantic v2, Alembic, React 19, Tailwind CSS, Recharts
**Storage**: PostgreSQL 16 (transacional + audit log), Redis 7 (cache + filas + real-time)
**Testing**: pytest + pytest-asyncio (backend), Vitest + React Testing Library (frontend), Playwright (E2E)
**Target Platform**: Web browser (frontend) + REST API (backend), Docker Compose (dev/local)
**Project Type**: Web service (backend API + SPA frontend)
**Performance Goals**: 500 transações/minuto com p95 < 2s de latência de processamento
**Constraints**: p95 < 2s (processamento), p95 < 60s (atraso dashboard), audit log imutável, LGPD compliance
**Scale/Scope**: 10.000 transações/dia inicial, picos de 5x, 3 perfis de usuário, ~5-20 regras ativas
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
| Principle | Status | Notes |
|-----------|--------|-------|
| I. Security-First | ✅ PASS | JWT + OAuth2 via IdP externo, API keys para ingestão, TLS 1.3, secrets via env vars, PII tokenizado antes da ingestão |
| II. Explainability | ✅ PASS | Cada regra gera explicação em linguagem natural. Explicação composta agregada no alerta. Formato JSON + texto |
| III. Test-First | ✅ PASS | TDD planejado: testes de regra, contrato, integração, E2E. Métricas de FP/FN como gate de deploy |
| IV. Observability | ✅ PASS | Audit log imutável com trace ID. Structured logging (JSON). Métricas exportáveis para dashboard |
| V. Data Privacy | ✅ PASS | Dados chegam tokenizados. Sem PII armazenado. LGPD: direito à explicação, exclusão, DPIA. Mascaramento em logs/UI |
| VI. Simplicity (YAGNI) | ✅ PASS | Rules-based v1 (sem ML). Monolith FastAPI (sem microservices). PostgreSQL sem TimescaleDB até necessidade comprovada. Redis Streams em vez de Kafka |
**Gate Result**: ALL PASS — proceeding to implementation.
## Project Structure
### Documentation (this feature)
```text
specs/001-fraud-detection-engine/
├── plan.md # This file
├── research.md # Technology research & decisions
├── data-model.md # Entity definitions, relationships, state machines
├── quickstart.md # Setup, test credentials, smoke test flow
├── contracts/
│ └── api.yaml # OpenAPI 3.0 specification
└── tasks.md # Phase 2 output (NOT created by /speckit.plan)
```
### Source Code (repository root)
```text
backend/
├── fraudshield/
│ ├── __init__.py
│ ├── main.py # FastAPI application factory
│ ├── config.py # pydantic-settings configuration
│ ├── auth.py # JWT validation, OAuth2 middleware, role checks
│ ├── db.py # SQLAlchemy engine, session, base
│ ├── models/
│ │ ├── __init__.py
│ │ ├── transaction.py # Transaction ORM model
│ │ ├── rule.py # DetectionRule ORM model
│ │ ├── alert.py # FraudAlert ORM model
│ │ └── audit.py # AuditLog ORM model (immutable)
│ ├── schemas/
│ │ ├── __init__.py
│ │ ├── transaction.py # Pydantic request/response schemas
│ │ ├── rule.py
│ │ ├── alert.py
│ │ └── dashboard.py
│ ├── engine/
│ │ ├── __init__.py
│ │ ├── evaluator.py # Core: iterate rules, compute composite score
│ │ ├── explainer.py # Generate natural language explanations
│ │ ├── loader.py # Load active rules from DB, cache in memory
│ │ └── rules/ # Individual rule implementations
│ │ ├── __init__.py
│ │ ├── base.py # Abstract Rule base class
│ │ ├── amount.py # Amount anomaly rules
│ │ ├── location.py # Geolocation rules
│ │ ├── time.py # Time-based rules
│ │ └── pattern.py # Customer pattern rules
│ ├── api/
│ │ ├── __init__.py
│ │ ├── deps.py # Dependency injection (DB session, current user)
│ │ ├── transactions.py # POST /transactions/ingest, GET /transactions/{id}
│ │ ├── alerts.py # GET /alerts, GET /alerts/{id}, POST /alerts/{id}/decide
│ │ ├── rules.py # CRUD + activate/deactivate + test
│ │ ├── dashboard.py # GET /dashboard/metrics, GET /dashboard/report
│ │ └── health.py # GET /health
│ ├── services/
│ │ ├── __init__.py
│ │ ├── ingestion.py # Transaction ingestion + queue dispatch
│ │ ├── detection.py # Orchestrate rule evaluation pipeline
│ │ ├── alerts.py # Alert creation, assignment, decision
│ │ ├── rules.py # Rule CRUD, activation, snapshot
│ │ └── dashboard.py # Metrics aggregation from DB + Redis
│ └── seed.py # Development data seeder
├── alembic/
│ ├── env.py
│ └── versions/
├── tests/
│ ├── conftest.py # Fixtures: test DB, test client, auth tokens
│ ├── unit/
│ │ ├── test_evaluator.py
│ │ ├── test_explainer.py
│ │ └── test_rules/
│ │ ├── test_amount.py
│ │ ├── test_location.py
│ │ └── test_time.py
│ ├── integration/
│ │ ├── test_ingestion_pipeline.py
│ │ ├── test_alert_workflow.py
│ │ └── test_rule_lifecycle.py
│ └── contract/
│ ├── test_transactions_api.py
│ ├── test_alerts_api.py
│ └── test_rules_api.py
├── pyproject.toml
└── Dockerfile
frontend/
├── src/
│ ├── api/
│ │ └── client.ts # Generated OpenAPI client
│ ├── components/
│ │ ├── layout/
│ │ │ ├── Shell.tsx
│ │ │ └── Navbar.tsx
│ │ ├── alerts/
│ │ │ ├── AlertQueue.tsx
│ │ │ ├── AlertCard.tsx
│ │ │ └── AlertDetail.tsx
│ │ ├── rules/
│ │ │ ├── RuleList.tsx
│ │ │ ├── RuleForm.tsx
│ │ │ └── RuleTestResults.tsx
│ │ └── dashboard/
│ │ ├── MetricCard.tsx
│ │ ├── AlertsByHour.tsx
│ │ ├── TopRules.tsx
│ │ └── ScoreDistribution.tsx
│ ├── pages/
│ │ ├── LoginPage.tsx
│ │ ├── AlertsPage.tsx
│ │ ├── RulesPage.tsx
│ │ └── DashboardPage.tsx
│ ├── hooks/
│ │ ├── useAuth.ts
│ │ ├── useAlerts.ts
│ │ └── useDashboard.ts
│ ├── contexts/
│ │ └── AuthContext.tsx
│ ├── lib/
│ │ └── utils.ts
│ ├── App.tsx
│ └── main.tsx
├── tests/
│ ├── components/
│ └── pages/
├── package.json
├── tsconfig.json
├── tailwind.config.ts
└── vite.config.ts
docker-compose.yml
AGENTS.md
```
**Structure Decision**: Web application with separate `backend/` and `frontend/` directories. Backend is a single FastAPI service (monolith per YAGNI principle). Frontend is a Vite + React SPA.
## Complexity Tracking
> No constitutional violations to justify. All gates passed.

View file

@ -0,0 +1,190 @@
# Quickstart: FraudShield
**Feature**: 001-fraud-detection-engine
## Pré-requisitos
- Docker & Docker Compose
- Python 3.12+
- Node.js 20+
- `uv` (Python package manager)
## Setup Local (Dev)
```bash
# 1. Clone e entre no projeto
git clone <repo-url> fraudshield
cd fraudshield
# 2. Suba os serviços de infra
docker compose up -d postgres redis
# 3. Backend
cd backend
uv sync # Instala dependências
uv run alembic upgrade head # Cria tabelas
uv run python -m fraudshield.seed # Popula regras iniciais + dados de teste
uv run uvicorn fraudshield.main:app --reload --port 8000
# 4. Frontend (outro terminal)
cd frontend
npm install
npm run dev # http://localhost:5173
# 5. Testes
cd backend && uv run pytest # Backend
cd frontend && npm test # Frontend
```
## Infraestrutura Docker
```yaml
# docker-compose.yml (visão geral)
services:
postgres:
image: postgres:16
environment:
POSTGRES_DB: fraudshield
POSTGRES_USER: fraudshield
POSTGRES_PASSWORD: fraudshield_dev
ports: ["5432:5432"]
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports: ["6379:6379"]
```
## Credenciais de Teste
| Perfil | Usuário | Senha |
|--------|---------|-------|
| Analista | `analista@fraudshield.local` | `fraudshield123` |
| Administrador | `admin@fraudshield.local` | `fraudshield123` |
| Sênior | `senior@fraudshield.local` | `fraudshield123` |
## Fluxo de Teste Manual
### 1. Ingerir uma transação suspeita
```bash
curl -X POST http://localhost:8000/api/v1/transactions/ingest \
-H "Content-Type: application/json" \
-H "X-API-Key: dev-api-key" \
-d '{
"external_id": "txn_test_001",
"amount": 15000.00,
"merchant_name": "Loja Desconhecida Ltda",
"merchant_category": "joalheria",
"latitude": -23.5505,
"longitude": -46.6333,
"customer_id": "tok_cust_001",
"channel": "web",
"transaction_at": "2026-05-11T03:15:00-03:00"
}'
```
### 2. Verificar alertas pendentes
```bash
curl http://localhost:8000/api/v1/alerts?status=pending \
-H "Authorization: Bearer <jwt_token>"
```
### 3. Decidir sobre um alerta
```bash
curl -X POST http://localhost:8000/api/v1/alerts/<alert_id>/decide \
-H "Authorization: Bearer <jwt_token>" \
-H "Content-Type: application/json" \
-d '{"decision": "confirmed"}'
```
### 4. Criar uma regra
```bash
curl -X POST http://localhost:8000/api/v1/rules \
-H "Authorization: Bearer <admin_jwt_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Valor noturno elevado",
"description": "Detecta transações acima de R$ 1000 entre 22h e 06h",
"condition": "amount > 1000 and hour >= 22 or hour <= 6",
"weight": 25
}'
```
### 5. Testar regra contra dados históricos
```bash
curl -X POST http://localhost:8000/api/v1/rules/<rule_id>/test \
-H "Authorization: Bearer <admin_jwt_token>" \
-H "Content-Type: application/json" \
-d '{"days_back": 90}'
```
### 6. Dashboard
```bash
curl http://localhost:8000/api/v1/dashboard/metrics?period=24h \
-H "Authorization: Bearer <jwt_token>"
```
## Estrutura do Projeto
```
fraudshield/
├── backend/
│ ├── fraudshield/
│ │ ├── main.py # FastAPI app + routers
│ │ ├── config.py # Settings via pydantic-settings
│ │ ├── models/ # SQLAlchemy models
│ │ │ ├── transaction.py
│ │ │ ├── rule.py
│ │ │ ├── alert.py
│ │ │ └── audit.py
│ │ ├── engine/ # Rule engine
│ │ │ ├── evaluator.py # Core evaluation logic
│ │ │ ├── rules/ # Individual rule implementations
│ │ │ └── explainer.py # Explanation generation
│ │ ├── api/ # Route handlers
│ │ │ ├── transactions.py
│ │ │ ├── alerts.py
│ │ │ ├── rules.py
│ │ │ └── dashboard.py
│ │ ├── services/ # Business logic
│ │ ├── auth.py # JWT + OAuth2
│ │ └── seed.py # Dev data seeder
│ ├── alembic/ # DB migrations
│ ├── tests/
│ │ ├── unit/
│ │ ├── integration/
│ │ └── contract/
│ └── pyproject.toml
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ ├── pages/
│ │ ├── hooks/
│ │ ├── api/ # Generated API client
│ │ └── App.tsx
│ ├── tests/
│ └── package.json
├── docker-compose.yml
└── specs/
```
## Regras Iniciais (Seed)
O seed popula 5 regras padrão:
| Regra | Condição | Peso |
|-------|----------|------|
| Valor atípico | `amount > customer.avg_amount_30d * 3` | 30 |
| Local incomum | `distance(current_location, customer.home_location) > 100km` | 25 |
| Horário suspeito | `hour >= 0 and hour <= 5 and amount > 500` | 20 |
| Categoria rara | `merchant_category not in customer.common_categories` | 15 |
| Canal novo | `channel not in customer.common_channels` | 10 |
Threshold padrão do sistema: **70/100**.

View file

@ -0,0 +1,127 @@
# Research: Motor de Detecção de Fraudes Bancárias
**Feature**: 001-fraud-detection-engine
**Date**: 2026-05-11
**Status**: Complete
## 1. Backend Runtime & Framework
**Decision**: Python 3.12 + FastAPI
**Rationale**:
- Python é a linguagem dominante em data science e financial services — facilita contratação e evolução futura para ML.
- FastAPI oferece async nativo, validação automática via Pydantic, OpenAPI auto-generated, e performance comparável a Node.js.
- Ecossistema rico para processamento de dados (pandas, numpy) útil para análise de transações e backtesting de regras.
- Tipagem estática com mypy alinha-se com o princípio de Test-First da constituição.
**Alternatives considered**:
- **Node.js + Express**: Bom para I/O, mas ecossistema de data processing inferior. Regras complexas seriam mais verbosas.
- **Go**: Performance superior, mas ecossistema de dados limitado e curva de aprendizado maior para analistas que contribuem com regras.
- **Java + Spring Boot**: Maduro para banking, mas verboso e lento para iteração. Overkill para MVP.
## 2. Frontend Framework
**Decision**: React 19 + TypeScript + Tailwind CSS
**Rationale**:
- Dashboard com múltiplos gráficos e filtros se beneficia do modelo de componentes React.
- TypeScript alinha-se com a tipagem do backend (Pydantic → TypeScript types via OpenAPI codegen).
- Tailwind CSS para velocidade de desenvolvimento e consistência visual sem CSS complexo.
- Recharts ou Tremor para gráficos de dashboard (nativos React, bem mantidos).
**Alternatives considered**:
- **HTMX + templates Jinja2**: Mais simples, mas dashboard interativo com filtros dinâmicos e real-time updates seria complexo.
- **Vue 3 + Nuxt**: Excelente framework, mas React tem ecossistema maior de componentes de dashboard.
- **Svelte**: Performance superior, mas ecossistema de componentes para dashboard ainda imaturo comparado a React.
## 3. Database
**Decision**: PostgreSQL 16
**Rationale**:
- Audit log imutável: PostgreSQL com triggers ou particionamento por mês atende sem complexidade adicional.
- JSONB para payload flexível das transações e regras.
- Full-text search nativo para busca em explicações.
- Window functions para cálculos de médias móveis (baseline do cliente).
- `pgAudit` extension para camada extra de auditoria a nível de banco.
- Row-Level Security como defesa em profundidade.
**Alternatives considered**:
- **MongoDB**: Schema flexível para transações, mas audit log imutável é mais natural em relacional. Transações ACID são essenciais para dados financeiros.
- **SQLite**: Ótimo para dev/local, insuficiente para concorrência em produção.
- **TimescaleDB (extensão PostgreSQL)**: Considerar para hypertables de transações se volume crescer além de 100k/dia. Não necessário para MVP.
## 4. Cache & Real-Time
**Decision**: Redis 7
**Rationale**:
- Contadores em tempo real do dashboard (transações/minuto, alertas/24h) via Redis `INCR` + TTL.
- Cache de médias do cliente (gasto médio, localização habitual) para o motor de regras (evita JOIN pesado a cada transação).
- Rate limiting da API de ingestão.
- Pub/Sub para notificações de novos alertas no dashboard (SSE ou WebSocket).
**Alternatives considered**:
- **Sem cache (query direta no PostgreSQL)**: Viável para MVP com <10k transações/dia, mas dashboard near real-time sofreria.
- **Memcached**: Mais simples, mas sem Pub/Sub e estruturas de dados avançadas.
## 5. Message Queue (Picos de Volume)
**Decision**: Redis Streams (MVP) → RabbitMQ (scale)
**Rationale**:
- Para MVP, Redis Streams oferece consumer groups, acknowledgments, e já está na stack.
- Se volume crescer, migrar para RabbitMQ que oferece dead letter queues, retry policies, e routing complexo.
- Padrão: API de ingestão → Redis Streams → Workers de processamento → PostgreSQL.
- Desacopla ingestão do processamento, essencial para os picos de 5x mencionados na spec.
**Alternatives considered**:
- **Kafka**: Overkill para MVP. Operação complexa.
- **Processamento síncrono**: Violaria SC-001 (2s p95) durante picos.
## 6. Rule Engine Architecture
**Decision**: Python rules engine built on Pydantic models + expression evaluator
**Rationale**:
- Regras são condições booleanas com peso. Exemplo: `transaction.amount > customer.avg_amount * 3` → score 30.
- Pydantic models definem o schema da regra, validação automática.
- `simpleeval` ou `lark-parser` para avaliar expressões de forma segura (sem `eval()` nativo).
- Cada regra é uma classe Python que implementa `evaluate(transaction, context) -> RuleResult`.
- Conjunto de regras é carregado do PostgreSQL no boot e cacheado em memória. Reload via endpoint admin.
- Snapshot versionado: cada transação registra qual versão do ruleset foi usada (rastreabilidade → FR-005).
**Alternatives considered**:
- **Drools (Java)**: Maduro mas exige JVM, complexidade desnecessária.
- **Django Rules**: Acoplado ao Django ORM, não adequado para FastAPI.
- **JSONLogic**: Bom para regras simples, limitado para expressões aritméticas complexas.
## 7. Testing Strategy
**Decision**: pytest + pytest-asyncio (backend), Vitest + React Testing Library (frontend)
**Rationale**:
- TDD mandatório por constituição.
- Testes de regra: dataset de transações com fraudes conhecidas → assert score, assert explicação, assert alerta.
- Testes de contrato: OpenAPI schema validation, garantir que API não quebra entre versões.
- Testes de integração: pipeline completo (API → rules → alerta → decisão).
- Testes E2E: Playwright para fluxos do analista (login → fila → avalia alerta → decide).
## 8. Authentication & Authorization
**Decision**: JWT + OAuth2/OIDC via external IdP (Keycloak ou Auth0)
**Rationale**:
- Spec assume identity provider externo. FastAPI tem suporte nativo a OAuth2 + JWT.
- Perfis (analista, admin, sênior) mapeados para claims no token.
- Middleware de autorização por rota verifica claims.
- Sem senhas armazenadas no FraudShield — compliance LGPD simplificada.
## 9. Deployment
**Decision**: Docker Compose (dev/local) → Kubernetes ou cloud PaaS (produção)
**Rationale**:
- Docker Compose para ambiente dev: PostgreSQL + Redis + API + Frontend em containers.
- Simplicidade para MVP: um `docker compose up` resolve.
- Produção depende de onde hospedar (AWS ECS, GCP Cloud Run, Railway, etc.). Fora do escopo do MVP decidir.