@omniradiology/omnirad 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +438 -0
  2. package/app/api/ai-config/route.ts +131 -0
  3. package/app/api/ai-config/test/route.ts +49 -0
  4. package/app/api/auth/auto-login/route.ts +66 -0
  5. package/app/api/auth/check/route.ts +17 -0
  6. package/app/api/auth/login/route.ts +72 -0
  7. package/app/api/auth/logout/route.ts +25 -0
  8. package/app/api/auth/me/route.ts +75 -0
  9. package/app/api/auth/password/route.ts +49 -0
  10. package/app/api/auth/setup/route.ts +63 -0
  11. package/app/api/auth/users/route.ts +100 -0
  12. package/app/api/auth/wipe/route.ts +27 -0
  13. package/app/api/compliance/anonymize/patient/[id]/route.ts +104 -0
  14. package/app/api/compliance/audit/route.ts +110 -0
  15. package/app/api/compliance/export/patient/[id]/route.ts +108 -0
  16. package/app/api/compliance/restrict/patient/[id]/route.ts +59 -0
  17. package/app/api/compliance/settings/route.ts +93 -0
  18. package/app/api/copilot/annotate/route.ts +94 -0
  19. package/app/api/copilot/chat/route.ts +238 -0
  20. package/app/api/copilot/history/route.ts +95 -0
  21. package/app/api/copilot/reports/route.ts +81 -0
  22. package/app/api/fhir/Bundle/report/[id]/route.ts +85 -0
  23. package/app/api/fhir/DiagnosticReport/[id]/route.ts +45 -0
  24. package/app/api/fhir/ImagingStudy/[id]/route.ts +57 -0
  25. package/app/api/fhir/Patient/[id]/route.ts +26 -0
  26. package/app/api/fhir/ServiceRequest/route.ts +85 -0
  27. package/app/api/fhir/config/route.ts +102 -0
  28. package/app/api/fhir/config/test-connection/route.ts +49 -0
  29. package/app/api/fhir/metadata/route.ts +51 -0
  30. package/app/api/pacs/metadata/route.ts +32 -0
  31. package/app/api/pacs/qido/instances/route.ts +39 -0
  32. package/app/api/pacs/qido/series/route.ts +38 -0
  33. package/app/api/pacs/qido/studies/route.ts +37 -0
  34. package/app/api/pacs/test/route.ts +30 -0
  35. package/app/api/pacs/wado/render/route.ts +51 -0
  36. package/app/api/patients/[id]/reports/route.ts +18 -0
  37. package/app/api/patients/[id]/route.ts +43 -0
  38. package/app/api/patients/merge/route.ts +57 -0
  39. package/app/api/patients/route.ts +67 -0
  40. package/app/api/patients/search/route.ts +25 -0
  41. package/app/api/reports/[id]/route.ts +84 -0
  42. package/app/api/reports/[id]/status/route.ts +87 -0
  43. package/app/api/reports/clear/route.ts +16 -0
  44. package/app/api/reports/route.ts +112 -0
  45. package/app/api/segmentation-config/route.ts +238 -0
  46. package/app/api/settings/route.ts +245 -0
  47. package/app/api/settings/test-supabase/route.ts +103 -0
  48. package/app/api/upload/route.ts +48 -0
  49. package/app/copilot/page.tsx +30 -0
  50. package/app/globals.css +141 -0
  51. package/app/history/page.tsx +242 -0
  52. package/app/icon.svg +3 -0
  53. package/app/layout.tsx +47 -0
  54. package/app/login/page.tsx +175 -0
  55. package/app/pacs/page.tsx +78 -0
  56. package/app/page.tsx +125 -0
  57. package/app/patients/[id]/page.tsx +315 -0
  58. package/app/patients/page.tsx +110 -0
  59. package/app/profile/page.tsx +208 -0
  60. package/app/reports/page.tsx +432 -0
  61. package/app/settings/page.tsx +454 -0
  62. package/app/setup/page.tsx +199 -0
  63. package/components/admin/AuditLogTable.tsx +293 -0
  64. package/components/copilot/ActivityIndicator.tsx +215 -0
  65. package/components/copilot/ChatHistoryPanel.tsx +140 -0
  66. package/components/copilot/ChatMessage.tsx +251 -0
  67. package/components/copilot/ClickableReference.tsx +40 -0
  68. package/components/copilot/CopilotCornerstoneViewer.tsx +562 -0
  69. package/components/copilot/CopilotPanel.tsx +311 -0
  70. package/components/copilot/FindingsList.tsx +75 -0
  71. package/components/copilot/ViewerPanel.tsx +460 -0
  72. package/components/copilot/WorkspaceLayout.tsx +398 -0
  73. package/components/dashboard/AIConfigPanel.tsx +339 -0
  74. package/components/dashboard/AppearancePanel.tsx +491 -0
  75. package/components/dashboard/ApprovalModal.tsx +163 -0
  76. package/components/dashboard/CollaborationPanel.tsx +134 -0
  77. package/components/dashboard/CopilotConfigPanel.tsx +337 -0
  78. package/components/dashboard/DicomViewer.tsx +645 -0
  79. package/components/dashboard/FhirIntegrationPanel.tsx +331 -0
  80. package/components/dashboard/FullReportOverlay.tsx +269 -0
  81. package/components/dashboard/ImageViewer.tsx +541 -0
  82. package/components/dashboard/PatientForm.tsx +597 -0
  83. package/components/dashboard/RejectionModal.tsx +74 -0
  84. package/components/dashboard/ReportEditor.tsx +160 -0
  85. package/components/dashboard/ReportTemplates.tsx +729 -0
  86. package/components/dashboard/ReportView.tsx +539 -0
  87. package/components/dashboard/SegmentationConfigPanel.tsx +490 -0
  88. package/components/dashboard/StudyPlaceholder.tsx +17 -0
  89. package/components/dashboard/SupabaseIntegrationPanel.tsx +345 -0
  90. package/components/dashboard/UserManagementPanel.tsx +272 -0
  91. package/components/layout/ClientLayout.tsx +39 -0
  92. package/components/layout/Header.tsx +20 -0
  93. package/components/layout/Sidebar.tsx +119 -0
  94. package/components/pacs/PacsImageViewerModal.tsx +121 -0
  95. package/components/pacs/PacsSearchFilters.tsx +117 -0
  96. package/components/pacs/PacsSeriesViewer.tsx +190 -0
  97. package/components/pacs/PacsStudyTable.tsx +113 -0
  98. package/components/patients/patient-card.tsx +117 -0
  99. package/components/patients/patient-header.tsx +122 -0
  100. package/components/patients/patient-search.tsx +137 -0
  101. package/components/patients/patient-timeline.tsx +153 -0
  102. package/components/settings/ComplianceSettingsPanel.tsx +278 -0
  103. package/components/settings/SecurityPanel.tsx +418 -0
  104. package/components/ui/badge.tsx +19 -0
  105. package/components/ui/basic.tsx +156 -0
  106. package/db/index.ts +350 -0
  107. package/db/migrations/0000_odd_quasimodo.sql +117 -0
  108. package/db/migrations/meta/0000_snapshot.json +778 -0
  109. package/db/migrations/meta/_journal.json +13 -0
  110. package/db/schema.ts +239 -0
  111. package/drizzle.config.ts +10 -0
  112. package/lib/api.ts +689 -0
  113. package/lib/auth.ts +22 -0
  114. package/lib/copilot/action-executor.ts +94 -0
  115. package/lib/copilot/action-types.ts +72 -0
  116. package/lib/copilot/coordinate-mapper.ts +84 -0
  117. package/lib/dicomImageExtractor.ts +103 -0
  118. package/lib/dicomMetadataParser.ts +111 -0
  119. package/lib/fhir/client.ts +25 -0
  120. package/lib/fhir/constants.ts +21 -0
  121. package/lib/fhir/diagnostic-report.ts +88 -0
  122. package/lib/fhir/helpers.ts +73 -0
  123. package/lib/fhir/imaging-study.ts +49 -0
  124. package/lib/fhir/patient.ts +55 -0
  125. package/lib/fhir/service-request.ts +85 -0
  126. package/lib/fhir.ts +6 -0
  127. package/lib/pacs/dicom-utils.ts +72 -0
  128. package/lib/pacs/dicomweb.ts +72 -0
  129. package/lib/pacs/server-utils.ts +37 -0
  130. package/lib/patients.ts +25 -0
  131. package/lib/pdfHelper.ts +119 -0
  132. package/lib/reportHtmlGenerator.ts +581 -0
  133. package/lib/security/audit.ts +180 -0
  134. package/lib/security/authz.ts +246 -0
  135. package/lib/security/phi-redaction.ts +156 -0
  136. package/lib/security/rate-limit.ts +106 -0
  137. package/lib/security/secrets.ts +179 -0
  138. package/lib/supabase.ts +72 -0
  139. package/lib/utils.ts +6 -0
  140. package/next.config.ts +35 -0
  141. package/package.json +76 -0
  142. package/public/file.svg +1 -0
  143. package/public/globe.svg +1 -0
  144. package/public/logo.svg +8 -0
  145. package/public/next.svg +1 -0
  146. package/public/omnirad-favicon.svg +8 -0
  147. package/public/vercel.svg +1 -0
  148. package/public/window.svg +1 -0
  149. package/tsconfig.json +34 -0
  150. package/types/copilot-viewer.ts +155 -0
  151. package/types/copilot.ts +105 -0
  152. package/types/fhir.ts +21 -0
  153. package/types/html2pdf.d.ts +20 -0
  154. package/types/index.ts +139 -0
  155. package/types/pacs.ts +41 -0
