diff --git a/src/pipeline.js b/src/pipeline.js index 10e5951..bb79f6a 100644 --- a/src/pipeline.js +++ b/src/pipeline.js @@ -55,7 +55,15 @@ function allCompatible(specs) { * 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'}>} + * deps.isKnownCode ist optional: eine Funktion (code: string) => boolean, + * mit der der Aufrufer prueft, ob ein roher Barcode-Inhalt bereits einem + * Stapel bekannt ist. Ist einer der gelesenen Codes bekannt, steht die + * Zuordnung fest und die Texterkennung wird uebersprungen. Fehlt die + * Funktion, verhaelt sich die Pipeline wie ohne diese Optimierung. + * @returns {Promise<{spec: object, source: 'barcode'|'ocr'|'none', confidence: 'green'|'yellow'|'red', codes: string[]}>} + * codes sind die im Bild gelesenen rohen Barcode-Zeichenketten (koennen + * neben der Teilenummer z.B. auch eine Seriennummer enthalten) - zur + * Weitergabe an proposeAssignment/commitAssignment in session.js. */ export async function recognize(frame, deps) { const barcodeTimeoutMs = deps.barcodeTimeoutMs ?? DEFAULT_BARCODE_TIMEOUT_MS; @@ -91,7 +99,7 @@ export async function recognize(frame, deps) { } if (usable.length === 1) { - return { spec: usable[0], source: 'barcode', confidence: 'green' }; + return { spec: usable[0], source: 'barcode', confidence: 'green', codes }; } if (usable.length > 1) { if (allCompatible(usable)) { @@ -101,10 +109,19 @@ export async function recognize(frame, deps) { // da alle uebrigen Felder aus derselben Teilenummer abgeleitet werden, // sind vertraegliche Treffer ohnehin gleich. Es gibt nichts aufzufuellen, // der erste Treffer genuegt. - return { spec: usable[0], source: 'barcode', confidence: 'green' }; + return { spec: usable[0], source: 'barcode', confidence: 'green', codes }; } // Mehrdeutigkeit zwischen verwertbaren Barcodes: nicht raten, Nutzer entscheidet. - return { spec: emptySpec(), source: 'none', confidence: 'red' }; + return { spec: emptySpec(), source: 'none', confidence: 'red', codes }; + } + + // Ist einer der gelesenen Codes bereits einem Stapel bekannt, steht die + // Zuordnung fest (siehe proposeAssignment in session.js) - die bis zu + // zwanzig Sekunden dauernde Texterkennung waere reine Zeitverschwendung. + // Bei einem tatsaechlich neuen Modul (kein Code bekannt) laeuft sie wie + // bisher, denn dort liefert sie die lesbare Beschriftung des neuen Stapels. + if (typeof deps.isKnownCode === 'function' && codes.some((code) => deps.isKnownCode(code))) { + return { spec: best, source: 'barcode', confidence: 'green', codes }; } const ocrResult = await safely(() => deps.runOcr(frame), '', ocrTimeoutMs); @@ -119,7 +136,15 @@ export async function recognize(frame, deps) { } if (isUsable(merged)) { - return { spec: merged, source: 'ocr', confidence: 'yellow' }; + return { spec: merged, source: 'ocr', confidence: 'yellow', codes }; } - return { spec: merged, source: 'none', confidence: 'red' }; + // Zum Sortieren muss keine Kapazitaet bekannt sein - eine exakt gelesene + // Teilenummer genuegt, um ein Modul wiederzuerkennen. Sie ueberlebt bis + // hierher nur, wenn genau ein unverwertbarer Code sie lieferte oder + // mehrere sich einig waren (siehe 'best' oben) - bei widersprechenden + // Codes bleibt merged.partNumber null und die Vorsicht damit erhalten. + if (merged.partNumber !== null) { + return { spec: merged, source: 'barcode', confidence: 'green', codes }; + } + return { spec: merged, source: 'none', confidence: 'red', codes }; } diff --git a/test/pipeline.test.js b/test/pipeline.test.js index 699e29a..d72d4f2 100644 --- a/test/pipeline.test.js +++ b/test/pipeline.test.js @@ -191,3 +191,93 @@ test('zwei unverwertbare Barcodes, die sich nur in einer Verwechslung unterschei assert.equal(result.confidence, 'yellow'); assert.equal(result.spec.partNumber, '732O773'); }); + +// --- Erweiterung: unbekanntes Nummernschema mit gelesener Teilenummer genuegt fuer Gruen --- + +test('Barcode mit unbekanntem Nummernschema und gelesener Teilenummer ergibt gruen, auch ohne Kapazitaet', async () => { + const result = await recognize({}, { + // Hynix-Teilenummer: Schema unbekannt (nur Samsung ist hinterlegt), aber + // exakt gelesen - und OCR findet auf dem Etikett ebenfalls nichts + // Verwertbares. Trotzdem genuegt die gelesene Teilenummer fuer Gruen. + decodeBarcodes: async () => ['HMA84GL7AFR4N-UH'], + runOcr: async () => '', + }); + assert.equal(result.source, 'barcode'); + assert.equal(result.confidence, 'green'); + assert.equal(result.spec.partNumber, 'HMA84GL7AFR4N-UH'); + assert.equal(result.spec.capacityGb, null); +}); + +test('findet OCR trotz unbekanntem Nummernschema eine Kapazitaet, wird sie uebernommen (weiterhin gelb, nicht gruen)', async () => { + const result = await recognize({}, { + decodeBarcodes: async () => ['HMA84GL7AFR4N-UH'], + runOcr: async () => OCR_TEXT, + }); + assert.equal(result.source, 'ocr'); + assert.equal(result.confidence, 'yellow'); + assert.equal(result.spec.capacityGb, 64); + // Die Teilenummer aus dem Barcode ist exakter und bleibt erhalten. + assert.equal(result.spec.partNumber, 'HMA84GL7AFR4N-UH'); +}); + +test('widersprechende unverwertbare Barcodes ohne jede OCR-Kapazitaet: die Vorsicht bleibt erhalten, kein Gruen', () => recognize({}, { + decodeBarcodes: async () => ['7325773', '9999999'], + runOcr: async () => '', +}).then((result) => { + assert.equal(result.source, 'none'); + assert.equal(result.confidence, 'red'); +})); + +test('recognize liefert die gelesenen Rohcodes zur Weitergabe an die Stapelzuordnung', async () => { + const result = await recognize({}, { + decodeBarcodes: async () => ['HMA84GL7AFR4N-UH', 'SN-000123'], + runOcr: async () => '', + }); + assert.deepEqual([...result.codes].sort(), ['HMA84GL7AFR4N-UH', 'SN-000123'].sort()); +}); + +// --- Erweiterung: ein bekannter Code ueberspringt die Texterkennung --- + +test('bekannter Code ueberspringt die Texterkennung vollstaendig', async () => { + let ocrCalled = false; + const result = await recognize({}, { + decodeBarcodes: async () => ['HMA84GL7AFR4N-UH'], + runOcr: async () => { ocrCalled = true; return ''; }, + isKnownCode: (code) => code === 'HMA84GL7AFR4N-UH', + }); + assert.equal(ocrCalled, false, 'ein bekannter Code macht die Texterkennung ueberfluessig'); + assert.equal(result.source, 'barcode'); + assert.equal(result.confidence, 'green'); +}); + +test('bekannter Code unter mehreren Codes ueberspringt die Texterkennung trotzdem (Seriennummer laeuft ins Leere)', async () => { + let ocrCalled = false; + const result = await recognize({}, { + decodeBarcodes: async () => ['HMA84GL7AFR4N-UH', 'SN-000123'], + runOcr: async () => { ocrCalled = true; return ''; }, + isKnownCode: (code) => code === 'HMA84GL7AFR4N-UH', + }); + assert.equal(ocrCalled, false); + assert.equal(result.confidence, 'green'); +}); + +test('unbekannter Code (isKnownCode liefert stets false) ueberspringt die Texterkennung nicht', async () => { + let ocrCalled = false; + const result = await recognize({}, { + decodeBarcodes: async () => ['HMA84GL7AFR4N-UH'], + runOcr: async () => { ocrCalled = true; return ''; }, + isKnownCode: () => false, + }); + assert.equal(ocrCalled, true); + assert.equal(result.confidence, 'green'); +}); + +test('ohne isKnownCode-Abhaengigkeit bleibt die Pipeline ohne sie lauffaehig (Texterkennung laeuft wie bisher)', async () => { + let ocrCalled = false; + const result = await recognize({}, { + decodeBarcodes: async () => ['HMA84GL7AFR4N-UH'], + runOcr: async () => { ocrCalled = true; return ''; }, + }); + assert.equal(ocrCalled, true); + assert.equal(result.confidence, 'green'); +});