|
| 1 | +-- ==================================================================== |
| 2 | +-- Facial Recognition System - Database Schema |
| 3 | +-- ==================================================================== |
| 4 | +-- This schema supports the facial recognition feature for CrimeLinkAnalyzer |
| 5 | +-- Created: December 11, 2025 |
| 6 | +-- ==================================================================== |
| 7 | + |
| 8 | +-- Enable required PostgreSQL extension for EXCLUDE constraints |
| 9 | +-- The btree_gist extension is required for using EXCLUDE constraints with equality operators |
| 10 | +-- This allows us to enforce "one primary photo per criminal" at the database level |
| 11 | +CREATE EXTENSION IF NOT EXISTS btree_gist; |
| 12 | + |
| 13 | +-- Drop existing tables if they exist (for clean setup) |
| 14 | +DROP TABLE IF EXISTS facial_recognition_logs CASCADE; |
| 15 | +DROP TABLE IF EXISTS suspect_photos CASCADE; |
| 16 | +DROP TABLE IF EXISTS criminals CASCADE; |
| 17 | + |
| 18 | +-- ==================================================================== |
| 19 | +-- CRIMINALS TABLE |
| 20 | +-- ==================================================================== |
| 21 | +-- Stores criminal records with biometric data |
| 22 | +CREATE TABLE criminals ( |
| 23 | + criminal_id SERIAL PRIMARY KEY, |
| 24 | + name VARCHAR(255) NOT NULL, |
| 25 | + nic VARCHAR(20) UNIQUE, |
| 26 | + alias VARCHAR(255), |
| 27 | + date_of_birth DATE, |
| 28 | + gender VARCHAR(10) CHECK (gender IN ('Male', 'Female', 'Other')), |
| 29 | + address TEXT, |
| 30 | + nationality VARCHAR(100) DEFAULT 'Sri Lankan', |
| 31 | + |
| 32 | + -- Crime information stored as JSONB for flexibility |
| 33 | + crime_history JSONB DEFAULT '[]'::jsonb, |
| 34 | + |
| 35 | + -- Primary photo reference |
| 36 | + primary_photo_url VARCHAR(500), |
| 37 | + |
| 38 | + -- Face embedding - stored as BYTEA (binary) |
| 39 | + -- This is the average embedding from all photos |
| 40 | + face_embedding BYTEA, |
| 41 | + embedding_model VARCHAR(50) DEFAULT 'buffalo_sc', |
| 42 | + embedding_dimension INTEGER DEFAULT 512, |
| 43 | + |
| 44 | + -- Status tracking |
| 45 | + status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'archived')), |
| 46 | + risk_level VARCHAR(20) DEFAULT 'medium' CHECK (risk_level IN ('low', 'medium', 'high', 'critical')), |
| 47 | + |
| 48 | + -- Audit fields |
| 49 | + created_by INTEGER, -- User ID who created this record |
| 50 | + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 51 | + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 52 | + |
| 53 | + -- Metadata |
| 54 | + notes TEXT, |
| 55 | + last_seen_location VARCHAR(255), |
| 56 | + last_seen_date DATE |
| 57 | +); |
| 58 | + |
| 59 | +-- ==================================================================== |
| 60 | +-- SUSPECT_PHOTOS TABLE |
| 61 | +-- ==================================================================== |
| 62 | +-- Stores multiple photos per criminal for better accuracy |
| 63 | +CREATE TABLE suspect_photos ( |
| 64 | + photo_id SERIAL PRIMARY KEY, |
| 65 | + criminal_id INTEGER NOT NULL REFERENCES criminals(criminal_id) ON DELETE CASCADE, |
| 66 | + |
| 67 | + -- Photo storage |
| 68 | + photo_url VARCHAR(500) NOT NULL, |
| 69 | + photo_hash VARCHAR(64) UNIQUE, -- SHA-256 hash to prevent duplicates |
| 70 | + file_size_bytes INTEGER, |
| 71 | + |
| 72 | + -- Face detection metadata |
| 73 | + face_embedding BYTEA NOT NULL, -- Individual photo embedding |
| 74 | + face_confidence DECIMAL(5,2), -- Detection confidence (0-100) |
| 75 | + face_bbox JSONB, -- Bounding box coordinates {x, y, width, height} |
| 76 | + |
| 77 | + -- Photo metadata |
| 78 | + is_primary BOOLEAN DEFAULT FALSE, |
| 79 | + photo_quality VARCHAR(20) CHECK (photo_quality IN ('low', 'medium', 'high', 'excellent')), |
| 80 | + image_width INTEGER, |
| 81 | + image_height INTEGER, |
| 82 | + |
| 83 | + -- Source tracking |
| 84 | + source VARCHAR(100), -- e.g., 'manual_upload', 'cctv', 'arrest_record' |
| 85 | + source_date DATE, |
| 86 | + |
| 87 | + -- Audit |
| 88 | + uploaded_by INTEGER, -- User ID |
| 89 | + uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 90 | + |
| 91 | + -- Constraints |
| 92 | + CONSTRAINT only_one_primary_per_criminal |
| 93 | + EXCLUDE USING gist (criminal_id WITH =) |
| 94 | + WHERE (is_primary = true) |
| 95 | +); |
| 96 | + |
| 97 | +-- ==================================================================== |
| 98 | +-- FACIAL_RECOGNITION_LOGS TABLE |
| 99 | +-- ==================================================================== |
| 100 | +-- Audit trail for all facial recognition requests |
| 101 | +CREATE TABLE facial_recognition_logs ( |
| 102 | + log_id SERIAL PRIMARY KEY, |
| 103 | + |
| 104 | + -- Request details |
| 105 | + analysis_type VARCHAR(50) DEFAULT 'suspect_match', -- 'suspect_match', 'criminal_registration' |
| 106 | + uploaded_image_url VARCHAR(500), |
| 107 | + uploaded_image_hash VARCHAR(64), |
| 108 | + |
| 109 | + -- Face detection results |
| 110 | + face_detected BOOLEAN DEFAULT FALSE, |
| 111 | + face_count INTEGER DEFAULT 0, |
| 112 | + face_quality VARCHAR(20), |
| 113 | + |
| 114 | + -- Matching results |
| 115 | + matches_found INTEGER DEFAULT 0, |
| 116 | + best_match_criminal_id INTEGER REFERENCES criminals(criminal_id), |
| 117 | + best_match_similarity DECIMAL(5,2), -- Percentage (0-100) |
| 118 | + match_threshold DECIMAL(5,2) DEFAULT 75.00, |
| 119 | + |
| 120 | + -- All matches stored as JSONB for detailed analysis |
| 121 | + all_matches JSONB DEFAULT '[]'::jsonb, |
| 122 | + |
| 123 | + -- Performance metrics |
| 124 | + processing_time_ms INTEGER, |
| 125 | + model_version VARCHAR(50), |
| 126 | + |
| 127 | + -- Security & Audit |
| 128 | + requested_by INTEGER, -- User ID |
| 129 | + user_role VARCHAR(50), |
| 130 | + ip_address INET, |
| 131 | + user_agent TEXT, |
| 132 | + |
| 133 | + -- Timestamps |
| 134 | + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 135 | + |
| 136 | + -- Investigation reference |
| 137 | + case_id VARCHAR(100), |
| 138 | + investigation_notes TEXT |
| 139 | +); |
| 140 | + |
| 141 | +-- ==================================================================== |
| 142 | +-- INDEXES FOR PERFORMANCE |
| 143 | +-- ==================================================================== |
| 144 | + |
| 145 | +-- Criminal table indexes |
| 146 | +CREATE INDEX idx_criminal_nic ON criminals(nic); |
| 147 | +CREATE INDEX idx_criminal_name ON criminals USING gin(to_tsvector('english', name)); |
| 148 | +CREATE INDEX idx_criminal_status ON criminals(status) WHERE status = 'active'; |
| 149 | +CREATE INDEX idx_criminal_risk_level ON criminals(risk_level); |
| 150 | +CREATE INDEX idx_criminal_created_at ON criminals(created_at DESC); |
| 151 | + |
| 152 | +-- Suspect photos indexes |
| 153 | +CREATE INDEX idx_suspect_photos_criminal_id ON suspect_photos(criminal_id); |
| 154 | +CREATE INDEX idx_suspect_photos_primary ON suspect_photos(criminal_id, is_primary) WHERE is_primary = true; |
| 155 | +CREATE INDEX idx_suspect_photos_hash ON suspect_photos(photo_hash); |
| 156 | + |
| 157 | +-- Facial recognition logs indexes |
| 158 | +CREATE INDEX idx_fr_logs_created_at ON facial_recognition_logs(created_at DESC); |
| 159 | +CREATE INDEX idx_fr_logs_user ON facial_recognition_logs(requested_by); |
| 160 | +CREATE INDEX idx_fr_logs_best_match ON facial_recognition_logs(best_match_criminal_id) WHERE best_match_criminal_id IS NOT NULL; |
| 161 | +CREATE INDEX idx_fr_logs_case_id ON facial_recognition_logs(case_id) WHERE case_id IS NOT NULL; |
| 162 | + |
| 163 | +-- ==================================================================== |
| 164 | +-- TRIGGERS FOR AUTO-UPDATE |
| 165 | +-- ==================================================================== |
| 166 | + |
| 167 | +-- Automatically update updated_at timestamp |
| 168 | +CREATE OR REPLACE FUNCTION update_updated_at_column() |
| 169 | +RETURNS TRIGGER AS $$ |
| 170 | +BEGIN |
| 171 | + NEW.updated_at = CURRENT_TIMESTAMP; |
| 172 | + RETURN NEW; |
| 173 | +END; |
| 174 | +$$ LANGUAGE plpgsql; |
| 175 | + |
| 176 | +CREATE TRIGGER update_criminals_updated_at |
| 177 | + BEFORE UPDATE ON criminals |
| 178 | + FOR EACH ROW |
| 179 | + EXECUTE FUNCTION update_updated_at_column(); |
| 180 | + |
| 181 | +-- ==================================================================== |
| 182 | +-- UTILITY FUNCTIONS |
| 183 | +-- ==================================================================== |
| 184 | + |
| 185 | +-- Function to calculate average embedding from multiple photos |
| 186 | +CREATE OR REPLACE FUNCTION calculate_average_embedding(p_criminal_id INTEGER) |
| 187 | +RETURNS BYTEA AS $$ |
| 188 | +DECLARE |
| 189 | + avg_embedding BYTEA; |
| 190 | +BEGIN |
| 191 | + -- This will be called from Python after uploading multiple photos |
| 192 | + -- Python will handle the actual embedding averaging logic |
| 193 | + -- This function is a placeholder for future stored procedure implementation |
| 194 | + RETURN NULL; |
| 195 | +END; |
| 196 | +$$ LANGUAGE plpgsql; |
| 197 | + |
| 198 | +-- Function to search similar faces (placeholder - actual search done in Python) |
| 199 | +CREATE OR REPLACE FUNCTION search_similar_faces( |
| 200 | + p_embedding BYTEA, |
| 201 | + p_threshold DECIMAL DEFAULT 0.75, |
| 202 | + p_limit INTEGER DEFAULT 10 |
| 203 | +) |
| 204 | +RETURNS TABLE ( |
| 205 | + criminal_id INTEGER, |
| 206 | + name VARCHAR, |
| 207 | + similarity DECIMAL |
| 208 | +) AS $$ |
| 209 | +BEGIN |
| 210 | + -- Actual similarity search is performed in Python using numpy |
| 211 | + -- This is a placeholder for documentation |
| 212 | + RETURN QUERY SELECT NULL::INTEGER, NULL::VARCHAR, NULL::DECIMAL LIMIT 0; |
| 213 | +END; |
| 214 | +$$ LANGUAGE plpgsql; |
| 215 | + |
| 216 | +-- ==================================================================== |
| 217 | +-- INITIAL DATA / SEED DATA |
| 218 | +-- ==================================================================== |
| 219 | + |
| 220 | +-- Insert sample criminal record for testing |
| 221 | +INSERT INTO criminals ( |
| 222 | + name, |
| 223 | + nic, |
| 224 | + date_of_birth, |
| 225 | + gender, |
| 226 | + crime_history, |
| 227 | + status, |
| 228 | + risk_level, |
| 229 | + notes |
| 230 | +) VALUES ( |
| 231 | + 'Test Suspect One', |
| 232 | + '199012345678', |
| 233 | + '1990-05-15', |
| 234 | + 'Male', |
| 235 | + '[{"crime_type": "Theft", "date": "2023-03-10", "status": "Convicted", "sentence": "2 years"}]'::jsonb, |
| 236 | + 'active', |
| 237 | + 'medium', |
| 238 | + 'Sample criminal record for testing facial recognition system' |
| 239 | +); |
| 240 | + |
| 241 | +-- ==================================================================== |
| 242 | +-- PERMISSIONS & SECURITY |
| 243 | +-- ==================================================================== |
| 244 | + |
| 245 | +-- Grant appropriate permissions (adjust based on your user roles) |
| 246 | +-- GRANT SELECT, INSERT, UPDATE ON criminals TO crimelink_app_user; |
| 247 | +-- GRANT SELECT, INSERT ON suspect_photos TO crimelink_app_user; |
| 248 | +-- GRANT INSERT ON facial_recognition_logs TO crimelink_app_user; |
| 249 | + |
| 250 | +-- ==================================================================== |
| 251 | +-- COMMENTS FOR DOCUMENTATION |
| 252 | +-- ==================================================================== |
| 253 | + |
| 254 | +COMMENT ON TABLE criminals IS 'Stores criminal records with biometric face embeddings for facial recognition'; |
| 255 | +COMMENT ON COLUMN criminals.face_embedding IS 'Average face embedding vector stored as binary data (512-dimensional float32 array)'; |
| 256 | +COMMENT ON COLUMN criminals.crime_history IS 'JSON array of crime records: [{crime_type, date, status, sentence}]'; |
| 257 | + |
| 258 | +COMMENT ON TABLE suspect_photos IS 'Multiple photos per criminal for improved recognition accuracy'; |
| 259 | +COMMENT ON COLUMN suspect_photos.face_embedding IS 'Individual face embedding for this specific photo'; |
| 260 | +COMMENT ON COLUMN suspect_photos.photo_hash IS 'SHA-256 hash to prevent duplicate photo uploads'; |
| 261 | + |
| 262 | +COMMENT ON TABLE facial_recognition_logs IS 'Audit trail for all facial recognition analysis requests'; |
| 263 | +COMMENT ON COLUMN facial_recognition_logs.all_matches IS 'JSON array of all matches: [{criminal_id, similarity, confidence}]'; |
| 264 | + |
| 265 | +-- ==================================================================== |
| 266 | +-- END OF SCHEMA |
| 267 | +-- ==================================================================== |
0 commit comments