README beschreibt jetzt, dass ein Codetreffer einen Widerspruch der technischen Angaben nicht mehr ueberstimmt (Fehler 1) und dass die Codemenge eines Stapels ein abgeleiteter statt angesammelter Wert ist (Fehler 2). Neue Grenze dokumentiert: ein gelesener Barcode-Inhalt ist exakt gelesen, aber nicht garantiert eine Teilenummer - eine geteilte Los- oder Wiederverkaeufer-Kennung kann zwei verschiedene Module verbinden, wogegen die neue Widerspruchspruefung schuetzt. Testanzahl auf 124 aktualisiert.
363 lines
21 KiB
Markdown
363 lines
21 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
|
|
|
|
```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 — 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 |
|
|
|
|
`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.
|