docs: README und Projektbeschreibung

README und .vch-description an den tatsaechlichen Code angeglichen: Otsu-
Schwellwert statt fester Kontrastspreizung fuer OCR, Zeitgrenzen und
Mehrdeutigkeitsbehandlung in der Pipeline, gruendliche Zustandspruefung in
storage.js, 97 Tests. Herstellertabellen-Validierung (Step 3) bleibt
blockiert, da sie reale Module vom Nutzer braucht - pn-tables.js unveraendert.
Ehrlicher Beleg-Stand der Tabellen sowie Erweiterungsanleitung in der README,
plus konsolidierte Liste dessen, was mangels Handy/Browser noch am Geraet zu
pruefen ist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
vchuser
2026-07-28 17:42:29 +02:00
co-authored by Claude Opus 5
parent b06b3abfd5
commit 2d3a9b0ddb
2 changed files with 231 additions and 18 deletions
+8 -1
View File
@@ -1 +1,8 @@
Describe your project here.
Sortierhilfe fuer gebrauchte Server-RAM-Module. Man haelt ein Modul vor die
Handykamera, und die App sagt sofort, auf welchen Stapel es gehoert - Module
mit identischen technischen Daten und identischer Hersteller-Teilenummer
landen zusammen, damit daraus verkaufsfertige Kits entstehen.
Gedacht fuer den Wareneingang im Gebrauchthandel mit Serverkomponenten, wo
gemischte Chargen ankommen und von Hand sortiert werden muessen. Die App
ersetzt das Abtippen von Etiketten und das Vergleichen im Kopf.
+223 -17
View File
@@ -1,37 +1,243 @@
# ocr_scanner
# RAM-Sortierhilfe
> Short description of what this project does.
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. Module mit identischer Spec **und** identischer
Hersteller-Teilenummer bilden einen Stapel, damit daraus verkaufsfertige Kits
entstehen.
## Getting Started
Die Erkennung läuft vollständig lokal im Browser — kein Server, keine Cloud,
keine Daten verlassen das Gerät.
### Prerequisites
## Funktionsweise
- Node.js 20+
- npm or pnpm
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.
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.
3. **OCR als Rückfallebene.** Nur wenn kein Barcode eindeutig lesbar war (z. B.
überklebtes oder beschädigtes Etikett). 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.
4. **Stapel-Zuweisung.** Grün (Barcode) und Gelb (OCR) laufen ohne Eingabe
durch; nur bei roter Konfidenz (nichts Eindeutiges erkannt oder
widersprüchliche Barcodes) oder mehrdeutiger Stapelzuordnung fragt die
App nach.
### Installation
Barcode- und OCR-Adapter sind gegen hängende Aufrufe abgesichert: Die
Erkennungs-Pipeline (`src/pipeline.js`) bricht einen Barcode-Versuch nach 10
Sekunden und einen OCR-Versuch nach 20 Sekunden ab und behandelt das wie ein
leeres Ergebnis, statt die App blockiert zu lassen. Diese Grenzen sind großzügig
bemessen (sie müssen auch das einmalige Laden der WebAssembly-/Sprachmodule
beim ersten Scan einer Sitzung abdecken) und keine Leistungserwartung.
## Installation
```bash
npm install
```
### Development
## Entwicklung
```bash
npm run dev
```
## Tech Stack
Der Server bindet an alle Interfaces; VCH stellt eine Preview-URL mit SSL
bereit. **HTTPS ist Pflicht** — Kamerazugriff (`getUserMedia`) verlangt eine
gesicherte Verbindung; über eine unverschlüsselte `http://`-Adresse verweigert
der Browser die Kamera grundsätzlich, unabhängig davon, ob die App selbst
korrekt funktioniert.
- ...
## Tests
## Project Structure
```
src/
...
```bash
npm test
```
## License
97 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.
...
## Produktion
```bash
npm run build
npm run preview
```
`preview` hört auf `PORT` aus der Umgebung (Vorgabe `4173`, falls `PORT`
nicht gesetzt ist).
## Technik
- 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
- [`tesseract.js`](https://tesseract.projectnaptha.com/) für Texterkennung
- `node:test` für Tests, ohne zusätzliche Test-Bibliothek
## Aufbau
| Datei | Verantwortung |
|---|---|
| `src/spec.js` | Spec-Objekt, bekannte Werte, Fingerabdruck, toleranter Vergleich (Verwechslungstabelle für OCR-Fehler) |
| `src/pn-tables.js` | Herstellertabellen für den Teilenummer-Decoder |
| `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 |
| `src/pipeline.js` | Barcode → Teilenummer → OCR → Ampelfarbe, inkl. Zeitgrenzen und Mehrdeutigkeitsbehandlung |
| `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/ocr.js` | Bildaufbereitung (Otsu) und Adapter zu `tesseract.js` |
| `src/ui/` | Ansichten: Scan-Ansicht, Ergebnis-Einblendung, Rot-Dialog bei Mehrdeutigkeit, Sitzungsliste |
| `src/main.js` | Verdrahtung aller Module zur lauffähigen App |
`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.
### Sitzungssicherung im Detail
`src/storage.js` prüft eine aus `localStorage` geladene Sitzung gründlich,
bevor sie übernommen wird: Stapel- und Eintragslisten müssen die erwartete
Form haben, Stapelkennungen und Eintragsnummern müssen jeweils eindeutig
sein, jeder Eintrag muss auf einen existierenden Stapel zeigen, und
`nextEntryId` muss größer sein als jede vorhandene Eintragsnummer. Trifft
auch nur eine dieser Bedingungen nicht zu, wird die gesamte gespeicherte
Sitzung verworfen (die App startet leer), statt mit einem halb kaputten
Zustand lautlos weiterzuarbeiten. Der gespeicherte Stapelzähler selbst wird
nie übernommen, sondern beim Laden immer neu aus der tatsächlichen Zahl der
zugeordneten Einträge berechnet.
## Grenzen
- Keine Bestandsführung über Sitzungen hinweg, kein Export. Die Sicherung in
`localStorage` ist ausdrücklich nur ein Absturzschutz für die laufende
Sitzung, keine dauerhafte Datenhaltung.
- Das Laden der Seite braucht eine Verbindung; das Sortieren selbst nicht
(nach dem Laden läuft alles lokal im Browser).
- Der Teilenummer-Decoder kennt bisher nur das Samsung-DDR4-Schema. Unbekannte
Schemata sind kein Fehler — sie führen automatisch zum OCR-Weg.
- **Der Teilenummer-Decoder ist nur teilweise gegen reale Module geprüft.**
Belegt ist ausschließlich der Samsung-Eintrag `A8K40` (64 GB, 4DRx4) mit
dem Geschwindigkeitscode `CRC`, anhand des Referenzmoduls
`M386A8K40BM1-CRC4Y`. Alle übrigen Einträge in `src/pn-tables.js`
die Bauform-Codes (`378`, `391`, `393`, `386`, `471`, `474`) und die
übrigen Geschwindigkeitscodes (`CPB`, `CTD`, `CVF`, `CWE`) — stammen aus
der veröffentlichten Samsung-Systematik und wurden **nicht** gegen ein
reales Etikett bestätigt. Hynix- und Micron-Module haben noch gar keinen
Tabelleneintrag und laufen vollständig über den OCR-Weg. Ein falscher
Tabelleneintrag fällt auf dem Barcode-Weg nicht automatisch auf, weil dort
keine Texterkennung gegenläuft, gegen die er widersprechen könnte — die
Validierung braucht deshalb echte Module, keine Code-Prüfung.
- Farb- und Schriftgestaltung ist bewusst schlicht gehalten und kann nach dem
ersten Einsatz am Tisch nachgezogen werden.
## Herstellertabellen erweitern
`src/pn-tables.js` ist tabellengesteuert. Ein neuer Hersteller oder ein neuer
Dichte-/Geschwindigkeitscode braucht einen Eintrag mit `pattern`,
`formFactor`, `density` und `speed` — keine Codeänderung an `pn-decoder.js`.
Vorgehen für jedes real vorliegende Modul:
1. Hersteller-Teilenummer vom Etikett ablesen (Barcode oder Klartext) sowie
die dazugehörige Klartextzeile mit Kapazität, Rank/Bauform und
Geschwindigkeit.
2. Einen Testfall in `test/pn-decoder.test.js` ergänzen, der die erwarteten
Spec-Felder für genau diese Teilenummer festhält.
3. Den passenden Tabelleneintrag (bzw. die passende Zeile in einer
bestehenden Tabelle) in `src/pn-tables.js` ergänzen, bis der Testfall
grün ist.
4. Den Kommentar am Tabellenkopf aktualisieren, welche Einträge damit belegt
sind.
Ohne reale Module bleiben unbestätigte Einträge unbestätigt — sie sollten
nicht "auf Verdacht" ergänzt werden, weil ein falscher Eintrag auf dem
Barcode-Weg (siehe „Grenzen") nicht mehr auffällt.
## Am Gerät noch zu prüfen
Diese Umgebung hat keinen Browser und kein Handy. Die folgenden Punkte
wurden während der Umsetzung deshalb nur durch Quelltext-Lektüre, `node
--check`, `npm test` und `npm run build` abgesichert, aber nie am echten
Gerät gesehen. Sie sollten vor dem produktiven Einsatz am Tisch geprüft
werden:
**Kamera und Aufnahme**
- Preview-URL auf dem Handy öffnen (muss `https://` sein), Rückkamera-Bild
erscheint im Vorschaubereich, "Kamera läuft" wird angezeigt.
- Verweigerte Kamera-Berechtigung: verständliche Fehlermeldung erscheint
*und* die Aufnahme-LED des Geräts erlischt tatsächlich wieder.
- Aufnahme-Knopf antippen, bevor das Kamerabild bereit ist: verständliche
Meldung statt technischer Fehler.
- Datei-Auswahl als Ersatzweg (ohne Kamera) öffnet den nativen Dialog und
hinterlässt keine leere Fläche im Layout.
**Barcode- und OCR-Erkennung**
- Samsung-Referenzmodul scannen: Code-128 *und* DataMatrix werden erkannt,
`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).
- Ladezeit des Tesseract-Arbeiters beim allerersten Scan einer Sitzung
beobachten — bleibt sie auf einem normalen Handy deutlich unter der
20-Sekunden-Zeitgrenze der Pipeline?
- 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 Code: "kein Barcode gefunden" statt Fehler oder Absturz.
**Bedienung und Anzeige**
- Zielrahmen liegt sinnvoll über dem Kamerabild, `object-fit: cover` füllt
es passend.
- Stapel-Leiste (`A: 12`, `B: 4`) aus rund einem Meter Entfernung mit einer
Hand lesbar.
- Grüne/gelbe Rückmeldung erscheint nach dem Scan und verschwindet nach
rund 1200 ms von selbst; bei zwei Scans deutlich unter 1200 ms Abstand
bleibt jeweils nur eine Rückmeldung sichtbar und die App bleibt bedienbar.
- Alle Bedienflächen (Scan-, Rückgängig-, Stapel-Knöpfe) mit dem Daumen
erreichbar, während die andere Hand das Modul hält.
- Rückgängig-Knopf: Screenreader liest "Letzten Scan zurücknehmen" vor.
- Rot-Dialog bei Mehrdeutigkeit: vier klar unterscheidbare Flächen ("Stapel
A/B …", "neuer Stapel", "nochmal scannen"); bei vielen Stapel-Kandidaten
bleibt alles per Scrollen erreichbar, ohne dass die Seite dahinter
mitrollt; Tastatur-/Screenreader-Fokus bleibt innerhalb des Dialogs.
**Sitzung**
- Ein Modul zweimal scannen: beide Male derselbe Stapel, Zähler steigt.
- Rücknahme-Fläche nimmt genau den zuletzt erfassten Eintrag zurück, Zähler
sinkt entsprechend.
- Seite neu laden während einer laufenden Sitzung: Stapel und Zähler bleiben
erhalten.
- Sitzungsliste öffnen, Eintrag umsortieren, Eintrag entfernen: Ansicht baut
sich korrekt neu auf.
- "Sitzung beenden": Ansicht wird leer, und ein anschließendes Neuladen
stellt **keine** alte Sitzung wieder her; ein danach neu gescanntes Modul
wird aber wieder normal gegen Neuladen abgesichert.
- Schnelles Mehrfachtippen auf die Stapel-Leiste öffnet nur eine
Sitzungsliste, kein Übereinanderlegen mehrerer Listen.