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.
This commit is contained in:
vchuser
2026-07-29 09:05:05 +02:00
parent 70f6a4fbce
commit 661bd07a26
2 changed files with 121 additions and 6 deletions
+31 -6
View File
@@ -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 };
}
+90
View File
@@ -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');
});