diff --git a/src/__tests__/doctor-report-control-gating-guard.test.ts b/src/__tests__/doctor-report-control-gating-guard.test.ts index 57227ecca..ad5fd641d 100644 --- a/src/__tests__/doctor-report-control-gating-guard.test.ts +++ b/src/__tests__/doctor-report-control-gating-guard.test.ts @@ -74,9 +74,9 @@ describe("report scope gating — structural guard", () => { it("reads a plausible catalogue", () => { // Sanity floor: a silently degraded catalogue would pass everything below // vacuously. - expect(ALL_LEAF_IDS.length).toBe(93); + expect(ALL_LEAF_IDS.length).toBe(94); expect(Object.keys(MEASUREMENT_LEAF_GROUP)).toHaveLength(77); - expect(Object.keys(STRUCTURED_LEAF_GROUP)).toHaveLength(16); + expect(Object.keys(STRUCTURED_LEAF_GROUP)).toHaveLength(17); }); it("places every leaf in exactly one group", () => { diff --git a/src/components/settings/__tests__/report-selection-panel.test.tsx b/src/components/settings/__tests__/report-selection-panel.test.tsx index 83535c8b4..d18a73f5c 100644 --- a/src/components/settings/__tests__/report-selection-panel.test.tsx +++ b/src/components/settings/__tests__/report-selection-panel.test.tsx @@ -187,7 +187,7 @@ describe(" — the first run", () => { // These six carry the standard template's leaves. On a first run they read // 0/n like every other group: the template is a button, not a starting // state. - expect(groupChip(html, "identity")).toBe("0/2"); + expect(groupChip(html, "identity")).toBe("0/3"); expect(groupChip(html, "vitals")).toBe("0/7"); expect(groupChip(html, "body")).toBe("0/12"); expect(groupChip(html, "labs")).toBe("0/1"); diff --git a/src/components/settings/__tests__/sharing-reselect.test.tsx b/src/components/settings/__tests__/sharing-reselect.test.tsx index 138f2ea75..4a01667fc 100644 --- a/src/components/settings/__tests__/sharing-reselect.test.tsx +++ b/src/components/settings/__tests__/sharing-reselect.test.tsx @@ -62,15 +62,16 @@ describe("share-link create form — re-mint seeding", () => { const html = render(); expect(html).toContain('data-testid="report-scope-picker-share"'); expect(html).toContain('data-testid="report-group-row-identity-share"'); - // The server refuses the leaf with a 422; a control that cannot be - // honoured has no business rendering, so the identity group is one leaf - // wide here and two wide in the export panel. Nothing in it is on: a new - // link starts with an empty scope on every surface. + // The server refuses the insurance leaf with a 422; a control that cannot + // be honoured has no business rendering, so the identity group is two + // leaves wide here (patient identity + emergency) and three wide in the + // export panel. Nothing in it is on: a new link starts with an empty scope + // on every surface. const row = html.match( /data-testid="report-group-row-identity-share"[\s\S]*?<\/div>/, )?.[0]; expect(row).toBeDefined(); - expect(row).toContain(">0/1<"); + expect(row).toContain(">0/2<"); // The fenced tier renders its leaves unconditionally; none is checked. expect(html).toContain('data-testid="report-leaf-MOOD-share"'); expect(html).not.toContain('data-testid="report-leaf-INSURANCE-share"'); diff --git a/src/lib/__tests__/doctor-report-pdf-core.test.ts b/src/lib/__tests__/doctor-report-pdf-core.test.ts index ff2ca9606..48092270c 100644 --- a/src/lib/__tests__/doctor-report-pdf-core.test.ts +++ b/src/lib/__tests__/doctor-report-pdf-core.test.ts @@ -591,6 +591,51 @@ describe("doctor-report illness section", () => { }); }); +// ── emergency first page ── the Notfalldaten sheet owns page one, but only +// when the aggregator populated `data.emergency` (the EMERGENCY leaf admitted +// AND the profile holds something). A null payload — which is exactly what the +// aggregator returns when the leaf is NOT admitted — withholds the page. +describe("doctor-report emergency first page", () => { + const EMERGENCY: NonNullable = { + bloodType: "O_NEG", + organDonor: "YES", + advanceDirective: "EXISTS", + contacts: "ICE contact on the recorded number", + contactsUnreadable: false, + implants: null, + implantsUnreadable: false, + note: null, + noteUnreadable: false, + }; + + it("prints the emergency sheet on page one when emergency data is present", async () => { + const { t } = getServerTranslator("en"); + const data = makeData({ emergency: EMERGENCY }); + const options = { t, locale: "en" as const, now: FIXED_NOW }; + const text = await extractText(renderDoctorReportPdfBytes(data, options)); + expect(text).toContain("Emergency information"); + expect(text).toContain("O negative"); + expect(text).toContain("ICE contact on the recorded number"); + // The baseline report is two pages; the emergency sheet plus its page break + // add exactly one. + expect(buildDoctorReportPdfDocument(data, options).getNumberOfPages()).toBe( + 3, + ); + }); + + it("withholds the emergency page when the leaf was not admitted (payload null)", async () => { + const { t } = getServerTranslator("en"); + const data = makeData({ emergency: null }); + const options = { t, locale: "en" as const, now: FIXED_NOW }; + const text = await extractText(renderDoctorReportPdfBytes(data, options)); + expect(text).not.toContain("Emergency information"); + // No emergency page, no extra page break: back to the two-page baseline. + expect(buildDoctorReportPdfDocument(data, options).getNumberOfPages()).toBe( + 2, + ); + }); +}); + describe("extracted doctor-report section boundaries", () => { it("header/profile prefers the legal name and renders insurance identity", async () => { const data = makeData({ diff --git a/src/lib/doctor-report-pdf-core.ts b/src/lib/doctor-report-pdf-core.ts index 0dbf833f9..f920068eb 100644 --- a/src/lib/doctor-report-pdf-core.ts +++ b/src/lib/doctor-report-pdf-core.ts @@ -25,6 +25,7 @@ import { import type { Locale } from "./i18n/config"; import { isValidTimezone } from "./tz/format"; import type { DoctorReportData } from "./doctor-report-data"; +import { buildEmergencyFirstPageSection } from "./doctor-report-pdf/emergency-first-page-section"; import { buildHeaderProfileSection } from "./doctor-report-pdf/header-profile-section"; import { buildMeasurementsChartsSection } from "./doctor-report-pdf/measurements-charts-section"; import { buildMedicationMoodWellnessSection } from "./doctor-report-pdf/medication-mood-wellness-section"; @@ -289,6 +290,15 @@ export function buildDoctorReportPdfDocument( }; let cursor = pdfCursorState(doc, margin); + // The emergency sheet owns page one. It renders only when emergency data is + // present (the aggregator gates on the EMERGENCY leaf + presence), and when it + // does, an explicit page break sends the rest of the report to page two so the + // opening page carries nothing else. + if (data.emergency) { + cursor = buildEmergencyFirstPageSection(context, cursor); + doc.addPage(); + cursor = pdfCursorState(doc, margin); + } cursor = buildHeaderProfileSection(context, cursor); cursor = buildMeasurementsChartsSection(context, cursor); cursor = buildMedicationMoodWellnessSection(context, cursor); diff --git a/src/lib/doctor-report-pdf/emergency-first-page-section.ts b/src/lib/doctor-report-pdf/emergency-first-page-section.ts new file mode 100644 index 000000000..ecb56efa4 --- /dev/null +++ b/src/lib/doctor-report-pdf/emergency-first-page-section.ts @@ -0,0 +1,158 @@ +import { + pdfCursorState, + type DoctorReportPdfCursorState, + type DoctorReportPdfRenderContext, +} from "./render-context"; + +/** + * Page one of the doctor report: the emergency ("Notfalldaten") sheet a + * clinician reads first in an acute situation. + * + * Rendered before the header profile and followed by an explicit page break, so + * whatever a first responder needs — blood type, severe allergies, the current + * drug list, chronic conditions, implants, the advance-directive and + * organ-donor declarations, who to call — sits alone on the opening page. + * + * The section only renders when `data.emergency` is present; the aggregator + * returns null unless the EMERGENCY leaf was admitted AND the profile holds + * something worth surfacing, so the presence check here is the render half of + * that one gate. + */ +export function buildEmergencyFirstPageSection( + context: DoctorReportPdfRenderContext, + state: DoctorReportPdfCursorState, +): DoctorReportPdfCursorState { + const { doc, data, t, margin, pageWidth, contentMaxY } = context; + const emergency = data.emergency; + if (!emergency) return state; + + const contentWidth = pageWidth - 2 * margin; + let y = state.y; + + const ensureRoom = (needed: number) => { + if (y + needed > contentMaxY) { + doc.addPage(); + y = margin; + } + }; + + // Banner: a filled bar so the page reads as the emergency sheet at a glance. + doc.setFillColor(180, 30, 30); + doc.rect(margin, y, contentWidth, 12, "F"); + doc.setTextColor(255, 255, 255); + doc.setFontSize(15); + doc.setFont("helvetica", "bold"); + doc.text(t("doctorReport.emergency.title"), margin + 3, y + 8); + y += 12; + + doc.setTextColor(120, 120, 120); + doc.setFontSize(8); + doc.setFont("helvetica", "normal"); + y += 5; + doc.text(t("doctorReport.emergency.subtitle"), margin, y); + y += 6; + + // A labelled paragraph block: bold label, wrapped body beneath. `emphasise` + // draws the value darker (blood type, severe allergies) so it carries. + const block = (label: string, body: string, emphasise = false) => { + ensureRoom(5 + 4.8); + doc.setFontSize(10); + doc.setFont("helvetica", "bold"); + doc.setTextColor(40, 40, 40); + doc.text(label, margin, y); + y += 5; + doc.setFontSize(emphasise ? 11 : 9.5); + doc.setFont("helvetica", emphasise ? "bold" : "normal"); + doc.setTextColor( + emphasise ? 150 : 60, + emphasise ? 20 : 60, + emphasise ? 20 : 60, + ); + for (const wrapped of doc.splitTextToSize(body, contentWidth) as string[]) { + ensureRoom(4.8); + doc.text(wrapped, margin, y); + y += 4.8; + } + y += 3; + }; + + const notRecorded = t("doctorReport.emergency.none"); + + // Blood type. + const bloodType = + emergency.bloodType && emergency.bloodType !== "UNKNOWN" + ? t(`doctorReport.emergency.bloodTypeValues.${emergency.bloodType}`) + : emergency.bloodType === "UNKNOWN" + ? t("doctorReport.emergency.bloodTypeUnknown") + : notRecorded; + block(t("doctorReport.emergency.bloodType"), bloodType, true); + + // Severe allergies — reuse the report's allergy collection, filter SEVERE. + const severe = (data.allergies ?? []).filter((a) => a.severity === "SEVERE"); + if (severe.length > 0) { + const lines = severe.map((a) => { + const reaction = a.reactionUnreadable + ? t("doctorReport.emergency.unreadable") + : a.reaction; + return reaction ? `${a.substance} (${reaction})` : a.substance; + }); + block(t("doctorReport.emergency.severeAllergies"), lines.join("; "), true); + } else { + block(t("doctorReport.emergency.severeAllergies"), notRecorded); + } + + // Active medications — reuse the report's medication list. + const meds = data.medications ?? []; + const medLine = + meds.length > 0 + ? meds.map((m) => (m.dose ? `${m.name} ${m.dose}` : m.name)).join("; ") + : notRecorded; + block(t("doctorReport.emergency.activeMedications"), medLine); + + // Chronic conditions — ongoing illness episodes plus the anamnesis free text. + const chronic = (data.illnessEpisodes ?? []) + .filter((e) => e.lifecycle === "CHRONIC_ONGOING") + .map((e) => e.label); + const anamnesisConditions = data.anamnesis?.conditions ?? null; + const conditionParts = [...chronic]; + if (anamnesisConditions) conditionParts.push(anamnesisConditions); + block( + t("doctorReport.emergency.chronicConditions"), + conditionParts.length > 0 ? conditionParts.join("; ") : notRecorded, + ); + + // Implants / devices. + const implants = emergency.implantsUnreadable + ? t("doctorReport.emergency.unreadable") + : (emergency.implants ?? notRecorded); + block(t("doctorReport.emergency.implants"), implants); + + // Advance directive + organ donor. + const advanceDirective = emergency.advanceDirective + ? t( + `doctorReport.emergency.advanceDirectiveValues.${emergency.advanceDirective}`, + ) + : notRecorded; + block(t("doctorReport.emergency.advanceDirective"), advanceDirective); + + const organDonor = emergency.organDonor + ? t(`doctorReport.emergency.organDonorValues.${emergency.organDonor}`) + : notRecorded; + block(t("doctorReport.emergency.organDonor"), organDonor); + + // Emergency contacts. + const contacts = emergency.contactsUnreadable + ? t("doctorReport.emergency.unreadable") + : (emergency.contacts ?? notRecorded); + block(t("doctorReport.emergency.contacts"), contacts, true); + + // ICE note. + const note = emergency.noteUnreadable + ? t("doctorReport.emergency.unreadable") + : emergency.note; + if (note) { + block(t("doctorReport.emergency.notes"), note); + } + + return pdfCursorState(doc, y); +} diff --git a/src/lib/doctor-report-types.ts b/src/lib/doctor-report-types.ts index 056cbdd1e..c342f50e8 100644 --- a/src/lib/doctor-report-types.ts +++ b/src/lib/doctor-report-types.ts @@ -25,6 +25,11 @@ import type { ShiftScheduleValue, SmokingStatusValue, } from "@/lib/validations/health-profile-facts"; +import type { + AdvanceDirectiveStatusValue, + EmergencyBloodTypeValue, + OrganDonorStatusValue, +} from "@/lib/validations/emergency-profile"; export interface DoctorReportStats { avg: number; @@ -395,6 +400,25 @@ export interface DoctorReportData { "SMOKING_STATUS" | "ALCOHOL_PATTERN" | "SHIFT_SCHEDULE" >; } | null; + /** + * Emergency ("Notfalldaten") facts for the page-one section. Present only + * when the `EMERGENCY` leaf was admitted AND at least one field holds a + * value; null otherwise, and the PDF then emits no emergency page. The three + * enums cross as constants (the renderer names them in the report's language); + * the free text decrypts fail-soft, an `*Unreadable` flag distinguishing a + * key-rotation gap from a genuinely unset field. + */ + emergency?: { + bloodType: EmergencyBloodTypeValue | null; + organDonor: OrganDonorStatusValue | null; + advanceDirective: AdvanceDirectiveStatusValue | null; + contacts: string | null; + contactsUnreadable: boolean; + implants: string | null; + implantsUnreadable: boolean; + note: string | null; + noteUnreadable: boolean; + } | null; } /** The three persisted score types surfaced in the wellness summary. */ diff --git a/src/lib/doctor-report/__tests__/leaf-gating-sweep.test.ts b/src/lib/doctor-report/__tests__/leaf-gating-sweep.test.ts index ffb179dd2..5ebd93d96 100644 --- a/src/lib/doctor-report/__tests__/leaf-gating-sweep.test.ts +++ b/src/lib/doctor-report/__tests__/leaf-gating-sweep.test.ts @@ -253,7 +253,19 @@ function seed() { rows.familyHistoryEntry = [ { relationship: "MOTHER", condition: "Hypertension", ageAtOnset: 50 }, ]; - rows.userHealthProfileUnique = [{ conditionsEncrypted: new Uint8Array([1]) }]; + rows.userHealthProfileUnique = [ + { + conditionsEncrypted: new Uint8Array([1]), + // Emergency profile: a real blood type makes the EMERGENCY leaf change + // the payload, so the sweep can prove its control is not inert. + emergencyBloodType: "O_POS", + organDonorStatus: "YES", + advanceDirectiveStatus: "EXISTS", + emergencyContactsEncrypted: null, + emergencyImplantsEncrypted: null, + emergencyNoteEncrypted: null, + }, + ]; rows.healthProfileFactRevision = [ { kind: "SMOKING_STATUS", @@ -290,6 +302,7 @@ describe("per-leaf gating sweep", () => { expect(allPayload.mood).not.toBeNull(); expect(allPayload.cycle).not.toBeNull(); expect(allPayload.anamnesis).not.toBeNull(); + expect(allPayload.emergency).not.toBeNull(); expect(allPayload.glp1).not.toBeNull(); expect(allPayload.medications.length).toBeGreaterThan(0); expect(allPayload.medicationAdministrations?.length ?? 0).toBeGreaterThan( @@ -443,17 +456,27 @@ describe("zero read for unchosen leaves", () => { ["VISITS", "encounter"], ["IMMUNIZATIONS", "vaccinationRecord"], ["ANAMNESIS", "userHealthProfile"], + ["EMERGENCY", "userHealthProfile"], ["ANAMNESIS", "healthProfileFactRevision"], ]; + // ANAMNESIS and EMERGENCY both read `userHealthProfile`, so withholding one + // while the other stays selected still queries the table. Both must be off + // before the read stops — the zero-read guarantee is over the SET of leaves + // that touch a table, not each one in isolation. + const TABLE_READERS: Record = { + userHealthProfile: ["ANAMNESIS", "EMERGENCY"], + }; + it.each(gated)( "never queries %s's table when it was not chosen", async (leaf, model) => { calls.length = 0; + const withheld = new Set(TABLE_READERS[model] ?? [leaf]); await collectDoctorReportData( "u1", RANGE, - selectionFromLeaves(ALL_LEAF_IDS.filter((l) => l !== leaf)), + selectionFromLeaves(ALL_LEAF_IDS.filter((l) => !withheld.has(l))), { moduleMap: moduleMap() }, ); expect(calls).not.toContain(model); @@ -503,6 +526,7 @@ describe("the empty selection serves nothing", () => { expect(payload.illnessEpisodes).toBeNull(); expect(payload.visits).toBeNull(); expect(payload.anamnesis).toBeNull(); + expect(payload.emergency).toBeNull(); expect(payload.cycle).toBeNull(); expect(payload.mood).toBeNull(); expect(payload.glp1).toBeNull(); diff --git a/src/lib/doctor-report/clinical-records.ts b/src/lib/doctor-report/clinical-records.ts index 380896a91..84ba348bd 100644 --- a/src/lib/doctor-report/clinical-records.ts +++ b/src/lib/doctor-report/clinical-records.ts @@ -18,6 +18,10 @@ import { decryptAllergyReaction } from "@/lib/doctor-report-helpers"; import type { DoctorReportData } from "@/lib/doctor-report-types"; import { decryptFromBytes } from "@/lib/ai/coach/bytes-codec"; import { decryptHealthProfileFactValue } from "@/lib/profile/health-facts"; +import { + emergencyProfileHasContent, + readEmergencyProfile, +} from "@/lib/profile/emergency-profile"; import type { HealthProfileFactKind, HealthProfileFactValue, @@ -309,6 +313,23 @@ export async function loadFamilyHistory( return rows.length > 0 ? rows : null; } +/** + * Emergency ("Notfalldaten") facts for the page-one section. The caller owns + * the selection gate, so reaching this function always means EMERGENCY was + * admitted. Returns null when nothing is worth surfacing, so the PDF emits no + * page over an emergency profile the person never filled in — the presence half + * of the page gate, kept beside the read so the two cannot drift. + * + * The DTO shape is exactly the report's emergency shape, so it crosses through + * unchanged. Free text decrypts fail-soft inside `readEmergencyProfile`. + */ +export async function loadEmergency( + userId: string, +): Promise { + const dto = await readEmergencyProfile(userId); + return emergencyProfileHasContent(dto) ? dto : null; +} + /** * Chronic conditions and the three current structured facts. The caller owns * the selection gate, so reaching this function always means ANAMNESIS was diff --git a/src/lib/doctor-report/collect.ts b/src/lib/doctor-report/collect.ts index 50c9ca5fe..42ce9e3dc 100644 --- a/src/lib/doctor-report/collect.ts +++ b/src/lib/doctor-report/collect.ts @@ -62,6 +62,7 @@ import { buildAdministrationLedger, buildGlp1Block } from "./medications"; import { loadAllergies, loadAnamnesis, + loadEmergency, loadFamilyHistory, loadIllnessEpisodes, loadImmunizations, @@ -505,6 +506,7 @@ export async function collectDoctorReportData( allergies, familyHistory, anamnesis, + emergency, ] = await Promise.all([ gate.admits("CYCLE") ? buildCycleExportSummary(userId, end.toISOString().slice(0, 10)) @@ -528,6 +530,7 @@ export async function collectDoctorReportData( ? loadFamilyHistory(userId) : Promise.resolve(null), gate.admits("ANAMNESIS") ? loadAnamnesis(userId) : Promise.resolve(null), + gate.admits("EMERGENCY") ? loadEmergency(userId) : Promise.resolve(null), ]); const identityOn = gate.admits("PATIENT_IDENTITY"); @@ -596,5 +599,6 @@ export async function collectDoctorReportData( allergies, familyHistory, anamnesis, + emergency, }; } diff --git a/src/lib/report-selection/__tests__/catalogue-guard.test.ts b/src/lib/report-selection/__tests__/catalogue-guard.test.ts index cce014fe6..038c50b57 100644 --- a/src/lib/report-selection/__tests__/catalogue-guard.test.ts +++ b/src/lib/report-selection/__tests__/catalogue-guard.test.ts @@ -52,8 +52,8 @@ function resolves(key: string): boolean { describe("report selection catalogue", () => { it("carries every measurement type and every structured leaf", () => { expect(MEASUREMENT_LEAF_IDS).toHaveLength(77); - expect(STRUCTURED_LEAF_IDS).toHaveLength(16); - expect(ALL_LEAF_IDS).toHaveLength(93); + expect(STRUCTURED_LEAF_IDS).toHaveLength(17); + expect(ALL_LEAF_IDS).toHaveLength(94); }); it("resolves a label for every leaf", () => { diff --git a/src/lib/report-selection/__tests__/panel-state.test.ts b/src/lib/report-selection/__tests__/panel-state.test.ts index 694d4c15d..ba27ba526 100644 --- a/src/lib/report-selection/__tests__/panel-state.test.ts +++ b/src/lib/report-selection/__tests__/panel-state.test.ts @@ -48,11 +48,14 @@ describe("scope picker state", () => { // control covers something they cannot see or reach. const identity = REPORT_GROUPS.find((g) => g.id === "identity")!; const rendered = identity.leaves.filter((l) => l !== "INSURANCE"); - const selected = new Set(["PATIENT_IDENTITY"]); - expect(groupCount(rendered, selected)).toEqual({ on: 1, total: 1 }); + // The share-link form renders identity minus insurance: patient identity + // and the emergency leaf. Selecting both is "all" of the rendered set even + // though the catalogue group also carries the hidden insurance leaf. + const selected = new Set(["PATIENT_IDENTITY", "EMERGENCY"]); + expect(groupCount(rendered, selected)).toEqual({ on: 2, total: 2 }); expect(groupCheckState(rendered, selected)).toBe("all"); - // And the whole group, for contrast. - expect(groupCount(identity.leaves, selected)).toEqual({ on: 1, total: 2 }); + // And the whole group, for contrast: insurance is unselected and hidden. + expect(groupCount(identity.leaves, selected)).toEqual({ on: 2, total: 3 }); }); it("never adds a leaf the surface does not render", () => { diff --git a/src/lib/report-selection/catalogue.ts b/src/lib/report-selection/catalogue.ts index ce96feed5..681112289 100644 --- a/src/lib/report-selection/catalogue.ts +++ b/src/lib/report-selection/catalogue.ts @@ -37,6 +37,7 @@ import { MEASUREMENT_TYPE_LABEL_KEYS } from "@/lib/measurements/type-label-keys" */ export type StructuredLeafId = | "PATIENT_IDENTITY" + | "EMERGENCY" | "INSURANCE" | "GLUCOSE_PANEL" | "LAB_RESULTS" @@ -192,6 +193,7 @@ export const MEASUREMENT_LEAF_GROUP: Record = { /** Structured leaf → group. Exhaustive over the closed union. */ export const STRUCTURED_LEAF_GROUP: Record = { PATIENT_IDENTITY: "identity", + EMERGENCY: "identity", INSURANCE: "identity", GLUCOSE_PANEL: "glucose", LAB_RESULTS: "labs", @@ -209,9 +211,10 @@ export const STRUCTURED_LEAF_GROUP: Record = { ANAMNESIS: "sensitive", }; -/** i18n label keys for the 16 structured leaves. */ +/** i18n label keys for the 17 structured leaves. */ export const STRUCTURED_LEAF_LABEL_KEYS: Record = { PATIENT_IDENTITY: "reportSelection.leafPatientIdentity", + EMERGENCY: "reportSelection.leafEmergency", INSURANCE: "reportSelection.leafInsurance", GLUCOSE_PANEL: "reportSelection.leafGlucosePanel", LAB_RESULTS: "reportSelection.leafLabResults", diff --git a/src/lib/report-selection/template.ts b/src/lib/report-selection/template.ts index 722efbbbe..0a2cc953f 100644 --- a/src/lib/report-selection/template.ts +++ b/src/lib/report-selection/template.ts @@ -25,6 +25,7 @@ import { SENSITIVE_LEAF_IDS, type ReportLeafId } from "./catalogue"; */ export const STANDARD_TEMPLATE_LEAVES: readonly ReportLeafId[] = [ "PATIENT_IDENTITY", + "EMERGENCY", "INSURANCE", "BLOOD_PRESSURE_SYS", "BLOOD_PRESSURE_DIA",