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:
Alphaeus Mote
2025-10-23 13:53:30 -04:00
parent 9d9104a4fe
commit b30733ccad
15 changed files with 3864 additions and 2 deletions
+477
View File
@@ -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';
+256
View File
@@ -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';