mirror of
https://github.com/freedbygrace/SQL.git
synced 2026-07-27 03:48:58 +00:00
Add complete financial fraud detection database with Docker, schema, data generation, and SQL exercises
- Docker setup with PostgreSQL 16 and DB-UI web interface - Comprehensive 20+ table schema with fraud detection patterns - Idempotent shell scripts for data generation (no Python dependency) - Realistic geographic data (100 US cities, 210 world cities) - 5M+ transactions with embedded fraud patterns (velocity, geographic, structuring, etc.) - Progressive SQL exercises from beginner to advanced fraud detection - Complete documentation and quick start guide - Setup and verification scripts
This commit is contained in:
@@ -0,0 +1,477 @@
|
||||
-- ============================================================================
|
||||
-- Financial Fraud Detection Database Schema
|
||||
-- Purpose: Educational SQL learning with realistic fraud investigation scenarios
|
||||
-- ============================================================================
|
||||
-- This script is IDEMPOTENT - it will drop and recreate all objects
|
||||
-- ============================================================================
|
||||
|
||||
-- Enable required extensions
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
|
||||
-- ============================================================================
|
||||
-- DROP ALL EXISTING OBJECTS (in reverse dependency order)
|
||||
-- ============================================================================
|
||||
|
||||
-- Drop triggers first
|
||||
DROP TRIGGER IF EXISTS trg_update_balance ON transactions;
|
||||
DROP TRIGGER IF EXISTS trg_check_suspicious ON transactions;
|
||||
|
||||
-- Drop functions
|
||||
DROP FUNCTION IF EXISTS update_account_balance() CASCADE;
|
||||
DROP FUNCTION IF EXISTS check_suspicious_transaction() CASCADE;
|
||||
DROP FUNCTION IF EXISTS generate_fraud_score(DECIMAL, BOOLEAN, DECIMAL, INT, BOOLEAN) CASCADE;
|
||||
|
||||
-- Drop tables in reverse dependency order
|
||||
DROP TABLE IF EXISTS audit_log CASCADE;
|
||||
DROP TABLE IF EXISTS suspicious_activity_reports CASCADE;
|
||||
DROP TABLE IF EXISTS case_alerts CASCADE;
|
||||
DROP TABLE IF EXISTS case_transactions CASCADE;
|
||||
DROP TABLE IF EXISTS fraud_cases CASCADE;
|
||||
DROP TABLE IF EXISTS alerts CASCADE;
|
||||
DROP TABLE IF EXISTS transfers CASCADE;
|
||||
DROP TABLE IF EXISTS beneficiaries CASCADE;
|
||||
DROP TABLE IF EXISTS transactions CASCADE;
|
||||
DROP TABLE IF EXISTS login_sessions CASCADE;
|
||||
DROP TABLE IF EXISTS devices CASCADE;
|
||||
DROP TABLE IF EXISTS cards CASCADE;
|
||||
DROP TABLE IF EXISTS accounts CASCADE;
|
||||
DROP TABLE IF EXISTS customer_relationships CASCADE;
|
||||
DROP TABLE IF EXISTS customers CASCADE;
|
||||
DROP TABLE IF EXISTS merchants CASCADE;
|
||||
DROP TABLE IF EXISTS merchant_categories CASCADE;
|
||||
DROP TABLE IF EXISTS fraud_types CASCADE;
|
||||
DROP TABLE IF EXISTS transaction_types CASCADE;
|
||||
DROP TABLE IF EXISTS countries CASCADE;
|
||||
|
||||
-- ============================================================================
|
||||
-- REFERENCE/LOOKUP TABLES
|
||||
-- ============================================================================
|
||||
|
||||
-- Countries reference table
|
||||
CREATE TABLE countries (
|
||||
country_id SERIAL PRIMARY KEY,
|
||||
country_code CHAR(2) NOT NULL UNIQUE,
|
||||
country_name VARCHAR(100) NOT NULL,
|
||||
region VARCHAR(50) NOT NULL,
|
||||
risk_level VARCHAR(20) DEFAULT 'LOW' CHECK (risk_level IN ('LOW', 'MEDIUM', 'HIGH', 'CRITICAL')),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Merchant categories
|
||||
CREATE TABLE merchant_categories (
|
||||
category_id SERIAL PRIMARY KEY,
|
||||
category_code VARCHAR(10) NOT NULL UNIQUE,
|
||||
category_name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
risk_weight DECIMAL(3,2) DEFAULT 1.00,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Transaction types
|
||||
CREATE TABLE transaction_types (
|
||||
type_id SERIAL PRIMARY KEY,
|
||||
type_code VARCHAR(20) NOT NULL UNIQUE,
|
||||
type_name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
requires_merchant BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Fraud types
|
||||
CREATE TABLE fraud_types (
|
||||
fraud_type_id SERIAL PRIMARY KEY,
|
||||
fraud_code VARCHAR(20) NOT NULL UNIQUE,
|
||||
fraud_name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
severity VARCHAR(20) DEFAULT 'MEDIUM' CHECK (severity IN ('LOW', 'MEDIUM', 'HIGH', 'CRITICAL')),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- CUSTOMER DOMAIN
|
||||
-- ============================================================================
|
||||
|
||||
-- Customers table
|
||||
CREATE TABLE customers (
|
||||
customer_id BIGSERIAL PRIMARY KEY,
|
||||
first_name VARCHAR(100) NOT NULL,
|
||||
last_name VARCHAR(100) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
phone VARCHAR(20),
|
||||
date_of_birth DATE NOT NULL,
|
||||
ssn_hash VARCHAR(64) NOT NULL UNIQUE, -- Hashed SSN for privacy
|
||||
address_line1 VARCHAR(255),
|
||||
address_line2 VARCHAR(255),
|
||||
city VARCHAR(100),
|
||||
state VARCHAR(50),
|
||||
postal_code VARCHAR(20),
|
||||
country_id INT NOT NULL REFERENCES countries(country_id),
|
||||
registration_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login TIMESTAMP,
|
||||
kyc_status VARCHAR(20) DEFAULT 'PENDING' CHECK (kyc_status IN ('PENDING', 'VERIFIED', 'REJECTED', 'EXPIRED')),
|
||||
kyc_verified_date TIMESTAMP,
|
||||
risk_score DECIMAL(5,2) DEFAULT 50.00 CHECK (risk_score BETWEEN 0 AND 100),
|
||||
is_pep BOOLEAN DEFAULT FALSE, -- Politically Exposed Person
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Customer relationships (for detecting collusion networks)
|
||||
CREATE TABLE customer_relationships (
|
||||
relationship_id BIGSERIAL PRIMARY KEY,
|
||||
customer_id_1 BIGINT NOT NULL REFERENCES customers(customer_id),
|
||||
customer_id_2 BIGINT NOT NULL REFERENCES customers(customer_id),
|
||||
relationship_type VARCHAR(50) NOT NULL CHECK (relationship_type IN ('FAMILY', 'BUSINESS', 'SHARED_ADDRESS', 'SHARED_DEVICE', 'SHARED_IP', 'SUSPECTED_MULE')),
|
||||
confidence_score DECIMAL(5,2) DEFAULT 50.00 CHECK (confidence_score BETWEEN 0 AND 100),
|
||||
detected_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
notes TEXT,
|
||||
CONSTRAINT different_customers CHECK (customer_id_1 != customer_id_2),
|
||||
CONSTRAINT unique_relationship UNIQUE (customer_id_1, customer_id_2, relationship_type)
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- ACCOUNT DOMAIN
|
||||
-- ============================================================================
|
||||
|
||||
-- Accounts table
|
||||
CREATE TABLE accounts (
|
||||
account_id BIGSERIAL PRIMARY KEY,
|
||||
customer_id BIGINT NOT NULL REFERENCES customers(customer_id),
|
||||
account_number VARCHAR(20) NOT NULL UNIQUE,
|
||||
account_type VARCHAR(20) NOT NULL CHECK (account_type IN ('CHECKING', 'SAVINGS', 'CREDIT', 'INVESTMENT', 'LOAN')),
|
||||
currency CHAR(3) DEFAULT 'USD',
|
||||
opening_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
closing_date DATE,
|
||||
status VARCHAR(20) DEFAULT 'ACTIVE' CHECK (status IN ('ACTIVE', 'SUSPENDED', 'CLOSED', 'FROZEN')),
|
||||
current_balance DECIMAL(15,2) DEFAULT 0.00,
|
||||
available_balance DECIMAL(15,2) DEFAULT 0.00,
|
||||
credit_limit DECIMAL(15,2),
|
||||
overdraft_limit DECIMAL(15,2) DEFAULT 0.00,
|
||||
interest_rate DECIMAL(5,4),
|
||||
monthly_fee DECIMAL(8,2) DEFAULT 0.00,
|
||||
is_primary BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Cards table
|
||||
CREATE TABLE cards (
|
||||
card_id BIGSERIAL PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL REFERENCES accounts(account_id),
|
||||
card_number_hash VARCHAR(64) NOT NULL UNIQUE, -- Hashed card number
|
||||
card_last_four CHAR(4) NOT NULL,
|
||||
card_type VARCHAR(20) NOT NULL CHECK (card_type IN ('DEBIT', 'CREDIT', 'PREPAID', 'VIRTUAL')),
|
||||
card_network VARCHAR(20) NOT NULL CHECK (card_network IN ('VISA', 'MASTERCARD', 'AMEX', 'DISCOVER')),
|
||||
issue_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
expiry_date DATE NOT NULL,
|
||||
cvv_hash VARCHAR(64) NOT NULL,
|
||||
status VARCHAR(20) DEFAULT 'ACTIVE' CHECK (status IN ('ACTIVE', 'BLOCKED', 'EXPIRED', 'LOST', 'STOLEN')),
|
||||
daily_limit DECIMAL(10,2) DEFAULT 5000.00,
|
||||
monthly_limit DECIMAL(12,2) DEFAULT 50000.00,
|
||||
is_contactless BOOLEAN DEFAULT TRUE,
|
||||
is_international BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- MERCHANT DOMAIN
|
||||
-- ============================================================================
|
||||
|
||||
-- Merchants table
|
||||
CREATE TABLE merchants (
|
||||
merchant_id BIGSERIAL PRIMARY KEY,
|
||||
merchant_name VARCHAR(255) NOT NULL,
|
||||
merchant_code VARCHAR(50) UNIQUE,
|
||||
category_id INT NOT NULL REFERENCES merchant_categories(category_id),
|
||||
country_id INT NOT NULL REFERENCES countries(country_id),
|
||||
city VARCHAR(100),
|
||||
website VARCHAR(255),
|
||||
registration_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
status VARCHAR(20) DEFAULT 'ACTIVE' CHECK (status IN ('ACTIVE', 'SUSPENDED', 'BLACKLISTED', 'CLOSED')),
|
||||
risk_rating VARCHAR(20) DEFAULT 'LOW' CHECK (risk_rating IN ('LOW', 'MEDIUM', 'HIGH', 'CRITICAL')),
|
||||
total_transactions BIGINT DEFAULT 0,
|
||||
total_volume DECIMAL(18,2) DEFAULT 0.00,
|
||||
fraud_incidents INT DEFAULT 0,
|
||||
is_verified BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- DEVICE & SESSION DOMAIN
|
||||
-- ============================================================================
|
||||
|
||||
-- Devices table (for tracking login devices)
|
||||
CREATE TABLE devices (
|
||||
device_id BIGSERIAL PRIMARY KEY,
|
||||
device_fingerprint VARCHAR(64) NOT NULL UNIQUE,
|
||||
device_type VARCHAR(20) CHECK (device_type IN ('MOBILE', 'TABLET', 'DESKTOP', 'OTHER')),
|
||||
os_name VARCHAR(50),
|
||||
os_version VARCHAR(50),
|
||||
browser_name VARCHAR(50),
|
||||
browser_version VARCHAR(50),
|
||||
first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
is_trusted BOOLEAN DEFAULT FALSE,
|
||||
is_blacklisted BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Login sessions
|
||||
CREATE TABLE login_sessions (
|
||||
session_id BIGSERIAL PRIMARY KEY,
|
||||
customer_id BIGINT NOT NULL REFERENCES customers(customer_id),
|
||||
device_id BIGINT NOT NULL REFERENCES devices(device_id),
|
||||
ip_address INET NOT NULL,
|
||||
country_id INT REFERENCES countries(country_id),
|
||||
city VARCHAR(100),
|
||||
latitude DECIMAL(10,8),
|
||||
longitude DECIMAL(11,8),
|
||||
login_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
logout_timestamp TIMESTAMP,
|
||||
session_duration_seconds INT,
|
||||
is_successful BOOLEAN DEFAULT TRUE,
|
||||
failure_reason VARCHAR(255),
|
||||
risk_score DECIMAL(5,2) DEFAULT 0.00,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- TRANSACTION DOMAIN
|
||||
-- ============================================================================
|
||||
|
||||
-- Transactions table (main transaction log)
|
||||
CREATE TABLE transactions (
|
||||
transaction_id BIGSERIAL PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL REFERENCES accounts(account_id),
|
||||
type_id INT NOT NULL REFERENCES transaction_types(type_id),
|
||||
transaction_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
amount DECIMAL(15,2) NOT NULL CHECK (amount > 0),
|
||||
currency CHAR(3) DEFAULT 'USD',
|
||||
merchant_id BIGINT REFERENCES merchants(merchant_id),
|
||||
card_id BIGINT REFERENCES cards(card_id),
|
||||
device_id BIGINT REFERENCES devices(device_id),
|
||||
ip_address INET,
|
||||
country_id INT REFERENCES countries(country_id),
|
||||
city VARCHAR(100),
|
||||
latitude DECIMAL(10,8),
|
||||
longitude DECIMAL(11,8),
|
||||
description TEXT,
|
||||
reference_number VARCHAR(50) UNIQUE,
|
||||
status VARCHAR(20) DEFAULT 'COMPLETED' CHECK (status IN ('PENDING', 'COMPLETED', 'FAILED', 'REVERSED', 'FLAGGED', 'BLOCKED')),
|
||||
is_online BOOLEAN DEFAULT TRUE,
|
||||
is_international BOOLEAN DEFAULT FALSE,
|
||||
is_card_present BOOLEAN DEFAULT FALSE,
|
||||
fraud_score DECIMAL(5,2) DEFAULT 0.00 CHECK (fraud_score BETWEEN 0 AND 100),
|
||||
is_flagged BOOLEAN DEFAULT FALSE,
|
||||
flagged_reason TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Beneficiaries (for transfers)
|
||||
CREATE TABLE beneficiaries (
|
||||
beneficiary_id BIGSERIAL PRIMARY KEY,
|
||||
customer_id BIGINT NOT NULL REFERENCES customers(customer_id),
|
||||
beneficiary_name VARCHAR(255) NOT NULL,
|
||||
account_number VARCHAR(50) NOT NULL,
|
||||
bank_name VARCHAR(255),
|
||||
bank_code VARCHAR(20),
|
||||
country_id INT NOT NULL REFERENCES countries(country_id),
|
||||
relationship VARCHAR(50),
|
||||
is_verified BOOLEAN DEFAULT FALSE,
|
||||
added_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
last_used TIMESTAMP,
|
||||
total_transfers INT DEFAULT 0,
|
||||
total_amount DECIMAL(18,2) DEFAULT 0.00,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Transfer transactions
|
||||
CREATE TABLE transfers (
|
||||
transfer_id BIGSERIAL PRIMARY KEY,
|
||||
transaction_id BIGINT NOT NULL REFERENCES transactions(transaction_id),
|
||||
from_account_id BIGINT NOT NULL REFERENCES accounts(account_id),
|
||||
to_account_id BIGINT REFERENCES accounts(account_id), -- NULL for external transfers
|
||||
beneficiary_id BIGINT REFERENCES beneficiaries(beneficiary_id),
|
||||
transfer_type VARCHAR(20) NOT NULL CHECK (transfer_type IN ('INTERNAL', 'DOMESTIC', 'INTERNATIONAL', 'WIRE')),
|
||||
purpose VARCHAR(255),
|
||||
is_recurring BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- FRAUD DETECTION & ALERTS DOMAIN
|
||||
-- ============================================================================
|
||||
|
||||
-- Alerts table (system-generated suspicious activity alerts)
|
||||
CREATE TABLE alerts (
|
||||
alert_id BIGSERIAL PRIMARY KEY,
|
||||
transaction_id BIGINT REFERENCES transactions(transaction_id),
|
||||
customer_id BIGINT REFERENCES customers(customer_id),
|
||||
account_id BIGINT REFERENCES accounts(account_id),
|
||||
alert_type VARCHAR(50) NOT NULL CHECK (alert_type IN (
|
||||
'VELOCITY_CHECK', 'AMOUNT_ANOMALY', 'GEOGRAPHIC_ANOMALY',
|
||||
'MERCHANT_RISK', 'DEVICE_CHANGE', 'UNUSUAL_TIME',
|
||||
'MULTIPLE_CARDS', 'ACCOUNT_TAKEOVER', 'MONEY_MULE', 'STRUCTURING'
|
||||
)),
|
||||
severity VARCHAR(20) DEFAULT 'MEDIUM' CHECK (severity IN ('LOW', 'MEDIUM', 'HIGH', 'CRITICAL')),
|
||||
alert_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
description TEXT NOT NULL,
|
||||
risk_score DECIMAL(5,2) DEFAULT 50.00 CHECK (risk_score BETWEEN 0 AND 100),
|
||||
status VARCHAR(20) DEFAULT 'OPEN' CHECK (status IN ('OPEN', 'INVESTIGATING', 'CLOSED', 'FALSE_POSITIVE', 'CONFIRMED_FRAUD')),
|
||||
assigned_to VARCHAR(100),
|
||||
reviewed_date TIMESTAMP,
|
||||
resolution_notes TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Fraud cases (confirmed fraud incidents)
|
||||
CREATE TABLE fraud_cases (
|
||||
case_id BIGSERIAL PRIMARY KEY,
|
||||
case_number VARCHAR(50) NOT NULL UNIQUE,
|
||||
customer_id BIGINT REFERENCES customers(customer_id),
|
||||
account_id BIGINT REFERENCES accounts(account_id),
|
||||
fraud_type_id INT NOT NULL REFERENCES fraud_types(fraud_type_id),
|
||||
detection_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
detection_method VARCHAR(50) CHECK (detection_method IN ('AUTOMATED', 'CUSTOMER_REPORT', 'MANUAL_REVIEW', 'THIRD_PARTY')),
|
||||
amount_lost DECIMAL(15,2) DEFAULT 0.00,
|
||||
amount_recovered DECIMAL(15,2) DEFAULT 0.00,
|
||||
status VARCHAR(20) DEFAULT 'OPEN' CHECK (status IN ('OPEN', 'INVESTIGATING', 'RESOLVED', 'CLOSED', 'LEGAL_ACTION')),
|
||||
priority VARCHAR(20) DEFAULT 'MEDIUM' CHECK (priority IN ('LOW', 'MEDIUM', 'HIGH', 'CRITICAL')),
|
||||
assigned_investigator VARCHAR(100),
|
||||
investigation_notes TEXT,
|
||||
resolution_date TIMESTAMP,
|
||||
resolution_summary TEXT,
|
||||
law_enforcement_notified BOOLEAN DEFAULT FALSE,
|
||||
customer_notified BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Case transactions (linking transactions to fraud cases)
|
||||
CREATE TABLE case_transactions (
|
||||
case_transaction_id BIGSERIAL PRIMARY KEY,
|
||||
case_id BIGINT NOT NULL REFERENCES fraud_cases(case_id),
|
||||
transaction_id BIGINT NOT NULL REFERENCES transactions(transaction_id),
|
||||
is_fraudulent BOOLEAN DEFAULT TRUE,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT unique_case_transaction UNIQUE (case_id, transaction_id)
|
||||
);
|
||||
|
||||
-- Case alerts (linking alerts to fraud cases)
|
||||
CREATE TABLE case_alerts (
|
||||
case_alert_id BIGSERIAL PRIMARY KEY,
|
||||
case_id BIGINT NOT NULL REFERENCES fraud_cases(case_id),
|
||||
alert_id BIGINT NOT NULL REFERENCES alerts(alert_id),
|
||||
relevance_score DECIMAL(5,2) DEFAULT 50.00,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT unique_case_alert UNIQUE (case_id, alert_id)
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- AUDIT & COMPLIANCE DOMAIN
|
||||
-- ============================================================================
|
||||
|
||||
-- Audit log (comprehensive audit trail)
|
||||
CREATE TABLE audit_log (
|
||||
audit_id BIGSERIAL PRIMARY KEY,
|
||||
table_name VARCHAR(100) NOT NULL,
|
||||
record_id BIGINT NOT NULL,
|
||||
action VARCHAR(20) NOT NULL CHECK (action IN ('INSERT', 'UPDATE', 'DELETE', 'SELECT')),
|
||||
old_values JSONB,
|
||||
new_values JSONB,
|
||||
changed_by VARCHAR(100),
|
||||
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
ip_address INET,
|
||||
user_agent TEXT
|
||||
);
|
||||
|
||||
-- Suspicious Activity Reports (SAR)
|
||||
CREATE TABLE suspicious_activity_reports (
|
||||
sar_id BIGSERIAL PRIMARY KEY,
|
||||
sar_number VARCHAR(50) NOT NULL UNIQUE,
|
||||
case_id BIGINT REFERENCES fraud_cases(case_id),
|
||||
customer_id BIGINT NOT NULL REFERENCES customers(customer_id),
|
||||
filing_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
activity_date_from DATE NOT NULL,
|
||||
activity_date_to DATE NOT NULL,
|
||||
total_amount DECIMAL(18,2) NOT NULL,
|
||||
activity_description TEXT NOT NULL,
|
||||
filed_by VARCHAR(100) NOT NULL,
|
||||
status VARCHAR(20) DEFAULT 'DRAFT' CHECK (status IN ('DRAFT', 'SUBMITTED', 'ACKNOWLEDGED', 'CLOSED')),
|
||||
submission_date DATE,
|
||||
acknowledgment_date DATE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- INDEXES FOR PERFORMANCE
|
||||
-- ============================================================================
|
||||
|
||||
-- Customer indexes
|
||||
CREATE INDEX idx_customers_email ON customers(email);
|
||||
CREATE INDEX idx_customers_country ON customers(country_id);
|
||||
CREATE INDEX idx_customers_risk_score ON customers(risk_score DESC);
|
||||
CREATE INDEX idx_customers_registration_date ON customers(registration_date);
|
||||
|
||||
-- Account indexes
|
||||
CREATE INDEX idx_accounts_customer ON accounts(customer_id);
|
||||
CREATE INDEX idx_accounts_status ON accounts(status);
|
||||
CREATE INDEX idx_accounts_type ON accounts(account_type);
|
||||
|
||||
-- Transaction indexes (critical for performance)
|
||||
CREATE INDEX idx_transactions_account ON transactions(account_id);
|
||||
CREATE INDEX idx_transactions_date ON transactions(transaction_date DESC);
|
||||
CREATE INDEX idx_transactions_merchant ON transactions(merchant_id);
|
||||
CREATE INDEX idx_transactions_status ON transactions(status);
|
||||
CREATE INDEX idx_transactions_flagged ON transactions(is_flagged) WHERE is_flagged = TRUE;
|
||||
CREATE INDEX idx_transactions_fraud_score ON transactions(fraud_score DESC);
|
||||
CREATE INDEX idx_transactions_amount ON transactions(amount);
|
||||
CREATE INDEX idx_transactions_country ON transactions(country_id);
|
||||
|
||||
-- Card indexes
|
||||
CREATE INDEX idx_cards_account ON cards(account_id);
|
||||
CREATE INDEX idx_cards_status ON cards(status);
|
||||
|
||||
-- Alert indexes
|
||||
CREATE INDEX idx_alerts_customer ON alerts(customer_id);
|
||||
CREATE INDEX idx_alerts_transaction ON alerts(transaction_id);
|
||||
CREATE INDEX idx_alerts_status ON alerts(status);
|
||||
CREATE INDEX idx_alerts_date ON alerts(alert_date DESC);
|
||||
CREATE INDEX idx_alerts_severity ON alerts(severity);
|
||||
|
||||
-- Fraud case indexes
|
||||
CREATE INDEX idx_fraud_cases_customer ON fraud_cases(customer_id);
|
||||
CREATE INDEX idx_fraud_cases_status ON fraud_cases(status);
|
||||
CREATE INDEX idx_fraud_cases_detection_date ON fraud_cases(detection_date DESC);
|
||||
|
||||
-- Login session indexes
|
||||
CREATE INDEX idx_login_sessions_customer ON login_sessions(customer_id);
|
||||
CREATE INDEX idx_login_sessions_timestamp ON login_sessions(login_timestamp DESC);
|
||||
CREATE INDEX idx_login_sessions_ip ON login_sessions(ip_address);
|
||||
|
||||
-- Merchant indexes
|
||||
CREATE INDEX idx_merchants_category ON merchants(category_id);
|
||||
CREATE INDEX idx_merchants_country ON merchants(country_id);
|
||||
CREATE INDEX idx_merchants_risk_rating ON merchants(risk_rating);
|
||||
|
||||
-- Comments for documentation
|
||||
COMMENT ON TABLE customers IS 'Customer master data with KYC and risk information';
|
||||
COMMENT ON TABLE accounts IS 'Customer accounts including checking, savings, credit, etc.';
|
||||
COMMENT ON TABLE transactions IS 'Main transaction log with fraud scoring';
|
||||
COMMENT ON TABLE alerts IS 'System-generated fraud alerts requiring review';
|
||||
COMMENT ON TABLE fraud_cases IS 'Confirmed fraud cases under investigation';
|
||||
COMMENT ON TABLE merchants IS 'Merchant directory with risk ratings';
|
||||
COMMENT ON TABLE cards IS 'Payment cards linked to accounts';
|
||||
COMMENT ON TABLE devices IS 'Device fingerprints for fraud detection';
|
||||
COMMENT ON TABLE login_sessions IS 'Login history for account takeover detection';
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
-- ============================================================================
|
||||
-- Seed Data for Reference Tables
|
||||
-- ============================================================================
|
||||
-- This script is IDEMPOTENT - it will delete and recreate all reference data
|
||||
-- ============================================================================
|
||||
|
||||
-- Clear existing reference data (in reverse dependency order)
|
||||
TRUNCATE TABLE suspicious_activity_reports CASCADE;
|
||||
TRUNCATE TABLE case_alerts CASCADE;
|
||||
TRUNCATE TABLE case_transactions CASCADE;
|
||||
TRUNCATE TABLE fraud_cases CASCADE;
|
||||
TRUNCATE TABLE alerts CASCADE;
|
||||
TRUNCATE TABLE transfers CASCADE;
|
||||
TRUNCATE TABLE beneficiaries CASCADE;
|
||||
TRUNCATE TABLE transactions CASCADE;
|
||||
TRUNCATE TABLE login_sessions CASCADE;
|
||||
TRUNCATE TABLE devices CASCADE;
|
||||
TRUNCATE TABLE cards CASCADE;
|
||||
TRUNCATE TABLE accounts CASCADE;
|
||||
TRUNCATE TABLE customer_relationships CASCADE;
|
||||
TRUNCATE TABLE customers CASCADE;
|
||||
TRUNCATE TABLE merchants CASCADE;
|
||||
TRUNCATE TABLE merchant_categories RESTART IDENTITY CASCADE;
|
||||
TRUNCATE TABLE fraud_types RESTART IDENTITY CASCADE;
|
||||
TRUNCATE TABLE transaction_types RESTART IDENTITY CASCADE;
|
||||
TRUNCATE TABLE countries RESTART IDENTITY CASCADE;
|
||||
|
||||
-- Insert Countries
|
||||
INSERT INTO countries (country_code, country_name, region, risk_level) VALUES
|
||||
('US', 'United States', 'North America', 'LOW'),
|
||||
('CA', 'Canada', 'North America', 'LOW'),
|
||||
('GB', 'United Kingdom', 'Europe', 'LOW'),
|
||||
('DE', 'Germany', 'Europe', 'LOW'),
|
||||
('FR', 'France', 'Europe', 'LOW'),
|
||||
('IT', 'Italy', 'Europe', 'MEDIUM'),
|
||||
('ES', 'Spain', 'Europe', 'MEDIUM'),
|
||||
('AU', 'Australia', 'Oceania', 'LOW'),
|
||||
('JP', 'Japan', 'Asia', 'LOW'),
|
||||
('CN', 'China', 'Asia', 'MEDIUM'),
|
||||
('IN', 'India', 'Asia', 'MEDIUM'),
|
||||
('BR', 'Brazil', 'South America', 'MEDIUM'),
|
||||
('MX', 'Mexico', 'North America', 'MEDIUM'),
|
||||
('RU', 'Russia', 'Europe', 'HIGH'),
|
||||
('NG', 'Nigeria', 'Africa', 'HIGH'),
|
||||
('PK', 'Pakistan', 'Asia', 'HIGH'),
|
||||
('IR', 'Iran', 'Middle East', 'CRITICAL'),
|
||||
('KP', 'North Korea', 'Asia', 'CRITICAL'),
|
||||
('SY', 'Syria', 'Middle East', 'CRITICAL'),
|
||||
('VE', 'Venezuela', 'South America', 'HIGH'),
|
||||
('CU', 'Cuba', 'Caribbean', 'HIGH'),
|
||||
('MM', 'Myanmar', 'Asia', 'HIGH'),
|
||||
('AF', 'Afghanistan', 'Asia', 'CRITICAL'),
|
||||
('IQ', 'Iraq', 'Middle East', 'HIGH'),
|
||||
('LY', 'Libya', 'Africa', 'HIGH'),
|
||||
('SD', 'Sudan', 'Africa', 'HIGH'),
|
||||
('SO', 'Somalia', 'Africa', 'CRITICAL'),
|
||||
('YE', 'Yemen', 'Middle East', 'CRITICAL'),
|
||||
('ZW', 'Zimbabwe', 'Africa', 'HIGH'),
|
||||
('NL', 'Netherlands', 'Europe', 'LOW'),
|
||||
('SE', 'Sweden', 'Europe', 'LOW'),
|
||||
('NO', 'Norway', 'Europe', 'LOW'),
|
||||
('DK', 'Denmark', 'Europe', 'LOW'),
|
||||
('FI', 'Finland', 'Europe', 'LOW'),
|
||||
('CH', 'Switzerland', 'Europe', 'LOW'),
|
||||
('SG', 'Singapore', 'Asia', 'LOW'),
|
||||
('HK', 'Hong Kong', 'Asia', 'MEDIUM'),
|
||||
('KR', 'South Korea', 'Asia', 'LOW'),
|
||||
('TW', 'Taiwan', 'Asia', 'LOW'),
|
||||
('NZ', 'New Zealand', 'Oceania', 'LOW');
|
||||
|
||||
-- Insert Merchant Categories (based on MCC codes)
|
||||
INSERT INTO merchant_categories (category_code, category_name, description, risk_weight) VALUES
|
||||
('5411', 'Grocery Stores', 'Supermarkets and grocery stores', 0.50),
|
||||
('5812', 'Restaurants', 'Eating places and restaurants', 0.60),
|
||||
('5541', 'Gas Stations', 'Service stations and fuel', 0.55),
|
||||
('5311', 'Department Stores', 'General merchandise stores', 0.70),
|
||||
('5912', 'Pharmacies', 'Drug stores and pharmacies', 0.50),
|
||||
('5999', 'Miscellaneous Retail', 'Specialty retail stores', 0.80),
|
||||
('5732', 'Electronics', 'Electronics and computer stores', 1.20),
|
||||
('5651', 'Clothing', 'Family clothing stores', 0.75),
|
||||
('5814', 'Fast Food', 'Quick service restaurants', 0.60),
|
||||
('5942', 'Books', 'Book stores', 0.65),
|
||||
('5945', 'Hobby Shops', 'Hobby, toy, and game shops', 0.70),
|
||||
('5971', 'Art Dealers', 'Art dealers and galleries', 1.50),
|
||||
('5993', 'Cigar Stores', 'Cigar stores and stands', 1.10),
|
||||
('5995', 'Pet Shops', 'Pet shops and supplies', 0.70),
|
||||
('7011', 'Hotels', 'Lodging and hotels', 0.90),
|
||||
('7512', 'Car Rental', 'Automobile rental agencies', 1.00),
|
||||
('7523', 'Parking', 'Parking lots and garages', 0.60),
|
||||
('7832', 'Movie Theaters', 'Motion picture theaters', 0.65),
|
||||
('7922', 'Theatrical Producers', 'Theatrical producers and ticket agencies', 0.80),
|
||||
('7991', 'Tourist Attractions', 'Tourist attractions and exhibits', 0.75),
|
||||
('7995', 'Gambling', 'Betting and casino gambling', 2.50),
|
||||
('5816', 'Digital Goods', 'Digital goods and games', 1.80),
|
||||
('5967', 'Direct Marketing', 'Direct marketing and inbound telemarketing', 1.90),
|
||||
('5966', 'Direct Marketing', 'Outbound telemarketing merchants', 2.00),
|
||||
('6051', 'Cryptocurrency', 'Cryptocurrency and digital currency', 3.00),
|
||||
('6211', 'Securities', 'Securities brokers and dealers', 1.50),
|
||||
('6300', 'Insurance', 'Insurance sales and underwriting', 1.20),
|
||||
('6513', 'Real Estate', 'Real estate agents and managers', 1.30),
|
||||
('7273', 'Dating Services', 'Dating and escort services', 2.20),
|
||||
('7297', 'Massage Parlors', 'Massage parlors', 2.50),
|
||||
('7995', 'Online Gambling', 'Online gambling and betting', 3.50),
|
||||
('5094', 'Precious Metals', 'Precious stones and metals', 2.80),
|
||||
('5933', 'Pawn Shops', 'Pawn shops', 2.60),
|
||||
('5960', 'Mail Order', 'Direct marketing and mail order', 1.70),
|
||||
('4829', 'Wire Transfer', 'Money transfer services', 2.40);
|
||||
|
||||
-- Insert Transaction Types
|
||||
INSERT INTO transaction_types (type_code, type_name, description, requires_merchant) VALUES
|
||||
('PURCHASE', 'Purchase', 'Card purchase at merchant', TRUE),
|
||||
('ATM_WITHDRAWAL', 'ATM Withdrawal', 'Cash withdrawal from ATM', FALSE),
|
||||
('DEPOSIT', 'Deposit', 'Cash or check deposit', FALSE),
|
||||
('TRANSFER_OUT', 'Transfer Out', 'Outgoing transfer', FALSE),
|
||||
('TRANSFER_IN', 'Transfer In', 'Incoming transfer', FALSE),
|
||||
('PAYMENT', 'Bill Payment', 'Bill payment transaction', TRUE),
|
||||
('REFUND', 'Refund', 'Merchant refund', TRUE),
|
||||
('FEE', 'Fee', 'Bank fee or charge', FALSE),
|
||||
('INTEREST', 'Interest', 'Interest credit', FALSE),
|
||||
('WIRE_OUT', 'Wire Transfer Out', 'Outgoing wire transfer', FALSE),
|
||||
('WIRE_IN', 'Wire Transfer In', 'Incoming wire transfer', FALSE),
|
||||
('CHECK', 'Check Payment', 'Check payment', FALSE),
|
||||
('DIRECT_DEBIT', 'Direct Debit', 'Automated direct debit', TRUE),
|
||||
('CASH_ADVANCE', 'Cash Advance', 'Credit card cash advance', FALSE),
|
||||
('BALANCE_TRANSFER', 'Balance Transfer', 'Credit card balance transfer', FALSE);
|
||||
|
||||
-- Insert Fraud Types
|
||||
INSERT INTO fraud_types (fraud_code, fraud_name, description, severity) VALUES
|
||||
('CARD_NOT_PRESENT', 'Card Not Present Fraud', 'Fraudulent online or phone transactions', 'HIGH'),
|
||||
('CARD_STOLEN', 'Stolen Card', 'Transactions using stolen physical card', 'HIGH'),
|
||||
('ACCOUNT_TAKEOVER', 'Account Takeover', 'Unauthorized access to customer account', 'CRITICAL'),
|
||||
('IDENTITY_THEFT', 'Identity Theft', 'Fraudulent account opened with stolen identity', 'CRITICAL'),
|
||||
('FRIENDLY_FRAUD', 'Friendly Fraud', 'Customer disputes legitimate transaction', 'MEDIUM'),
|
||||
('MONEY_MULE', 'Money Mule', 'Account used to launder money', 'CRITICAL'),
|
||||
('SYNTHETIC_IDENTITY', 'Synthetic Identity', 'Fake identity using real and fake information', 'CRITICAL'),
|
||||
('BUST_OUT', 'Bust Out Fraud', 'Building credit then maxing out and disappearing', 'HIGH'),
|
||||
('REFUND_FRAUD', 'Refund Fraud', 'Fraudulent refund requests', 'MEDIUM'),
|
||||
('CHARGEBACK_FRAUD', 'Chargeback Fraud', 'Abusing chargeback process', 'MEDIUM'),
|
||||
('ATM_SKIMMING', 'ATM Skimming', 'Card data stolen via ATM skimmer', 'HIGH'),
|
||||
('PHISHING', 'Phishing', 'Credentials stolen via phishing attack', 'HIGH'),
|
||||
('SIM_SWAP', 'SIM Swap', 'Phone number hijacked for 2FA bypass', 'CRITICAL'),
|
||||
('CHECK_FRAUD', 'Check Fraud', 'Fraudulent or altered checks', 'MEDIUM'),
|
||||
('WIRE_FRAUD', 'Wire Fraud', 'Fraudulent wire transfer', 'CRITICAL'),
|
||||
('STRUCTURING', 'Structuring', 'Breaking up transactions to avoid reporting', 'HIGH'),
|
||||
('SMURFING', 'Smurfing', 'Using multiple people to structure transactions', 'HIGH'),
|
||||
('TRADE_BASED', 'Trade-Based Money Laundering', 'Using trade to launder money', 'CRITICAL'),
|
||||
('SHELL_COMPANY', 'Shell Company', 'Using fake companies for fraud', 'CRITICAL'),
|
||||
('INVOICE_FRAUD', 'Invoice Fraud', 'Fraudulent invoicing schemes', 'HIGH');
|
||||
|
||||
-- Create a function to generate realistic transaction patterns
|
||||
CREATE OR REPLACE FUNCTION generate_fraud_score(
|
||||
p_amount DECIMAL,
|
||||
p_is_international BOOLEAN,
|
||||
p_merchant_risk DECIMAL,
|
||||
p_time_of_day INT,
|
||||
p_is_online BOOLEAN
|
||||
) RETURNS DECIMAL AS $$
|
||||
DECLARE
|
||||
v_score DECIMAL := 0;
|
||||
BEGIN
|
||||
-- Amount-based scoring
|
||||
IF p_amount > 5000 THEN v_score := v_score + 20; END IF;
|
||||
IF p_amount > 10000 THEN v_score := v_score + 30; END IF;
|
||||
|
||||
-- International transactions
|
||||
IF p_is_international THEN v_score := v_score + 15; END IF;
|
||||
|
||||
-- Merchant risk
|
||||
v_score := v_score + (p_merchant_risk * 10);
|
||||
|
||||
-- Time of day (late night transactions)
|
||||
IF p_time_of_day >= 23 OR p_time_of_day <= 4 THEN v_score := v_score + 10; END IF;
|
||||
|
||||
-- Online transactions
|
||||
IF p_is_online THEN v_score := v_score + 5; END IF;
|
||||
|
||||
-- Cap at 100
|
||||
IF v_score > 100 THEN v_score := 100; END IF;
|
||||
|
||||
RETURN v_score;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create a function to update account balances
|
||||
CREATE OR REPLACE FUNCTION update_account_balance()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.status = 'COMPLETED' THEN
|
||||
IF TG_TABLE_NAME = 'transactions' THEN
|
||||
-- Update based on transaction type
|
||||
UPDATE accounts
|
||||
SET current_balance = current_balance +
|
||||
CASE
|
||||
WHEN NEW.type_id IN (SELECT type_id FROM transaction_types WHERE type_code IN ('DEPOSIT', 'TRANSFER_IN', 'WIRE_IN', 'REFUND', 'INTEREST'))
|
||||
THEN NEW.amount
|
||||
ELSE -NEW.amount
|
||||
END,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE account_id = NEW.account_id;
|
||||
END IF;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create trigger for balance updates (commented out for bulk loading)
|
||||
-- CREATE TRIGGER trg_update_balance
|
||||
-- AFTER INSERT ON transactions
|
||||
-- FOR EACH ROW
|
||||
-- EXECUTE FUNCTION update_account_balance();
|
||||
|
||||
-- Create a function to auto-generate alerts for suspicious transactions
|
||||
CREATE OR REPLACE FUNCTION check_suspicious_transaction()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
v_alert_type VARCHAR(50);
|
||||
v_description TEXT;
|
||||
v_severity VARCHAR(20);
|
||||
BEGIN
|
||||
-- High amount transactions
|
||||
IF NEW.amount > 10000 THEN
|
||||
v_alert_type := 'AMOUNT_ANOMALY';
|
||||
v_description := 'Large transaction amount: $' || NEW.amount;
|
||||
v_severity := 'HIGH';
|
||||
|
||||
INSERT INTO alerts (transaction_id, customer_id, account_id, alert_type, severity, description, risk_score)
|
||||
SELECT NEW.transaction_id, a.customer_id, NEW.account_id, v_alert_type, v_severity, v_description, NEW.fraud_score
|
||||
FROM accounts a WHERE a.account_id = NEW.account_id;
|
||||
END IF;
|
||||
|
||||
-- International transactions
|
||||
IF NEW.is_international AND NEW.amount > 1000 THEN
|
||||
v_alert_type := 'GEOGRAPHIC_ANOMALY';
|
||||
v_description := 'International transaction: $' || NEW.amount;
|
||||
v_severity := 'MEDIUM';
|
||||
|
||||
INSERT INTO alerts (transaction_id, customer_id, account_id, alert_type, severity, description, risk_score)
|
||||
SELECT NEW.transaction_id, a.customer_id, NEW.account_id, v_alert_type, v_severity, v_description, NEW.fraud_score
|
||||
FROM accounts a WHERE a.account_id = NEW.account_id;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create trigger for alert generation (commented out for bulk loading)
|
||||
-- CREATE TRIGGER trg_check_suspicious
|
||||
-- AFTER INSERT ON transactions
|
||||
-- FOR EACH ROW
|
||||
-- WHEN (NEW.fraud_score > 50)
|
||||
-- EXECUTE FUNCTION check_suspicious_transaction();
|
||||
|
||||
COMMENT ON FUNCTION generate_fraud_score IS 'Calculates fraud risk score based on transaction attributes';
|
||||
COMMENT ON FUNCTION update_account_balance IS 'Automatically updates account balance after transaction';
|
||||
COMMENT ON FUNCTION check_suspicious_transaction IS 'Generates alerts for suspicious transactions';
|
||||
|
||||
Reference in New Issue
Block a user