fix(dn): a fragment of background paper no longer holds a page in grouping

Co-Authored-By: MiniMax Code <noreply@minimax.io>
This commit is contained in:
DEls 2026-10-09 13:30:11 +02:00
parent fbc1f4f5c6
commit eca2cb845c
2 changed files with 73 additions and 2 deletions

View file

@ -51,6 +51,53 @@ describe("assemblePages", () => {
});
});
describe("background fragments", () => {
const fragment = (over: Partial<PageRead> = {}) => page(1, {
multiple_documents: true,
items: [{ description: "Bend 90", qty: 7, unit: "ea", unit_price: null, line_amount: null, product_code: null }],
evidence: { multiple_documents: "A cropped corner of another paper is visible behind this invoice." },
...over,
});
it("groups a complete document that only has background paper behind it", () => {
const result = assemblePages([fragment()]);
expect(result.groups.map((group) => group.pages)).toEqual([[1]]);
expect(result.openPages).toEqual([]);
expect(result.requiresReview).toBe(false);
});
it("still holds a page showing two complete documents side by side", () => {
const result = assemblePages([fragment({
evidence: { multiple_documents: "Two delivery notes side by side, each with its own header." },
})]);
expect(result.groups).toEqual([]);
expect(result.openPages).toEqual([{ page: 1, reason: "multiple_documents" }]);
expect(result.requiresReview).toBe(true);
});
it("still holds a background fragment on a page with no line items", () => {
const result = assemblePages([fragment({ items: [] })]);
expect(result.openPages).toEqual([{ page: 1, reason: "multiple_documents" }]);
});
it("still holds a background fragment on a page with no document number", () => {
const result = assemblePages([fragment({ doc_number: "" })]);
expect(result.openPages).toEqual([{ page: 1, reason: "multiple_documents" }]);
});
it("still holds the page when the evidence names a second document number", () => {
const result = assemblePages([fragment({
evidence: { multiple_documents: "Corner of invoice 22433700 visible behind, a second document." },
})]);
expect(result.openPages).toEqual([{ page: 1, reason: "multiple_documents" }]);
});
it("groups a fragment with no evidence text at all on a complete page", () => {
const result = assemblePages([fragment({ evidence: {} })]);
expect(result.openPages).toEqual([{ page: 1, reason: "multiple_documents" }]);
});
});
it("preserves manual split/merge decisions instead of rejoining by printed numbers",()=>{
const reads=[page(1,{doc_number:"DN"}),page(2,{doc_number:"DN"}),page(3,{doc_number:"OTHER"})];

View file

@ -43,8 +43,10 @@ export function assemblePages(
const readsByPage = new Map(ordered.map((read) => [read.page, read]));
for (const read of ordered) {
if (read.multiple_documents) {
openPages.push({ page: read.page, reason: "multiple_documents" });
continue;
if (!backgroundFragment(read)) {
openPages.push({ page: read.page, reason: "multiple_documents" });
continue;
}
}
const previous = groups[groups.length - 1];
@ -113,6 +115,28 @@ export function assemblePages(
return { groups, openPages, requiresReview: openPages.length > 0 };
}
const BACKGROUND = /background|behind|fragment|cropped|partial|edge|corner|stack/i;
const DOCUMENT_NUMBER = /\d{4,}/g;
/**
* True when a page flagged `multiple_documents` is really one complete
* document with a scrap of other paper behind it.
*
* The extraction prompt already tells the reader to ignore fragments, and it
* still flags them often enough to hold whole invoices in "Check grouping"
* for the want of a corner. Every condition has to hold: a number of its own
* and at least one line, so there is a document to group, and evidence that
* describes the other paper as background without naming a second reference —
* a second reference means there is a second document to decide about.
*/
function backgroundFragment(read: PageRead): boolean {
if (!read.doc_number.trim() || read.items.length === 0) return false;
const evidence = read.evidence?.multiple_documents;
if (!evidence || !BACKGROUND.test(evidence)) return false;
const own = [read.doc_number, ...(read.doc_number_alts ?? [])].join("|");
const elsewhere = evidence.replace(own, " ").match(DOCUMENT_NUMBER);
return elsewhere === null;
}
function sessionFor(page: number, hints: AssemblyHints): string | undefined {
return hints.page_delivery_session_ids?.[page] ?? hints.delivery_session_ids?.[page] ??
(hints.delivery_session_id ?? hints.deliverySessionId);