Ka-Note/docs/feature-inventory.md

219 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Feature: Inventar
## Zweck
Hausinventar erfassen und verwalten: Gegenstände per Kamera oder Barcode aufnehmen,
KI erkennt Titel + Kategorie automatisch, Zuordnung zu Räumen und Familienmitgliedern.
Gesamtwert als Grundlage für Versicherungssumme.
---
## Räume
Räume werden in drei Gruppen eingeteilt (hardcodiert, nicht erweiterbar):
| `groupType` | Label | Beispiel-Default-Räume |
|---|---|---|
| `living` | Wohnbereiche | Wohnzimmer, Schlafzimmer, Kinderzimmer, Esszimmer, Flur |
| `functional` | Funktionsräume | Küche, Bad, WC, Arbeitszimmer, Hauswirtschaft |
| `outdoor` | Außen & Nebenbereiche | Keller, Dachboden, Garage, Garten, Abstellraum |
Default-Räume werden beim ersten Aufruf von `/inventory` automatisch angelegt
(`seedDefaultRoomsIfNeeded()`, Flag `'inventory.seeded'` in `settings`-Tabelle).
Räume sind löschbar (Soft-Delete). Jeder Raum hat ein `icon`-Feld (lucide icon name).
---
## Asset-Felder
| Feld | Typ | Beschreibung |
|---|---|---|
| `title` | string | Name des Gegenstands (KI-Vorschlag oder manuell) |
| `roomId` | string\|null | Zugeordneter Raum (null = ohne Raum) |
| `category` | string\|null | Aus hardcodierter Liste (s.u.) oder null |
| `status` | `draft`\|`complete` | Draft = Schnellerfassung, noch unvollständig |
| `condition` | `new`\|`good`\|`fair`\|`poor`\|null | Zustand |
| `brand` | string\|null | Marke |
| `model` | string\|null | Modell |
| `serialNumber` | string\|null | Seriennummer |
| `purchasePrice` | real\|null | Kaufpreis (€), manuell oder via KI-Schätzung übernommen |
| `purchaseYear` | integer\|null | Anschaffungsjahr |
| `notes` | string\|null | Freies Markdown-Notizfeld |
| `coverImageId` | string\|null | Primärbild (→ imageBlobs.id, Server-seitig) |
| `lastPriceEstimate` | real\|null | Letzter geschätzter Marktwert (€) |
| `lastPriceEstimateAt` | string\|null | ISO-Zeitstempel der letzten Schätzung |
| `lastPriceEstimateSource` | `'ebay'`\|`'formula'`\|`'category'`\|null | Quelle der Schätzung |
---
## Kategorien (hardcoded)
`null` (Keine Kategorie), Elektronik, Möbel, Kleidung, Schmuck, Kunstwerke,
Sportgeräte, Haushaltsgeräte, Haustechnik, Werkzeuge, Musikinstrumente,
Bücher & Medien, Spielzeug, Sammlerstücke, Gartengeräte, Fahrzeugzubehör, Sonstiges
Kein eigenes DB-Table — als TypeScript-Konstante in den jeweiligen Komponenten/Seiten.
---
## Erfassungs-Modi
### Foto-Modus (Schnellerfassung)
1. Raum wählen (optional via `?roomId=` Query-Parameter)
2. Loop: Foto aufnehmen → KI-Erkennung → Label + Kategorie bestätigen → Asset(draft) anlegen
3. [Fertig] → Dashboard
Route: `/inventory/capture/photo?roomId=...`
KI: `POST /api/vision/recognize` (OpenAI gpt-4o-mini oder Gemini)
Input: `accept="image/jpeg,image/png,image/webp"` — kein HEIC (Canvas kann HEIC nicht dekodieren)
### Barcode-Modus (Schnellerfassung)
1. Raum wählen (optional via `?roomId=` Query-Parameter)
2. Loop: EAN manuell eingeben oder via physischem Scanner → UPCitemdb-Lookup → bestätigen → Asset(draft)
3. [Fertig] → Dashboard
Route: `/inventory/capture/barcode?roomId=...`
Lookup: `POST /api/vision/barcode` → UPCitemdb → Open Food Facts → EAN als Fallback-Label
Rate-Limit gilt **nicht** für Barcode-Lookups (nutzt UPCitemdb, nicht die Vision-API des Users).
### Manuell (Einzel)
Titel eingeben → Asset(draft) → Detailseite
Auch via CommandBar: `/asset Titel`
---
## KI-Preisschätzung (enrichAsset)
Auf der Detailseite: Button "Preis schätzen" → `POST /api/vision/enrich`
Die KI schätzt auf Basis der vorhandenen Felder:
- `title` (Pflicht)
- `brand`, `model` (optional, verbessern Genauigkeit)
**Response:**
```json
{
"brand": "Sony",
"model": "WH-1000XM5",
"estimatedNewPrice": 350,
"estimatedUsedPrice": 180
}
```
Der User kann Marke/Modell und Preise einzeln übernehmen. `purchasePrice` wird auf den
übernommenen Wert gesetzt. `lastPriceEstimate` / `lastPriceEstimateAt` / `lastPriceEstimateSource`
werden beim Speichern der Schätzung aktualisiert.
Rate-Limit: zählt gegen die 100 Vision-Calls/Tag des Users (gleicher Zähler wie Foto-Erkennung).
---
## Bilder-Architektur
**Inventory-Fotos werden NICHT lokal gecacht** (Grund: 500 Assets × ~300 KB = 150 MB).
Statt `storeImage()` (schreibt in Dexie): `uploadInventoryImage(blob)`:
1. Client komprimiert: JPEG, max-width 1920px, quality 0.75 → ~300 KB
2. `POST /api/inventory/images` → Server speichert in `imageBlobs`-Tabelle
3. Client erhält `imageId` zurück, speichert nur die ID im Asset
4. Anzeige: `getImageUrl(id)` fetcht live via `/api/sync/blob/{id}`
`storeImage()` aus `imageStore.ts` wird weiterhin für Editor-Paste-Bilder verwendet — **nicht** für Inventory.
### Cover-Konsistenz bei Image-Löschung
`softDeleteAssetImage` prüft ob `asset.coverImageId === assetImage.imageId`. Wenn ja:
`updateAsset({ coverImageId: nächstesAssetImage?.imageId ?? null })`.
---
## Personen-Zuordnung
Assets können Familienmitgliedern zugeordnet werden (`assetPersons`-Tabelle, n:m).
Auswählbar sind nur Personen mit `contexts.type='person'` + `meta.personSubType='family'`.
Anzeige auf Detailseite als Chips (`PersonMeta.abbreviation` oder `fullName`).
---
## Vision-API-Key
Kein globaler API-Key — jeder User hinterlegt seinen eigenen Key in den Einstellungen
(Abschnitt "Bild-Erkennung"). Keys werden AES-256-GCM verschlüsselt in `user_settings`
gespeichert. Server-seitiges Verschlüsselungs-Secret: `SETTINGS_ENCRYPTION_KEY` in `.env`.
Rate-Limit: 100 Vision-Calls/Tag pro User (`vision_usage`-Tabelle). Gilt für:
- `POST /api/vision/recognize` (Foto-Erkennung)
- `POST /api/vision/enrich` (Preisschätzung)
Gilt **nicht** für `POST /api/vision/barcode` (UPCitemdb, kein User-API-Key).
---
## Routen-Übersicht
| Route | Beschreibung |
|---|---|
| `/inventory` | Dashboard: Stats, Gesamtwert, Quick-Actions |
| `/inventory/rooms` | Räume-Grid (3-spaltig, gruppiert) |
| `/inventory/rooms/[id]` | Asset-Liste eines Raums |
| `/inventory/items` | Alle Assets (Suche, Filter, Grid/Liste) |
| `/inventory/capture/photo` | Foto-Schnellerfassungs-Loop |
| `/inventory/capture/barcode` | Barcode-Schnellerfassungs-Loop |
| `/inventory/[id]` | Asset-Detailseite |
---
## CommandBar-Integration
- Suche: Assets erscheinen in Ergebnissen (Badge `INVENTAR`, Icon 📦)
- `/inventar` → Dashboard
- `/asset [Titel]` → Asset anlegen (roomId=null) → Detailseite
---
## Server-Endpoints (Inventory-spezifisch)
```
POST /api/inventory/images Bild hochladen → { id }
POST /api/vision/recognize Foto → { label, category, candidates, remaining }
POST /api/vision/enrich { label, brand?, model? } → { brand, model, estimatedNewPrice, estimatedUsedPrice }
POST /api/vision/barcode { ean } → { label, category? } (kein Rate-Limit)
GET /api/vision/settings { provider, apiKeySet, dailyUsage, dailyLimit }
PUT /api/vision/settings { provider?, apiKey? }
```
Sync läuft über den bestehenden `/api/sync/push` + `/api/sync/pull` Endpunkt.
Neue Tabellen im Sync: `rooms`, `assets`, `assetImages`, `assetPersons`.
---
## Wichtige Implementierungshinweise
### Bild-Upload vs. storeImage()
`storeImage()` und `uploadInventoryImage()` sind NICHT austauschbar:
- `storeImage()` → Dexie lokal + Sync (für Editor-Bilder)
- `uploadInventoryImage()` → direkt Server, kein Dexie (für Inventory-Fotos)
### Draft vs. Complete
Assets beginnen immer als `draft`. Status `complete` muss manuell gesetzt werden.
Dashboard zeigt Gesamtzahl unabhängig vom Status.
`/inventory/items` filtert nach Status (Dropdown "Alle Status").
### Raum-Seed
`seedDefaultRoomsIfNeeded()` prüft `db.settings.get('inventory.seeded')`.
Wird nur einmal ausgeführt. Danach kann der User Räume löschen/umbenennen ohne Re-Seed.
### Barcode-Erkennung
Kein client-seitiger Barcode-Scanner (BarcodeDetector API unterstützt kein iOS/Safari).
EAN manuell eingeben oder physischen Scanner verwenden → `POST /api/vision/barcode`.
---
## Bekannte Einschränkungen
- Kamera-Erfassung erfordert Online-Verbindung (kein offline capture)
- Barcode-Lookup max. 100/Tag server-weit (UPCitemdb free tier)
- Räume können nicht per Drag-Drop sortiert werden (`sortOrder` vorhanden, aber kein UI)
- Asset-Export (PDF/CSV) nicht implementiert (geplant als spätere Phase)
- BarcodeDetector API (live Kamera-Scan) nicht implementiert — iOS unterstützt es nicht