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.
16 KiB
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
PHPhotoLibrarymit 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
- App prüft Konnektivität.
- Bild wird per
POSTan einen Convex HTTP Action Endpoint gesendet. - Der HTTP Action ruft die Mistral OCR API auf (
mistral-ocr-latestodermistral-ocr-2512). - Mistral gibt strukturiertes Markdown mit dem gesamten erkannten Kartentext zurück.
- 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
URLSessionoder den Convex Swift Client (ConvexMobile) viaaction()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:
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 AufrufSystemLanguageModel.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:
.accurateals Default. - Sprachen: Konfigurierbar — mindestens
enundde. - 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/NNNim 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
NavigationStackgepusht. - 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:
- Mistral-Pfad (online): Kartenname in ≥ 90 %, Nummer/Set in ≥ 85 %, Seltenheit in ≥ 80 % korrekt erkannt.
- On-Device-Pfad (offline): Kartenname in ≥ 75 %, Nummer/Set in ≥ 70 %, Seltenheit in ≥ 65 % korrekt erkannt.
- Bei schwacher Confidence wird nie ein falsches Ergebnis ohne Hinweis als sicher dargestellt.
- Der manuelle Korrektur-/Eingabeweg ist jederzeit erreichbar.
- 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).
- Kamera- und Foto-Berechtigungen werden korrekt behandelt, inklusive
.limitedund.deniedRecovery. - Bilder werden nach Analyse zuverlässig verworfen.
- Convex HTTP Action Endpoint ist deployt und erreichbar; API-Key ist serverseitig geschützt.
- 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,@Guidemit.anyOf()und.pattern()existieren wie im PRD beschrieben. Availability-Check über.availabilityProperty mit.available,.unavailable(.deviceNotEligible | .appleIntelligenceNotEnabled | .modelNotReady). Sprachunterstützung prüfbar viasupportsLocale(). GenerationGuide.pattern(_:)— Die Methode heißt.pattern(), nicht.regex(). PRD-Code-Beispiel wurde korrigiert.VNRecognizeTextRequest— Seit iOS 13 verfügbar, nicht deprecated. PropertiescustomWords,recognitionLanguages,usesLanguageCorrection,recognitionLevelbestätigt.AVCaptureDevice.RotationCoordinator— Seit iOS 17 verfügbar, kompatibel mit iOS 26 Target.- Convex Swift Client (
ConvexMobile) — Unterstütztmutation()undaction()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 +POSTan 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.