feat(db): lab_drafts, lab_draws, biomarkers tables
Refs PAI-91.
This commit is contained in:
33
server/drizzle/0001_labs.sql
Normal file
33
server/drizzle/0001_labs.sql
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
CREATE TABLE `biomarkers` (
|
||||||
|
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
`draw_id` text NOT NULL,
|
||||||
|
`panel` text NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`marker` text NOT NULL,
|
||||||
|
`analyte_key` text,
|
||||||
|
`value` text NOT NULL,
|
||||||
|
`value_num` real,
|
||||||
|
`unit` text,
|
||||||
|
`reference_range` text,
|
||||||
|
`flagged` integer DEFAULT 0 NOT NULL,
|
||||||
|
`value_canonical` real,
|
||||||
|
`canonical_unit` text
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `lab_drafts` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`filename` text NOT NULL,
|
||||||
|
`file_path` text NOT NULL,
|
||||||
|
`status` text NOT NULL,
|
||||||
|
`extracted` text,
|
||||||
|
`error` text,
|
||||||
|
`created_at` integer NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `lab_draws` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`collected_at` text NOT NULL,
|
||||||
|
`lab_name` text,
|
||||||
|
`draft_id` text,
|
||||||
|
`created_at` integer NOT NULL
|
||||||
|
);
|
||||||
279
server/drizzle/meta/0001_snapshot.json
Normal file
279
server/drizzle/meta/0001_snapshot.json
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "8d0cf0e5-668d-4526-a0cb-5a264d813be3",
|
||||||
|
"prevId": "383d7bd7-3ebf-456e-8c5c-f39cebab14c4",
|
||||||
|
"tables": {
|
||||||
|
"biomarkers": {
|
||||||
|
"name": "biomarkers",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"draw_id": {
|
||||||
|
"name": "draw_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"panel": {
|
||||||
|
"name": "panel",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"marker": {
|
||||||
|
"name": "marker",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"analyte_key": {
|
||||||
|
"name": "analyte_key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value_num": {
|
||||||
|
"name": "value_num",
|
||||||
|
"type": "real",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"unit": {
|
||||||
|
"name": "unit",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"reference_range": {
|
||||||
|
"name": "reference_range",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"flagged": {
|
||||||
|
"name": "flagged",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"value_canonical": {
|
||||||
|
"name": "value_canonical",
|
||||||
|
"type": "real",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"canonical_unit": {
|
||||||
|
"name": "canonical_unit",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"lab_drafts": {
|
||||||
|
"name": "lab_drafts",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"filename": {
|
||||||
|
"name": "filename",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"file_path": {
|
||||||
|
"name": "file_path",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"extracted": {
|
||||||
|
"name": "extracted",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"name": "error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"lab_draws": {
|
||||||
|
"name": "lab_draws",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"collected_at": {
|
||||||
|
"name": "collected_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"lab_name": {
|
||||||
|
"name": "lab_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"draft_id": {
|
||||||
|
"name": "draft_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"name": "sessions",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"name": "settings",
|
||||||
|
"columns": {
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,13 @@
|
|||||||
"when": 1787000672259,
|
"when": 1787000672259,
|
||||||
"tag": "0000_init",
|
"tag": "0000_init",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1787005028172,
|
||||||
|
"tag": "0001_labs",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
import { integer, real, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
||||||
|
|
||||||
export const settings = sqliteTable("settings", {
|
export const settings = sqliteTable("settings", {
|
||||||
key: text("key").primaryKey(),
|
key: text("key").primaryKey(),
|
||||||
@@ -10,3 +10,37 @@ export const sessions = sqliteTable("sessions", {
|
|||||||
createdAt: integer("created_at").notNull(),
|
createdAt: integer("created_at").notNull(),
|
||||||
expiresAt: integer("expires_at").notNull(),
|
expiresAt: integer("expires_at").notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const labDrafts = sqliteTable("lab_drafts", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
filename: text("filename").notNull(),
|
||||||
|
filePath: text("file_path").notNull(),
|
||||||
|
status: text("status").notNull(), // pending | confirmed | discarded
|
||||||
|
extracted: text("extracted"),
|
||||||
|
error: text("error"),
|
||||||
|
createdAt: integer("created_at").notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const labDraws = sqliteTable("lab_draws", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
collectedAt: text("collected_at").notNull(), // ISO date YYYY-MM-DD
|
||||||
|
labName: text("lab_name"),
|
||||||
|
draftId: text("draft_id"),
|
||||||
|
createdAt: integer("created_at").notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const biomarkers = sqliteTable("biomarkers", {
|
||||||
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||||
|
drawId: text("draw_id").notNull(),
|
||||||
|
panel: text("panel").notNull(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
marker: text("marker").notNull(),
|
||||||
|
analyteKey: text("analyte_key"),
|
||||||
|
value: text("value").notNull(),
|
||||||
|
valueNum: real("value_num"),
|
||||||
|
unit: text("unit"),
|
||||||
|
referenceRange: text("reference_range"),
|
||||||
|
flagged: integer("flagged").notNull().default(0),
|
||||||
|
valueCanonical: real("value_canonical"),
|
||||||
|
canonicalUnit: text("canonical_unit"),
|
||||||
|
});
|
||||||
|
|||||||
21
server/test/db-labs.test.ts
Normal file
21
server/test/db-labs.test.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { describe, expect, test } from "bun:test";
|
||||||
|
import { mkdtempSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { openDb } from "../src/db";
|
||||||
|
import { biomarkers, labDraws } from "../src/db/schema";
|
||||||
|
|
||||||
|
describe("labs schema", () => {
|
||||||
|
test("draw + biomarker round-trip", async () => {
|
||||||
|
const db = openDb(mkdtempSync(join(tmpdir(), "helios-")));
|
||||||
|
await db.insert(labDraws).values({ id: "d1", collectedAt: "2026-01-15", labName: "Acme Lab", draftId: null, createdAt: 1 });
|
||||||
|
await db.insert(biomarkers).values({
|
||||||
|
drawId: "d1", panel: "lipids", name: "LDL Cholesterol", marker: "ldl", analyteKey: "ldl",
|
||||||
|
value: "3.1", valueNum: 3.1, unit: "mmol/L", referenceRange: "< 3.4", flagged: 0,
|
||||||
|
valueCanonical: 3.1, canonicalUnit: "mmol/L",
|
||||||
|
});
|
||||||
|
const rows = await db.select().from(biomarkers);
|
||||||
|
expect(rows).toHaveLength(1);
|
||||||
|
expect(rows[0].marker).toBe("ldl");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user