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:
+8
-1
@@ -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.
|
||||||
|
|||||||
@@ -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+
|
1. **Barcode zuerst.** Code-128 und DataMatrix werden aus dem Kamerabild
|
||||||
- npm or pnpm
|
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
|
```bash
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Development
|
## Entwicklung
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
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
|
```bash
|
||||||
|
npm test
|
||||||
```
|
|
||||||
src/
|
|
||||||
...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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.
|
||||||
|
|||||||
Reference in New Issue
Block a user