Doku: drei Scan-Funktionen statt automatischem Barcode/OCR-Ausweichen

Funktionsweise-Abschnitt, Technik- und Aufbau-Tabelle sowie die
Geraete-Checkliste auf die neue Funktionswahl gebracht. Haelt fest,
dass in "Text erkennen" mangels Barcode-Teilenummer allein die
technischen Angaben ueber die Stapelzuordnung entscheiden, und dass
"QR-Code" bewusst kein DataMatrix liest. Testanzahl auf 143 aktualisiert.
This commit is contained in:
2026-07-29 13:37:18 +02:00
parent 2e619ed64d
commit ebff173443
+146 -56
View File
@@ -4,6 +4,11 @@ Browser-App, die per Handykamera gebrauchte Server-RAM-Module erkennt und
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
ausschließlich ihre eigene Quelle; anders als früher weicht die App nicht mehr
automatisch auf Texterkennung aus, wenn kein Code gefunden wird.
Zwei Module gelten als zusammengehörig, wenn sie denselben Barcode-Inhalt
tragen — ein Barcode ist exakt gelesen, das ist der zuverlässigste Maßstab.
Jeder Stapel führt dafür die Menge der Barcode-Zeichenketten, die bei seinen
@@ -51,16 +56,57 @@ keine Daten verlassen das Gerät.
## Funktionsweise
1. **Barcode zuerst.** Code-128 und DataMatrix werden aus dem Kamerabild
dekodiert (`zxing-wasm`). Das ist exakt, im Gegensatz zu Texterkennung.
Werden mehrere Barcodes im Bild gefunden, die sich widersprechen (nicht
dieselbe Teilenummer), gilt das als mehrdeutig — die 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.** 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.
0. **Funktion wählen.** Vor dem ersten Scan (und jederzeit erneut über die
antippbare Anzeige in der Scan-Ansicht) wählt der Nutzer eine von drei
Funktionen. Kennung, Beschriftung, gelesene Codearten und Verhalten stehen
ausschließlich in `src/scan-modes.js` — es gibt keine zweite Stelle, an der
das steht:
| 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 |
| **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.
„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"
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
Suche startet oder stoppt, der Knopf erscheint oder verschwindet.
Liegt beim Start eine gesicherte Sitzung vor, erscheint zuerst die Frage
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
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
`src/scan-recognition.js`). Die Erkennungs-Pipeline selbst (`src/pipeline.js`,
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
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
Texterkennung. Werden mehrere Barcodes im Bild gefunden, die sich
widersprechen (nicht dieselbe Teilenummer), gilt das als mehrdeutig — die
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
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.
Kennt der Decoder das Nummernschema nicht (siehe „Grenzen“ unten), ist das
allein noch kein Grund für Rot: Zum Sortieren muss keine Kapazität bekannt
sein, eine exakt gelesene Teilenummer genügt, um ein Modul wiederzuerkennen
@@ -72,17 +118,23 @@ keine Daten verlassen das Gerät.
könnte, das über die Zuordnung entscheidet. Bei einem tatsächlich neuen
Modul läuft sie dagegen wie gehabt, denn dort liefert sie die lesbare
Beschriftung des neuen Stapels.
3. **OCR als Rückfallebene.** Nur wenn kein Barcode eindeutig lesbar war oder
sein Nummernschema unbekannt ist (z. B. überklebtes/beschädigtes Etikett
oder ein noch nicht in `src/pn-tables.js` hinterlegter Hersteller). Das
Kamerabild wird dafür in Graustufen gewandelt und mit einem
**Otsu-Schwellwert** in Schwarz/Weiß aufbereitet: Der Schwellwert wird aus
der Helligkeitsverteilung des gesamten Bildes bestimmt, nicht aus einer
festen Kontrastspreizung zwischen hellstem und dunkelstem Pixel. Das macht
die Aufbereitung robust gegen einzelne Lichtreflexe auf glänzenden
Metalletiketten, die eine reine Min/Max-Spreizung leicht kippen würden.
Das OCR-Ergebnis wird anschließend gegen die bekannten Spec-Werte
abgeglichen, was die typischen Verwechslungen (0/O, 1/I, 8/B, …) auflöst.
3. **Texterkennung** (ausschließlich in „Text erkennen", ausgelöst durch
„Modul scannen"). Anders als früher ist das kein automatischer Rückfall
vom Barcode-Weg mehr, sondern die einzige Quelle dieser Funktion — sie
liest ohnehin nie einen Barcode (siehe Punkt 0). Das Kamerabild wird dafür
in Graustufen gewandelt und mit einem **Otsu-Schwellwert** in Schwarz/Weiß
aufbereitet: Der Schwellwert wird aus der Helligkeitsverteilung des
gesamten Bildes bestimmt, nicht aus einer festen Kontrastspreizung
zwischen hellstem und dunkelstem Pixel. Das macht die Aufbereitung robust
gegen einzelne Lichtreflexe auf glänzenden Metalletiketten, die eine reine
Min/Max-Spreizung leicht kippen würden. Das OCR-Ergebnis wird anschließend
gegen die bekannten Spec-Werte abgeglichen, was die typischen
Verwechslungen (0/O, 1/I, 8/B, …) auflöst. Da in dieser Funktion nie eine
per Barcode gelesene Teilenummer vorliegt, entscheiden hier ausschließlich
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.
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)
@@ -133,14 +185,19 @@ korrekt funktioniert.
npm test
```
124 Tests, alle grün (Stand dieses Dokuments). Getestet werden ausschließlich
143 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, sowie die Sitzungssicherung samt Prüfung eines
wiederhergestellten Zustands. Kamera, Barcode-/OCR-Adapter selbst und
Oberfläche laufen nur im echten Browser und werden dort manuell geprüft
(siehe „Am Gerät noch zu prüfen" unten) — `node --test` kennt kein DOM.
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
tatsächlich aufgerufenen Adapter (`scan-recognition.js` — Texterkennung läuft
nachweislich nie in „Strichcode"/„QR-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"
unten) — `node --test` kennt kein DOM.
## Produktion
@@ -189,7 +246,9 @@ worauf du bei der Veröffentlichung achten solltest.
- Vanilla JavaScript (ES Modules), kein Framework
- [Vite](https://vite.dev/) als Entwicklungsserver und Bündler
- [`zxing-wasm`](https://github.com/Sec-ant/zxing-wasm) für Code-128 und DataMatrix
- [`zxing-wasm`](https://github.com/Sec-ant/zxing-wasm) zur Barcode-Dekodierung;
welche Codearten gesucht werden, gibt die gewählte Funktion vor
(`src/scan-modes.js`)
- [`tesseract.js`](https://tesseract.projectnaptha.com/) für Texterkennung
- `node:test` für Tests, ohne zusätzliche Test-Bibliothek
@@ -202,15 +261,18 @@ worauf du bei der Veröffentlichung achten solltest.
| `src/pn-decoder.js` | Teilenummer → Spec-Felder |
| `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`) |
| `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/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` |
| `src/barcode.js` | Adapter zu `zxing-wasm`; `decodeBarcodes(imageData, formats)` bekommt die Codearten übergeben, ohne Angabe wie bisher (Code128 + DataMatrix) |
| `src/ocr.js` | Bildaufbereitung (Otsu) und Adapter zu `tesseract.js` |
| `src/ui/scan-view.js` | Scan-Ansicht: Kamera-Vorschau, Scan-Knopf, Stapel-Leiste, „Zuletzt"-Zeile, Ersatzweg-Schaltfläche bei fehlender Kamera |
| `src/ui/scan-view.js` | Scan-Ansicht: Kamera-Vorschau, antippbare Anzeige der gewählten Funktion (Wechsel), Scan-Knopf (nur in „Text erkennen" sichtbar), Stapel-Leiste, „Zuletzt"-Zeile, Ersatzweg-Schaltfläche bei fehlender Kamera |
| `src/ui/mode-dialog.js` | Vollbild-Auswahl der Scan-Funktion vor dem Start und beim Wechsel: drei große Flächen, eine je Funktion aus `src/scan-modes.js` |
| `src/ui/result-overlay.js` | Kurze Treffer-Rückmeldung (grün/gelb), blendet sich nach kurzer Zeit selbst wieder aus |
| `src/ui/ambiguous-dialog.js` | Rot-Dialog bei roter Konfidenz oder mehrdeutiger Stapelzuordnung |
| `src/ui/resume-dialog.js` | Dialog beim Start: gesicherte Sitzung fortsetzen oder verwerfen |
| `src/ui/resume-dialog.js` | Dialog beim Start: gesicherte Sitzung fortsetzen oder verwerfen — läuft vor der Funktionswahl |
| `src/ui/session-list.js` | Sitzungsliste: Stapel-Übersicht, Umsortieren, Entfernen, Sitzung beenden |
| `src/ui/describe-spec.js` | Gemeinsame Kurzbeschreibung eines Specs (Kapazität, Rank, Geschwindigkeit, Bauform), wahlweise mit Platzhaltern für fehlende Felder; von Treffer-Rückmeldung, „Zuletzt"-Zeile und Sitzungsliste gemeinsam genutzt |
| `src/main.js` | Verdrahtung aller Module zur lauffähigen App |
@@ -218,11 +280,11 @@ worauf du bei der Veröffentlichung achten solltest.
| `server.py` | Auslieferung im Betrieb: statischer Server für `dist/` mit Passwortabfrage vor jeder Datei, plus passwortfreier Gesundheitspfad `/healthz`. Nur Python-Standardbibliothek, keine zusätzliche Abhängigkeit |
| `.vch/deploy.yaml` | Bau- und Startbefehl sowie Gesundheitspfad für die Hosting-Umgebung |
`spec`, `pn-decoder`, `ocr-extract`, `session`, `pipeline` und `storage` sind
reine Funktionen ohne Browser-Zugriff (kein `window`, `document` oder
`localStorage` direkt) und deshalb vollständig mit `node:test` prüfbar.
`camera.js`, `barcode.js`, `ocr.js` und `src/ui/` brauchen einen echten
Browser und werden nur manuell geprüft.
`spec`, `pn-decoder`, `ocr-extract`, `session`, `pipeline`, `storage`,
`scan-modes` und `scan-recognition` sind reine Funktionen ohne Browser-Zugriff
(kein `window`, `document` oder `localStorage` direkt) und deshalb vollständig
mit `node:test` prüfbar. `camera.js`, `barcode.js`, `ocr.js` und `src/ui/`
brauchen einen echten Browser und werden nur manuell geprüft.
### Sitzungssicherung im Detail
@@ -325,40 +387,68 @@ werden:
- Datei-Auswahl als Ersatzweg (ohne Kamera) öffnet den nativen Dialog und
hinterlässt keine leere Fläche im Layout.
**Funktionswahl (neu)**
- Erster Start ohne gesicherte Sitzung: Auswahl mit drei großen Flächen
(„Strichcode", „QR-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.
- „Text erkennen" wählen: kein automatisches Hervorheben des Rahmens (er
bleibt durchgehend schlicht), „Modul scannen"-Knopf sichtbar und löst die
Texterkennung aus.
- Anzeige der gewählten Funktion in der Scan-Ansicht antippen: Auswahl
erscheint erneut, nach der Wahl gilt der Wechsel sofort — laufende Suche
startet/stoppt, Knopf erscheint/verschwindet, **ohne** die Seite neu zu
laden.
- Anzeige der gewählten Funktion aus rund einem Meter Entfernung lesbar.
**Barcode- und OCR-Erkennung**
- Samsung-Referenzmodul scannen: Code-128 *und* DataMatrix werden erkannt,
- Samsung-Referenzmodul in Funktion „Strichcode" scannen:
`M386A8K40BM1-CRC4Y` wird korrekt als Teilenummer übernommen, grüne
Rückmeldung mit `STAPEL A`, `64GB 4DRx4 PC4-2400 LRDIMM`.
- Etikett ohne lesbaren Barcode fotografieren: OCR-Weg greift, die
Otsu-Aufbereitung liefert auf einem echten, glänzenden Etikettenfoto
tatsächlich brauchbaren Text (bisher nur an synthetischen Testbildern
geprüft, nie an einem echten Foto).
- In Funktion „Text erkennen" ein Etikett fotografieren und „Modul scannen"
antippen: die Otsu-Aufbereitung liefert auf einem echten, glänzenden
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 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)?
Sitzung in Funktion „Strichcode" oder „QR-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
erkennen" darf dieses Laden dagegen gar nicht erst anlaufen, da dort nie
`decodeBarcodes` aufgerufen wird.
- Verbindung während des allerersten Scans einer Sitzung unterbrechen (WASM
lädt per CDN), danach mit wiederhergestellter Verbindung erneut scannen:
Das muss einen echten neuen Ladeversuch auslösen statt dauerhaft mit
demselben Fehler zu scheitern.
- Ladezeit des Tesseract-Arbeiters beim allerersten Scan einer Sitzung
beobachten — bleibt sie auf einem normalen Handy deutlich unter der
20-Sekunden-Zeitgrenze der Pipeline?
- 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
nicht bei einem gänzlich unbekannten Etikett, da `runOcr` dort nie
aufgerufen wird.
- Verfügbarkeitsanzeige der Texterkennung: bleibt nach einem erfolgreichen
Ladevorgang dauerhaft "verfügbar", springt nach einem erzwungenen
Fehlschlag (z. B. Flugmodus beim ersten Laden) beim nächsten Erfolg wieder
darauf zurück?
- Stichprobe, ob die feste Zeichen-Whitelist der Texterkennung auf echten
Etiketten keine tatsächlich benötigten Zeichen ausschließt.
- Bild ohne lesbaren Barcode und ohne verwertbaren OCR-Text: Rot-Dialog
erscheint statt Fehler oder Absturz.
- Modul mit unbekanntem Nummernschema (z. B. ein Hynix-Modul) scannen: grüne
Rückmeldung statt Rot, auch wenn keine Kapazität abgeleitet werden kann.
Dasselbe Modul ein zweites Mal scannen: landet auf demselben Stapel — und
die Rückmeldung erscheint spürbar schneller als beim ersten Scan, weil die
Texterkennung diesmal übersprungen wird (nur am Gerät beobachtbar, da
`node --test` keine echte Ladezeit misst).
- Funktion „Strichcode"/„QR-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.
- In Funktion „Strichcode" ein Modul mit unbekanntem Nummernschema (z. B. ein
Hynix-Modul) scannen: grüne Rückmeldung statt Rot, auch wenn keine
Kapazität abgeleitet werden kann (die Texterkennung ist in dieser Funktion
ohnehin stumm geschaltet und liefert nie eine Kapazität nach). Dasselbe
Modul ein zweites Mal scannen: landet auf demselben Stapel.
- Etikett mit zwei Barcodes (Teile- *und* Seriennummer) mehrfach scannen:
beide Scans landen auf demselben Stapel, obwohl die Seriennummer bei realen
Modulen nie identisch ist — das belegt, dass die Teilenummer und nicht die