chore: Spezifikation und Umsetzungsplan
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.DS_Store
|
||||
.superpowers/
|
||||
@@ -0,0 +1 @@
|
||||
Describe your project here.
|
||||
@@ -0,0 +1,114 @@
|
||||
# Project
|
||||
|
||||
## Environment
|
||||
|
||||
You are working inside a VCH cloud development environment.
|
||||
|
||||
- **OS:** Debian-based LXC container
|
||||
- **Editor:** code-server (VS Code in browser) on port 8080
|
||||
- **CLI:** Claude Code is available as `claude` in terminal
|
||||
- **Git:** Pre-configured. Gitea integration available through VCH.
|
||||
- **Preview:** Start a dev server on any port — VCH detects listening ports automatically and provides preview URLs with SSL.
|
||||
- **Publishing:** Assign a public subdomain to any port via the VCH Publish page (automatic SSL).
|
||||
- **User:** `vchuser` with home at `/home/vchuser`
|
||||
|
||||
## Preview Access (IMPORTANT)
|
||||
|
||||
Apps are accessed through a **reverse proxy**, not directly. There are two modes:
|
||||
|
||||
1. **Subdomain proxy** (preferred): `https://lxc{VMID}-{PORT}.dev.example.com/` — your app is at the domain root, everything works normally.
|
||||
2. **Path-based proxy** (fallback): `https://host/proxy/{INSTANCE}/p/{PORT}/` — your app is behind a path prefix.
|
||||
|
||||
### Rules for portable apps that work in both modes:
|
||||
|
||||
- **ALWAYS use relative paths** for assets, API calls, and links:
|
||||
- `css/style.css` ✅ — NOT `/css/style.css` ❌
|
||||
- `api/users` ✅ — NOT `/api/users` ❌
|
||||
- `fetch('api/data')` ✅ — NOT `fetch('/api/data')` ❌
|
||||
- **Static file serving:** Configure your server to serve from the request path, not the filesystem root.
|
||||
- **`<base>` tag:** If you must use absolute paths, add `<base href="./">` in `<head>`.
|
||||
|
||||
### Framework-specific configuration:
|
||||
|
||||
- **Express:** `app.use(express.static('public'))` works — just ensure HTML references are relative.
|
||||
- **Vite:** Set `base: './'` in `vite.config.ts`.
|
||||
- **Next.js:** Set `basePath` in `next.config.js` if using path-based proxy, or leave default for subdomain proxy.
|
||||
- **Create React App:** Set `"homepage": "."` in `package.json`.
|
||||
|
||||
## Development Conventions
|
||||
|
||||
- Write clear, descriptive commit messages (imperative mood: "Add feature", not "Added feature")
|
||||
- Prefer small, focused commits over large ones
|
||||
- Run linters and formatters before committing
|
||||
- Write tests for critical business logic
|
||||
- Keep dependencies minimal — use native browser/Node APIs where possible
|
||||
|
||||
## README Requirement (MANDATORY)
|
||||
|
||||
Every project MUST have a meaningful `README.md` in the project root. This is enforced by the VCH audit system — projects without a proper README cannot be published.
|
||||
|
||||
Your README must include at minimum:
|
||||
- **Project description** — what the project does (not just the template placeholder)
|
||||
- **Installation instructions** — how to install dependencies
|
||||
- **Development instructions** — how to start the dev server
|
||||
- **Tech stack** — what technologies are used
|
||||
|
||||
Update the README whenever you add features, change setup steps, or modify the tech stack. The audit will reject READMEs that are still just the default template.
|
||||
|
||||
## Project Description (MANDATORY)
|
||||
|
||||
Every project has a `.vch-description` file in the project root. Keep this file up to date with a clear, non-technical description of your project:
|
||||
- What the project does
|
||||
- What problem it solves or what it's used for
|
||||
- Who it's for
|
||||
|
||||
Do NOT include technical details (tech stack, dependencies, setup instructions) — those belong in the README. The `.vch-description` content is shown in the VCH Showcase and is synced automatically when an audit runs.
|
||||
|
||||
Update `.vch-description` whenever the project's purpose or scope changes significantly.
|
||||
|
||||
## Web Best Practices
|
||||
|
||||
- Use semantic HTML elements (`nav`, `main`, `article`, `section`, etc.)
|
||||
- Mobile-first responsive design
|
||||
- Follow accessibility guidelines (ARIA labels, keyboard navigation, color contrast)
|
||||
- Optimize images and assets for performance
|
||||
- Use environment variables for configuration — never hardcode secrets
|
||||
|
||||
## Commands
|
||||
|
||||
Fill in project-specific commands below:
|
||||
|
||||
- **Install dependencies:** `npm install`
|
||||
- **Start dev server:** `npm run dev`
|
||||
- **Run tests:** `npm test`
|
||||
- **Build for production:** `npm run build`
|
||||
- **Lint:** `npm run lint`
|
||||
|
||||
## Deploy preparation
|
||||
|
||||
If this project should be deployed and reads environment variables from `process.env` (or equivalent), create `.env.example` at the repo root. List every variable the app reads, one per line: `KEY=example-value`. Example values are hints only — they are **not** used in production. If the project needs no env vars, no file is required.
|
||||
|
||||
Vibecoders' tip: when in doubt, run `grep -RhoE "process\.env\.[A-Z_]+" src/ | sort -u` and put every result into `.env.example`.
|
||||
|
||||
**Listen on `PORT` (IMPORTANT).** VCH assigns every deployed app its own host port automatically — you never pick or coordinate ports, and two projects on the same machine never clash. Your app MUST bind the port from the `PORT` environment variable, not a hardcoded one:
|
||||
|
||||
- Node/Express: `app.listen(process.env.PORT || 3000)`
|
||||
- Next.js: `next start` honours `PORT` automatically
|
||||
- Vite preview / other servers: pass `--port "$PORT"` (or read `process.env.PORT`)
|
||||
|
||||
A hardcoded port works locally but fails the production health-check (VCH checks the assigned port, which usually isn't 3000). For Docker, set `port:` below to the port your app listens on *inside* the container — VCH maps the auto-assigned host port to it for you.
|
||||
|
||||
**Containerized projects (Docker).** If your project has a `Dockerfile` (or a `compose.yaml`/`docker-compose.yml`), add a `.vch/deploy.yaml` at the repo root so VCH deploys it correctly:
|
||||
|
||||
```yaml
|
||||
runtime: docker
|
||||
port: 3000 # the port your app LISTENS ON inside the container
|
||||
health: / # a path that returns 2xx/3xx when the app is up
|
||||
```
|
||||
|
||||
- VCH builds your image, runs the container with `--restart=always`, injects production env vars via `--env-file` at runtime, and health-checks `port`.
|
||||
- **Build-time variables:** only variables prefixed `NEXT_PUBLIC_`, `VITE_`, or `PUBLIC_` are passed to the build as `--build-arg` (they are client-visible by convention). Secrets are runtime-only and are never baked into the image — declare matching `ARG` lines in your Dockerfile for the public ones.
|
||||
- **Compose:** the app service MUST publish its port as `ports: ["${VCH_PORT}:<internal>"]`. VCH sets `VCH_PORT` so it can run an isolated candidate stack during audits without touching your live stack. Add `env_file: [.env]` to any service that needs production env vars — VCH writes your production variables to a `.env` file next to your compose file.
|
||||
- **VCH runs exactly ONE compose file — no `-f` override chains.** VCH auto-discovers a single compose file (`compose.yaml`/`compose.yml`/`docker-compose.yaml`/`docker-compose.yml`) at the repo root **or in a subfolder** (e.g. `docker/`) and runs only that file. A separate override such as `docker-compose.ports.yml` or `*.override.yml` is **ignored** — put the `${VCH_PORT}` port mapping in the *main* compose file, not in an override. If several matching compose files exist, choose one explicitly with `compose: <path>` in `.vch/deploy.yaml`.
|
||||
- **The whole compose starts.** VCH brings up the entire compose project, so DB/cache/auth services your compose defines itself (Postgres, Redis, etc.) start automatically — you do **not** also need a separate managed database for them. (Services behind a compose `profiles:` key stay off unless you activate the profile.)
|
||||
- If you omit `.vch/deploy.yaml`, VCH falls back to the Dockerfile `EXPOSE` port (or `3000`).
|
||||
@@ -0,0 +1,37 @@
|
||||
# ocr_scanner
|
||||
|
||||
> Short description of what this project does.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 20+
|
||||
- npm or pnpm
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- ...
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
...
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
...
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,253 @@
|
||||
# RAM-Sortierhilfe — Design
|
||||
|
||||
**Datum:** 2026-07-28
|
||||
**Status:** Freigegeben, bereit für Umsetzungsplan
|
||||
|
||||
## Zweck
|
||||
|
||||
Eine Sortierhilfe für gebrauchte Server-RAM-Module. Beim Wareneingang werden Module
|
||||
einzeln gescannt; die App sagt sofort, auf welchen Stapel das Modul gehört. Ziel ist
|
||||
das physische Sortieren am Tisch — aus einer gemischten Charge entstehen Stapel
|
||||
identischer Module, die als Kit verkauft werden können.
|
||||
|
||||
Die App ist eine reine Sortierhilfe. Nach der Sitzung werden die Daten nicht weiter
|
||||
gebraucht.
|
||||
|
||||
## Ausgangslage
|
||||
|
||||
Der vorhandene Netum 2D Tischscanner liest ausschließlich Barcodes und gibt dekodierte
|
||||
Strings als Tastatureingabe aus. Er stellt kein Rohbild bereit, kann also kein OCR
|
||||
leisten. Klartext auf Etiketten muss über eine Kamera erfasst werden.
|
||||
|
||||
Die Ware ist gemischt: teils Hersteller-Etiketten mit lesbarem Barcode (Samsung, Hynix,
|
||||
Micron), teils OEM-überklebt (Oracle, HP, Dell, Lenovo), wobei die Hersteller-PN dann
|
||||
verdeckt sein kann.
|
||||
|
||||
## Nicht im Umfang
|
||||
|
||||
- Bestandsführung über Sitzungen hinweg
|
||||
- Ein- und Ausbuchen, Verkaufsabwicklung
|
||||
- Export nach CSV, Excel oder in ein Warenwirtschaftssystem
|
||||
- Anbindung des Netum-Scanners an die App (er bleibt für andere Abläufe im Einsatz)
|
||||
- Andere Komponenten als RAM-Module
|
||||
- Mehrbenutzerbetrieb, Benutzerkonten, Server
|
||||
|
||||
## Hardware und Zugang
|
||||
|
||||
- **Kamera:** Handykamera. Die App wird im Handy-Browser geöffnet.
|
||||
- **HTTPS:** Kamerazugriff verlangt eine sichere Verbindung. Die VCH-Preview-URL
|
||||
liefert SSL.
|
||||
- **Netz:** Erkennung und Sortierung laufen vollständig lokal im Browser; während einer
|
||||
laufenden Sitzung wird keine Netzverbindung gebraucht. Zum Laden der Seite selbst
|
||||
(und beim ersten Start zusätzlich für die Tesseract-Sprachdaten) ist eine Verbindung
|
||||
nötig. Echte Offline-Fähigkeit über einen Service Worker ist nicht im Umfang.
|
||||
|
||||
## Erkennungsstrategie
|
||||
|
||||
Gewählt wurde lokale Erkennung ohne Cloud-Dienst. Die bekannten Schwächen von Tesseract
|
||||
auf glänzenden Metall-Etiketten werden durch drei Maßnahmen aufgefangen:
|
||||
|
||||
1. **Barcode zuerst.** Code-128 und DataMatrix werden aus dem Kamerabild dekodiert.
|
||||
Barcode-Dekodierung ist exakt, im Gegensatz zu OCR. Aus der Hersteller-PN leitet ein
|
||||
Decoder die Specs ab. Gelingt das, entfällt OCR vollständig.
|
||||
2. **Kleiner Erwartungsraum.** RAM-Specs sind kein Freitext. Kapazität, Speed und Rank
|
||||
stammen aus kurzen, bekannten Wertelisten. Das OCR-Ergebnis wird dagegen abgeglichen;
|
||||
typische Verwechslungen (0/O, 1/I, 8/B) lassen sich dadurch eindeutig auflösen.
|
||||
3. **Toleranter Stapel-Abgleich.** Ein neuer Scan wird gegen die bereits offenen Stapel
|
||||
geprüft. Weicht ein Fingerabdruck nur in einer typischen Verwechslung ab, gilt er als
|
||||
derselbe Stapel und nicht als neuer.
|
||||
|
||||
## Ablauf pro Modul
|
||||
|
||||
1. **Kamerabild** — Live-Vorschau, Etikett ins Bild halten.
|
||||
2. **Barcode-Versuch** — laufende Suche nach Code-128 und DataMatrix.
|
||||
3. **PN-Decoder** — Hersteller-PN wird in Spec-Felder zerlegt. Erfolg beendet die
|
||||
Erkennung.
|
||||
4. **OCR-Fallback** — nur wenn kein Barcode lesbar oder die PN unbekannt ist.
|
||||
Bildaufbereitung (Graustufen, Kontrast, Schwellwert), Texterkennung, Abgleich der
|
||||
Felder gegen die bekannten Werte.
|
||||
5. **Bewertung** — grün bei Barcode, gelb bei eindeutig zugeordnetem OCR-Ergebnis, rot
|
||||
bei Unsicherheit.
|
||||
6. **Stapel-Zuweisung** — Ergebnis und Stapelbuchstabe werden groß angezeigt.
|
||||
|
||||
Grün und Gelb laufen ohne Eingabe durch. Nur Rot hält den Ablauf an.
|
||||
|
||||
## Fingerabdruck
|
||||
|
||||
Ein Modul wird auf folgende Merkmale reduziert:
|
||||
|
||||
| Merkmal | Beispiel | Quelle |
|
||||
|---|---|---|
|
||||
| Kapazität | 64 GB | PN-Decoder oder OCR |
|
||||
| Bauform | LRDIMM | PN-Decoder |
|
||||
| Organisation/Rank | 4DRx4 | PN-Decoder oder OCR |
|
||||
| Speed | PC4-2400 | PN-Decoder oder OCR |
|
||||
| Hersteller-PN | M386A8K40BM1-CRC4Y | Barcode |
|
||||
|
||||
**Gruppierungsstufe:** Elektrische Spec **und** Hersteller-PN müssen übereinstimmen.
|
||||
Module mit gleicher Spec, aber unterschiedlichem Hersteller bilden getrennte Stapel.
|
||||
Diese Stufe entspricht dem, was im Gebrauchthandel als "matched kit" gilt.
|
||||
|
||||
Der Datumscode wird erfasst und angezeigt, ist aber **nicht** Teil des Fingerabdrucks.
|
||||
|
||||
## Stapel-Logik
|
||||
|
||||
- Die Sitzung startet mit leerem Tisch, ohne vorab definierte Fächer.
|
||||
- Erster Scan erzeugt Stapel A, der nächste unbekannte Fingerabdruck Stapel B und so
|
||||
weiter.
|
||||
- Vor dem Anlegen eines neuen Stapels wird geprüft, ob ein bestehender Stapel bis auf
|
||||
typische OCR-Verwechslungen identisch ist. Trifft das zu, wird dorthin einsortiert.
|
||||
- Die Stapelbuchstaben gelten nur für die laufende Sitzung.
|
||||
|
||||
## Rot-Kriterien
|
||||
|
||||
Die App fragt nach, wenn:
|
||||
|
||||
- kein Barcode lesbar war und OCR keine bekannte Spec-Kombination ergeben hat
|
||||
- das OCR-Ergebnis auf zwei bestehende Stapel gleich gut passt
|
||||
- die Kapazität fehlt
|
||||
|
||||
Im Rot-Fall zeigt die App die in Frage kommenden Stapel als große, antippbare Flächen,
|
||||
zusätzlich "neuer Stapel". Ein Tipp setzt den Ablauf fort.
|
||||
|
||||
## Korrekturweg
|
||||
|
||||
Da grün und gelb ohne Bestätigung durchlaufen, braucht es einen Rückweg:
|
||||
|
||||
- Der zuletzt erfasste Eintrag bleibt am unteren Bildschirmrand sichtbar, mit
|
||||
Rückgängig-Fläche.
|
||||
- Eine Sitzungsliste zeigt alle Einträge der laufenden Sitzung; jeder lässt sich einem
|
||||
anderen Stapel zuordnen oder entfernen.
|
||||
|
||||
## Oberfläche
|
||||
|
||||
Mobil zuerst, große Flächen, im Stehen und mit einer Hand bedienbar.
|
||||
|
||||
**Scan-Ansicht** (Hauptansicht):
|
||||
|
||||
```
|
||||
┌──────────────────────────┐
|
||||
│ │
|
||||
│ Kamera-Vorschau │
|
||||
│ ┌────────────────┐ │
|
||||
│ │ Zielrahmen │ │
|
||||
│ └────────────────┘ │
|
||||
│ │
|
||||
├──────────────────────────┤
|
||||
│ A:12 B:4 C:7 │ Stapel-Leiste, laufende Zählung
|
||||
├──────────────────────────┤
|
||||
│ Zuletzt: 64GB PC4-2400 │
|
||||
│ LRDIMM → Stapel B [↶] │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
**Treffer-Rückmeldung** (kurz eingeblendet, dann automatisch weiter):
|
||||
|
||||
```
|
||||
┌──────────────────────────┐
|
||||
│ │
|
||||
│ ► STAPEL B ◄ │ sehr groß, farbig hinterlegt
|
||||
│ │
|
||||
│ 64GB 4DRx4 PC4-2400 │
|
||||
│ LRDIMM │
|
||||
│ M386A8K40BM1-CRC4Y │
|
||||
│ │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
Hintergrundfarbe grün bei Barcode-Erkennung, gelb bei OCR.
|
||||
|
||||
**Rot-Dialog** (wartet auf Eingabe):
|
||||
|
||||
```
|
||||
┌──────────────────────────┐
|
||||
│ Nicht eindeutig │
|
||||
│ Gelesen: 64GB PC4-24?? │
|
||||
├──────────────────────────┤
|
||||
│ ┌────────┐ ┌────────┐ │
|
||||
│ │Stapel B│ │Stapel D│ │ große Flächen
|
||||
│ └────────┘ └────────┘ │
|
||||
│ ┌────────────────────┐ │
|
||||
│ │ neuer Stapel │ │
|
||||
│ └────────────────────┘ │
|
||||
│ ┌────────────────────┐ │
|
||||
│ │ nochmal scannen │ │
|
||||
│ └────────────────────┘ │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
**Sitzungsliste:** erreichbar über die Stapel-Leiste. Zeigt Stapel mit ihren Modulen,
|
||||
erlaubt Umsortieren und Entfernen einzelner Einträge sowie "Sitzung beenden".
|
||||
|
||||
Die konkrete Farb- und Typografiegestaltung wird während der Umsetzung festgelegt.
|
||||
|
||||
## Technik
|
||||
|
||||
- **Vanilla JavaScript mit Vite.** Kein Framework — der Funktionsumfang rechtfertigt
|
||||
keins, und die Ladezeit auf dem Handy bleibt niedrig. `base: './'` in der
|
||||
Vite-Konfiguration, damit die App hinter dem VCH-Reverse-Proxy funktioniert.
|
||||
- **`zxing-wasm`** für Code-128 und DataMatrix.
|
||||
- **`tesseract.js`** für OCR.
|
||||
- **Node-eigener Test-Runner** (`node:test`) — keine zusätzliche Test-Abhängigkeit.
|
||||
|
||||
Weitere Abhängigkeiten werden bewusst vermieden.
|
||||
|
||||
## Module
|
||||
|
||||
Jedes Modul hat eine Aufgabe und kennt die Interna der anderen nicht.
|
||||
|
||||
| Modul | Aufgabe | Abhängigkeiten |
|
||||
|---|---|---|
|
||||
| `camera` | Kamerastrom, Einzelbilder liefern | Browser-API |
|
||||
| `barcode` | Bild → dekodierte Codes | `zxing-wasm` |
|
||||
| `pn-decoder` | Hersteller-PN → Spec-Felder | keine |
|
||||
| `ocr` | Bildaufbereitung, Texterkennung, Feldextraktion | `tesseract.js`, `spec` |
|
||||
| `spec` | Normalisierung, Fingerabdruck, toleranter Vergleich | keine |
|
||||
| `session` | Stapel halten, zuweisen, rückgängig machen | `spec` |
|
||||
| `ui` | Ansichten und Eingaben | alle |
|
||||
|
||||
`pn-decoder` und `spec` sind reine Funktionen ohne Kamera- und DOM-Zugriff und damit
|
||||
vollständig testbar. Sie bilden den fachlichen Kern.
|
||||
|
||||
### PN-Decoder
|
||||
|
||||
Tabellengesteuert, ein Eintrag pro Herstellerschema. Die Zuordnung von Codefragmenten zu
|
||||
Kapazität, Bauform und Speed wird gegen reale Module validiert und nicht aus dem
|
||||
Gedächtnis festgelegt. Unbekannte Schemata sind kein Fehler, sondern führen zum
|
||||
OCR-Weg. Die Tabelle ist so aufgebaut, dass neue Hersteller ohne Codeänderung ergänzt
|
||||
werden können.
|
||||
|
||||
## Fehlerfälle
|
||||
|
||||
| Fall | Verhalten |
|
||||
|---|---|
|
||||
| Kamera verweigert oder nicht verfügbar | Hinweis, dazu Datei-Auswahl als Ersatzweg (App bleibt am Rechner nutzbar) |
|
||||
| Tesseract lädt nicht | App bleibt voll nutzbar, aber barcode-only; sichtbarer Hinweis |
|
||||
| PN unbekannt | Kein Fehler — regulärer Übergang zum OCR-Weg |
|
||||
| Barcode unlesbar und OCR unbrauchbar | Rot-Dialog |
|
||||
| Versehentlicher Seiten-Neuladen | Stapel werden bei jeder Änderung lokal gesichert und beim Start zur Fortsetzung angeboten. Absturzschutz, keine Bestandsführung. "Sitzung beenden" räumt auf. |
|
||||
|
||||
## Tests
|
||||
|
||||
Mit `node:test`:
|
||||
|
||||
- **`pn-decoder`** gegen reale Teilenummern mehrerer Hersteller, einschließlich
|
||||
`M386A8K40BM1-CRC4Y` (Samsung, 64GB LRDIMM PC4-2400)
|
||||
- **`spec`** — Normalisierung und toleranter Vergleich, gezielt mit den typischen
|
||||
Verwechslungen 0/O, 1/I, 8/B
|
||||
- **`session`** — Szenariotests: eine Folge von Scans hinein, die erwartete
|
||||
Stapelverteilung heraus, inklusive Rückgängig
|
||||
- **OCR-Korrektur** — verrauschte Texteingaben gegen die Liste bekannter Spec-Werte
|
||||
|
||||
Kamera und Oberfläche werden manuell geprüft; dafür lohnt keine Testautomatisierung.
|
||||
|
||||
## Referenzmaterial
|
||||
|
||||
Zwei Beispielaufnahmen liegen der Gestaltung zugrunde:
|
||||
|
||||
- Oracle-überklebtes Modul, Etikett `Oracle ®PN: 7325773` mit Code-128
|
||||
- Rückseite desselben Moduls, Samsung-Etikett mit Serial `K136000908252DF287`,
|
||||
`64GB 4DRx4 PC4-2400T-LD1-11-MC0`, PN `M386A8K40BM1-CRC4Y`, Datumscode `1908`,
|
||||
dazu DataMatrix und Code-128
|
||||
|
||||
Diese beiden dienen als erste Testfälle für den PN-Decoder und die OCR-Feldextraktion.
|
||||
Reference in New Issue
Block a user