diff --git a/src/pipeline.js b/src/pipeline.js index 0b83709..10e5951 100644 --- a/src/pipeline.js +++ b/src/pipeline.js @@ -1,4 +1,4 @@ -import { emptySpec, isUsable, specsCompatible } from './spec.js'; +import { canonical, emptySpec, isUsable, specsCompatible } from './spec.js'; import { decodePartNumber } from './pn-decoder.js'; import { extractFields } from './ocr-extract.js'; @@ -51,10 +51,10 @@ function allCompatible(specs) { /** * Barcode zuerst, OCR nur als Rueckfallebene. - * @param {object} deps.decodeBarcodes und deps.runOcr sind erforderlich. - * deps.barcodeTimeoutMs und deps.ocrTimeoutMs sind optional und ueberschreiben - * die eingebauten Obergrenzen - fuer Tests, die einen haengenden Adapter - * simulieren, ohne tatsaechlich zu warten. + * @param {object} deps Adapter- und Zeitgrenzen-Bundel: deps.decodeBarcodes und + * deps.runOcr sind erforderlich. deps.barcodeTimeoutMs und deps.ocrTimeoutMs + * sind optional und ueberschreiben die eingebauten Obergrenzen - fuer Tests, + * die einen haengenden Adapter simulieren, ohne tatsaechlich zu warten. * @returns {Promise<{spec: object, source: 'barcode'|'ocr'|'none', confidence: 'green'|'yellow'|'red'}>} */ export async function recognize(frame, deps) { @@ -64,16 +64,30 @@ export async function recognize(frame, deps) { const decodedCodes = await safely(() => deps.decodeBarcodes(frame), [], barcodeTimeoutMs); const codes = Array.isArray(decodedCodes) ? decodedCodes : []; - let best = emptySpec(); const usable = []; + const unusable = []; for (const code of codes) { const decoded = decodePartNumber(code); if (isUsable(decoded)) { usable.push(decoded); - continue; + } else { + unusable.push(decoded); } - // Teilenummer merken, auch wenn das Schema unbekannt ist. - if (best.partNumber === null) best = decoded; + } + + // Auch unter den nicht verwertbaren Barcodes (unbekanntes Nummernschema) + // gilt: Vorrang vor der gelesenen Teilenummer nur, wenn er eindeutig ist. + // Genau ein Code - oder mehrere mit uebereinstimmender Teilenummer - behaelt + // seinen Vorrang samt abgeleiteter Felder. Widersprechen sich mehrere + // untereinander, erhaelt keiner Vorrang; es wird nichts aus ihnen uebernommen. + let best = emptySpec(); + const withPartNumber = unusable.filter((decoded) => decoded.partNumber !== null); + if (withPartNumber.length > 0) { + const first = withPartNumber[0]; + const allSameCode = withPartNumber.every( + (decoded) => canonical(decoded.partNumber) === canonical(first.partNumber) + ); + if (allSameCode) best = first; } if (usable.length === 1) { diff --git a/test/pipeline.test.js b/test/pipeline.test.js index f412d7f..a5506a4 100644 --- a/test/pipeline.test.js +++ b/test/pipeline.test.js @@ -155,3 +155,39 @@ test('haengender OCR-Adapter blockiert nicht dauerhaft, Erkennung ergibt rot', { assert.equal(result.source, 'none'); assert.equal(result.confidence, 'red'); }); + +// --- Beanstandung 1 (3. Runde): Mehrdeutigkeit bei unverwertbaren Barcodes --- + +test('zwei widersprechende unverwertbare Barcodes: keiner erhaelt Vorrang, gelesene Teilenummer bleibt', async () => { + const result = await recognize({}, { + // Beide ohne bekanntes Nummernschema, und sie widersprechen sich - + // keiner darf die aus dem Klartext gelesene Teilenummer ueberschreiben. + decodeBarcodes: async () => ['7325773', '9999999'], + runOcr: async () => OCR_TEXT, + }); + assert.equal(result.source, 'ocr'); + assert.equal(result.confidence, 'yellow'); + assert.equal(result.spec.partNumber, 'M386A8K40BM1-CRC4Y'); + assert.equal(result.spec.capacityGb, 64); +}); + +test('zwei unverwertbare Barcodes mit gleicher Teilenummer: Vorrang bleibt bestehen', async () => { + const result = await recognize({}, { + decodeBarcodes: async () => ['7325773', '7325773'], + runOcr: async () => OCR_TEXT, + }); + assert.equal(result.source, 'ocr'); + assert.equal(result.confidence, 'yellow'); + assert.equal(result.spec.partNumber, '7325773'); +}); + +test('zwei unverwertbare Barcodes, die sich nur in einer Verwechslung unterscheiden, gelten als derselbe', async () => { + const result = await recognize({}, { + // 'O' und '0' sind eine typische Verwechslung (siehe CONFUSIONS in spec.js). + decodeBarcodes: async () => ['732O773', '7320773'], + runOcr: async () => OCR_TEXT, + }); + assert.equal(result.source, 'ocr'); + assert.equal(result.confidence, 'yellow'); + assert.equal(result.spec.partNumber, '732O773'); +});