feat(labs): draw list/detail, marker history, PDF file serving
This commit is contained in:
@@ -508,6 +508,12 @@ for (const a of ANALYTES) {
|
||||
for (const al of a.aliases) INDEX.set(norm(al), a);
|
||||
}
|
||||
|
||||
const BY_KEY = new Map(ANALYTES.map((a) => [a.key, a]));
|
||||
|
||||
export function resolveAnalyte(name: string): Analyte | null {
|
||||
return INDEX.get(norm(name)) ?? null;
|
||||
}
|
||||
|
||||
export function getAnalyte(key: string): Analyte | null {
|
||||
return BY_KEY.get(key) ?? null;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { Hono } from "hono";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { existsSync, mkdirSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import type { Db } from "../db";
|
||||
import { biomarkers, labDrafts, labDraws } from "../db/schema";
|
||||
import { extractFromText } from "../lib/extract";
|
||||
import { normalizeMarker } from "../lib/normalize";
|
||||
import { pdfToText } from "../lib/pdf";
|
||||
import { getAnalyte } from "../lib/analytes";
|
||||
import { ConfirmDraftBody } from "@helios/shared";
|
||||
|
||||
export type LabsDeps = { db: Db; key: Buffer; dataDir: string; llmFetch?: typeof fetch };
|
||||
@@ -111,5 +112,58 @@ export function labsRoutes(deps: LabsDeps) {
|
||||
return c.body(null, 204);
|
||||
});
|
||||
|
||||
app.get("/labs/draws", async (c) => {
|
||||
const draws = await deps.db.select().from(labDraws).orderBy(desc(labDraws.collectedAt));
|
||||
const rows = await deps.db.select().from(biomarkers);
|
||||
return c.json({
|
||||
draws: draws.map((d) => ({
|
||||
id: d.id, collectedAt: d.collectedAt, labName: d.labName,
|
||||
markerCount: rows.filter((r) => r.drawId === d.id).length,
|
||||
flaggedCount: rows.filter((r) => r.drawId === d.id && r.flagged === 1).length,
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/labs/draws/:id", async (c) => {
|
||||
const d = (await deps.db.select().from(labDraws).where(eq(labDraws.id, c.req.param("id")))).at(0);
|
||||
if (!d) return c.json({ error: "not found" }, 404);
|
||||
const rows = await deps.db.select().from(biomarkers).where(eq(biomarkers.drawId, d.id));
|
||||
const byPanel = new Map<string, typeof rows>();
|
||||
for (const r of rows) byPanel.set(r.panel, [...(byPanel.get(r.panel) ?? []), r]);
|
||||
return c.json({
|
||||
id: d.id, collectedAt: d.collectedAt, labName: d.labName,
|
||||
panels: [...byPanel.keys()].sort().map((panel) => ({
|
||||
panel,
|
||||
markers: byPanel.get(panel)!
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((r) => ({ ...r, flagged: r.flagged === 1 })),
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/labs/markers/:key/history", async (c) => {
|
||||
const analyte = getAnalyte(c.req.param("key"));
|
||||
if (!analyte) return c.json({ error: "unknown marker" }, 404);
|
||||
const draws = await deps.db.select().from(labDraws);
|
||||
const dates = new Map(draws.map((d) => [d.id, d.collectedAt]));
|
||||
const rows = await deps.db.select().from(biomarkers).where(eq(biomarkers.analyteKey, analyte.key));
|
||||
const points = rows
|
||||
.filter((r) => r.valueCanonical !== null)
|
||||
.map((r) => ({ drawId: r.drawId, collectedAt: dates.get(r.drawId) ?? "", value: r.valueCanonical! }))
|
||||
.sort((a, b) => a.collectedAt.localeCompare(b.collectedAt));
|
||||
return c.json({ key: analyte.key, display: analyte.display, canonicalUnit: analyte.canonicalUnit, points });
|
||||
});
|
||||
|
||||
app.get("/labs/drafts/:id/file", async (c) => {
|
||||
const row = (await deps.db.select().from(labDrafts).where(eq(labDrafts.id, c.req.param("id")))).at(0);
|
||||
if (!row || !existsSync(row.filePath)) return c.json({ error: "not found" }, 404);
|
||||
return new Response(Bun.file(row.filePath), {
|
||||
headers: {
|
||||
"content-type": "application/pdf",
|
||||
"content-disposition": `inline; filename="${row.filename.replace(/[^\w.-]/g, "_")}"`,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user