Spaces:
Sleeping
Sleeping
| -- ===================================================== | |
| -- AnemiaLens Database Schema Enhancement | |
| -- Production-ready schema with indexing, analytics, and audit capabilities | |
| -- ===================================================== | |
| -- Enable UUID extension | |
| CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; | |
| -- Enable pgcrypto for encryption functions | |
| CREATE EXTENSION IF NOT EXISTS "pgcrypto"; | |
| -- ===================================================== | |
| -- Core Tables (Already Exist - Enhanced Version) | |
| -- ===================================================== | |
| -- Users table (enhanced) | |
| CREATE TABLE IF NOT EXISTS users ( | |
| id BIGSERIAL PRIMARY KEY, | |
| uid UUID NOT NULL DEFAULT uuid_generate_v4() UNIQUE, | |
| email VARCHAR(255) NOT NULL UNIQUE, | |
| hashed_password TEXT NOT NULL, | |
| full_name VARCHAR(255), | |
| role VARCHAR(50) NOT NULL DEFAULT 'user' CHECK (role IN ('user', 'admin', 'clinician')), | |
| is_active BOOLEAN NOT NULL DEFAULT true, | |
| scan_count INTEGER NOT NULL DEFAULT 0, | |
| subscription_tier VARCHAR(50) NOT NULL DEFAULT 'free' CHECK (subscription_tier IN ('free', 'pro', 'enterprise')), | |
| stripe_customer_id VARCHAR(255), | |
| -- Enhanced fields | |
| phone VARCHAR(50), | |
| date_of_birth DATE, | |
| ethnicity VARCHAR(100), | |
| locale VARCHAR(10) NOT NULL DEFAULT 'en', | |
| timezone VARCHAR(50) NOT NULL DEFAULT 'UTC', | |
| last_login_at TIMESTAMPTZ, | |
| email_verified BOOLEAN NOT NULL DEFAULT false, | |
| mfa_enabled BOOLEAN NOT NULL DEFAULT false, | |
| mfa_secret TEXT, | |
| -- Metadata | |
| created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | |
| updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | |
| deleted_at TIMESTAMPTZ, -- Soft delete | |
| -- Indexes for performance | |
| CONSTRAINT users_email_check CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$') | |
| ); | |
| -- Screenings table (enhanced) | |
| CREATE TABLE IF NOT EXISTS screenings ( | |
| id BIGSERIAL PRIMARY KEY, | |
| uid UUID NOT NULL DEFAULT uuid_generate_v4() UNIQUE, | |
| user_id BIGINT REFERENCES users(id) ON DELETE SET NULL, | |
| -- Core screening data | |
| image_url TEXT NOT NULL, | |
| predicted_hemoglobin DECIMAL(5, 2), | |
| anemia_risk DECIMAL(5, 4), | |
| confidence_score DECIMAL(5, 4), | |
| triage_band VARCHAR(50), | |
| -- Quality metrics | |
| image_quality_score DECIMAL(5, 4), | |
| quality_passed BOOLEAN NOT NULL DEFAULT false, | |
| quality_issues JSONB, | |
| -- Clinical data | |
| symptoms JSONB, | |
| patient_profile JSONB, | |
| clinical_brief JSONB, | |
| guidance TEXT, | |
| -- ML metadata | |
| model_version VARCHAR(50), | |
| inference_time_ms INTEGER, | |
| calibration_applied BOOLEAN NOT NULL DEFAULT false, | |
| -- Workflow tracking | |
| workflow_stage VARCHAR(50) NOT NULL DEFAULT 'completed', | |
| handoff_sent BOOLEAN NOT NULL DEFAULT false, | |
| -- Metadata | |
| ip_address INET, | |
| user_agent TEXT, | |
| created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | |
| updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | |
| deleted_at TIMESTAMPTZ -- Soft delete | |
| ); | |
| -- ===================================================== | |
| -- Analytics Tables | |
| -- ===================================================== | |
| -- Screening analytics for business intelligence | |
| CREATE TABLE IF NOT EXISTS screening_analytics ( | |
| id BIGSERIAL PRIMARY KEY, | |
| screening_id UUID NOT NULL REFERENCES screenings(uid) ON DELETE CASCADE, | |
| -- Performance metrics | |
| total_processing_time_ms INTEGER, | |
| quality_check_time_ms INTEGER, | |
| inference_time_ms INTEGER, | |
| guidance_generation_time_ms INTEGER, | |
| -- Model performance | |
| model_predictions JSONB, | |
| ensemble_weights JSONB, | |
| calibration_delta DECIMAL(5, 4), | |
| -- User behavior | |
| time_to_upload INTEGER, -- seconds from page load to upload | |
| time_to_complete INTEGER, -- total screening time | |
| steps_completed INTEGER, | |
| abandoned_at_step INTEGER, -- NULL if completed | |
| -- Device/browser info | |
| device_type VARCHAR(50), | |
| browser_name VARCHAR(50), | |
| os_name VARCHAR(50), | |
| -- Geographic | |
| country VARCHAR(100), | |
| region VARCHAR(100), | |
| city VARCHAR(100), | |
| created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() | |
| ); | |
| -- Model performance tracking | |
| CREATE TABLE IF NOT EXISTS model_performance ( | |
| id BIGSERIAL PRIMARY KEY, | |
| model_version VARCHAR(50) NOT NULL, | |
| model_name VARCHAR(100) NOT NULL, | |
| -- Performance metrics | |
| accuracy DECIMAL(5, 4), | |
| precision DECIMAL(5, 4), | |
| recall DECIMAL(5, 4), | |
| f1_score DECIMAL(5, 4), | |
| auc_roc DECIMAL(5, 4), | |
| -- Calibration metrics | |
| expected_calibration_error DECIMAL(5, 4), | |
| brier_score DECIMAL(5, 4), | |
| -- Demographic breakdown | |
| demographic_metrics JSONB, | |
| -- Data | |
| sample_size INTEGER, | |
| evaluation_date DATE NOT NULL, | |
| created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | |
| UNIQUE(model_version, evaluation_date) | |
| ); | |
| -- A/B test tracking | |
| CREATE TABLE IF NOT EXISTS ab_tests ( | |
| id BIGSERIAL PRIMARY KEY, | |
| test_name VARCHAR(255) NOT NULL UNIQUE, | |
| description TEXT, | |
| -- Test configuration | |
| variant_a VARCHAR(100) NOT NULL, | |
| variant_b VARCHAR(100) NOT NULL, | |
| traffic_split DECIMAL(5, 2) NOT NULL DEFAULT 50.00, -- percentage for variant B | |
| -- Status | |
| status VARCHAR(50) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'running', 'completed', 'cancelled')), | |
| start_date TIMESTAMPTZ, | |
| end_date TIMESTAMPTZ, | |
| -- Results | |
| winner VARCHAR(100), | |
| statistical_significance DECIMAL(5, 4), | |
| results JSONB, | |
| created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | |
| updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() | |
| ); | |
| -- A/B test assignments | |
| CREATE TABLE IF NOT EXISTS ab_test_assignments ( | |
| id BIGSERIAL PRIMARY KEY, | |
| test_id BIGINT NOT NULL REFERENCES ab_tests(id) ON DELETE CASCADE, | |
| user_id BIGINT REFERENCES users(id) ON DELETE SET NULL, | |
| screening_id UUID REFERENCES screenings(uid) ON DELETE CASCADE, | |
| variant VARCHAR(100) NOT NULL CHECK (variant IN ('A', 'B')), | |
| created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | |
| UNIQUE(test_id, screening_id) | |
| ); | |
| -- ===================================================== | |
| -- Audit & Compliance Tables | |
| -- ===================================================== | |
| -- HIPAA audit log | |
| CREATE TABLE IF NOT EXISTS audit_log ( | |
| id BIGSERIAL PRIMARY KEY, | |
| -- Event details | |
| event_type VARCHAR(100) NOT NULL, | |
| event_subtype VARCHAR(100), | |
| -- User context | |
| user_id BIGINT REFERENCES users(id) ON DELETE SET NULL, | |
| user_email VARCHAR(255), | |
| -- Resource context | |
| resource_type VARCHAR(100), | |
| resource_id VARCHAR(255), | |
| -- Request context | |
| ip_address INET, | |
| user_agent TEXT, | |
| correlation_id UUID, | |
| -- Event data | |
| details JSONB, | |
| -- Compliance | |
| break_glass BOOLEAN NOT NULL DEFAULT false, | |
| justification TEXT, | |
| created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() | |
| ); | |
| -- Data access log (for compliance reporting) | |
| CREATE TABLE IF NOT EXISTS data_access_log ( | |
| id BIGSERIAL PRIMARY KEY, | |
| user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, | |
| resource_type VARCHAR(100) NOT NULL, | |
| resource_id VARCHAR(255) NOT NULL, | |
| action VARCHAR(50) NOT NULL, | |
| ip_address INET, | |
| user_agent TEXT, | |
| created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() | |
| ); | |
| -- ===================================================== | |
| -- Notification System | |
| -- ===================================================== | |
| CREATE TABLE IF NOT EXISTS notifications ( | |
| id BIGSERIAL PRIMARY KEY, | |
| user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, | |
| type VARCHAR(100) NOT NULL, | |
| title VARCHAR(255) NOT NULL, | |
| message TEXT NOT NULL, | |
| -- Metadata | |
| data JSONB, | |
| -- Status | |
| read BOOLEAN NOT NULL DEFAULT false, | |
| read_at TIMESTAMPTZ, | |
| -- Priority | |
| priority VARCHAR(50) NOT NULL DEFAULT 'normal' CHECK (priority IN ('low', 'normal', 'high', 'critical')), | |
| created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() | |
| ); | |
| -- ===================================================== | |
| -- Indexes for Performance | |
| -- ===================================================== | |
| -- Users indexes | |
| CREATE INDEX IF NOT EXISTS idx_users_email ON users(email) WHERE deleted_at IS NULL; | |
| CREATE INDEX IF NOT EXISTS idx_users_uid ON users(uid); | |
| CREATE INDEX IF NOT EXISTS idx_users_subscription ON users(subscription_tier) WHERE is_active = true; | |
| CREATE INDEX IF NOT EXISTS idx_users_last_login ON users(last_login_at) WHERE last_login_at IS NOT NULL; | |
| -- Screenings indexes (critical for query performance) | |
| CREATE INDEX IF NOT EXISTS idx_screenings_user_id ON screenings(user_id) WHERE deleted_at IS NULL; | |
| CREATE INDEX IF NOT EXISTS idx_screenings_uid ON screenings(uid); | |
| CREATE INDEX IF NOT EXISTS idx_screenings_created_at ON screenings(created_at DESC); | |
| CREATE INDEX IF NOT EXISTS idx_screenings_triage_band ON screenings(triage_band); | |
| CREATE INDEX IF NOT EXISTS idx_screenings_user_created ON screenings(user_id, created_at DESC) WHERE deleted_at IS NULL; | |
| CREATE INDEX IF NOT EXISTS idx_screenings_quality_passed ON screenings(quality_passed); | |
| CREATE INDEX IF NOT EXISTS idx_screenings_model_version ON screenings(model_version); | |
| -- Analytics indexes | |
| CREATE INDEX IF NOT EXISTS idx_screening_analytics_screening_id ON screening_analytics(screening_id); | |
| CREATE INDEX IF NOT EXISTS idx_screening_analytics_created_at ON screening_analytics(created_at DESC); | |
| CREATE INDEX IF NOT EXISTS idx_screening_analytics_device_type ON screening_analytics(device_type); | |
| -- Audit log indexes | |
| CREATE INDEX IF NOT EXISTS idx_audit_log_user_id ON audit_log(user_id); | |
| CREATE INDEX IF NOT EXISTS idx_audit_log_event_type ON audit_log(event_type); | |
| CREATE INDEX IF NOT EXISTS idx_audit_log_created_at ON audit_log(created_at DESC); | |
| CREATE INDEX IF NOT EXISTS idx_audit_log_resource ON audit_log(resource_type, resource_id); | |
| -- Notifications indexes | |
| CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications(user_id) WHERE read = false; | |
| CREATE INDEX IF NOT EXISTS idx_notifications_created ON notifications(user_id, created_at DESC); | |
| -- ===================================================== | |
| -- Triggers for Automatic Updates | |
| -- ===================================================== | |
| -- Auto-update updated_at timestamp | |
| CREATE OR REPLACE FUNCTION update_updated_at_column() | |
| RETURNS TRIGGER AS $$ | |
| BEGIN | |
| NEW.updated_at = NOW(); | |
| RETURN NEW; | |
| END; | |
| $$ language 'plpgsql'; | |
| CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users | |
| FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); | |
| CREATE TRIGGER update_screenings_updated_at BEFORE UPDATE ON screenings | |
| FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); | |
| CREATE TRIGGER update_ab_tests_updated_at BEFORE UPDATE ON ab_tests | |
| FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); | |
| -- ===================================================== | |
| -- Views for Common Queries | |
| -- ===================================================== | |
| -- Active users in last 30 days | |
| CREATE OR REPLACE VIEW active_users_30d AS | |
| SELECT | |
| u.id, | |
| u.uid, | |
| u.email, | |
| u.full_name, | |
| u.subscription_tier, | |
| u.last_login_at, | |
| COUNT(s.id) as screenings_last_30d | |
| FROM users u | |
| LEFT JOIN screenings s ON u.id = s.user_id | |
| AND s.created_at >= NOW() - INTERVAL '30 days' | |
| AND s.deleted_at IS NULL | |
| WHERE u.is_active = true | |
| AND u.deleted_at IS NULL | |
| AND u.last_login_at >= NOW() - INTERVAL '30 days' | |
| GROUP BY u.id | |
| ORDER BY u.last_login_at DESC; | |
| -- Screening statistics by day | |
| CREATE OR REPLACE VIEW daily_screening_stats AS | |
| SELECT | |
| DATE(created_at) as date, | |
| COUNT(*) as total_screenings, | |
| COUNT(*) FILTER (WHERE quality_passed = true) as quality_passed, | |
| COUNT(*) FILTER (WHERE quality_passed = false) as quality_failed, | |
| AVG(predicted_hemoglobin) as avg_hemoglobin, | |
| AVG(anemia_risk) as avg_risk, | |
| COUNT(*) FILTER (WHERE triage_band = 'high_concern') as high_concern, | |
| COUNT(*) FILTER (WHERE triage_band = 'moderate_risk') as moderate_risk, | |
| COUNT(*) FILTER (WHERE triage_band = 'low_risk') as low_risk | |
| FROM screenings | |
| WHERE deleted_at IS NULL | |
| GROUP BY DATE(created_at) | |
| ORDER BY date DESC; | |
| -- Model performance summary | |
| CREATE OR REPLACE VIEW model_performance_summary AS | |
| SELECT | |
| model_version, | |
| COUNT(*) as total_predictions, | |
| AVG(anemia_risk) as avg_risk_score, | |
| AVG(confidence_score) as avg_confidence, | |
| AVG(inference_time_ms) as avg_inference_time, | |
| PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY inference_time_ms) as p95_inference_time | |
| FROM screenings | |
| WHERE model_version IS NOT NULL | |
| AND deleted_at IS NULL | |
| GROUP BY model_version | |
| ORDER BY total_predictions DESC; | |
| -- ===================================================== | |
| -- Row Level Security (RLS) Policies | |
| -- ===================================================== | |
| -- Enable RLS on sensitive tables | |
| ALTER TABLE screenings ENABLE ROW LEVEL SECURITY; | |
| ALTER TABLE audit_log ENABLE ROW LEVEL SECURITY; | |
| ALTER TABLE notifications ENABLE ROW LEVEL SECURITY; | |
| -- Users can only see their own screenings | |
| CREATE POLICY users_see_own_screenings ON screenings | |
| FOR SELECT | |
| USING (user_id = auth.uid()); | |
| -- Users can only see their own notifications | |
| CREATE POLICY users_see_own_notifications ON notifications | |
| FOR ALL | |
| USING (user_id = auth.uid()); | |
| -- Audit log is append-only (no updates/deletes) | |
| CREATE POLICY audit_log_append_only ON audit_log | |
| FOR INSERT | |
| WITH CHECK (true); | |
| CREATE POLICY audit_log_read_admin ON audit_log | |
| FOR SELECT | |
| USING (auth.jwt() ->> 'role' = 'admin'); | |
| -- ===================================================== | |
| -- Comments for Documentation | |
| -- ===================================================== | |
| COMMENT ON TABLE users IS 'User accounts with authentication and profile data'; | |
| COMMENT ON TABLE screenings IS 'Medical screening records with ML predictions'; | |
| COMMENT ON TABLE screening_analytics IS 'Business intelligence analytics for screenings'; | |
| COMMENT ON TABLE model_performance IS 'Model evaluation metrics and performance tracking'; | |
| COMMENT ON TABLE ab_tests IS 'A/B test configurations and results'; | |
| COMMENT ON TABLE audit_log IS 'HIPAA-compliant audit trail for all PHI access'; | |
| COMMENT ON TABLE notifications IS 'User notifications and alerts'; | |
| COMMENT ON COLUMN screenings.predicted_hemoglobin IS 'Predicted hemoglobin level in g/dL'; | |
| COMMENT ON COLUMN screenings.anemia_risk IS 'Anemia risk score (0.0-1.0)'; | |
| COMMENT ON COLUMN screenings.triage_band IS 'Triage category: low_risk, moderate_risk, high_concern'; | |
| COMMENT ON COLUMN screenings.model_version IS 'ML model version used for prediction'; | |
| -- ===================================================== | |
| -- Initial Data | |
| -- ===================================================== | |
| -- Insert default model performance tracking | |
| INSERT INTO model_performance (model_version, model_name, evaluation_date) | |
| VALUES | |
| ('v7-ultimate-clinical', 'Archive Fusion v7', CURRENT_DATE), | |
| ('v8-clinical', 'Archive Fusion v8', CURRENT_DATE) | |
| ON CONFLICT (model_version, evaluation_date) DO NOTHING; | |