Add adapter timeouts and drop unreachable spec-merge code
- Barcode- und OCR-Adapter erhalten je eine eigene, ueberschreibbare Zeitobergrenze (deps.barcodeTimeoutMs / deps.ocrTimeoutMs). Ein Adapter, der nie einloest, blockiert recognize() damit nicht mehr dauerhaft und faellt stattdessen auf den Ersatzwert zurueck, genau wie bei einem geworfenen Fehler. Kein offener Timer bleibt zurueck. - mergeCompatibleSpecs entfernt: vertraegliche verwertbare Barcode-Treffer sind wegen der Kopplung von partNumber und den uebrigen Feldern ohnehin identisch, der erste Treffer genuegt. Test entsprechend umbenannt, um das tatsaechlich gepruefte Verhalten zu benennen statt eine nicht mehr vorhandene Auffuell-Semantik zu behaupten. - Kommentar in pn-tables.js von einer inhaltlichen Wiederholung befreit.
This commit is contained in:
+44
-22
@@ -2,31 +2,40 @@ import { emptySpec, isUsable, specsCompatible } from './spec.js';
|
||||
import { decodePartNumber } from './pn-decoder.js';
|
||||
import { extractFields } from './ocr-extract.js';
|
||||
|
||||
async function safely(fn, fallback) {
|
||||
// Barcode-Dekodierung ist schnell; ein Adapter, der laenger braucht, gilt
|
||||
// als gescheitert. Grosszuegig genug, dass ein normaler Ablauf sie nie reisst.
|
||||
const DEFAULT_BARCODE_TIMEOUT_MS = 1500;
|
||||
// Texterkennung kann auf einem Handy durchaus einige Sekunden dauern - die
|
||||
// Obergrenze muss so grosszuegig sein, dass ein normaler Ablauf auf einem
|
||||
// langsamen Handy sie nie erreicht.
|
||||
const DEFAULT_OCR_TIMEOUT_MS = 20000;
|
||||
|
||||
const TIMED_OUT = Symbol('timed-out');
|
||||
|
||||
/**
|
||||
* Wettlauf zwischen dem uebergebenen Versprechen und einem Zeitgeber. Ist der
|
||||
* Zeitgeber schneller, wird TIMED_OUT geliefert, ohne dass der urspruengliche
|
||||
* Aufruf abgebrochen wird - er darf spaeter noch einloesen, wir warten nur
|
||||
* nicht mehr darauf. Der Zeitgeber wird in jedem Fall wieder aufgeraeumt,
|
||||
* damit kein offener Timer den Node-Prozess am Beenden hindert.
|
||||
*/
|
||||
function withTimeout(promise, ms) {
|
||||
let timer;
|
||||
const timeout = new Promise((resolve) => {
|
||||
timer = setTimeout(() => resolve(TIMED_OUT), ms);
|
||||
});
|
||||
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
||||
}
|
||||
|
||||
async function safely(fn, fallback, timeoutMs) {
|
||||
try {
|
||||
return await fn();
|
||||
const result = await withTimeout(fn(), timeoutMs);
|
||||
return result === TIMED_OUT ? fallback : result;
|
||||
} catch {
|
||||
return 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++) {
|
||||
@@ -39,10 +48,17 @@ 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.
|
||||
* @returns {Promise<{spec: object, source: 'barcode'|'ocr'|'none', confidence: 'green'|'yellow'|'red'}>}
|
||||
*/
|
||||
export async function recognize(frame, deps) {
|
||||
const decodedCodes = await safely(() => deps.decodeBarcodes(frame), []);
|
||||
const barcodeTimeoutMs = deps.barcodeTimeoutMs ?? DEFAULT_BARCODE_TIMEOUT_MS;
|
||||
const ocrTimeoutMs = deps.ocrTimeoutMs ?? DEFAULT_OCR_TIMEOUT_MS;
|
||||
|
||||
const decodedCodes = await safely(() => deps.decodeBarcodes(frame), [], barcodeTimeoutMs);
|
||||
const codes = Array.isArray(decodedCodes) ? decodedCodes : [];
|
||||
|
||||
let best = emptySpec();
|
||||
@@ -62,13 +78,19 @@ export async function recognize(frame, deps) {
|
||||
}
|
||||
if (usable.length > 1) {
|
||||
if (allCompatible(usable)) {
|
||||
return { spec: mergeCompatibleSpecs(usable), source: 'barcode', confidence: 'green' };
|
||||
// Vertraeglichkeit unter verwertbaren Barcode-Treffern setzt eine
|
||||
// uebereinstimmende Teilenummer voraus (decodePartNumber setzt sie fuer
|
||||
// jede nicht-leere Eingabe, specsCompatible vergleicht sie mit) - und
|
||||
// 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' };
|
||||
}
|
||||
// Mehrdeutigkeit zwischen verwertbaren Barcodes: nicht raten, Nutzer entscheidet.
|
||||
return { spec: emptySpec(), source: 'none', confidence: 'red' };
|
||||
}
|
||||
|
||||
const ocrResult = await safely(() => deps.runOcr(frame), '');
|
||||
const ocrResult = await safely(() => deps.runOcr(frame), '', ocrTimeoutMs);
|
||||
const text = typeof ocrResult === 'string' ? ocrResult : '';
|
||||
const fromOcr = extractFields(text);
|
||||
|
||||
|
||||
+1
-2
@@ -6,8 +6,7 @@
|
||||
// Systematik und muessen vor produktivem Einsatz gegen reale Module bestaetigt
|
||||
// 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.
|
||||
// widersprechen koennte.
|
||||
|
||||
export const VENDOR_TABLES = [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user