/// Receipt parsing entry point: spatial OCR pipeline with text fallback. library; import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart'; import '../models/line_item.dart'; import '../models/ocr_element.dart'; import 'receipt_ocr.dart'; import 'receipt_parse_config.dart'; export 'receipt_parse_config.dart' show loadReceiptParseConfig; export 'receipt_text_parser.dart' show ReceiptParseResult; import 'receipt_spatial_parser.dart'; import 'receipt_text_parser.dart'; class ReceiptParser { ReceiptParser._(); static Future loadConfig() => loadReceiptParseConfig(); static List parse(String rawText) => parseReceipt(rawText).items; /// Fallback when only flattened OCR text is available (tests, re-parse). static ReceiptParseResult parseReceipt(String rawText) => ReceiptTextParser.parseReceipt(rawText); /// Primary path: positioned ML Kit elements → row clustering → lookahead. static ReceiptParseResult parseReceiptFromOcr(OcrDocument document) => _parseDocument(document); static ReceiptParseResult parseRecognizedText( RecognizedText recognized, { required double imageWidth, required double imageHeight, }) { final document = ReceiptOcr.fromRecognizedText( recognized, imageWidth: imageWidth, imageHeight: imageHeight, ); return _parseDocument(document); } static ReceiptParseResult _parseDocument(OcrDocument document) { if (ReceiptTextParser.looksLikeColumnOcr(document.flattenedText)) { return _withImageDimensions( ReceiptTextParser.parseReceipt(document.flattenedText), document, ); } final spatial = ReceiptSpatialParser.parse(document); if (_shouldFallbackToText(spatial)) { return _withImageDimensions( ReceiptTextParser.parseReceipt(document.flattenedText), document, ); } return spatial; } static bool _shouldFallbackToText(ReceiptParseResult spatial) { if (spatial.items.isEmpty) return true; final flagged = spatial.items.where((item) => item.needsManualReview).length; if (flagged > 0) return true; if (spatial.items.any((item) => item.price <= 0)) return true; if (spatial.receiptTotal != null && !spatial.validationPassed) return true; return false; } static ReceiptParseResult _withImageDimensions( ReceiptParseResult result, OcrDocument document, ) { return ReceiptParseResult( items: result.items, store: result.store, receiptTotal: result.receiptTotal, totalDiscount: result.totalDiscount, validationPassed: result.validationPassed, expectedItemCount: result.expectedItemCount, ocrImageWidth: document.imageWidth, ocrImageHeight: document.imageHeight, ); } }