Files
stackdex_neu/.docs/prd.md
Matthias a60a76b797 Add scan flow MVP and local Axiom skill workspace
This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
2026-04-19 21:11:32 +02:00

16 KiB
Raw Blame History

StackDex — PRD: Kartenerkennung MVP

Scope: Zuverlässige Erkennung von Name, Nummer/Set und Seltenheit einer physischen Pokémon-Karte. Primärpfad über Mistral OCR (via Convex), mit On-Device-Fallback für Offline-Situationen und Foundation-Models-Enhancement auf kompatiblen Geräten.


1 Ziel

Einen durchgängigen Erkennungsfluss bauen, der aus einem Foto einer Pokémon-Karte drei Kerndaten extrahiert und dem Nutzer zur Bestätigung anzeigt:

Datum Beispiel
Kartenname Glurak
Nummer / Set 4/102 · Base Set
Seltenheit Holo Rare (★)

Differenzierung: Kostenlose Konkurrenten setzen typischerweise auf reine On-Device-OCR, die bei holografischen Oberflächen, schlechter Beleuchtung und älteren Sets regelmäßig versagt. StackDex setzt bewusst auf Cloud-OCR als Primärpfad für signifikant bessere Erkennungsraten.


2 Eingabequellen

Beide Quellen werden gemeinsam ausgeliefert (Entscheidung aus Planung: Slice B).

2.1 Kamera

  • Eigener Kamerascreen auf Basis von AVCaptureSession + AVCaptureDevice.RotationCoordinator (iOS 17+).
  • Der Nutzer rahmt die Karte und löst die Aufnahme explizit per Shutter-Button aus.
  • Kein Live-Erkennungsmodus; Analyse startet erst nach Capture.

2.2 Fotobibliothek

  • Kompakter Recent-Images-Strip direkt im Scan-Screen unterhalb des Kamerabilds.
  • Nutzt PHPhotoLibrary mit vollständiger Autorisierungsbehandlung:
    • .authorized → Zugriff auf Recents.
    • .limited → eingeschränkter Zugriff + presentLimitedLibraryPicker(from:) als Recovery.
    • .denied / .restricted → In-Context-Hinweis mit „Einstellungen öffnen" + Fallback auf Kamera oder manuelle Eingabe.
  • Berechtigungen werden just-in-time angefragt, nicht beim App-Start.

2.3 Bildlebenszyklus

Aufgenommene/importierte Bilder werden nur temporär für die Analyse verwendet und danach verworfen — kein dauerhaftes Speichern im MVP.


3 Erkennungs-Pipeline

3.1 Überblick — Dreistufiges Modell

Foto (Capture / Import)
  │
  ▼
Preprocessing (on-device, immer)
  │
  ├── Online? ──► Stufe 1: Mistral OCR via Convex Action
  │                  │
  │                  ▼
  │               Strukturierte Extraktion
  │                  │
  │                  ├── iOS 26 + Foundation Models verfügbar?
  │                  │      ▼
  │                  │   Stufe 1a: Foundation Models Enhancement
  │                  │      │
  │                  │      ▼
  │                  │   Ergebnis-Screen
  │                  │
  │                  └── sonst ──► Regex/Heuristik-Extraktion
  │                                  │
  │                                  ▼
  │                               Ergebnis-Screen
  │
  └── Offline? ──► Stufe 2: On-Device Vision/OCR
                     │
                     ▼
                  Strukturierte Extraktion (Regex/Heuristik + optional Foundation Models)
                     │
                     ▼
                  Ergebnis-Screen (mit Offline-Hinweis)

3.2 Preprocessing (immer, on-device)

Läuft vor jeder Erkennungsstufe, unabhängig vom gewählten Pfad:

  • Default: Vollbild-Analyse ohne manuellen Crop.
  • Bildoptimierung: Automatische Rotation und leichter Perspektivausgleich, wenn sie die Erkennungsqualität verbessern.
  • Bildkompression: Für den Mistral-Upload wird das Bild auf eine sinnvolle Auflösung skaliert (Bandbreite vs. Qualität), z. B. max. 2048px auf der langen Seite.
  • Fallback bei schwacher Confidence: Optionaler Crop-/Refinement-Schritt mit festem Karten-Seitenverhältnis (≈ 2.5:3.5).

