fix(documents): key an undated fact on its report's date in the merge

The extraction merged facts across reads on a key whose only date was
the fact's own effective date. Facts without one collapsed across reads
of different reports, so the same value on two days staged once. An
undated fact is now keyed on its read's report date, falling back to
the document's, as the lab scan keys a row.

Refs #1257
This commit is contained in:
Marc-André Bombeck committed 2026-10-25 02:03:27 +01:00
1 parent c9481b672c
commit da4056ce05
2 files changed
+30 -4

No files matched your search

@@ -103,6 +103,24 @@ describe("runInboundExtraction over several reads", () => {
expect(result.incomplete).toBe(false);
});
it("keeps a value two reads list for different report dates, and merges it on the same date", async () => {
const page = (reportDate: string | null) => ({
content: JSON.stringify({ reportDate, facts: [observation("Hb", 14)] }),
});
// Two reports of different days, one value each with no date of its own.
const twoDays = await run(
scripted([page("2026-09-01"), page("2026-09-30")]),
reads(2),
);
expect(twoDays.facts).toHaveLength(2);
// A page without a date reads under the report's, as the lab scan does.
const sameDay = await run(
scripted([page("2026-09-01"), page(null)]),
reads(2),
);
expect(sameDay.facts).toHaveLength(1);
});
it("keeps the lab scan's row cap, and says when a report holds more", async () => {
expect(INBOUND_MAX_FACTS).toBe(OCR_MAX_ROWS);
const reply = (r: number) => ({
+12 -4
View File
@@ -293,8 +293,13 @@ function factPage(raw: ExtractedFactRaw, chunk: OcrChunk): number | null {
: null;
}
/** The identity of a fact for the merge: the same fact in two reads is one. */
function factKey(raw: ExtractedFactRaw): string {
/**
* The identity of a fact for the merge: the same fact in two reads is one.
* A fact without a date of its own is keyed on the date of the report its
* read belongs to, as the lab scan keys a row, so the same value on two
* reports of different days stays two facts.
*/
function factKey(raw: ExtractedFactRaw, readDate: string | null): string {
return [
raw.type,
raw.label.trim().toLowerCase(),
@@ -303,7 +308,7 @@ function factKey(raw: ExtractedFactRaw): string {
(raw.unit ?? "").trim().toLowerCase(),
(raw.referenceText ?? "").trim().toLowerCase(),
(raw.dose ?? "").trim().toLowerCase(),
normaliseDate(raw.effectiveDate) ?? "",
normaliseDate(raw.effectiveDate) ?? readDate ?? "",
].join("|");
}
@@ -345,9 +350,12 @@ export async function runInboundExtraction(
let droppedFacts = 0;
for (const { envelope, chunk } of reads.reads) {
droppedFacts += envelope.droppedFacts;
// A read that prints its own date keeps it; the others read under the
// report's.
const readDate = normaliseDate(envelope.reportDate) ?? reportDate;
const readKeys: string[] = [];
for (const raw of envelope.facts) {
const key = factKey(raw);
const key = factKey(raw, readDate);
if (seen.has(key)) {
mergedDuplicates += 1;
continue;