Files
receipity/lib/services/receipt_parser.dart
T
2026-09-02 13:38:47 +02:00

87 lines
2.7 KiB
Dart

/// 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<void> loadConfig() => loadReceiptParseConfig();
static List<LineItem> 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,
);
}
}