mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
fix(doctor-report): name a managed record by its display name
The report cover, the PDF author field, the FHIR Patient and the share page printed `fullName ?? username`, and the collector never read the display name. A managed record has no way to get a full name, so its generated `managed-<hex>` login landed on every clinical document made for it; an own account printed the login instead of its display name. The collector now resolves the name once (`patientName()`: full name, display name, login, never a managed record's generated handle) and the five readers print that one field. `patient.username` and `patient.fullName` leave the report payload; nothing else read them.
This commit is contained in:
1 parent
aae029d94e
commit
5cf4a7d213
30 files changed
+169
-77
No files matched your search
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$comment": "Comment lines that open with a release version, per file under src/. Maintained by src/__tests__/version-stamp-ratchet.test.ts.",
|
||||
"count": 2892,
|
||||
"total": 9220,
|
||||
"total": 9216,
|
||||
"entries": {
|
||||
"__tests__/account-payload-consumer-guard.test.ts": 2,
|
||||
"__tests__/acting-account-boundary-guard.test.ts": 7,
|
||||
@@ -1963,10 +1963,10 @@
|
||||
"lib/doctor-report-helpers.ts": 1,
|
||||
"lib/doctor-report-pdf-core.ts": 4,
|
||||
"lib/doctor-report-pdf/clinical-records-notes-section.ts": 6,
|
||||
"lib/doctor-report-pdf/header-profile-section.ts": 3,
|
||||
"lib/doctor-report-pdf/header-profile-section.ts": 2,
|
||||
"lib/doctor-report-pdf/measurements-charts-section.ts": 3,
|
||||
"lib/doctor-report-pdf/medication-mood-wellness-section.ts": 7,
|
||||
"lib/doctor-report-types.ts": 21,
|
||||
"lib/doctor-report-types.ts": 20,
|
||||
"lib/doctor-report/__tests__/leaf-gating-sweep.test.ts": 1,
|
||||
"lib/doctor-report/clinical-records.ts": 2,
|
||||
"lib/doctor-report/collect.ts": 4,
|
||||
|
||||
@@ -59,7 +59,7 @@ function emptyReport(): DoctorReportData {
|
||||
end: "2026-01-31T00:00:00.000Z",
|
||||
},
|
||||
patient: {
|
||||
username: null,
|
||||
name: null,
|
||||
dateOfBirth: null,
|
||||
gender: null,
|
||||
heightCm: null,
|
||||
@@ -156,11 +156,10 @@ const DISPOSITIONS: Record<StructuredLeafId, Disposition> = {
|
||||
kind: "rendered",
|
||||
data: {
|
||||
patient: {
|
||||
username: "shared-account",
|
||||
name: "A. Patient",
|
||||
dateOfBirth: "1979-04-02T00:00:00.000Z",
|
||||
gender: "FEMALE",
|
||||
heightCm: 171,
|
||||
fullName: "A. Patient",
|
||||
},
|
||||
},
|
||||
marker: "A. Patient",
|
||||
|
||||
@@ -97,7 +97,7 @@ function report(): DoctorReportData {
|
||||
end: "2026-01-31T00:00:00.000Z",
|
||||
},
|
||||
patient: {
|
||||
username: null,
|
||||
name: null,
|
||||
dateOfBirth: null,
|
||||
gender: null,
|
||||
heightCm: null,
|
||||
|
||||
@@ -42,11 +42,10 @@ const FULL_RECORD = {
|
||||
end: "2026-01-31T00:00:00.000Z",
|
||||
},
|
||||
patient: {
|
||||
username: "shared-account",
|
||||
name: "A. Patient",
|
||||
dateOfBirth: "1980-01-01T00:00:00.000Z",
|
||||
gender: "FEMALE",
|
||||
heightCm: 170,
|
||||
fullName: "A. Patient",
|
||||
},
|
||||
practiceName: null,
|
||||
measurements: {},
|
||||
|
||||
@@ -27,7 +27,7 @@ function makeReport(
|
||||
end: "2026-01-31T00:00:00.000Z",
|
||||
},
|
||||
patient: {
|
||||
username: "tester",
|
||||
name: "tester",
|
||||
dateOfBirth: null,
|
||||
gender: null,
|
||||
heightCm: null,
|
||||
|
||||
@@ -56,7 +56,7 @@ export function PatientIdentitySection({
|
||||
fmtDate: (iso: string) => string;
|
||||
}) {
|
||||
const patient = report.patient;
|
||||
const name = patient.fullName ?? patient.username ?? null;
|
||||
const name = patient.name;
|
||||
const genderKey = patient.gender
|
||||
? GENDER_LABEL_KEYS[patient.gender]
|
||||
: undefined;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { accountName } from "../account-name";
|
||||
import { accountName, nameInitials, patientName } from "../account-name";
|
||||
|
||||
describe("accountName", () => {
|
||||
it("names the account by its display name when one is set", () => {
|
||||
@@ -19,3 +19,41 @@ describe("accountName", () => {
|
||||
expect(accountName({ username: "jdoe" })).toBe("jdoe");
|
||||
});
|
||||
});
|
||||
|
||||
describe("patientName", () => {
|
||||
const own = {
|
||||
username: "jdoe",
|
||||
displayName: null,
|
||||
fullName: null,
|
||||
managedProfileAt: null,
|
||||
};
|
||||
const managed = {
|
||||
username: "managed-3f9a0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f",
|
||||
displayName: null,
|
||||
fullName: null,
|
||||
managedProfileAt: new Date("2026-01-01T00:00:00.000Z"),
|
||||
};
|
||||
|
||||
it("prefers the full name, then the display name, then the login", () => {
|
||||
expect(
|
||||
patientName({ ...own, fullName: "Jane Doe", displayName: "Jane" }),
|
||||
).toBe("Jane Doe");
|
||||
expect(patientName({ ...own, fullName: " ", displayName: "Jane" })).toBe(
|
||||
"Jane",
|
||||
);
|
||||
expect(patientName(own)).toBe("jdoe");
|
||||
});
|
||||
|
||||
it("never prints a managed record's generated login", () => {
|
||||
expect(patientName({ ...managed, displayName: "Robin" })).toBe("Robin");
|
||||
expect(patientName(managed)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("nameInitials", () => {
|
||||
it("takes the first letter of the first two words", () => {
|
||||
expect(nameInitials("Jane Doe")).toBe("JD");
|
||||
expect(nameInitials("max.mustermann")).toBe("MM");
|
||||
expect(nameInitials("Robin")).toBe("R");
|
||||
});
|
||||
});
|
||||
@@ -27,7 +27,7 @@ function makeData(overrides?: Partial<DoctorReportData>): DoctorReportData {
|
||||
end: "2026-05-03T12:00:00.000Z",
|
||||
},
|
||||
patient: {
|
||||
username: "testuser",
|
||||
name: "testuser",
|
||||
dateOfBirth: "1985-06-15T00:00:00.000Z",
|
||||
gender: "MALE",
|
||||
heightCm: 182,
|
||||
@@ -1003,12 +1003,11 @@ describe("doctor-report emergency first page", () => {
|
||||
});
|
||||
|
||||
describe("extracted doctor-report section boundaries", () => {
|
||||
it("header/profile prefers the legal name and renders insurance identity", async () => {
|
||||
it("header/profile prints the resolved name and renders insurance identity", async () => {
|
||||
const data = makeData({
|
||||
patient: {
|
||||
...makeData().patient,
|
||||
fullName: "Ada Lovelace",
|
||||
username: "ignored-handle",
|
||||
name: "Ada Lovelace",
|
||||
insurerName: "Example Health",
|
||||
},
|
||||
});
|
||||
@@ -1022,7 +1021,6 @@ describe("extracted doctor-report section boundaries", () => {
|
||||
});
|
||||
const text = await extractText(bytes);
|
||||
expect(text).toContain("Ada Lovelace");
|
||||
expect(text).not.toContain("ignored-handle");
|
||||
expect(text).toContain("Example Health");
|
||||
expect(text).toContain("A123456789");
|
||||
});
|
||||
|
||||
@@ -11,3 +11,31 @@ export function accountName(user: {
|
||||
const displayName = user.displayName?.trim();
|
||||
return displayName ? displayName : user.username;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name a clinical document gives a person: the full name when one is
|
||||
* set, the display name next, the login last. A managed record's login is a
|
||||
* handle generated when the record was created (`managed-<hex>`), not a name
|
||||
* anybody chose, so it never prints; with no other name the line is left out.
|
||||
*/
|
||||
export function patientName(user: {
|
||||
username: string;
|
||||
displayName: string | null;
|
||||
fullName: string | null;
|
||||
managedProfileAt: Date | null;
|
||||
}): string | null {
|
||||
const fullName = user.fullName?.trim();
|
||||
if (fullName) return fullName;
|
||||
const displayName = user.displayName?.trim();
|
||||
if (displayName) return displayName;
|
||||
return user.managedProfileAt ? null : user.username;
|
||||
}
|
||||
|
||||
/** Up to two letters for an avatar, from a name such as `accountName()`. */
|
||||
export function nameInitials(name: string): string {
|
||||
return name
|
||||
.split(/[\s._-]+/)
|
||||
.slice(0, 2)
|
||||
.map((s) => s[0]?.toUpperCase() ?? "")
|
||||
.join("");
|
||||
}
|
||||
@@ -164,7 +164,7 @@ export function buildDoctorReportPdfDocument(
|
||||
title: t("doctorReport.title"),
|
||||
subject: t("doctorReport.subtitle"),
|
||||
creator: "HealthLog",
|
||||
author: data.patient.fullName ?? data.patient.username ?? "HealthLog",
|
||||
author: data.patient.name ?? "HealthLog",
|
||||
});
|
||||
const pageWidth = doc.internal.pageSize.getWidth();
|
||||
const pageHeight = doc.internal.pageSize.getHeight();
|
||||
|
||||
@@ -97,7 +97,7 @@ function data(overrides: Partial<DoctorReportData> = {}): DoctorReportData {
|
||||
end: "2026-05-03T12:00:00.000Z",
|
||||
},
|
||||
patient: {
|
||||
username: null,
|
||||
name: null,
|
||||
dateOfBirth: null,
|
||||
gender: null,
|
||||
heightCm: null,
|
||||
|
||||
@@ -32,8 +32,7 @@ function renderPatientLines(gender: string | null): string[] {
|
||||
doc,
|
||||
data: {
|
||||
patient: {
|
||||
username: "testuser",
|
||||
fullName: null,
|
||||
name: "testuser",
|
||||
dateOfBirth: null,
|
||||
gender,
|
||||
heightCm: null,
|
||||
|
||||
@@ -62,12 +62,9 @@ export function buildHeaderProfileSection(
|
||||
doc.setFontSize(9);
|
||||
doc.setTextColor(80, 80, 80);
|
||||
const patientInfo: string[] = [];
|
||||
// v1.7.0 — prefer the legal full name on the cover; fall back to the
|
||||
// username when no full name is set (collapses exactly like the
|
||||
// practice name when neither is present).
|
||||
const patientName = data.patient.fullName ?? data.patient.username ?? null;
|
||||
if (patientName) {
|
||||
patientInfo.push(`${t("doctorReport.patient")}: ${patientName}`);
|
||||
// Collapses like the practice name when the record has no name to print.
|
||||
if (data.patient.name) {
|
||||
patientInfo.push(`${t("doctorReport.patient")}: ${data.patient.name}`);
|
||||
}
|
||||
if (data.patient.dateOfBirth) {
|
||||
patientInfo.push(
|
||||
|
||||
@@ -80,17 +80,19 @@ export interface DoctorReportData {
|
||||
*/
|
||||
period: { days: number; since: string; start: string; end: string };
|
||||
patient: {
|
||||
username: string | null;
|
||||
/**
|
||||
* The name the cover, the PDF metadata, the FHIR Patient and the share
|
||||
* page print, resolved once by `patientName()`: full name, display name,
|
||||
* login, never a managed record's generated handle. Null prints no line.
|
||||
*/
|
||||
name: string | null;
|
||||
dateOfBirth: string | null;
|
||||
gender: string | null;
|
||||
heightCm: number | null;
|
||||
// v1.7.0 — optional patient-identity fields for the export cover +
|
||||
// FHIR Patient. `fullName` + `insurerName` are plaintext columns; the
|
||||
// KVNR is NOT carried here (it's encrypted at rest and decrypted by
|
||||
// the route, then handed to the builders separately). Optional in the
|
||||
// type so pre-v1.7.0 fixtures that omit them still typecheck — the
|
||||
// renderer treats absent and null identically.
|
||||
fullName?: string | null;
|
||||
// `insurerName` is a plaintext column; the KVNR is NOT carried here (it's
|
||||
// encrypted at rest and decrypted by the route, then handed to the
|
||||
// builders separately). Optional so fixtures that omit it still
|
||||
// typecheck; the renderer treats absent and null identically.
|
||||
insurerName?: string | null;
|
||||
// v1.8.6 — German insurer institution number (IKNR), plaintext. Carried
|
||||
// here for the FHIR `Coverage` payor Organization identifier. Optional
|
||||
|
||||
@@ -130,6 +130,8 @@ function seed() {
|
||||
rows.userUnique = [
|
||||
{
|
||||
username: "owner",
|
||||
displayName: null,
|
||||
managedProfileAt: null,
|
||||
dateOfBirth: new Date("1980-05-05T00:00:00.000Z"),
|
||||
gender: "OTHER",
|
||||
heightCm: 180,
|
||||
@@ -328,7 +330,7 @@ describe("per-leaf gating sweep", () => {
|
||||
0,
|
||||
);
|
||||
expect(Object.keys(allPayload.compliance).length).toBeGreaterThan(0);
|
||||
expect(allPayload.patient.username).toBe("owner");
|
||||
expect(allPayload.patient.name).toBe("Owner Name");
|
||||
expect(allPayload.patient.insurerName).toBe("Sample Insurer");
|
||||
expect(allPayload.bmi).not.toBeNull();
|
||||
expect(Object.keys(allPayload.glucoseStats).length).toBeGreaterThan(0);
|
||||
@@ -457,6 +459,47 @@ describe("the derived figures ride their own leaf", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("the name the report prints", () => {
|
||||
const identity = selectionFromLeaves(["PATIENT_IDENTITY"]);
|
||||
const asUser = (fields: Record<string, unknown>) => {
|
||||
rows.userUnique = [{ ...(rows.userUnique[0] as object), ...fields }];
|
||||
};
|
||||
|
||||
it("names a managed record by its display name, never its generated login", async () => {
|
||||
asUser({
|
||||
username: "managed-3f9a0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f",
|
||||
displayName: "Robin",
|
||||
fullName: null,
|
||||
managedProfileAt: new Date("2026-01-01T00:00:00.000Z"),
|
||||
});
|
||||
const payload = await collectDoctorReportData("u1", RANGE, identity, {
|
||||
moduleMap: moduleMap(),
|
||||
});
|
||||
expect(payload.patient.name).toBe("Robin");
|
||||
});
|
||||
|
||||
it("prints no name for a managed record that has none", async () => {
|
||||
asUser({
|
||||
username: "managed-3f9a0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f",
|
||||
displayName: null,
|
||||
fullName: null,
|
||||
managedProfileAt: new Date("2026-01-01T00:00:00.000Z"),
|
||||
});
|
||||
const payload = await collectDoctorReportData("u1", RANGE, identity, {
|
||||
moduleMap: moduleMap(),
|
||||
});
|
||||
expect(payload.patient.name).toBeNull();
|
||||
});
|
||||
|
||||
it("prefers the display name over the login on an own account", async () => {
|
||||
asUser({ username: "owner", displayName: "Jane Doe", fullName: null });
|
||||
const payload = await collectDoctorReportData("u1", RANGE, identity, {
|
||||
moduleMap: moduleMap(),
|
||||
});
|
||||
expect(payload.patient.name).toBe("Jane Doe");
|
||||
});
|
||||
});
|
||||
|
||||
describe("fail-closed exclusion", () => {
|
||||
it("excludes a measurement type the catalogue does not know", () => {
|
||||
const gate = resolveReportGate(
|
||||
@@ -571,7 +614,7 @@ describe("the empty selection serves nothing", () => {
|
||||
selectionFromLeaves([]),
|
||||
{ moduleMap: moduleMap() },
|
||||
);
|
||||
expect(payload.patient.username).toBeNull();
|
||||
expect(payload.patient.name).toBeNull();
|
||||
expect(payload.patient.insurerName).toBeNull();
|
||||
expect(payload.labResults).toBeNull();
|
||||
expect(payload.allergies).toBeNull();
|
||||
|
||||
@@ -84,6 +84,7 @@ import {
|
||||
loadLabResults,
|
||||
} from "./clinical-records";
|
||||
import { dayKeyAsUtcMidnight } from "@/lib/tz/date-only";
|
||||
import { patientName } from "@/lib/account-name";
|
||||
import { readingsMean } from "@/lib/measurements/day-mean";
|
||||
import { loadScreenerUnvalidatedTranslations } from "./screener-translations";
|
||||
|
||||
@@ -179,6 +180,8 @@ export async function collectDoctorReportData(
|
||||
where: { id: userId },
|
||||
select: {
|
||||
username: true,
|
||||
displayName: true,
|
||||
managedProfileAt: true,
|
||||
dateOfBirth: true,
|
||||
gender: true,
|
||||
heightCm: true,
|
||||
@@ -720,14 +723,13 @@ export async function collectDoctorReportData(
|
||||
end: end.toISOString(),
|
||||
},
|
||||
patient: {
|
||||
username: identityOn ? (userProfile?.username ?? null) : null,
|
||||
name: identityOn && userProfile ? patientName(userProfile) : null,
|
||||
dateOfBirth:
|
||||
identityOn && userProfile?.dateOfBirth
|
||||
? userProfile.dateOfBirth.toISOString()
|
||||
: null,
|
||||
gender: identityOn ? (userProfile?.gender ?? null) : null,
|
||||
heightCm: identityOn ? (userProfile?.heightCm ?? null) : null,
|
||||
fullName: identityOn ? (userProfile?.fullName ?? null) : null,
|
||||
insurerName: insuranceOn ? (userProfile?.insurerName ?? null) : null,
|
||||
insurerIkNumber: insuranceOn
|
||||
? (userProfile?.insurerIkNumber ?? null)
|
||||
|
||||
@@ -69,11 +69,10 @@ function makeData(overrides?: Partial<DoctorReportData>): DoctorReportData {
|
||||
end: "2026-05-03T12:00:00.000Z",
|
||||
},
|
||||
patient: {
|
||||
username: "sample-user",
|
||||
name: "Sample Patient",
|
||||
dateOfBirth: "1985-06-15T00:00:00.000Z",
|
||||
gender: "MALE",
|
||||
heightCm: 182,
|
||||
fullName: "Sample Patient",
|
||||
insurerName: "Example Insurer",
|
||||
},
|
||||
practiceName: null,
|
||||
@@ -181,11 +180,10 @@ describe("buildFhirDocumentBundle", () => {
|
||||
const bundle = buildFhirDocumentBundle(
|
||||
makeData({
|
||||
patient: {
|
||||
username: "sample-user",
|
||||
name: "Sample Patient",
|
||||
dateOfBirth: "1985-06-15T00:00:00.000Z",
|
||||
gender: "MALE",
|
||||
heightCm: 182,
|
||||
fullName: "Sample Patient",
|
||||
insurerName: "Example Insurer",
|
||||
insurerIkNumber: "101234567",
|
||||
},
|
||||
@@ -215,11 +213,10 @@ describe("buildFhirDocumentBundle", () => {
|
||||
const bundle = buildFhirDocumentBundle(
|
||||
makeData({
|
||||
patient: {
|
||||
username: "sample-user",
|
||||
name: "Sample Patient",
|
||||
dateOfBirth: null,
|
||||
gender: null,
|
||||
heightCm: null,
|
||||
fullName: "Sample Patient",
|
||||
insurerName: "Example Insurer",
|
||||
insurerIkNumber: "101234567",
|
||||
},
|
||||
@@ -238,11 +235,10 @@ describe("buildFhirDocumentBundle", () => {
|
||||
const bundle = buildFhirDocumentBundle(
|
||||
makeData({
|
||||
patient: {
|
||||
username: "sample-user",
|
||||
name: "Sample Patient",
|
||||
dateOfBirth: null,
|
||||
gender: null,
|
||||
heightCm: null,
|
||||
fullName: "Sample Patient",
|
||||
insurerName: "Example Insurer",
|
||||
// no insurerIkNumber
|
||||
},
|
||||
@@ -262,11 +258,10 @@ describe("buildFhirDocumentBundle", () => {
|
||||
const bundle = buildFhirDocumentBundle(
|
||||
makeData({
|
||||
patient: {
|
||||
username: "sample-user",
|
||||
name: "Sample Patient",
|
||||
dateOfBirth: null,
|
||||
gender: null,
|
||||
heightCm: null,
|
||||
fullName: "Sample Patient",
|
||||
insurerName: "Example Insurer",
|
||||
insurerIkNumber: "101234567",
|
||||
},
|
||||
@@ -284,11 +279,10 @@ describe("buildFhirDocumentBundle", () => {
|
||||
const bundle = buildFhirDocumentBundle(
|
||||
makeData({
|
||||
patient: {
|
||||
username: "sample-user",
|
||||
name: "Sample Patient",
|
||||
dateOfBirth: null,
|
||||
gender: null,
|
||||
heightCm: null,
|
||||
fullName: "Sample Patient",
|
||||
// neither insurerName nor insurerIkNumber
|
||||
},
|
||||
}),
|
||||
@@ -303,11 +297,10 @@ describe("buildFhirDocumentBundle", () => {
|
||||
const bundle = buildFhirDocumentBundle(
|
||||
makeData({
|
||||
patient: {
|
||||
username: "sample-user",
|
||||
name: "Sample Patient",
|
||||
dateOfBirth: null,
|
||||
gender: null,
|
||||
heightCm: null,
|
||||
fullName: "Sample Patient",
|
||||
// neither insurerName nor insurerIkNumber — just a bare KVNR.
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -36,11 +36,10 @@ function makeData(overrides?: Partial<DoctorReportData>): DoctorReportData {
|
||||
end: "2026-05-03T12:00:00.000Z",
|
||||
},
|
||||
patient: {
|
||||
username: "sample-user",
|
||||
name: "Sample Patient",
|
||||
dateOfBirth: "1985-06-15T00:00:00.000Z",
|
||||
gender: "MALE",
|
||||
heightCm: 182,
|
||||
fullName: "Sample Patient",
|
||||
insurerName: "Example Insurer",
|
||||
insurerIkNumber: "101234567",
|
||||
},
|
||||
@@ -286,7 +285,7 @@ describe("document Bundle — identity and resolvability", () => {
|
||||
fullBundle(),
|
||||
fullBundle({
|
||||
patient: {
|
||||
username: "u",
|
||||
name: "u",
|
||||
dateOfBirth: null,
|
||||
gender: null,
|
||||
heightCm: null,
|
||||
@@ -375,7 +374,7 @@ describe("Patient.gender", () => {
|
||||
for (const [stored, expected] of cases) {
|
||||
const bundle = fullBundle({
|
||||
patient: {
|
||||
username: "u",
|
||||
name: "u",
|
||||
dateOfBirth: null,
|
||||
gender: stored,
|
||||
heightCm: null,
|
||||
|
||||
@@ -15,7 +15,7 @@ function baseData(overrides?: Partial<DoctorReportData>): DoctorReportData {
|
||||
end: "2026-05-03T12:00:00.000Z",
|
||||
},
|
||||
patient: {
|
||||
username: "sample-user",
|
||||
name: "sample-user",
|
||||
dateOfBirth: "1990-06-15T00:00:00.000Z",
|
||||
gender: "FEMALE",
|
||||
heightCm: 168,
|
||||
|
||||
@@ -22,7 +22,7 @@ function makeData(overrides?: Partial<DoctorReportData>): DoctorReportData {
|
||||
end: "2026-05-03T12:00:00.000Z",
|
||||
},
|
||||
patient: {
|
||||
username: "sample-user",
|
||||
name: "sample-user",
|
||||
dateOfBirth: "1985-06-15T00:00:00.000Z",
|
||||
gender: "MALE",
|
||||
heightCm: 182,
|
||||
|
||||
@@ -18,7 +18,7 @@ function makeData(overrides?: Partial<DoctorReportData>): DoctorReportData {
|
||||
end: "2026-05-03T12:00:00.000Z",
|
||||
},
|
||||
patient: {
|
||||
username: "sample-user",
|
||||
name: "sample-user",
|
||||
dateOfBirth: "1985-06-15T00:00:00.000Z",
|
||||
gender: "MALE",
|
||||
heightCm: 182,
|
||||
|
||||
@@ -17,7 +17,7 @@ function makeData(overrides?: Partial<DoctorReportData>): DoctorReportData {
|
||||
end: "2026-05-03T12:00:00.000Z",
|
||||
},
|
||||
patient: {
|
||||
username: "sample-user",
|
||||
name: "sample-user",
|
||||
dateOfBirth: "1985-06-15T00:00:00.000Z",
|
||||
gender: "MALE",
|
||||
heightCm: 182,
|
||||
|
||||
@@ -22,7 +22,7 @@ function codings(overrides: Partial<DoctorReportData>): FhirCoding[] {
|
||||
start: "2026-02-02T00:00:00.000Z",
|
||||
end: "2026-05-03T12:00:00.000Z",
|
||||
},
|
||||
patient: { username: "sample-user", dateOfBirth: null, gender: null },
|
||||
patient: { name: "sample-user", dateOfBirth: null, gender: null },
|
||||
practiceName: null,
|
||||
measurements: {},
|
||||
stats: {},
|
||||
@@ -118,7 +118,7 @@ describe("medication codings", () => {
|
||||
start: "2026-02-02T00:00:00.000Z",
|
||||
end: "2026-05-03T12:00:00.000Z",
|
||||
},
|
||||
patient: { username: "sample-user", dateOfBirth: null, gender: null },
|
||||
patient: { name: "sample-user", dateOfBirth: null, gender: null },
|
||||
practiceName: null,
|
||||
measurements: {},
|
||||
stats: {},
|
||||
|
||||
@@ -315,7 +315,7 @@ export function buildFhirDocumentBundle(
|
||||
// v1.9.0 — when the aggregator capped the administration set, disclose
|
||||
// it in the narrative so the export is honest: it carries the
|
||||
// most-recent N of M acted intakes, the oldest having been omitted.
|
||||
const displayName = data.patient.fullName ?? data.patient.username ?? null;
|
||||
const displayName = data.patient.name;
|
||||
const truncation = data.medicationAdministrationsTruncation;
|
||||
const narrativeText = [
|
||||
`Health record for ${escapeXml(displayName ?? "patient")}.`,
|
||||
|
||||
@@ -49,8 +49,7 @@ export function patientResource(
|
||||
resourceType: "Patient",
|
||||
id: PATIENT_RESOURCE_ID,
|
||||
};
|
||||
const displayName = data.patient.fullName ?? data.patient.username ?? null;
|
||||
if (displayName) patient.name = [{ text: displayName }];
|
||||
if (data.patient.name) patient.name = [{ text: data.patient.name }];
|
||||
const gender = administrativeGender(data.patient.gender);
|
||||
if (gender) patient.gender = gender;
|
||||
if (data.patient.dateOfBirth) {
|
||||
|
||||
@@ -74,7 +74,7 @@ const REPORT_FIXTURE = {
|
||||
start: "2026-03-29T00:00:00.000Z",
|
||||
end: "2026-06-27T00:00:00.000Z",
|
||||
},
|
||||
patient: { username: "tester" },
|
||||
patient: { name: "tester" },
|
||||
stats: { WEIGHT: { avg: 80, min: 79, max: 82, count: 30, latest: 80 } },
|
||||
compliance: {},
|
||||
medications: [],
|
||||
|
||||
@@ -29,7 +29,7 @@ function payload(overrides: Partial<DoctorReportData> = {}): DoctorReportData {
|
||||
end: "2026-04-01T00:00:00.000Z",
|
||||
},
|
||||
patient: {
|
||||
username: null,
|
||||
name: null,
|
||||
dateOfBirth: null,
|
||||
gender: null,
|
||||
heightCm: null,
|
||||
|
||||
@@ -73,8 +73,7 @@ function reportFixture(
|
||||
end: "2026-06-27T00:00:00.000Z",
|
||||
},
|
||||
patient: {
|
||||
username: "tester",
|
||||
fullName: "A Person",
|
||||
name: "A Person",
|
||||
dateOfBirth: "1990-01-01",
|
||||
gender: "female",
|
||||
heightCm: 170,
|
||||
@@ -213,8 +212,7 @@ describe("doctor_visit_summary — grounding + structure", () => {
|
||||
.text,
|
||||
);
|
||||
const patient = data.patient as Record<string, unknown>;
|
||||
expect(patient).not.toHaveProperty("fullName");
|
||||
expect(patient).not.toHaveProperty("username");
|
||||
expect(patient).not.toHaveProperty("name");
|
||||
expect(patient.dateOfBirth).toBe("1990-01-01");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -224,8 +224,7 @@ describe("healthlog://report/doctor-visit", () => {
|
||||
end: "2026-06-27T00:00:00.000Z",
|
||||
},
|
||||
patient: {
|
||||
username: "tester",
|
||||
fullName: "A Person",
|
||||
name: "A Person",
|
||||
dateOfBirth: "1990-01-01",
|
||||
gender: "female",
|
||||
heightCm: 170,
|
||||
@@ -267,8 +266,7 @@ describe("healthlog://report/doctor-visit", () => {
|
||||
expect(result).toHaveProperty("illnessEpisodes");
|
||||
// Patient name never leaks.
|
||||
const patient = result.patient as Record<string, unknown>;
|
||||
expect(patient).not.toHaveProperty("fullName");
|
||||
expect(patient).not.toHaveProperty("username");
|
||||
expect(patient).not.toHaveProperty("name");
|
||||
});
|
||||
|
||||
it("omits absent sections honestly (no zero-fill)", async () => {
|
||||
|
||||
@@ -314,7 +314,7 @@ describe("doctor-report — per-section toggles", () => {
|
||||
expect(data.labResults).toBeNull();
|
||||
expect(data.allergies).toBeNull();
|
||||
expect(data.familyHistory).toBeNull();
|
||||
expect(data.patient.username).toBeNull();
|
||||
expect(data.patient.name).toBeNull();
|
||||
expect(data.patient.insurerName).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user