mirror of
https://github.com/domfelipe/fraudshield.git
synced 2026-08-07 13:16:53 +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
803
specs/001-fraud-detection-engine/contracts/api.yaml
Normal file
803
specs/001-fraud-detection-engine/contracts/api.yaml
Normal 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
|
||||
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}
|
||||
177
specs/001-fraud-detection-engine/plan.md
Normal file
177
specs/001-fraud-detection-engine/plan.md
Normal 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.
|
||||
190
specs/001-fraud-detection-engine/quickstart.md
Normal file
190
specs/001-fraud-detection-engine/quickstart.md
Normal 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**.
|
||||
127
specs/001-fraud-detection-engine/research.md
Normal file
127
specs/001-fraud-detection-engine/research.md
Normal 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue