Files
ocr_scanner/test/pipeline.test.js
T
vchuser 37df87a324 Behandle Mehrdeutigkeit auch bei unverwertbaren Barcodes
Mehrere Barcodes ohne bekanntes Nummernschema, die sich in der
Teilenummer widersprechen, durften bisher nicht mehr Vorrang vor der
per OCR gelesenen Teilenummer erhalten als ein einzelner Treffer -
der erste gewann ungeprueft. Jetzt gilt derselbe Massstab wie bei
verwertbaren Treffern: Vorrang nur bei Eindeutigkeit (ein Code, oder
mehrere mit derselben - via canonical() verwechslungstolerant
verglichenen - Teilenummer). Widersprechen sie sich, stuetzt sich das
Ergebnis allein auf die Texterkennung.

Zusaetzlich: ungueltige @param-Zeile fuer deps in recognize() korrigiert.
2026-07-28 15:49:39 +02:00

194 lines
7.6 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { recognize } from '../src/pipeline.js';
import { emptySpec } from '../src/spec.js';
const OCR_TEXT = '64GB 4DRx4 PC4-2400T-LD1-11-MC0 M386A8K40BM1-CRC4Y 1908';
const deps = ({ codes = [], text = '' }) => ({
decodeBarcodes: async () => codes,
runOcr: async () => text,
});
test('Barcode mit bekannter Teilenummer ergibt gruen und ueberspringt OCR', async () => {
let ocrAufgerufen = false;
const result = await recognize({}, {
decodeBarcodes: async () => ['M386A8K40BM1-CRC4Y'],
runOcr: async () => { ocrAufgerufen = true; return ''; },
});
assert.equal(result.source, 'barcode');
assert.equal(result.confidence, 'green');
assert.equal(result.spec.capacityGb, 64);
assert.equal(ocrAufgerufen, false, 'OCR darf bei gruenem Barcode nicht laufen');
});
test('Barcode ohne bekannte Teilenummer faellt auf OCR zurueck', async () => {
const result = await recognize({}, deps({ codes: ['7325773'], text: OCR_TEXT }));
assert.equal(result.source, 'ocr');
assert.equal(result.confidence, 'yellow');
assert.equal(result.spec.capacityGb, 64);
});
test('OCR-Ergebnis behaelt die Teilenummer aus dem Barcode bei', async () => {
const result = await recognize({}, deps({ codes: ['7325773'], text: '64GB 4DRx4 PC4-2400' }));
assert.equal(result.spec.partNumber, '7325773');
});
test('ohne Barcode und ohne brauchbaren Text ergibt rot', async () => {
const result = await recognize({}, deps({ codes: [], text: 'Made in Philippines' }));
assert.equal(result.source, 'none');
assert.equal(result.confidence, 'red');
});
test('OCR-Fehler fuehrt zu rot statt zu einem Absturz', async () => {
const result = await recognize({}, {
decodeBarcodes: async () => [],
runOcr: async () => { throw new Error('tesseract nicht geladen'); },
});
assert.equal(result.confidence, 'red');
assert.equal(result.source, 'none');
});
test('Barcode-Fehler fuehrt nicht zum Abbruch, OCR uebernimmt', async () => {
const result = await recognize({}, {
decodeBarcodes: async () => { throw new Error('zxing nicht geladen'); },
runOcr: async () => OCR_TEXT,
});
assert.equal(result.source, 'ocr');
assert.equal(result.spec.capacityGb, 64);
});
// --- Beanstandung 1: unerwartete Rueckgabeform statt eines Fehlers ---
test('decodeBarcodes liefert null statt einer Liste, kein Absturz', async () => {
const result = await recognize({}, {
decodeBarcodes: async () => null,
runOcr: async () => OCR_TEXT,
});
assert.equal(result.source, 'ocr');
assert.equal(result.confidence, 'yellow');
assert.equal(result.spec.capacityGb, 64);
});
test('decodeBarcodes liefert undefined statt einer Liste, kein Absturz', async () => {
const result = await recognize({}, {
decodeBarcodes: async () => undefined,
runOcr: async () => OCR_TEXT,
});
assert.equal(result.source, 'ocr');
assert.equal(result.confidence, 'yellow');
assert.equal(result.spec.capacityGb, 64);
});
test('decodeBarcodes liefert ein Objekt statt einer Liste, kein Absturz', async () => {
const result = await recognize({}, {
decodeBarcodes: async () => ({ notAList: true }),
runOcr: async () => OCR_TEXT,
});
assert.equal(result.source, 'ocr');
assert.equal(result.confidence, 'yellow');
assert.equal(result.spec.capacityGb, 64);
});
test('runOcr liefert keine Zeichenkette, wird wie leerer Text behandelt', async () => {
const result = await recognize({}, {
decodeBarcodes: async () => [],
runOcr: async () => ({ unerwartet: true }),
});
assert.equal(result.source, 'none');
assert.equal(result.confidence, 'red');
});
// --- Beanstandung 2: mehrere verwertbare Barcodes im selben Bild ---
test('mehrere unvertraegliche Barcodes ergeben rot statt eines ungeprueften ersten Treffers', async () => {
let ocrAufgerufen = false;
const result = await recognize({}, {
// Gleiche Bauform und Dichte, aber widersprechende Geschwindigkeit:
// zwei unterschiedliche Module im selben Bild.
decodeBarcodes: async () => ['M386A8K40BM1-CRC4Y', 'M386A8K40BM1-CWE4Y'],
runOcr: async () => { ocrAufgerufen = true; return ''; },
});
assert.equal(result.source, 'none');
assert.equal(result.confidence, 'red');
assert.deepEqual(result.spec, emptySpec());
assert.equal(ocrAufgerufen, false, 'bei mehrdeutigem Barcode-Ergebnis soll nicht geraten werden');
});
test('mehrere untereinander vertraegliche Barcodes ergeben gruen (der erste Treffer entscheidet, es gibt nichts zusammenzufuehren)', async () => {
let ocrAufgerufen = false;
const result = await recognize({}, {
// Zwei Lesungen desselben Etiketts (z.B. Mehrfachtreffer durch
// Spiegelung) - inhaltlich identisch, nur die Schreibweise weicht
// an einer verwechslungstoleranten Stelle ab (B/8).
decodeBarcodes: async () => ['M386A8K40BM1-CRC4Y', 'M386A8K408M1-CRC4Y'],
runOcr: async () => { ocrAufgerufen = true; return ''; },
});
assert.equal(result.source, 'barcode');
assert.equal(result.confidence, 'green');
assert.equal(result.spec.capacityGb, 64);
assert.equal(result.spec.formFactor, 'LRDIMM');
assert.equal(result.spec.rank, '4DRx4');
assert.equal(result.spec.speed, 'PC4-2400');
assert.equal(ocrAufgerufen, false, 'OCR darf bei gruenem Mehrfachtreffer nicht laufen');
});
// --- Beanstandung 1: haengender Adapter darf die Erkennung nicht dauerhaft blockieren ---
test('haengender Barcode-Adapter blockiert nicht dauerhaft, OCR uebernimmt', { timeout: 500 }, async () => {
const result = await recognize({}, {
decodeBarcodes: () => new Promise(() => {}), // loest nie ein und wirft nie
runOcr: async () => OCR_TEXT,
barcodeTimeoutMs: 20,
});
assert.equal(result.source, 'ocr');
assert.equal(result.confidence, 'yellow');
assert.equal(result.spec.capacityGb, 64);
});
test('haengender OCR-Adapter blockiert nicht dauerhaft, Erkennung ergibt rot', { timeout: 500 }, async () => {
const result = await recognize({}, {
decodeBarcodes: async () => [],
runOcr: () => new Promise(() => {}), // loest nie ein und wirft nie
ocrTimeoutMs: 20,
});
assert.equal(result.source, 'none');
assert.equal(result.confidence, 'red');
});
// --- Beanstandung 1 (3. Runde): Mehrdeutigkeit bei unverwertbaren Barcodes ---
test('zwei widersprechende unverwertbare Barcodes: keiner erhaelt Vorrang, gelesene Teilenummer bleibt', async () => {
const result = await recognize({}, {
// Beide ohne bekanntes Nummernschema, und sie widersprechen sich -
// keiner darf die aus dem Klartext gelesene Teilenummer ueberschreiben.
decodeBarcodes: async () => ['7325773', '9999999'],
runOcr: async () => OCR_TEXT,
});
assert.equal(result.source, 'ocr');
assert.equal(result.confidence, 'yellow');
assert.equal(result.spec.partNumber, 'M386A8K40BM1-CRC4Y');
assert.equal(result.spec.capacityGb, 64);
});
test('zwei unverwertbare Barcodes mit gleicher Teilenummer: Vorrang bleibt bestehen', async () => {
const result = await recognize({}, {
decodeBarcodes: async () => ['7325773', '7325773'],
runOcr: async () => OCR_TEXT,
});
assert.equal(result.source, 'ocr');
assert.equal(result.confidence, 'yellow');
assert.equal(result.spec.partNumber, '7325773');
});
test('zwei unverwertbare Barcodes, die sich nur in einer Verwechslung unterscheiden, gelten als derselbe', async () => {
const result = await recognize({}, {
// 'O' und '0' sind eine typische Verwechslung (siehe CONFUSIONS in spec.js).
decodeBarcodes: async () => ['732O773', '7320773'],
runOcr: async () => OCR_TEXT,
});
assert.equal(result.source, 'ocr');
assert.equal(result.confidence, 'yellow');
assert.equal(result.spec.partNumber, '732O773');
});