From 661bd07a265c11ddd065f5e5d7671911e4dd8523 Mon Sep 17 00:00:00 2001 From: vchuser Date: Wed, 29 Jul 2026 09:05:05 +0200 Subject: [PATCH] Barcode-Teilenummer allein genuegt fuer Gruen, bekannter Code ueberspringt OCR Der Teilenummer-Decoder kennt bislang nur das Samsung-Schema; ein Hynix-Modul mit einwandfrei gelesenem, aber unbekanntem Barcode-Schema endete deshalb in Rot. Zum Sortieren muss keine Kapazitaet bekannt sein - eine exakt gelesene Teilenummer genuegt, um ein Modul wiederzuerkennen. recognize() greift jetzt erst dann auf Rot zurueck, wenn auch die Texterkennung keine Kapazitaet und keine eindeutige Teilenummer liefert; die bestehende Vorsicht bei widerspruechlichen unverwertbaren Barcodes bleibt dabei unangetastet. Zusaetzlich kann recognize() jetzt ueber deps.isKnownCode (optional) erfahren, dass ein gelesener Code bereits einem Stapel bekannt ist, und die bis zu 20 Sekunden dauernde Texterkennung dann ueberspringen. Ohne diese Abhaengigkeit verhaelt sich die Pipeline unveraendert. recognize() liefert zusaetzlich die rohen gelesenen Barcode-Inhalte (codes), damit session.js sie einem Stapel zuordnen kann. --- src/pipeline.js | 37 +++++++++++++++--- test/pipeline.test.js | 90 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 6 deletions(-) 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'); +});