Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@
import javax.inject.Inject

internal class FaydaCardOcrReaderUseCase @Inject constructor() {
operator fun invoke(ocrReader: OcrReader): OcrScanResult.FaydaCard? {
val credential = ocrReader.find {
matchesCondition { line ->
// Removing non-digits from the line readout. Checking if remaining digits match FAN_PATTERN
line.filter(Char::isDigit).matches(FAN_PATTERN)
}
} ?: return null
val credentialText = credential.text.filter(Char::isDigit)
return OcrScanResult.FaydaCard(credential.copy(text = credentialText))
operator fun invoke(ocrReader: OcrReader): OcrScanResult.FaydaCard? = ocrReader.ocrText.allLines.firstNotNullOfOrNull { line ->
FAN_OCR_PATTERN.find(line.text)?.let { match ->
OcrScanResult.FaydaCard(line.copy(text = match.value.filter(Char::isDigit)))
}
}

companion object {
// Fayda Alias Number (FAN): exactly 16 digits after stripping all non-digit characters
// Fayda Alias Number (FAN): exactly 16 digits
val FAN_PATTERN = Regex("^\\d{16}$")

// FAN within an OCR line: 16 digits, optionally split by OCR into up to 4 groups separated by single whitespace
// characters, and not part of a longer digit run. Punctuation between digits and more than 4 groups are not
// allowed, so that adjacent dates such as "07/02/1998 | 2005/10/17" are not read as a FAN, even if OCR reads the
// separators as spaces ("07 02 1998 2005 10 17").
val FAN_OCR_PATTERN = Regex("(?<!\\d\\s?)(?=(?:\\d+\\s){0,3}\\d+(?!\\s?\\d))\\d(?:\\s?\\d){15}(?!\\s?\\d)")

Check warning on line 22 in feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/usecase/FaydaCardOcrReaderUseCase.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Simplify this regular expression to reduce its complexity from 22 to the 20 allowed.

See more on https://sonarcloud.io/project/issues?id=Simprints_Android-Simprints-ID&issues=AaFZBpbzKqHurdCtiFC9&open=AaFZBpbzKqHurdCtiFC9&pullRequest=1869
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,32 @@ internal class FaydaCardOcrReaderUseCaseTest {
"0000000000000000",
"9999999999999999",
)
private val validFansWithNoise = listOf(
private val validFansWithWhitespace = listOf(
"1234 5678 1234 5678",
"1234-5678-1234-5678",
"1234.5678.1234.5678",
"12345678 12345678",
"258094592481 5610",
"1 23456781234567 8",
)
private val linesContainingFan = mapOf(
"FAN 8642370540168134" to "8642370540168134",
"8642370540168134 FAN" to "8642370540168134",
"ቁጥር: 2580945924815610" to "2580945924815610",
)
private val invalidFans = listOf(
"123456781234567", // 15 digits
"123456781234567", // 15 digits
"12345678123456789", // 17 digits
"1234567812345678 9", // 17 digits, last one separated by whitespace
"ABCDEFGHIJKLMNOP", // 16 letters, 0 digits
"1234567812345A78", // letter in the middle
"1234-5678-1234-5678", // punctuation between digits
"1234.5678.1234.5678", // punctuation between digits
"1234 5678 1234 5678", // more than one whitespace between digits

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be a realistic case from OCR? Maybe not all double spaces, but one of them could be.

"07/02/1998 | 2005/10/17", // date of birth line (Gregorian | Ethiopian)
"25/12/2000 | 2008/08/31", // date of birth line (Gregorian | Ethiopian)
"07/02/1998 2005/10/17", // date of birth line with separator lost by OCR
"07 02 1998 2005 10 17", // date of birth line with all separators read as spaces by OCR
"07 02 1998 | 2005 10 17", // date of birth line with slashes read as spaces by OCR
"1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8", // more than 4 groups
"",
)

Expand All @@ -44,13 +60,21 @@ internal class FaydaCardOcrReaderUseCaseTest {
}

@Test
fun `returns matching line and strips non-digit characters from FAN`() {
validFansWithNoise.forEachIndexed { id, fan ->
fun `returns matching line and strips whitespace from FAN`() {
validFansWithWhitespace.forEachIndexed { id, fan ->
val reader = buildReader(line(id = id, text = fan, top = 100))
assertThat(useCase(reader)?.credential?.text).isEqualTo(fan.filter(Char::isDigit))
}
}

@Test
fun `extracts FAN from line containing other text`() {
linesContainingFan.entries.forEachIndexed { id, (text, fan) ->
val reader = buildReader(line(id = id, text = text, top = 100))
assertThat(useCase(reader)?.credential?.text).isEqualTo(fan)
}
}

@Test
fun `returns null for invalid FAN formats`() {
invalidFans.forEachIndexed { id, fan ->
Expand All @@ -59,6 +83,18 @@ internal class FaydaCardOcrReaderUseCaseTest {
}
}

@Test
fun `skips date of birth line and returns FAN line`() {
val reader = buildReader(
line(id = 0, text = "07/02/1998 | 2005/10/17", top = 100),
line(id = 1, text = "30/07/2026 | 08/04/2034", top = 150),
line(id = 2, text = "8642370540168134", top = 200),
)
val result = useCase(reader)
assertThat(result?.credential?.id).isEqualTo(2)
assertThat(result?.credential?.text).isEqualTo("8642370540168134")
}

private fun buildReader(vararg lines: OcrLine) = OcrReader(
OcrText(allLines = lines.toList()),
)
Expand Down
Loading