Fix pipeline: robust gegen unerwartete Adapter-Rueckgaben und mehrdeutige Barcodes

recognize() stuerzte ab, wenn decodeBarcodes() etwas anderes als eine Liste
zurueckgab (null/undefined/Objekt), da die for...of-Schleife nicht iterierbare
Werte nicht abfaengt. Nicht-Listen werden nun wie eine leere Liste behandelt,
ebenso wird eine nicht-string-wertige runOcr()-Rueckgabe wie leerer Text
behandelt.

Ausserdem gewann bei mehreren verwertbaren Barcodes im selben Bild bisher
ungeprueft der erste Treffer mit gruener Ampel. Jetzt werden alle verwertbaren
Codes gesammelt: genau einer bleibt gruen wie bisher, mehrere untereinander
vertraegliche werden zusammengefuehrt (spaeterer Treffer ueberschreibt kein
bereits gesetztes Feld) und bleiben gruen, mehrere unvertraegliche ergeben rot
mit leerem Spec statt eines geratenen Ergebnisses.

Korrigiert ausserdem den irrefuehrenden Warnkommentar in pn-tables.js: ein
falscher Tabelleneintrag faellt auf dem Barcode-Weg nicht automatisch auf, da
dort keine Texterkennung zum Abgleich laeuft.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
vchuser
2026-07-28 15:29:54 +02:00
co-authored by Claude Opus 5
parent 2d81f7b005
commit 4099687707
3 changed files with 126 additions and 6 deletions
+46 -4
View File
@@ -1,4 +1,4 @@
import { emptySpec, isUsable } from './spec.js';
import { emptySpec, isUsable, specsCompatible } from './spec.js';
import { decodePartNumber } from './pn-decoder.js';
import { extractFields } from './ocr-extract.js';
@@ -10,24 +10,66 @@ async function safely(fn, fallback) {
}
}
/**
* Fuehrt mehrere untereinander vertraegliche Specs zusammen: ein spaeterer
* Treffer fuellt nur Felder, die noch offen sind, ueberschreibt aber nie
* ein bereits gesetztes Feld.
*/
function mergeCompatibleSpecs(specs) {
const merged = { ...specs[0] };
for (let i = 1; i < specs.length; i++) {
for (const field of Object.keys(merged)) {
if (merged[field] === null && specs[i][field] !== null) {
merged[field] = specs[i][field];
}
}
}
return merged;
}
/** Prueft, ob alle Specs paarweise vertraeglich sind. */
function allCompatible(specs) {
for (let i = 0; i < specs.length; i++) {
for (let j = i + 1; j < specs.length; j++) {
if (!specsCompatible(specs[i], specs[j])) return false;
}
}
return true;
}
/**
* Barcode zuerst, OCR nur als Rueckfallebene.
* @returns {Promise<{spec: object, source: 'barcode'|'ocr'|'none', confidence: 'green'|'yellow'|'red'}>}
*/
export async function recognize(frame, deps) {
const codes = await safely(() => deps.decodeBarcodes(frame), []);
const decodedCodes = await safely(() => deps.decodeBarcodes(frame), []);
const codes = Array.isArray(decodedCodes) ? decodedCodes : [];
let best = emptySpec();
const usable = [];
for (const code of codes) {
const decoded = decodePartNumber(code);
if (isUsable(decoded)) {
return { spec: decoded, source: 'barcode', confidence: 'green' };
usable.push(decoded);
continue;
}
// Teilenummer merken, auch wenn das Schema unbekannt ist.
if (best.partNumber === null) best = decoded;
}
const text = await safely(() => deps.runOcr(frame), '');
if (usable.length === 1) {
return { spec: usable[0], source: 'barcode', confidence: 'green' };
}
if (usable.length > 1) {
if (allCompatible(usable)) {
return { spec: mergeCompatibleSpecs(usable), source: 'barcode', confidence: 'green' };
}
// Mehrdeutigkeit zwischen verwertbaren Barcodes: nicht raten, Nutzer entscheidet.
return { spec: emptySpec(), source: 'none', confidence: 'red' };
}
const ocrResult = await safely(() => deps.runOcr(frame), '');
const text = typeof ocrResult === 'string' ? ocrResult : '';
const fromOcr = extractFields(text);
// Der Barcode ist die exaktere Quelle: seine Teilenummer gewinnt.
+4 -2
View File
@@ -4,8 +4,10 @@
// sind gegen ein reales Modul geprueft (64GB 4DRx4 PC4-2400T LRDIMM,
// M386A8K40BM1-CRC4Y). Alle uebrigen Eintraege stammen aus der veroeffentlichten
// Systematik und muessen vor produktivem Einsatz gegen reale Module bestaetigt
// werden - siehe Task 14. Ein falscher Eintrag faellt beim Sortieren dadurch
// auf, dass der OCR-Klartext dem dekodierten Wert widerspricht.
// werden - siehe Task 14. Ein falscher Eintrag faellt auf dem Barcode-Weg
// nicht automatisch auf, weil dort keine Texterkennung laeuft, gegen die er
// widersprechen koennte - die Eintraege muessen deshalb gegen reale Module
// geprueft werden.
export const VENDOR_TABLES = [
{