Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Add unit test for PresenceTracingRiskRepository (EXPOSUREAPP-6332) #2814

Merged
merged 7 commits into from
Apr 13, 2021
Merged
Show file tree
Hide file tree
Changes from 6 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 @@ -68,7 +68,7 @@ class PresenceTracingTestViewModel @AssistedInject constructor(
taskRunTime.postValue(duration)

val warningPackages = traceWarningRepository.allMetaData.first()
val overlaps = presenceTracingRiskRepository.checkInWarningOverlaps.first()
val overlaps = presenceTracingRiskRepository.overlapsOfLast14DaysPlusToday.first()
val lastResult = presenceTracingRiskRepository.latestEntries(1).first().singleOrNull()

val infoText = when {
Expand Down Expand Up @@ -99,7 +99,7 @@ class PresenceTracingTestViewModel @AssistedInject constructor(
riskCalculationRuntime.postValue(it)
},
{
val checkInWarningOverlaps = presenceTracingRiskRepository.checkInWarningOverlaps.first()
val checkInWarningOverlaps = presenceTracingRiskRepository.overlapsOfLast14DaysPlusToday.first()
val normalizedTimePerCheckInDayList =
presenceTracingRiskCalculator.calculateNormalizedTime(checkInWarningOverlaps)
val riskStates =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,43 @@ import org.joda.time.Instant
import org.joda.time.LocalDate

/**
* @param presenceTracingDayRisk Only available for the last calculation, if successful, otherwise null
* @param checkInWarningOverlaps Only available for the last calculation, if successful, otherwise null
* @param presenceTracingDayRisk Only available for the latest calculation, otherwise null
* @param checkInWarningOverlaps Only available for the latest calculation, otherwise null
*/
data class PtRiskLevelResult(
val calculatedAt: Instant,
val riskState: RiskState,
val presenceTracingDayRisk: List<PresenceTracingDayRisk>? = null,
private val presenceTracingDayRisk: List<PresenceTracingDayRisk>? = null,
private val checkInWarningOverlaps: List<CheckInWarningOverlap>? = null,
) {

val wasSuccessfullyCalculated: Boolean
get() = riskState != RiskState.CALCULATION_FAILED
val wasSuccessfullyCalculated: Boolean by lazy {
riskState != RiskState.CALCULATION_FAILED
}

val numberOfDaysWithHighRisk: Int
get() = presenceTracingDayRisk?.count { it.riskState == RiskState.INCREASED_RISK } ?: 0
val numberOfDaysWithHighRisk: Int by lazy {
presenceTracingDayRisk?.count { it.riskState == RiskState.INCREASED_RISK } ?: 0
}

val numberOfDaysWithLowRisk: Int
get() = presenceTracingDayRisk?.count { it.riskState == RiskState.LOW_RISK } ?: 0
val numberOfDaysWithLowRisk: Int by lazy {
presenceTracingDayRisk?.count { it.riskState == RiskState.LOW_RISK } ?: 0
}

val mostRecentDateWithHighRisk: LocalDate?
get() = presenceTracingDayRisk
val mostRecentDateWithHighRisk: LocalDate? by lazy {
presenceTracingDayRisk
?.filter { it.riskState == RiskState.INCREASED_RISK }
?.maxByOrNull { it.localDateUtc }
?.localDateUtc
}

val mostRecentDateWithLowRisk: LocalDate?
get() = presenceTracingDayRisk
val mostRecentDateWithLowRisk: LocalDate? by lazy {
presenceTracingDayRisk
?.filter { it.riskState == RiskState.LOW_RISK }
?.maxByOrNull { it.localDateUtc }
?.localDateUtc
}

val daysWithEncounters: Int
get() = when (riskState) {
RiskState.INCREASED_RISK -> numberOfDaysWithHighRisk
RiskState.LOW_RISK -> numberOfDaysWithLowRisk
else -> 0
}

val checkInOverlapCount: Int
get() = checkInWarningOverlaps?.size ?: 0
val checkInOverlapCount: Int by lazy {
checkInWarningOverlaps?.size ?: 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,19 @@ class PresenceTracingRiskCalculator @Inject constructor(
}
}

suspend fun calculateAggregatedRiskPerDay(list: List<CheckInNormalizedTime>):
List<PresenceTracingDayRisk> {
return list.groupBy { it.localDateUtc }.map {
val normalizedTimePerDate = it.value.sumByDouble {
it.normalizedTime
}
PresenceTracingDayRisk(
localDateUtc = it.key,
riskState = presenceTracingRiskMapper.lookupRiskStatePerDay(normalizedTimePerDate)
)
suspend fun calculateDayRisk(
list: List<CheckInNormalizedTime>
): List<PresenceTracingDayRisk> {
return list.groupBy { it.localDateUtc }.map {
val normalizedTimePerDate = it.value.sumByDouble {
it.normalizedTime
}
PresenceTracingDayRisk(
localDateUtc = it.key,
riskState = presenceTracingRiskMapper.lookupRiskStatePerDay(normalizedTimePerDate)
)
}
}

suspend fun calculateTotalRisk(list: List<CheckInNormalizedTime>): RiskState {
if (list.isEmpty()) return RiskState.LOW_RISK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,35 +46,24 @@ class PresenceTracingRiskRepository @Inject constructor(
database.presenceTracingRiskLevelResultDao()
}

private val matchesOfLast14DaysPlusToday = traceTimeIntervalMatchDao.allMatches()
.map { timeIntervalMatchEntities ->
timeIntervalMatchEntities
.map { it.toCheckInWarningOverlap() }
.filter { it.localDateUtc.isAfter(fifteenDaysAgo.toLocalDateUtc()) }
}

val checkInWarningOverlaps: Flow<List<CheckInWarningOverlap>> =
traceTimeIntervalMatchDao.allMatches().map { matchEntities ->
matchEntities.map {
it.toCheckInWarningOverlap()
}
}
val overlapsOfLast14DaysPlusToday = traceTimeIntervalMatchDao.allMatches().map { entities ->
entities
.map { it.toCheckInWarningOverlap() }
.filter { it.localDateUtc.isAfter(fifteenDaysAgo.toLocalDateUtc()) }
}

private val normalizedTimeOfLast14DaysPlusToday = matchesOfLast14DaysPlusToday.map {
private val normalizedTimeOfLast14DaysPlusToday = overlapsOfLast14DaysPlusToday.map {
presenceTracingRiskCalculator.calculateNormalizedTime(it)
}

private val fifteenDaysAgo: Instant
get() = timeStamper.nowUTC.minus(Days.days(15).toStandardDuration())

val traceLocationCheckInRiskStates: Flow<List<TraceLocationCheckInRisk>> =
normalizedTimeOfLast14DaysPlusToday.map {
presenceTracingRiskCalculator.calculateCheckInRiskPerDay(it)
}

val presenceTracingDayRisk: Flow<List<PresenceTracingDayRisk>> =
normalizedTimeOfLast14DaysPlusToday.map {
presenceTracingRiskCalculator.calculateAggregatedRiskPerDay(it)
presenceTracingRiskCalculator.calculateDayRisk(it)
}

/**
Expand Down Expand Up @@ -120,39 +109,23 @@ class PresenceTracingRiskRepository @Inject constructor(
}

fun latestEntries(limit: Int) = riskLevelResultDao.latestEntries(limit).map { list ->
var lastSuccessfulFound = false
list.sortedByDescending {
it.calculatedAtMillis
}
.map { entity ->
if (!lastSuccessfulFound && entity.riskState != RiskState.CALCULATION_FAILED) {
lastSuccessfulFound = true
// add risk per day to the last successful result
entity.toRiskLevelResult(
presenceTracingDayRisks = presenceTracingDayRisk.first(),
checkInWarningOverlaps = checkInWarningOverlaps.first(),
)
} else {
entity.toRiskLevelResult(
presenceTracingDayRisks = null,
checkInWarningOverlaps = null,
)
}
}
list.sortAndComplementLatestResult()
}

fun allEntries() = riskLevelResultDao.allEntries().map { list ->
var lastSuccessfulFound = false
list.sortedByDescending {
list.sortAndComplementLatestResult()
}

private suspend fun List<PresenceTracingRiskLevelResultEntity>.sortAndComplementLatestResult() =
sortedByDescending {
it.calculatedAtMillis
}
.map { entity ->
if (!lastSuccessfulFound && entity.riskState != RiskState.CALCULATION_FAILED) {
lastSuccessfulFound = true
// add risk per day to the last successful result
.mapIndexed { index, entity ->
if (index == 0) {
// add risk per day to the latest result
entity.toRiskLevelResult(
presenceTracingDayRisks = presenceTracingDayRisk.first(),
checkInWarningOverlaps = checkInWarningOverlaps.first(),
checkInWarningOverlaps = overlapsOfLast14DaysPlusToday.first(),
)
} else {
entity.toRiskLevelResult(
Expand All @@ -161,13 +134,15 @@ class PresenceTracingRiskRepository @Inject constructor(
)
}
}
}

private fun addResult(result: PtRiskLevelResult) {
Timber.i("Saving risk calculation from ${result.calculatedAt} with result ${result.riskState}.")
riskLevelResultDao.insert(result.toRiskLevelEntity())
}

private val fifteenDaysAgo: Instant
get() = timeStamper.nowUTC.minus(Days.days(15).toStandardDuration())

suspend fun clearAllTables() {
traceTimeIntervalMatchDao.deleteAll()
riskLevelResultDao.deleteAll()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class PresenceTracingRiskCalculatorTest : BaseTest() {
)

runBlockingTest {
val result = createInstance().calculateAggregatedRiskPerDay(listOf(normTime))
val result = createInstance().calculateDayRisk(listOf(normTime))
result.size shouldBe 1
result[0].riskState shouldBe RiskState.CALCULATION_FAILED
}
Expand Down Expand Up @@ -133,7 +133,7 @@ class PresenceTracingRiskCalculatorTest : BaseTest() {
)

runBlockingTest {
val result = createInstance().calculateAggregatedRiskPerDay(listOf(normTime, normTime2, normTime3))
val result = createInstance().calculateDayRisk(listOf(normTime, normTime2, normTime3))
result.size shouldBe 2
result.find { it.localDateUtc == localDate }!!.riskState shouldBe RiskState.INCREASED_RISK
result.find { it.localDateUtc == localDate2 }!!.riskState shouldBe RiskState.LOW_RISK
Expand Down
Loading