mirror of
https://github.com/domfelipe/fraudshield.git
synced 2026-08-07 07:36:52 +00:00
plan: add implementation plan for fraud detection engine
Stack: Python 3.12/FastAPI + React 19/TypeScript + PostgreSQL + Redis. 6 artifacts: plan.md, research.md, data-model.md, api.yaml (OpenAPI), quickstart.md, updated AGENTS.md. Constitution check: 6/6 PASS.
This commit is contained in:
parent
e31cba2847
commit
7f9832bf4b
6 changed files with 1487 additions and 1 deletions
16
AGENTS.md
16
AGENTS.md
|
|
@ -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 -->
|
||||||
|
|
|
||||||
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