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 75beae1cde4a2d812660775b43d86134e04050e1..cd78b9e7ecf5edaf9ae8d7c742da8af74abf203e 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 @@ -9,7 +9,6 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.appconfig.AppConfigProvider -import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysSettings import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository @@ -109,31 +108,9 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( val backendParameters = appConfigProvider .currentConfig - .map { it.toReadableString() } + .map { it.rawConfig.riskCalculationParameters.toString() } .asLiveData() - private fun ConfigData.toReadableString(): String = StringBuilder() - .appendLine("Transmission RiskLevel Multiplier: $transmissionRiskLevelMultiplier") - .appendLine() - .appendLine("Minutes At Attenuation Filters:") - .appendLine(minutesAtAttenuationFilters) - .appendLine() - .appendLine("Minutes At Attenuation Weights:") - .appendLine(minutesAtAttenuationWeights) - .appendLine() - .appendLine("Transmission RiskLevel Encoding:") - .appendLine(transmissionRiskLevelEncoding) - .appendLine() - .appendLine("Transmission RiskLevel Filters:") - .appendLine(transmissionRiskLevelFilters) - .appendLine() - .appendLine("Normalized Time Per Exposure Window To RiskLevel Mapping:") - .appendLine(normalizedTimePerExposureWindowToRiskLevelMapping) - .appendLine() - .appendLine("Normalized Time Per Day To RiskLevel Mapping List:") - .appendLine(normalizedTimePerDayToRiskLevelMappingList) - .toString() - val additionalRiskCalcInfo = combine( riskLevelStorage.latestAndLastSuccessful, exposureDetectionTracker.latestSubmission() diff --git a/Corona-Warn-App/src/main/assets/default_app_config_android.bin b/Corona-Warn-App/src/main/assets/default_app_config_android.bin index 71db85788c2beff255f07410e14f0ec2bbea4bbb..aeac5e2d45c54db350f6219cbc4487ee8c691af0 100644 Binary files a/Corona-Warn-App/src/main/assets/default_app_config_android.bin and b/Corona-Warn-App/src/main/assets/default_app_config_android.bin differ diff --git a/Corona-Warn-App/src/main/assets/default_app_config_android.sha256 b/Corona-Warn-App/src/main/assets/default_app_config_android.sha256 index dcbdbc7b85dbed0619623f041c6f02d32ae7ede1..bd4fdf7c0da4ec5b13788ea1547f8d91c8fdb278 100644 --- a/Corona-Warn-App/src/main/assets/default_app_config_android.sha256 +++ b/Corona-Warn-App/src/main/assets/default_app_config_android.sha256 @@ -1 +1 @@ -12d0b93c0c02c6870ef75c173a53a8ffb9cab6828fbf22e751053329c425eef2 \ No newline at end of file +3d108b3fee7d1b4c227087c82bb804048de8d0542c3f2b26cf507a918201124d \ No newline at end of file diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureWindowRiskCalculationConfig.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureWindowRiskCalculationConfig.kt index cf72d970a6f1ea04e86bf7530fe85b156f976322..f772fa17ec4784b7c0f53dfd6777979831047a22 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureWindowRiskCalculationConfig.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureWindowRiskCalculationConfig.kt @@ -9,11 +9,11 @@ interface ExposureWindowRiskCalculationConfig { val minutesAtAttenuationWeights: List<RiskCalculationParametersOuterClass.MinutesAtAttenuationWeight> val transmissionRiskLevelEncoding: RiskCalculationParametersOuterClass.TransmissionRiskLevelEncoding val transmissionRiskLevelFilters: List<RiskCalculationParametersOuterClass.TrlFilter> - val transmissionRiskLevelMultiplier: Double val normalizedTimePerExposureWindowToRiskLevelMapping: List<RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping> val normalizedTimePerDayToRiskLevelMappingList: List<RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping> + val transmissionRiskValueMapping: List<RiskCalculationParametersOuterClass.TransmissionRiskValueMapping> val diagnosisKeysDataMapping: DiagnosisKeysDataMapping interface Mapper { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiV2.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiV2.kt index 5d1c0f2c3d0e9654b9af57aa6121bb3cd9d53950..18c6e25091e38a124ec41967ccb6816dbc6175d3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiV2.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiV2.kt @@ -6,6 +6,6 @@ import retrofit2.http.GET interface AppConfigApiV2 { - @GET("/version/v1/app_config_android") + @GET("/version/v2/app_config_android") suspend fun getApplicationConfiguration(): Response<ResponseBody> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt index 460187ecc5343703dde92e21bdb38ce35a2155fc..03ab772373f8841087ab2d1ab32662dc2ab0a015 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt @@ -28,20 +28,15 @@ class ExposureWindowRiskCalculationConfigMapper @Inject constructor() : val riskCalculationParameters = rawConfig.riskCalculationParameters return ExposureWindowRiskCalculationContainer( - minutesAtAttenuationFilters = riskCalculationParameters - .minutesAtAttenuationFiltersList, - minutesAtAttenuationWeights = riskCalculationParameters - .minutesAtAttenuationWeightsList, - transmissionRiskLevelEncoding = riskCalculationParameters - .trlEncoding, - transmissionRiskLevelFilters = riskCalculationParameters - .trlFiltersList, - transmissionRiskLevelMultiplier = riskCalculationParameters - .transmissionRiskLevelMultiplier, - normalizedTimePerExposureWindowToRiskLevelMapping = riskCalculationParameters - .normalizedTimePerEWToRiskLevelMappingList, - normalizedTimePerDayToRiskLevelMappingList = riskCalculationParameters - .normalizedTimePerDayToRiskLevelMappingList, + minutesAtAttenuationFilters = riskCalculationParameters.minutesAtAttenuationFiltersList, + minutesAtAttenuationWeights = riskCalculationParameters.minutesAtAttenuationWeightsList, + transmissionRiskLevelEncoding = riskCalculationParameters.trlEncoding, + transmissionRiskLevelFilters = riskCalculationParameters.trlFiltersList, + normalizedTimePerExposureWindowToRiskLevelMapping = + riskCalculationParameters.normalizedTimePerEWToRiskLevelMappingList, + normalizedTimePerDayToRiskLevelMappingList = + riskCalculationParameters.normalizedTimePerDayToRiskLevelMappingList, + transmissionRiskValueMapping = riskCalculationParameters.transmissionRiskValueMappingList, diagnosisKeysDataMapping = rawConfig.diagnosisKeysDataMapping() ) } @@ -63,12 +58,14 @@ class ExposureWindowRiskCalculationConfigMapper @Inject constructor() : override val minutesAtAttenuationFilters: List<RiskCalculationParametersOuterClass.MinutesAtAttenuationFilter>, override val minutesAtAttenuationWeights: List<RiskCalculationParametersOuterClass.MinutesAtAttenuationWeight>, override val transmissionRiskLevelEncoding: RiskCalculationParametersOuterClass.TransmissionRiskLevelEncoding, - override val transmissionRiskLevelFilters: List<RiskCalculationParametersOuterClass.TrlFilter>, - override val transmissionRiskLevelMultiplier: Double, + override val transmissionRiskLevelFilters: + List<RiskCalculationParametersOuterClass.TrlFilter>, override val normalizedTimePerExposureWindowToRiskLevelMapping: List<RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping>, override val normalizedTimePerDayToRiskLevelMappingList: List<RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping>, + override val transmissionRiskValueMapping: + List<RiskCalculationParametersOuterClass.TransmissionRiskValueMapping>, override val diagnosisKeysDataMapping: DiagnosisKeysDataMapping ) : ExposureWindowRiskCalculationConfig } 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 734d7ff3de2b076aa8307f5c99c4e379730d17b2..85fd30cce117c6ae076783472a42c243834f098e 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 @@ -1,6 +1,5 @@ package de.rki.coronawarnapp.risk -import android.text.TextUtils import com.google.android.gms.nearby.exposurenotification.ExposureWindow import com.google.android.gms.nearby.exposurenotification.Infectiousness import com.google.android.gms.nearby.exposurenotification.ReportType @@ -114,8 +113,9 @@ class DefaultRiskLevels @Inject constructor() : RiskLevels { return null } - val transmissionRiskValue: Double = - transmissionRiskLevel * appConfig.transmissionRiskLevelMultiplier + val transmissionRiskValue: Double = appConfig.transmissionRiskValueMapping + .find { it.transmissionRiskLevel == transmissionRiskLevel } + ?.transmissionRiskValue ?: 0.0 Timber.d("%s's transmissionRiskValue is: %s", exposureWindow, transmissionRiskValue) @@ -135,8 +135,12 @@ class DefaultRiskLevels @Inject constructor() : RiskLevels { ) if (riskLevel == null) { - Timber.e("Exposure Window: $exposureWindow could not be mapped to a risk level") - throw NormalizedTimePerExposureWindowToRiskLevelMappingMissingException() + Timber.d( + "%s dropped due to risk level filter is %s", + exposureWindow, + riskLevel + ) + return null } Timber.d("%s's riskLevel is: %s", exposureWindow, riskLevel) @@ -158,13 +162,13 @@ class DefaultRiskLevels @Inject constructor() : RiskLevels { Timber.d( "uniqueDates: %s", - { TextUtils.join(System.lineSeparator(), uniqueDatesMillisSinceEpoch) } + uniqueDatesMillisSinceEpoch ) - val exposureHistory = uniqueDatesMillisSinceEpoch.map { + val exposureHistory = uniqueDatesMillisSinceEpoch.mapNotNull { aggregateRiskPerDate(appConfig, it, exposureWindowResultMap) } - Timber.d("exposureHistory size: ${exposureHistory.size}") + Timber.d("exposureHistory size: %d", exposureHistory.size) // 6. Determine `Total Risk` val totalRiskLevel = @@ -180,43 +184,43 @@ class DefaultRiskLevels @Inject constructor() : RiskLevels { RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.LOW } - Timber.d("totalRiskLevel: ${totalRiskLevel.name} (${totalRiskLevel.ordinal})") + Timber.d("totalRiskLevel: %s (%d)", totalRiskLevel.name, totalRiskLevel.ordinal) // 7. Determine `Date of Most Recent Date with Low Risk` val mostRecentDateWithLowRisk = exposureHistory.mostRecentDateForRisk(ProtoRiskLevel.LOW) - Timber.d("mostRecentDateWithLowRisk: $mostRecentDateWithLowRisk") + Timber.d("mostRecentDateWithLowRisk: %s", mostRecentDateWithLowRisk) // 8. Determine `Date of Most Recent Date with High Risk` val mostRecentDateWithHighRisk = exposureHistory.mostRecentDateForRisk(ProtoRiskLevel.HIGH) - Timber.d("mostRecentDateWithHighRisk: $mostRecentDateWithHighRisk") + Timber.d("mostRecentDateWithHighRisk: %s", mostRecentDateWithHighRisk) // 9. Determine `Total Minimum Distinct Encounters With Low Risk` val totalMinimumDistinctEncountersWithLowRisk = exposureHistory .sumBy { it.minimumDistinctEncountersWithLowRisk } - Timber.d("totalMinimumDistinctEncountersWithLowRisk: $totalMinimumDistinctEncountersWithLowRisk") + Timber.d("totalMinimumDistinctEncountersWithLowRisk: %d", totalMinimumDistinctEncountersWithLowRisk) // 10. Determine `Total Minimum Distinct Encounters With High Risk` val totalMinimumDistinctEncountersWithHighRisk = exposureHistory .sumBy { it.minimumDistinctEncountersWithHighRisk } - Timber.d("totalMinimumDistinctEncountersWithHighRisk: $totalMinimumDistinctEncountersWithHighRisk") + Timber.d("totalMinimumDistinctEncountersWithHighRisk: %d", totalMinimumDistinctEncountersWithHighRisk) // 11. Determine `Number of Days With Low Risk` val numberOfDaysWithLowRisk = exposureHistory.numberOfDaysForRisk(ProtoRiskLevel.LOW) - Timber.d("numberOfDaysWithLowRisk: $numberOfDaysWithLowRisk") + Timber.d("numberOfDaysWithLowRisk: %d", numberOfDaysWithLowRisk) // 12. Determine `Number of Days With High Risk` val numberOfDaysWithHighRisk = exposureHistory.numberOfDaysForRisk(ProtoRiskLevel.HIGH) - Timber.d("numberOfDaysWithHighRisk: $numberOfDaysWithHighRisk") + Timber.d("numberOfDaysWithHighRisk: %d", numberOfDaysWithHighRisk) return AggregatedRiskResult( totalRiskLevel = totalRiskLevel, @@ -243,7 +247,7 @@ class DefaultRiskLevels @Inject constructor() : RiskLevels { appConfig: ExposureWindowRiskCalculationConfig, dateMillisSinceEpoch: Long, exposureWindowsAndResult: Map<ExposureWindow, RiskResult> - ): AggregatedRiskPerDateResult { + ): AggregatedRiskPerDateResult? { // 1. Group `Exposure Windows by Date` val exposureWindowsAndResultForDate = exposureWindowsAndResult .filter { it.key.dateMillisSinceEpoch == dateMillisSinceEpoch } @@ -252,31 +256,40 @@ class DefaultRiskLevels @Inject constructor() : RiskLevels { val normalizedTime = exposureWindowsAndResultForDate.values .sumOf { it.normalizedTime } - Timber.d("Aggregating result for date $dateMillisSinceEpoch - ${Instant.ofEpochMilli(dateMillisSinceEpoch)}") + Timber.d( + "Aggregating result for date %d - %s", + dateMillisSinceEpoch, + Instant.ofEpochMilli(dateMillisSinceEpoch) + ) // 3. Determine `Risk Level per Date` - val riskLevel = try { - appConfig.normalizedTimePerDayToRiskLevelMappingList - .filter { it.normalizedTimeRange.inRange(normalizedTime) } - .map { it.riskLevel } - .first() - } catch (e: Exception) { - throw NormalizedTimePerDayToRiskLevelMappingMissingException() + val riskLevel = appConfig.normalizedTimePerDayToRiskLevelMappingList + .filter { it.normalizedTimeRange.inRange(normalizedTime) } + .map { it.riskLevel } + .firstOrNull() + + if (riskLevel == null) { + Timber.d( + "No Risk Level is associated with date %d - %s", + dateMillisSinceEpoch, + Instant.ofEpochMilli(dateMillisSinceEpoch) + ) + return null } - Timber.d("riskLevel: ${riskLevel.name} (${riskLevel.ordinal})") + Timber.d("riskLevel: %s (%d)", riskLevel.name, riskLevel.ordinal) // 4. Determine `Minimum Distinct Encounters With Low Risk per Date` val minimumDistinctEncountersWithLowRisk = exposureWindowsAndResultForDate.minimumDistinctEncountersForRisk(ProtoRiskLevel.LOW) - Timber.d("minimumDistinctEncountersWithLowRisk: $minimumDistinctEncountersWithLowRisk") + Timber.d("minimumDistinctEncountersWithLowRisk: %d", minimumDistinctEncountersWithLowRisk) // 5. Determine `Minimum Distinct Encounters With High Risk per Date` val minimumDistinctEncountersWithHighRisk = exposureWindowsAndResultForDate.minimumDistinctEncountersForRisk(ProtoRiskLevel.HIGH) - Timber.d("minimumDistinctEncountersWithHighRisk: $minimumDistinctEncountersWithHighRisk") + Timber.d("minimumDistinctEncountersWithHighRisk: %d", minimumDistinctEncountersWithHighRisk) return AggregatedRiskPerDateResult( dateMillisSinceEpoch = dateMillisSinceEpoch, @@ -293,17 +306,6 @@ class DefaultRiskLevels @Inject constructor() : RiskLevels { .size companion object { - - open class RiskLevelMappingMissingException(msg: String) : Exception(msg) - - class NormalizedTimePerExposureWindowToRiskLevelMappingMissingException : RiskLevelMappingMissingException( - "Failed to map the normalized Time per Exposure Window to a Risk Level" - ) - - class NormalizedTimePerDayToRiskLevelMappingMissingException : RiskLevelMappingMissingException( - "Failed to map the normalized Time per Day to a Risk Level" - ) - class UnknownReportTypeException : Exception( "The Report Type returned by the ENF is not known" ) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSanityCheck.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSanityCheck.kt index 3c94d8afd20b5c09a3fe2d814ab60c77d98242af..67679862b65b1b2560d1b91ba942016cbc58e787 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSanityCheck.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSanityCheck.kt @@ -37,7 +37,7 @@ class DefaultAppConfigSanityCheck : BaseTest() { fun `current default matches checksum`() { val config = context.assets.open(configName).readBytes() val sha256 = context.assets.open(checkSumName).readBytes().toString(Charsets.UTF_8) - sha256 shouldBe "12d0b93c0c02c6870ef75c173a53a8ffb9cab6828fbf22e751053329c425eef2" + sha256 shouldBe "3d108b3fee7d1b4c227087c82bb804048de8d0542c3f2b26cf507a918201124d" config.toSHA256() shouldBe sha256 } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSourceTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSourceTest.kt index 313f4cbca19208d229f306c3160cb26c4d3c00df..cf21c6b07f6b0cb657363536e346f57084b7ec32 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSourceTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSourceTest.kt @@ -20,6 +20,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseIOTest import java.io.File +import java.io.FileNotFoundException class DefaultAppConfigSourceTest : BaseIOTest() { @MockK private lateinit var context: Context @@ -81,6 +82,7 @@ class DefaultAppConfigSourceTest : BaseIOTest() { @Test fun `exceptions when getting the default config are rethrown`() = runBlockingTest { + every { assetManager.open("default_app_config_android.bin") } throws FileNotFoundException("default_app_config_android.bin does not exist") val instance = createInstance() shouldThrowAny { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigApiTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigApiTest.kt index 7534816f002e9bb7318568e32601aaf1393fd0d4..5f9f1e884a6d690df3212cf6d328a7d0a6d8e31a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigApiTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigApiTest.kt @@ -75,6 +75,6 @@ class AppConfigApiTest : BaseIOTest() { val request = webServer.takeRequest(5, TimeUnit.SECONDS)!! request.method shouldBe "GET" - request.path shouldBe "/version/v1/app_config_android" + request.path shouldBe "/version/v2/app_config_android" } } 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 0e88002d6c34e088b097b392a753296fd458b982..d02dac3c4f8a1936f323ed36c43e1c3e841b729d 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 @@ -14,6 +14,7 @@ import de.rki.coronawarnapp.nearby.windows.entities.configuration.DefaultRiskCal import de.rki.coronawarnapp.nearby.windows.entities.configuration.JsonMinutesAtAttenuationFilter import de.rki.coronawarnapp.nearby.windows.entities.configuration.JsonMinutesAtAttenuationWeight import de.rki.coronawarnapp.nearby.windows.entities.configuration.JsonNormalizedTimeToRiskLevelMapping +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 @@ -261,8 +262,14 @@ class ExposureWindowsCalculationTest : BaseTest() { result.append(logRange(filter.dropIfTrlInRange, "Drop If Trl In Range")) } - result.append("\n").append("◦ Transmission Risk Level Multiplier: ${config.transmissionRiskLevelMultiplier}") - result.append("\n").append("-------------------------------------------- ⚙ -").append("\n") + result.append("\n").appendLine("◦ Transmission Risk Value Mapping (${config.transmissionRiskValueMapping.size})") + for (mapping in config.transmissionRiskValueMapping) { + result.append("\t").appendLine("⇥ Mapping") + result.append("\t\t").append("↳ transmissionRiskLevel: ").appendLine(mapping.transmissionRiskLevel) + result.append("\t\t").append("↳ transmissionRiskValue: ").appendLine(mapping.transmissionRiskValue) + } + + result.appendLine("-------------------------------------------- ⚙ -") debugLog(result.toString(), LogLevel.NONE) } @@ -364,8 +371,6 @@ class ExposureWindowsCalculationTest : BaseTest() { } every { testConfig.normalizedTimePerExposureWindowToRiskLevelMapping } returns normalizedTimePerExposureWindowToRiskLevelMapping - every { testConfig.transmissionRiskLevelMultiplier } returns json.transmissionRiskLevelMultiplier - val trlEncoding: RiskCalculationParametersOuterClass.TransmissionRiskLevelEncoding = mockk() every { trlEncoding.infectiousnessOffsetHigh } returns json.trlEncoding.infectiousnessOffsetHigh every { trlEncoding.infectiousnessOffsetStandard } returns json.trlEncoding.infectiousnessOffsetStandard @@ -385,6 +390,18 @@ class ExposureWindowsCalculationTest : BaseTest() { trlFilters.add(filter) } every { testConfig.transmissionRiskLevelFilters } returns trlFilters + + val transmissionRiskValueMapping = + mutableListOf<RiskCalculationParametersOuterClass.TransmissionRiskValueMapping>() + for (jsonMapping: JsonTransmissionRiskValueMapping in json.transmissionRiskValueMapping) { + val mapping: RiskCalculationParametersOuterClass.TransmissionRiskValueMapping = mockk() + mapping.run { + every { transmissionRiskLevel } returns jsonMapping.transmissionRiskLevel + every { transmissionRiskValue } returns jsonMapping.transmissionRiskValue + } + transmissionRiskValueMapping += mapping + } + every { testConfig.transmissionRiskValueMapping } returns transmissionRiskValueMapping } private fun jsonToExposureWindow(json: JsonWindow): ExposureWindow { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/DefaultRiskCalculationConfiguration.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/DefaultRiskCalculationConfiguration.kt index 7445d86e786d7250f7cf57dc4c5498eba97fabeb..1273b327df115d6ba365a5da1cb6e8340353d40d 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/DefaultRiskCalculationConfiguration.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/DefaultRiskCalculationConfiguration.kt @@ -11,10 +11,10 @@ data class DefaultRiskCalculationConfiguration( val normalizedTimePerDayToRiskLevelMapping: List<JsonNormalizedTimeToRiskLevelMapping>, @SerializedName("normalizedTimePerEWToRiskLevelMapping") val normalizedTimePerEWToRiskLevelMapping: List<JsonNormalizedTimeToRiskLevelMapping>, - @SerializedName("transmissionRiskLevelMultiplier") - val transmissionRiskLevelMultiplier: Double, @SerializedName("trlEncoding") val trlEncoding: JsonTrlEncoding, @SerializedName("trlFilters") - val trlFilters: List<JsonTrlFilter> + val trlFilters: List<JsonTrlFilter>, + @SerializedName("transmissionRiskValueMapping") + val transmissionRiskValueMapping: List<JsonTransmissionRiskValueMapping> ) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTransmissionRiskValueMapping.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTransmissionRiskValueMapping.kt new file mode 100644 index 0000000000000000000000000000000000000000..64bce14b999d03f9d6b64b8e8ea73530321e6401 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTransmissionRiskValueMapping.kt @@ -0,0 +1,10 @@ +package de.rki.coronawarnapp.nearby.windows.entities.configuration + +import com.google.gson.annotations.SerializedName + +data class JsonTransmissionRiskValueMapping( + @SerializedName("transmissionRiskLevel") + val transmissionRiskLevel: Int, + @SerializedName("transmissionRiskValue") + val transmissionRiskValue: Double +) diff --git a/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json b/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json index 2b11428431878d27278183ac72545a1c0ebdb851..0e47872a66c3792c27df150cf1919854e200a164 100644 --- a/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json +++ b/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json @@ -1,28 +1,8 @@ { "__comment__": "JSON has been generated from YAML, see README", "defaultRiskCalculationConfiguration": { - "minutesAtAttenuationFilters": [ - { - "attenuationRange": { - "min": 0, - "max": 73, - "maxExclusive": true - }, - "dropIfMinutesInRange": { - "min": 0, - "max": 10, - "maxExclusive": true - } - } - ], - "trlFilters": [ - { - "dropIfTrlInRange": { - "min": 1, - "max": 2 - } - } - ], + "minutesAtAttenuationFilters": [], + "trlFilters": [], "minutesAtAttenuationWeights": [ { "attenuationRange": { @@ -39,12 +19,20 @@ "maxExclusive": true }, "weight": 0.5 + }, + { + "attenuationRange": { + "min": 63, + "max": 73, + "maxExclusive": true + }, + "weight": 0.3 } ], "normalizedTimePerEWToRiskLevelMapping": [ { "normalizedTimeRange": { - "min": 0, + "min": 5, "max": 15, "maxExclusive": true }, @@ -61,7 +49,7 @@ "normalizedTimePerDayToRiskLevelMapping": [ { "normalizedTimeRange": { - "min": 0, + "min": 5, "max": 15, "maxExclusive": true }, @@ -83,11 +71,44 @@ "reportTypeOffsetConfirmedClinicalDiagnosis": 4, "reportTypeOffsetConfirmedTest": 6 }, - "transmissionRiskLevelMultiplier": 0.2 + "transmissionRiskValueMapping": [ + { + "transmissionRiskLevel": 1, + "transmissionRiskValue": 0 + }, + { + "transmissionRiskLevel": 2, + "transmissionRiskValue": 0 + }, + { + "transmissionRiskLevel": 3, + "transmissionRiskValue": 0.6 + }, + { + "transmissionRiskLevel": 4, + "transmissionRiskValue": 0.8 + }, + { + "transmissionRiskLevel": 5, + "transmissionRiskValue": 1 + }, + { + "transmissionRiskLevel": 6, + "transmissionRiskValue": 1.2 + }, + { + "transmissionRiskLevel": 7, + "transmissionRiskValue": 1.4 + }, + { + "transmissionRiskLevel": 8, + "transmissionRiskValue": 1.6 + } + ] }, "testCases": [ { - "description": "drop Exposure Windows that do not match minutesAtAttenuationFilters (< 10 minutes)", + "description": "keep Exposure Windows (< 10 minutes)", "exposureWindows": [ { "ageInDays": 1, @@ -109,15 +130,15 @@ } ], "expTotalRiskLevel": 1, - "expTotalMinimumDistinctEncountersWithLowRisk": 0, - "expAgeOfMostRecentDateWithLowRisk": null, + "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, - "expNumberOfDaysWithLowRisk": 0, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithLowRisk": 1, + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { - "description": "keep Exposure Windows that match minutesAtAttenuationFilters (>= 10 minutes)", + "description": "keep Exposure Windows (>= 10 minutes)", "exposureWindows": [ { "ageInDays": 1, @@ -139,15 +160,15 @@ } ], "expTotalRiskLevel": 1, - "expTotalMinimumDistinctEncountersWithLowRisk": 1, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expNumberOfDaysWithLowRisk": 1, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { - "description": "drop Exposure Windows that do not match minutesAtAttenuationFilters (>= 73 dB)", + "description": "keep Exposure Windows (>= 73 dB)", "exposureWindows": [ { "ageInDays": 1, @@ -169,15 +190,15 @@ } ], "expTotalRiskLevel": 1, - "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expNumberOfDaysWithLowRisk": 0, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { - "description": "keep Exposure Windows that match minutesAtAttenuationFilters (< 73 dB)", + "description": "keep Exposure Windows (< 73 dB)", "exposureWindows": [ { "ageInDays": 1, @@ -199,15 +220,15 @@ } ], "expTotalRiskLevel": 1, - "expTotalMinimumDistinctEncountersWithLowRisk": 1, - "expAgeOfMostRecentDateWithLowRisk": 1, + "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, - "expNumberOfDaysWithLowRisk": 1, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithLowRisk": 0, + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { - "description": "drop Exposure Windows that do not match trlFilters (<= 2)", + "description": "keep Exposure Windows with TRL <= 2", "exposureWindows": [ { "ageInDays": 1, @@ -229,15 +250,15 @@ } ], "expTotalRiskLevel": 1, - "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expNumberOfDaysWithLowRisk": 0, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { - "description": "keep Exposure Windows that match trlFilters (> 2)", + "description": "keep Exposure Windows with TRL > 2", "exposureWindows": [ { "ageInDays": 1, @@ -259,12 +280,62 @@ } ], "expTotalRiskLevel": 1, + "expAgeOfMostRecentDateWithLowRisk": 1, + "expAgeOfMostRecentDateWithHighRisk": null, + "expNumberOfDaysWithLowRisk": 1, + "expNumberOfDaysWithHighRisk": 0, "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 + }, + { + "description": "identify Exposure Window as no risk based on normalizedTime (< 5)", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 2, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "minAttenuation": 30, + "typicalAttenuation": 25, + "secondsSinceLastScan": 299 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expAgeOfMostRecentDateWithLowRisk": null, + "expAgeOfMostRecentDateWithHighRisk": null, + "expNumberOfDaysWithLowRisk": 0, + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 + }, + { + "description": "identify Exposure Window as Low Risk based on normalizedTime (>= 5)", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 2, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "minAttenuation": 30, + "typicalAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 1, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expNumberOfDaysWithLowRisk": 1, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { "description": "identify Exposure Window as Low Risk based on normalizedTime (< 15)", @@ -294,12 +365,12 @@ } ], "expTotalRiskLevel": 1, - "expTotalMinimumDistinctEncountersWithLowRisk": 1, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expNumberOfDaysWithLowRisk": 1, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { "description": "identify Exposure Window as High Risk based on normalizedTime (>= 15)", @@ -329,12 +400,55 @@ } ], "expTotalRiskLevel": 2, - "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": 1, - "expTotalMinimumDistinctEncountersWithHighRisk": 1, "expNumberOfDaysWithLowRisk": 0, - "expNumberOfDaysWithHighRisk": 1 + "expNumberOfDaysWithHighRisk": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 1 + }, + { + "description": "ignore Exposure Windows with no Risk Level", + "exposureWindows": [ + { + "ageInDays": 2, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "minAttenuation": 30, + "typicalAttenuation": 25, + "secondsSinceLastScan": 299 + } + ] + }, + { + "ageInDays": 3, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "minAttenuation": 30, + "typicalAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "minAttenuation": 30, + "typicalAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expAgeOfMostRecentDateWithLowRisk": 3, + "expAgeOfMostRecentDateWithHighRisk": null, + "expNumberOfDaysWithLowRisk": 1, + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { "description": "identify the most recent date with Low Risk", @@ -395,12 +509,12 @@ } ], "expTotalRiskLevel": 1, - "expTotalMinimumDistinctEncountersWithLowRisk": 3, "expAgeOfMostRecentDateWithLowRisk": 2, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expNumberOfDaysWithLowRisk": 3, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 3, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { "description": "count Exposure Windows with same Date/TRL/CallibrationConfidence only once towards distinct encounters with Low Risk", @@ -443,12 +557,12 @@ } ], "expTotalRiskLevel": 1, - "expTotalMinimumDistinctEncountersWithLowRisk": 1, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expNumberOfDaysWithLowRisk": 1, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { "description": "count Exposure Windows with same Date/TRL but different CallibrationConfidence separately towards distinct encounters with Low Risk", @@ -491,12 +605,12 @@ } ], "expTotalRiskLevel": 1, - "expTotalMinimumDistinctEncountersWithLowRisk": 2, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expNumberOfDaysWithLowRisk": 1, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 2, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { "description": "count Exposure Windows with same Date/CallibrationConfidence but different TRL separately towards distinct encounters with Low Risk", @@ -539,12 +653,12 @@ } ], "expTotalRiskLevel": 1, - "expTotalMinimumDistinctEncountersWithLowRisk": 2, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expNumberOfDaysWithLowRisk": 1, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 2, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { "description": "count Exposure Windows with same TRL/CallibrationConfidence but different Date separately towards distinct encounters with Low Risk", @@ -587,12 +701,12 @@ } ], "expTotalRiskLevel": 1, - "expTotalMinimumDistinctEncountersWithLowRisk": 2, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expNumberOfDaysWithLowRisk": 2, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 2, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { "description": "determine High Risk in total if there are sufficient Exposure Windows with a Low Risk", @@ -653,12 +767,12 @@ } ], "expTotalRiskLevel": 2, - "expTotalMinimumDistinctEncountersWithLowRisk": 1, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": 1, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expNumberOfDaysWithLowRisk": 0, - "expNumberOfDaysWithHighRisk": 1 + "expNumberOfDaysWithHighRisk": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { "description": "identify the most recent date with High Risk", @@ -719,12 +833,12 @@ } ], "expTotalRiskLevel": 2, - "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": 2, - "expTotalMinimumDistinctEncountersWithHighRisk": 3, "expNumberOfDaysWithLowRisk": 0, - "expNumberOfDaysWithHighRisk": 3 + "expNumberOfDaysWithHighRisk": 3, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 3 }, { "description": "count Exposure Windows with same Date/TRL/CallibrationConfidence only once towards distinct encounters with High Risk", @@ -767,12 +881,12 @@ } ], "expTotalRiskLevel": 2, - "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": 1, - "expTotalMinimumDistinctEncountersWithHighRisk": 1, "expNumberOfDaysWithLowRisk": 0, - "expNumberOfDaysWithHighRisk": 1 + "expNumberOfDaysWithHighRisk": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 1 }, { "description": "count Exposure Windows with same Date/TRL but different CallibrationConfidence separately towards distinct encounters with High Risk", @@ -815,12 +929,12 @@ } ], "expTotalRiskLevel": 2, - "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": 1, - "expTotalMinimumDistinctEncountersWithHighRisk": 2, "expNumberOfDaysWithLowRisk": 0, - "expNumberOfDaysWithHighRisk": 1 + "expNumberOfDaysWithHighRisk": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 2 }, { "description": "count Exposure Windows with same Date/CallibrationConfidence but different TRL separately towards distinct encounters with High Risk", @@ -863,12 +977,12 @@ } ], "expTotalRiskLevel": 2, - "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": 1, - "expTotalMinimumDistinctEncountersWithHighRisk": 2, "expNumberOfDaysWithLowRisk": 0, - "expNumberOfDaysWithHighRisk": 1 + "expNumberOfDaysWithHighRisk": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 2 }, { "description": "count Exposure Windows with same TRL/CallibrationConfidence but different Date separately towards distinct encounters with High Risk", @@ -911,12 +1025,12 @@ } ], "expTotalRiskLevel": 2, - "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": 1, - "expTotalMinimumDistinctEncountersWithHighRisk": 2, "expNumberOfDaysWithLowRisk": 0, - "expNumberOfDaysWithHighRisk": 2 + "expNumberOfDaysWithHighRisk": 2, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 2 }, { "description": "determine High Risk in total if there is at least one Exposure Window with High Risk", @@ -959,23 +1073,23 @@ } ], "expTotalRiskLevel": 2, - "expTotalMinimumDistinctEncountersWithLowRisk": 1, "expAgeOfMostRecentDateWithLowRisk": 2, "expAgeOfMostRecentDateWithHighRisk": 1, - "expTotalMinimumDistinctEncountersWithHighRisk": 1, "expNumberOfDaysWithLowRisk": 1, - "expNumberOfDaysWithHighRisk": 1 + "expNumberOfDaysWithHighRisk": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 1 }, { "description": "handle empty set of Exposure Windows", "exposureWindows": [], "expTotalRiskLevel": 1, - "expTotalMinimumDistinctEncountersWithLowRisk": 0, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": null, "expNumberOfDaysWithLowRisk": 0, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { "description": "handle empty set of Scan Instances (should never happen)", @@ -989,12 +1103,12 @@ } ], "expTotalRiskLevel": 1, - "expTotalMinimumDistinctEncountersWithLowRisk": 0, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": null, "expNumberOfDaysWithLowRisk": 0, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { "description": "handle a typicalAttenuation: of zero (should never happen)", @@ -1019,12 +1133,12 @@ } ], "expTotalRiskLevel": 1, - "expTotalMinimumDistinctEncountersWithLowRisk": 1, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, "expNumberOfDaysWithLowRisk": 1, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { "description": "handle secondsSinceLastScan of zero (should never happen)", @@ -1054,12 +1168,12 @@ } ], "expTotalRiskLevel": 1, - "expTotalMinimumDistinctEncountersWithLowRisk": 1, - "expTotalMinimumDistinctEncountersWithHighRisk": 0, - "expAgeOfMostRecentDateWithLowRisk": 1, + "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": null, - "expNumberOfDaysWithLowRisk": 1, - "expNumberOfDaysWithHighRisk": 0 + "expNumberOfDaysWithLowRisk": 0, + "expNumberOfDaysWithHighRisk": 0, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 }, { "description": "ignores negative secondsSinceLastScan (can happen when time-travelling, not officially supported)", @@ -1094,12 +1208,12 @@ } ], "expTotalRiskLevel": 2, - "expTotalMinimumDistinctEncountersWithLowRisk": 0, - "expTotalMinimumDistinctEncountersWithHighRisk": 1, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": 1, "expNumberOfDaysWithLowRisk": 0, - "expNumberOfDaysWithHighRisk": 1 + "expNumberOfDaysWithHighRisk": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 1 } ] } \ No newline at end of file