diff --git a/AGENTS.md b/AGENTS.md index 7e6443a..e077469 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,18 @@ 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 diff --git a/specs/001-fraud-detection-engine/contracts/api.yaml b/specs/001-fraud-detection-engine/contracts/api.yaml new file mode 100644 index 0000000..04bef9b --- /dev/null +++ b/specs/001-fraud-detection-engine/contracts/api.yaml @@ -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 diff --git a/specs/001-fraud-detection-engine/data-model.md b/specs/001-fraud-detection-engine/data-model.md new file mode 100644 index 0000000..e63d29f --- /dev/null +++ b/specs/001-fraud-detection-engine/data-model.md @@ -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} diff --git a/specs/001-fraud-detection-engine/plan.md b/specs/001-fraud-detection-engine/plan.md new file mode 100644 index 0000000..097252e --- /dev/null +++ b/specs/001-fraud-detection-engine/plan.md @@ -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. diff --git a/specs/001-fraud-detection-engine/quickstart.md b/specs/001-fraud-detection-engine/quickstart.md new file mode 100644 index 0000000..9153f95 --- /dev/null +++ b/specs/001-fraud-detection-engine/quickstart.md @@ -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 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 " +``` + +### 3. Decidir sobre um alerta + +```bash +curl -X POST http://localhost:8000/api/v1/alerts//decide \ + -H "Authorization: Bearer " \ + -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 " \ + -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//test \ + -H "Authorization: Bearer " \ + -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 " +``` + +## 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**. diff --git a/specs/001-fraud-detection-engine/research.md b/specs/001-fraud-detection-engine/research.md new file mode 100644 index 0000000..5396401 --- /dev/null +++ b/specs/001-fraud-detection-engine/research.md @@ -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.