Repository navigation
Expand file tree
/
Copy pathinit.sql
More file actions
executable file
·288 lines (264 loc) · 9.75 KB
/
Copy pathinit.sql
File metadata and controls
executable file
·288 lines (264 loc) · 9.75 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
-- OmniMedical Suite v2.0 Database Schema
-- This file is executed when the PostgreSQL container starts for the first time.
-- Enable extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
-- Documents table
CREATE TABLE IF NOT EXISTS documents (
id SERIAL PRIMARY KEY,
filename VARCHAR(255) NOT NULL,
original_path VARCHAR(512),
status VARCHAR(20) DEFAULT 'uploaded',
file_size INTEGER,
mime_type VARCHAR(100),
ocr_text_ar TEXT,
ocr_text_en TEXT,
fused_text TEXT,
confidence_score FLOAT,
ocr_engines_used JSONB DEFAULT '[]',
medical_terms JSONB DEFAULT '[]',
translated_terms JSONB DEFAULT '[]',
processing_time FLOAT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Corrections table with context tracking
CREATE TABLE IF NOT EXISTS corrections (
id SERIAL PRIMARY KEY,
document_id INTEGER REFERENCES documents(id) ON DELETE CASCADE,
original_text TEXT NOT NULL,
corrected_text TEXT NOT NULL,
context TEXT,
confidence FLOAT DEFAULT 0.0,
auto_applied BOOLEAN DEFAULT FALSE,
frequency INTEGER DEFAULT 1,
first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
max_confidence FLOAT DEFAULT 0.0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Processing tasks table
CREATE TABLE IF NOT EXISTS processing_tasks (
id SERIAL PRIMARY KEY,
task_id VARCHAR(100) UNIQUE NOT NULL,
document_id INTEGER REFERENCES documents(id) ON DELETE SET NULL,
status VARCHAR(20) DEFAULT 'pending',
engine VARCHAR(50),
result JSONB,
error TEXT,
started_at TIMESTAMP,
completed_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- OCR engine results table
CREATE TABLE IF NOT EXISTS ocr_results (
id SERIAL PRIMARY KEY,
document_id INTEGER REFERENCES documents(id) ON DELETE CASCADE,
engine VARCHAR(50) NOT NULL,
raw_text TEXT,
processed_text TEXT,
confidence FLOAT,
processing_time FLOAT,
bbox_data JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Medical terms table
CREATE TABLE IF NOT EXISTS medical_terms_cache (
id SERIAL PRIMARY KEY,
term_ar TEXT NOT NULL,
term_en TEXT,
category VARCHAR(100),
frequency INTEGER DEFAULT 1,
first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(term_ar)
);
-- Indexes for performance
CREATE INDEX IF NOT EXISTS idx_documents_status ON documents(status);
CREATE INDEX IF NOT EXISTS idx_documents_created ON documents(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_corrections_document ON corrections(document_id);
CREATE INDEX IF NOT EXISTS idx_corrections_original ON corrections(original_text);
CREATE INDEX IF NOT EXISTS idx_corrections_frequency ON corrections(frequency DESC);
CREATE INDEX IF NOT EXISTS idx_tasks_status ON processing_tasks(status);
CREATE INDEX IF NOT EXISTS idx_tasks_task_id ON processing_tasks(task_id);
CREATE INDEX IF NOT EXISTS idx_ocr_results_document ON ocr_results(document_id);
CREATE INDEX IF NOT EXISTS idx_medical_terms_term ON medical_terms_cache(term_ar);
CREATE INDEX IF NOT EXISTS idx_medical_terms_category ON medical_terms_cache(category);
CREATE INDEX IF NOT EXISTS idx_documents_fused_trgm ON documents USING gin(fused_text gin_trgm_ops);
-- Views for reporting
CREATE OR REPLACE VIEW v_processing_stats AS
SELECT
status,
COUNT(*) as count,
AVG(processing_time) as avg_time,
AVG(confidence_score) as avg_confidence
FROM documents
GROUP BY status;
CREATE OR REPLACE VIEW v_top_corrections AS
SELECT
original_text,
corrected_text,
frequency,
max_confidence,
last_seen
FROM corrections
ORDER BY frequency DESC, max_confidence DESC
LIMIT 100;
CREATE OR REPLACE VIEW v_engine_performance AS
SELECT
engine,
COUNT(*) as total_docs,
AVG(confidence) as avg_confidence,
AVG(processing_time) as avg_time
FROM ocr_results
GROUP BY engine;
-- Triggers
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE OR REPLACE TRIGGER update_documents_updated_at
BEFORE UPDATE ON documents
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE OR REPLACE FUNCTION update_correction_stats()
RETURNS TRIGGER AS $$
BEGIN
NEW.frequency = OLD.frequency + 1;
NEW.last_seen = CURRENT_TIMESTAMP;
IF NEW.confidence > OLD.max_confidence THEN
NEW.max_confidence = NEW.confidence;
END IF;
RETURN NEW;
END;
$$ language 'plpgsql';
-- ============================================================
-- Dictionary Management System (Medical Dictionary Import)
-- ============================================================
-- Dictionaries registry
CREATE TABLE IF NOT EXISTS dictionaries (
id SERIAL PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
title TEXT,
source_lang TEXT DEFAULT 'ar',
target_lang TEXT DEFAULT 'en',
total_entries INTEGER DEFAULT 0,
format_version TEXT DEFAULT '1.0',
import_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
file_path TEXT,
file_size INTEGER DEFAULT 0,
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
last_imported TIMESTAMP
);
-- Extended medical terms table (replaces old medical_terms_cache for dict management)
CREATE TABLE IF NOT EXISTS medical_terms (
id SERIAL PRIMARY KEY,
term_ar TEXT,
term_en TEXT,
definition TEXT,
category VARCHAR(100),
frequency INTEGER DEFAULT 1,
first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
dict_id INTEGER REFERENCES dictionaries(id) ON DELETE CASCADE,
source_entry_id INTEGER,
CONSTRAINT unique_term UNIQUE(term_ar, term_en)
);
-- OCR corrections dictionary
CREATE TABLE IF NOT EXISTS ocr_corrections (
id SERIAL PRIMARY KEY,
wrong_term TEXT NOT NULL,
correct_term TEXT NOT NULL,
language TEXT CHECK(language IN ('ar', 'en', 'both')) DEFAULT 'ar',
confidence FLOAT DEFAULT 0.0,
usage_count INTEGER DEFAULT 0,
source TEXT,
dict_id INTEGER REFERENCES dictionaries(id) ON DELETE SET NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_used TIMESTAMP
);
-- Protected terms (prevented from spell correction)
CREATE TABLE IF NOT EXISTS protected_terms (
id SERIAL PRIMARY KEY,
term TEXT UNIQUE NOT NULL,
language TEXT DEFAULT 'both',
category TEXT,
reason TEXT,
dict_id INTEGER REFERENCES dictionaries(id) ON DELETE SET NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Synonyms mapping
CREATE TABLE IF NOT EXISTS term_synonyms (
id SERIAL PRIMARY KEY,
term1_id INTEGER REFERENCES medical_terms(id) ON DELETE CASCADE,
term2_id INTEGER REFERENCES medical_terms(id) ON DELETE CASCADE,
similarity FLOAT DEFAULT 1.0
);
-- Import audit log
CREATE TABLE IF NOT EXISTS dictionary_import_log (
id SERIAL PRIMARY KEY,
dict_id INTEGER REFERENCES dictionaries(id) ON DELETE SET NULL,
action TEXT NOT NULL,
entries_processed INTEGER DEFAULT 0,
entries_added INTEGER DEFAULT 0,
entries_skipped INTEGER DEFAULT 0,
errors TEXT,
duration_ms INTEGER DEFAULT 0,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Dictionary management indexes
CREATE INDEX IF NOT EXISTS idx_medical_terms_ar ON medical_terms(term_ar) WHERE term_ar IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_medical_terms_en ON medical_terms(term_en) WHERE term_en IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_medical_terms_category ON medical_terms(category) WHERE category IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_medical_terms_frequency ON medical_terms(frequency DESC);
CREATE INDEX IF NOT EXISTS idx_medical_terms_dict ON medical_terms(dict_id);
CREATE INDEX IF NOT EXISTS idx_medical_terms_ar_trgm ON medical_terms USING gin(term_ar gin_trgm_ops);
CREATE INDEX IF NOT EXISTS idx_medical_terms_en_trgm ON medical_terms USING gin(term_en gin_trgm_ops);
CREATE INDEX IF NOT EXISTS idx_medical_terms_def_trgm ON medical_terms USING gin(definition gin_trgm_ops);
CREATE INDEX IF NOT EXISTS idx_ocr_corrections_wrong ON ocr_corrections(wrong_term);
CREATE INDEX IF NOT EXISTS idx_ocr_corrections_correct ON ocr_corrections(correct_term);
CREATE INDEX IF NOT EXISTS idx_ocr_corrections_lang ON ocr_corrections(language);
CREATE INDEX IF NOT EXISTS idx_protected_term ON protected_terms(term);
CREATE INDEX IF NOT EXISTS idx_protected_lang ON protected_terms(language);
CREATE INDEX IF NOT EXISTS idx_dicts_active ON dictionaries(is_active);
CREATE INDEX IF NOT EXISTS idx_dict_import_log ON dictionary_import_log(timestamp DESC);
-- View: Dictionary statistics
CREATE OR REPLACE VIEW v_dictionary_stats AS
SELECT
d.id, d.name, d.title, d.source_lang, d.target_lang,
d.total_entries, d.import_date, d.last_imported,
COALESCE(t.actual_count, 0) as actual_entries,
COALESCE(c.corrections_count, 0) as corrections_count
FROM dictionaries d
LEFT JOIN (
SELECT dict_id, COUNT(*) as actual_count FROM medical_terms GROUP BY dict_id
) t ON t.dict_id = d.id
LEFT JOIN (
SELECT dict_id, COUNT(*) as corrections_count FROM ocr_corrections GROUP BY dict_id
) c ON c.dict_id = d.id
WHERE d.is_active = TRUE;
-- View: Category breakdown
CREATE OR REPLACE VIEW v_category_breakdown AS
SELECT
category,
COUNT(*) as term_count,
AVG(frequency) as avg_frequency,
COUNT(DISTINCT dict_id) as source_dicts
FROM medical_terms
WHERE category IS NOT NULL
GROUP BY category
ORDER BY term_count DESC;
-- Function: Update term frequency on match
CREATE OR REPLACE FUNCTION update_term_frequency()
RETURNS TRIGGER AS $$
BEGIN
UPDATE medical_terms
SET frequency = frequency + 1,
last_seen = CURRENT_TIMESTAMP
WHERE id = NEW.id;
RETURN NEW;
END;
$$ language 'plpgsql';