# 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 ~$1–2 pro 1.000 Seiten/Bilder. Bei typischem Hobbynutzer-Volumen (10–50 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`: ```swift 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: 1–3 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 30–50 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.