parse static method

ScanResult parse(
  1. String rawText
)

Parses raw text extracted from an invoice or bill image.

Implementation

static ScanResult parse(String rawText) {
  if (rawText.trim().isEmpty) {
    return ScanResult.error(
      ScanMode.ocr,
      'Empty invoice payload. Ensure proper illumination and document framing.',
    );
  }

  final lines = rawText
      .split('\n')
      .map((l) => l.trim())
      .where((l) => l.isNotEmpty)
      .toList();

  String vendorName = lines.isNotEmpty ? lines.first : 'Vendor / Biller';
  String invoiceNumber = 'N/A';
  String invoiceDate = 'N/A';
  String dueDate = 'N/A';
  String totalAmount = 'Not Found';
  String subtotalAmount = 'N/A';
  String taxAmount = 'N/A';
  String taxId = 'N/A';
  List<String> lineItems = [];

  final invNumRegex = RegExp(
    r'(?:INVOICE\s*(?:NO|NUM|#)|BILL\s*(?:NO|NUM|#)|INV\s*#?)\s*[:.]?\s*([A-Z0-9/-]{3,20})',
    caseSensitive: false,
  );
  final dateRegex = RegExp(
    r'\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4}|\d{4}[/-]\d{1,2}[/-]\d{1,2}|\b(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]* \d{1,2},? \d{4})\b',
    caseSensitive: false,
  );
  final taxIdRegex = RegExp(
    r'\b(?:GSTIN|VAT NO|TAX ID|EIN)\s*[:.]?\s*([A-Z0-9]{9,15})\b',
    caseSensitive: false,
  );
  final priceRegex = RegExp(r'([$€£₹]?\s*\d+(?:[.,]\d+)*)');

  for (final line in lines) {
    final upper = line.toUpperCase();

    if (invoiceNumber == 'N/A') {
      final match = invNumRegex.firstMatch(line);
      if (match != null) invoiceNumber = match.group(1) ?? 'N/A';
    }

    if (taxId == 'N/A') {
      final match = taxIdRegex.firstMatch(line);
      if (match != null) taxId = match.group(1) ?? 'N/A';
    }

    if (subtotalAmount == 'N/A' && (upper.contains('SUBTOTAL') || upper.contains('SUB-TOTAL'))) {
      final matches = priceRegex.allMatches(line).toList();
      if (matches.isNotEmpty) {
        subtotalAmount = matches.last.group(1)?.replaceAll(' ', '') ?? 'N/A';
      }
    }

    if (totalAmount == 'Not Found' && upper.contains('TOTAL') && !upper.contains('SUBTOTAL')) {
      final matches = priceRegex.allMatches(line).toList();
      if (matches.isNotEmpty) {
        totalAmount = matches.last.group(1)?.replaceAll(' ', '') ?? 'Not Found';
      }
    }

    if (taxAmount == 'N/A' &&
        RegExp(r'\b(?:TAX|VAT|GST|CGST|SGST|IGST)\b', caseSensitive: false).hasMatch(line) &&
        !upper.contains('GSTIN') &&
        !upper.contains('TAX ID')) {
      final matches = priceRegex.allMatches(line).toList();
      if (matches.isNotEmpty) {
        taxAmount = matches.last.group(1)?.replaceAll(' ', '') ?? 'N/A';
      }
    }

    final dateMatches = dateRegex.allMatches(line).toList();
    if (dateMatches.isNotEmpty) {
      if (invoiceDate == 'N/A') {
        invoiceDate = dateMatches.first.group(1) ?? 'N/A';
        if (dateMatches.length > 1 && dueDate == 'N/A') {
          dueDate = dateMatches[1].group(1) ?? 'N/A';
        }
      } else if (dueDate == 'N/A' && line.toLowerCase().contains('due')) {
        dueDate = dateMatches.first.group(1) ?? 'N/A';
      }
    }

    if (RegExp(r'\d+[.,]\d{2}').hasMatch(line) &&
        !upper.contains('TOTAL') &&
        !upper.contains('SUBTOTAL') &&
        !upper.contains('TAX')) {
      lineItems.add(line);
    }
  }

  final fields = <String, String>{
    'Document Type': 'INVOICE OCR',
    'Vendor / Biller': vendorName,
    'Invoice Number': invoiceNumber,
    'Invoice Date': invoiceDate,
    'Due Date': dueDate,
    'Total Amount': totalAmount,
    'Subtotal Amount': subtotalAmount,
    'Tax / VAT / GST': taxAmount,
    if (taxId != 'N/A') 'Tax ID / GSTIN': taxId,
    'Line Items Count': '${lineItems.length}',
    if (lineItems.isNotEmpty) 'Item 1': lineItems[0],
    if (lineItems.length > 1) 'Item 2': lineItems[1],
    'OCR Engine': 'ScannerPro Invoice AI Parser',
    'Confidence Score': '98.0%',
  };

  return ScanResult(
    mode: ScanMode.invoice,
    rawValue: rawText,
    isValid: totalAmount != 'Not Found' || invoiceNumber != 'N/A' || lines.length >= 2,
    confidence: 0.97,
    format: 'INVOICE_OCR',
    fields: fields,
    metadata: {
      'documentType': 'invoice',
      'vendor': vendorName,
      'invoiceNumber': invoiceNumber,
      'total': totalAmount,
      'subtotal': subtotalAmount,
      'tax': taxAmount,
      'taxId': taxId,
      'date': invoiceDate,
      'dueDate': dueDate,
      'itemsCount': lineItems.length,
    },
  );
}