handleSession method

OcrMrzConsensus handleSession(
  1. OcrMrzAggregator aggregator,
  2. OcrData ocr,
  3. OcrMrzSetting setting,
  4. List<NameValidationData> names,
)

Implementation

OcrMrzConsensus handleSession(OcrMrzAggregator aggregator, OcrData ocr, OcrMrzSetting setting, List<NameValidationData> names) {
  try {
    final rawOcrText = ocr.text.replaceAll('\n', ' ');
    final rawOcrTextMultiLine = ocr.text;
    var updatedSession = aggregator.buildStatus();
    if ((updatedSession.step ?? 0) > 0) {
      if (!ocr.text.contains("<")) {
        return aggregator.build();
      }
    }
    final consensus = aggregator.build();
    logger.log(message: "--- New OCR Frame ---", step: updatedSession.step, details: {'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)});
    final List<String> lines = ocr.lines.map((a) => a.text).toList();
    aggregator.addFrameLines(lines);
    updatedSession = aggregator.buildStatus();
    logger.log(message: "Current Step: ${updatedSession.step}", step: updatedSession.step, details: {'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)});

    String secondLineGuess = lines.firstWhere((a) => _dateSexRe.hasMatch(a), orElse: () => '');
    if (secondLineGuess.isNotEmpty) {
      final dateSexMatch = _dateSexRe.firstMatch(secondLineGuess);
      final birthDateStr = dateSexMatch!.group(1);
      final birthCheck = dateSexMatch.group(2);
      final sexStr = dateSexMatch.group(3);
      final expiryDateStr = dateSexMatch.group(4);
      final expiryCheck = dateSexMatch.group(5);

      final calculatedBirthCheck = _computeMrzCheckDigit(birthDateStr!);
      final calculatedExpiryCheck = _computeMrzCheckDigit(expiryDateStr!);

      bool birthDateValid = calculatedBirthCheck == birthCheck;
      bool expDateValid = calculatedExpiryCheck == expiryCheck;
      bool sexValid = ["M", "F", "X", "<"].contains(sexStr);
      if ((updatedSession.step ?? 0) < 2) {
        logger.log(
          message: "Date/Sex Validation",
          step: updatedSession.step,
          details: {
            'birthDate': {'value': birthDateStr, 'checkDigit': birthCheck, 'calculated': calculatedBirthCheck, 'valid': birthDateValid},
            'expiryDate': {'value': expiryDateStr, 'checkDigit': expiryCheck, 'calculated': calculatedExpiryCheck, 'valid': expDateValid},
            'sex': {'value': sexStr, 'valid': sexValid},
            'line': secondLineGuess,
            'ocr_text': rawOcrTextMultiLine,
            'consensus': consensus.toJson(includeHistograms: true),
          },
        );
      }

      var currentVal = aggregator.validation;
      currentVal.birthDateValid = birthDateValid;
      currentVal.expiryDateValid = expDateValid;
      currentVal.sexValid = sexValid;
      aggregator.validation = currentVal;

      if (birthDateValid && expDateValid) {
        if (aggregator.buildStatus().birthDate != birthDateStr) {
          logger.log(
            message: "New birth date detected. Resetting session.",
            step: updatedSession.step,
            details: {'new_birth_date': birthDateStr, 'old_birth_date': aggregator.buildStatus().birthDate, 'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)},
          );
          aggregator.reset();
        }
        aggregator.addBirthDate(birthDateStr);
        aggregator.addExpiryDate(expiryDateStr);
        aggregator.addExpCheck(expiryCheck!);
        aggregator.addBirthCheck(birthCheck!);
        aggregator.addSex(sexStr!);
        if ((updatedSession.step ?? 0) < 2) {
          aggregator.setStep(2);
          logger.log(message: "Step updated to 2. Found valid birth and expiry dates.", step: 2, details: {'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)});
        }
      }
    } else {
      if ((updatedSession.step ?? 0) < 2) {
        logger.log(
          message: "RegExp search for date/sex line failed to find a match.",
          step: updatedSession.step,
          details: {'pattern': _dateSexRe.pattern, 'searched_lines': lines, 'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)},
        );
      }
    }

    updatedSession = aggregator.buildStatus();

    if (_dateSexRe.hasMatch(ocr.text)) {
      final dateSexMatchCheck = _dateSexRe.firstMatch(secondLineGuess);
      if (dateSexMatchCheck != null) {
        String dateSexCheckStr = dateSexMatchCheck.group(0)!;
        if (updatedSession.dateSexStr != dateSexCheckStr) {
          logger.log(
            message: "New document detected based on date/sex string change. Resetting session.",
            step: updatedSession.step,
            details: {'new_date_sex': dateSexCheckStr, 'old_date_sex': updatedSession.dateSexStr, 'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)},
          );
          aggregator.reset();
        }
      }
    }

    if ((aggregator.buildStatus().step ?? 0) >= 2) {
      if (aggregator.buildStatus().step == 2) {
        logger.log(message: "Attempting to find nationality (Step 2->3)", step: updatedSession.step, details: {'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)});
      }
      String? type;
      final parts = updatedSession.dateSexStr!.split(RegExp(r'[^0-9]+'));
      String? nationalityStr;
      String birth = parts[0];
      String exp = parts[1];
      final countryBeforeBirthReg = RegExp(r'([A-Za-z0-9]{3})(?=' + RegExp.escape(birth) + r')');
      final countryAfterExpReg = RegExp(RegExp.escape(exp) + r'([A-Za-z]{3})');
      String line1 = "";
      String? line3;
      for (var l in lines) {
        int index = lines.indexOf(l);
        l = normalize(l);
        final countryBeforeBirthMatch = countryBeforeBirthReg.firstMatch(l);
        if (countryBeforeBirthMatch != null) {
          type = l.length < 40 ? "td2" : "td2";
          nationalityStr = countryBeforeBirthMatch.group(0)!;
          if (index != 0) line1 = lines[index - 1];
        } else if (l.contains(birth)) {
          String beforeBirth = l.split(birth).first;
          if (beforeBirth.length > 2) {
            nationalityStr = beforeBirth.substring(beforeBirth.length - 3);
            type = l.length < 40 ? "td2" : "td3";
            if (index != 0) line1 = lines[index - 1];
          }
        }

        if (nationalityStr == null) {
          final countryAfterExpMatch = countryAfterExpReg.firstMatch(l);
          if (countryAfterExpMatch != null) {
            type = "td1";
            nationalityStr = countryAfterExpMatch.group(1)!;
            if (index != 0) line1 = lines[index - 1];
            if (index != lines.length - 1) line3 = lines[index + 1];
          }
        }

        if (nationalityStr != null) {
          final fixedNationalityStr = fixAlphaOnlyField(nationalityStr);
          bool isCountryValid = isValidMrzCountry(nationalityStr) || isValidMrzCountry(fixedNationalityStr);
          if ((updatedSession.step ?? 0) < 3) {
            logger.log(
              message: "Potential nationality found",
              step: updatedSession.step,
              details: {'nationality': nationalityStr, 'valid': isCountryValid, 'line': l, 'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)},
            );
          }

          if (isCountryValid) {
            var currentVal = aggregator.validation;
            currentVal.nationalityValid = true;
            aggregator.addNationality(nationalityStr);
            aggregator.validation = currentVal;
            aggregator.setType(type);
            if ((updatedSession.step ?? 0) < 3) {
              aggregator.setStep(3);
              logger.log(
                message: "Step updated to 3. Nationality confirmed.",
                step: 3,
                details: {'nationality': nationalityStr, 'type': type, 'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)},
              );
              updatedSession = updatedSession.copyWith(step: 3, nationality: nationalityStr, type: type, line1: line1, line2: l, line3: line3, validation: currentVal);
            }
            break;
          }
        }
      }
      if (nationalityStr == null) {
        if ((updatedSession.step ?? 0) < 3) {
          logger.log(
            message: "Could not find a valid nationality.",
            step: updatedSession.step,
            details: {'birth_date': birth, 'expiry_date': exp, 'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)},
          );
        }
      }
    }
    updatedSession = aggregator.buildStatus();
    if ((aggregator.buildStatus().step ?? 0) >= 3) {
      if (aggregator.buildStatus().step == 3) {
        logger.log(message: "Attempting to find document number (Step 3->4)", step: updatedSession.step, details: {'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)});
      }
      String? numberStr;
      if (updatedSession.type == "td1") {
        // TD1 logic
      } else {
        final natOnly = "${updatedSession.nationality}";
        final numberBeforeNatReg = RegExp(r'([A-Z0-9<]{9,12})(\d)(?=' + RegExp.escape(natOnly) + r')');
        for (var l in lines) {
          int index = lines.indexOf(l);
          var numberBeforeNatMatch = numberBeforeNatReg.firstMatch(normalize(l)) ?? numberBeforeNatReg.firstMatch(fixOcrBeforeNatOnly(l, natOnly));

          if (numberBeforeNatMatch != null && index != 0) {
            numberStr = numberBeforeNatMatch.group(1)!.replaceAll("O", '0').replaceAll("<", '');
            String numberStrCheck = numberBeforeNatMatch.group(2)!;
            final calculatedDocNumberCheck = _computeMrzCheckDigit(numberStr);
            bool docNumberValid = calculatedDocNumberCheck == numberStrCheck;
            if ((updatedSession.step ?? 0) < 4) {
              logger.log(
                message: "Potential document number found => $numberStr",
                step: updatedSession.step,
                details: {
                  'doc_number': numberStr,
                  'checkDigit': numberStrCheck,
                  'calculated': calculatedDocNumberCheck,
                  'valid': docNumberValid,
                  'line': l,
                  'ocr_text': rawOcrTextMultiLine,
                  'consensus': consensus.toJson(includeHistograms: true),
                },
              );
            }

            var currentVal = aggregator.validation;
            currentVal.docNumberValid = docNumberValid;

            String firstLineGuess = lines[index - 1];
            if (firstLineGuess.length > 5) {
              String docCode = firstLineGuess.substring(0, 2);
              String countryCode = firstLineGuess.substring(2, 5);
              bool validCode = DocumentCodeHelper.isValid(docCode);
              bool validCountry = isValidMrzCountry(countryCode);
              if ((updatedSession.step ?? 0) < 4) {
                logger.log(
                  message: "Header validation",
                  step: updatedSession.step,
                  details: {'docCode': docCode, 'docCodeValid': validCode, 'country': countryCode, 'countryValid': validCountry, 'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)},
                );
              }
              if (validCode && validCountry) {
                currentVal.countryValid = validCountry;
                currentVal.docCodeValid = validCode;

                if (docNumberValid) {
                  aggregator.addDocNum(numberStr);
                  aggregator.addNumCheck(numberStrCheck);
                  if ((updatedSession.step ?? 0) < 4) {
                    aggregator.setStep(4);
                    logger.log(message: "Step updated to 4. Document number confirmed.", step: 4, details: {'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)});
                  }
                }
                aggregator.addDocCode(docCode);
                aggregator.addCountry(countryCode);
              }
            }
          }
        }
        if (numberStr == null) {
          if ((updatedSession.step ?? 0) < 4) {
            logger.log(
              message: "RegExp search for document number failed to find a match.",
              step: updatedSession.step,
              details: {'pattern': numberBeforeNatReg.pattern, 'searched_lines': lines.map((l) => normalize(l)).toList(), 'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)},
            );
          }
        }
      }
    }

    updatedSession = aggregator.buildStatus();
    if ((aggregator.buildStatus().step ?? 0) >= 4) {
      if (aggregator.buildStatus().step == 4) {
        logger.log(message: "Attempting to find and validate names (Step 4->5)", step: updatedSession.step, details: {'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)});
      }
      if (updatedSession.type == "td1") {
        // TD1 name logic
      } else {
        String line1Start = updatedSession.docCode! + updatedSession.countryCode!;
        for (var l in lines) {
          if (l.startsWith(line1Start)) {
            MrzName name = parseNamesTd3OrTd2(l);
            if ((updatedSession.step ?? 0) <5) {
              logger.log(
                message: "Parsed Names",
                step: updatedSession.step,
                details: {'surname': name.surname, 'givenNames': name.givenNames.join(' '), 'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)},
              );
            }
            List<String> otherLines = [...lines.where((a) => a != l)];
            var currentVal = aggregator.validation;
            final (isValid, validationSource,fixed) = name.validateNames(otherLines, setting, names);
            if(isValid){
              logger.log(
                message: "FIXED VALID NAME: ${fixed.full} source ${validationSource}",
                step: updatedSession.step,
                details: {
                  'source': validationSource,
                  'parsed_surname': name.surname,
                  'parsed_given_names': name.givenNames,
                  'lookup_lines': otherLines,
                  'ocr_text': rawOcrTextMultiLine,
                  'consensus': consensus.toJson(includeHistograms: true),
                },
              );
            }
            name  = fixed;
            currentVal.nameValid = isValid;
            aggregator.validation = currentVal;
            if ((updatedSession.step ?? 0) < 5) {
              logger.log(
                message: "Name validation result: $isValid Looking for ${name.surname}| ${name.givenNames.join(" ")} in\n ${otherLines.join("\n")}",
                step: updatedSession.step,
                details: {
                  'source': validationSource,
                  'parsed_surname': name.surname,
                  'parsed_given_names': name.givenNames,
                  'lookup_lines': otherLines,
                  'ocr_text': rawOcrTextMultiLine,
                  'consensus': consensus.toJson(includeHistograms: true),
                },
              );
            }

            if (!currentVal.nameValid) {
              if ((updatedSession.step ?? 0) < 3) {
                logger.log(
                  message: "Validation failed: Name validation failed. Looking for ${name.surname} ${name.givenNames.join(" ")} in\n ${otherLines.join("\n")}",
                  step: updatedSession.step,
                  details: {'source': validationSource, 'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)},
                );
              }
            }

            if (currentVal.nameValid) {
              aggregator.addFirstName(name.givenNames.join(" "));
              aggregator.addLastName(name.surname);
              if ((updatedSession.step ?? 0) < 5) {
                aggregator.setStep(5);
                logger.log(message: "Step updated to 5. Name confirmed.", step: 5, details: {'ocr_text': rawOcrTextMultiLine, 'consensus': consensus.toJson(includeHistograms: true)});
              }
            }
          }
        }
      }
    }

    final finalConsensus = aggregator.build();
    if ((aggregator.buildStatus().step ?? 0) >= 5) {
      logger.log(message: "Finalizing session check.", step: aggregator
          .buildStatus()
          .step, details: {'status': aggregator.buildStatus().toString(), 'consensus': finalConsensus.toJson(includeHistograms: true), 'ocr_text': rawOcrTextMultiLine});
    }
    return finalConsensus;
  } catch (e, st) {
    logger.log(
      message: "!!! An error occurred in handleSession !!!",
      details: {'error': e.toString(), 'stackTrace': st.toString(), 'ocr_text': ocr.text.replaceAll('\n', ' '), 'consensus': aggregator.build().toJson(includeHistograms: true)},
    );
    rethrow;
  }
}