3.3 Stufe 1 — Mistral OCR (Primärpfad, online)

Der bevorzugte Erkennungsweg bei bestehender Internetverbindung.

3.3.1 Ablauf

  1. App prüft Konnektivität.
  2. Bild wird per POST an einen Convex HTTP Action Endpoint gesendet.
  3. Der HTTP Action ruft die Mistral OCR API auf (mistral-ocr-latest oder mistral-ocr-2512).
  4. Mistral gibt strukturiertes Markdown mit dem gesamten erkannten Kartentext zurück.
  5. HTTP Action gibt das OCR-Ergebnis als JSON-Response an die App zurück.

3.3.2 Convex als Proxy

Mistral OCR wird nicht direkt vom Gerät aufgerufen, sondern über Convex:

  • HTTP Action statt Client Action: Convex-Docs empfehlen, Actions nicht direkt über den Swift Client aufzurufen (Anti-Pattern). Stattdessen wird ein dedizierter HTTP Action Endpoint exponiert, den die App per URLSession oder den Convex Swift Client (ConvexMobile) via action() anspricht.
  • API-Key bleibt serverseitig und wird nie an den Client ausgeliefert.
  • Ermöglicht serverseitiges Logging, Rate-Limiting und Kostenmonitoring.
  • Vorbereitend für den nächsten Schritt, in dem Convex das OCR-Ergebnis direkt gegen die Pokewallet-Datenbank matchen kann.

3.3.3 Kostenrahmen

Mistral OCR berechnet ~$12 pro 1.000 Seiten/Bilder. Bei typischem Hobbynutzer-Volumen (1050 Scans/Monat) sind die Kosten marginal. Für das MVP ist kein nutzerseitiges Limit nötig, aber serverseitiges Monitoring sollte ungewöhnliche Spikes erkennen.

3.4 Stufe 1a — Foundation Models Enhancement (optional, iOS 26+)

Wenn das Gerät iOS 26+ läuft und SystemLanguageModel.availability == .available:

  • Das Mistral-OCR-Markdown wird lokal an eine LanguageModelSession übergeben.
  • Foundation Models extrahiert die drei Zielfelder typsicher via @Generable:
import FoundationModels

@Generable
struct CardRecognitionResult {
    @Guide(description: "The Pokémon card name including suffixes like ex, V, VMAX, GX")
    let cardName: String

    @Guide(description: "Card number in format like 4/102 or 025/198",
           .pattern(/\d{1,3}\s*\/\s*\d{1,3}/))
    let cardNumber: String

    @Guide(description: "The set name or abbreviation, e.g. Base Set, Scarlet & Violet")
    let setIdentifier: String

    @Guide(description: "Rarity level of the card",
           .anyOf(["Common", "Uncommon", "Rare", "Holo Rare",
                   "Ultra Rare", "Illustration Rare", "Special Art Rare",
                   "Hyper Rare", "Secret Rare"]))
    let rarity: String
}
  • Vorteil: Robuster als Regex bei ungewöhnlichen Layouts, mehrsprachigen Karten oder fragmentiertem OCR-Output.
  • Wenn Foundation Models nicht verfügbar → Fallback auf Regex/Heuristik-Extraktion (siehe 3.6).

3.4.1 Foundation Models Error-Handling

Die LanguageModelSession kann mehrere relevante Fehler werfen, die alle zum Regex/Heuristik-Fallback führen:

  • guardrailViolation — Pokémon-Kartennamen oder -Beschreibungen könnten unbeabsichtigt Guardrails triggern. Bei diesem Fehler: Fallback auf Regex.
  • unsupportedLanguageOrLocale — Vor dem Aufruf SystemLanguageModel.default.supportsLocale() prüfen; Deutsch ist unterstützt, aber nicht alle Locales.
  • rateLimited — On-Device-Modell hat Rate-Limits. Bei Batch-Scans relevant.
  • decodingFailure@Generable-Struct konnte nicht befüllt werden. Fallback auf Regex.

