feat: implement FraudShield MVP — backend API + frontend alerts

Backend (Python 3.12/FastAPI):
- 5 models: Transaction, DetectionRule, FraudAlert, AuditLog, RuleSnapshot
- Rule engine: 4 rule types (amount, location, time, pattern)
- 11 API routes: health, auth, transactions, alerts
- Sync ingestion pipeline with explanation generation
- JWT auth with 3 roles (analyst, admin, senior)
- Seed script with 5 default rules + 50 synthetic transactions

Frontend (React 19/TypeScript/Tailwind):
- Auth: login page, JWT token management, role-based routing
- Alerts: queue with filters, detail panel, decision workflow
- Layout: responsive sidebar, top bar, protected routes
- Full TypeScript, zero ts-ignore, Tailwind CSS

Build: Backend imports clean, Frontend tsc --noEmit passes,
vite build produces 296KB production bundle.
This commit is contained in:
Felipe Domingues 2026-05-12 16:54:03 -03:00
parent 14f2616037
commit 989202648d
60 changed files with 6943 additions and 0 deletions

113
frontend/src/api/client.ts Normal file
View file

@ -0,0 +1,113 @@
const API_BASE = '/api/v1';
function getAuthHeaders(): Record<string, string> {
const token = localStorage.getItem('fraudshield_token');
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
if (token) headers['Authorization'] = `Bearer ${token}`;
return headers;
}
async function apiFetch<T>(path: string, options: RequestInit = {}): Promise<T> {
const res = await fetch(`${API_BASE}${path}`, {
...options,
headers: { ...getAuthHeaders(), ...((options.headers as Record<string, string>) || {}) },
});
if (!res.ok) {
const err = await res.json().catch(() => ({ detail: res.statusText }));
throw new Error(err.detail || `HTTP ${res.status}`);
}
return res.json();
}
export interface AlertFilters {
status?: string;
min_score?: number;
from_date?: string;
to_date?: string;
triggered_rule?: string;
limit?: number;
offset?: number;
}
export interface TriggeredRule {
rule_id: string;
rule_name: string;
score: number;
explanation: string;
}
export interface AlertSummary {
id: string;
score: number;
status: string;
amount: number;
merchant_name: string;
top_rule: string;
created_at: string;
}
export interface AlertDetail {
id: string;
transaction_id: string;
score: number;
triggered_rules: TriggeredRule[];
explanation: string;
status: string;
assigned_to?: string | null;
decision?: string | null;
decided_by?: string | null;
decided_at?: string | null;
justification?: string | null;
created_at: string;
amount: number;
merchant_name: string;
merchant_category: string;
channel: string;
}
export interface AlertListResponse {
items: AlertSummary[];
total: number;
limit: number;
offset: number;
}
export interface AlertDecisionRequest {
decision: string;
justification?: string;
notes?: string;
}
export interface TransactionAccepted {
transaction_id: string;
status: string;
trace_id: string;
}
export interface DashboardMetrics {
total_transactions: number;
total_alerts: number;
fraud_rate_pct: number;
false_positive_rate_pct: number;
alerts_by_status: { pending: number; confirmed: number; false_positive: number; escalated: number };
}
export function listAlerts(params: AlertFilters = {}): Promise<AlertListResponse> {
const qs = new URLSearchParams();
Object.entries(params).forEach(([k, v]) => { if (v !== undefined) qs.set(k, String(v)); });
return apiFetch<AlertListResponse>(`/alerts?${qs.toString()}`);
}
export function getAlert(id: string): Promise<AlertDetail> {
return apiFetch<AlertDetail>(`/alerts/${id}`);
}
export function decideAlert(id: string, data: AlertDecisionRequest): Promise<AlertDetail> {
return apiFetch<AlertDetail>(`/alerts/${id}/decide`, { method: 'POST', body: JSON.stringify(data) });
}
export function getDashboardMetrics(period = '24h'): Promise<DashboardMetrics> {
return apiFetch<DashboardMetrics>(`/dashboard/metrics?period=${period}`);
}
export { getAuthHeaders };