From 7b4a3532bbe5300de35c1770f8efc0f0fd90f4e9 Mon Sep 17 00:00:00 2001 From: vchuser Date: Wed, 29 Jul 2026 09:05:35 +0200 Subject: [PATCH] Aktualisiere README fuer Barcode-Codeabgleich und gelockerten Gruen-Massstab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- README.md | 119 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 81 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 9c823d3..c419f25 100644 --- a/README.md +++ b/README.md @@ -2,23 +2,37 @@ 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. Verglichen wird ü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, fragt die App nach -(`proposeAssignment` in `src/session.js`). 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. +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 +Modulen gelesen wurden (`stack.codes`); trägt ein neu gescanntes Modul einen +Code, den ein Stapel bereits kennt, gehört es dorthin — das hat Vorrang vor +allem Weiteren, 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 (typischerweise beim ersten Modul +eines neuen Stapels), 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. @@ -31,25 +45,36 @@ keine Daten verlassen das Gerät. 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 und das Ergebnis - stützt sich allein auf die Texterkennung, ohne dass die App nachfragt. + 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. -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. + 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 oder - widersprüchliche Barcodes) oder mehrdeutiger Stapelzuordnung fragt die - App nach. + 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 @@ -96,7 +121,7 @@ korrekt funktioniert. 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 und toleranter Vergleich, OCR-Bildaufbereitung (Otsu-Schwellwert) und -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-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/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, 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` | @@ -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 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. +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 @@ -173,7 +202,11 @@ zugeordneten Einträge berechnet. - 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. + 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 @@ -259,6 +292,16 @@ werden: 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