Generell: Jeder Foundation-Models-Fehler wird still abgefangen und führt zum Regex-Pfad — der Nutzer bekommt davon nichts mit.

3.5 Stufe 2 — On-Device Vision/OCR (Offline-Fallback)

Greift nur, wenn keine Internetverbindung besteht.

3.5.1 OCR-Konfiguration

Basiert auf VNRecognizeTextRequest (Vision Framework):

  • Recognition Level: .accurate als Default.
  • Sprachen: Konfigurierbar — mindestens en und de.
  • Language Correction: Aktiviert, aber als Tuning-Punkt (kann für Eigennamen kontraproduktiv sein).
  • Custom Words: Statische Wortliste im Bundle mit häufigen Pokémon-Namen, Set-Abkürzungen und Seltenheits-Begriffen.

3.5.2 Offline-UX

  • Der Ergebnis-Screen zeigt einen dezenten Hinweis: „Offline erkannt — Ergebnis kann weniger genau sein".
  • Kein Blocker — der Nutzer kann das Ergebnis trotzdem bestätigen oder korrigieren.
  • Wenn Foundation Models verfügbar (iOS 26+), wird der Vision-Rohtext zusätzlich durch die @Generable-Extraktion geschickt.

3.6 Regex/Heuristik-Extraktion (Fallback für strukturierte Felder)

Wird verwendet, wenn Foundation Models nicht verfügbar sind — sowohl nach Mistral OCR als auch nach On-Device Vision:

