handleSession method
OcrMrzConsensus
handleSession(
- OcrMrzAggregator aggregator,
- OcrData ocr,
- OcrMrzSetting setting,
- 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;
}
}