Ohne gueltiges Passwort gibt der Server weder Seite noch Skript heraus. Das Passwort kommt aus APP_PASSWORD; fehlt die Variable, startet der Server absichtlich nicht. /healthz bleibt passwortfrei, damit die Ueberwachung den geschuetzten Dienst nicht faelschlich fuer ausgefallen haelt.
398 lines
23 KiB
Markdown
398 lines
23 KiB
Markdown
# RAM-Sortierhilfe
|
|
|
|
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.
|
|
|
|
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
|
|
*aktuell* ihm zugeordneten Modulen gelesen wurden (`stack.codes`); diese Menge
|
|
wird nicht angesammelt, sondern aus den Codes der zugeordneten Einträge
|
|
(`entry.codes`) abgeleitet — genau wie der Stapelzähler (siehe „Sitzungssicherung
|
|
im Detail" unten). Sortiert der Nutzer ein Modul um oder entfernt es, verliert
|
|
der alte Stapel dessen Codes deshalb sofort wieder; ein bereinigter Fehler
|
|
zieht kein weiteres Modul mehr fälschlich an. Trägt ein neu gescanntes Modul
|
|
einen Code, den ein Stapel bereits kennt, ist dieser Stapel Kandidat — aber
|
|
nur, wenn die technischen Angaben beider Seiten sich dabei nicht widersprechen
|
|
(`specsCompatible` in `src/spec.js`, siehe unten); ein Codetreffer allein
|
|
genügt nicht, sonst könnte ein Code, der gar keine Teilenummer ist, zwei
|
|
technisch verschiedene Module zusammenführen (siehe „Grenzen"). Genau ein
|
|
solcher Kandidat gehört dorthin, auch wenn die technischen Angaben
|
|
unvollständig sind (`proposeAssignment` in `src/session.js`). Auf Etiketten
|
|
stehen oft zwei Codes, von denen einer die Seriennummer ist, die bei jedem
|
|
Modul anders ist: Sie läuft beim Codeabgleich einfach ins Leere, während die
|
|
danebenstehende Teilenummer trägt — kein Sonderfall, nur eine Konsequenz des
|
|
Mengenvergleichs.
|
|
|
|
Kennt kein Stapel einen der gelesenen Codes ohne Widerspruch (typischerweise
|
|
beim ersten Modul eines neuen Stapels, aber auch dann, wenn ein Codetreffer an
|
|
der Widerspruchsprüfung scheitert), vergleicht die App stattdessen über alle
|
|
Merkmale, die
|
|
bei Modul und Stapel bekannt sind — Kapazität, Bauform, Rank, Geschwindigkeit
|
|
und, sofern gelesen, Hersteller-Teilenummer (`specsCompatible` in
|
|
`src/spec.js`); ein auf einer Seite fehlendes Merkmal wird dabei übersprungen,
|
|
nicht als Widerspruch gewertet. Ein Stapel gilt dabei nur als Kandidat, wenn
|
|
er mit dem Modul mindestens ein tatsächlich erkanntes Merkmal teilt
|
|
(`sharesSetField` in `src/session.js`) — ein Stapel ganz ohne erkannte Angaben
|
|
(etwa aus einem manuell entschiedenen Rot-Fall) ist deshalb nie automatisch
|
|
treffend, sonst würde er lautlos jedes folgende Modul an sich ziehen. Passt
|
|
danach genau ein Stapel, wird das Modul zugewiesen; passen mehrere oder mehrere
|
|
über einen bekannten Code, fragt die App nach. Das hat eine bewusst in Kauf
|
|
genommene Folge: Konnte die Teilenummer nicht gelesen werden — der häufige
|
|
Fall bei überklebten oder beschädigten Etiketten — und passt anhand der
|
|
übrigen Merkmale nur ein Stapel, landet das Modul dort, auch wenn seine
|
|
tatsächliche Teilenummer eine andere wäre. Diese Abwägung ist Absicht: Ohne
|
|
sie müsste die App bei jedem zweiten Modul nachfragen, sobald die
|
|
Texterkennung keine Teilenummer liefert.
|
|
|
|
Die Erkennung läuft vollständig lokal im Browser — kein Server, keine Cloud,
|
|
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.
|
|
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
|
|
(`recognize` in `src/pipeline.js`). Findet die anschließende Texterkennung
|
|
dazu auch keine Kapazität, ist das Ergebnis trotzdem grün, nur eben ohne
|
|
abgeleitete Angaben in der Rückmeldung. Ist einer der gelesenen Codes
|
|
bereits einem Stapel dieser Sitzung bekannt, steht die Zuordnung ohnehin
|
|
fest — dann entfällt auch die Texterkennung, weil sie nichts mehr beitragen
|
|
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.
|
|
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)
|
|
oder mehrdeutiger Stapelzuordnung fragt die App nach.
|
|
5. **Sitzungsfortsetzung beim Start.** Findet die App beim Laden eine in
|
|
`localStorage` gesicherte, nicht leere Sitzung vor, wird sie nicht
|
|
stillschweigend übernommen, sondern zur Fortsetzung oder zum Verwerfen
|
|
angeboten (`askResumeSession` in `src/ui/resume-dialog.js`, verdrahtet über
|
|
`initResumeOffer()` in `src/main.js`). Erst „fortsetzen“ stellt Stapel,
|
|
Zähler und die „Zuletzt“-Zeile wieder her; „verwerfen“ löscht den
|
|
gesicherten Stand und die App startet leer.
|
|
6. **Ersatzweg ohne Kamera.** Lässt sich die Kamera nicht starten (z. B.
|
|
verweigerte Berechtigung), bleibt die App bedienbar: eine dauerhaft
|
|
sichtbare Schaltfläche „Bild auswählen“ öffnet stattdessen die native
|
|
Dateiauswahl (`setFilePickerVisible` in `src/ui/scan-view.js`). Die App
|
|
versucht danach nicht automatisch erneut, die Kamera zu starten - der
|
|
Kamera-Hinweistext bleibt für den Rest der Sitzung stehen und wird nicht
|
|
von der OCR-Verfügbarkeitsanzeige überschrieben.
|
|
|
|
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
|
|
```
|
|
|
|
## Entwicklung
|
|
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
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
|
|
|
|
```bash
|
|
npm test
|
|
```
|
|
|
|
124 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
|
|
|
|
Zum lokalen Nachschauen ohne Passwort:
|
|
|
|
```bash
|
|
npm run build
|
|
npm run preview
|
|
```
|
|
|
|
`preview` hört auf `PORT` aus der Umgebung (Vorgabe `4173`, falls `PORT`
|
|
nicht gesetzt ist).
|
|
|
|
Im Betrieb läuft stattdessen `server.py` — siehe nächster Abschnitt.
|
|
|
|
## Passwortschutz
|
|
|
|
Die veröffentlichte Seite ist passwortgeschützt. Geprüft wird **im Server**,
|
|
nicht im Browser: Ohne gültiges Passwort gibt `server.py` nichts heraus —
|
|
weder die Seite noch das gebündelte Skript. Ein Passwortschutz allein im
|
|
Browser wäre wirkungslos, weil das Passwort dann im ausgelieferten Skript
|
|
stünde und dort mitgelesen werden könnte.
|
|
|
|
```bash
|
|
APP_PASSWORD=dein-passwort python3 server.py
|
|
```
|
|
|
|
- Das Passwort kommt ausschließlich aus der Umgebungsvariable `APP_PASSWORD`
|
|
(siehe `.env.example`). Es steht **nicht** im Quelltext.
|
|
- **Fehlt die Variable, startet der Server nicht** und meldet das im Klartext.
|
|
Das ist Absicht: Ein vergessener Eintrag soll auffallen, statt stillschweigend
|
|
eine ungeschützte Seite ins Netz zu stellen.
|
|
- Abgefragt wird per HTTP-Basic-Auth. Der Browser fragt **einmal pro Sitzung**,
|
|
nicht bei jedem Seitenaufruf — beim Sortieren wäre das sonst unbrauchbar.
|
|
- Der **Benutzername ist beliebig**; geprüft wird nur das Passwort. Beim
|
|
Vergleich wird eine zeitkonstante Prüfung verwendet.
|
|
- `/healthz` antwortet **ohne** Passwort mit `ok`. Das braucht die Überwachung
|
|
der Hosting-Umgebung, die den geschützten Dienst sonst für ausgefallen hielte.
|
|
Der Pfad gibt nichts über die Anwendung preis.
|
|
|
|
Das Passwort wandert bei einer Basic-Auth-Anmeldung mit jeder Anfrage über die
|
|
Leitung. Das ist unbedenklich, solange die Seite über HTTPS ausgeliefert wird —
|
|
worauf du bei der Veröffentlichung achten solltest.
|
|
|
|
## 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 — 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/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/scan-view.js` | Scan-Ansicht: Kamera-Vorschau, Scan-Knopf, Stapel-Leiste, „Zuletzt"-Zeile, Ersatzweg-Schaltfläche bei fehlender Kamera |
|
|
| `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/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 |
|
|
| `src/styles.css` | Farbvariablen (Grün/Gelb/Rot der Ampel-Rückmeldung), Layout des Kamera-Vollbilds samt Zielrahmen, Stapel-Leiste und Aktionsknöpfe sowie die Overlays für Treffer-Rückmeldung, Rot-Dialog und Sitzungsliste |
|
|
| `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.
|
|
|
|
### 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 — genauso die Codemenge eines Stapels
|
|
(`stack.codes`): Sie wird nie aus dem gesicherten Wert übernommen, sondern
|
|
stets aus den Codes der ihm tatsächlich zugeordneten Einträge (`entry.codes`)
|
|
neu zusammengesetzt, dieselbe Ableitung, die `session.js` nach jedem
|
|
Umsortieren oder Entfernen eines Eintrags vornimmt. Sind `stack.codes` bzw.
|
|
`entry.codes` vorhanden, werden sie streng geprüft (Liste von
|
|
Zeichenketten); ein gesicherter Stand aus einer Fassung vor dieser Erweiterung
|
|
ohne diese Felder gilt dagegen nicht als unstimmig, sondern wird beim Laden
|
|
als Stapel bzw. Eintrag ohne bekannte Codes wiederhergestellt.
|
|
|
|
## 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 Codeabgleich (siehe oben) weiß nicht, ob ein gelesener Barcode-Inhalt
|
|
tatsächlich eine Teilenummer ist. Trägt ein Etikett daneben noch eine
|
|
Los- oder Wiederverkäufer-Kennung und teilen zwei technisch verschiedene
|
|
Module dieselbe, würde ein reiner Codetreffer sie fälschlich zusammenführen.
|
|
Dagegen schützt, dass ein Codetreffer allein nie genügt: Ein Stapel ist über
|
|
einen Code nur dann Kandidat, wenn die technischen Angaben ihm zusätzlich
|
|
nicht widersprechen (`specsCompatible`, siehe `proposeAssignment` in
|
|
`src/session.js`) — widerspricht der Stapel trotz passendem Code, fällt das
|
|
Modul auf einen eigenen Stapel zurück, statt lautlos falsch einsortiert zu
|
|
werden.
|
|
- Der Teilenummer-Decoder kennt bisher nur das Samsung-DDR4-Schema. Unbekannte
|
|
Schemata sind kein Fehler — sie führen automatisch zum OCR-Weg, und selbst
|
|
wenn dieser keine Kapazität findet, genügt die exakt gelesene Teilenummer
|
|
allein für Grün (siehe „Funktionsweise“ oben). Nur Kapazität, Bauform,
|
|
Rank und Geschwindigkeit hängen am Tabelleneintrag — die Wiedererkennung
|
|
eines bereits erfassten Moduls über seinen Barcode-Inhalt nicht.
|
|
- **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; die Statuszeile bleibt leer, solange Kamera
|
|
und Texterkennung verfügbar sind.
|
|
- 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 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)?
|
|
- 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?
|
|
- 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).
|
|
- 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
|
|
Seriennummer die Zuordnung trägt.
|
|
|
|
**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: Dialog „Gesicherte
|
|
Sitzung gefunden“ erscheint; erst nach „fortsetzen“ bleiben Stapel und
|
|
Zähler erhalten und die „Zuletzt“-Zeile zeigt wieder den tatsächlich
|
|
letzten Eintrag; „verwerfen“ führt zu einem leeren Start.
|
|
- 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.
|