From ebff17344395149e576ba7f33a99a4691b5a54d1 Mon Sep 17 00:00:00 2001 From: TanerUslu Date: Wed, 29 Jul 2026 13:37:18 +0200 Subject: [PATCH] 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. --- README.md | 202 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 146 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 00268ab..4feea90 100644 --- a/README.md +++ b/README.md @@ -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 1–4 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