diff --git a/Corona-Warn-App/schemas/de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase/1.json b/Corona-Warn-App/schemas/de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase/1.json index ae4411da473cb49eb102d552dcf25abf7e4053c2..1ad43a6bfeb62f114ce06611700e8e2477972c7e 100644 --- a/Corona-Warn-App/schemas/de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase/1.json +++ b/Corona-Warn-App/schemas/de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "06285e5c436b0ac4ef046ec858b505c0", + "identityHash": "3017a63593d3342376a56b56743815b7", "entities": [ { "tableName": "checkin", @@ -169,62 +169,12 @@ }, "indices": [], "foreignKeys": [] - }, - { - "tableName": "TraceTimeIntervalMatchEntity", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `checkInId` INTEGER NOT NULL, `traceWarningPackageId` TEXT NOT NULL, `transmissionRiskLevel` INTEGER NOT NULL, `startTimeMillis` INTEGER NOT NULL, `endTimeMillis` INTEGER NOT NULL)", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "checkInId", - "columnName": "checkInId", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "traceWarningPackageId", - "columnName": "traceWarningPackageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "transmissionRiskLevel", - "columnName": "transmissionRiskLevel", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "startTimeMillis", - "columnName": "startTimeMillis", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "endTimeMillis", - "columnName": "endTimeMillis", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": true - }, - "indices": [], - "foreignKeys": [] } ], "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '06285e5c436b0ac4ef046ec858b505c0')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3017a63593d3342376a56b56743815b7')" ] } -} \ No newline at end of file +} diff --git a/Corona-Warn-App/schemas/de.rki.coronawarnapp.presencetracing.risk.PresenceTracingRiskDatabase/1.json b/Corona-Warn-App/schemas/de.rki.coronawarnapp.presencetracing.risk.PresenceTracingRiskDatabase/1.json new file mode 100644 index 0000000000000000000000000000000000000000..59228f0a75bd2fb51813fc35b1f212b6304dd2eb --- /dev/null +++ b/Corona-Warn-App/schemas/de.rki.coronawarnapp.presencetracing.risk.PresenceTracingRiskDatabase/1.json @@ -0,0 +1,90 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "8f6fbeaffe608cffaae19b13399cdd3a", + "entities": [ + { + "tableName": "TraceTimeIntervalMatchEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `checkInId` INTEGER NOT NULL, `traceWarningPackageId` TEXT NOT NULL, `transmissionRiskLevel` INTEGER NOT NULL, `startTimeMillis` INTEGER NOT NULL, `endTimeMillis` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "checkInId", + "columnName": "checkInId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "traceWarningPackageId", + "columnName": "traceWarningPackageId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "transmissionRiskLevel", + "columnName": "transmissionRiskLevel", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "startTimeMillis", + "columnName": "startTimeMillis", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "endTimeMillis", + "columnName": "endTimeMillis", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "PresenceTracingRiskLevelResultEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`calculatedAtMillis` INTEGER NOT NULL, `riskStateCode` INTEGER NOT NULL, PRIMARY KEY(`calculatedAtMillis`))", + "fields": [ + { + "fieldPath": "calculatedAtMillis", + "columnName": "calculatedAtMillis", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "riskState", + "columnName": "riskStateCode", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "calculatedAtMillis" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8f6fbeaffe608cffaae19b13399cdd3a')" + ] + } +} \ No newline at end of file diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/risk/storage/RiskResultDatabaseMigrationTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/risk/storage/RiskResultDatabaseMigrationTest.kt index 3b699ab53c884822c6c863ec4e429a16f3fe4164..7535a1c304d20c99660beeda82a370f0b80aece8 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/risk/storage/RiskResultDatabaseMigrationTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/risk/storage/RiskResultDatabaseMigrationTest.kt @@ -7,7 +7,7 @@ import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import de.rki.coronawarnapp.risk.RiskLevelResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.risk.storage.internal.RiskResultDatabase import de.rki.coronawarnapp.risk.storage.internal.migrations.RiskResultDatabaseMigration1To2 import de.rki.coronawarnapp.risk.storage.internal.migrations.RiskResultDatabaseMigration2To3 @@ -129,7 +129,7 @@ class RiskResultDatabaseMigrationTest : BaseTestInstrumentation() { monotonicId = 3, id = "0235fef8-4332-4a43-b7d8-f5eacb54a6ee", calculatedAt = Instant.parse("2020-12-31T16:28:25.400Z"), - failureReason = RiskLevelResult.FailureReason.TRACING_OFF, + failureReason = EwRiskLevelResult.FailureReason.TRACING_OFF, aggregatedRiskResult = null ) diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/risk/storage/RiskResultDatabaseTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/risk/storage/RiskResultDatabaseTest.kt index 3c3b2446dd1eb2337b6da46b335e2469f25c77cc..9dc220c35ab5734be19430032de85670217fdd79 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/risk/storage/RiskResultDatabaseTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/risk/storage/RiskResultDatabaseTest.kt @@ -2,7 +2,7 @@ package de.rki.coronawarnapp.risk.storage import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import de.rki.coronawarnapp.risk.RiskLevelResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.risk.storage.internal.RiskResultDatabase import de.rki.coronawarnapp.risk.storage.internal.riskresults.PersistedAggregatedRiskPerDateResult import de.rki.coronawarnapp.risk.storage.internal.riskresults.PersistedRiskLevelResultDao @@ -53,7 +53,7 @@ class RiskResultDatabaseTest { numberOfDaysWithLowRisk = 4, numberOfDaysWithHighRisk = 5 ), - failureReason = RiskLevelResult.FailureReason.TRACING_OFF + failureReason = EwRiskLevelResult.FailureReason.TRACING_OFF ) private val newestEntryFailed = PersistedRiskLevelResultDao( @@ -69,7 +69,7 @@ class RiskResultDatabaseTest { numberOfDaysWithLowRisk = 4, numberOfDaysWithHighRisk = 5 ), - failureReason = RiskLevelResult.FailureReason.TRACING_OFF + failureReason = EwRiskLevelResult.FailureReason.TRACING_OFF ) @Test diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt index 125fccb815653aebafec4a7aa1ca8e377533ba13..2a8bc98f65164640d0f1ce0c63292ebb7390d435 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt @@ -28,6 +28,7 @@ import de.rki.coronawarnapp.tracing.ui.homecards.LowRiskCard import de.rki.coronawarnapp.tracing.ui.homecards.TracingDisabledCard import de.rki.coronawarnapp.tracing.ui.homecards.TracingFailedCard import de.rki.coronawarnapp.tracing.ui.homecards.TracingProgressCard +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc import org.joda.time.Instant import java.util.Date @@ -71,7 +72,7 @@ object HomeData { riskState = RiskState.LOW_RISK, isInDetailsMode = false, lastExposureDetectionTime = todayAtNineFiftyFive, - lastEncounterAt = todayAtNineFiftyFive, + lastEncounterAt = todayAtNineFiftyFive.toLocalDateUtc(), allowManualUpdate = false, daysWithEncounters = 1, daysSinceInstallation = 4 @@ -87,7 +88,7 @@ object HomeData { lastExposureDetectionTime = todayAtNineFiftyFive, allowManualUpdate = false, daysWithEncounters = 1, - lastEncounterAt = todayAtNineFiftyFive + lastEncounterAt = todayAtNineFiftyFive.toLocalDateUtc() ), onCardClick = {}, onUpdateClick = {} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt index 8608e00b8de5d06aaaa83b7ba24d1afe570bd640..c1b30a16e5ee62271d1774e40f2bbd7690d67890 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt @@ -17,6 +17,7 @@ import de.rki.coronawarnapp.tracing.ui.details.items.risk.TracingFailedBox import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsFailedCalculationBox import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsIncreasedRiskBox import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsLowRiskBox +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc import org.joda.time.Instant object TracingData { @@ -97,7 +98,7 @@ object TracingData { allowManualUpdate = false, daysWithEncounters = 1, daysSinceInstallation = 4, - lastEncounterAt = todayAtNineFiftyFive + lastEncounterAt = todayAtNineFiftyFive.toLocalDateUtc() ) ), BehaviorNormalRiskBox.Item( @@ -128,7 +129,7 @@ object TracingData { allowManualUpdate = false, daysWithEncounters = 2, daysSinceInstallation = 4, - lastEncounterAt = todayAtNineFiftyFive + lastEncounterAt = todayAtNineFiftyFive.toLocalDateUtc() ) ), BehaviorNormalRiskBox.Item( @@ -158,7 +159,7 @@ object TracingData { lastExposureDetectionTime = todayAtNineFiftyFive, allowManualUpdate = false, daysWithEncounters = 1, - lastEncounterAt = todayAtNineFiftyFive + lastEncounterAt = todayAtNineFiftyFive.toLocalDateUtc() ) ), BehaviorIncreasedRiskBox.Item, @@ -168,7 +169,7 @@ object TracingData { ), DetailsIncreasedRiskBox.Item( riskState = RiskState.INCREASED_RISK, - lastEncounteredAt = todayAtNineFiftyFive + lastEncounteredAt = todayAtNineFiftyFive.toLocalDateUtc() ) ) ) diff --git a/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/risk/storage/DefaultRiskLevelStorage.kt b/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/risk/storage/DefaultRiskLevelStorage.kt index 0b615380191e2ba7a777c274e5cf383e8b9757db..c88e6445be335789386094922c591394762337bf 100644 --- a/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/risk/storage/DefaultRiskLevelStorage.kt +++ b/Corona-Warn-App/src/device/java/de/rki/coronawarnapp/risk/storage/DefaultRiskLevelStorage.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.risk.storage import de.rki.coronawarnapp.presencetracing.risk.PresenceTracingRiskRepository -import de.rki.coronawarnapp.risk.RiskLevelResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.risk.storage.internal.RiskResultDatabase import de.rki.coronawarnapp.util.coroutine.AppScope import kotlinx.coroutines.CoroutineScope @@ -20,7 +20,7 @@ class DefaultRiskLevelStorage @Inject constructor( // Taken from TimeVariables.MAX_STALE_EXPOSURE_RISK_RANGE override val storedResultLimit: Int = 2 * 6 - override suspend fun storeExposureWindows(storedResultId: String, result: RiskLevelResult) { + override suspend fun storeExposureWindows(storedResultId: String, result: EwRiskLevelResult) { Timber.d("storeExposureWindows(): NOOP") // NOOP } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/risk/storage/DefaultRiskLevelStorage.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/risk/storage/DefaultRiskLevelStorage.kt index f3d38f4c1549b7b491c40f639682e45368aa545e..ee15ef5abd872e3749674aa965c61f40a35986a2 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/risk/storage/DefaultRiskLevelStorage.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/risk/storage/DefaultRiskLevelStorage.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.risk.storage import de.rki.coronawarnapp.presencetracing.risk.PresenceTracingRiskRepository -import de.rki.coronawarnapp.risk.RiskLevelResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.risk.storage.internal.RiskResultDatabase import de.rki.coronawarnapp.risk.storage.internal.windows.PersistedExposureWindowDao.PersistedScanInstance import de.rki.coronawarnapp.risk.storage.internal.windows.toPersistedExposureWindow @@ -24,11 +24,11 @@ class DefaultRiskLevelStorage @Inject constructor( // For testers keep all the results! override val storedResultLimit: Int = 14 * 6 - override suspend fun storeExposureWindows(storedResultId: String, result: RiskLevelResult) { + override suspend fun storeExposureWindows(storedResultId: String, resultEw: EwRiskLevelResult) { Timber.d("Storing exposure windows for storedResultId=%s", storedResultId) try { val startTime = System.currentTimeMillis() - val exposureWindows = result.exposureWindows ?: emptyList() + val exposureWindows = resultEw.exposureWindows ?: emptyList() val windowIds = exposureWindows .map { it.toPersistedExposureWindow(riskLevelResultId = storedResultId) } .let { exposureWindowsTables.insertWindows(it) } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt index f2fbf7ae81e61e27b2d095a460116576d687e6c2..ed246e65b60fc63a60c27263933ca1ee8315a5c0 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt @@ -16,9 +16,9 @@ import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTra import de.rki.coronawarnapp.nearby.modules.detectiontracker.latestSubmission import de.rki.coronawarnapp.risk.RiskLevelTask import de.rki.coronawarnapp.risk.RiskState -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import de.rki.coronawarnapp.risk.storage.RiskLevelStorage -import de.rki.coronawarnapp.risk.tryLatestResultsWithDefaults +import de.rki.coronawarnapp.risk.tryLatestEwResultsWithDefaults import de.rki.coronawarnapp.storage.TestSettings import de.rki.coronawarnapp.task.TaskController import de.rki.coronawarnapp.task.common.DefaultTaskRequest @@ -71,7 +71,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( val dataResetEvent = SingleLiveEvent<String>() val shareFileEvent = SingleLiveEvent<File>() - private val lastRiskResult = riskLevelStorage.allRiskLevelResults.map { results -> + private val lastRiskResult = riskLevelStorage.allEwRiskLevelResults.map { results -> results.maxByOrNull { it.calculatedAt } } @@ -85,7 +85,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( if (it.wasSuccessfullyCalculated) { // wasSuccessfullyCalculated check for aggregatedRiskResult != null - it.aggregatedRiskResult!!.toReadableString() + it.ewAggregatedRiskResult!!.toReadableString() } else { var notAvailable = "Aggregated risk result is not available" it.failureReason?.let { failureReason -> notAvailable += " because ${failureReason.failureCode}" } @@ -94,7 +94,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( } .asLiveData() - private fun AggregatedRiskResult.toReadableString(): String = StringBuilder() + private fun EwAggregatedRiskResult.toReadableString(): String = StringBuilder() .appendLine("Total RiskLevel: $totalRiskLevel") .appendLine("Total Minimum Distinct Encounters With High Risk: $totalMinimumDistinctEncountersWithHighRisk") .appendLine("Total Minimum Distinct Encounters With Low Risk: $totalMinimumDistinctEncountersWithLowRisk") @@ -110,11 +110,11 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( .asLiveData() val additionalRiskCalcInfo = combine( - riskLevelStorage.latestAndLastSuccessful, + riskLevelStorage.latestAndLastSuccessfulEwRiskLevelResult, exposureDetectionTracker.latestSubmission() ) { riskLevelResults, latestSubmission -> - val (latestCalc, latestSuccessfulCalc) = riskLevelResults.tryLatestResultsWithDefaults() + val (latestCalc, latestSuccessfulCalc) = riskLevelResults.tryLatestEwResultsWithDefaults() createAdditionalRiskCalcInfo( latestCalc.calculatedAt, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/retention/ContactDiaryRetentionCalculation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/retention/ContactDiaryRetentionCalculation.kt index 778d3c9e4187009d22c29cfac6c7c07930533422..5502a5e70ab83e480975d7c8450cdfe522023c21 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/retention/ContactDiaryRetentionCalculation.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/retention/ContactDiaryRetentionCalculation.kt @@ -53,7 +53,7 @@ class ContactDiaryRetentionCalculation @Inject constructor( } suspend fun clearObsoleteRiskPerDate() { - val list = riskLevelStorage.aggregatedRiskPerDateResults.first() + val list = riskLevelStorage.ewDayRiskStates.first() Timber.d("Aggregated Risk Per Date Results total count: ${list.size}") val toDeleteList = list.filter { risk -> isOutOfRetention(risk.localDateUtc) } Timber.d("AggregatedRiskPerDateResult to be deleted count: ${toDeleteList.size}") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/DiaryLocationViewHolder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/DiaryLocationViewHolder.kt index f7998681bcf6a83adbd1497e4cc146e7aefe0808..e9c93ba31002468d8431ec261184f6b5845d9d64 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/DiaryLocationViewHolder.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/location/DiaryLocationViewHolder.kt @@ -5,8 +5,8 @@ import android.view.accessibility.AccessibilityEvent import de.rki.coronawarnapp.R import de.rki.coronawarnapp.contactdiary.util.hideKeyboard import de.rki.coronawarnapp.contactdiary.util.setClickLabel -import de.rki.coronawarnapp.ui.durationpicker.toContactDiaryFormat import de.rki.coronawarnapp.databinding.ContactDiaryLocationListItemBinding +import de.rki.coronawarnapp.ui.durationpicker.toContactDiaryFormat import de.rki.coronawarnapp.ui.lists.BaseAdapter import de.rki.coronawarnapp.util.lists.BindableVH import de.rki.coronawarnapp.util.ui.setOnClickListenerThrottled diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt index 36b4bb566eca046fcbcb3a1c18399b6d5738d03c..3409ad4b6bea470d8213773a93070efb7081697a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt @@ -21,7 +21,7 @@ import de.rki.coronawarnapp.contactdiary.ui.overview.adapter.day.riskevent.RiskE import de.rki.coronawarnapp.contactdiary.ui.overview.adapter.subheader.OverviewSubHeaderItem import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.TraceLocationCheckInRisk -import de.rki.coronawarnapp.risk.result.AggregatedRiskPerDateResult +import de.rki.coronawarnapp.risk.result.ExposureWindowDayRisk import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass import de.rki.coronawarnapp.task.TaskController @@ -55,7 +55,7 @@ class ContactDiaryOverviewViewModel @AssistedInject constructor( private val locationVisitsFlow = contactDiaryRepository.locationVisits private val personEncountersFlow = contactDiaryRepository.personEncounters - private val riskLevelPerDateFlow = riskLevelStorage.aggregatedRiskPerDateResults + private val riskLevelPerDateFlow = riskLevelStorage.ewDayRiskStates private val traceLocationCheckInRiskFlow = riskLevelStorage.traceLocationCheckInRiskStates val listItems = combine( @@ -92,7 +92,7 @@ class ContactDiaryOverviewViewModel @AssistedInject constructor( dateList: List<LocalDate>, visits: List<ContactDiaryLocationVisit>, encounters: List<ContactDiaryPersonEncounter>, - riskLevelPerDateList: List<AggregatedRiskPerDateResult>, + riskLevelPerDateList: List<ExposureWindowDayRisk>, traceLocationCheckInRiskList: List<TraceLocationCheckInRisk> ): List<DiaryOverviewItem> { Timber.v( @@ -145,7 +145,7 @@ class ContactDiaryOverviewViewModel @AssistedInject constructor( } } - private fun AggregatedRiskPerDateResult.toRisk(locationOrPerson: Boolean): RiskEnfItem { + private fun ExposureWindowDayRisk.toRisk(locationOrPerson: Boolean): RiskEnfItem { @StringRes val title: Int @StringRes var body: Int = R.string.contact_diary_risk_body @DrawableRes val drawableId: Int diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensions.kt index 2a898e5a0a3d943d7321b3d111dbe5d86dfecaab..c8006b7c30b171dea2319d412054614ca4372015 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensions.kt @@ -2,7 +2,7 @@ package de.rki.coronawarnapp.datadonation.analytics.common import androidx.annotation.StringRes import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.risk.RiskLevelResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData @@ -67,7 +67,7 @@ val PpaData.PPAFederalState.federalStateShortName: String ) } -fun RiskLevelResult.toMetadataRiskLevel(): PpaData.PPARiskLevel = +fun EwRiskLevelResult.toMetadataRiskLevel(): PpaData.PPARiskLevel = when (riskState) { RiskState.INCREASED_RISK -> PpaData.PPARiskLevel.RISK_LEVEL_HIGH else -> PpaData.PPARiskLevel.RISK_LEVEL_LOW diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposureriskmetadata/ExposureRiskMetadataDonor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposureriskmetadata/ExposureRiskMetadataDonor.kt index 47b17821aaff71159b1d46c699fe23f0b25a7ac3..011d089bc1562bd99c106502aeb7e8895ac7073f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposureriskmetadata/ExposureRiskMetadataDonor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposureriskmetadata/ExposureRiskMetadataDonor.kt @@ -2,10 +2,10 @@ package de.rki.coronawarnapp.datadonation.analytics.modules.exposureriskmetadata import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings -import de.rki.coronawarnapp.risk.RiskLevelResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.storage.RiskLevelStorage -import de.rki.coronawarnapp.risk.tryLatestResultsWithDefaults +import de.rki.coronawarnapp.risk.tryLatestEwResultsWithDefaults import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData import de.rki.coronawarnapp.util.TimeAndDateExtensions.seconds import kotlinx.coroutines.flow.first @@ -22,9 +22,9 @@ class ExposureRiskMetadataDonor @Inject constructor( val previousMetadata = analyticsSettings.previousExposureRiskMetadata.value val lastRiskResult = riskLevelStorage - .latestAndLastSuccessful + .latestAndLastSuccessfulEwRiskLevelResult .first() - .tryLatestResultsWithDefaults() + .tryLatestEwResultsWithDefaults() .lastCalculated val riskLevelForMetadata = lastRiskResult.toMetadataRiskLevel() @@ -75,7 +75,7 @@ class ExposureRiskMetadataDonor @Inject constructor( } } -private fun RiskLevelResult.toMetadataRiskLevel(): PpaData.PPARiskLevel = +private fun EwRiskLevelResult.toMetadataRiskLevel(): PpaData.PPARiskLevel = when (riskState) { RiskState.LOW_RISK -> PpaData.PPARiskLevel.RISK_LEVEL_LOW RiskState.INCREASED_RISK -> PpaData.PPARiskLevel.RISK_LEVEL_HIGH diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollector.kt index 81529146337c67d83d97488a976bd00f81ebb0cb..a7133bdb0a24430ff3ea374a2c2f4cc87c2de503 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollector.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollector.kt @@ -5,7 +5,7 @@ import de.rki.coronawarnapp.datadonation.analytics.common.toMetadataRiskLevel import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings import de.rki.coronawarnapp.risk.RiskLevelSettings import de.rki.coronawarnapp.risk.storage.RiskLevelStorage -import de.rki.coronawarnapp.risk.tryLatestResultsWithDefaults +import de.rki.coronawarnapp.risk.tryLatestEwResultsWithDefaults import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData import de.rki.coronawarnapp.util.TimeStamper import kotlinx.coroutines.flow.first @@ -35,9 +35,9 @@ class AnalyticsKeySubmissionCollector @Inject constructor( analyticsKeySubmissionStorage.testRegisteredAt.update { testRegisteredAt.millis } val lastRiskResult = riskLevelStorage - .latestAndLastSuccessful + .latestAndLastSuccessfulEwRiskLevelResult .first() - .tryLatestResultsWithDefaults() + .tryLatestEwResultsWithDefaults() .lastCalculated val riskLevelAtRegistration = lastRiskResult.toMetadataRiskLevel() analyticsKeySubmissionStorage.riskLevelAtTestRegistration.update { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollector.kt index 487f3420717bcccdfba54279ba3ab74e3f55644b..5335b72d1ad71c4e88d5806cc9ae4411331ea630 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollector.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollector.kt @@ -3,7 +3,7 @@ package de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings import de.rki.coronawarnapp.datadonation.analytics.storage.TestResultDonorSettings import de.rki.coronawarnapp.risk.storage.RiskLevelStorage -import de.rki.coronawarnapp.risk.tryLatestResultsWithDefaults +import de.rki.coronawarnapp.risk.tryLatestEwResultsWithDefaults import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.formatter.TestResult import kotlinx.coroutines.flow.first @@ -33,9 +33,9 @@ class TestResultDataCollector @Inject constructor( // User consented to donate analytics data if (analyticsSettings.analyticsEnabled.value) { val lastRiskResult = riskLevelStorage - .latestAndLastSuccessful + .latestAndLastSuccessfulEwRiskLevelResult .first() - .tryLatestResultsWithDefaults() + .tryLatestEwResultsWithDefaults() .lastCalculated Timber.d("saveTestResultDonorDataAtRegistration($testResult, $lastRiskResult)") testResultDonorSettings.saveTestResultDonorDataAtRegistration(testResult, lastRiskResult) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/TestResultDonorSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/TestResultDonorSettings.kt index f97c069684923f6c49ad6703da3d13d4d4782d1a..3cec71475ff6dd8801f73babdd62b8a340f5c32d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/TestResultDonorSettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/TestResultDonorSettings.kt @@ -2,7 +2,7 @@ package de.rki.coronawarnapp.datadonation.analytics.storage import android.content.Context import de.rki.coronawarnapp.datadonation.analytics.common.toMetadataRiskLevel -import de.rki.coronawarnapp.risk.RiskLevelResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.di.AppContext @@ -95,14 +95,14 @@ class TestResultDonorSettings @Inject constructor( } ) - fun saveTestResultDonorDataAtRegistration(testResult: TestResult, lastRiskResult: RiskLevelResult) { + fun saveTestResultDonorDataAtRegistration(testResult: TestResult, lastEwRiskResult: EwRiskLevelResult) { testScannedAfterConsent.update { true } testResultAtRegistration.update { testResult } if (testResult in listOf(TestResult.POSITIVE, TestResult.NEGATIVE)) { finalTestResultReceivedAt.update { timeStamper.nowUTC } } - riskLevelAtTestRegistration.update { lastRiskResult.toMetadataRiskLevel() } + riskLevelAtTestRegistration.update { lastEwRiskResult.toMetadataRiskLevel() } } fun clear() = prefs.clearAndNotify() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabase.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabase.kt index 86ab94da5ab4d7495a766647b8c03bfa9388e2bf..30e508cf4c45d0a9145c9551e64523c457f20a99 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabase.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabase.kt @@ -10,8 +10,6 @@ import de.rki.coronawarnapp.eventregistration.storage.dao.TraceLocationDao import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationConverters import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationEntity -import de.rki.coronawarnapp.presencetracing.risk.TraceTimeIntervalMatchDao -import de.rki.coronawarnapp.presencetracing.risk.TraceTimeIntervalMatchEntity import de.rki.coronawarnapp.util.database.CommonConverters import de.rki.coronawarnapp.util.di.AppContext import javax.inject.Inject @@ -19,8 +17,7 @@ import javax.inject.Inject @Database( entities = [ TraceLocationCheckInEntity::class, - TraceLocationEntity::class, - TraceTimeIntervalMatchEntity::class + TraceLocationEntity::class ], version = 1, exportSchema = true @@ -30,7 +27,6 @@ abstract class TraceLocationDatabase : RoomDatabase() { abstract fun eventCheckInDao(): CheckInDao abstract fun traceLocationDao(): TraceLocationDao - abstract fun traceTimeIntervalMatchDao(): TraceTimeIntervalMatchDao class Factory @Inject constructor(@AppContext private val context: Context) { fun create() = Room diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/CheckInWarningMatcher.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/CheckInWarningMatcher.kt index ea28693fca1bc08bb96b25b08a9668a8c112d37a..fed793d677c41830109154b540c7c0887b7cb2e1 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/CheckInWarningMatcher.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/CheckInWarningMatcher.kt @@ -28,29 +28,47 @@ class CheckInWarningMatcher @Inject constructor( ) { suspend fun execute(): List<CheckInWarningOverlap> { + presenceTracingRiskRepository.deleteStaleData() + val checkIns = checkInsRepository.allCheckIns.firstOrNull() + if (checkIns.isNullOrEmpty()) { + Timber.i("No check-ins available. Deleting all matches.") + presenceTracingRiskRepository.deleteAllMatches() + presenceTracingRiskRepository.reportSuccessfulCalculation(emptyList()) + return emptyList() + } + val warningPackages = traceTimeIntervalWarningRepository.allWarningPackages.firstOrNull() - if (checkIns.isNullOrEmpty() || warningPackages.isNullOrEmpty()) { - Timber.i("No check-ins or packages available. Deleting all matches.") - presenceTracingRiskRepository.deleteAllMatches() + if (warningPackages.isNullOrEmpty()) { + // nothing to be done here return emptyList() } val splitCheckIns = checkIns.flatMap { it.splitByMidnightUTC() } - val matchLists = createMatchingLaunchers(splitCheckIns, warningPackages, dispatcherProvider.IO) + val matchLists = createMatchingLaunchers( + splitCheckIns, + warningPackages, + dispatcherProvider.IO + ) .awaitAll() if (matchLists.contains(null)) { - Timber.e("Error occurred during matching. Deleting all stale matches.") - presenceTracingRiskRepository.deleteAllMatches() - // TODO report calculation failed to show on home card + Timber.e("Error occurred during matching. Abort calculation.") + presenceTracingRiskRepository.reportFailedCalculation() return emptyList() } + // delete stale matches from new packages and mark packages as processed + warningPackages.forEach { + presenceTracingRiskRepository.deleteMatchesOfPackage(it.warningPackageId) + presenceTracingRiskRepository.markPackageProcessed(it.warningPackageId) + } val matches = matchLists.filterNotNull().flatten() - presenceTracingRiskRepository.replaceAllMatches(matches) + + presenceTracingRiskRepository.reportSuccessfulCalculation(matches) + return matches } } @@ -69,7 +87,6 @@ internal suspend fun createMatchingLaunchers( { list, packageChunk -> async { try { - packageChunk.flatMap { findMatches(list, it) } @@ -119,12 +136,12 @@ internal fun CheckIn.calculateOverlap( if (warning.locationIdHash != locationGuidHash) return null - val warningStartTimestamp = warning.startIntervalNumber.tenMinIntervalToMillis() - val warningEndTimestamp = (warning.startIntervalNumber + warning.period).tenMinIntervalToMillis() + val warningStartMillis = warning.startIntervalNumber.tenMinIntervalToMillis() + val warningEndMillis = (warning.startIntervalNumber + warning.period).tenMinIntervalToMillis() - val overlapStartTimestamp = kotlin.math.max(checkInStart.millis, warningStartTimestamp) - val overlapEndTimestamp = kotlin.math.min(checkInEnd.millis, warningEndTimestamp) - val overlapMillis = overlapEndTimestamp - overlapStartTimestamp + val overlapStartMillis = kotlin.math.max(checkInStart.millis, warningStartMillis) + val overlapEndMillis = kotlin.math.min(checkInEnd.millis, warningEndMillis) + val overlapMillis = overlapEndMillis - overlapStartMillis if (overlapMillis <= 0) return null @@ -132,7 +149,7 @@ internal fun CheckIn.calculateOverlap( checkInId = id, transmissionRiskLevel = warning.transmissionRiskLevel, traceWarningPackageId = traceWarningPackageId, - startTime = Instant.ofEpochMilli(overlapStartTimestamp), - endTime = Instant.ofEpochMilli(overlapEndTimestamp) + startTime = Instant.ofEpochMilli(overlapStartMillis), + endTime = Instant.ofEpochMilli(overlapEndMillis) ) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PresenceTracingRiskCalculator.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PresenceTracingRiskCalculator.kt index 36f86c9056b4d38c3e65495ab80b2438509e251d..1c2bd002f2aa3bdb6499247ce107f69fa68151b4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PresenceTracingRiskCalculator.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PresenceTracingRiskCalculator.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.presencetracing.risk +import de.rki.coronawarnapp.risk.RiskState import javax.inject.Inject class PresenceTracingRiskCalculator @Inject constructor( @@ -49,4 +50,14 @@ class PresenceTracingRiskCalculator @Inject constructor( ) } } + + suspend fun calculateTotalRisk(list: List<CheckInNormalizedTime>): RiskState { + if (list.isEmpty()) return RiskState.LOW_RISK + val riskPerDay = calculateCheckInRiskPerDay(list) + if (riskPerDay.find { it.riskState == RiskState.INCREASED_RISK } != null) + return RiskState.INCREASED_RISK + if (riskPerDay.find { it.riskState == RiskState.LOW_RISK } != null) + return RiskState.LOW_RISK + return RiskState.CALCULATION_FAILED + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PresenceTracingRiskDatabase.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PresenceTracingRiskDatabase.kt new file mode 100644 index 0000000000000000000000000000000000000000..2d39bd5eaa6ab3ca9b186aacca859ae0b747a7e8 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PresenceTracingRiskDatabase.kt @@ -0,0 +1,32 @@ +package de.rki.coronawarnapp.presencetracing.risk + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import de.rki.coronawarnapp.util.di.AppContext +import javax.inject.Inject + +@Database( + entities = [ + TraceTimeIntervalMatchEntity::class, + PresenceTracingRiskLevelResultEntity::class + ], + version = 1, + exportSchema = true +) +@TypeConverters(RiskStateConverter::class) +abstract class PresenceTracingRiskDatabase : RoomDatabase() { + + abstract fun presenceTracingRiskLevelResultDao(): PresenceTracingRiskLevelResultDao + abstract fun traceTimeIntervalMatchDao(): TraceTimeIntervalMatchDao + + class Factory @Inject constructor(@AppContext private val context: Context) { + fun create() = Room + .databaseBuilder(context, PresenceTracingRiskDatabase::class.java, DATABASE_NAME) + .build() + } +} + +private const val DATABASE_NAME = "PresenceTracingRisk_db" diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PresenceTracingRiskRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PresenceTracingRiskRepository.kt index ebc3ad35a998ca696b2bcf8c924136048cb0d29d..0d0f7add0e2639932e641501a87abccfd259fa74 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PresenceTracingRiskRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PresenceTracingRiskRepository.kt @@ -5,13 +5,17 @@ import androidx.room.Dao import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.Insert +import androidx.room.OnConflictStrategy.REPLACE import androidx.room.PrimaryKey import androidx.room.Query -import de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase +import androidx.room.TypeConverter import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity +import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.TraceLocationCheckInRisk +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc import de.rki.coronawarnapp.util.TimeStamper import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import org.joda.time.Days import org.joda.time.Instant @@ -21,7 +25,7 @@ import javax.inject.Singleton @Singleton class PresenceTracingRiskRepository @Inject constructor( private val presenceTracingRiskCalculator: PresenceTracingRiskCalculator, - private val databaseFactory: TraceLocationDatabase.Factory, + private val databaseFactory: PresenceTracingRiskDatabase.Factory, private val timeStamper: TimeStamper ) { @@ -33,14 +37,23 @@ class PresenceTracingRiskRepository @Inject constructor( database.traceTimeIntervalMatchDao() } - private val normalizedTime = traceTimeIntervalMatchDao.allEntries().map { - it.map { + private val riskLevelResultDao by lazy { + database.presenceTracingRiskLevelResultDao() + } + + private val allMatches = traceTimeIntervalMatchDao.allMatches().map { list -> + list.map { it.toModel() } - }.map { + } + + private val normalizedTime = allMatches.map { presenceTracingRiskCalculator.calculateNormalizedTime(it) } + private val fifteenDaysAgo: Instant + get() = timeStamper.nowUTC.minus(Days.days(15).toStandardDuration()) + val traceLocationCheckInRiskStates: Flow<List<TraceLocationCheckInRisk>> = normalizedTime.map { presenceTracingRiskCalculator.calculateCheckInRiskPerDay(it) @@ -51,30 +64,66 @@ class PresenceTracingRiskRepository @Inject constructor( presenceTracingRiskCalculator.calculateAggregatedRiskPerDay(it) } - suspend fun replaceAllMatches(list: List<CheckInWarningOverlap>) { - deleteAllMatches() - addAll(list) + internal suspend fun reportSuccessfulCalculation(list: List<CheckInWarningOverlap>) { + traceTimeIntervalMatchDao.insert(list.map { it.toEntity() }) + val last14days = normalizedTime.first().filter { it.localDateUtc.isAfter(fifteenDaysAgo.toLocalDateUtc()) } + val risk = presenceTracingRiskCalculator.calculateTotalRisk(last14days) + add( + PtRiskLevelResult( + timeStamper.nowUTC, + risk + ) + ) } - private suspend fun addAll(list: List<CheckInWarningOverlap>) { - traceTimeIntervalMatchDao.insert(list.map { it.toEntity() }) + internal suspend fun deleteStaleData() { + traceTimeIntervalMatchDao.deleteOlderThan(fifteenDaysAgo.millis) + riskLevelResultDao.deleteOlderThan(fifteenDaysAgo.millis) } - suspend fun deleteStaleMatches() { - val endTime = timeStamper.nowUTC.minus(Days.days(15).toStandardDuration()) - traceTimeIntervalMatchDao.deleteOlderThan(endTime.millis) + internal suspend fun markPackageProcessed(warningPackageId: String) { + // TODO + } + + internal suspend fun deleteMatchesOfPackage(warningPackageId: String) { + traceTimeIntervalMatchDao.deleteMatchesForPackage(warningPackageId) } suspend fun deleteAllMatches() { traceTimeIntervalMatchDao.deleteAll() } + + fun latestAndLastSuccessful() = riskLevelResultDao.latestAndLastSuccessful().map { list -> + list.map { entity -> + entity.toModel() + } + } + + fun latestEntries(limit: Int) = riskLevelResultDao.latestEntries(limit).map { list -> + list.map { entity -> + entity.toModel() + } + } + + fun add(riskLevelResult: PtRiskLevelResult) { + riskLevelResultDao.insert(riskLevelResult.toEntity()) + } + + fun reportFailedCalculation() { + add( + PtRiskLevelResult( + timeStamper.nowUTC, + RiskState.CALCULATION_FAILED + ) + ) + } } @Dao interface TraceTimeIntervalMatchDao { @Query("SELECT * FROM TraceTimeIntervalMatchEntity") - fun allEntries(): Flow<List<TraceTimeIntervalMatchEntity>> + fun allMatches(): Flow<List<TraceTimeIntervalMatchEntity>> @Query("DELETE FROM TraceTimeIntervalMatchEntity") suspend fun deleteAll() @@ -82,6 +131,9 @@ interface TraceTimeIntervalMatchDao { @Query("DELETE FROM TraceTimeIntervalMatchEntity WHERE endTimeMillis < :endTimeMillis") suspend fun deleteOlderThan(endTimeMillis: Long) + @Query("DELETE FROM TraceTimeIntervalMatchEntity WHERE traceWarningPackageId = :warningPackageId") + suspend fun deleteMatchesForPackage(warningPackageId: String) + @Insert suspend fun insert(entities: List<TraceTimeIntervalMatchEntity>) } @@ -117,3 +169,56 @@ private fun TraceTimeIntervalMatchEntity.toModel() = CheckInWarningOverlap( startTime = Instant.ofEpochMilli(startTimeMillis), endTime = Instant.ofEpochMilli(endTimeMillis) ) + +@Suppress("MaxLineLength") +@Dao +interface PresenceTracingRiskLevelResultDao { + @Query("SELECT * FROM (SELECT * FROM PresenceTracingRiskLevelResultEntity ORDER BY calculatedAtMillis DESC LIMIT 1) UNION ALL SELECT * FROM (SELECT * FROM PresenceTracingRiskLevelResultEntity where riskStateCode is not 0 ORDER BY calculatedAtMillis DESC LIMIT 1)") + fun latestAndLastSuccessful(): Flow<List<PresenceTracingRiskLevelResultEntity>> + + @Query("SELECT * FROM PresenceTracingRiskLevelResultEntity ORDER BY calculatedAtMillis DESC LIMIT :limit") + fun latestEntries(limit: Int): Flow<List<PresenceTracingRiskLevelResultEntity>> + + @Insert(onConflict = REPLACE) + fun insert(entity: PresenceTracingRiskLevelResultEntity) + + @Query("DELETE FROM PresenceTracingRiskLevelResultEntity WHERE calculatedAtMillis < :calculatedAtMillis") + suspend fun deleteOlderThan(calculatedAtMillis: Long) +} + +@Entity +data class PresenceTracingRiskLevelResultEntity( + @PrimaryKey @ColumnInfo(name = "calculatedAtMillis") val calculatedAtMillis: Long, + @ColumnInfo(name = "riskStateCode")val riskState: RiskState +) + +private fun PresenceTracingRiskLevelResultEntity.toModel() = PtRiskLevelResult( + calculatedAt = Instant.ofEpochMilli((calculatedAtMillis)), + riskState = riskState +) + +private fun PtRiskLevelResult.toEntity() = PresenceTracingRiskLevelResultEntity( + calculatedAtMillis = calculatedAt.millis, + riskState = riskState +) + +class RiskStateConverter { + @TypeConverter + fun toRiskStateCode(value: Int?): RiskState? = value?.toRiskState() + + @TypeConverter + fun fromRiskStateCode(code: RiskState?): Int? = code?.toCode() + + private fun RiskState.toCode() = when (this) { + RiskState.CALCULATION_FAILED -> 0 + RiskState.LOW_RISK -> 1 + RiskState.INCREASED_RISK -> 2 + } + + private fun Int.toRiskState() = when (this) { + 0 -> RiskState.CALCULATION_FAILED + 1 -> RiskState.LOW_RISK + 2 -> RiskState.INCREASED_RISK + else -> null + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PtRiskLevelResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PtRiskLevelResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..1fa338aab48560329f4776711fde3e6439c59272 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PtRiskLevelResult.kt @@ -0,0 +1,40 @@ +package de.rki.coronawarnapp.presencetracing.risk + +import de.rki.coronawarnapp.risk.RiskState +import org.joda.time.Instant +import org.joda.time.LocalDate + +data class PtRiskLevelResult( + val calculatedAt: Instant, + val riskState: RiskState, + val presenceTracingDayRisk: List<PresenceTracingDayRisk>? = null +) { + + val wasSuccessfullyCalculated: Boolean + get() = riskState != RiskState.CALCULATION_FAILED + + val numberOfDaysWithHighRisk: Int + get() = presenceTracingDayRisk?.count { it.riskState == RiskState.INCREASED_RISK } ?: 0 + + val numberOfDaysWithLowRisk: Int + get() = presenceTracingDayRisk?.count { it.riskState == RiskState.LOW_RISK } ?: 0 + + val mostRecentDateWithHighRisk: LocalDate? + get() = presenceTracingDayRisk + ?.filter { it.riskState == RiskState.INCREASED_RISK } + ?.maxByOrNull { it.localDateUtc } + ?.localDateUtc + + val mostRecentDateWithLowRisk: LocalDate? + get() = 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 + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt index b7d4b469423c17b89646fb7e7d290469b4a38fe1..f94a7ef3cf039e41fba0903102e216fda582112c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt @@ -4,8 +4,8 @@ import com.google.android.gms.nearby.exposurenotification.ExposureWindow import com.google.android.gms.nearby.exposurenotification.Infectiousness import com.google.android.gms.nearby.exposurenotification.ReportType import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig -import de.rki.coronawarnapp.risk.result.AggregatedRiskPerDateResult -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult +import de.rki.coronawarnapp.risk.result.ExposureWindowDayRisk import de.rki.coronawarnapp.risk.result.RiskResult import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass import org.joda.time.Instant @@ -155,7 +155,7 @@ class DefaultRiskLevels @Inject constructor() : RiskLevels { override fun aggregateResults( appConfig: ExposureWindowRiskCalculationConfig, exposureWindowResultMap: Map<ExposureWindow, RiskResult> - ): AggregatedRiskResult { + ): EwAggregatedRiskResult { val uniqueDatesMillisSinceEpoch = exposureWindowResultMap.keys .map { it.dateMillisSinceEpoch } .toSet() @@ -222,7 +222,7 @@ class DefaultRiskLevels @Inject constructor() : RiskLevels { Timber.d("numberOfDaysWithHighRisk: %d", numberOfDaysWithHighRisk) - return AggregatedRiskResult( + return EwAggregatedRiskResult( totalRiskLevel = totalRiskLevel, totalMinimumDistinctEncountersWithLowRisk = totalMinimumDistinctEncountersWithLowRisk, totalMinimumDistinctEncountersWithHighRisk = totalMinimumDistinctEncountersWithHighRisk, @@ -230,16 +230,16 @@ class DefaultRiskLevels @Inject constructor() : RiskLevels { mostRecentDateWithHighRisk = mostRecentDateWithHighRisk, numberOfDaysWithLowRisk = numberOfDaysWithLowRisk, numberOfDaysWithHighRisk = numberOfDaysWithHighRisk, - aggregatedRiskPerDateResults = exposureHistory + exposureWindowDayRisks = exposureHistory ) } - private fun List<AggregatedRiskPerDateResult>.mostRecentDateForRisk(riskLevel: ProtoRiskLevel): Instant? = + private fun List<ExposureWindowDayRisk>.mostRecentDateForRisk(riskLevel: ProtoRiskLevel): Instant? = filter { it.riskLevel == riskLevel } .maxOfOrNull { it.dateMillisSinceEpoch } ?.let { Instant.ofEpochMilli(it) } - private fun List<AggregatedRiskPerDateResult>.numberOfDaysForRisk(riskLevel: ProtoRiskLevel): Int = + private fun List<ExposureWindowDayRisk>.numberOfDaysForRisk(riskLevel: ProtoRiskLevel): Int = filter { it.riskLevel == riskLevel } .size @@ -247,7 +247,7 @@ class DefaultRiskLevels @Inject constructor() : RiskLevels { appConfig: ExposureWindowRiskCalculationConfig, dateMillisSinceEpoch: Long, exposureWindowsAndResult: Map<ExposureWindow, RiskResult> - ): AggregatedRiskPerDateResult? { + ): ExposureWindowDayRisk? { // 1. Group `Exposure Windows by Date` val exposureWindowsAndResultForDate = exposureWindowsAndResult .filter { it.key.dateMillisSinceEpoch == dateMillisSinceEpoch } @@ -291,7 +291,7 @@ class DefaultRiskLevels @Inject constructor() : RiskLevels { Timber.d("minimumDistinctEncountersWithHighRisk: %d", minimumDistinctEncountersWithHighRisk) - return AggregatedRiskPerDateResult( + return ExposureWindowDayRisk( dateMillisSinceEpoch = dateMillisSinceEpoch, riskLevel = riskLevel, minimumDistinctEncountersWithLowRisk = minimumDistinctEncountersWithLowRisk, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/EwRiskLevelResult.kt similarity index 58% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelResult.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/EwRiskLevelResult.kt index 1feb415e71dd3e6ce4951b4e8dff34ccc8caa7ae..952b994c4ca78dfc7cb904d7282392e9abd789e0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelResult.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/EwRiskLevelResult.kt @@ -1,21 +1,21 @@ package de.rki.coronawarnapp.risk import com.google.android.gms.nearby.exposurenotification.ExposureWindow -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import org.joda.time.Instant -interface RiskLevelResult { +interface EwRiskLevelResult { val calculatedAt: Instant val riskState: RiskState get() = when { - aggregatedRiskResult?.isIncreasedRisk() == true -> RiskState.INCREASED_RISK - aggregatedRiskResult?.isLowRisk() == true -> RiskState.LOW_RISK + ewAggregatedRiskResult?.isIncreasedRisk() == true -> RiskState.INCREASED_RISK + ewAggregatedRiskResult?.isLowRisk() == true -> RiskState.LOW_RISK else -> RiskState.CALCULATION_FAILED } val failureReason: FailureReason? - val aggregatedRiskResult: AggregatedRiskResult? + val ewAggregatedRiskResult: EwAggregatedRiskResult? /** * This will only be filled in deviceForTester builds @@ -23,30 +23,30 @@ interface RiskLevelResult { val exposureWindows: List<ExposureWindow>? val wasSuccessfullyCalculated: Boolean - get() = aggregatedRiskResult != null + get() = ewAggregatedRiskResult != null val isIncreasedRisk: Boolean - get() = aggregatedRiskResult?.isIncreasedRisk() ?: false + get() = ewAggregatedRiskResult?.isIncreasedRisk() ?: false val matchedKeyCount: Int get() = if (isIncreasedRisk) { - aggregatedRiskResult?.totalMinimumDistinctEncountersWithHighRisk ?: 0 + ewAggregatedRiskResult?.totalMinimumDistinctEncountersWithHighRisk ?: 0 } else { - aggregatedRiskResult?.totalMinimumDistinctEncountersWithLowRisk ?: 0 + ewAggregatedRiskResult?.totalMinimumDistinctEncountersWithLowRisk ?: 0 } val daysWithEncounters: Int get() = if (isIncreasedRisk) { - aggregatedRiskResult?.numberOfDaysWithHighRisk ?: 0 + ewAggregatedRiskResult?.numberOfDaysWithHighRisk ?: 0 } else { - aggregatedRiskResult?.numberOfDaysWithLowRisk ?: 0 + ewAggregatedRiskResult?.numberOfDaysWithLowRisk ?: 0 } val lastRiskEncounterAt: Instant? get() = if (isIncreasedRisk) { - aggregatedRiskResult?.mostRecentDateWithHighRisk + ewAggregatedRiskResult?.mostRecentDateWithHighRisk } else { - aggregatedRiskResult?.mostRecentDateWithLowRisk + ewAggregatedRiskResult?.mostRecentDateWithLowRisk } enum class FailureReason(val failureCode: String) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTaskResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/EwRiskLevelTaskResult.kt similarity index 62% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTaskResult.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/EwRiskLevelTaskResult.kt index deddc698d80068ace635d18f229796dada054d07..40827be362ef6557639f2683be23ee48e7dd6234 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTaskResult.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/EwRiskLevelTaskResult.kt @@ -1,42 +1,42 @@ package de.rki.coronawarnapp.risk import com.google.android.gms.nearby.exposurenotification.ExposureWindow -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import de.rki.coronawarnapp.task.Task import org.joda.time.Instant -data class RiskLevelTaskResult( +data class EwRiskLevelTaskResult( override val calculatedAt: Instant, - override val failureReason: RiskLevelResult.FailureReason?, - override val aggregatedRiskResult: AggregatedRiskResult?, + override val failureReason: EwRiskLevelResult.FailureReason?, + override val ewAggregatedRiskResult: EwAggregatedRiskResult?, override val exposureWindows: List<ExposureWindow>? -) : Task.Result, RiskLevelResult { +) : Task.Result, EwRiskLevelResult { constructor( calculatedAt: Instant, - aggregatedRiskResult: AggregatedRiskResult, + ewAggregatedRiskResult: EwAggregatedRiskResult, exposureWindows: List<ExposureWindow>? ) : this( calculatedAt = calculatedAt, - aggregatedRiskResult = aggregatedRiskResult, + ewAggregatedRiskResult = ewAggregatedRiskResult, exposureWindows = exposureWindows, failureReason = null ) constructor( calculatedAt: Instant, - failureReason: RiskLevelResult.FailureReason + failureReason: EwRiskLevelResult.FailureReason ) : this( calculatedAt = calculatedAt, failureReason = failureReason, - aggregatedRiskResult = null, + ewAggregatedRiskResult = null, exposureWindows = null ) override fun toString(): String = "RiskLevelTaskResult(" + "calculatedAt=$calculatedAt, " + "failureReason=$failureReason, " + - "aggregatedRiskResult=$aggregatedRiskResult, " + + "ewAggregatedRiskResult=$ewAggregatedRiskResult, " + "exposureWindows.size=${exposureWindows?.size}" + ")" } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt index 7b68e5789a1697719c07e25ccc83722a3bb1f006..d09e184b9cd11199166917a2337a23bf200aaf48 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt @@ -8,6 +8,7 @@ import de.rki.coronawarnapp.datadonation.analytics.storage.TestResultDonorSettin import de.rki.coronawarnapp.datadonation.survey.Surveys import de.rki.coronawarnapp.notification.GeneralNotifications import de.rki.coronawarnapp.notification.NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID +import de.rki.coronawarnapp.risk.storage.CombinedEwPtRiskLevelResult import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.submission.SubmissionSettings @@ -42,36 +43,65 @@ class RiskLevelChangeDetector @Inject constructor( fun launch() { Timber.v("Monitoring risk level changes.") - riskLevelStorage.latestRiskLevelResults + riskLevelStorage.latestEwRiskLevelResults .map { results -> results.sortedBy { it.calculatedAt }.takeLast(2) } .filter { it.size == 2 } .onEach { Timber.v("Checking for risklevel change.") - check(it) + checkEwRiskForStateChanges(it) + } + .catch { Timber.e(it, "App config change checks failed.") } + .launchIn(appScope) + + riskLevelStorage.latestCombinedEwPtRiskLevelResults + .map { results -> + results.sortedBy { it.calculatedAt }.takeLast(2) + } + .filter { it.size == 2 } + .onEach { + Timber.v("Checking for risklevel change.") + checkCombinedRiskForStateChanges(it) } .catch { Timber.e(it, "App config change checks failed.") } .launchIn(appScope) } - private suspend fun check(changedLevels: List<RiskLevelResult>) { - val oldResult = changedLevels.first() - val newResult = changedLevels.last() + private suspend fun checkCombinedRiskForStateChanges(results: List<CombinedEwPtRiskLevelResult>) { + // TODO refactor + val oldResult = results.first() + val newResult = results.last() - val lastCheckedResult = riskLevelSettings.lastChangeCheckedRiskLevelTimestamp + val lastCheckedResult = riskLevelSettings.lastChangeCheckedRiskLevelCombinedTimestamp if (lastCheckedResult == newResult.calculatedAt) { Timber.d("We already checked this risk level change, skipping further checks.") return } - riskLevelSettings.lastChangeCheckedRiskLevelTimestamp = newResult.calculatedAt + riskLevelSettings.lastChangeCheckedRiskLevelCombinedTimestamp = newResult.calculatedAt val oldRiskState = oldResult.riskState val newRiskState = newResult.riskState - Timber.d("Last state was $oldRiskState and current state is $newRiskState") + Timber.d("Last combined state was $oldRiskState and current state is $newRiskState") // Check sending a notification when risk level changes checkSendingNotification(oldRiskState, newRiskState) + } + + private fun checkEwRiskForStateChanges(results: List<EwRiskLevelResult>) { + val oldResult = results.first() + val newResult = results.last() + + val lastCheckedResult = riskLevelSettings.lastChangeCheckedRiskLevelTimestamp + if (lastCheckedResult == newResult.calculatedAt) { + Timber.d("We already checked this risk level change, skipping further checks.") + return + } + riskLevelSettings.lastChangeCheckedRiskLevelTimestamp = newResult.calculatedAt + + val oldRiskState = oldResult.riskState + val newRiskState = newResult.riskState + Timber.d("Last state was $oldRiskState and current state is $newRiskState") // Save Survey related data based on the risk state saveSurveyRiskState(oldRiskState, newRiskState, newResult) @@ -81,30 +111,30 @@ class RiskLevelChangeDetector @Inject constructor( } private fun saveTestDonorRiskLevelAnalytics( - newRiskState: RiskLevelResult + newEwRiskState: EwRiskLevelResult ) { // Save riskLevelTurnedRedTime if not already set before for high risk detection Timber.i("riskLevelTurnedRedTime=%s", testResultDonorSettings.riskLevelTurnedRedTime.value) if (testResultDonorSettings.riskLevelTurnedRedTime.value == null) { - if (newRiskState.isIncreasedRisk) { + if (newEwRiskState.isIncreasedRisk) { testResultDonorSettings.riskLevelTurnedRedTime.update { - newRiskState.calculatedAt + newEwRiskState.calculatedAt } Timber.i( "riskLevelTurnedRedTime: newRiskState=%s, riskLevelTurnedRedTime=%s", - newRiskState.riskState, - newRiskState.calculatedAt + newEwRiskState.riskState, + newEwRiskState.calculatedAt ) } } // Save most recent date of high or low risks - if (newRiskState.riskState in listOf(RiskState.INCREASED_RISK, RiskState.LOW_RISK)) { - Timber.d("newRiskState=$newRiskState") - val lastRiskEncounterAt = newRiskState.lastRiskEncounterAt + if (newEwRiskState.riskState in listOf(RiskState.INCREASED_RISK, RiskState.LOW_RISK)) { + Timber.d("newRiskState=$newEwRiskState") + val lastRiskEncounterAt = newEwRiskState.lastRiskEncounterAt Timber.i( "mostRecentDateWithHighOrLowRiskLevel: newRiskState=%s, lastRiskEncounterAt=%s", - newRiskState.riskState, + newEwRiskState.riskState, lastRiskEncounterAt ) @@ -142,7 +172,7 @@ class RiskLevelChangeDetector @Inject constructor( private fun saveSurveyRiskState( oldRiskState: RiskState, newRiskState: RiskState, - newResult: RiskLevelResult + newResult: EwRiskLevelResult ) { if (oldRiskState == RiskState.INCREASED_RISK && newRiskState == RiskState.LOW_RISK) { tracingSettings.isUserToBeNotifiedOfLoweredRiskLevel.update { true } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelResultExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelResultExtensions.kt index 8911020fcceda4ed3392ce67d19b7d5bad108f87..c47ff8c40e84d723cf7d1585d83c48174aa81c66 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelResultExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelResultExtensions.kt @@ -1,15 +1,35 @@ package de.rki.coronawarnapp.risk import com.google.android.gms.nearby.exposurenotification.ExposureWindow -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.presencetracing.risk.PtRiskLevelResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult +import de.rki.coronawarnapp.risk.storage.CombinedEwPtRiskLevelResult import org.joda.time.Instant -fun List<RiskLevelResult>.tryLatestResultsWithDefaults(): DisplayableRiskResults { +fun List<EwRiskLevelResult>.tryLatestEwResultsWithDefaults(): DisplayableEwRiskResults { val latestCalculation = this.maxByOrNull { it.calculatedAt } - ?: InitialLowLevelRiskLevelResult + ?: EwInitialLowRiskLevelResult val lastSuccessfullyCalculated = this.filter { it.wasSuccessfullyCalculated } - .maxByOrNull { it.calculatedAt } ?: UndeterminedRiskLevelResult + .maxByOrNull { it.calculatedAt } ?: EwUndeterminedRiskLevelResult + + return DisplayableEwRiskResults( + lastCalculated = latestCalculation, + lastSuccessfullyCalculated = lastSuccessfullyCalculated + ) +} + +data class DisplayableEwRiskResults( + val lastCalculated: EwRiskLevelResult, + val lastSuccessfullyCalculated: EwRiskLevelResult +) + +fun List<CombinedEwPtRiskLevelResult>.tryLatestResultsWithDefaults(): DisplayableRiskResults { + val latestCalculation = this.maxByOrNull { it.calculatedAt } + ?: initialLowLevelEwRiskLevelResult + + val lastSuccessfullyCalculated = this.filter { it.wasSuccessfullyCalculated } + .maxByOrNull { it.calculatedAt } ?: undeterminedEwRiskLevelResult return DisplayableRiskResults( lastCalculated = latestCalculation, @@ -18,25 +38,41 @@ fun List<RiskLevelResult>.tryLatestResultsWithDefaults(): DisplayableRiskResults } data class DisplayableRiskResults( - val lastCalculated: RiskLevelResult, - val lastSuccessfullyCalculated: RiskLevelResult + val lastCalculated: CombinedEwPtRiskLevelResult, + val lastSuccessfullyCalculated: CombinedEwPtRiskLevelResult +) + +private val undeterminedEwRiskLevelResult = CombinedEwPtRiskLevelResult( + PtRiskLevelResult( + calculatedAt = Instant.EPOCH, + riskState = RiskState.CALCULATION_FAILED + ), + EwUndeterminedRiskLevelResult +) + +private val initialLowLevelEwRiskLevelResult = CombinedEwPtRiskLevelResult( + PtRiskLevelResult( + calculatedAt = Instant.now(), + riskState = RiskState.LOW_RISK + ), + EwInitialLowRiskLevelResult ) -private object InitialLowLevelRiskLevelResult : RiskLevelResult { +private object EwInitialLowRiskLevelResult : EwRiskLevelResult { override val calculatedAt: Instant = Instant.now() override val riskState: RiskState = RiskState.LOW_RISK - override val failureReason: RiskLevelResult.FailureReason? = null - override val aggregatedRiskResult: AggregatedRiskResult? = null + override val failureReason: EwRiskLevelResult.FailureReason? = null + override val ewAggregatedRiskResult: EwAggregatedRiskResult? = null override val exposureWindows: List<ExposureWindow>? = null override val matchedKeyCount: Int = 0 override val daysWithEncounters: Int = 0 } -private object UndeterminedRiskLevelResult : RiskLevelResult { +private object EwUndeterminedRiskLevelResult : EwRiskLevelResult { override val calculatedAt: Instant = Instant.EPOCH override val riskState: RiskState = RiskState.CALCULATION_FAILED - override val failureReason: RiskLevelResult.FailureReason? = null - override val aggregatedRiskResult: AggregatedRiskResult? = null + override val failureReason: EwRiskLevelResult.FailureReason? = null + override val ewAggregatedRiskResult: EwAggregatedRiskResult? = null override val exposureWindows: List<ExposureWindow>? = null override val matchedKeyCount: Int = 0 override val daysWithEncounters: Int = 0 diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelSettings.kt index 652905a0d75ad32267f8c1e4c1bb19764a9087ca..d11ccaa23b4537ca8a79865366300634dca46f94 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelSettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelSettings.kt @@ -41,11 +41,21 @@ class RiskLevelSettings @Inject constructor( putLong(PKEY_LAST_CHANGE_TO_HIGH_RISKLEVEL_TIMESTAMP, value?.millis ?: 0L) } + var lastChangeCheckedRiskLevelCombinedTimestamp: Instant? + get() = prefs.getLong(PKEY_LAST_CHANGE_CHECKED_RISKLEVEL_TIMESTAMP_COMBINED, 0L).let { + if (it != 0L) Instant.ofEpochMilli(it) else null + } + set(value) = prefs.edit { + putLong(PKEY_LAST_CHANGE_CHECKED_RISKLEVEL_TIMESTAMP_COMBINED, value?.millis ?: 0L) + } + companion object { private const val NAME_SHARED_PREFS = "risklevel_localdata" private const val PKEY_RISKLEVEL_CALC_LAST_CONFIG_ID = "risklevel.config.identifier.last" private const val PKEY_LAST_CHANGE_CHECKED_RISKLEVEL_TIMESTAMP = "PKEY_RISKLEVEL_CALC_LAST_CONFIG_ID" private const val PKEY_LAST_CHANGE_TO_HIGH_RISKLEVEL_TIMESTAMP = "PKEY_RISKLEVEL_CALC_LAST_CHANGE_TO_HIGH_RISKLEVEL" + private const val PKEY_LAST_CHANGE_CHECKED_RISKLEVEL_TIMESTAMP_COMBINED = + "PKEY_LAST_CHANGE_CHECKED_RISKLEVEL_TIMESTAMP_COMBINED" } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt index dbb6257fc3a20b4ef99fd01e3e3844e12c76a404..c79f789b7d07d76b8132f1adea7cb3fd3c114bb6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt @@ -13,8 +13,8 @@ import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetection import de.rki.coronawarnapp.presencetracing.checkins.checkout.auto.AutoCheckOut -import de.rki.coronawarnapp.risk.RiskLevelResult.FailureReason -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult.FailureReason +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.task.Task @@ -49,14 +49,14 @@ class RiskLevelTask @Inject constructor( private val submissionSettings: SubmissionSettings, private val analyticsExposureWindowCollector: AnalyticsExposureWindowCollector, private val autoCheckOut: AutoCheckOut, -) : Task<DefaultProgress, RiskLevelTaskResult> { +) : Task<DefaultProgress, EwRiskLevelTaskResult> { private val internalProgress = ConflatedBroadcastChannel<DefaultProgress>() override val progress: Flow<DefaultProgress> = internalProgress.asFlow() private var isCanceled = false - override suspend fun run(arguments: Task.Arguments): RiskLevelTaskResult = try { + override suspend fun run(arguments: Task.Arguments): EwRiskLevelTaskResult = try { Timber.d("Running with arguments=%s", arguments) autoCheckOut.apply { @@ -86,14 +86,14 @@ class RiskLevelTask @Inject constructor( internalProgress.close() } - private suspend fun determineRiskLevelResult(configData: ConfigData): RiskLevelTaskResult { + private suspend fun determineRiskLevelResult(configData: ConfigData): EwRiskLevelTaskResult { val nowUTC = timeStamper.nowUTC.also { Timber.d("The current time is %s", it) } if (submissionSettings.isAllowedToSubmitKeys && submissionSettings.hasViewedTestResult.value) { Timber.i("Positive test result and user has seen it, skip risk calculation") - return RiskLevelTaskResult( + return EwRiskLevelTaskResult( calculatedAt = nowUTC, failureReason = FailureReason.POSITIVE_TEST_RESULT ) @@ -101,7 +101,7 @@ class RiskLevelTask @Inject constructor( if (!configData.isDeviceTimeCorrect) { Timber.w("Device time is incorrect, offset: %s", configData.localOffset) - return RiskLevelTaskResult( + return EwRiskLevelTaskResult( calculatedAt = nowUTC, failureReason = FailureReason.INCORRECT_DEVICE_TIME ) @@ -109,7 +109,7 @@ class RiskLevelTask @Inject constructor( if (!isNetworkEnabled(context)) { Timber.i("Risk not calculated, internet unavailable.") - return RiskLevelTaskResult( + return EwRiskLevelTaskResult( calculatedAt = nowUTC, failureReason = FailureReason.NO_INTERNET ) @@ -117,7 +117,7 @@ class RiskLevelTask @Inject constructor( if (!enfClient.isTracingEnabled.first()) { Timber.i("Risk not calculated, tracing is disabled.") - return RiskLevelTaskResult( + return EwRiskLevelTaskResult( calculatedAt = nowUTC, failureReason = FailureReason.TRACING_OFF ) @@ -125,7 +125,7 @@ class RiskLevelTask @Inject constructor( if (areKeyPkgsOutDated(nowUTC)) { Timber.i("Risk not calculated, results are outdated.") - return RiskLevelTaskResult( + return EwRiskLevelTaskResult( calculatedAt = nowUTC, failureReason = when (backgroundJobsEnabled()) { true -> FailureReason.OUTDATED_RESULTS @@ -162,7 +162,7 @@ class RiskLevelTask @Inject constructor( } } - private suspend fun calculateRiskLevel(configData: ExposureWindowRiskCalculationConfig): RiskLevelTaskResult { + private suspend fun calculateRiskLevel(configData: ExposureWindowRiskCalculationConfig): EwRiskLevelTaskResult { Timber.tag(TAG).d("Calculating risklevel") val exposureWindows = enfClient.exposureWindows() @@ -174,9 +174,9 @@ class RiskLevelTask @Inject constructor( Timber.tag(TAG).d("Risk is not increased, continuing evaluating.") } - RiskLevelTaskResult( + EwRiskLevelTaskResult( calculatedAt = timeStamper.nowUTC, - aggregatedRiskResult = it, + ewAggregatedRiskResult = it, exposureWindows = exposureWindows ) } @@ -185,7 +185,7 @@ class RiskLevelTask @Inject constructor( private suspend fun determineRisk( appConfig: ExposureWindowRiskCalculationConfig, exposureWindows: List<ExposureWindow> - ): AggregatedRiskResult { + ): EwAggregatedRiskResult { val riskResultsPerWindow = exposureWindows.mapNotNull { window -> riskLevels.calculateRisk(appConfig, window)?.let { window to it } @@ -235,10 +235,10 @@ class RiskLevelTask @Inject constructor( class Factory @Inject constructor( private val taskByDagger: Provider<RiskLevelTask>, private val exposureDetectionTracker: ExposureDetectionTracker - ) : TaskFactory<DefaultProgress, RiskLevelTaskResult> { + ) : TaskFactory<DefaultProgress, EwRiskLevelTaskResult> { override suspend fun createConfig(): TaskFactory.Config = Config(exposureDetectionTracker) - override val taskProvider: () -> Task<DefaultProgress, RiskLevelTaskResult> = { + override val taskProvider: () -> Task<DefaultProgress, EwRiskLevelTaskResult> = { taskByDagger.get() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt index 30089d352db9a570d12661d5474a0eca9ce82047..0344e1c8bf59b03f428b5170041904fd45140b6c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt @@ -2,7 +2,7 @@ package de.rki.coronawarnapp.risk import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import de.rki.coronawarnapp.risk.result.RiskResult interface RiskLevels { @@ -15,5 +15,5 @@ interface RiskLevels { fun aggregateResults( appConfig: ExposureWindowRiskCalculationConfig, exposureWindowResultMap: Map<ExposureWindow, RiskResult> - ): AggregatedRiskResult + ): EwAggregatedRiskResult } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/EwAggregatedRiskResult.kt similarity index 86% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskResult.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/EwAggregatedRiskResult.kt index 45130825e126c6de46031398ab03f32f59205029..b17b64e0dca10625af633a101be090b53ce2700c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskResult.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/EwAggregatedRiskResult.kt @@ -4,7 +4,7 @@ import de.rki.coronawarnapp.risk.ProtoRiskLevel import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass import org.joda.time.Instant -data class AggregatedRiskResult( +data class EwAggregatedRiskResult( val totalRiskLevel: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel, val totalMinimumDistinctEncountersWithLowRisk: Int, val totalMinimumDistinctEncountersWithHighRisk: Int, @@ -12,7 +12,7 @@ data class AggregatedRiskResult( val mostRecentDateWithHighRisk: Instant?, val numberOfDaysWithLowRisk: Int, val numberOfDaysWithHighRisk: Int, - var aggregatedRiskPerDateResults: List<AggregatedRiskPerDateResult>? = null + var exposureWindowDayRisks: List<ExposureWindowDayRisk>? = null ) { fun isIncreasedRisk(): Boolean = totalRiskLevel == ProtoRiskLevel.HIGH diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskPerDateResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/ExposureWindowDayRisk.kt similarity index 93% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskPerDateResult.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/ExposureWindowDayRisk.kt index b2aed66ee269f3a147fd0f218c5872c31bb5fbee..a4dc9cbe72860f1572d0d47802b4847b3a837505 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskPerDateResult.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/ExposureWindowDayRisk.kt @@ -4,7 +4,7 @@ import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParamete import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc import org.joda.time.Instant -data class AggregatedRiskPerDateResult( +data class ExposureWindowDayRisk( val dateMillisSinceEpoch: Long, val riskLevel: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel, val minimumDistinctEncountersWithLowRisk: Int, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt index 1362d76cc6974631d978732ed469678f6d7d4c36..a0844cd0b8f1415b491cdf1fd3dfc16133335a3e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt @@ -3,25 +3,30 @@ package de.rki.coronawarnapp.risk.storage import androidx.annotation.VisibleForTesting import de.rki.coronawarnapp.presencetracing.risk.PresenceTracingDayRisk import de.rki.coronawarnapp.presencetracing.risk.PresenceTracingRiskRepository +import de.rki.coronawarnapp.presencetracing.risk.PtRiskLevelResult import de.rki.coronawarnapp.presencetracing.risk.mapToRiskState -import de.rki.coronawarnapp.risk.RiskLevelResult -import de.rki.coronawarnapp.risk.RiskLevelTaskResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult +import de.rki.coronawarnapp.risk.EwRiskLevelTaskResult import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.TraceLocationCheckInRisk -import de.rki.coronawarnapp.risk.result.AggregatedRiskPerDateResult +import de.rki.coronawarnapp.risk.result.ExposureWindowDayRisk import de.rki.coronawarnapp.risk.storage.internal.RiskResultDatabase import de.rki.coronawarnapp.risk.storage.internal.riskresults.PersistedRiskLevelResultDao import de.rki.coronawarnapp.risk.storage.internal.riskresults.toPersistedAggregatedRiskPerDateResult import de.rki.coronawarnapp.risk.storage.internal.riskresults.toPersistedRiskResult import de.rki.coronawarnapp.risk.storage.internal.windows.PersistedExposureWindowDaoWrapper -import de.rki.coronawarnapp.util.flow.combine import de.rki.coronawarnapp.util.flow.shareLatest import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import org.joda.time.Instant +import org.joda.time.LocalDate import timber.log.Timber import java.lang.reflect.Modifier.PRIVATE +import kotlin.math.max +import de.rki.coronawarnapp.util.flow.combine as flowCombine abstract class BaseRiskLevelStorage constructor( private val riskResultDatabaseFactory: RiskResultDatabase.Factory, @@ -38,7 +43,7 @@ abstract class BaseRiskLevelStorage constructor( private suspend fun List<PersistedRiskLevelResultDao>.combineWithWindows( givenWindows: List<PersistedExposureWindowDaoWrapper>? - ): List<RiskLevelTaskResult> { + ): List<EwRiskLevelTaskResult> { if (this.isEmpty()) return emptyList() val windows = if (givenWindows != null) { @@ -61,7 +66,7 @@ abstract class BaseRiskLevelStorage constructor( } } - final override val allRiskLevelResults: Flow<List<RiskLevelResult>> = combine( + final override val allEwRiskLevelResults: Flow<List<EwRiskLevelResult>> = combine( riskResultsTables.allEntries(), exposureWindowsTables.allEntries() ) { allRiskResults, allWindows -> @@ -74,42 +79,35 @@ abstract class BaseRiskLevelStorage constructor( } .shareLatest(tag = TAG, scope = scope) - override val latestRiskLevelResults: Flow<List<RiskLevelResult>> = riskResultsTables.latestEntries(2) + override val latestEwRiskLevelResults: Flow<List<EwRiskLevelResult>> = riskResultsTables.latestEntries(2) .map { results -> Timber.v("Mapping latestRiskLevelResults:\n%s", results.joinToString("\n")) results.combineWithWindows(null) } .shareLatest(tag = TAG, scope = scope) - override val latestAndLastSuccessful: Flow<List<RiskLevelResult>> = riskResultsTables.latestAndLastSuccessful() - .map { results -> - Timber.v("Mapping latestAndLastSuccessful:\n%s", results.joinToString("\n")) - results.combineWithWindows(null) - } - .shareLatest(tag = TAG, scope = scope) - - override suspend fun storeResult(result: RiskLevelResult) { - Timber.d("Storing result (exposureWindows.size=%s)", result.exposureWindows?.size) + override suspend fun storeResult(resultEw: EwRiskLevelResult) { + Timber.d("Storing result (exposureWindows.size=%s)", resultEw.exposureWindows?.size) val storedResultId = try { val startTime = System.currentTimeMillis() - require(result.aggregatedRiskResult == null || result.failureReason == null) { + require(resultEw.ewAggregatedRiskResult == null || resultEw.failureReason == null) { "A result needs to have either an aggregatedRiskResult or a failureReason, not both!" } - val resultToPersist = result.toPersistedRiskResult() + val resultToPersist = resultEw.toPersistedRiskResult() riskResultsTables.insertEntry(resultToPersist).also { Timber.d("Storing RiskLevelResult took %dms.", (System.currentTimeMillis() - startTime)) } - result.aggregatedRiskResult?.aggregatedRiskPerDateResults?.let { + resultEw.ewAggregatedRiskResult?.exposureWindowDayRisks?.let { insertAggregatedRiskPerDateResults(it) } resultToPersist.id } catch (e: Exception) { - Timber.e(e, "Failed to store latest result: %s", result) + Timber.e(e, "Failed to store latest result: %s", resultEw) throw e } @@ -125,13 +123,13 @@ abstract class BaseRiskLevelStorage constructor( } Timber.d("Storing exposure windows.") - storeExposureWindows(storedResultId = storedResultId, result) + storeExposureWindows(storedResultId = storedResultId, resultEw) Timber.d("Deleting orphaned exposure windows.") deletedOrphanedExposureWindows() } - override val aggregatedRiskPerDateResults: Flow<List<AggregatedRiskPerDateResult>> by lazy { + override val ewDayRiskStates: Flow<List<ExposureWindowDayRisk>> by lazy { aggregatedRiskPerDateResultTables.allEntries() .map { it.map { persistedAggregatedRiskPerDateResult -> @@ -142,12 +140,12 @@ abstract class BaseRiskLevelStorage constructor( } private suspend fun insertAggregatedRiskPerDateResults( - aggregatedRiskPerDateResults: List<AggregatedRiskPerDateResult> + exposureWindowDayRisks: List<ExposureWindowDayRisk> ) { - Timber.d("insertAggregatedRiskPerDateResults(aggregatedRiskPerDateResults=%s)", aggregatedRiskPerDateResults) + Timber.d("insertAggregatedRiskPerDateResults(aggregatedRiskPerDateResults=%s)", exposureWindowDayRisks) try { aggregatedRiskPerDateResultTables.insertRisk( - aggregatedRiskPerDateResults.map { + exposureWindowDayRisks.map { it.toPersistedAggregatedRiskPerDateResult() } ) @@ -156,7 +154,7 @@ abstract class BaseRiskLevelStorage constructor( } } - override suspend fun deleteAggregatedRiskPerDateResults(results: List<AggregatedRiskPerDateResult>) { + override suspend fun deleteAggregatedRiskPerDateResults(results: List<ExposureWindowDayRisk>) { Timber.d("deleteAggregatedRiskPerDateResults(results=%s)", results) try { aggregatedRiskPerDateResultTables.delete(results.map { it.toPersistedAggregatedRiskPerDateResult() }) @@ -168,23 +166,105 @@ abstract class BaseRiskLevelStorage constructor( override val traceLocationCheckInRiskStates: Flow<List<TraceLocationCheckInRisk>> = presenceTracingRiskRepository.traceLocationCheckInRiskStates - override val presenceTracingDayRisk: Flow<List<PresenceTracingDayRisk>> = + override val ptDayRiskStates: Flow<List<PresenceTracingDayRisk>> = presenceTracingRiskRepository.presenceTracingDayRisk - override val aggregatedDayRisk: Flow<List<AggregatedDayRisk>> - get() = combine( - presenceTracingDayRisk, - aggregatedRiskPerDateResults + override val combinedEwPtDayRisk: Flow<List<CombinedEwPtDayRisk>> + get() = flowCombine( + ptDayRiskStates, + ewDayRiskStates ) { ptRiskList, ewRiskList -> combineRisk(ptRiskList, ewRiskList) } - internal abstract suspend fun storeExposureWindows(storedResultId: String, result: RiskLevelResult) + override val latestAndLastSuccessfulEwRiskLevelResult: Flow<List<EwRiskLevelResult>> = riskResultsTables + .latestAndLastSuccessful() + .map { results -> + Timber.v("Mapping latestAndLastSuccessful:\n%s", results.joinToString("\n")) + results.combineWithWindows(null) + } + .shareLatest(tag = TAG, scope = scope) + + private val latestAndLastSuccessfulPtRiskLevelResult: Flow<List<PtRiskLevelResult>> = + presenceTracingRiskRepository + .latestAndLastSuccessful() + .shareLatest(tag = TAG, scope = scope) + + // TODO maybe refactor + override val latestAndLastSuccessfulCombinedEwPtRiskLevelResult: Flow<List<CombinedEwPtRiskLevelResult>> + get() = combine( + latestAndLastSuccessfulEwRiskLevelResult, + latestAndLastSuccessfulPtRiskLevelResult + ) { ewRiskLevelResults, ptRiskLevelResults -> + val latestEwResult = ewRiskLevelResults.maxByOrNull { it.calculatedAt } + val latestPtResult = ptRiskLevelResults.maxByOrNull { it.calculatedAt } + val combinedList = mutableListOf<CombinedEwPtRiskLevelResult>() + if (latestEwResult != null && latestPtResult != null) { + combinedList.add( + CombinedEwPtRiskLevelResult( + ewRiskLevelResult = latestEwResult, + ptRiskLevelResult = latestPtResult + ) + ) + } + val lastSuccessfulEwResult = ewRiskLevelResults + .filter { it.wasSuccessfullyCalculated }.maxByOrNull { it.calculatedAt } + val lastSuccessfulPtResult = ptRiskLevelResults + .filter { it.wasSuccessfullyCalculated }.maxByOrNull { it.calculatedAt } + if (lastSuccessfulEwResult != null && lastSuccessfulPtResult != null) { + combinedList.add( + CombinedEwPtRiskLevelResult( + ewRiskLevelResult = lastSuccessfulEwResult, + // current ptDayRiskStates belong to the last successful calculation - ugly + ptRiskLevelResult = lastSuccessfulPtResult.copy( + presenceTracingDayRisk = ptDayRiskStates.first() + ) + ) + ) + } + combinedList + } + + private val latestPtRiskLevelResults: Flow<List<PtRiskLevelResult>> = + presenceTracingRiskRepository + .latestEntries(2) + .shareLatest(tag = TAG, scope = scope) + + override val latestCombinedEwPtRiskLevelResults: Flow<List<CombinedEwPtRiskLevelResult>> + get() = combine( + latestEwRiskLevelResults, + latestPtRiskLevelResults + ) { ewRiskLevelResults, ptRiskLevelResults -> + val latestEwResult = ewRiskLevelResults.maxByOrNull { it.calculatedAt } + val latestPtResult = ptRiskLevelResults.maxByOrNull { it.calculatedAt } + val olderEwResult = ewRiskLevelResults.maxByOrNull { it.calculatedAt } + val olderPtResult = ptRiskLevelResults.maxByOrNull { it.calculatedAt } + val combinedList = mutableListOf<CombinedEwPtRiskLevelResult>() + if (latestEwResult != null && latestPtResult != null) { + combinedList.add( + CombinedEwPtRiskLevelResult( + ewRiskLevelResult = latestEwResult, + ptRiskLevelResult = latestPtResult + ) + ) + } + if (olderEwResult != null && olderPtResult != null) { + combinedList.add( + CombinedEwPtRiskLevelResult( + ewRiskLevelResult = olderEwResult, + ptRiskLevelResult = olderPtResult + ) + ) + } + combinedList + } + + internal abstract suspend fun storeExposureWindows(storedResultId: String, resultEw: EwRiskLevelResult) internal abstract suspend fun deletedOrphanedExposureWindows() override suspend fun clear() { - Timber.w("clear() - Clearing stored riskleve/exposure-detection results.") + Timber.w("clear() - Clearing stored risklevel/exposure-detection results.") database.clearAllTables() } @@ -196,13 +276,13 @@ abstract class BaseRiskLevelStorage constructor( @VisibleForTesting(otherwise = PRIVATE) internal fun combineRisk( ptRiskList: List<PresenceTracingDayRisk>, - ewRiskList: List<AggregatedRiskPerDateResult> -): List<AggregatedDayRisk> { - val allDates = ptRiskList.map { it.localDateUtc }.plus(ewRiskList.map { it.localDateUtc }).distinct() + exposureWindowDayRiskList: List<ExposureWindowDayRisk> +): List<CombinedEwPtDayRisk> { + val allDates = ptRiskList.map { it.localDateUtc }.plus(exposureWindowDayRiskList.map { it.localDateUtc }).distinct() return allDates.map { date -> val ptRisk = ptRiskList.find { it.localDateUtc == date } - val ewRisk = ewRiskList.find { it.localDateUtc == date } - AggregatedDayRisk( + val ewRisk = exposureWindowDayRiskList.find { it.localDateUtc == date } + CombinedEwPtDayRisk( date, max( ptRisk?.riskState, @@ -212,9 +292,19 @@ internal fun combineRisk( } } -@VisibleForTesting(otherwise = PRIVATE) internal fun max(left: RiskState?, right: RiskState?): RiskState { return if (left == RiskState.INCREASED_RISK || right == RiskState.INCREASED_RISK) RiskState.INCREASED_RISK else if (left == RiskState.LOW_RISK || right == RiskState.LOW_RISK) RiskState.LOW_RISK else RiskState.CALCULATION_FAILED } + +internal fun max(left: Instant, right: Instant): Instant { + return Instant.ofEpochMilli(max(left.millis, right.millis)) +} + +internal fun max(left: LocalDate?, right: LocalDate?): LocalDate? { + if (left == null) return right + if (right == null) return left + return if (left.isAfter(right)) left + else right +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/RiskLevelStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/RiskLevelStorage.kt index a458989c8441d9ef9246b1c7d06eb2bc9aea1121..46d4366ef381eaf6cd2d4d992692f80ffebec0d6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/RiskLevelStorage.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/RiskLevelStorage.kt @@ -1,71 +1,129 @@ package de.rki.coronawarnapp.risk.storage import de.rki.coronawarnapp.presencetracing.risk.PresenceTracingDayRisk -import de.rki.coronawarnapp.risk.RiskLevelResult +import de.rki.coronawarnapp.presencetracing.risk.PtRiskLevelResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.TraceLocationCheckInRisk -import de.rki.coronawarnapp.risk.result.AggregatedRiskPerDateResult +import de.rki.coronawarnapp.risk.result.ExposureWindowDayRisk +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc import kotlinx.coroutines.flow.Flow +import org.joda.time.Instant import org.joda.time.LocalDate interface RiskLevelStorage { - /** + /** EXPOSURE WINDOW RISK RESULT * All currently available risk results. * This is an expensive operation on tester builds due to mapping all available windows. * Newest item first. */ - val allRiskLevelResults: Flow<List<RiskLevelResult>> + val allEwRiskLevelResults: Flow<List<EwRiskLevelResult>> - /** + /** EXPOSURE WINDOW RISK RESULT * The newest 2 results. - * Use by the risklevel detector to check for state changes (LOW/INCREASED RISK). + * Use by the risk level detector to check for state changes (LOW/INCREASED RISK), + * collecting data for analytics and survey. * Can be 0-2 entries. * Newest item first. */ - val latestRiskLevelResults: Flow<List<RiskLevelResult>> + val latestEwRiskLevelResults: Flow<List<EwRiskLevelResult>> - /** - * The newest result, and the last successfully result result. - * Used by the tracing info cards in home and details screen. + /** COMBINED RISK RESULT + * The newest 2 results. + * Use by the risk level detector to check for state changes (LOW/INCREASED RISK) triggering NOTIFICATION. + * Can be 0-2 entries. + * Newest item first. + */ + val latestCombinedEwPtRiskLevelResults: Flow<List<CombinedEwPtRiskLevelResult>> + + /** EXPOSURE WINDOW RISK RESULT + * The newest result, and the last successfully result. + * Used only for analytics + * Can be 0-2 entries. + * Newest item first. + */ + val latestAndLastSuccessfulEwRiskLevelResult: Flow<List<EwRiskLevelResult>> + + /** COMBINED RISK RESULT + * The newest result, and the last successfully result for ew and pt combined. + * Used for TRACING info cards in HOME and DETAILS SCREEN. * Can be 0-2 entries. * Newest item first. */ - val latestAndLastSuccessful: Flow<List<RiskLevelResult>> + val latestAndLastSuccessfulCombinedEwPtRiskLevelResult: Flow<List<CombinedEwPtRiskLevelResult>> - /** + /** EXPOSURE WINDOW RISK RESULT * Risk level per date/day * Used by contact diary overview * Item with newest date first. */ - val aggregatedRiskPerDateResults: Flow<List<AggregatedRiskPerDateResult>> + val ewDayRiskStates: Flow<List<ExposureWindowDayRisk>> - /** + /** PRESENCE TRACING RISK RESULT * Risk level per date/day and checkIn * Used by contact diary overview */ val traceLocationCheckInRiskStates: Flow<List<TraceLocationCheckInRisk>> - /** + /** PRESENCE TRACING RISK RESULT * Risk level per date/day aggregated over check-ins - * Used by contact diary overview */ - val presenceTracingDayRisk: Flow<List<PresenceTracingDayRisk>> + val ptDayRiskStates: Flow<List<PresenceTracingDayRisk>> - /** + /** COMBINED RISK RESULT * Risk level per date/day aggregated form Exposure Windows and Presence Tracing - * Used by contact diary overview */ - val aggregatedDayRisk: Flow<List<AggregatedDayRisk>> + val combinedEwPtDayRisk: Flow<List<CombinedEwPtDayRisk>> - suspend fun deleteAggregatedRiskPerDateResults(results: List<AggregatedRiskPerDateResult>) + suspend fun deleteAggregatedRiskPerDateResults(results: List<ExposureWindowDayRisk>) - suspend fun storeResult(result: RiskLevelResult) + suspend fun storeResult(resultEw: EwRiskLevelResult) suspend fun clear() } -data class AggregatedDayRisk( +data class CombinedEwPtDayRisk( val localDate: LocalDate, val riskState: RiskState ) + +data class CombinedEwPtRiskLevelResult( + val ptRiskLevelResult: PtRiskLevelResult, + val ewRiskLevelResult: EwRiskLevelResult +) { + + val riskState: RiskState = max(ptRiskLevelResult.riskState, ewRiskLevelResult.riskState) + + val wasSuccessfullyCalculated: Boolean + get() = ewRiskLevelResult.ewAggregatedRiskResult != null && + ptRiskLevelResult.riskState != RiskState.CALCULATION_FAILED + + val calculatedAt: Instant = max(ewRiskLevelResult.calculatedAt, ptRiskLevelResult.calculatedAt) + + val daysWithEncounters: Int + get() = when (riskState) { + RiskState.INCREASED_RISK -> { + (ewRiskLevelResult.ewAggregatedRiskResult?.numberOfDaysWithHighRisk ?: 0) + + ptRiskLevelResult.numberOfDaysWithHighRisk + } + RiskState.LOW_RISK -> { + (ewRiskLevelResult.ewAggregatedRiskResult?.numberOfDaysWithLowRisk ?: 0) + + ptRiskLevelResult.numberOfDaysWithLowRisk + } + else -> 0 + } + + val lastRiskEncounterAt: LocalDate? + get() = if (riskState == RiskState.INCREASED_RISK) { + max( + ewRiskLevelResult.ewAggregatedRiskResult?.mostRecentDateWithHighRisk?.toLocalDateUtc(), + ptRiskLevelResult.mostRecentDateWithHighRisk + ) + } else { + max( + ewRiskLevelResult.ewAggregatedRiskResult?.mostRecentDateWithLowRisk?.toLocalDateUtc(), + ptRiskLevelResult.mostRecentDateWithLowRisk + ) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/internal/riskresults/PersistedAggregatedRiskPerDateResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/internal/riskresults/PersistedAggregatedRiskPerDateResult.kt index c3693602599d42d9f97607853ed7221c25172487..dcd8a3bcae175261ad7248518243fbf16c6209f6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/internal/riskresults/PersistedAggregatedRiskPerDateResult.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/internal/riskresults/PersistedAggregatedRiskPerDateResult.kt @@ -3,7 +3,7 @@ package de.rki.coronawarnapp.risk.storage.internal.riskresults import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import de.rki.coronawarnapp.risk.result.AggregatedRiskPerDateResult +import de.rki.coronawarnapp.risk.result.ExposureWindowDayRisk import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass @Suppress("MaxLineLength") @@ -14,8 +14,8 @@ data class PersistedAggregatedRiskPerDateResult( @ColumnInfo(name = "minimumDistinctEncountersWithLowRisk") val minimumDistinctEncountersWithLowRisk: Int, @ColumnInfo(name = "minimumDistinctEncountersWithHighRisk") val minimumDistinctEncountersWithHighRisk: Int ) { - fun toAggregatedRiskPerDateResult(): AggregatedRiskPerDateResult = - AggregatedRiskPerDateResult( + fun toAggregatedRiskPerDateResult(): ExposureWindowDayRisk = + ExposureWindowDayRisk( dateMillisSinceEpoch = dateMillisSinceEpoch, riskLevel = riskLevel, minimumDistinctEncountersWithLowRisk = minimumDistinctEncountersWithLowRisk, @@ -23,7 +23,7 @@ data class PersistedAggregatedRiskPerDateResult( ) } -fun AggregatedRiskPerDateResult.toPersistedAggregatedRiskPerDateResult(): PersistedAggregatedRiskPerDateResult = +fun ExposureWindowDayRisk.toPersistedAggregatedRiskPerDateResult(): PersistedAggregatedRiskPerDateResult = PersistedAggregatedRiskPerDateResult( dateMillisSinceEpoch = dateMillisSinceEpoch, riskLevel = riskLevel, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/internal/riskresults/PersistedRiskLevelResultDao.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/internal/riskresults/PersistedRiskLevelResultDao.kt index 1be333a7f979ebae4eb0c95cb2fda7c4bf9e4616..b3d26710ed0f05c9869cd5dd36a3d3edc91d9f0d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/internal/riskresults/PersistedRiskLevelResultDao.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/internal/riskresults/PersistedRiskLevelResultDao.kt @@ -5,9 +5,9 @@ import androidx.room.Embedded import androidx.room.Entity import androidx.room.PrimaryKey import androidx.room.TypeConverter -import de.rki.coronawarnapp.risk.RiskLevelResult.FailureReason -import de.rki.coronawarnapp.risk.RiskLevelTaskResult -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult.FailureReason +import de.rki.coronawarnapp.risk.EwRiskLevelTaskResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import de.rki.coronawarnapp.risk.storage.internal.windows.PersistedExposureWindowDaoWrapper import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping import org.joda.time.Instant @@ -24,9 +24,9 @@ data class PersistedRiskLevelResultDao( fun toRiskResult(exposureWindows: List<PersistedExposureWindowDaoWrapper>? = null) = when { aggregatedRiskResult != null -> { - RiskLevelTaskResult( + EwRiskLevelTaskResult( calculatedAt = calculatedAt, - aggregatedRiskResult = aggregatedRiskResult.toAggregatedRiskResult(), + ewAggregatedRiskResult = aggregatedRiskResult.toAggregatedRiskResult(), exposureWindows = exposureWindows?.map { it.toExposureWindow() } ) } @@ -34,7 +34,7 @@ data class PersistedRiskLevelResultDao( if (failureReason == null) { Timber.e("Entry contained no aggregateResult and no failure reason, shouldn't happen.") } - RiskLevelTaskResult( + EwRiskLevelTaskResult( calculatedAt = calculatedAt, failureReason = failureReason ?: FailureReason.UNKNOWN ) @@ -58,7 +58,7 @@ data class PersistedRiskLevelResultDao( val numberOfDaysWithHighRisk: Int ) { - fun toAggregatedRiskResult() = AggregatedRiskResult( + fun toAggregatedRiskResult() = EwAggregatedRiskResult( totalRiskLevel = totalRiskLevel, totalMinimumDistinctEncountersWithLowRisk = totalMinimumDistinctEncountersWithLowRisk, totalMinimumDistinctEncountersWithHighRisk = totalMinimumDistinctEncountersWithHighRisk, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/internal/riskresults/PersistedRiskResultDaoExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/internal/riskresults/PersistedRiskResultDaoExtensions.kt index 75d9d80c869a7f954caff5c62d03dd6dc9066a37..51341811a686c78055c84342611cb8fdd565318d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/internal/riskresults/PersistedRiskResultDaoExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/internal/riskresults/PersistedRiskResultDaoExtensions.kt @@ -1,24 +1,25 @@ package de.rki.coronawarnapp.risk.storage.internal.riskresults -import de.rki.coronawarnapp.risk.RiskLevelResult -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import java.util.UUID -fun RiskLevelResult.toPersistedRiskResult( +fun EwRiskLevelResult.toPersistedRiskResult( id: String = UUID.randomUUID().toString() ) = PersistedRiskLevelResultDao( id = id, calculatedAt = calculatedAt, - aggregatedRiskResult = aggregatedRiskResult?.toPersistedAggregatedRiskResult(), + aggregatedRiskResult = ewAggregatedRiskResult?.toPersistedAggregatedRiskResult(), failureReason = failureReason ) -fun AggregatedRiskResult.toPersistedAggregatedRiskResult() = PersistedRiskLevelResultDao.PersistedAggregatedRiskResult( - totalRiskLevel = totalRiskLevel, - totalMinimumDistinctEncountersWithLowRisk = totalMinimumDistinctEncountersWithLowRisk, - totalMinimumDistinctEncountersWithHighRisk = totalMinimumDistinctEncountersWithHighRisk, - mostRecentDateWithLowRisk = mostRecentDateWithLowRisk, - mostRecentDateWithHighRisk = mostRecentDateWithHighRisk, - numberOfDaysWithLowRisk = numberOfDaysWithLowRisk, - numberOfDaysWithHighRisk = numberOfDaysWithHighRisk -) +fun EwAggregatedRiskResult.toPersistedAggregatedRiskResult() = + PersistedRiskLevelResultDao.PersistedAggregatedRiskResult( + totalRiskLevel = totalRiskLevel, + totalMinimumDistinctEncountersWithLowRisk = totalMinimumDistinctEncountersWithLowRisk, + totalMinimumDistinctEncountersWithHighRisk = totalMinimumDistinctEncountersWithHighRisk, + mostRecentDateWithLowRisk = mostRecentDateWithLowRisk, + mostRecentDateWithHighRisk = mostRecentDateWithHighRisk, + numberOfDaysWithLowRisk = numberOfDaysWithLowRisk, + numberOfDaysWithHighRisk = numberOfDaysWithHighRisk + ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt index 8c391b14abbece0143e3a26f32b4924d4b2b992d..a12be64f0bd184ca6149d1d56e8e598d1382f6b2 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt @@ -7,8 +7,8 @@ import de.rki.coronawarnapp.R import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.tracing.TracingProgress import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat -import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc import org.joda.time.Instant +import org.joda.time.LocalDate import org.joda.time.format.DateTimeFormat sealed class TracingState { @@ -30,7 +30,7 @@ data class IncreasedRisk( override val riskState: RiskState, override val isInDetailsMode: Boolean, val lastExposureDetectionTime: Instant?, - val lastEncounterAt: Instant?, + val lastEncounterAt: LocalDate?, val allowManualUpdate: Boolean, val daysWithEncounters: Int ) : TracingState() { @@ -81,7 +81,7 @@ data class IncreasedRisk( return context.getString( stringRes, - lastEncounterAt.toLocalDateUtc().toString(DateTimeFormat.mediumDate()) + lastEncounterAt.toString(DateTimeFormat.mediumDate()) ) } } @@ -91,7 +91,7 @@ data class LowRisk( override val riskState: RiskState, override val isInDetailsMode: Boolean, val lastExposureDetectionTime: Instant?, - val lastEncounterAt: Instant?, + val lastEncounterAt: LocalDate?, val allowManualUpdate: Boolean, val daysWithEncounters: Int, val daysSinceInstallation: Long @@ -149,7 +149,7 @@ data class LowRisk( return context.getString( stringRes, - lastEncounterAt.toLocalDateUtc().toString(DateTimeFormat.mediumDate()) + lastEncounterAt.toString(DateTimeFormat.mediumDate()) ) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt index c5168a647055003923754ed9b61cc2bf9c7ed3b5..c3d9018fc884b95d644643eeb8f0f713e0fcc0c8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt @@ -36,7 +36,7 @@ class TracingStateProvider @AssistedInject constructor( tracingRepository.tracingProgress.onEach { Timber.v("tracingProgress: $it") }, - riskLevelStorage.latestAndLastSuccessful.onEach { + riskLevelStorage.latestAndLastSuccessfulCombinedEwPtRiskLevelResult.onEach { Timber.v("riskLevelResults: $it") }, exposureDetectionTracker.latestSubmission().onEach { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragmentViewModel.kt index 66db208208dd0bf51abb16c562e5c0067ebd626a..eba70d84b142b805a168c7253e5c67b18fd0779e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragmentViewModel.kt @@ -75,7 +75,7 @@ class TracingDetailsFragmentViewModel @AssistedInject constructor( val buttonStates: LiveData<TracingDetailsState> = combine( tracingStatus.generalStatus, - riskLevelStorage.latestAndLastSuccessful, + riskLevelStorage.latestAndLastSuccessfulCombinedEwPtRiskLevelResult, backgroundModeStatus.isAutoModeEnabled ) { status, riskLevelResults, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt index 41de9b9285a31174afd8b13561af92100c8f02f7..f58268644a91e315a23dce10bd13513da1be2568 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt @@ -17,6 +17,7 @@ import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsFailedCa import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsIncreasedRiskBox import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsLowRiskBox import de.rki.coronawarnapp.tracing.ui.details.items.survey.UserSurveyBox +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.onCompletion @@ -36,7 +37,7 @@ class TracingDetailsItemProvider @Inject constructor( val state: Flow<List<DetailsItem>> = combine( tracingStatus.generalStatus, - riskLevelStorage.latestAndLastSuccessful, + riskLevelStorage.latestAndLastSuccessfulCombinedEwPtRiskLevelResult, surveys.availableSurveys ) { status, riskLevelResults, @@ -47,7 +48,7 @@ class TracingDetailsItemProvider @Inject constructor( mutableListOf<DetailsItem>().apply { if (status != Status.TRACING_INACTIVE && latestCalc.riskState == RiskState.LOW_RISK && - latestCalc.matchedKeyCount > 0 + latestCalc.ewRiskLevelResult.matchedKeyCount > 0 ) { add(AdditionalInfoLowRiskBox.Item) } @@ -81,11 +82,11 @@ class TracingDetailsItemProvider @Inject constructor( } latestCalc.riskState == RiskState.LOW_RISK -> DetailsLowRiskBox.Item( riskState = latestCalc.riskState, - matchedKeyCount = latestCalc.matchedKeyCount + matchedKeyCount = latestCalc.ewRiskLevelResult.matchedKeyCount ) latestCalc.riskState == RiskState.INCREASED_RISK -> DetailsIncreasedRiskBox.Item( riskState = latestCalc.riskState, - lastEncounteredAt = latestCalc.lastRiskEncounterAt ?: Instant.EPOCH + lastEncounteredAt = latestCalc.lastRiskEncounterAt ?: Instant.EPOCH.toLocalDateUtc() ) else -> null }?.let { add(it) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/riskdetails/DetailsIncreasedRiskBox.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/riskdetails/DetailsIncreasedRiskBox.kt index 73ea588e229fccd5740246d8172b85ac9b88feaf..fdfac32a385688acbfbe52f346a843448606e288 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/riskdetails/DetailsIncreasedRiskBox.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/riskdetails/DetailsIncreasedRiskBox.kt @@ -8,7 +8,7 @@ import de.rki.coronawarnapp.databinding.TracingDetailsItemRiskdetailsIncreasedVi import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.tracing.ui.details.TracingDetailsAdapter import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsIncreasedRiskBox.Item -import org.joda.time.Instant +import org.joda.time.LocalDate class DetailsIncreasedRiskBox( parent: ViewGroup, @@ -35,7 +35,7 @@ class DetailsIncreasedRiskBox( data class Item( val riskState: RiskState, - val lastEncounteredAt: Instant + val lastEncounteredAt: LocalDate ) : RiskDetailsStateItem { fun getRiskDetailsRiskLevelBody(context: Context): String { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt index 4155d9059992f459dce2ab89167997d1dc11db11..aa58e0c9544ef0f81dc43f4df15ece862552de05 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt @@ -14,10 +14,10 @@ import de.rki.coronawarnapp.ui.eventregistration.organizer.category.TraceLocatio import de.rki.coronawarnapp.ui.eventregistration.organizer.category.TraceLocationCategoryFragmentModule import de.rki.coronawarnapp.ui.eventregistration.organizer.create.TraceLocationCreateFragment import de.rki.coronawarnapp.ui.eventregistration.organizer.create.TraceLocationCreateFragmentModule -import de.rki.coronawarnapp.ui.eventregistration.organizer.qrinfo.TraceLocationQRInfoFragment -import de.rki.coronawarnapp.ui.eventregistration.organizer.qrinfo.TraceLocationQRInfoFragmentModule import de.rki.coronawarnapp.ui.eventregistration.organizer.list.TraceLocationsFragment import de.rki.coronawarnapp.ui.eventregistration.organizer.list.TraceLocationsFragmentModule +import de.rki.coronawarnapp.ui.eventregistration.organizer.qrinfo.TraceLocationQRInfoFragment +import de.rki.coronawarnapp.ui.eventregistration.organizer.qrinfo.TraceLocationQRInfoFragmentModule @Module internal abstract class EventRegistrationUIModule { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/items/PastCheckInVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/items/PastCheckInVH.kt index 5088e56c27478658cf9c665ec76d9f39be29d6ff..7613181df503d685c44a4d591abf0222a9a09b9c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/items/PastCheckInVH.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/items/PastCheckInVH.kt @@ -5,8 +5,8 @@ import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.TraceLocationAttendeeCheckinsItemPastBinding import de.rki.coronawarnapp.eventregistration.checkins.CheckIn import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUserTimeZone -import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer import de.rki.coronawarnapp.util.list.SwipeConsumer +import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer import org.joda.time.format.DateTimeFormat class PastCheckInVH(parent: ViewGroup) : diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeFragment.kt index 30154100322cfd4111f4e382a50cad32054bd143..4da62b575f1b31ecbfedb0d9626177d92cb0fadf 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeFragment.kt @@ -14,9 +14,9 @@ import com.journeyapps.barcodescanner.DefaultDecoderFactory import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentScanCheckInQrCodeBinding import de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.CheckInsFragment -import de.rki.coronawarnapp.util.permission.CameraPermissionHelper import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.permission.CameraPermissionHelper import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.popBackStack import de.rki.coronawarnapp.util.ui.viewBindingLazy diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt index 0aac8920aa65ac977b7479eb23dc6259fd0c3dc7..855e5810489aef55471e6980f336ea3aa506ad83 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt @@ -18,10 +18,10 @@ import de.rki.coronawarnapp.ui.main.MainActivity import de.rki.coronawarnapp.ui.submission.ApiRequestState import de.rki.coronawarnapp.ui.submission.ScanStatus import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents -import de.rki.coronawarnapp.util.permission.CameraPermissionHelper import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.formatter.TestResult +import de.rki.coronawarnapp.util.permission.CameraPermissionHelper import de.rki.coronawarnapp.util.ui.doNavigate import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.viewBindingLazy diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_overview_list_item.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_overview_list_item.xml index e52dfe374e1657ad8142fb6694c6f5a1a6eb7bbc..e3e49b04b3799bb1ecf64afe7d51e7edff2c52b8 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_overview_list_item.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_overview_list_item.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" android:id="@+id/day_element_body" style="@style/contactDiaryCardRipple" android:layout_width="match_parent" diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/retention/ContactDiaryDataRetentionCalculationTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/retention/ContactDiaryDataRetentionCalculationTest.kt index b0fc4b2d91eca301c83825a0c08e3da359d71e1d..6e72592e32b5bff74229ca690b92b68900b86497 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/retention/ContactDiaryDataRetentionCalculationTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/retention/ContactDiaryDataRetentionCalculationTest.kt @@ -3,7 +3,7 @@ package de.rki.coronawarnapp.contactdiary.retention import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocationVisit import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPersonEncounter import de.rki.coronawarnapp.contactdiary.storage.repo.DefaultContactDiaryRepository -import de.rki.coronawarnapp.risk.result.AggregatedRiskPerDateResult +import de.rki.coronawarnapp.risk.result.ExposureWindowDayRisk import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass import de.rki.coronawarnapp.util.TimeStamper @@ -124,10 +124,10 @@ class ContactDiaryDataRetentionCalculationTest : BaseTest() { @Test fun `test risk per date results`() = runBlockingTest { val instance = createInstance() - val list: List<AggregatedRiskPerDateResult> = testDates.map { createAggregatedRiskPerDateResult(Instant.parse(it)) } + val list: List<ExposureWindowDayRisk> = testDates.map { createAggregatedRiskPerDateResult(Instant.parse(it)) } val filteredList = list.filter { instance.isOutOfRetention(it.localDateUtc) } - every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf(list) + every { riskLevelStorage.ewDayRiskStates } returns flowOf(list) coEvery { riskLevelStorage.deleteAggregatedRiskPerDateResults(any()) } just runs filteredList.size shouldBe 1 @@ -135,7 +135,7 @@ class ContactDiaryDataRetentionCalculationTest : BaseTest() { coVerify { riskLevelStorage.deleteAggregatedRiskPerDateResults(filteredList) } } - private fun createAggregatedRiskPerDateResult(date: Instant) = AggregatedRiskPerDateResult( + private fun createAggregatedRiskPerDateResult(date: Instant) = ExposureWindowDayRisk( dateMillisSinceEpoch = date.millis, riskLevel = RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH, minimumDistinctEncountersWithLowRisk = 0, diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModelTest.kt index 80768278381b1449854d029dfd51cef8e0f53e13..bd06b47d299e49aeb0b611eac9e98b24534209ce 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModelTest.kt @@ -17,7 +17,7 @@ import de.rki.coronawarnapp.contactdiary.util.ContactDiaryData import de.rki.coronawarnapp.contactdiary.util.mockStringsForContactDiaryExporterTests import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.TraceLocationCheckInRisk -import de.rki.coronawarnapp.risk.result.AggregatedRiskPerDateResult +import de.rki.coronawarnapp.risk.result.ExposureWindowDayRisk import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass import de.rki.coronawarnapp.task.TaskController @@ -63,7 +63,7 @@ open class ContactDiaryOverviewViewModelTest { every { taskController.submit(any()) } just runs every { contactDiaryRepository.locationVisits } returns flowOf(emptyList()) every { contactDiaryRepository.personEncounters } returns flowOf(emptyList()) - every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf(emptyList()) + every { riskLevelStorage.ewDayRiskStates } returns flowOf(emptyList()) every { riskLevelStorage.traceLocationCheckInRiskStates } returns flowOf(emptyList()) mockStringsForContactDiaryExporterTests(context) @@ -113,21 +113,21 @@ open class ContactDiaryOverviewViewModelTest { override val riskState: RiskState = RiskState.INCREASED_RISK } - private val aggregatedRiskPerDateResultLowRisk = AggregatedRiskPerDateResult( + private val aggregatedRiskPerDateResultLowRisk = ExposureWindowDayRisk( dateMillisSinceEpoch = dateMillis, riskLevel = RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.LOW, minimumDistinctEncountersWithLowRisk = 1, minimumDistinctEncountersWithHighRisk = 0 ) - private val aggregatedRiskPerDateResultHighRiskDueToHighRiskEncounter = AggregatedRiskPerDateResult( + private val aggregatedRiskPerDateResultHighRiskDueToHighRiskEncounter = ExposureWindowDayRisk( dateMillisSinceEpoch = dateMillis, riskLevel = RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH, minimumDistinctEncountersWithLowRisk = 0, minimumDistinctEncountersWithHighRisk = 1 ) - private val aggregatedRiskPerDateResultHighRiskDueToLowRiskEncounter = AggregatedRiskPerDateResult( + private val aggregatedRiskPerDateResultHighRiskDueToLowRiskEncounter = ExposureWindowDayRisk( dateMillisSinceEpoch = dateMillis, riskLevel = RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH, minimumDistinctEncountersWithLowRisk = 10, @@ -199,7 +199,7 @@ open class ContactDiaryOverviewViewModelTest { fun `low risk enf with person and location`() { every { contactDiaryRepository.personEncounters } returns flowOf(listOf(personEncounter)) every { contactDiaryRepository.locationVisits } returns flowOf(listOf(locationVisit)) - every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf(listOf(aggregatedRiskPerDateResultLowRisk)) + every { riskLevelStorage.ewDayRiskStates } returns flowOf(listOf(aggregatedRiskPerDateResultLowRisk)) val item = createInstance().listItems.getOrAwaitValue().first { it is DayOverviewItem && it.date == date @@ -221,7 +221,7 @@ open class ContactDiaryOverviewViewModelTest { @Test fun `low risk enf without person or location`() { - every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf(listOf(aggregatedRiskPerDateResultLowRisk)) + every { riskLevelStorage.ewDayRiskStates } returns flowOf(listOf(aggregatedRiskPerDateResultLowRisk)) val item = createInstance().listItems.getOrAwaitValue().first { it is DayOverviewItem && it.date == date @@ -242,7 +242,7 @@ open class ContactDiaryOverviewViewModelTest { fun `high risk enf due to high risk encounter with person and location`() { every { contactDiaryRepository.personEncounters } returns flowOf(listOf(personEncounter)) every { contactDiaryRepository.locationVisits } returns flowOf(listOf(locationVisit)) - every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf( + every { riskLevelStorage.ewDayRiskStates } returns flowOf( listOf( aggregatedRiskPerDateResultHighRiskDueToHighRiskEncounter ) @@ -268,7 +268,7 @@ open class ContactDiaryOverviewViewModelTest { @Test fun `high risk enf due to high risk encounter without person or location`() { - every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf( + every { riskLevelStorage.ewDayRiskStates } returns flowOf( listOf( aggregatedRiskPerDateResultHighRiskDueToHighRiskEncounter ) @@ -293,7 +293,7 @@ open class ContactDiaryOverviewViewModelTest { fun `high risk enf due to low risk encounter with person and location`() { every { contactDiaryRepository.personEncounters } returns flowOf(listOf(personEncounter)) every { contactDiaryRepository.locationVisits } returns flowOf(listOf(locationVisit)) - every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf( + every { riskLevelStorage.ewDayRiskStates } returns flowOf( listOf( aggregatedRiskPerDateResultHighRiskDueToLowRiskEncounter ) @@ -319,7 +319,7 @@ open class ContactDiaryOverviewViewModelTest { @Test fun `high risk enf due to low risk encounter without person or location`() { - every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf( + every { riskLevelStorage.ewDayRiskStates } returns flowOf( listOf( aggregatedRiskPerDateResultHighRiskDueToLowRiskEncounter ) @@ -342,7 +342,7 @@ open class ContactDiaryOverviewViewModelTest { @Test fun `risk enf and risk event are independently of each other`() { - every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf(listOf(aggregatedRiskPerDateResultLowRisk)) + every { riskLevelStorage.ewDayRiskStates } returns flowOf(listOf(aggregatedRiskPerDateResultLowRisk)) every { riskLevelStorage.traceLocationCheckInRiskStates } returns flowOf(listOf(traceLocationCheckInRiskHigh)) every { contactDiaryRepository.locationVisits } returns flowOf(listOf(locationEventHighRiskVisit)) @@ -360,7 +360,7 @@ open class ContactDiaryOverviewViewModelTest { riskEventItem!!.validate(highRisk = true) } - every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf(listOf(aggregatedRiskPerDateResultHighRiskDueToLowRiskEncounter)) + every { riskLevelStorage.ewDayRiskStates } returns flowOf(listOf(aggregatedRiskPerDateResultHighRiskDueToLowRiskEncounter)) every { riskLevelStorage.traceLocationCheckInRiskStates } returns flowOf(listOf(traceLocationCheckInRiskLow)) every { contactDiaryRepository.locationVisits } returns flowOf(listOf(locationEventLowRiskVisit)) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/ExposureRiskMetadataDonorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/ExposureRiskMetadataDonorTest.kt index 8e65fb16cf165c58a2429ccf987cde6fba68609d..6451d5f74de1e7f538722940f47c557b8f444fe8 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/ExposureRiskMetadataDonorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/ExposureRiskMetadataDonorTest.kt @@ -4,8 +4,8 @@ import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.datadonation.analytics.modules.exposureriskmetadata.ExposureRiskMetadataDonor import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings -import de.rki.coronawarnapp.risk.RiskLevelResult -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData import de.rki.coronawarnapp.util.TimeAndDateExtensions.seconds @@ -25,8 +25,8 @@ import testhelpers.preferences.mockFlowPreference class ExposureRiskMetadataDonorTest : BaseTest() { @MockK lateinit var riskLevelStorage: RiskLevelStorage @MockK lateinit var analyticsSettings: AnalyticsSettings - @MockK lateinit var highAggregatedRiskResult: AggregatedRiskResult - @MockK lateinit var lowAggregatedRiskResult: AggregatedRiskResult + @MockK lateinit var highEwAggregatedRiskResult: EwAggregatedRiskResult + @MockK lateinit var lowEwAggregatedRiskResult: EwAggregatedRiskResult private val baseDate: Instant = Instant.ofEpochMilli(101010) @@ -34,20 +34,20 @@ class ExposureRiskMetadataDonorTest : BaseTest() { fun setup() { MockKAnnotations.init(this) - every { highAggregatedRiskResult.isIncreasedRisk() } returns true - every { highAggregatedRiskResult.mostRecentDateWithHighRisk } returns baseDate - every { lowAggregatedRiskResult.isIncreasedRisk() } returns false - every { lowAggregatedRiskResult.mostRecentDateWithHighRisk } returns baseDate + every { highEwAggregatedRiskResult.isIncreasedRisk() } returns true + every { highEwAggregatedRiskResult.mostRecentDateWithHighRisk } returns baseDate + every { lowEwAggregatedRiskResult.isIncreasedRisk() } returns false + every { lowEwAggregatedRiskResult.mostRecentDateWithHighRisk } returns baseDate } private fun createRiskLevelResult( - aggregatedRiskResult: AggregatedRiskResult?, - failureReason: RiskLevelResult.FailureReason?, + ewAggregatedRiskResult: EwAggregatedRiskResult?, + failureReason: EwRiskLevelResult.FailureReason?, calculatedAt: Instant - ): RiskLevelResult = object : RiskLevelResult { + ): EwRiskLevelResult = object : EwRiskLevelResult { override val calculatedAt: Instant = calculatedAt - override val aggregatedRiskResult: AggregatedRiskResult? = aggregatedRiskResult - override val failureReason: RiskLevelResult.FailureReason? = failureReason + override val ewAggregatedRiskResult: EwAggregatedRiskResult? = ewAggregatedRiskResult + override val failureReason: EwRiskLevelResult.FailureReason? = failureReason override val exposureWindows: List<ExposureWindow>? = null override val matchedKeyCount: Int = 0 override val daysWithEncounters: Int = 0 @@ -68,16 +68,16 @@ class ExposureRiskMetadataDonorTest : BaseTest() { .build() every { analyticsSettings.previousExposureRiskMetadata } returns mockFlowPreference(null) - every { riskLevelStorage.latestAndLastSuccessful } returns flowOf( + every { riskLevelStorage.latestAndLastSuccessfulEwRiskLevelResult } returns flowOf( listOf( createRiskLevelResult( - aggregatedRiskResult = highAggregatedRiskResult, + ewAggregatedRiskResult = highEwAggregatedRiskResult, failureReason = null, calculatedAt = baseDate ), createRiskLevelResult( - aggregatedRiskResult = lowAggregatedRiskResult, - failureReason = RiskLevelResult.FailureReason.UNKNOWN, + ewAggregatedRiskResult = lowEwAggregatedRiskResult, + failureReason = EwRiskLevelResult.FailureReason.UNKNOWN, calculatedAt = baseDate ) ) @@ -118,16 +118,16 @@ class ExposureRiskMetadataDonorTest : BaseTest() { every { analyticsSettings.previousExposureRiskMetadata } returns mockFlowPreference(initialMetadata) - every { riskLevelStorage.latestAndLastSuccessful } returns flowOf( + every { riskLevelStorage.latestAndLastSuccessfulEwRiskLevelResult } returns flowOf( listOf( createRiskLevelResult( - aggregatedRiskResult = highAggregatedRiskResult, + ewAggregatedRiskResult = highEwAggregatedRiskResult, failureReason = null, calculatedAt = baseDate ), createRiskLevelResult( - aggregatedRiskResult = lowAggregatedRiskResult, - failureReason = RiskLevelResult.FailureReason.UNKNOWN, + ewAggregatedRiskResult = lowEwAggregatedRiskResult, + failureReason = EwRiskLevelResult.FailureReason.UNKNOWN, calculatedAt = baseDate ) ) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollectorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollectorTest.kt index 859cf662132c0e10231b12ede552934534fe0a2b..bb1a53945edd276af0b953aaac3652024447362e 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollectorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollectorTest.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings -import de.rki.coronawarnapp.risk.RiskLevelResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.risk.RiskLevelSettings import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.storage.RiskLevelStorage @@ -31,7 +31,7 @@ class AnalyticsKeySubmissionCollectorTest : BaseTest() { @MockK lateinit var analyticsKeySubmissionStorage: AnalyticsKeySubmissionStorage @MockK lateinit var riskLevelStorage: RiskLevelStorage @MockK lateinit var riskLevelSettings: RiskLevelSettings - @MockK lateinit var riskLevelResult: RiskLevelResult + @MockK lateinit var ewRiskLevelResult: EwRiskLevelResult private val now = Instant.now() @@ -44,16 +44,16 @@ class AnalyticsKeySubmissionCollectorTest : BaseTest() { @Test fun `save test registered`() { coEvery { analyticsSettings.analyticsEnabled.value } returns true - every { riskLevelResult.riskState } returns RiskState.INCREASED_RISK + every { ewRiskLevelResult.riskState } returns RiskState.INCREASED_RISK coEvery { - riskLevelStorage.latestAndLastSuccessful - } returns flowOf(listOf(riskLevelResult)) + riskLevelStorage.latestAndLastSuccessfulEwRiskLevelResult + } returns flowOf(listOf(ewRiskLevelResult)) every { riskLevelSettings.lastChangeToHighRiskLevelTimestamp } returns now.minus( Hours.hours(2).toStandardDuration() ) val testRegisteredAt = mockFlowPreference(now.millis) coEvery { analyticsKeySubmissionStorage.testRegisteredAt } returns testRegisteredAt - every { riskLevelResult.wasSuccessfullyCalculated } returns true + every { ewRiskLevelResult.wasSuccessfullyCalculated } returns true val riskLevelAtTestRegistration = mockFlowPreference(-1) every { analyticsKeySubmissionStorage.riskLevelAtTestRegistration } returns riskLevelAtTestRegistration val hoursSinceHighRiskWarningAtTestRegistration = mockFlowPreference(-1) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollectorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollectorTest.kt index cf33dfdaa4a7052cf80ae317eef41a728c8d9311..fe6d8af0e238e196f949f8a58c52fff4485db9fb 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollectorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/TestResultDataCollectorTest.kt @@ -2,7 +2,7 @@ package de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings import de.rki.coronawarnapp.datadonation.analytics.storage.TestResultDonorSettings -import de.rki.coronawarnapp.risk.RiskLevelResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.formatter.TestResult @@ -62,11 +62,11 @@ class TestResultDataCollectorTest : BaseTest() { runBlockingTest { every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(true) - val mockRiskLevelResult = mockk<RiskLevelResult>().apply { + val mockRiskLevelResult = mockk<EwRiskLevelResult>().apply { every { calculatedAt } returns Instant.now() every { wasSuccessfullyCalculated } returns true } - every { riskLevelStorage.latestAndLastSuccessful } returns flowOf(listOf(mockRiskLevelResult)) + every { riskLevelStorage.latestAndLastSuccessfulEwRiskLevelResult } returns flowOf(listOf(mockRiskLevelResult)) every { testResultDonorSettings.saveTestResultDonorDataAtRegistration(any(), any()) } just Runs testResultDataCollector.saveTestResultAnalyticsSettings(TestResult.POSITIVE) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt index d02dac3c4f8a1936f323ed36c43e1c3e841b729d..4668c0e165817e96991fe088264c9d1f6fcf14ef 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt @@ -17,7 +17,7 @@ import de.rki.coronawarnapp.nearby.windows.entities.configuration.JsonNormalized import de.rki.coronawarnapp.nearby.windows.entities.configuration.JsonTransmissionRiskValueMapping import de.rki.coronawarnapp.nearby.windows.entities.configuration.JsonTrlFilter import de.rki.coronawarnapp.risk.DefaultRiskLevels -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import de.rki.coronawarnapp.risk.result.RiskResult import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass import de.rki.coronawarnapp.util.TimeStamper @@ -140,7 +140,7 @@ class ExposureWindowsCalculationTest : BaseTest() { return timeStamper.nowUTC - expAge * DateTimeConstants.MILLIS_PER_DAY } - private fun comparisonDebugTable(aggregated: AggregatedRiskResult, case: TestCase): String { + private fun comparisonDebugTable(ewAggregated: EwAggregatedRiskResult, case: TestCase): String { val result = StringBuilder() result.append("\n").append(case.description) result.append("\n").append("+----------------------+--------------------------+--------------------------+") @@ -149,35 +149,35 @@ class ExposureWindowsCalculationTest : BaseTest() { result.append( addPropertyCheckToComparisonDebugTable( "Total Risk", - aggregated.totalRiskLevel.number, + ewAggregated.totalRiskLevel.number, case.expTotalRiskLevel ) ) result.append( addPropertyCheckToComparisonDebugTable( "Date With High Risk", - aggregated.mostRecentDateWithHighRisk, + ewAggregated.mostRecentDateWithHighRisk, getTestCaseDate(case.expAgeOfMostRecentDateWithHighRiskInDays) ) ) result.append( addPropertyCheckToComparisonDebugTable( "Date With Low Risk", - aggregated.mostRecentDateWithLowRisk, + ewAggregated.mostRecentDateWithLowRisk, getTestCaseDate(case.expAgeOfMostRecentDateWithLowRiskInDays) ) ) result.append( addPropertyCheckToComparisonDebugTable( "Encounters High Risk", - aggregated.totalMinimumDistinctEncountersWithHighRisk, + ewAggregated.totalMinimumDistinctEncountersWithHighRisk, case.expTotalMinimumDistinctEncountersWithHighRisk ) ) result.append( addPropertyCheckToComparisonDebugTable( "Encounters Low Risk", - aggregated.totalMinimumDistinctEncountersWithLowRisk, + ewAggregated.totalMinimumDistinctEncountersWithLowRisk, case.expTotalMinimumDistinctEncountersWithLowRisk ) ) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/CheckInWarningMatcherTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/CheckInWarningMatcherTest.kt index c417f1a01b99d5969edc8bb66bb6eb3198ad0c1b..26643e4b4d6dd52f106bde6e17cd6d1680b89608 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/CheckInWarningMatcherTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/CheckInWarningMatcherTest.kt @@ -29,12 +29,16 @@ class CheckInWarningMatcherTest : BaseTest() { @BeforeEach fun setup() { MockKAnnotations.init(this) - coEvery { presenceTracingRiskRepository.replaceAllMatches(any()) } just Runs + coEvery { presenceTracingRiskRepository.reportSuccessfulCalculation(any()) } just Runs coEvery { presenceTracingRiskRepository.deleteAllMatches() } just Runs + coEvery { presenceTracingRiskRepository.deleteStaleData() } just Runs + // TODO tests + coEvery { presenceTracingRiskRepository.deleteMatchesOfPackage(any()) } just Runs + coEvery { presenceTracingRiskRepository.markPackageProcessed(any()) } just Runs } @Test - fun `replaces matches`() { + fun `reports new matches`() { val checkIn1 = createCheckIn( id = 2L, traceLocationGuid = "fe84394e73838590cc7707aba0350c130f6d0fb6f0f2535f9735f481dee61871", @@ -77,13 +81,13 @@ class CheckInWarningMatcherTest : BaseTest() { runBlockingTest { createInstance().execute() - coVerify(exactly = 1) { presenceTracingRiskRepository.replaceAllMatches(any()) } + coVerify(exactly = 1) { presenceTracingRiskRepository.reportSuccessfulCalculation(any()) } coVerify(exactly = 0) { presenceTracingRiskRepository.deleteAllMatches() } } } @Test - fun `replace with empty list if no matches found`() { + fun `report empty list if no matches found`() { val checkIn1 = createCheckIn( id = 2L, traceLocationGuid = "fe84394e73838590cc7707aba0350c130f6d0fb6f0f2535f9735f481dee61871", @@ -126,13 +130,13 @@ class CheckInWarningMatcherTest : BaseTest() { runBlockingTest { createInstance().execute() - coVerify(exactly = 1) { presenceTracingRiskRepository.replaceAllMatches(emptyList()) } + coVerify(exactly = 1) { presenceTracingRiskRepository.reportSuccessfulCalculation(emptyList()) } coVerify(exactly = 0) { presenceTracingRiskRepository.deleteAllMatches() } } } @Test - fun `replace with empty list if package is empty`() { + fun `report empty list if package is empty`() { val checkIn1 = createCheckIn( id = 2L, traceLocationGuid = "fe84394e73838590cc7707aba0350c130f6d0fb6f0f2535f9735f481dee61871", @@ -161,7 +165,7 @@ class CheckInWarningMatcherTest : BaseTest() { runBlockingTest { createInstance().execute() - coVerify(exactly = 1) { presenceTracingRiskRepository.replaceAllMatches(emptyList()) } + coVerify(exactly = 1) { presenceTracingRiskRepository.reportSuccessfulCalculation(emptyList()) } coVerify(exactly = 0) { presenceTracingRiskRepository.deleteAllMatches() } } } @@ -198,7 +202,7 @@ class CheckInWarningMatcherTest : BaseTest() { runBlockingTest { createInstance().execute() - coVerify(exactly = 0) { presenceTracingRiskRepository.replaceAllMatches(any()) } + coVerify(exactly = 1) { presenceTracingRiskRepository.reportSuccessfulCalculation(emptyList()) } coVerify(exactly = 1) { presenceTracingRiskRepository.deleteAllMatches() } } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelResultExtensionsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/EwRiskLevelResultExtensionsTest.kt similarity index 79% rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelResultExtensionsTest.kt rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/EwRiskLevelResultExtensionsTest.kt index 243588ccb2c954f230e0dfafd7c429299bfa9615..f1b801552258d43dfbaa690f7beebcca753efdba 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelResultExtensionsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/EwRiskLevelResultExtensionsTest.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.risk import com.google.android.gms.nearby.exposurenotification.ExposureWindow -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import io.kotest.matchers.longs.shouldBeInRange import io.kotest.matchers.shouldBe import io.mockk.mockk @@ -9,16 +9,16 @@ import org.joda.time.Instant import org.junit.jupiter.api.Test import testhelpers.BaseTest -class RiskLevelResultExtensionsTest : BaseTest() { +class EwRiskLevelResultExtensionsTest : BaseTest() { private fun createRiskLevelResult( hasResult: Boolean, calculatedAt: Instant - ): RiskLevelResult = object : RiskLevelResult { + ): EwRiskLevelResult = object : EwRiskLevelResult { override val calculatedAt: Instant = calculatedAt - override val aggregatedRiskResult: AggregatedRiskResult? = if (hasResult) mockk() else null - override val failureReason: RiskLevelResult.FailureReason? - get() = if (!hasResult) RiskLevelResult.FailureReason.UNKNOWN else null + override val ewAggregatedRiskResult: EwAggregatedRiskResult? = if (hasResult) mockk() else null + override val failureReason: EwRiskLevelResult.FailureReason? + get() = if (!hasResult) EwRiskLevelResult.FailureReason.UNKNOWN else null override val exposureWindows: List<ExposureWindow>? = null override val matchedKeyCount: Int = 0 override val daysWithEncounters: Int = 0 @@ -26,9 +26,9 @@ class RiskLevelResultExtensionsTest : BaseTest() { @Test fun `getLatestAndLastSuccessful on empty results`() { - val emptyResults = emptyList<RiskLevelResult>() + val emptyResults = emptyList<EwRiskLevelResult>() - emptyResults.tryLatestResultsWithDefaults().apply { + emptyResults.tryLatestEwResultsWithDefaults().apply { lastCalculated.apply { riskState shouldBe RiskState.LOW_RISK val now = Instant.now().millis @@ -47,7 +47,7 @@ class RiskLevelResultExtensionsTest : BaseTest() { createRiskLevelResult(hasResult = true, calculatedAt = Instant.EPOCH.plus(1)) ) - results.tryLatestResultsWithDefaults().apply { + results.tryLatestEwResultsWithDefaults().apply { lastCalculated.calculatedAt shouldBe Instant.EPOCH.plus(1) lastSuccessfullyCalculated.calculatedAt shouldBe Instant.EPOCH.plus(1) } @@ -61,7 +61,7 @@ class RiskLevelResultExtensionsTest : BaseTest() { createRiskLevelResult(hasResult = false, calculatedAt = Instant.EPOCH.plus(2)) ) - results.tryLatestResultsWithDefaults().apply { + results.tryLatestEwResultsWithDefaults().apply { lastCalculated.calculatedAt shouldBe Instant.EPOCH.plus(2) lastSuccessfullyCalculated.calculatedAt shouldBe Instant.EPOCH.plus(1) } @@ -76,7 +76,7 @@ class RiskLevelResultExtensionsTest : BaseTest() { createRiskLevelResult(hasResult = false, calculatedAt = Instant.EPOCH.plus(13)) ) - results.tryLatestResultsWithDefaults().apply { + results.tryLatestEwResultsWithDefaults().apply { lastCalculated.calculatedAt shouldBe Instant.EPOCH.plus(13) lastSuccessfullyCalculated.calculatedAt shouldBe Instant.EPOCH } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelResultTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/EwRiskLevelResultTest.kt similarity index 55% rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelResultTest.kt rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/EwRiskLevelResultTest.kt index b401435390b6df088edcc5d583196d34be7fc066..c5b8c6c55f0dfa11297e982e6880bdae16abba57 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelResultTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/EwRiskLevelResultTest.kt @@ -1,22 +1,22 @@ package de.rki.coronawarnapp.risk import com.google.android.gms.nearby.exposurenotification.ExposureWindow -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import io.kotest.matchers.shouldBe import io.mockk.mockk import org.joda.time.Instant import org.junit.Test import testhelpers.BaseTest -class RiskLevelResultTest : BaseTest() { +class EwRiskLevelResultTest : BaseTest() { private fun createRiskLevel( - aggregatedRiskResult: AggregatedRiskResult?, - failureReason: RiskLevelResult.FailureReason? - ): RiskLevelResult = object : RiskLevelResult { + ewAggregatedRiskResult: EwAggregatedRiskResult?, + failureReason: EwRiskLevelResult.FailureReason? + ): EwRiskLevelResult = object : EwRiskLevelResult { override val calculatedAt: Instant = Instant.EPOCH - override val aggregatedRiskResult: AggregatedRiskResult? = aggregatedRiskResult - override val failureReason: RiskLevelResult.FailureReason? = failureReason + override val ewAggregatedRiskResult: EwAggregatedRiskResult? = ewAggregatedRiskResult + override val failureReason: EwRiskLevelResult.FailureReason? = failureReason override val exposureWindows: List<ExposureWindow>? = null override val matchedKeyCount: Int = 0 override val daysWithEncounters: Int = 0 @@ -25,12 +25,12 @@ class RiskLevelResultTest : BaseTest() { @Test fun testUnsuccessfulRistLevels() { createRiskLevel( - aggregatedRiskResult = null, - failureReason = RiskLevelResult.FailureReason.UNKNOWN + ewAggregatedRiskResult = null, + failureReason = EwRiskLevelResult.FailureReason.UNKNOWN ).wasSuccessfullyCalculated shouldBe false createRiskLevel( - aggregatedRiskResult = mockk(), + ewAggregatedRiskResult = mockk(), failureReason = null ).wasSuccessfullyCalculated shouldBe true } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt index 7f496fa9426ef06947ccdf87f4248ffe35c44eb1..d391e5cc2a9f7d54390d3eb777452ba6b2de1459 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt @@ -9,7 +9,7 @@ import de.rki.coronawarnapp.notification.GeneralNotifications import de.rki.coronawarnapp.risk.RiskState.CALCULATION_FAILED import de.rki.coronawarnapp.risk.RiskState.INCREASED_RISK import de.rki.coronawarnapp.risk.RiskState.LOW_RISK -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.submission.SubmissionSettings @@ -66,17 +66,18 @@ class RiskLevelChangeDetectorTest : BaseTest() { every { testResultDonorSettings.riskLevelTurnedRedTime } returns mockFlowPreference(null) every { testResultDonorSettings.mostRecentDateWithHighOrLowRiskLevel } returns mockFlowPreference(null) + every { riskLevelStorage.latestCombinedEwPtRiskLevelResults } returns flowOf(listOf()) } private fun createRiskLevel( riskState: RiskState, calculatedAt: Instant = Instant.EPOCH, - aggregatedRiskResult: AggregatedRiskResult? = null - ): RiskLevelResult = object : RiskLevelResult { + ewAggregatedRiskResult: EwAggregatedRiskResult? = null + ): EwRiskLevelResult = object : EwRiskLevelResult { override val riskState: RiskState = riskState override val calculatedAt: Instant = calculatedAt - override val aggregatedRiskResult: AggregatedRiskResult? = aggregatedRiskResult - override val failureReason: RiskLevelResult.FailureReason? = null + override val ewAggregatedRiskResult: EwAggregatedRiskResult? = ewAggregatedRiskResult + override val failureReason: EwRiskLevelResult.FailureReason? = null override val exposureWindows: List<ExposureWindow>? = null override val matchedKeyCount: Int = 0 override val daysWithEncounters: Int = 0 @@ -98,7 +99,7 @@ class RiskLevelChangeDetectorTest : BaseTest() { @Test fun `nothing happens if there is only one result yet`() { - every { riskLevelStorage.latestRiskLevelResults } returns flowOf(listOf(createRiskLevel(LOW_RISK))) + every { riskLevelStorage.latestEwRiskLevelResults } returns flowOf(listOf(createRiskLevel(LOW_RISK))) runBlockingTest { val instance = createInstance(scope = this) @@ -115,7 +116,7 @@ class RiskLevelChangeDetectorTest : BaseTest() { @Test fun `no risklevel change, nothing should happen`() { - every { riskLevelStorage.latestRiskLevelResults } returns flowOf( + every { riskLevelStorage.latestEwRiskLevelResults } returns flowOf( listOf( createRiskLevel(LOW_RISK), createRiskLevel(LOW_RISK) @@ -135,9 +136,11 @@ class RiskLevelChangeDetectorTest : BaseTest() { } } + // TODO test if risk level change for combined risk triggers notification + @Test fun `risklevel went from HIGH to LOW`() { - every { riskLevelStorage.latestRiskLevelResults } returns flowOf( + every { riskLevelStorage.latestEwRiskLevelResults } returns flowOf( listOf( createRiskLevel(LOW_RISK, calculatedAt = Instant.EPOCH.plus(1)), createRiskLevel(INCREASED_RISK, calculatedAt = Instant.EPOCH) @@ -151,16 +154,16 @@ class RiskLevelChangeDetectorTest : BaseTest() { advanceUntilIdle() coVerifySequence { - submissionSettings.isSubmissionSuccessful - foregroundState.isInForeground surveys.resetSurvey(Surveys.Type.HIGH_RISK_ENCOUNTER) } } } + // TODO test if risk level change for combined risk triggers notification + @Test fun `risklevel went from LOW to HIGH`() { - every { riskLevelStorage.latestRiskLevelResults } returns flowOf( + every { riskLevelStorage.latestEwRiskLevelResults } returns flowOf( listOf( createRiskLevel(INCREASED_RISK, calculatedAt = Instant.EPOCH.plus(1)), createRiskLevel(LOW_RISK, calculatedAt = Instant.EPOCH) @@ -174,8 +177,6 @@ class RiskLevelChangeDetectorTest : BaseTest() { advanceUntilIdle() coVerifySequence { - submissionSettings.isSubmissionSuccessful - foregroundState.isInForeground surveys wasNot Called } } @@ -183,7 +184,7 @@ class RiskLevelChangeDetectorTest : BaseTest() { @Test fun `risklevel went from LOW to HIGH but it is has already been processed`() { - every { riskLevelStorage.latestRiskLevelResults } returns flowOf( + every { riskLevelStorage.latestEwRiskLevelResults } returns flowOf( listOf( createRiskLevel(INCREASED_RISK, calculatedAt = Instant.EPOCH.plus(1)), createRiskLevel(LOW_RISK, calculatedAt = Instant.EPOCH) @@ -208,12 +209,12 @@ class RiskLevelChangeDetectorTest : BaseTest() { fun `riskLevelTurnedRedTime is only set once`() { testResultDonorSettings.riskLevelTurnedRedTime.update { Instant.EPOCH.plus(1) } - every { riskLevelStorage.latestRiskLevelResults } returns flowOf( + every { riskLevelStorage.latestEwRiskLevelResults } returns flowOf( listOf( createRiskLevel( INCREASED_RISK, calculatedAt = Instant.EPOCH.plus(2), - aggregatedRiskResult = mockk<AggregatedRiskResult>().apply { + ewAggregatedRiskResult = mockk<EwAggregatedRiskResult>().apply { every { isIncreasedRisk() } returns true } ), @@ -242,12 +243,12 @@ class RiskLevelChangeDetectorTest : BaseTest() { @Test fun `mostRecentDateWithHighOrLowRiskLevel is updated every time`() { - every { riskLevelStorage.latestRiskLevelResults } returns flowOf( + every { riskLevelStorage.latestEwRiskLevelResults } returns flowOf( listOf( createRiskLevel( INCREASED_RISK, calculatedAt = Instant.EPOCH.plus(1), - aggregatedRiskResult = mockk<AggregatedRiskResult>().apply { + ewAggregatedRiskResult = mockk<EwAggregatedRiskResult>().apply { every { mostRecentDateWithHighRisk } returns Instant.EPOCH.plus(10) every { isIncreasedRisk() } returns true } @@ -264,12 +265,12 @@ class RiskLevelChangeDetectorTest : BaseTest() { testResultDonorSettings.mostRecentDateWithHighOrLowRiskLevel.value shouldBe Instant.EPOCH.plus(10) - every { riskLevelStorage.latestRiskLevelResults } returns flowOf( + every { riskLevelStorage.latestEwRiskLevelResults } returns flowOf( listOf( createRiskLevel( INCREASED_RISK, calculatedAt = Instant.EPOCH.plus(1), - aggregatedRiskResult = mockk<AggregatedRiskResult>().apply { + ewAggregatedRiskResult = mockk<EwAggregatedRiskResult>().apply { every { mostRecentDateWithLowRisk } returns Instant.EPOCH.plus(20) every { isIncreasedRisk() } returns false } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelTaskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelTaskTest.kt index b89b99fcb0a8a2850fa1922a958cce9e4927739d..ffc2478a2edc4d10d4e4d1265e78b59c0de45deb 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelTaskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelTaskTest.kt @@ -12,7 +12,7 @@ import de.rki.coronawarnapp.diagnosiskeys.storage.CachedKeyInfo import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.presencetracing.checkins.checkout.auto.AutoCheckOut -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.task.Task @@ -119,9 +119,9 @@ class RiskLevelTaskTest : BaseTest() { every { configData.isDeviceTimeCorrect } returns false every { configData.localOffset } returns Duration.standardHours(5) - createTask().run(arguments) shouldBe RiskLevelTaskResult( + createTask().run(arguments) shouldBe EwRiskLevelTaskResult( calculatedAt = Instant.EPOCH, - failureReason = RiskLevelResult.FailureReason.INCORRECT_DEVICE_TIME + failureReason = EwRiskLevelResult.FailureReason.INCORRECT_DEVICE_TIME ) } @@ -135,9 +135,9 @@ class RiskLevelTaskTest : BaseTest() { } } - createTask().run(arguments) shouldBe RiskLevelTaskResult( + createTask().run(arguments) shouldBe EwRiskLevelTaskResult( calculatedAt = Instant.EPOCH, - failureReason = RiskLevelResult.FailureReason.NO_INTERNET + failureReason = EwRiskLevelResult.FailureReason.NO_INTERNET ) } @@ -145,9 +145,9 @@ class RiskLevelTaskTest : BaseTest() { fun `risk calculation is skipped if tracing is disabled`() = runBlockingTest { every { enfClient.isTracingEnabled } returns flowOf(false) - createTask().run(arguments) shouldBe RiskLevelTaskResult( + createTask().run(arguments) shouldBe EwRiskLevelTaskResult( calculatedAt = Instant.EPOCH, - failureReason = RiskLevelResult.FailureReason.TRACING_OFF + failureReason = EwRiskLevelResult.FailureReason.TRACING_OFF ) } @@ -155,9 +155,9 @@ class RiskLevelTaskTest : BaseTest() { fun `risk calculation is skipped if results are not existing while in background mode`() = runBlockingTest { coEvery { keyCacheRepository.getAllCachedKeys() } returns listOf() every { backgroundModeStatus.isAutoModeEnabled } returns flowOf(true) - createTask().run(arguments) shouldBe RiskLevelTaskResult( + createTask().run(arguments) shouldBe EwRiskLevelTaskResult( calculatedAt = Instant.EPOCH, - failureReason = RiskLevelResult.FailureReason.OUTDATED_RESULTS + failureReason = EwRiskLevelResult.FailureReason.OUTDATED_RESULTS ) } @@ -165,9 +165,9 @@ class RiskLevelTaskTest : BaseTest() { fun `risk calculation is skipped if results are not existing while no background mode`() = runBlockingTest { coEvery { keyCacheRepository.getAllCachedKeys() } returns listOf() every { backgroundModeStatus.isAutoModeEnabled } returns flowOf(false) - createTask().run(arguments) shouldBe RiskLevelTaskResult( + createTask().run(arguments) shouldBe EwRiskLevelTaskResult( calculatedAt = Instant.EPOCH, - failureReason = RiskLevelResult.FailureReason.OUTDATED_RESULTS_MANUAL + failureReason = EwRiskLevelResult.FailureReason.OUTDATED_RESULTS_MANUAL ) } @@ -184,9 +184,9 @@ class RiskLevelTaskTest : BaseTest() { every { backgroundModeStatus.isAutoModeEnabled } returns flowOf(true) every { timeStamper.nowUTC } returns now - createTask().run(arguments) shouldBe RiskLevelTaskResult( + createTask().run(arguments) shouldBe EwRiskLevelTaskResult( calculatedAt = now, - failureReason = RiskLevelResult.FailureReason.OUTDATED_RESULTS + failureReason = EwRiskLevelResult.FailureReason.OUTDATED_RESULTS ) } @@ -203,9 +203,9 @@ class RiskLevelTaskTest : BaseTest() { every { backgroundModeStatus.isAutoModeEnabled } returns flowOf(false) every { timeStamper.nowUTC } returns now - createTask().run(arguments) shouldBe RiskLevelTaskResult( + createTask().run(arguments) shouldBe EwRiskLevelTaskResult( calculatedAt = now, - failureReason = RiskLevelResult.FailureReason.OUTDATED_RESULTS_MANUAL + failureReason = EwRiskLevelResult.FailureReason.OUTDATED_RESULTS_MANUAL ) } @@ -224,9 +224,9 @@ class RiskLevelTaskTest : BaseTest() { every { submissionSettings.isAllowedToSubmitKeys } returns true every { submissionSettings.hasViewedTestResult.value } returns true - createTask().run(arguments) shouldBe RiskLevelTaskResult( + createTask().run(arguments) shouldBe EwRiskLevelTaskResult( calculatedAt = now, - failureReason = RiskLevelResult.FailureReason.POSITIVE_TEST_RESULT + failureReason = EwRiskLevelResult.FailureReason.POSITIVE_TEST_RESULT ) } @@ -238,7 +238,7 @@ class RiskLevelTaskTest : BaseTest() { } } val now = Instant.parse("2020-12-28") - val aggregatedRiskResult = mockk<AggregatedRiskResult>().apply { + val aggregatedRiskResult = mockk<EwAggregatedRiskResult>().apply { every { isIncreasedRisk() } returns true } @@ -251,10 +251,10 @@ class RiskLevelTaskTest : BaseTest() { every { submissionSettings.isAllowedToSubmitKeys } returns true every { submissionSettings.hasViewedTestResult.value } returns false - createTask().run(arguments) shouldBe RiskLevelTaskResult( + createTask().run(arguments) shouldBe EwRiskLevelTaskResult( calculatedAt = now, failureReason = null, - aggregatedRiskResult = aggregatedRiskResult, + ewAggregatedRiskResult = aggregatedRiskResult, listOf() ) } @@ -267,7 +267,7 @@ class RiskLevelTaskTest : BaseTest() { } } val now = Instant.parse("2020-12-28") - val aggregatedRiskResult = mockk<AggregatedRiskResult>().apply { + val aggregatedRiskResult = mockk<EwAggregatedRiskResult>().apply { every { isIncreasedRisk() } returns true } @@ -278,10 +278,10 @@ class RiskLevelTaskTest : BaseTest() { every { timeStamper.nowUTC } returns now coEvery { analyticsExposureWindowCollector.reportRiskResultsPerWindow(any()) } just Runs - createTask().run(arguments) shouldBe RiskLevelTaskResult( + createTask().run(arguments) shouldBe EwRiskLevelTaskResult( calculatedAt = now, failureReason = null, - aggregatedRiskResult = aggregatedRiskResult, + ewAggregatedRiskResult = aggregatedRiskResult, listOf() ) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/result/AggregatedRiskPerDateResultTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/result/ExposureWindowDayRiskTest.kt similarity index 95% rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/result/AggregatedRiskPerDateResultTest.kt rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/result/ExposureWindowDayRiskTest.kt index f31e3b75a8ff31d9891ba1ef6895ec4456b7246b..cf04f40a166d7543ff6a7fe049f52551e090d7e8 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/result/AggregatedRiskPerDateResultTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/result/ExposureWindowDayRiskTest.kt @@ -9,7 +9,7 @@ import org.junit.jupiter.api.Test import testhelpers.BaseTest import timber.log.Timber -class AggregatedRiskPerDateResultTest : BaseTest() { +class ExposureWindowDayRiskTest : BaseTest() { @Test fun `day is correct`() { @@ -34,7 +34,7 @@ class AggregatedRiskPerDateResultTest : BaseTest() { tomorrowAggregatedRiskPerDateResult.localDateUtc shouldBe tomorrowLocalDate } - private fun createAggregatedRiskPerDateResult(date: Instant) = AggregatedRiskPerDateResult( + private fun createAggregatedRiskPerDateResult(date: Instant) = ExposureWindowDayRisk( dateMillisSinceEpoch = date.millis, riskLevel = RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH, minimumDistinctEncountersWithLowRisk = 0, diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorageTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorageTest.kt index 2ea28a2923f9e6476e850f44a8cc3899529b20b9..099afd385a811e046f64a17a1cc4e9a742d1cbaa 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorageTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorageTest.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.risk.storage import de.rki.coronawarnapp.presencetracing.risk.PresenceTracingRiskRepository -import de.rki.coronawarnapp.risk.RiskLevelResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.risk.storage.RiskStorageTestData.testAggregatedRiskPerDateResult import de.rki.coronawarnapp.risk.storage.RiskStorageTestData.testExposureWindow import de.rki.coronawarnapp.risk.storage.RiskStorageTestData.testExposureWindowDaoWrapper @@ -66,14 +66,18 @@ class BaseRiskLevelStorageTest : BaseTest() { every { aggregatedRiskPerDateResultDao.allEntries() } returns emptyFlow() coEvery { aggregatedRiskPerDateResultDao.insertRisk(any()) } just Runs + + // TODO proper tests coEvery { presenceTracingRiskRepository.traceLocationCheckInRiskStates } returns emptyFlow() coEvery { presenceTracingRiskRepository.presenceTracingDayRisk } returns emptyFlow() + coEvery { presenceTracingRiskRepository.latestAndLastSuccessful() } returns emptyFlow() + coEvery { presenceTracingRiskRepository.latestEntries(any()) } returns emptyFlow() } private fun createInstance( scope: CoroutineScope = TestCoroutineScope(), storedResultLimit: Int = 10, - onStoreExposureWindows: (String, RiskLevelResult) -> Unit = { id, result -> }, + onStoreExposureWindows: (String, EwRiskLevelResult) -> Unit = { id, result -> }, onDeletedOrphanedExposureWindows: () -> Unit = {} ) = object : BaseRiskLevelStorage( scope = scope, @@ -82,8 +86,8 @@ class BaseRiskLevelStorageTest : BaseTest() { ) { override val storedResultLimit: Int = storedResultLimit - override suspend fun storeExposureWindows(storedResultId: String, result: RiskLevelResult) { - onStoreExposureWindows(storedResultId, result) + override suspend fun storeExposureWindows(storedResultId: String, resultEw: EwRiskLevelResult) { + onStoreExposureWindows(storedResultId, resultEw) } override suspend fun deletedOrphanedExposureWindows() { @@ -102,7 +106,7 @@ class BaseRiskLevelStorageTest : BaseTest() { allEntries shouldBe testPersistedAggregatedRiskPerDateResultFlow allEntries.first().map { it.toAggregatedRiskPerDateResult() } shouldBe listOf(testAggregatedRiskPerDateResult) - val aggregatedRiskPerDateResults = instance.aggregatedRiskPerDateResults.first() + val aggregatedRiskPerDateResults = instance.ewDayRiskStates.first() aggregatedRiskPerDateResults shouldNotBe listOf(testPersistedAggregatedRiskPerDateResult) aggregatedRiskPerDateResults shouldBe listOf(testAggregatedRiskPerDateResult) } @@ -127,7 +131,7 @@ class BaseRiskLevelStorageTest : BaseTest() { runBlockingTest { val instance = createInstance() - instance.allRiskLevelResults.first() shouldBe listOf(testRisklevelResult) + instance.allEwRiskLevelResults.first() shouldBe listOf(testRisklevelResult) } } @@ -139,7 +143,7 @@ class BaseRiskLevelStorageTest : BaseTest() { runBlockingTest { val instance = createInstance() val riskLevelResult = testRisklevelResult.copy(exposureWindows = listOf(testExposureWindow)) - instance.allRiskLevelResults.first() shouldBe listOf(riskLevelResult) + instance.allEwRiskLevelResults.first() shouldBe listOf(riskLevelResult) verify { riskResultTables.allEntries() @@ -157,7 +161,7 @@ class BaseRiskLevelStorageTest : BaseTest() { val instance = createInstance(scope = this) val riskLevelResult = testRisklevelResult.copy(exposureWindows = listOf(testExposureWindow)) - instance.latestRiskLevelResults.first() shouldBe listOf(riskLevelResult) + instance.latestEwRiskLevelResults.first() shouldBe listOf(riskLevelResult) verify { riskResultTables.latestEntries(2) @@ -176,7 +180,7 @@ class BaseRiskLevelStorageTest : BaseTest() { val instance = createInstance(scope = this) val riskLevelResult = testRisklevelResult.copy(exposureWindows = listOf(testExposureWindow)) - instance.latestAndLastSuccessful.first() shouldBe listOf(riskLevelResult) + instance.latestAndLastSuccessfulEwRiskLevelResult.first() shouldBe listOf(riskLevelResult) verify { riskResultTables.latestAndLastSuccessful() @@ -204,7 +208,7 @@ class BaseRiskLevelStorageTest : BaseTest() { @Test fun `storeResult works`() = runBlockingTest { - val mockStoreWindows: (String, RiskLevelResult) -> Unit = spyk() + val mockStoreWindows: (String, EwRiskLevelResult) -> Unit = spyk() val mockDeleteOrphanedWindows: () -> Unit = spyk() val instance = createInstance( diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/CombineRiskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/CombineRiskTest.kt index c984d0b6a0a53dbbc6adfdf1229797b6f0f67b95..824be93cc148c67284dc133d0de47990018141c5 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/CombineRiskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/CombineRiskTest.kt @@ -2,7 +2,7 @@ package de.rki.coronawarnapp.risk.storage import de.rki.coronawarnapp.presencetracing.risk.PresenceTracingDayRisk import de.rki.coronawarnapp.risk.RiskState -import de.rki.coronawarnapp.risk.result.AggregatedRiskPerDateResult +import de.rki.coronawarnapp.risk.result.ExposureWindowDayRisk import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel import io.kotest.matchers.shouldBe import org.joda.time.LocalDate @@ -25,25 +25,25 @@ class CombineRiskTest { localDateUtc = LocalDate(2021, 3, 22), riskState = RiskState.CALCULATION_FAILED ) - val ewRisk = AggregatedRiskPerDateResult( + val ewRisk = ExposureWindowDayRisk( dateMillisSinceEpoch = Instant.parse("2021-03-22T14:00:00.000Z").toEpochMilli(), riskLevel = RiskLevel.HIGH, 0, 0 ) - val ewRisk2 = AggregatedRiskPerDateResult( + val ewRisk2 = ExposureWindowDayRisk( dateMillisSinceEpoch = Instant.parse("2021-03-19T14:00:00.000Z").toEpochMilli(), riskLevel = RiskLevel.LOW, 0, 0 ) - val ewRisk3 = AggregatedRiskPerDateResult( + val ewRisk3 = ExposureWindowDayRisk( dateMillisSinceEpoch = Instant.parse("2021-03-20T14:00:00.000Z").toEpochMilli(), riskLevel = RiskLevel.UNSPECIFIED, 0, 0 ) - val ewRisk4 = AggregatedRiskPerDateResult( + val ewRisk4 = ExposureWindowDayRisk( dateMillisSinceEpoch = Instant.parse("2021-03-15T14:00:00.000Z").toEpochMilli(), riskLevel = RiskLevel.UNSPECIFIED, 0, @@ -51,8 +51,8 @@ class CombineRiskTest { ) val ptRiskList: List<PresenceTracingDayRisk> = listOf(ptRisk, ptRisk2, ptRisk3) - val ewRiskList: List<AggregatedRiskPerDateResult> = listOf(ewRisk, ewRisk2, ewRisk3, ewRisk4) - val result = combineRisk(ptRiskList, ewRiskList) + val exposureWindowDayRiskList: List<ExposureWindowDayRisk> = listOf(ewRisk, ewRisk2, ewRisk3, ewRisk4) + val result = combineRisk(ptRiskList, exposureWindowDayRiskList) result.size shouldBe 5 result.find { it.localDate == LocalDate(2021, 3, 15) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/RiskStorageTestData.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/RiskStorageTestData.kt index 3301437ffe8c94c9557de4a6e95643493a539cf7..34c79dac15a342d5194b7f53f23d8db624fad6e8 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/RiskStorageTestData.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/RiskStorageTestData.kt @@ -2,9 +2,9 @@ package de.rki.coronawarnapp.risk.storage import com.google.android.gms.nearby.exposurenotification.ExposureWindow import com.google.android.gms.nearby.exposurenotification.ScanInstance -import de.rki.coronawarnapp.risk.RiskLevelTaskResult -import de.rki.coronawarnapp.risk.result.AggregatedRiskPerDateResult -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.EwRiskLevelTaskResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult +import de.rki.coronawarnapp.risk.result.ExposureWindowDayRisk import de.rki.coronawarnapp.risk.storage.internal.riskresults.PersistedAggregatedRiskPerDateResult import de.rki.coronawarnapp.risk.storage.internal.riskresults.PersistedRiskLevelResultDao import de.rki.coronawarnapp.risk.storage.internal.windows.PersistedExposureWindowDao @@ -29,7 +29,7 @@ object RiskStorageTestData { ) ) - val testAggregatedRiskResult = AggregatedRiskResult( + val testAggregatedRiskResult = EwAggregatedRiskResult( totalRiskLevel = RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH, totalMinimumDistinctEncountersWithLowRisk = 1, totalMinimumDistinctEncountersWithHighRisk = 2, @@ -39,9 +39,9 @@ object RiskStorageTestData { numberOfDaysWithHighRisk = 6 ) - val testRisklevelResult = RiskLevelTaskResult( + val testRisklevelResult = EwRiskLevelTaskResult( calculatedAt = Instant.ofEpochMilli(9999L), - aggregatedRiskResult = testAggregatedRiskResult, + ewAggregatedRiskResult = testAggregatedRiskResult, exposureWindows = null ) @@ -75,7 +75,7 @@ object RiskStorageTestData { }.build().let { setScanInstances(listOf(it)) } }.build() - val testAggregatedRiskPerDateResult = AggregatedRiskPerDateResult( + val testAggregatedRiskPerDateResult = ExposureWindowDayRisk( dateMillisSinceEpoch = 9999L, riskLevel = RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH, minimumDistinctEncountersWithLowRisk = 0, @@ -90,8 +90,8 @@ object RiskStorageTestData { ) val testRisklevelResultWithAggregatedRiskPerDateResult = testRisklevelResult.copy( - aggregatedRiskResult = testAggregatedRiskResult.copy( - aggregatedRiskPerDateResults = listOf(testAggregatedRiskPerDateResult) + ewAggregatedRiskResult = testAggregatedRiskResult.copy( + exposureWindowDayRisks = listOf(testAggregatedRiskPerDateResult) ) ) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/internal/PersistedRiskResultDaoTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/internal/PersistedRiskResultDaoTest.kt index 51adcacd6ddef65210794636c93bcf0586a6648b..52e7ac6608e75a1db404491453f5d65442d29d09 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/internal/PersistedRiskResultDaoTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/internal/PersistedRiskResultDaoTest.kt @@ -1,6 +1,6 @@ package de.rki.coronawarnapp.risk.storage.internal -import de.rki.coronawarnapp.risk.RiskLevelResult +import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.storage.RiskStorageTestData.testExposureWindow import de.rki.coronawarnapp.risk.storage.RiskStorageTestData.testExposureWindowDaoWrapper @@ -34,8 +34,8 @@ class PersistedRiskResultDaoTest : BaseTest() { calculatedAt.millis shouldBe 931161601L exposureWindows shouldBe listOf(testExposureWindow) failureReason shouldBe null - aggregatedRiskResult shouldNotBe null - aggregatedRiskResult?.apply { + ewAggregatedRiskResult shouldNotBe null + ewAggregatedRiskResult?.apply { totalRiskLevel shouldBe RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.LOW totalMinimumDistinctEncountersWithLowRisk shouldBe 89 totalMinimumDistinctEncountersWithHighRisk shouldBe 59 @@ -69,8 +69,8 @@ class PersistedRiskResultDaoTest : BaseTest() { calculatedAt.millis shouldBe 931161601L exposureWindows shouldBe null failureReason shouldBe null - aggregatedRiskResult shouldNotBe null - aggregatedRiskResult?.apply { + ewAggregatedRiskResult shouldNotBe null + ewAggregatedRiskResult?.apply { totalRiskLevel shouldBe RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.LOW totalMinimumDistinctEncountersWithLowRisk shouldBe 89 totalMinimumDistinctEncountersWithHighRisk shouldBe 59 @@ -89,14 +89,14 @@ class PersistedRiskResultDaoTest : BaseTest() { PersistedRiskLevelResultDao( id = "", calculatedAt = Instant.ofEpochMilli(931161601L), - failureReason = RiskLevelResult.FailureReason.TRACING_OFF, + failureReason = EwRiskLevelResult.FailureReason.TRACING_OFF, aggregatedRiskResult = null ).toRiskResult().apply { riskState shouldBe RiskState.CALCULATION_FAILED calculatedAt.millis shouldBe 931161601L exposureWindows shouldBe null - failureReason shouldBe RiskLevelResult.FailureReason.TRACING_OFF - aggregatedRiskResult shouldBe null + failureReason shouldBe EwRiskLevelResult.FailureReason.TRACING_OFF + ewAggregatedRiskResult shouldBe null } } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/IncreasedRiskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/IncreasedRiskTest.kt index 438f59d49a8f58bb6915a21ecd65de085d656edd..6bdb0e7b9956f87eab251c24a447ed198196a4ee 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/IncreasedRiskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/IncreasedRiskTest.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.tracing.states import android.content.Context import de.rki.coronawarnapp.R import de.rki.coronawarnapp.risk.RiskState +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.every @@ -61,6 +62,6 @@ internal class IncreasedRiskTest { lastExposureDetectionTime = Instant.now(), allowManualUpdate = false, daysWithEncounters = 1, - lastEncounterAt = Instant.now() + lastEncounterAt = Instant.now().toLocalDateUtc() ) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/LowRiskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/LowRiskTest.kt index 3bc2c760b50fb3d259a4fde85c58fcd134002d1b..c05e76379087e2cbad7a02ad7a43f09f4190c1ed 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/LowRiskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/LowRiskTest.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.tracing.states import android.content.Context import de.rki.coronawarnapp.R import de.rki.coronawarnapp.risk.RiskState +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.every @@ -44,7 +45,7 @@ internal class LowRiskTest { @Test fun `Active tracing row should be gone in low risk card on the home screen when there are encounters`() { defaultRisk - .copy(daysWithEncounters = 1, lastEncounterAt = Instant.now()) + .copy(daysWithEncounters = 1, lastEncounterAt = Instant.now().toLocalDateUtc()) .isGoneOnContentLowView(context) shouldBe true } @@ -58,7 +59,7 @@ internal class LowRiskTest { @Test fun `Active tracing row should be shown in low risk detail screen when there are encounters`() { defaultRisk - .copy(daysWithEncounters = 1, lastEncounterAt = Instant.now(), isInDetailsMode = true) + .copy(daysWithEncounters = 1, lastEncounterAt = Instant.now().toLocalDateUtc(), isInDetailsMode = true) .isGoneOnContentLowView(context) shouldBe false } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt index 7ca361ae4bfbf43bfd339e3262fadbd93f3c097c..27175e36d1849b303abc331b0bf1ebbe25b838ce 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt @@ -5,9 +5,12 @@ import android.content.res.Resources import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.datadonation.survey.Surveys import de.rki.coronawarnapp.installTime.InstallTimeProvider +import de.rki.coronawarnapp.presencetracing.risk.PtRiskLevelResult +import de.rki.coronawarnapp.risk.EwRiskLevelTaskResult import de.rki.coronawarnapp.risk.ProtoRiskLevel -import de.rki.coronawarnapp.risk.RiskLevelTaskResult -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.RiskState +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult +import de.rki.coronawarnapp.risk.storage.CombinedEwPtRiskLevelResult import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.tracing.GeneralTracingStatus import de.rki.coronawarnapp.tracing.ui.details.items.additionalinfos.AdditionalInfoLowRiskBox @@ -36,7 +39,7 @@ class TracingDetailsItemProviderTest : BaseTest() { @MockK(relaxed = true) lateinit var context: Context @MockK(relaxed = true) lateinit var resources: Resources - @MockK(relaxed = true) lateinit var aggregatedRiskResult: AggregatedRiskResult + @MockK(relaxed = true) lateinit var ewAggregatedRiskResult: EwAggregatedRiskResult @MockK lateinit var tracingStatus: GeneralTracingStatus @MockK lateinit var riskLevelStorage: RiskLevelStorage @@ -64,25 +67,36 @@ class TracingDetailsItemProviderTest : BaseTest() { availableSurveys: List<Surveys.Type> = emptyList() ) { every { tracingStatus.generalStatus } returns flowOf(status) - every { aggregatedRiskResult.totalRiskLevel } returns riskLevel + every { ewAggregatedRiskResult.totalRiskLevel } returns riskLevel every { installTimeProvider.daysSinceInstallation } returns daysSinceInstallation every { surveys.availableSurveys } returns flowOf(availableSurveys) if (riskLevel == ProtoRiskLevel.LOW) { - every { aggregatedRiskResult.isLowRisk() } returns true + every { ewAggregatedRiskResult.isLowRisk() } returns true } else if (riskLevel == ProtoRiskLevel.HIGH) { - every { aggregatedRiskResult.isIncreasedRisk() } returns true + every { ewAggregatedRiskResult.isIncreasedRisk() } returns true } val exposureWindow: ExposureWindow = mockk() - val riskLevelResult = RiskLevelTaskResult( + val ewRiskLevelTaskResult = EwRiskLevelTaskResult( calculatedAt = Instant.EPOCH, - aggregatedRiskResult = aggregatedRiskResult, + ewAggregatedRiskResult = ewAggregatedRiskResult, exposureWindows = listOf(exposureWindow) ) - every { riskLevelResult.matchedKeyCount } returns matchedKeyCount - every { riskLevelStorage.latestAndLastSuccessful } returns flowOf(listOf(riskLevelResult)) + + val ptRiskLevelResult = PtRiskLevelResult( + calculatedAt = Instant.EPOCH, + riskState = RiskState.CALCULATION_FAILED + ) + val combined = CombinedEwPtRiskLevelResult( + ewRiskLevelResult = ewRiskLevelTaskResult, + ptRiskLevelResult = ptRiskLevelResult + ) + every { ewRiskLevelTaskResult.matchedKeyCount } returns matchedKeyCount + every { riskLevelStorage.latestAndLastSuccessfulEwRiskLevelResult } returns flowOf(listOf(ewRiskLevelTaskResult)) + // TODO tests + every { riskLevelStorage.latestAndLastSuccessfulCombinedEwPtRiskLevelResult } returns flowOf(listOf(combined)) } @Test diff --git a/Corona-Warn-App/src/testDevice/java/de/rki/coronawarnapp/test/risk/storage/DefaultRiskLevelStorageTest.kt b/Corona-Warn-App/src/testDevice/java/de/rki/coronawarnapp/test/risk/storage/DefaultRiskLevelStorageTest.kt index c8ff0a812d47f5402c613e21a3642fd0a9d7bab6..8c676d6ab2f8cd5353ff3445e8919809507c5722 100644 --- a/Corona-Warn-App/src/testDevice/java/de/rki/coronawarnapp/test/risk/storage/DefaultRiskLevelStorageTest.kt +++ b/Corona-Warn-App/src/testDevice/java/de/rki/coronawarnapp/test/risk/storage/DefaultRiskLevelStorageTest.kt @@ -2,8 +2,8 @@ package de.rki.coronawarnapp.test.risk.storage import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.presencetracing.risk.PresenceTracingRiskRepository -import de.rki.coronawarnapp.risk.RiskLevelTaskResult -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.EwRiskLevelTaskResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import de.rki.coronawarnapp.risk.storage.DefaultRiskLevelStorage import de.rki.coronawarnapp.risk.storage.internal.RiskResultDatabase import de.rki.coronawarnapp.risk.storage.internal.riskresults.PersistedRiskLevelResultDao @@ -48,9 +48,9 @@ class DefaultRiskLevelStorageTest : BaseTest() { ), failureReason = null ) - private val testRisklevelResult = RiskLevelTaskResult( + private val testRisklevelResult = EwRiskLevelTaskResult( calculatedAt = Instant.ofEpochMilli(9999L), - aggregatedRiskResult = AggregatedRiskResult( + ewAggregatedRiskResult = EwAggregatedRiskResult( totalRiskLevel = RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH, totalMinimumDistinctEncountersWithLowRisk = 1, totalMinimumDistinctEncountersWithHighRisk = 2, @@ -88,6 +88,8 @@ class DefaultRiskLevelStorageTest : BaseTest() { every { presenceTracingRiskRepository.traceLocationCheckInRiskStates } returns emptyFlow() every { presenceTracingRiskRepository.presenceTracingDayRisk } returns emptyFlow() + every { presenceTracingRiskRepository.latestAndLastSuccessful() } returns emptyFlow() + every { presenceTracingRiskRepository.latestEntries(any()) } returns emptyFlow() } private fun createInstance( diff --git a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risk/storage/DefaultRiskLevelStorageTest.kt b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risk/storage/DefaultRiskLevelStorageTest.kt index 17810289bc5b74f043484df754dd2cfdfa7f1e4d..24478cb801c98a653901968debb4b0b5e1374967 100644 --- a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risk/storage/DefaultRiskLevelStorageTest.kt +++ b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risk/storage/DefaultRiskLevelStorageTest.kt @@ -2,8 +2,8 @@ package de.rki.coronawarnapp.test.risk.storage import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.presencetracing.risk.PresenceTracingRiskRepository -import de.rki.coronawarnapp.risk.RiskLevelTaskResult -import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.EwRiskLevelTaskResult +import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import de.rki.coronawarnapp.risk.storage.DefaultRiskLevelStorage import de.rki.coronawarnapp.risk.storage.internal.RiskResultDatabase import de.rki.coronawarnapp.risk.storage.internal.riskresults.PersistedRiskLevelResultDao @@ -47,9 +47,9 @@ class DefaultRiskLevelStorageTest : BaseTestInstrumentation() { ), failureReason = null ) - private val testRisklevelResult = RiskLevelTaskResult( + private val testRisklevelResult = EwRiskLevelTaskResult( calculatedAt = Instant.ofEpochMilli(9999L), - aggregatedRiskResult = AggregatedRiskResult( + ewAggregatedRiskResult = EwAggregatedRiskResult( totalRiskLevel = RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH, totalMinimumDistinctEncountersWithLowRisk = 1, totalMinimumDistinctEncountersWithHighRisk = 2, @@ -87,6 +87,8 @@ class DefaultRiskLevelStorageTest : BaseTestInstrumentation() { every { presenceTracingRiskRepository.traceLocationCheckInRiskStates } returns emptyFlow() every { presenceTracingRiskRepository.presenceTracingDayRisk } returns emptyFlow() + every { presenceTracingRiskRepository.latestAndLastSuccessful() } returns emptyFlow() + every { presenceTracingRiskRepository.latestEntries(any()) } returns emptyFlow() } private fun createInstance() = DefaultRiskLevelStorage(