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.
This commit is contained in:
+23
-9
@@ -1,4 +1,4 @@
|
|||||||
import { emptySpec, isUsable, specsCompatible } from './spec.js';
|
import { canonical, emptySpec, isUsable, specsCompatible } from './spec.js';
|
||||||
import { decodePartNumber } from './pn-decoder.js';
|
import { decodePartNumber } from './pn-decoder.js';
|
||||||
import { extractFields } from './ocr-extract.js';
|
import { extractFields } from './ocr-extract.js';
|
||||||
|
|
||||||
@@ -51,10 +51,10 @@ function allCompatible(specs) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Barcode zuerst, OCR nur als Rueckfallebene.
|
* Barcode zuerst, OCR nur als Rueckfallebene.
|
||||||
* @param {object} deps.decodeBarcodes und deps.runOcr sind erforderlich.
|
* @param {object} deps Adapter- und Zeitgrenzen-Bundel: deps.decodeBarcodes und
|
||||||
* deps.barcodeTimeoutMs und deps.ocrTimeoutMs sind optional und ueberschreiben
|
* deps.runOcr sind erforderlich. deps.barcodeTimeoutMs und deps.ocrTimeoutMs
|
||||||
* die eingebauten Obergrenzen - fuer Tests, die einen haengenden Adapter
|
* sind optional und ueberschreiben die eingebauten Obergrenzen - fuer Tests,
|
||||||
* simulieren, ohne tatsaechlich zu warten.
|
* die einen haengenden Adapter simulieren, ohne tatsaechlich zu warten.
|
||||||
* @returns {Promise<{spec: object, source: 'barcode'|'ocr'|'none', confidence: 'green'|'yellow'|'red'}>}
|
* @returns {Promise<{spec: object, source: 'barcode'|'ocr'|'none', confidence: 'green'|'yellow'|'red'}>}
|
||||||
*/
|
*/
|
||||||
export async function recognize(frame, deps) {
|
export async function recognize(frame, deps) {
|
||||||
@@ -64,16 +64,30 @@ export async function recognize(frame, deps) {
|
|||||||
const decodedCodes = await safely(() => deps.decodeBarcodes(frame), [], barcodeTimeoutMs);
|
const decodedCodes = await safely(() => deps.decodeBarcodes(frame), [], barcodeTimeoutMs);
|
||||||
const codes = Array.isArray(decodedCodes) ? decodedCodes : [];
|
const codes = Array.isArray(decodedCodes) ? decodedCodes : [];
|
||||||
|
|
||||||
let best = emptySpec();
|
|
||||||
const usable = [];
|
const usable = [];
|
||||||
|
const unusable = [];
|
||||||
for (const code of codes) {
|
for (const code of codes) {
|
||||||
const decoded = decodePartNumber(code);
|
const decoded = decodePartNumber(code);
|
||||||
if (isUsable(decoded)) {
|
if (isUsable(decoded)) {
|
||||||
usable.push(decoded);
|
usable.push(decoded);
|
||||||
continue;
|
} else {
|
||||||
|
unusable.push(decoded);
|
||||||
}
|
}
|
||||||
// Teilenummer merken, auch wenn das Schema unbekannt ist.
|
}
|
||||||
if (best.partNumber === null) best = decoded;
|
|
||||||
|
// Auch unter den nicht verwertbaren Barcodes (unbekanntes Nummernschema)
|
||||||
|
// gilt: Vorrang vor der gelesenen Teilenummer nur, wenn er eindeutig ist.
|
||||||
|
// Genau ein Code - oder mehrere mit uebereinstimmender Teilenummer - behaelt
|
||||||
|
// seinen Vorrang samt abgeleiteter Felder. Widersprechen sich mehrere
|
||||||
|
// untereinander, erhaelt keiner Vorrang; es wird nichts aus ihnen uebernommen.
|
||||||
|
let best = emptySpec();
|
||||||
|
const withPartNumber = unusable.filter((decoded) => decoded.partNumber !== null);
|
||||||
|
if (withPartNumber.length > 0) {
|
||||||
|
const first = withPartNumber[0];
|
||||||
|
const allSameCode = withPartNumber.every(
|
||||||
|
(decoded) => canonical(decoded.partNumber) === canonical(first.partNumber)
|
||||||
|
);
|
||||||
|
if (allSameCode) best = first;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usable.length === 1) {
|
if (usable.length === 1) {
|
||||||
|
|||||||
@@ -155,3 +155,39 @@ test('haengender OCR-Adapter blockiert nicht dauerhaft, Erkennung ergibt rot', {
|
|||||||
assert.equal(result.source, 'none');
|
assert.equal(result.source, 'none');
|
||||||
assert.equal(result.confidence, 'red');
|
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');
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user