Skip to content
Merged
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 @@ -27,6 +27,7 @@ import com.simprints.infra.config.store.models.ProjectConfiguration
import com.simprints.infra.config.store.models.allowedAgeRanges
import com.simprints.infra.config.store.models.fromDomainToModuleApi
import com.simprints.infra.config.store.models.isAgeRestricted
import com.simprints.infra.config.store.models.sortedUniqueAgeGroups
import com.simprints.infra.enrolment.records.store.domain.models.BiometricDataSource
import com.simprints.infra.enrolment.records.store.domain.models.SubjectQuery
import com.simprints.infra.orchestration.data.ActionRequest
Expand Down Expand Up @@ -422,7 +423,7 @@ internal class BuildStepsUseCase @Inject constructor(

private fun ageGroupFromSubjectAge(action: ActionRequest, projectConfiguration: ProjectConfiguration): AgeGroup? {
return action.getSubjectAgeIfAvailable()?.let { subjectAge ->
projectConfiguration.allowedAgeRanges().firstOrNull{ it.includes(subjectAge) }
projectConfiguration.sortedUniqueAgeGroups().firstOrNull{ it.includes(subjectAge) }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ class BuildStepsUseCaseTest {
Finger.LEFT_THUMB,
Finger.RIGHT_THUMB,
)
every { secugenSimMatcher.allowedAgeRange } returns null
every { secugenSimMatcher.allowedAgeRange } returns AgeGroup(0, null)
every { projectConfiguration.fingerprint?.secugenSimMatcher } returns secugenSimMatcher
every { projectConfiguration.fingerprint?.getSdkConfiguration(SECUGEN_SIM_MATCHER) } returns secugenSimMatcher

every { nec.fingersToCapture } returns listOf(
Finger.LEFT_INDEX_FINGER,
Finger.RIGHT_INDEX_FINGER,
)
every { nec.allowedAgeRange } returns null
every { nec.allowedAgeRange } returns AgeGroup(0, null)
every { projectConfiguration.fingerprint?.nec } returns nec
every { projectConfiguration.fingerprint?.getSdkConfiguration(NEC) } returns nec

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.simprints.feature.selectagegroup.screen
import android.content.Context
import com.simprints.infra.config.store.ConfigRepository
import com.simprints.infra.config.store.models.AgeGroup
import com.simprints.infra.config.store.models.allowedAgeRanges
import com.simprints.infra.config.store.models.sortedUniqueAgeGroups
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import com.simprints.infra.resources.R as IDR
Expand All @@ -19,28 +19,12 @@ internal class BuildAgeGroupsDescriptionUseCase @Inject constructor(
* it also adds a 0-<first age range> and <last age range>-above if they are not present
*/
suspend operator fun invoke(): List<AgeGroupDisplayModel> {
val allowedAgeRanges = configurationRepo.getProjectConfiguration().allowedAgeRanges()
val processedAgeRanges = generateSortedUniqueAgeGroups(allowedAgeRanges)
val processedAgeRanges = configurationRepo.getProjectConfiguration().sortedUniqueAgeGroups()
return processedAgeRanges.map { ageGroup ->
AgeGroupDisplayModel(ageGroup.getDisplayName(), ageGroup)
}
}

private fun generateSortedUniqueAgeGroups(ageGroups: List<AgeGroup>): List<AgeGroup> {
// Handle empty list case by returning a single age group starting at 0 and ending with null
if (ageGroups.isEmpty()) return listOf(AgeGroup(0, null))

// Flatten all start and end ages into a single list, removing nulls, duplicates, and sorting.
var sortedUniqueAges =
ageGroups.flatMap { listOf(it.startInclusive, it.endExclusive) }.filterNotNull()
// Ensure the first age group starts at 0
sortedUniqueAges = (listOf(0) + sortedUniqueAges).sorted().distinct()
// Create age groups based on sorted unique ages
return sortedUniqueAges.zipWithNext { start, end -> AgeGroup(start, end) } + AgeGroup(
sortedUniqueAges.last(), null
)
}

// Helper function to convert months to readable format
private fun formatAgeInMonthsForDisplay(ageInMonths: Int): String {
val years = ageInMonths / 12
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.simprints.infra.config.store.local.models

import com.simprints.infra.config.store.exceptions.InvalidProtobufEnumException
import com.simprints.infra.config.store.models.AgeGroup
import com.simprints.infra.config.store.models.FaceConfiguration

internal fun FaceConfiguration.toProto(): ProtoFaceConfiguration =
Expand All @@ -22,8 +23,8 @@ internal fun FaceConfiguration.FaceSdkConfiguration.toProto() =
.setImageSavingStrategy(imageSavingStrategy.toProto())
.setDecisionPolicy(decisionPolicy.toProto())
.setVersion(version)
.setAllowedAgeRange(allowedAgeRange.toProto())
.also {
if (allowedAgeRange != null) it.allowedAgeRange = allowedAgeRange.toProto()
if (verificationMatchThreshold != null) it.verificationMatchThreshold = verificationMatchThreshold
}.build()

Expand Down Expand Up @@ -53,8 +54,8 @@ internal fun ProtoFaceConfiguration.ProtoFaceSdkConfiguration.toDomain() =
imageSavingStrategy = imageSavingStrategy.toDomain(),
decisionPolicy = decisionPolicy.toDomain(),
version = version,
if (hasAllowedAgeRange()) allowedAgeRange.toDomain() else null,
if (hasVerificationMatchThreshold()) verificationMatchThreshold else null
allowedAgeRange = if (hasAllowedAgeRange()) allowedAgeRange.toDomain() else AgeGroup(0, null),
verificationMatchThreshold = if (hasVerificationMatchThreshold()) verificationMatchThreshold else null
)

internal fun ProtoFaceConfiguration.ImageSavingStrategy.toDomain(): FaceConfiguration.ImageSavingStrategy =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ internal fun FingerprintConfiguration.FingerprintSdkConfiguration.toProto() =
.addAllFingersToCapture(fingersToCapture.map { it.toProto() })
.setDecisionPolicy(decisionPolicy.toProto())
.setComparisonStrategyForVerification(comparisonStrategyForVerification.toProto())
.setAllowedAgeRange(allowedAgeRange.toProto())
.also {
if (vero1 != null) it.vero1 = vero1.toProto()
if (vero2 != null) it.vero2 = vero2.toProto()
if (allowedAgeRange != null) it.allowedAgeRange = allowedAgeRange.toProto()
if (verificationMatchThreshold != null) it.verificationMatchThreshold = verificationMatchThreshold
}.build()

Expand Down Expand Up @@ -85,13 +85,13 @@ internal fun ProtoFingerprintConfiguration.ProtoBioSdk.toDomain() = when (this)

internal fun ProtoFingerprintConfiguration.ProtoFingerprintSdkConfiguration.toDomain() =
FingerprintConfiguration.FingerprintSdkConfiguration(
fingersToCaptureList.map { it.toDomain() },
decisionPolicy.toDomain(),
comparisonStrategyForVerification.toDomain(),
if (hasVero1()) vero1.toDomain() else null,
if (hasVero2()) vero2.toDomain() else null,
if (hasAllowedAgeRange()) allowedAgeRange.toDomain() else null,
if (hasVerificationMatchThreshold()) verificationMatchThreshold else null,
fingersToCapture = fingersToCaptureList.map { it.toDomain() },
decisionPolicy = decisionPolicy.toDomain(),
comparisonStrategyForVerification = comparisonStrategyForVerification.toDomain(),
vero1 = if (hasVero1()) vero1.toDomain() else null,
vero2 = if (hasVero2()) vero2.toDomain() else null,
allowedAgeRange = if (hasAllowedAgeRange()) allowedAgeRange.toDomain() else AgeGroup(0, null),
verificationMatchThreshold = if (hasVerificationMatchThreshold()) verificationMatchThreshold else null,
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ data class FaceConfiguration(
val imageSavingStrategy: ImageSavingStrategy,
val decisionPolicy: DecisionPolicy,
val version: String,
val allowedAgeRange: AgeGroup? = null,
val allowedAgeRange: AgeGroup = AgeGroup(0, null),
val verificationMatchThreshold: Float? = null,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ data class FingerprintConfiguration(
val comparisonStrategyForVerification: FingerComparisonStrategy,
val vero1: Vero1Configuration? = null,
val vero2: Vero2Configuration? = null,
val allowedAgeRange: AgeGroup? = null,
val allowedAgeRange: AgeGroup = AgeGroup(0, null),
val verificationMatchThreshold: Float? = null,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,33 @@ fun ProjectConfiguration.imagesUploadRequiresUnmeteredConnection(): Boolean =
synchronization.up.simprints.imagesRequireUnmeteredConnection

fun ProjectConfiguration.allowedAgeRanges(): List<AgeGroup> {
return listOf(
return listOfNotNull(
face?.rankOne?.allowedAgeRange,
fingerprint?.secugenSimMatcher?.allowedAgeRange,
fingerprint?.nec?.allowedAgeRange
).filterNotNull().filterNot { it.isEmpty() }
)
}

fun ProjectConfiguration.isAgeRestricted() = allowedAgeRanges().isNotEmpty()
/**
* Returns a list of all age groups a subject can fall into based on the allowed age ranges
* of the project's face and fingerprint SDK configurations.
* Note that some of these age groups might be unsupported!
*/
fun ProjectConfiguration.sortedUniqueAgeGroups(): List<AgeGroup> {
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.

This function can now be tested directly ;)

val ageGroups = allowedAgeRanges()

// Handle empty list case by returning a single age group starting at 0 and ending with null
if (ageGroups.isEmpty()) return listOf(AgeGroup(0, null))

// Flatten all start and end ages into a single list, removing nulls, duplicates, and sorting.
var sortedUniqueAges =
ageGroups.flatMap { listOf(it.startInclusive, it.endExclusive) }.filterNotNull()
// Ensure the first age group starts at 0
sortedUniqueAges = (listOf(0) + sortedUniqueAges).sorted().distinct()
// Create age groups based on sorted unique ages
return sortedUniqueAges.zipWithNext { start, end -> AgeGroup(start, end) } + AgeGroup(
sortedUniqueAges.last(), null
)
}

fun ProjectConfiguration.isAgeRestricted() = allowedAgeRanges().any { !it.isEmpty()}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,5 @@ data class ApiAllowedAgeRange(
val endExclusive: Int?,
) {

fun toDomain() =
// When allowedAgeRange is disabled the API returns an empty object
// which is then parsed as {null, null}
if (startInclusive == null && endExclusive == null) null
else AgeGroup(startInclusive ?: 0, endExclusive)
fun toDomain() = AgeGroup(startInclusive ?: 0, endExclusive)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.simprints.infra.config.store.remote.models

import androidx.annotation.Keep
import com.simprints.infra.config.store.models.AgeGroup
import com.simprints.infra.config.store.models.FaceConfiguration

@Keep
Expand Down Expand Up @@ -30,7 +31,7 @@ internal data class ApiFaceConfiguration(
qualityThreshold = qualityThreshold,
decisionPolicy = decisionPolicy.toDomain(),
imageSavingStrategy = imageSavingStrategy.toDomain(),
allowedAgeRange = allowedAgeRange?.toDomain(),
allowedAgeRange = allowedAgeRange?.toDomain() ?: AgeGroup(0, null),
verificationMatchThreshold = verificationMatchThreshold,
version = version
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.simprints.infra.config.store.remote.models

import androidx.annotation.Keep
import com.simprints.infra.config.store.models.AgeGroup
import com.simprints.infra.config.store.models.FingerprintConfiguration
import com.simprints.infra.config.store.models.Finger as DomainFingerprint

Expand Down Expand Up @@ -37,7 +38,7 @@ internal data class ApiFingerprintConfiguration(
comparisonStrategyForVerification = comparisonStrategyForVerification.toDomain(),
vero1 = vero1?.toDomain(),
vero2 = vero2?.toDomain(),
allowedAgeRange = allowedAgeRange?.toDomain(),
allowedAgeRange = allowedAgeRange?.toDomain() ?: AgeGroup(0, null),
verificationMatchThreshold = verificationMatchThreshold,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.simprints.infra.authstore.AuthStore
import com.simprints.infra.config.store.local.migrations.ProjectConfigSharedPrefsMigration.Companion.ALL_KEYS
import com.simprints.infra.config.store.local.migrations.ProjectConfigSharedPrefsMigration.Companion.PROJECT_SETTINGS_JSON_STRING_KEY
import com.simprints.infra.config.store.local.migrations.models.OldProjectConfig
import com.simprints.infra.config.store.local.models.ProtoAllowedAgeRange
import com.simprints.infra.config.store.local.models.ProtoConsentConfiguration
import com.simprints.infra.config.store.local.models.ProtoDecisionPolicy
import com.simprints.infra.config.store.local.models.ProtoDownSynchronizationConfiguration
Expand Down Expand Up @@ -654,6 +655,7 @@ class ProjectConfigSharedPrefsMigrationTest {
.setDecisionPolicy(
ProtoDecisionPolicy.newBuilder().setLow(1).setMedium(20).setHigh(100).build()
)
.setAllowedAgeRange(ProtoAllowedAgeRange.newBuilder().build())
.setVersion("1.23")
.build()
)
Expand All @@ -669,6 +671,7 @@ class ProjectConfigSharedPrefsMigrationTest {
.setDecisionPolicy(
ProtoDecisionPolicy.newBuilder().setLow(0).setMedium(0).setHigh(0).build()
)
.setAllowedAgeRange(ProtoAllowedAgeRange.newBuilder().setStartInclusive(0).build())
.setVersion("1.23")
.build()
)
Expand Down Expand Up @@ -725,6 +728,7 @@ class ProjectConfigSharedPrefsMigrationTest {
.setComparisonStrategyForVerification(ProtoFingerprintConfiguration.FingerComparisonStrategy.SAME_FINGER)
.setVero1(ProtoVero1Configuration.newBuilder().setQualityThreshold(60).build())
.setVero2(PROTO_VERO_2_CONFIGURATION)
.setAllowedAgeRange(ProtoAllowedAgeRange.newBuilder().build())
.build()
).build()

Expand All @@ -749,6 +753,7 @@ class ProjectConfigSharedPrefsMigrationTest {
.setVero1(
ProtoVero1Configuration.newBuilder().setQualityThreshold(60).build()
)
.setAllowedAgeRange(ProtoAllowedAgeRange.newBuilder().build())
.build()
).build()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ class FaceConfigurationTest {
assertThat(faceConfiguration.toProto()).isEqualTo(protoFaceConfiguration)
}

@Test
fun `should map correctly the model with allowedAgeRange missing`() {
val protoFaceConfigurationWithoutAgeRange = protoFaceConfiguration.toBuilder()
.setRankOne(protoFaceConfiguration.rankOne.toBuilder().clearAllowedAgeRange())
.build()

assertThat(protoFaceConfigurationWithoutAgeRange.toDomain()).isEqualTo(faceConfiguration)
}

@Test
fun `should map correctly the ImageSavingStrategy enums`() {
val mapping = mapOf(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.simprints.infra.config.store.local.models

import com.google.common.truth.Truth.assertThat
import com.simprints.infra.config.store.models.AgeGroup
import com.simprints.infra.config.store.models.Finger
import com.simprints.infra.config.store.models.FingerprintConfiguration
import com.simprints.infra.config.store.testtools.fingerprintConfiguration
Expand All @@ -15,6 +16,20 @@ class FingerprintConfigurationTest {
assertThat(fingerprintConfiguration.toProto()).isEqualTo(protoFingerprintConfiguration)
}

@Test
fun `should map correctly the model with allowedAgeRange missing`() {
val protoFingerprintConfigurationWithoutAgeRange = protoFingerprintConfiguration.toBuilder()
.setSecugenSimMatcher(protoFingerprintConfiguration.secugenSimMatcher.toBuilder().clearAllowedAgeRange())
.build()
val fingerprintConfigurationWithoutAgeRange = fingerprintConfiguration.copy(
secugenSimMatcher = fingerprintConfiguration.secugenSimMatcher?.copy(
allowedAgeRange = AgeGroup(0, null)
)
)

assertThat(protoFingerprintConfigurationWithoutAgeRange.toDomain()).isEqualTo(fingerprintConfigurationWithoutAgeRange)
}

@Test
fun `should map correctly the old model to new model`() {
val proto = protoFingerprintConfiguration.toBuilder()
Expand Down
Loading