Aktualisiere README fuer Barcode-Codeabgleich und gelockerten Gruen-Massstab

Beschreibt die neue Vorrangregel (bekannter Barcode-Inhalt entscheidet vor
dem Vergleich der technischen Angaben), dass eine exakt gelesene Teilenummer
allein fuer Gruen genuegt, und den OCR-Kurzschluss bei bereits bekanntem
Code. Ergaenzt entsprechende Pruefpunkte im Abschnitt "Am Gerät noch zu
pruefen" und aktualisiert die Testanzahl auf 121.
This commit is contained in:
vchuser
2026-07-29 09:05:35 +02:00
parent c9084830b8
commit 7b4a3532bb
+81 -38
View File
@@ -2,23 +2,37 @@
Browser-App, die per Handykamera gebrauchte Server-RAM-Module erkennt und Browser-App, die per Handykamera gebrauchte Server-RAM-Module erkennt und
beim physischen Sortieren am Tisch anleitet: Modul vor die Kamera halten, die beim physischen Sortieren am Tisch anleitet: Modul vor die Kamera halten, die
App nennt den Stapel. Verglichen wird über alle Merkmale, die bei Modul und App nennt den Stapel.
Stapel bekannt sind — Kapazität, Bauform, Rank, Geschwindigkeit und, sofern
gelesen, Hersteller-Teilenummer (`specsCompatible` in `src/spec.js`); ein auf Zwei Module gelten als zusammengehörig, wenn sie denselben Barcode-Inhalt
einer Seite fehlendes Merkmal wird dabei übersprungen, nicht als Widerspruch tragen — ein Barcode ist exakt gelesen, das ist der zuverlässigste Maßstab.
gewertet. Ein Stapel gilt dabei nur als Kandidat, wenn er mit dem Modul Jeder Stapel führt dafür die Menge der Barcode-Zeichenketten, die bei seinen
mindestens ein tatsächlich erkanntes Merkmal teilt (`sharesSetField` in Modulen gelesen wurden (`stack.codes`); trägt ein neu gescanntes Modul einen
`src/session.js`) — ein Stapel ganz ohne erkannte Angaben (etwa aus einem Code, den ein Stapel bereits kennt, gehört es dorthin — das hat Vorrang vor
manuell entschiedenen Rot-Fall) ist deshalb nie automatisch treffend, sonst allem Weiteren, auch wenn die technischen Angaben unvollständig sind
würde er lautlos jedes folgende Modul an sich ziehen. Passt danach genau ein (`proposeAssignment` in `src/session.js`). Auf Etiketten stehen oft zwei
Stapel, wird das Modul zugewiesen; passen mehrere, fragt die App nach Codes, von denen einer die Seriennummer ist, die bei jedem Modul anders ist:
(`proposeAssignment` in `src/session.js`). Das hat eine bewusst in Kauf Sie läuft beim Codeabgleich einfach ins Leere, während die danebenstehende
genommene Folge: Konnte die Teilenummer nicht gelesen Teilenummer trägt — kein Sonderfall, nur eine Konsequenz des Mengenvergleichs.
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 Kennt kein Stapel einen der gelesenen Codes (typischerweise beim ersten Modul
wenn seine tatsächliche Teilenummer eine andere wäre. Diese Abwägung ist eines neuen Stapels), vergleicht die App stattdessen über alle Merkmale, die
Absicht: Ohne sie müsste die App bei jedem zweiten Modul nachfragen, sobald bei Modul und Stapel bekannt sind — Kapazität, Bauform, Rank, Geschwindigkeit
die Texterkennung keine Teilenummer liefert. 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, Die Erkennung läuft vollständig lokal im Browser — kein Server, keine Cloud,
keine Daten verlassen das Gerät. keine Daten verlassen das Gerät.
@@ -31,25 +45,36 @@ keine Daten verlassen das Gerät.
dieselbe Teilenummer), gilt das als mehrdeutig — die App rät nicht, dieselbe Teilenummer), gilt das als mehrdeutig — die App rät nicht,
sondern fragt nach. Das gilt nur für Barcodes mit einem bekannten sondern fragt nach. Das gilt nur für Barcodes mit einem bekannten
Nummernschema; widersprechen sich mehrere Barcodes mit unbekanntem Nummernschema; widersprechen sich mehrere Barcodes mit unbekanntem
Nummernschema, wird keine ihrer Teilenummern übernommen und das Ergebnis Nummernschema, wird keine ihrer Teilenummern übernommen.
stützt sich allein auf die Texterkennung, ohne dass die App nachfragt.
2. **Teilenummer-Decoder.** Aus einer Hersteller-PN wie `M386A8K40BM1-CRC4Y` 2. **Teilenummer-Decoder.** Aus einer Hersteller-PN wie `M386A8K40BM1-CRC4Y`
werden Kapazität, Bauform und Geschwindigkeit tabellengesteuert werden Kapazität, Bauform und Geschwindigkeit tabellengesteuert
abgeleitet. Gelingt das, entfällt OCR vollständig. abgeleitet. Gelingt das, entfällt OCR vollständig, das Ergebnis ist grün.
3. **OCR als Rückfallebene.** Nur wenn kein Barcode eindeutig lesbar war (z. B. Kennt der Decoder das Nummernschema nicht (siehe „Grenzen“ unten), ist das
überklebtes oder beschädigtes Etikett). Das Kamerabild wird dafür in allein noch kein Grund für Rot: Zum Sortieren muss keine Kapazität bekannt
Graustufen gewandelt und mit einem **Otsu-Schwellwert** in Schwarz/Weiß sein, eine exakt gelesene Teilenummer genügt, um ein Modul wiederzuerkennen
aufbereitet: Der Schwellwert wird aus der Helligkeitsverteilung des (`recognize` in `src/pipeline.js`). Findet die anschließende Texterkennung
gesamten Bildes bestimmt, nicht aus einer festen Kontrastspreizung dazu auch keine Kapazität, ist das Ergebnis trotzdem grün, nur eben ohne
zwischen hellstem und dunkelstem Pixel. Das macht die Aufbereitung robust abgeleitete Angaben in der Rückmeldung. Ist einer der gelesenen Codes
gegen einzelne Lichtreflexe auf glänzenden Metalletiketten, die eine reine bereits einem Stapel dieser Sitzung bekannt, steht die Zuordnung ohnehin
Min/Max-Spreizung leicht kippen würden. Das OCR-Ergebnis wird fest — dann entfällt auch die Texterkennung, weil sie nichts mehr beitragen
anschließend gegen die bekannten Spec-Werte abgeglichen, was die könnte, das über die Zuordnung entscheidet. Bei einem tatsächlich neuen
typischen Verwechslungen (0/O, 1/I, 8/B, …) auflöst. 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 4. **Stapel-Zuweisung.** Grün (Barcode) und Gelb (OCR) laufen ohne Eingabe
durch; nur bei roter Konfidenz (nichts Eindeutiges erkannt oder durch; nur bei roter Konfidenz (nichts Eindeutiges erkannt, widersprüchliche
widersprüchliche Barcodes) oder mehrdeutiger Stapelzuordnung fragt die Barcodes oder widersprüchliche unbekannte Barcodes ohne jede OCR-Kapazität)
App nach. oder mehrdeutiger Stapelzuordnung fragt die App nach.
5. **Sitzungsfortsetzung beim Start.** Findet die App beim Laden eine in 5. **Sitzungsfortsetzung beim Start.** Findet die App beim Laden eine in
`localStorage` gesicherte, nicht leere Sitzung vor, wird sie nicht `localStorage` gesicherte, nicht leere Sitzung vor, wird sie nicht
stillschweigend übernommen, sondern zur Fortsetzung oder zum Verwerfen stillschweigend übernommen, sondern zur Fortsetzung oder zum Verwerfen
@@ -96,7 +121,7 @@ korrekt funktioniert.
npm test npm test
``` ```
101 Tests, alle grün (Stand dieses Dokuments). Getestet werden ausschließlich 121 Tests, alle grün (Stand dieses Dokuments). Getestet werden ausschließlich
die reinen Module ohne Browser-Zugriff: Teilenummer-Decoder, Spec-Normalisierung die reinen Module ohne Browser-Zugriff: Teilenummer-Decoder, Spec-Normalisierung
und toleranter Vergleich, OCR-Bildaufbereitung (Otsu-Schwellwert) und und toleranter Vergleich, OCR-Bildaufbereitung (Otsu-Schwellwert) und
-Feldextraktion, Stapel-Zuweisung, die Erkennungs-Pipeline samt Zeitgrenzen -Feldextraktion, Stapel-Zuweisung, die Erkennungs-Pipeline samt Zeitgrenzen
@@ -131,8 +156,8 @@ nicht gesetzt ist).
| `src/pn-tables.js` | Herstellertabellen für den Teilenummer-Decoder | | `src/pn-tables.js` | Herstellertabellen für den Teilenummer-Decoder |
| `src/pn-decoder.js` | Teilenummer → Spec-Felder | | `src/pn-decoder.js` | Teilenummer → Spec-Felder |
| `src/ocr-extract.js` | OCR-Rohtext → Spec-Felder | | `src/ocr-extract.js` | OCR-Rohtext → Spec-Felder |
| `src/session.js` | Stapel halten, vorschlagen, buchen, umsortieren, zurücknehmen | | `src/session.js` | Stapel halten, vorschlagen, buchen, umsortieren, zurücknehmen — inklusive der je Stapel gesammelten Barcode-Inhalte (`stack.codes`), die vor dem Vergleich der technischen Angaben über die Zuordnung entscheiden |
| `src/pipeline.js` | Barcode → Teilenummer → OCR → Ampelfarbe, inkl. Zeitgrenzen und Mehrdeutigkeitsbehandlung | | `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/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/camera.js` | Kamerastart, Einzelbildaufnahme, Bilddatei-Ersatzweg |
| `src/barcode.js` | Adapter zu `zxing-wasm` | | `src/barcode.js` | Adapter zu `zxing-wasm` |
@@ -163,7 +188,11 @@ auch nur eine dieser Bedingungen nicht zu, wird die gesamte gespeicherte
Sitzung verworfen (die App startet leer), statt mit einem halb kaputten Sitzung verworfen (die App startet leer), statt mit einem halb kaputten
Zustand lautlos weiterzuarbeiten. Der gespeicherte Stapelzähler selbst wird Zustand lautlos weiterzuarbeiten. Der gespeicherte Stapelzähler selbst wird
nie übernommen, sondern beim Laden immer neu aus der tatsächlichen Zahl der nie übernommen, sondern beim Laden immer neu aus der tatsächlichen Zahl der
zugeordneten Einträge berechnet. zugeordneten Einträge berechnet. Die je Stapel gesicherten Barcode-Inhalte
(`stack.codes`) werden, sofern vorhanden, ebenso streng geprüft (Liste von
Zeichenketten); ein gesicherter Stand aus einer Fassung vor dieser Erweiterung
ohne dieses Feld gilt dagegen nicht als unstimmig, sondern wird beim Laden als
Stapel ohne bekannte Codes wiederhergestellt.
## Grenzen ## Grenzen
@@ -173,7 +202,11 @@ zugeordneten Einträge berechnet.
- Das Laden der Seite braucht eine Verbindung; das Sortieren selbst nicht - Das Laden der Seite braucht eine Verbindung; das Sortieren selbst nicht
(nach dem Laden läuft alles lokal im Browser). (nach dem Laden läuft alles lokal im Browser).
- Der Teilenummer-Decoder kennt bisher nur das Samsung-DDR4-Schema. Unbekannte - Der Teilenummer-Decoder kennt bisher nur das Samsung-DDR4-Schema. Unbekannte
Schemata sind kein Fehler — sie führen automatisch zum OCR-Weg. 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.** - **Der Teilenummer-Decoder ist nur teilweise gegen reale Module geprüft.**
Belegt ist ausschließlich der Samsung-Eintrag `A8K40` (64 GB, 4DRx4) mit Belegt ist ausschließlich der Samsung-Eintrag `A8K40` (64 GB, 4DRx4) mit
dem Geschwindigkeitscode `CRC`, anhand des Referenzmoduls dem Geschwindigkeitscode `CRC`, anhand des Referenzmoduls
@@ -259,6 +292,16 @@ werden:
Etiketten keine tatsächlich benötigten Zeichen ausschließt. Etiketten keine tatsächlich benötigten Zeichen ausschließt.
- Bild ohne lesbaren Barcode und ohne verwertbaren OCR-Text: Rot-Dialog - Bild ohne lesbaren Barcode und ohne verwertbaren OCR-Text: Rot-Dialog
erscheint statt Fehler oder Absturz. 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** **Bedienung und Anzeige**
- Zielrahmen liegt sinnvoll über dem Kamerabild, `object-fit: cover` füllt - Zielrahmen liegt sinnvoll über dem Kamerabild, `object-fit: cover` füllt