OCR-Rohtext additiv zurückgeben, je Eintrag speichern und anzeigen
recognize() (pipeline.js) liefert zusätzlich den ungefilterten OCR-Rohtext (rawText) zurück - leer, wenn keine Texterkennung lief. Rein additiv: die Stapelzuordnung (session.js/spec.js, unangetastet) stützt sich weiterhin ausschließlich auf die daraus abgeleiteten, verstandenen Spec-Felder. main.js hängt den Rohtext nach dem Buchen als zusätzliche Eigenschaft an den Eintrag (analog zu den bereits vorhandenen Barcode-Inhalten je Eintrag). storage.js sichert/prüft/stellt entry.rawText nach demselben optionalen Muster wie entry.codes wieder her - ein gesicherter Stand ohne dieses Feld wird nicht verworfen. In Funktion "Text erkennen" bleibt die Treffer-Rückmeldung jetzt stehen, bis der Nutzer sie wegtippt, und zeigt den vollen erkannten Text (lesbar über dasselbe Scroll-Muster wie die übrigen Vollbild-Overlays); in den beiden Barcode-Funktionen bleibt es beim automatischen Ausblenden. Das zurückgegebene Versprechen von showResult löst in jedem Fall - Zeitgeber, Wegtippen, verdrängende neue Rückmeldung - genau einmal ein, damit die Bedienung nie dauerhaft gesperrt bleibt. Die Sitzungsliste zeigt den Rohtext zusätzlich je Eintrag zum Nachlesen. Tests (pipeline.test.js, storage.test.js): rawText nur bei tatsächlich gelaufener Texterkennung, unterschiedlicher Rohtext beeinflusst die Stapelzuordnung nachweislich nicht, Rohtext übersteht Sichern/Laden inkl. Abwärtskompatibilität zu einem Stand ohne dieses Feld.
This commit is contained in:
@@ -2,6 +2,7 @@ import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { recognize } from '../src/pipeline.js';
|
||||
import { emptySpec } from '../src/spec.js';
|
||||
import { createSession, proposeAssignment, commitAssignment } from '../src/session.js';
|
||||
|
||||
const OCR_TEXT = '64GB 4DRx4 PC4-2400T-LD1-11-MC0 M386A8K40BM1-CRC4Y 1908';
|
||||
|
||||
@@ -281,3 +282,93 @@ test('ohne isKnownCode-Abhaengigkeit bleibt die Pipeline ohne sie lauffaehig (Te
|
||||
assert.equal(ocrCalled, true);
|
||||
assert.equal(result.confidence, 'green');
|
||||
});
|
||||
|
||||
// --- Erweiterung: Rohtext als additives Feld (Funktion "Text erkennen") ---
|
||||
//
|
||||
// Der Rohtext ist reine Anzeige-Information fuer den Nutzer - er verlaesst
|
||||
// die Erkennung, wird aber nirgends zur Stapelzuordnung herangezogen (siehe
|
||||
// den letzten Test unten, der recognize() mit session.js zusammenspielen
|
||||
// laesst).
|
||||
|
||||
test('recognize liefert den Rohtext, wenn die Texterkennung gelaufen ist (gelbes Ergebnis)', async () => {
|
||||
const result = await recognize({}, deps({ codes: [], text: OCR_TEXT }));
|
||||
assert.equal(result.source, 'ocr');
|
||||
assert.equal(result.rawText, OCR_TEXT);
|
||||
});
|
||||
|
||||
test('recognize liefert den Rohtext auch, wenn die Texterkennung lief, aber nichts Verwertbares fand (rot)', async () => {
|
||||
const result = await recognize({}, deps({ codes: [], text: 'Made in Philippines' }));
|
||||
assert.equal(result.confidence, 'red');
|
||||
assert.equal(result.rawText, 'Made in Philippines');
|
||||
});
|
||||
|
||||
test('recognize liefert den Rohtext, wenn OCR keine Kapazitaet findet, aber eine unverwertbare Barcode-Teilenummer bestaetigt (gruen)', async () => {
|
||||
// '7325773' hat kein bekanntes Nummernschema (decodePartNumber liefert
|
||||
// keine Kapazitaet) - die Texterkennung laeuft trotzdem und liefert Text
|
||||
// ohne jede Kapazitaetsangabe. Die Teilenummer aus dem Barcode traegt
|
||||
// (siehe recognize()), das Ergebnis bleibt gruen - der gelaufene Rohtext
|
||||
// wird trotzdem zurueckgegeben.
|
||||
const result = await recognize({}, {
|
||||
decodeBarcodes: async () => ['7325773'],
|
||||
runOcr: async () => 'Made in Philippines',
|
||||
});
|
||||
assert.equal(result.source, 'barcode');
|
||||
assert.equal(result.confidence, 'green');
|
||||
assert.equal(result.spec.partNumber, '7325773');
|
||||
assert.equal(result.rawText, 'Made in Philippines');
|
||||
});
|
||||
|
||||
test('recognize liefert eine leere Zeichenkette als Rohtext, wenn die Texterkennung nicht lief (gruener Barcode-Treffer)', async () => {
|
||||
const result = await recognize({}, {
|
||||
decodeBarcodes: async () => ['M386A8K40BM1-CRC4Y'],
|
||||
runOcr: async () => { throw new Error('darf nicht aufgerufen werden'); },
|
||||
});
|
||||
assert.equal(result.source, 'barcode');
|
||||
assert.equal(result.rawText, '');
|
||||
});
|
||||
|
||||
test('recognize liefert eine leere Zeichenkette als Rohtext, wenn ein bekannter Code die Texterkennung ueberfluessig macht', async () => {
|
||||
const result = await recognize({}, {
|
||||
decodeBarcodes: async () => ['HMA84GL7AFR4N-UH'],
|
||||
runOcr: async () => { throw new Error('darf nicht aufgerufen werden'); },
|
||||
isKnownCode: (code) => code === 'HMA84GL7AFR4N-UH',
|
||||
});
|
||||
assert.equal(result.rawText, '');
|
||||
});
|
||||
|
||||
test('recognize liefert eine leere Zeichenkette als Rohtext bei mehrdeutigen verwertbaren Barcodes (rot, ohne OCR)', async () => {
|
||||
const result = await recognize({}, {
|
||||
decodeBarcodes: async () => ['M386A8K40BM1-CRC4Y', 'M386A8K40BM1-CWE4Y'],
|
||||
runOcr: async () => { throw new Error('darf nicht aufgerufen werden'); },
|
||||
});
|
||||
assert.equal(result.confidence, 'red');
|
||||
assert.equal(result.rawText, '');
|
||||
});
|
||||
|
||||
test('der Rohtext beeinflusst die Stapelzuordnung nicht: zwei Aufnahmen mit unterschiedlichem Rohtext, aber gleichen verstandenen Feldern, landen auf demselben Stapel', async () => {
|
||||
const firstText = '64GB 4DRx4 PC4-2400T-LD1-11-MC0 M386A8K40BM1-CRC4Y 1908';
|
||||
// Anderer Rohtext (andere Reihenfolge, zusaetzliches "Made in Korea",
|
||||
// andere Gross-/Kleinschreibung) - dieselben verstandenen Felder.
|
||||
const secondText = 'made in korea m386a8k40bm1-crc4y 64gb 4drx4 PC4-2400 1908';
|
||||
|
||||
const first = await recognize({}, deps({ codes: [], text: firstText }));
|
||||
const second = await recognize({}, deps({ codes: [], text: secondText }));
|
||||
|
||||
assert.notEqual(first.rawText, second.rawText, 'Vorbedingung: die Rohtexte unterscheiden sich tatsaechlich');
|
||||
assert.deepEqual(first.spec, second.spec, 'Vorbedingung: dieselben verstandenen Felder');
|
||||
|
||||
const session = createSession();
|
||||
const plan1 = proposeAssignment(session, first.spec, first.codes);
|
||||
const entry1 = commitAssignment(session, first.spec, first.source, plan1.stackId, first.codes);
|
||||
// Der Rohtext haengt (wie main.js es tut) rein additiv am Eintrag, ohne
|
||||
// dass session.js davon weiss - siehe main.js/processCapture().
|
||||
entry1.rawText = first.rawText;
|
||||
|
||||
const plan2 = proposeAssignment(session, second.spec, second.codes);
|
||||
assert.equal(plan2.kind, 'match', 'das zweite Modul muss denselben Stapel treffen');
|
||||
const entry2 = commitAssignment(session, second.spec, second.source, plan2.stackId, second.codes);
|
||||
entry2.rawText = second.rawText;
|
||||
|
||||
assert.equal(entry1.stackId, entry2.stackId, 'unterschiedlicher Rohtext darf keinen eigenen Stapel erzeugen');
|
||||
assert.notEqual(entry1.rawText, entry2.rawText, 'die Eintraege fuehren trotzdem je ihren eigenen Rohtext');
|
||||
});
|
||||
|
||||
@@ -308,3 +308,39 @@ test('Eintrag mit gebrochener entryId liefert null', () => {
|
||||
}));
|
||||
assert.equal(loadSession(store), null);
|
||||
});
|
||||
|
||||
// --- Erweiterung: der OCR-Rohtext eines Eintrags (entry.rawText) ---
|
||||
|
||||
test('Rohtext eines Eintrags ueberlebt Sichern und Laden', () => {
|
||||
const store = fakeStore();
|
||||
const session = createSession();
|
||||
const entry = commitAssignment(session, { ...emptySpec(), capacityGb: 32 }, 'ocr', 'A');
|
||||
entry.rawText = '32GB 2Rx4 PC4-2666 Vollständiges Etikett Made in Korea';
|
||||
saveSession(session, store);
|
||||
|
||||
const reloaded = loadSession(store);
|
||||
assert.notEqual(reloaded, null);
|
||||
assert.equal(reloaded.entries[0].rawText, '32GB 2Rx4 PC4-2666 Vollständiges Etikett Made in Korea');
|
||||
});
|
||||
|
||||
test('ein Stand ohne rawText-Feld (aeltere Fassung) wird nicht verworfen, der Eintrag gilt als ohne Rohtext', () => {
|
||||
const store = fakeStore();
|
||||
store.setItem('ram-sortierhilfe:session', JSON.stringify({
|
||||
stacks: [{ id: 'A', count: 1, spec: { ...emptySpec(), capacityGb: 64 } }],
|
||||
entries: [{ entryId: 1, stackId: 'A', spec: { ...emptySpec(), capacityGb: 64 }, source: 'barcode' }],
|
||||
nextEntryId: 2,
|
||||
}));
|
||||
const reloaded = loadSession(store);
|
||||
assert.notEqual(reloaded, null, 'ein Stand ohne rawText-Feld darf nicht verworfen werden');
|
||||
assert.equal(reloaded.entries[0].rawText, '');
|
||||
});
|
||||
|
||||
test('Eintrag mit rawText als Nicht-Zeichenkette liefert null', () => {
|
||||
const store = fakeStore();
|
||||
store.setItem('ram-sortierhilfe:session', JSON.stringify({
|
||||
stacks: [{ id: 'A', count: 1, spec: emptySpec() }],
|
||||
entries: [{ entryId: 1, stackId: 'A', spec: emptySpec(), source: 'barcode', rawText: 123 }],
|
||||
nextEntryId: 2,
|
||||
}));
|
||||
assert.equal(loadSession(store), null);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user