3.6.1 Kartenname

  • Typischerweise der größte/prominenteste Textblock im oberen Kartendrittel.
  • Heuristiken: Position (oberes Drittel), Schriftgröße relativ zum Rest, Abwesenheit von Ziffern.
  • Sonderfälle: Mehrteilige Namen („Dunkles Glurak"), Zusätze wie „ex", „V", „VMAX", „GX" gehören zum Namen.

3.6.2 Nummer / Set

  • Format typischerweise NNN/NNN im unteren Kartenbereich.
  • Ergänzende Set-Kürzel (z. B. „SV", „BS", „EX") in unmittelbarer Nähe der Nummer.
  • Regex-Pattern: \d{1,3}\s*/\s*\d{1,3}, ergänzt um optionale Präfixe/Suffixe.

3.6.3 Seltenheit

  • Primär über das Seltenheitssymbol im unteren rechten Bereich (●, ◆, ★ etc.).
  • Sekundär über erkannten Text: „Common", „Uncommon", „Rare", „Holo Rare", „Ultra Rare" und Varianten.
  • Mapping-Tabelle von Symbol → Seltenheitsstufe, erweiterbar für neuere Seltenheitsklassen.

3.7 Confidence-Modell

Für jedes der drei Felder wird eine interne Confidence berechnet (high / medium / low), basierend auf:

  • Mistral-Pfad: Vollständigkeit des Markdown-Outputs + Extraktionserfolg aller drei Felder.
  • On-Device-Pfad: OCR-eigener Confidence-Wert pro Textblock + strukturelle Plausibilität.
  • Beide Pfade: Vollständigkeit (alle drei Felder gefunden?), Format-Plausibilität (Nummer im erwarteten Schema?).
Gesamt-Confidence Verhalten
High Ergebnis direkt anzeigen, ggf. mit dezenter Bestätigungsaufforderung.
Medium Ergebnis anzeigen mit Hinweis „Treffer prüfen".
Low Crop-/Refinement-Option anbieten + manuelle Korrekturmöglichkeit.

4 Ergebnis-Screen

4.1 Inhalte

  • Kartenname — editierbar.
  • Nummer / Set — editierbar.
  • Seltenheit — editierbar (Auswahl aus bekannten Stufen).
  • Confidence-Hinweis — bei Medium/Low als menschenlesbarer Text, z. B. „Bitte prüfe die erkannten Daten".
  • Erkennungsquelle — dezenter Hinweis, ob Mistral (Cloud) oder On-Device (Offline) verwendet wurde.

4.2 Aktionen

Aktion Beschreibung
Bestätigen In diesem MVP-Schritt: Bestätigung loggen / in temporärem State halten. Persistenz folgt im nächsten Schritt.
Korrigieren Inline-Editing der drei Felder, dann bestätigen.
Erneut scannen Zurück zum Scan-Screen für neuen Versuch.
Manuell eingeben Wechsel in eine Freitext-Eingabe aller drei Felder (dient gleichzeitig als No-Match-Recovery).

4.3 No-Match-Verhalten

Wenn die Pipeline keines der drei Felder mit brauchbarer Confidence extrahieren kann:

  • Nutzer wird in den manuellen Eingabe-Flow geleitet, vorausgefüllt mit OCR-Fragmenten soweit verfügbar.
  • Kein Blocker-Dialog — der Übergang soll sich nahtlos anfühlen.

5 Navigation & UI (nur erkennungsrelevant)

  • Scannen-Tab ist der zentrale Einstieg für diesen Slice.
  • Layout: Kamera-Vollbild oben, Recent-Photos-Strip unten (Entscheidung: Scan-Tab Layout A).
  • Ergebnis-Screen wird per NavigationStack gepusht.
  • Kein Collection-Switcher oder Target-Anzeige in diesem Schritt — kommt mit der Sammlung.
  • Loading-State: Nach Capture/Import zeigt die App einen Erkennungs-Indikator, während der Mistral-Call läuft. Erwartete Latenz: 13 Sekunden.

6 Technische Rahmenbedingungen

Thema Entscheidung
Deployment Target iOS 26 (SwiftUI), um Foundation Models nutzen zu können
Lokale Persistenz SwiftData, in diesem Slice nur für temporäre Scan-Ergebnisse / Dev-Logging
Kamera-API AVCaptureSession + RotationCoordinator
Primäre OCR Mistral OCR API via Convex Action
Fallback-OCR VNRecognizeTextRequest (Vision), greift offline
Strukturierte Extraktion Foundation Models (@Generable) wenn verfügbar, sonst Regex/Heuristik
Foto-Zugriff PHPhotoLibrary (mit Limited-Library-Handling)
Backend Convex — in diesem Slice als OCR-Proxy (HTTP Action Endpoint), vorbereitet für Pokewallet-Matching im nächsten Schritt
Netzwerk Erforderlich für Primärpfad; Offline-Fallback funktionsfähig

7 Qualitätskriterien für den Slice

Der Erkennungs-Slice gilt als abgeschlossen, wenn:

  1. Mistral-Pfad (online): Kartenname in ≥ 90 %, Nummer/Set in ≥ 85 %, Seltenheit in ≥ 80 % korrekt erkannt.
  2. On-Device-Pfad (offline): Kartenname in ≥ 75 %, Nummer/Set in ≥ 70 %, Seltenheit in ≥ 65 % korrekt erkannt.
  3. Bei schwacher Confidence wird nie ein falsches Ergebnis ohne Hinweis als sicher dargestellt.
  4. Der manuelle Korrektur-/Eingabeweg ist jederzeit erreichbar.
  5. Der Wechsel zwischen Online- und Offline-Pfad erfolgt transparent — der Nutzer muss nicht wissen, welcher Pfad aktiv ist (außer dem dezenten Hinweis im Ergebnis).
  6. Kamera- und Foto-Berechtigungen werden korrekt behandelt, inklusive .limited und .denied Recovery.
  7. Bilder werden nach Analyse zuverlässig verworfen.
  8. Convex HTTP Action Endpoint ist deployt und erreichbar; API-Key ist serverseitig geschützt.
  9. Foundation Models Enhancement aktiviert sich automatisch auf kompatiblen Geräten ohne Nutzereingriff.

8 Bewusst ausgeschlossen (nächster Schritt)

  • Pokewallet-API-Integration (Matching erkannter Daten gegen Kartendatenbank).
  • Sammlung / SwiftData-Persistenz der erkannten Karten.
  • Stack-Logik, Duplikat-Handling, Condition-Buckets.
  • Auth, Sync, Multi-Device.
  • Preisanzeige, Bewertung, Collection-Übersicht.
  • Sharing / Export.
  • Nutzerseitige Scan-Limits oder Kostenweitergabe.

9 Offene Punkte innerhalb dieses Slices

# Frage Empfehlung
1 Bildformat für Mistral-Upload: Base64 inline im HTTP Action Body oder Signed URL via Convex File Storage? Signed URL über Convex File Storage — vermeidet große Payloads im HTTP Request Body.
2 Soll die Custom-Words-Liste für Vision-OCR statisch gebündelt oder dynamisch erweiterbar sein? Statisch im Bundle als v1, dynamisch wenn Backend-Metadaten verfügbar sind.
3 Wie wird die Erkennungsqualität systematisch getestet? Testset mit 3050 Fotos verschiedener Karten/Sets/Sprachen, aufgeteilt nach Mistral- und On-Device-Pfad.
4 Soll der Crop-Refinement-Schritt ein freies Rect oder ein festes Karten-Seitenverhältnis verwenden? Festes Seitenverhältnis (≈ 2.5:3.5) mit minimalem Padding.
5 Braucht der Ergebnis-Screen eine Vorschau des gescannten Bilds? Ja — kleines Thumbnail zur Orientierung, wird aber nicht persistiert.
6 Soll Convex die strukturierte Extraktion serverseitig übernehmen (z. B. via Mistral Chat nach OCR)? Vorerst nein — Extraktion bleibt client-seitig (Foundation Models oder Regex). Kann in Stufe 2 auf den Server wandern, wenn Pokewallet-Matching hinzukommt.
7 Timeout/Retry-Strategie für den Mistral-Call? 10s Timeout, 1 automatischer Retry, dann Fallback auf On-Device mit Hinweis.
8 Auswirkung von iOS 26 Deployment Target auf Nutzerbasis? Vertretbar für Neustart ohne Bestandsnutzer. Genaue Gerätekompatibilität für iOS 26 prüfen.

10 Doc-Validierung (Sosumi + Context7)

  • Foundation Models Framework — API-Shape bestätigt: SystemLanguageModel, LanguageModelSession, @Generable, @Guide mit .anyOf() und .pattern() existieren wie im PRD beschrieben. Availability-Check über .availability Property mit .available, .unavailable(.deviceNotEligible | .appleIntelligenceNotEnabled | .modelNotReady). Sprachunterstützung prüfbar via supportsLocale().
  • GenerationGuide.pattern(_:) — Die Methode heißt .pattern(), nicht .regex(). PRD-Code-Beispiel wurde korrigiert.
  • VNRecognizeTextRequest — Seit iOS 13 verfügbar, nicht deprecated. Properties customWords, recognitionLanguages, usesLanguageCorrection, recognitionLevel bestätigt.
  • AVCaptureDevice.RotationCoordinator — Seit iOS 17 verfügbar, kompatibel mit iOS 26 Target.
  • Convex Swift Client (ConvexMobile) — Unterstützt mutation() und action() Calls. Convex-Docs empfehlen jedoch, Actions nicht direkt vom Client aufzurufen (Anti-Pattern). PRD wurde auf HTTP Action Endpoint angepasst.
  • Convex File Storage — Upload via generateUploadUrl() Mutation + POST an Signed URL bestätigt. Passt für Bild-Upload vor OCR.
  • Foundation Models Error-Handling — Relevante Fehlertypen (guardrailViolation, decodingFailure, rateLimited, unsupportedLanguageOrLocale) im PRD als Fallback-Auslöser dokumentiert.