Funktion 2 liest 2D-Codes statt nur QR (Auftraggeber-Entscheidung revidiert)

Der Auftraggeber hatte Funktion 2 ausdruecklich auf QR beschraenkt, obwohl
die 2D-Codes auf seinen RAM-Etiketten DataMatrix sind. Am Geraet zeigte sich,
dass die Funktion auf seiner Ware nichts fand - er hat die Entscheidung
revidiert. Funktion 2 liest jetzt zusaetzlich zu QRCode/MicroQRCode/RMQRCode
auch DataMatrix, Aztec und PDF417 und heisst in der Oberflaeche "2D-Code"
statt "QR-Code". Die Kennung bleibt aus historischen Gruenden `qrcode`.

Veralteten Kommentar in scan-modes.js berichtigt, Test auf die neue
Codeliste/Beschriftung umgestellt (statt geloescht) und einen Test ergaenzt,
der paarweise verschiedene Kennungen/Beschriftungen der drei Funktionen
erzwingt. README-Funktionstabelle und Geraete-Checkliste entsprechend
angepasst.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-29 14:32:41 +02:00
co-authored by Claude Opus 5
parent ebff173443
commit 2d7778cea8
4 changed files with 55 additions and 39 deletions
+29 -26
View File
@@ -5,7 +5,7 @@ beim physischen Sortieren am Tisch anleitet: Modul vor die Kamera halten, die
App nennt den Stapel.
Vor dem Scannen wählt der Nutzer eine von drei Funktionen — Strichcode,
QR-Code oder Text erkennen (siehe „Funktionsweise" unten). Jede Funktion nutzt
2D-Code oder Text erkennen (siehe „Funktionsweise" unten). Jede Funktion nutzt
ausschließlich ihre eigene Quelle; anders als früher weicht die App nicht mehr
automatisch auf Texterkennung aus, wenn kein Code gefunden wird.
@@ -65,18 +65,21 @@ keine Daten verlassen das Gerät.
| Funktion | Liest | Laufende Suche | Texterkennung |
|---|---|---|---|
| **Strichcode** | Code128, Code39, Code93, ITF, EAN-13, EAN-8, UPC-A, UPC-E, Codabar | ja | nein |
| **QR-Code** | ausschließlich QRCode, MicroQRCode, RMQRCode | ja | nein |
| **2D-Code** | QRCode, MicroQRCode, RMQRCode, DataMatrix, Aztec, PDF417 | ja | nein |
| **Text erkennen** | keine Codes | nein | ja, auf Knopfdruck |
QR-Code" liest bewusst **kein** DataMatrix — auch wenn die 2D-Codes auf
eigenen RAM-Etiketten DataMatrix sind. Der reguläre Arbeitsablauf läuft über
„Strichcode", weil dort die Teilenummer im Strichcode steht; „QR-Code" ist
für andere Ware gedacht. Diese Beschränkung ist eine ausdrückliche
Entscheidung, keine Lücke.
2D-Code" liest neben den QR-Varianten auch DataMatrix, Aztec und PDF417.
Ursprünglich war die Funktion (damals „QR-Code" beschriftet) auf QR
beschränkt; am Gerät zeigte sich, dass sie damit auf der eigenen Ware nichts
fand, weil die 2D-Codes auf den RAM-Etiketten DataMatrix sind. Die
Beschriftung wurde daraufhin auf „2D-Code" geändert, damit sie nicht mehr
verspricht, was sie nicht meint. Die interne Kennung heißt weiterhin
`qrcode` (aus historischen Gründen, siehe `src/scan-modes.js`) — daran
ändert die Erweiterung nichts.
„Modul scannen" erscheint nur in „Text erkennen" — in „Strichcode" und
QR-Code" sucht die App ohnehin laufend, ein Knopf ohne Texterkennung hätte
dort nichts zu tun. Der Zielrahmen hebt sich in „Strichcode" und „QR-Code"
2D-Code" sucht die App ohnehin laufend, ein Knopf ohne Texterkennung hätte
dort nichts zu tun. Der Zielrahmen hebt sich in „Strichcode" und „2D-Code"
hervor, sobald ein passender Code im Bild ist; in „Text erkennen" bleibt er
schlicht, weil dort nichts laufend erkannt wird. Ein Wechsel der Funktion
während des Sortierens (z. B. bei gemischter Ware) gilt sofort: laufende
@@ -86,7 +89,7 @@ keine Daten verlassen das Gerät.
nach dem Fortsetzen (siehe Punkt 5 unten) und erst danach die Funktionswahl
— die Kamera startet in jedem Fall erst, nachdem beides geklärt ist.
Technisch bekommen „Strichcode" und „QR-Code" eine Texterkennung
Technisch bekommen „Strichcode" und „2D-Code" eine Texterkennung
hineingereicht, die sofort leeren Text liefert, ohne Tesseract anzustoßen;
„Text erkennen" bekommt eine Barcode-Dekodierung hineingereicht, die sofort
eine leere Liste liefert (`buildRecognitionAdapters` in
@@ -94,7 +97,7 @@ keine Daten verlassen das Gerät.
Punkte 14 unten) bleibt dabei unverändert und kennt keine Funktionen — die
drei Funktionen unterscheiden sich ausschließlich darin, welche Adapter ihr
übergeben werden.
1. **Barcode-Dekodierung** (nur in „Strichcode" und „QR-Code"). Die zur
1. **Barcode-Dekodierung** (nur in „Strichcode" und „2D-Code"). Die zur
Funktion gehörenden Codearten werden laufend aus dem Kamerabild dekodiert
(`zxing-wasm`, `decodeBarcodes` in `src/barcode.js` bekommt die Codearten
der gewählten Funktion übergeben). Das ist exakt, im Gegensatz zu
@@ -103,7 +106,7 @@ keine Daten verlassen das Gerät.
App rät nicht, sondern fragt nach. Das gilt nur für Barcodes mit einem
bekannten Nummernschema; widersprechen sich mehrere Barcodes mit
unbekanntem Nummernschema, wird keine ihrer Teilenummern übernommen.
2. **Teilenummer-Decoder** (nur relevant in „Strichcode"/„QR-Code" — „Text
2. **Teilenummer-Decoder** (nur relevant in „Strichcode"/„2D-Code" — „Text
erkennen" liest nie einen Barcode, siehe Punkt 0). Aus einer Hersteller-PN
wie `M386A8K40BM1-CRC4Y` werden Kapazität, Bauform und Geschwindigkeit
tabellengesteuert abgeleitet. Gelingt das, entfällt OCR vollständig, das Ergebnis ist grün.
@@ -134,7 +137,7 @@ keine Daten verlassen das Gerät.
die aus dem erkannten Text abgeleiteten technischen Angaben (Kapazität,
Bauform, Rank, Geschwindigkeit) über die Stapelzuordnung
(`specsCompatible` in `src/spec.js`) — anders als in „Strichcode"/
QR-Code", wo meist die exakte Teilenummer trägt.
2D-Code", wo meist die exakte Teilenummer trägt.
4. **Stapel-Zuweisung.** Grün (Barcode) und Gelb (OCR) laufen ohne Eingabe
durch; nur bei roter Konfidenz (nichts Eindeutiges erkannt, widersprüchliche
Barcodes oder widersprüchliche unbekannte Barcodes ohne jede OCR-Kapazität)
@@ -185,15 +188,15 @@ korrekt funktioniert.
npm test
```
143 Tests, alle grün (Stand dieses Dokuments). Getestet werden ausschließlich
144 Tests, alle grün (Stand dieses Dokuments). Getestet werden ausschließlich
die reinen Module ohne Browser-Zugriff: Teilenummer-Decoder, Spec-Normalisierung
und toleranter Vergleich, OCR-Bildaufbereitung (Otsu-Schwellwert) und
-Feldextraktion, Stapel-Zuweisung, die Erkennungs-Pipeline samt Zeitgrenzen
und Mehrdeutigkeitsbehandlung, die Sitzungssicherung samt Prüfung eines
wiederhergestellten Zustands, die drei Scan-Funktionen (`scan-modes.js`
Codearten je Funktion, kein DataMatrix in „QR-Code") sowie die je Funktion
Codearten je Funktion, DataMatrix/Aztec/PDF417 in „2D-Code") sowie die je Funktion
tatsächlich aufgerufenen Adapter (`scan-recognition.js` — Texterkennung läuft
nachweislich nie in „Strichcode"/„QR-Code", Barcode-Dekodierung nachweislich
nachweislich nie in „Strichcode"/„2D-Code", Barcode-Dekodierung nachweislich
nie in „Text erkennen"). Kamera, Barcode-/OCR-Adapter selbst, die
Auswahl-Oberfläche und die Verdrahtung in `main.js` laufen nur im echten
Browser und werden dort manuell geprüft (siehe „Am Gerät noch zu prüfen"
@@ -262,8 +265,8 @@ worauf du bei der Veröffentlichung achten solltest.
| `src/ocr-extract.js` | OCR-Rohtext → Spec-Felder |
| `src/session.js` | Stapel halten, vorschlagen, buchen, umsortieren, zurücknehmen — inklusive der aus den Einträgen abgeleiteten, je Stapel bekannten Barcode-Inhalte (`stack.codes`), die vor dem Vergleich der technischen Angaben über die Zuordnung entscheiden, sofern diese Angaben nicht widersprechen |
| `src/pipeline.js` | Barcode → Teilenummer → OCR → Ampelfarbe, inkl. Zeitgrenzen, Mehrdeutigkeitsbehandlung und Kurzschluss über einen bereits bekannten Barcode-Inhalt (`deps.isKnownCode`) — unverändert; kennt keine Scan-Funktionen |
| `src/scan-modes.js` | Die drei Scan-Funktionen (Strichcode, QR-Code, Text erkennen): Kennung, deutsche Beschriftung, gelesene Codearten, laufende Suche ja/nein, Texterkennung ja/nein — einzige Quelle, sowohl für die Auswahl-Oberfläche als auch für `main.js` |
| `src/scan-recognition.js` | Baut die an `recognize()` übergebenen Adapter anhand der gewählten Funktion: in „Strichcode"/„QR-Code" liefert die Texterkennung sofort leeren Text ohne Tesseract anzustoßen, in „Text erkennen" liefert die Barcode-Dekodierung sofort eine leere Liste ohne `zxing-wasm` anzustoßen |
| `src/scan-modes.js` | Die drei Scan-Funktionen (Strichcode, 2D-Code, Text erkennen): Kennung, deutsche Beschriftung, gelesene Codearten, laufende Suche ja/nein, Texterkennung ja/nein — einzige Quelle, sowohl für die Auswahl-Oberfläche als auch für `main.js` |
| `src/scan-recognition.js` | Baut die an `recognize()` übergebenen Adapter anhand der gewählten Funktion: in „Strichcode"/„2D-Code" liefert die Texterkennung sofort leeren Text ohne Tesseract anzustoßen, in „Text erkennen" liefert die Barcode-Dekodierung sofort eine leere Liste ohne `zxing-wasm` anzustoßen |
| `src/storage.js` | Absturzschutz der laufenden Sitzung: sichert und lädt aus `localStorage`, verwirft beim Laden jeden in sich unstimmigen Zustand vollständig (siehe unten) |
| `src/camera.js` | Kamerastart, Einzelbildaufnahme, Bilddatei-Ersatzweg |
| `src/barcode.js` | Adapter zu `zxing-wasm`; `decodeBarcodes(imageData, formats)` bekommt die Codearten übergeben, ohne Angabe wie bisher (Code128 + DataMatrix) |
@@ -389,17 +392,17 @@ werden:
**Funktionswahl (neu)**
- Erster Start ohne gesicherte Sitzung: Auswahl mit drei großen Flächen
(„Strichcode", „QR-Code", „Text erkennen") erscheint, *bevor* die Kamera
(„Strichcode", „2D-Code", „Text erkennen") erscheint, *bevor* die Kamera
startet — kein Kamerabild sichtbar, solange keine Funktion gewählt ist.
- Erster Start *mit* gesicherter Sitzung: erst die Frage „Gesicherte Sitzung
gefunden" (fortsetzen/verwerfen), erst danach die Funktionswahl.
- „Strichcode" wählen: laufende Suche erkennt Code-128/Code-39/EAN/UPC/…,
kein „Modul scannen"-Knopf sichtbar, Zielrahmen hebt sich bei erkanntem
Code hervor.
-QR-Code" wählen und ein Etikett mit dem eigenen DataMatrix-Code vor die
Kamera halten: **bewusst kein Treffer** (kein DataMatrix in dieser
Funktion) — Rahmen bleibt neutral. Einen echten QR-Code vor dieselbe
Funktion halten: Rahmen hebt sich hervor, Treffer wird gebucht.
-2D-Code" wählen und ein Etikett mit dem eigenen DataMatrix-Code vor die
Kamera halten: Rahmen hebt sich hervor, Treffer wird gebucht (DataMatrix
liest diese Funktion jetzt mit). Einen echten QR-Code vor dieselbe Funktion
halten: ebenfalls Treffer.
- „Text erkennen" wählen: kein automatisches Hervorheben des Rahmens (er
bleibt durchgehend schlicht), „Modul scannen"-Knopf sichtbar und löst die
Texterkennung aus.
@@ -418,7 +421,7 @@ werden:
Etikettenfoto tatsächlich brauchbaren Text (bisher nur an synthetischen
Testbildern geprüft, nie an einem echten Foto).
- Ladezeit des WASM-Barcode-Moduls (`zxing-wasm`) beim allerersten Scan einer
Sitzung in Funktion „Strichcode" oder „QR-Code" beobachten — bleibt sie
Sitzung in Funktion „Strichcode" oder „2D-Code" beobachten — bleibt sie
deutlich unter der 10-Sekunden-Zeitgrenze der Pipeline, und braucht ein
zweiter, schnell nachfolgender Scan nicht erneut die volle Ladezeit (Beleg,
dass die Vorbereitung tatsächlich nur einmal läuft)? In Funktion „Text
@@ -431,7 +434,7 @@ werden:
- Ladezeit des Tesseract-Arbeiters beim ersten Antippen von „Modul scannen"
in Funktion „Text erkennen" beobachten — bleibt sie auf einem normalen
Handy deutlich unter der 20-Sekunden-Zeitgrenze der Pipeline? In den
Funktionen „Strichcode"/„QR-Code" darf dieses Laden nie anlaufen, auch
Funktionen „Strichcode"/„2D-Code" darf dieses Laden nie anlaufen, auch
nicht bei einem gänzlich unbekannten Etikett, da `runOcr` dort nie
aufgerufen wird.
- Verfügbarkeitsanzeige der Texterkennung: bleibt nach einem erfolgreichen
@@ -440,7 +443,7 @@ werden:
darauf zurück?
- Stichprobe, ob die feste Zeichen-Whitelist der Texterkennung auf echten
Etiketten keine tatsächlich benötigten Zeichen ausschließt.
- Funktion „Strichcode"/„QR-Code": kein passender Code im Bild führt zu
- Funktion „Strichcode"/„2D-Code": kein passender Code im Bild führt zu
Rot-Dialog (statt Fehler oder Absturz) — hier ohne jeden OCR-Versuch.
Funktion „Text erkennen": kein verwertbarer OCR-Text führt ebenfalls zu
Rot, hier ohne jeden Barcode-Versuch.
+12 -8
View File
@@ -5,11 +5,12 @@
// hier - es darf keine zweite Stelle geben, an der Codearten oder
// Beschriftungen stehen.
//
// Ausdrueckliche Entscheidung des Auftraggebers (nicht abaendern): Funktion 2
// liest streng nur QR (QRCode, MicroQRCode, RMQRCode) - kein DataMatrix, auch
// wenn die 2D-Codes auf den eigenen RAM-Etiketten DataMatrix sind. Der
// Arbeitsablauf des Auftraggebers laeuft ueber Funktion 1 (Teilenummer im
// Strichcode); Funktion 2 dient anderer Ware.
// Funktion 2 ("2D-Code") deckt die praktisch vorkommenden zweidimensionalen
// Codearten ab: die QR-Varianten (QRCode, MicroQRCode, RMQRCode) sowie
// DataMatrix, Aztec und PDF417. Grund: Die 2D-Codes auf den eigenen
// RAM-Etiketten sind DataMatrix, nicht QR - eine auf QR beschraenkte Funktion
// faende auf dieser Ware nichts. Die Kennung heisst weiterhin `qrcode`, siehe
// Kommentar dort.
/**
* @typedef {object} ScanMode
@@ -34,10 +35,13 @@ export const SCAN_MODES = [
useOcr: false,
},
{
// Kennung bleibt aus historischen Gruenden `qrcode`, obwohl die Funktion
// inzwischen alle 2D-Codearten liest (nicht mehr nur QR) - Umbenennung
// nicht vorgenommen, da `qrcode` an mehreren Stellen durchgereicht wird.
id: 'qrcode',
label: 'QR-Code',
// Ausschliesslich QR-Varianten - bewusst kein DataMatrix, siehe Kommentar oben.
barcodeFormats: ['QRCode', 'MicroQRCode', 'RMQRCode'],
label: '2D-Code',
// QR-Varianten plus DataMatrix, Aztec, PDF417 - siehe Kommentar am Kopf.
barcodeFormats: ['QRCode', 'MicroQRCode', 'RMQRCode', 'DataMatrix', 'Aztec', 'PDF417'],
continuousSearch: true,
useOcr: false,
},
+13 -4
View File
@@ -17,15 +17,24 @@ test('Funktion 1 (Strichcode): eindimensionale Codearten, laufende Suche, keine
assert.equal(mode.useOcr, false);
});
test('Funktion 2 (QR-Code): ausschliesslich QR-Varianten, kein DataMatrix', () => {
test('Funktion 2 (2D-Code): QR-Varianten und DataMatrix, Aztec, PDF417', () => {
const mode = getScanMode('qrcode');
assert.equal(mode.label, 'QR-Code');
assert.deepEqual(mode.barcodeFormats, ['QRCode', 'MicroQRCode', 'RMQRCode']);
assert.ok(!mode.barcodeFormats.includes('DataMatrix'), 'Funktion 2 darf kein DataMatrix lesen');
assert.equal(mode.label, '2D-Code');
assert.deepEqual(mode.barcodeFormats, [
'QRCode', 'MicroQRCode', 'RMQRCode', 'DataMatrix', 'Aztec', 'PDF417',
]);
assert.ok(mode.barcodeFormats.includes('DataMatrix'), 'Funktion 2 muss DataMatrix lesen (Etiketten der Ware tragen DataMatrix)');
assert.equal(mode.continuousSearch, true);
assert.equal(mode.useOcr, false);
});
test('die drei Funktionen haben paarweise verschiedene Kennungen und Beschriftungen', () => {
const ids = SCAN_MODES.map((mode) => mode.id);
const labels = SCAN_MODES.map((mode) => mode.label);
assert.equal(new Set(ids).size, ids.length, 'Kennungen muessen paarweise verschieden sein');
assert.equal(new Set(labels).size, labels.length, 'Beschriftungen muessen paarweise verschieden sein');
});
test('Funktion 3 (Text erkennen): keine Codearten, keine laufende Suche, Texterkennung', () => {
const mode = getScanMode('ocr');
assert.equal(mode.label, 'Text erkennen');
+1 -1
View File
@@ -64,7 +64,7 @@ test('decodeBarcodes bekommt die Codearten der jeweiligen Funktion uebergeben',
runOcr: async () => '',
});
await adapters.decodeBarcodes({});
assert.deepEqual(receivedFormats, ['QRCode', 'MicroQRCode', 'RMQRCode']);
assert.deepEqual(receivedFormats, ['QRCode', 'MicroQRCode', 'RMQRCode', 'DataMatrix', 'Aztec', 'PDF417']);
});
test('Funktion 3 uebergibt keine Codearten, weil decodeBarcodes gar nicht aufgerufen wird', async () => {