From b828870e1b2d8d9e0631bfaa57414860f4da2f34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 21 Oct 2026 23:42:34 +0200 Subject: [PATCH] fix(import): cut lab CSV duplicate days in the zone the file is read in The rows were parsed in the query's timezone while the duplicate keys used the account's, so two draws on separate days of the file's zone could collapse into one, or one day split in two. --- src/lib/import/labs-csv.ts | 20 +++++++++------ tests/integration/record-csv-import.test.ts | 27 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/lib/import/labs-csv.ts b/src/lib/import/labs-csv.ts index 86e4b202c..3c874dbfe 100644 --- a/src/lib/import/labs-csv.ts +++ b/src/lib/import/labs-csv.ts @@ -275,6 +275,10 @@ export async function importLabsCsv(args: { }): Promise { const { userId, tz, query } = args; const dryRun = query.dryRun; + // One zone for everything: the file's times are read in it, and the + // calendar day a reading is a duplicate on is cut in it too. Cutting the + // days in the account's zone while reading the file in another split one + // draw day in two, or folded two into one. const timezone = query.timezone ?? tz; const report = new RecordImportReport("labs", dryRun); const index: MarkerIndex = await loadMarkerIndex(prisma, userId); @@ -365,7 +369,9 @@ export async function importLabsCsv(args: { } // 2. Duplicates against the record, one query for the chunk. - const days = planned.map((p) => labReadingDay(p.row.takenAt, tz)).sort(); + const days = planned + .map((p) => labReadingDay(p.row.takenAt, timezone)) + .sort(); const existingKeys = new Set(); if (planned.length > 0) { const from = labReadingDaySearchRange(days[0]).gte; @@ -396,10 +402,10 @@ export async function importLabsCsv(args: { }, }); for (const e of existing) { - for (const k of readingKeys(e, tz)) existingKeys.add(k); + for (const k of readingKeys(e, timezone)) existingKeys.add(k); // A reading filed under a marker also answers to its printed name. if (e.biomarkerId) { - for (const k of readingKeys({ ...e, biomarkerId: null }, tz)) + for (const k of readingKeys({ ...e, biomarkerId: null }, timezone)) existingKeys.add(k); } } @@ -418,10 +424,10 @@ export async function importLabsCsv(args: { sourceUnit: row.unit, }; const keys = [ - ...readingKeys(probe, tz), - ...readingKeys({ ...probe, biomarkerId: null }, tz), + ...readingKeys(probe, timezone), + ...readingKeys({ ...probe, biomarkerId: null }, timezone), ...(marker - ? readingKeys({ ...probe, biomarkerId: marker.id }, tz) + ? readingKeys({ ...probe, biomarkerId: marker.id }, timezone) : []), ]; if (keys.some((k) => existingKeys.has(k) || seen.has(k))) { @@ -506,7 +512,7 @@ export async function importLabsCsv(args: { }); for (const k of [ ...keys, - ...readingKeys({ ...probe, biomarkerId: biomarker.id }, tz), + ...readingKeys({ ...probe, biomarkerId: biomarker.id }, timezone), ]) seen.add(k); report.valid += 1; diff --git a/tests/integration/record-csv-import.test.ts b/tests/integration/record-csv-import.test.ts index 665fc6da6..7164cab39 100644 --- a/tests/integration/record-csv-import.test.ts +++ b/tests/integration/record-csv-import.test.ts @@ -176,6 +176,33 @@ describe("lab CSV import", () => { }); }); +describe("lab CSV import in a zone of the file's own", () => { + it("cuts the duplicate day in the zone the file is read in", async () => { + // Read in New York, these are draws on two days (1 and 2 March). In the + // account's Berlin they both fall on 2 March, which used to make the + // second a duplicate of the first. + const csv = [ + "Analyt;Wert;Einheit;Datum", + "Zone marker;7;mg/dL;2025-03-01T22:30", + "Zone marker;7;mg/dL;2025-03-02T02:00", + ].join("\n"); + const result = await importLabsCsv({ + userId: USER, + tz: TZ, + stream: textToStream(csv), + maxBytes: 1_000_000, + query: labsCsvQuerySchema.parse({ + analyte: "0", + value: "1", + unit: "2", + date: "3", + timezone: "America/New_York", + }), + }); + expect(result).toMatchObject({ inserted: 2, duplicates: 0 }); + }); +}); + describe("medication CSV import", () => { const MEDS = [ "name,dose,times,as needed,start,end",