-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdatabase-schema.sql
More file actions
317 lines (282 loc) · 11.1 KB
/
database-schema.sql
File metadata and controls
317 lines (282 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
-- Medical Reports AI Database Schema
-- This schema is designed for storing and retrieving medical report analysis data
-- Note: Row Level Security (RLS) is enabled by default in Supabase
-- Create custom types for better data validation
CREATE TYPE test_status AS ENUM ('NORMAL', 'ABNORMAL', 'CONCERNING', 'UNKNOWN');
CREATE TYPE file_status AS ENUM ('PROCESSING', 'COMPLETED', 'FAILED');
-- Main table for storing medical report analysis sessions
CREATE TABLE medical_reports (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
file_name TEXT NOT NULL,
file_size INTEGER NOT NULL,
file_type TEXT NOT NULL,
original_text TEXT, -- Raw OCR extracted text
patient_info TEXT, -- AI generated patient summary
analysis_status file_status DEFAULT 'PROCESSING',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
-- Metadata for easy querying
total_tests INTEGER DEFAULT 0,
normal_tests INTEGER DEFAULT 0,
abnormal_tests INTEGER DEFAULT 0,
concerning_tests INTEGER DEFAULT 0
);
-- Table for storing individual test results
CREATE TABLE test_results (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
report_id UUID REFERENCES medical_reports(id) ON DELETE CASCADE NOT NULL,
test_name TEXT NOT NULL,
test_value TEXT NOT NULL,
status test_status NOT NULL,
explanation TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
-- Index for faster queries
CONSTRAINT unique_test_per_report UNIQUE(report_id, test_name)
);
-- Table for storing processing logs and errors
CREATE TABLE processing_logs (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
report_id UUID REFERENCES medical_reports(id) ON DELETE CASCADE NOT NULL,
step TEXT NOT NULL, -- 'OCR', 'AI_ANALYSIS', 'PARSING', etc.
status TEXT NOT NULL, -- 'STARTED', 'COMPLETED', 'FAILED'
message TEXT,
duration_ms INTEGER, -- Processing time in milliseconds
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
);
-- Table for user preferences and settings
CREATE TABLE user_preferences (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE UNIQUE NOT NULL,
email_notifications BOOLEAN DEFAULT true,
analysis_history_retention_days INTEGER DEFAULT 365,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
);
-- Create indexes for better query performance
CREATE INDEX idx_medical_reports_user_id ON medical_reports(user_id);
CREATE INDEX idx_medical_reports_created_at ON medical_reports(created_at DESC);
CREATE INDEX idx_medical_reports_status ON medical_reports(analysis_status);
CREATE INDEX idx_test_results_report_id ON test_results(report_id);
CREATE INDEX idx_test_results_status ON test_results(status);
CREATE INDEX idx_processing_logs_report_id ON processing_logs(report_id);
CREATE INDEX idx_processing_logs_created_at ON processing_logs(created_at DESC);
-- Create a function to update the 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 triggers to automatically update updated_at
CREATE TRIGGER update_medical_reports_updated_at
BEFORE UPDATE ON medical_reports
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_user_preferences_updated_at
BEFORE UPDATE ON user_preferences
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Function to update test counts when test results are inserted/updated/deleted
CREATE OR REPLACE FUNCTION update_report_test_counts()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'DELETE' THEN
-- Update counts when a test is deleted
UPDATE medical_reports
SET
total_tests = (SELECT COUNT(*) FROM test_results WHERE report_id = OLD.report_id),
normal_tests = (SELECT COUNT(*) FROM test_results WHERE report_id = OLD.report_id AND status = 'NORMAL'),
abnormal_tests = (SELECT COUNT(*) FROM test_results WHERE report_id = OLD.report_id AND status = 'ABNORMAL'),
concerning_tests = (SELECT COUNT(*) FROM test_results WHERE report_id = OLD.report_id AND status = 'CONCERNING')
WHERE id = OLD.report_id;
RETURN OLD;
ELSE
-- Update counts when a test is inserted or updated
UPDATE medical_reports
SET
total_tests = (SELECT COUNT(*) FROM test_results WHERE report_id = NEW.report_id),
normal_tests = (SELECT COUNT(*) FROM test_results WHERE report_id = NEW.report_id AND status = 'NORMAL'),
abnormal_tests = (SELECT COUNT(*) FROM test_results WHERE report_id = NEW.report_id AND status = 'ABNORMAL'),
concerning_tests = (SELECT COUNT(*) FROM test_results WHERE report_id = NEW.report_id AND status = 'CONCERNING')
WHERE id = NEW.report_id;
RETURN NEW;
END IF;
END;
$$ language 'plpgsql';
-- Create triggers for test count updates
CREATE TRIGGER update_test_counts_insert
AFTER INSERT ON test_results
FOR EACH ROW EXECUTE FUNCTION update_report_test_counts();
CREATE TRIGGER update_test_counts_update
AFTER UPDATE ON test_results
FOR EACH ROW EXECUTE FUNCTION update_report_test_counts();
CREATE TRIGGER update_test_counts_delete
AFTER DELETE ON test_results
FOR EACH ROW EXECUTE FUNCTION update_report_test_counts();
-- Enable Row Level Security
ALTER TABLE medical_reports ENABLE ROW LEVEL SECURITY;
ALTER TABLE test_results ENABLE ROW LEVEL SECURITY;
ALTER TABLE processing_logs ENABLE ROW LEVEL SECURITY;
ALTER TABLE user_preferences ENABLE ROW LEVEL SECURITY;
-- Create RLS policies
-- Users can only access their own data
CREATE POLICY "Users can view their own medical reports" ON medical_reports
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can insert their own medical reports" ON medical_reports
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update their own medical reports" ON medical_reports
FOR UPDATE USING (auth.uid() = user_id);
CREATE POLICY "Users can delete their own medical reports" ON medical_reports
FOR DELETE USING (auth.uid() = user_id);
-- Test results policies
CREATE POLICY "Users can view test results for their reports" ON test_results
FOR SELECT USING (
EXISTS (
SELECT 1 FROM medical_reports
WHERE medical_reports.id = test_results.report_id
AND medical_reports.user_id = auth.uid()
)
);
CREATE POLICY "Users can insert test results for their reports" ON test_results
FOR INSERT WITH CHECK (
EXISTS (
SELECT 1 FROM medical_reports
WHERE medical_reports.id = test_results.report_id
AND medical_reports.user_id = auth.uid()
)
);
CREATE POLICY "Users can update test results for their reports" ON test_results
FOR UPDATE USING (
EXISTS (
SELECT 1 FROM medical_reports
WHERE medical_reports.id = test_results.report_id
AND medical_reports.user_id = auth.uid()
)
);
CREATE POLICY "Users can delete test results for their reports" ON test_results
FOR DELETE USING (
EXISTS (
SELECT 1 FROM medical_reports
WHERE medical_reports.id = test_results.report_id
AND medical_reports.user_id = auth.uid()
)
);
-- Processing logs policies
CREATE POLICY "Users can view logs for their reports" ON processing_logs
FOR SELECT USING (
EXISTS (
SELECT 1 FROM medical_reports
WHERE medical_reports.id = processing_logs.report_id
AND medical_reports.user_id = auth.uid()
)
);
CREATE POLICY "Users can insert logs for their reports" ON processing_logs
FOR INSERT WITH CHECK (
EXISTS (
SELECT 1 FROM medical_reports
WHERE medical_reports.id = processing_logs.report_id
AND medical_reports.user_id = auth.uid()
)
);
-- User preferences policies
CREATE POLICY "Users can view their own preferences" ON user_preferences
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can insert their own preferences" ON user_preferences
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update their own preferences" ON user_preferences
FOR UPDATE USING (auth.uid() = user_id);
-- Create a view for easy querying of complete report data
CREATE VIEW report_summary AS
SELECT
mr.id,
mr.user_id,
mr.file_name,
mr.file_size,
mr.file_type,
mr.patient_info,
mr.analysis_status,
mr.created_at,
mr.updated_at,
mr.total_tests,
mr.normal_tests,
mr.abnormal_tests,
mr.concerning_tests,
-- Calculate health score (0-100, higher is better)
CASE
WHEN mr.total_tests = 0 THEN 100
ELSE ROUND(
(mr.normal_tests::float / mr.total_tests::float) * 100
)
END as health_score
FROM medical_reports mr;
-- Grant permissions to authenticated users
GRANT SELECT, INSERT, UPDATE, DELETE ON medical_reports TO authenticated;
GRANT SELECT, INSERT, UPDATE, DELETE ON test_results TO authenticated;
GRANT SELECT, INSERT ON processing_logs TO authenticated;
GRANT SELECT, INSERT, UPDATE ON user_preferences TO authenticated;
GRANT SELECT ON report_summary TO authenticated;
-- Create helpful functions for common operations
-- Function to get a user's recent reports
CREATE OR REPLACE FUNCTION get_user_reports(
p_user_id UUID,
p_limit INTEGER DEFAULT 10,
p_offset INTEGER DEFAULT 0
)
RETURNS TABLE (
id UUID,
file_name TEXT,
analysis_status file_status,
created_at TIMESTAMP WITH TIME ZONE,
total_tests INTEGER,
health_score INTEGER
) AS $$
BEGIN
RETURN QUERY
SELECT
mr.id,
mr.file_name,
mr.analysis_status,
mr.created_at,
mr.total_tests,
CASE
WHEN mr.total_tests = 0 THEN 100
ELSE ROUND((mr.normal_tests::float / mr.total_tests::float) * 100)
END as health_score
FROM medical_reports mr
WHERE mr.user_id = p_user_id
ORDER BY mr.created_at DESC
LIMIT p_limit
OFFSET p_offset;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Function to get complete report with all test results
CREATE OR REPLACE FUNCTION get_report_details(p_report_id UUID)
RETURNS TABLE (
report_id UUID,
file_name TEXT,
patient_info TEXT,
analysis_status file_status,
created_at TIMESTAMP WITH TIME ZONE,
test_name TEXT,
test_value TEXT,
status test_status,
explanation TEXT
) AS $$
BEGIN
RETURN QUERY
SELECT
mr.id as report_id,
mr.file_name,
mr.patient_info,
mr.analysis_status,
mr.created_at,
tr.test_name,
tr.test_value,
tr.status,
tr.explanation
FROM medical_reports mr
LEFT JOIN test_results tr ON mr.id = tr.report_id
WHERE mr.id = p_report_id
ORDER BY tr.created_at;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;