import UIKit enum CardRecognitionPipelineError: LocalizedError { case ocrUnavailable(String) var errorDescription: String? { switch self { case .ocrUnavailable(let message): return message } } } struct CardRecognitionPipeline { let preprocessor: ImagePreprocessor let networkStatusProvider: any NetworkStatusProviding let cloudOCRClient: any CloudOCRClient let fallbackOCR: any CardTextRecognizing let extractor: CardTextHeuristicExtractor let enhancer: any CardFieldEnhancing init( preprocessor: ImagePreprocessor = ImagePreprocessor(), networkStatusProvider: any NetworkStatusProviding, cloudOCRClient: any CloudOCRClient = StubCloudOCRClient(), fallbackOCR: any CardTextRecognizing = VisionCardOCRService(), extractor: CardTextHeuristicExtractor = CardTextHeuristicExtractor(), enhancer: any CardFieldEnhancing = NoOpCardFieldEnhancer() ) { self.preprocessor = preprocessor self.networkStatusProvider = networkStatusProvider self.cloudOCRClient = cloudOCRClient self.fallbackOCR = fallbackOCR self.extractor = extractor self.enhancer = enhancer } func recognizeCard(in image: UIImage) async throws -> RecognitionSession { let preparedImage = try preprocessor.prepare(image) if networkStatusProvider.isOnline, let cloudResponse = try? await cloudOCRClient.recognizeText(from: CloudOCRRequest(jpegData: preparedImage.uploadJPEGData)), !cloudResponse.markdown.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { let cloudLines = cloudResponse.markdown .split(whereSeparator: \.isNewline) .map { RecognizedTextLine(text: String($0), confidence: 0.95, normalizedBounds: .zero) } let payload = OCRTextPayload(rawText: cloudResponse.markdown, lines: cloudLines, averageConfidence: 0.95) var draft = extractor.extract(payload: payload, source: .cloud) draft = await enhancer.enhance(draft: draft, rawText: payload.rawText) return RecognitionSession(draft: draft, thumbnailJPEGData: preparedImage.thumbnailJPEGData) } let note = networkStatusProvider.isOnline ? "Cloud OCR is stubbed in this MVP, so StackDex used the local Vision pipeline." : "Offline erkannt — Ergebnis kann weniger genau sein." let source: RecognitionSource = networkStatusProvider.isOnline ? .onDeviceFallback : .onDeviceOffline let payload = try await fallbackOCR.recognizeText(in: preparedImage) var draft = extractor.extract(payload: payload, source: source, notes: [note]) draft = await enhancer.enhance(draft: draft, rawText: payload.rawText) return RecognitionSession(draft: draft, thumbnailJPEGData: preparedImage.thumbnailJPEGData) } }