package/db/index.ts ADDED
@@ -0,0 +1,350 @@
1
+ import Database from "better-sqlite3";
2
+ import { drizzle } from "drizzle-orm/better-sqlite3";
3
+ import * as schema from "./schema";
4
+ import path from "path";
5
+ import fs from "fs";
6
+
7
+ // Ensure the data directory exists
8
+ const dataDir = path.join(process.cwd(), "data");
9
+ if (!fs.existsSync(dataDir)) {
10
+ fs.mkdirSync(dataDir, { recursive: true });
11
+ }
12
+
13
+ const dbPath = path.join(dataDir, "omnirad.db");
14
+
15
+ // Singleton pattern - reuse connection across hot reloads in dev
16
+ const globalForDb = globalThis as unknown as {
17
+ __omniradDb: ReturnType<typeof drizzle> | undefined;
18
+ __omniradSqlite: Database.Database | undefined;
19
+ };
20
+
21
+ if (!globalForDb.__omniradSqlite) {
22
+ globalForDb.__omniradSqlite = new Database(dbPath);
23
+ globalForDb.__omniradSqlite.pragma("journal_mode = WAL");
24
+ globalForDb.__omniradSqlite.pragma("foreign_keys = ON");
25
+ globalForDb.__omniradSqlite.pragma("busy_timeout = 5000"); // 5 seconds to wait for lock
26
+
27
+ // Auto-create tables if they don't exist
28
+ // Wrap in try/catch to avoid build-time crashes if another worker is creating them
29
+ try {
30
+ globalForDb.__omniradSqlite.exec(`
31
+ CREATE TABLE IF NOT EXISTS patients (
32
+ id TEXT PRIMARY KEY,
33
+ patient_id_number TEXT,
34
+ patient_name TEXT NOT NULL,
35
+ date_of_birth TEXT,
36
+ age INTEGER,
37
+ gender TEXT,
38
+ mobile TEXT,
39
+ address TEXT,
40
+ contact_info TEXT,
41
+ notes TEXT,
42
+ created_at TEXT NOT NULL,
43
+ updated_at TEXT
44
+ );
45
+
46
+ CREATE TABLE IF NOT EXISTS reports (
47
+ id TEXT PRIMARY KEY,
48
+ patient_id TEXT REFERENCES patients(id) ON DELETE CASCADE,
49
+ patient_name TEXT,
50
+ modality TEXT,
51
+ urgency TEXT,
52
+ report_status TEXT DEFAULT 'Pending',
53
+ report_data TEXT NOT NULL,
54
+ image_data TEXT,
55
+ created_at TEXT NOT NULL
56
+ );
57
+
58
+ CREATE TABLE IF NOT EXISTS config (
59
+ id INTEGER PRIMARY KEY DEFAULT 1,
60
+ n8n_webhook_url TEXT DEFAULT '',
61
+ supabase_url TEXT DEFAULT '',
62
+ supabase_anon_key TEXT DEFAULT ''
63
+ );
64
+
65
+ CREATE TABLE IF NOT EXISTS profile (
66
+ id INTEGER PRIMARY KEY DEFAULT 1,
67
+ full_name TEXT DEFAULT '',
68
+ role TEXT DEFAULT '',
69
+ hospital_name TEXT DEFAULT '',
70
+ department TEXT DEFAULT ''
71
+ );
72
+
73
+ CREATE TABLE IF NOT EXISTS appearance (
74
+ id INTEGER PRIMARY KEY DEFAULT 1,
75
+ theme TEXT DEFAULT 'dark',
76
+ template TEXT DEFAULT 'standard',
77
+ hospital_name TEXT DEFAULT '',
78
+ logo TEXT DEFAULT ''
79
+ );
80
+
81
+ CREATE TABLE IF NOT EXISTS users (
82
+ id TEXT PRIMARY KEY,
83
+ full_name TEXT NOT NULL,
84
+ username TEXT NOT NULL UNIQUE,
85
+ email TEXT NOT NULL UNIQUE,
86
+ password_hash TEXT NOT NULL,
87
+ role TEXT DEFAULT 'User',
88
+ created_at TEXT NOT NULL
89
+ );
90
+
91
+ CREATE TABLE IF NOT EXISTS sessions (
92
+ id TEXT PRIMARY KEY,
93
+ user_id TEXT NOT NULL,
94
+ expires_at INTEGER NOT NULL,
95
+ FOREIGN KEY (user_id) REFERENCES users(id)
96
+ );
97
+
98
+ CREATE TABLE IF NOT EXISTS ai_configurations (
99
+ id TEXT PRIMARY KEY,
100
+ provider_type TEXT NOT NULL,
101
+ provider_name TEXT NOT NULL,
102
+ api_endpoint_url TEXT,
103
+ api_secret_key TEXT,
104
+ model_name TEXT NOT NULL,
105
+ is_active INTEGER DEFAULT 0,
106
+ is_vision_capable INTEGER DEFAULT 0,
107
+ max_tokens INTEGER DEFAULT 4096,
108
+ temperature REAL DEFAULT 0.3,
109
+ timeout_seconds INTEGER DEFAULT 120,
110
+ created_at TEXT NOT NULL,
111
+ updated_at TEXT
112
+ );
113
+
114
+ CREATE TABLE IF NOT EXISTS segmentation_configurations (
115
+ id TEXT PRIMARY KEY,
116
+ deployment_mode TEXT,
117
+ provider_name TEXT,
118
+ model_name TEXT,
119
+ model_type TEXT DEFAULT 'medsam3',
120
+ base_url TEXT,
121
+ health_endpoint TEXT,
122
+ predict_endpoint TEXT,
123
+ api_secret_key TEXT,
124
+ timeout_seconds INTEGER,
125
+ supports_contours INTEGER,
126
+ supports_3d INTEGER,
127
+ returns_mask INTEGER,
128
+ returns_box INTEGER,
129
+ is_active INTEGER,
130
+ created_at TEXT,
131
+ updated_at TEXT
132
+ );
133
+
134
+ CREATE TABLE IF NOT EXISTS prompt_templates (
135
+ id TEXT PRIMARY KEY,
136
+ name TEXT NOT NULL,
137
+ template TEXT NOT NULL,
138
+ is_active INTEGER DEFAULT 0,
139
+ created_at TEXT NOT NULL,
140
+ updated_at TEXT
141
+ );
142
+
143
+ CREATE TABLE IF NOT EXISTS report_generation_logs (
144
+ id TEXT PRIMARY KEY,
145
+ report_id TEXT,
146
+ ai_config_id TEXT,
147
+ model_used TEXT,
148
+ prompt_template_id TEXT,
149
+ raw_llm_response TEXT,
150
+ parsed_successfully INTEGER,
151
+ retry_count INTEGER,
152
+ generation_time_ms INTEGER,
153
+ error_message TEXT,
154
+ created_at TEXT NOT NULL,
155
+ FOREIGN KEY (ai_config_id) REFERENCES ai_configurations(id)
156
+ );
157
+
158
+ CREATE TABLE IF NOT EXISTS chat_messages (
159
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
160
+ session_id TEXT NOT NULL,
161
+ role TEXT NOT NULL,
162
+ content TEXT NOT NULL,
163
+ viewer_actions TEXT,
164
+ references_data TEXT,
165
+ patient_id TEXT,
166
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
167
+ );
168
+
169
+ CREATE TABLE IF NOT EXISTS compliance_settings (
170
+ id INTEGER PRIMARY KEY DEFAULT 1,
171
+ data_retention_days INTEGER DEFAULT 2555,
172
+ audit_retention_days INTEGER DEFAULT 2555,
173
+ session_timeout_minutes INTEGER DEFAULT 15,
174
+ idle_timeout_minutes INTEGER DEFAULT 30,
175
+ enable_gdpr_export INTEGER DEFAULT 1,
176
+ enable_gdpr_anonymize INTEGER DEFAULT 1,
177
+ enable_gdpr_restriction INTEGER DEFAULT 1,
178
+ legal_basis TEXT DEFAULT 'legitimate_interest',
179
+ privacy_policy_url TEXT,
180
+ dpo_contact_email TEXT,
181
+ updated_at TEXT
182
+ );
183
+
184
+ CREATE TABLE IF NOT EXISTS fhir_integration_config (
185
+ id INTEGER PRIMARY KEY DEFAULT 1,
186
+ enabled INTEGER DEFAULT 0,
187
+ public_base_url TEXT DEFAULT '',
188
+ auth_mode TEXT DEFAULT 'bearer_token',
189
+ inbound_service_request_enabled INTEGER DEFAULT 0,
190
+ outbound_read_enabled INTEGER DEFAULT 1,
191
+ external_fhir_base_url TEXT DEFAULT '',
192
+ external_fhir_auth_type TEXT DEFAULT 'none',
193
+ external_fhir_client_id TEXT DEFAULT '',
194
+ external_fhir_client_secret TEXT,
195
+ external_fhir_bearer_token TEXT,
196
+ api_token_hash TEXT,
197
+ created_at TEXT,
198
+ updated_at TEXT
199
+ );
200
+
201
+ CREATE TABLE IF NOT EXISTS worklist_orders (
202
+ id TEXT PRIMARY KEY,
203
+ source_system TEXT DEFAULT 'FHIR',
204
+ fhir_service_request_id TEXT,
205
+ patient_id TEXT REFERENCES patients(id) ON DELETE CASCADE,
206
+ patient_name TEXT,
207
+ patient_identifier TEXT,
208
+ status TEXT DEFAULT 'active',
209
+ intent TEXT DEFAULT 'order',
210
+ priority TEXT DEFAULT 'routine',
211
+ urgency TEXT DEFAULT 'Routine',
212
+ modality TEXT,
213
+ requested_procedure TEXT,
214
+ reason TEXT,
215
+ authored_on TEXT,
216
+ requester_display TEXT,
217
+ raw_fhir TEXT,
218
+ created_at TEXT NOT NULL,
219
+ updated_at TEXT
220
+ );
221
+
222
+ CREATE TABLE IF NOT EXISTS patient_privacy_controls (
223
+ id TEXT PRIMARY KEY,
224
+ patient_id TEXT NOT NULL REFERENCES patients(id) ON DELETE CASCADE,
225
+ restriction TEXT,
226
+ consent_status TEXT,
227
+ anonymized_at TEXT,
228
+ anonymized_by TEXT,
229
+ last_exported_at TEXT,
230
+ last_exported_by TEXT,
231
+ created_at TEXT NOT NULL,
232
+ updated_at TEXT
233
+ );
234
+ `);
235
+
236
+ // ─── Migrations for existing databases ──────────────────────────────
237
+ // Add patient_id column to reports if it doesn't already exist
238
+ try {
239
+ const cols = globalForDb.__omniradSqlite!.pragma('table_info(reports)') as any[];
240
+ const hasPatientId = cols.some((c: any) => c.name === 'patient_id');
241
+ if (!hasPatientId) {
242
+ globalForDb.__omniradSqlite!.exec(`ALTER TABLE reports ADD COLUMN patient_id TEXT REFERENCES patients(id) ON DELETE CASCADE;`);
243
+ console.log('[OmniRad Db] Migrated: Added patient_id column to reports table.');
244
+ }
245
+ } catch (migErr) {
246
+ console.warn('[OmniRad Db] Migration check for patient_id skipped:', migErr);
247
+ }
248
+
249
+ // Add age column to patients if it doesn't already exist
250
+ try {
251
+ const cols = globalForDb.__omniradSqlite!.pragma('table_info(patients)') as any[];
252
+ const hasAge = cols.some((c: any) => c.name === 'age');
253
+ if (!hasAge) {
254
+ globalForDb.__omniradSqlite!.exec(`ALTER TABLE patients ADD COLUMN age INTEGER;`);
255
+ console.log('[OmniRad Db] Migrated: Added age column to patients table.');
256
+ }
257
+ } catch (migErr) {
258
+ console.warn('[OmniRad Db] Migration check for patients age skipped:', migErr);
259
+ }
260
+
261
+ // Add mobile and address columns to patients if they don't already exist
262
+ try {
263
+ const cols = globalForDb.__omniradSqlite!.pragma('table_info(patients)') as any[];
264
+ if (!cols.some((c: any) => c.name === 'mobile')) {
265
+ globalForDb.__omniradSqlite!.exec(`ALTER TABLE patients ADD COLUMN mobile TEXT;`);
266
+ console.log('[OmniRad Db] Migrated: Added mobile column to patients table.');
267
+ }
268
+ if (!cols.some((c: any) => c.name === 'address')) {
269
+ globalForDb.__omniradSqlite!.exec(`ALTER TABLE patients ADD COLUMN address TEXT;`);
270
+ console.log('[OmniRad Db] Migrated: Added address column to patients table.');
271
+ }
272
+ } catch (migErr) {
273
+ console.warn('[OmniRad Db] Migration check for patients mobile/address skipped:', migErr);
274
+ }
275
+
276
+ // Also ensure position column exists on users (from a prior migration)
277
+ try {
278
+ const userCols = globalForDb.__omniradSqlite!.pragma('table_info(users)') as any[];
279
+ const hasPosition = userCols.some((c: any) => c.name === 'position');
280
+ if (!hasPosition) {
281
+ globalForDb.__omniradSqlite!.exec(`ALTER TABLE users ADD COLUMN position TEXT DEFAULT '';`);
282
+ }
283
+ } catch (migErr) { /* ignore */ }
284
+
285
+ // Ensure PACS columns exist on reports (from a prior migration)
286
+ try {
287
+ const repCols = globalForDb.__omniradSqlite!.pragma('table_info(reports)') as any[];
288
+ const hasPacsStudy = repCols.some((c: any) => c.name === 'pacs_study_uid');
289
+ if (!hasPacsStudy) {
290
+ globalForDb.__omniradSqlite!.exec(`
291
+ ALTER TABLE reports ADD COLUMN pacs_study_uid TEXT;
292
+ ALTER TABLE reports ADD COLUMN pacs_series_uid TEXT;
293
+ ALTER TABLE reports ADD COLUMN pacs_source TEXT;
294
+ `);
295
+ }
296
+ } catch (migErr) { /* ignore */ }
297
+
298
+ // Ensure PACS columns exist on the config table (migration for databases created before PACS support)
299
+ try {
300
+ const cfgCols = globalForDb.__omniradSqlite!.pragma('table_info(config)') as any[];
301
+ const addIfMissing = (col: string, def: string) => {
302
+ if (!cfgCols.some((c: any) => c.name === col)) {
303
+ globalForDb.__omniradSqlite!.exec(`ALTER TABLE config ADD COLUMN ${col} TEXT DEFAULT '${def}';`);
304
+ console.log(`[OmniRad Db] Migrated: Added ${col} column to config table.`);
305
+ }
306
+ };
307
+ addIfMissing('pacs_orthanc_url', '');
308
+ addIfMissing('pacs_auth_type', 'none');
309
+ addIfMissing('pacs_username', '');
310
+ addIfMissing('pacs_password', '');
311
+ addIfMissing('pacs_bearer_token', '');
312
+ addIfMissing('pacs_ae_title', '');
313
+ } catch (migErr) {
314
+ console.warn('[OmniRad Db] Migration check for config PACS columns skipped:', migErr);
315
+ }
316
+
317
+ // Add purpose, langsmith_api_key, langsmith_project columns to ai_configurations
318
+ try {
319
+ const aiCols = globalForDb.__omniradSqlite!.pragma('table_info(ai_configurations)') as any[];
320
+ if (!aiCols.some((c: any) => c.name === 'purpose')) {
321
+ globalForDb.__omniradSqlite!.exec(`ALTER TABLE ai_configurations ADD COLUMN purpose TEXT DEFAULT 'report_generation';`);
322
+ console.log('[OmniRad Db] Migrated: Added purpose column to ai_configurations table.');
323
+ }
324
+ if (!aiCols.some((c: any) => c.name === 'langsmith_api_key')) {
325
+ globalForDb.__omniradSqlite!.exec(`ALTER TABLE ai_configurations ADD COLUMN langsmith_api_key TEXT;`);
326
+ console.log('[OmniRad Db] Migrated: Added langsmith_api_key column to ai_configurations table.');
327
+ }
328
+ if (!aiCols.some((c: any) => c.name === 'langsmith_project')) {
329
+ globalForDb.__omniradSqlite!.exec(`ALTER TABLE ai_configurations ADD COLUMN langsmith_project TEXT;`);
330
+ console.log('[OmniRad Db] Migrated: Added langsmith_project column to ai_configurations table.');
331
+ }
332
+ } catch (migErr) {
333
+ console.warn('[OmniRad Db] Migration check for ai_configurations copilot columns skipped:', migErr);
334
+ }
335
+
336
+ } catch (e: any) {
337
+ if (e.code === 'SQLITE_BUSY') {
338
+ console.warn('[OmniRad Db] SQLite is busy during auto-creation (another worker might be creating tables). Ignoring.');
339
+ } else {
340
+ console.error('[OmniRad Db] Error auto-creating tables:', e);
341
+ }
342
+ }
343
+ }
344
+
345
+ if (!globalForDb.__omniradDb) {
346
+ globalForDb.__omniradDb = drizzle(globalForDb.__omniradSqlite!, { schema });
347
+ }
348
+
349
+ export const db = globalForDb.__omniradDb;
350
+ export const sqlite = globalForDb.__omniradSqlite!;
@@ -0,0 +1,117 @@
1
+ CREATE TABLE `ai_configurations` (
2
+ `id` text PRIMARY KEY NOT NULL,
3
+ `provider_type` text NOT NULL,
4
+ `provider_name` text NOT NULL,
5
+ `api_endpoint_url` text,
6
+ `api_secret_key` text,
7
+ `model_name` text NOT NULL,
8
+ `is_active` integer DEFAULT false,
9
+ `is_vision_capable` integer DEFAULT false,
10
+ `max_tokens` integer DEFAULT 4096,
11
+ `temperature` real DEFAULT 0.3,
12
+ `timeout_seconds` integer DEFAULT 120,
13
+ `created_at` text NOT NULL,
14
+ `updated_at` text
15
+ );
16
+ --> statement-breakpoint
17
+ CREATE TABLE `appearance` (
18
+ `id` integer PRIMARY KEY DEFAULT 1 NOT NULL,
19
+ `theme` text DEFAULT 'dark',
20
+ `template` text DEFAULT 'standard',
21
+ `hospital_name` text DEFAULT '',
22
+ `logo` text DEFAULT ''
23
+ );
24
+ --> statement-breakpoint
25
+ CREATE TABLE `config` (
26
+ `id` integer PRIMARY KEY DEFAULT 1 NOT NULL,
27
+ `n8n_webhook_url` text DEFAULT '',
28
+ `supabase_url` text DEFAULT '',
29
+ `supabase_anon_key` text DEFAULT '',
30
+ `pacs_orthanc_url` text DEFAULT '',
31
+ `pacs_auth_type` text DEFAULT 'none',
32
+ `pacs_username` text DEFAULT '',
33
+ `pacs_password` text DEFAULT '',
34
+ `pacs_bearer_token` text DEFAULT '',
35
+ `pacs_ae_title` text DEFAULT ''
36
+ );
37
+ --> statement-breakpoint
38
+ CREATE TABLE `patients` (
39
+ `id` text PRIMARY KEY NOT NULL,
40
+ `patient_id_number` text,
41
+ `patient_name` text NOT NULL,
42
+ `date_of_birth` text,
43
+ `gender` text,
44
+ `contact_info` text,
45
+ `notes` text,
46
+ `created_at` text NOT NULL,
47
+ `updated_at` text
48
+ );
49
+ --> statement-breakpoint
50
+ CREATE TABLE `profile` (
51
+ `id` integer PRIMARY KEY DEFAULT 1 NOT NULL,
52
+ `full_name` text DEFAULT '',
53
+ `role` text DEFAULT '',
54
+ `hospital_name` text DEFAULT '',
55
+ `department` text DEFAULT ''
56
+ );
57
+ --> statement-breakpoint
58
+ CREATE TABLE `prompt_templates` (
59
+ `id` text PRIMARY KEY NOT NULL,
60
+ `name` text NOT NULL,
61
+ `template` text NOT NULL,
62
+ `is_active` integer DEFAULT false,
63
+ `created_at` text NOT NULL,
64
+ `updated_at` text
65
+ );
66
+ --> statement-breakpoint
67
+ CREATE TABLE `report_generation_logs` (
68
+ `id` text PRIMARY KEY NOT NULL,
69
+ `report_id` text,
70
+ `ai_config_id` text,
71
+ `model_used` text,
72
+ `prompt_template_id` text,
73
+ `raw_llm_response` text,
74
+ `parsed_successfully` integer,
75
+ `retry_count` integer,
76
+ `generation_time_ms` integer,
77
+ `error_message` text,
78
+ `created_at` text NOT NULL,
79
+ FOREIGN KEY (`ai_config_id`) REFERENCES `ai_configurations`(`id`) ON UPDATE no action ON DELETE no action
80
+ );
81
+ --> statement-breakpoint
82
+ CREATE TABLE `reports` (
83
+ `id` text PRIMARY KEY NOT NULL,
84
+ `patient_id` text,
85
+ `patient_name` text,
86
+ `modality` text,
87
+ `urgency` text,
88
+ `report_status` text DEFAULT 'Pending',
89
+ `report_data` text NOT NULL,
90
+ `image_data` text,
91
+ `pacs_study_uid` text,
92
+ `pacs_series_uid` text,
93
+ `pacs_source` text,
94
+ `created_at` text NOT NULL,
95
+ FOREIGN KEY (`patient_id`) REFERENCES `patients`(`id`) ON UPDATE no action ON DELETE cascade
96
+ );
97
+ --> statement-breakpoint
98
+ CREATE TABLE `sessions` (
99
+ `id` text PRIMARY KEY NOT NULL,
100
+ `user_id` text NOT NULL,
101
+ `expires_at` integer NOT NULL,
102
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
103
+ );
104
+ --> statement-breakpoint
105
+ CREATE TABLE `users` (
106
+ `id` text PRIMARY KEY NOT NULL,
107
+ `full_name` text NOT NULL,
108
+ `username` text NOT NULL,
109
+ `email` text NOT NULL,
110
+ `password_hash` text NOT NULL,
111
+ `role` text DEFAULT 'User',
112
+ `position` text DEFAULT '',
113
+ `created_at` text NOT NULL
114
+ );
115
+ --> statement-breakpoint
116
+ CREATE UNIQUE INDEX `users_username_unique` ON `users` (`username`);--> statement-breakpoint
117
+ CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);