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 ada0d0cc078c72aa8a5c5a09ce8f2e1d640ee7c1..146f92532b706a663fde17ff13420001aa0ab2df 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
@@ -308,7 +308,7 @@ class DefaultRiskLevels @Inject constructor(
                     .filter { it.attenuationRange.inRange(scanInstance.typicalAttenuationDb) }
                     .map { it.weight }
                     .firstOrNull() ?: .0
-            return seconds + scanInstance.secondsSinceLastScan * weight
+            seconds + scanInstance.secondsSinceLastScan * weight
         }
 
     private fun determineRiskLevel(
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
new file mode 100644
index 0000000000000000000000000000000000000000..3dab010d2aa1d892ad60015075aa6aec837bac91
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt
@@ -0,0 +1,402 @@
+package de.rki.coronawarnapp.nearby.windows
+
+import com.google.android.gms.nearby.exposurenotification.ExposureWindow
+import com.google.android.gms.nearby.exposurenotification.ScanInstance
+import com.google.gson.Gson
+import de.rki.coronawarnapp.appconfig.AppConfigProvider
+import de.rki.coronawarnapp.appconfig.ConfigData
+import de.rki.coronawarnapp.appconfig.DefaultConfigData
+import de.rki.coronawarnapp.nearby.windows.entities.ExposureWindowsJsonInput
+import de.rki.coronawarnapp.nearby.windows.entities.cases.JsonScanInstance
+import de.rki.coronawarnapp.nearby.windows.entities.cases.JsonWindow
+import de.rki.coronawarnapp.nearby.windows.entities.cases.TestCase
+import de.rki.coronawarnapp.nearby.windows.entities.configuration.DefaultRiskCalculationConfiguration
+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.JsonTrlFilter
+import de.rki.coronawarnapp.risk.DefaultRiskLevels
+import de.rki.coronawarnapp.risk.result.AggregatedRiskResult
+import de.rki.coronawarnapp.risk.result.RiskResult
+import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass
+import de.rki.coronawarnapp.util.TimeStamper
+import de.rki.coronawarnapp.util.serialization.fromJson
+import io.kotest.matchers.ints.shouldBeGreaterThan
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.shouldNotBe
+import io.mockk.MockKAnnotations
+import io.mockk.clearAllMocks
+import io.mockk.coEvery
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import io.mockk.mockk
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.runBlocking
+import org.joda.time.DateTimeConstants
+import org.joda.time.Duration
+import org.joda.time.Instant
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+import timber.log.Timber
+import java.io.FileReader
+import java.nio.file.Paths
+
+class ExposureWindowsCalculationTest : BaseTest() {
+
+    @MockK lateinit var appConfigProvider: AppConfigProvider
+    @MockK lateinit var configData: ConfigData
+    @MockK lateinit var timeStamper: TimeStamper
+
+    private lateinit var riskLevels: DefaultRiskLevels
+    private lateinit var testConfig: ConfigData
+
+    // Json file (located in /test/resources/exposure-windows-risk-calculation.json)
+    private val fileName = "exposure-windows-risk-calculation.json"
+
+    // Debug logs
+    private enum class LogLevel {
+        NONE,
+        ONLY_COMPARISON,
+        EXTENDED,
+        ALL
+    }
+    private val logLevel = LogLevel.ONLY_COMPARISON
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+        every { timeStamper.nowUTC } returns Instant.now()
+    }
+
+    @AfterEach
+    fun teardown() {
+        clearAllMocks()
+    }
+
+    private fun debugLog(s: String, toShow: LogLevel = LogLevel.ALL) {
+        if (logLevel < toShow)
+            return
+        Timber.v(s)
+    }
+
+    @Test
+    fun `one test to rule them all`(): Unit = runBlocking {
+        // 1 - Load and parse json file
+        val jsonFile = Paths.get("src", "test", "resources", fileName).toFile()
+        jsonFile shouldNotBe null
+        val jsonString = FileReader(jsonFile).readText()
+        jsonString.length shouldBeGreaterThan 0
+        val json = Gson().fromJson<ExposureWindowsJsonInput>(jsonString)
+        json shouldNotBe null
+
+        // 2 - Check test cases
+        for (case: TestCase in json.testCases) {
+            checkTestCase(case)
+        }
+        debugLog("Test cases checked. Total count: ${json.testCases.size}")
+
+        // 3 - Mock calculation configuration and create default risk level with it
+        jsonToConfiguration(json.defaultRiskCalculationConfiguration)
+        coEvery { appConfigProvider.getAppConfig() } returns testConfig
+        every { appConfigProvider.currentConfig } returns flow { testConfig }
+        logConfiguration(testConfig)
+        riskLevels = DefaultRiskLevels(appConfigProvider)
+
+        // 4 - Mock and log exposure windows
+        val allExposureWindows = mutableListOf<ExposureWindow>()
+        for (case: TestCase in json.testCases) {
+            val exposureWindows: List<ExposureWindow> =
+                case.exposureWindows.map { window -> jsonToExposureWindow(window) }
+            allExposureWindows.addAll(exposureWindows)
+
+            // 5 - Calculate risk level for test case and aggregate results
+            val exposureWindowsAndResult = HashMap<ExposureWindow, RiskResult>()
+            for (exposureWindow: ExposureWindow in exposureWindows) {
+
+                logExposureWindow(exposureWindow, "âž¡âž¡ EXPOSURE WINDOW PASSED âž¡âž¡", LogLevel.EXTENDED)
+                val riskResult = riskLevels.calculateRisk(exposureWindow) ?: continue
+                exposureWindowsAndResult[exposureWindow] = riskResult
+            }
+            debugLog("Exposure windows and result: ${exposureWindowsAndResult.size}")
+
+            val aggregatedRiskResult = riskLevels.aggregateResults(exposureWindowsAndResult)
+
+            debugLog(
+                "\n" + comparisonDebugTable(aggregatedRiskResult, case),
+                LogLevel.ONLY_COMPARISON
+            )
+
+            // 6 - Check with expected result from test case
+            aggregatedRiskResult.totalRiskLevel.number shouldBe case.expTotalRiskLevel
+            aggregatedRiskResult.mostRecentDateWithHighRisk shouldBe getTestCaseDate(case.expAgeOfMostRecentDateWithHighRisk)
+            aggregatedRiskResult.mostRecentDateWithLowRisk shouldBe getTestCaseDate(case.expAgeOfMostRecentDateWithLowRisk)
+            aggregatedRiskResult.totalMinimumDistinctEncountersWithHighRisk shouldBe case.expTotalMinimumDistinctEncountersWithHighRisk
+            aggregatedRiskResult.totalMinimumDistinctEncountersWithLowRisk shouldBe case.expTotalMinimumDistinctEncountersWithLowRisk
+        }
+    }
+
+    private fun getTestCaseDate(expAge: Long?): Instant? {
+        if (expAge == null) return null
+        return timeStamper.nowUTC - expAge * DateTimeConstants.MILLIS_PER_DAY
+    }
+
+    private fun comparisonDebugTable(aggregated: AggregatedRiskResult, case: TestCase): String {
+        val result = StringBuilder()
+        result.append("\n").append(case.description)
+        result.append("\n").append("+----------------------+--------------------------+--------------------------+")
+        result.append("\n").append("| Property             | Actual                   | Expected                 |")
+        result.append("\n").append("+----------------------+--------------------------+--------------------------+")
+        result.append(
+            addPropertyCheckToComparisonDebugTable(
+                "Total Risk",
+                aggregated.totalRiskLevel.number,
+                case.expTotalRiskLevel
+            )
+        )
+        result.append(
+            addPropertyCheckToComparisonDebugTable(
+                "Date With High Risk",
+                aggregated.mostRecentDateWithHighRisk,
+                getTestCaseDate(case.expAgeOfMostRecentDateWithHighRisk)
+            )
+        )
+        result.append(
+            addPropertyCheckToComparisonDebugTable(
+                "Date With Low Risk",
+                aggregated.mostRecentDateWithLowRisk,
+                getTestCaseDate(case.expAgeOfMostRecentDateWithLowRisk)
+            )
+        )
+        result.append(
+            addPropertyCheckToComparisonDebugTable(
+                "Encounters High Risk",
+                aggregated.totalMinimumDistinctEncountersWithHighRisk,
+                case.expTotalMinimumDistinctEncountersWithHighRisk
+            )
+        )
+        result.append(
+            addPropertyCheckToComparisonDebugTable(
+                "Encounters Low Risk",
+                aggregated.totalMinimumDistinctEncountersWithLowRisk,
+                case.expTotalMinimumDistinctEncountersWithLowRisk
+            )
+        )
+        result.append("\n")
+        return result.toString()
+    }
+
+    private fun addPropertyCheckToComparisonDebugTable(propertyName: String, expected: Any?, actual: Any?): String {
+        val format = "| %-20s | %-24s | %-24s |"
+        val result = StringBuilder()
+        result.append("\n").append(String.format(format, propertyName, expected, actual))
+        result.append("\n").append("+----------------------+--------------------------+--------------------------+")
+        return result.toString()
+    }
+
+    private fun checkTestCase(case: TestCase) {
+        debugLog("Checking ${case.description}", LogLevel.ALL)
+        case.expTotalRiskLevel shouldNotBe null
+        case.expTotalMinimumDistinctEncountersWithLowRisk shouldNotBe null
+        case.expTotalMinimumDistinctEncountersWithHighRisk shouldNotBe null
+        case.exposureWindows.map { exposureWindow -> checkExposureWindow(exposureWindow) }
+    }
+
+    private fun checkExposureWindow(jsonWindow: JsonWindow) {
+        jsonWindow.ageInDays shouldNotBe null
+        jsonWindow.reportType shouldNotBe null
+        jsonWindow.infectiousness shouldNotBe null
+        jsonWindow.calibrationConfidence shouldNotBe null
+    }
+
+    private fun logConfiguration(config: ConfigData) {
+        val result = StringBuilder()
+        result.append("\n\n").append("----------------- \uD83D\uDEE0 CONFIGURATION \uD83D\uDEE0 -----------")
+
+        result.append("\n").append("â—¦ Minutes At Attenuation Filters (${config.minutesAtAttenuationFilters.size})")
+        for (filter: RiskCalculationParametersOuterClass.MinutesAtAttenuationFilter in config.minutesAtAttenuationFilters) {
+            result.append("\n\t").append("⇥ Filter")
+            result.append(logRange(filter.attenuationRange, "Attenuation Range"))
+            result.append(logRange(filter.dropIfMinutesInRange, "Drop If Minutes In Range"))
+        }
+
+        result.append("\n").append("â—¦ Minutes At Attenuation Weights (${config.minutesAtAttenuationWeights.size})")
+        for (weight: RiskCalculationParametersOuterClass.MinutesAtAttenuationWeight in config.minutesAtAttenuationWeights) {
+            result.append("\n\t").append("⇥ Weight")
+            result.append(logRange(weight.attenuationRange, "Attenuation Range"))
+            result.append("\n\t\t").append("↳ Weight: ${weight.weight}")
+        }
+
+        result.append("\n").append("â—¦ Normalized Time Per Day To Risk Level Mapping List (${config.normalizedTimePerDayToRiskLevelMappingList.size})")
+        for (mapping: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping in config.normalizedTimePerDayToRiskLevelMappingList) {
+            result.append("\n\t").append("⇥ Mapping")
+            result.append(logRange(mapping.normalizedTimeRange, "Normalized Time Range"))
+            result.append("\n\t\t").append("↳ Risk Level: ${mapping.riskLevel}")
+        }
+
+        result.append("\n").append("â—¦ Normalized Time Per Exposure Window To Risk Level Mapping (${config.normalizedTimePerExposureWindowToRiskLevelMapping.size})")
+        for (mapping: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping in config.normalizedTimePerExposureWindowToRiskLevelMapping) {
+            result.append("\n\t").append("⇥ Mapping")
+            result.append(logRange(mapping.normalizedTimeRange, "Normalized Time Range"))
+            result.append("\n\t\t").append("↳ Risk Level: ${mapping.riskLevel}")
+        }
+
+        result.append("\n").append("â—¦ Transmission Risk Level Encoding:")
+        result.append("\n\t").append("↳ Infectiousness Offset High: ${config.transmissionRiskLevelEncoding.infectiousnessOffsetHigh}")
+        result.append("\n\t").append("↳ Infectiousness Offset Standard: ${config.transmissionRiskLevelEncoding.infectiousnessOffsetStandard}")
+        result.append("\n\t").append("↳ Report Type Offset Confirmed Clinical Diagnosis: ${config.transmissionRiskLevelEncoding.reportTypeOffsetConfirmedClinicalDiagnosis}")
+        result.append("\n\t").append("↳ Report Type Offset Confirmed Test: ${config.transmissionRiskLevelEncoding.reportTypeOffsetConfirmedTest}")
+        result.append("\n\t").append("↳ Report Type Offset Recursive: ${config.transmissionRiskLevelEncoding.reportTypeOffsetRecursive}")
+        result.append("\n\t").append("↳ Report Type Offset Self Report: ${config.transmissionRiskLevelEncoding.reportTypeOffsetSelfReport}")
+
+        result.append("\n").append("â—¦ Transmission Risk Level Filters (${config.transmissionRiskLevelFilters.size})")
+        for (filter: RiskCalculationParametersOuterClass.TrlFilter in config.transmissionRiskLevelFilters) {
+            result.append("\n\t").append("⇥ Trl Filter")
+            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")
+        debugLog(result.toString(), LogLevel.NONE)
+    }
+
+    private fun logRange(range: RiskCalculationParametersOuterClass.Range, rangeName: String): String {
+        val builder = StringBuilder()
+        builder.append("\n\t\t").append("⇥ $rangeName")
+        builder.append("\n\t\t\t").append("↳ Min: ${range.min}")
+        builder.append("\n\t\t\t").append("↳ Max: ${range.max}")
+        builder.append("\n\t\t\t").append("↳ Min Exclusive: ${range.minExclusive}")
+        builder.append("\n\t\t\t").append("↳ Max Exclusive: ${range.maxExclusive}")
+        return builder.toString()
+    }
+
+    private fun logExposureWindow(exposureWindow: ExposureWindow, title: String, logLevel: LogLevel = LogLevel.ALL) {
+        val result = StringBuilder()
+        result.append("\n\n").append("------------ $title -----------")
+        result.append("\n").append("Mocked Exposure window: #${exposureWindow.hashCode()}")
+        result.append("\n").append("â—¦ Calibration Confidence: ${exposureWindow.calibrationConfidence}")
+        result.append("\n").append("â—¦ Date Millis Since Epoch: ${exposureWindow.dateMillisSinceEpoch}")
+        result.append("\n").append("â—¦ Infectiousness: ${exposureWindow.infectiousness}")
+        result.append("\n").append("â—¦ Report type: ${exposureWindow.reportType}")
+
+        result.append("\n").append("‣ Scan Instances (${exposureWindow.scanInstances.size}):")
+        for (scan: ScanInstance in exposureWindow.scanInstances) {
+            result.append("\n\t").append("⇥ Mocked Scan Instance: #${scan.hashCode()}")
+            result.append("\n\t\t").append("↳ Min Attenuation: ${scan.minAttenuationDb}")
+            result.append("\n\t\t").append("↳ Seconds Since Last Scan: ${scan.secondsSinceLastScan}")
+            result.append("\n\t\t").append("↳ Typical Attenuation: ${scan.typicalAttenuationDb}")
+        }
+        result.append("\n").append("-------------------------------------------- ✂ ----").append("\n")
+        debugLog(result.toString(), logLevel)
+    }
+
+    private fun jsonToConfiguration(json: DefaultRiskCalculationConfiguration) {
+
+        testConfig = DefaultConfigData(
+            serverTime = Instant.now(),
+            localOffset = Duration.ZERO,
+            mappedConfig = configData,
+            isFallback = false
+        )
+
+        val attenuationFilters = mutableListOf<RiskCalculationParametersOuterClass.MinutesAtAttenuationFilter>()
+        for (jsonFilter: JsonMinutesAtAttenuationFilter in json.minutesAtAttenuationFilters) {
+            val filter: RiskCalculationParametersOuterClass.MinutesAtAttenuationFilter = mockk()
+            every { filter.attenuationRange.min } returns jsonFilter.attenuationRange.min
+            every { filter.attenuationRange.max } returns jsonFilter.attenuationRange.max
+            every { filter.attenuationRange.minExclusive } returns jsonFilter.attenuationRange.minExclusive
+            every { filter.attenuationRange.maxExclusive } returns jsonFilter.attenuationRange.maxExclusive
+            every { filter.dropIfMinutesInRange.min } returns jsonFilter.dropIfMinutesInRange.min
+            every { filter.dropIfMinutesInRange.max } returns jsonFilter.dropIfMinutesInRange.max
+            every { filter.dropIfMinutesInRange.minExclusive } returns jsonFilter.dropIfMinutesInRange.minExclusive
+            every { filter.dropIfMinutesInRange.maxExclusive } returns jsonFilter.dropIfMinutesInRange.maxExclusive
+            attenuationFilters.add(filter)
+        }
+        every { testConfig.minutesAtAttenuationFilters } returns attenuationFilters
+
+        val attenuationWeights = mutableListOf<RiskCalculationParametersOuterClass.MinutesAtAttenuationWeight>()
+        for (jsonWeight: JsonMinutesAtAttenuationWeight in json.minutesAtAttenuationWeights) {
+            val weight: RiskCalculationParametersOuterClass.MinutesAtAttenuationWeight = mockk()
+            every { weight.attenuationRange.min } returns jsonWeight.attenuationRange.min
+            every { weight.attenuationRange.max } returns jsonWeight.attenuationRange.max
+            every { weight.attenuationRange.minExclusive } returns jsonWeight.attenuationRange.minExclusive
+            every { weight.attenuationRange.maxExclusive } returns jsonWeight.attenuationRange.maxExclusive
+            every { weight.weight } returns jsonWeight.weight
+            attenuationWeights.add(weight)
+        }
+        every { testConfig.minutesAtAttenuationWeights } returns attenuationWeights
+
+        val normalizedTimePerDayToRiskLevelMapping = mutableListOf< RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping>()
+        for (jsonMapping: JsonNormalizedTimeToRiskLevelMapping in json.normalizedTimePerDayToRiskLevelMapping) {
+            val mapping: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping = mockk()
+            every { mapping.riskLevel } returns RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.forNumber(jsonMapping.riskLevel)
+            every { mapping.normalizedTimeRange.min } returns jsonMapping.normalizedTimeRange.min
+            every { mapping.normalizedTimeRange.max } returns jsonMapping.normalizedTimeRange.max
+            every { mapping.normalizedTimeRange.minExclusive } returns jsonMapping.normalizedTimeRange.minExclusive
+            every { mapping.normalizedTimeRange.maxExclusive } returns jsonMapping.normalizedTimeRange.maxExclusive
+            normalizedTimePerDayToRiskLevelMapping.add(mapping)
+        }
+        every { testConfig.normalizedTimePerDayToRiskLevelMappingList } returns normalizedTimePerDayToRiskLevelMapping
+
+        val normalizedTimePerExposureWindowToRiskLevelMapping = mutableListOf< RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping>()
+        for (jsonMapping: JsonNormalizedTimeToRiskLevelMapping in json.normalizedTimePerEWToRiskLevelMapping) {
+            val mapping: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping = mockk()
+            every { mapping.riskLevel } returns RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.forNumber(jsonMapping.riskLevel)
+            every { mapping.normalizedTimeRange.min } returns jsonMapping.normalizedTimeRange.min
+            every { mapping.normalizedTimeRange.max } returns jsonMapping.normalizedTimeRange.max
+            every { mapping.normalizedTimeRange.minExclusive } returns jsonMapping.normalizedTimeRange.minExclusive
+            every { mapping.normalizedTimeRange.maxExclusive } returns jsonMapping.normalizedTimeRange.maxExclusive
+            normalizedTimePerExposureWindowToRiskLevelMapping.add(mapping)
+        }
+        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
+        every { trlEncoding.reportTypeOffsetConfirmedClinicalDiagnosis } returns json.trlEncoding.reportTypeOffsetConfirmedClinicalDiagnosis
+        every { trlEncoding.reportTypeOffsetConfirmedTest } returns json.trlEncoding.reportTypeOffsetConfirmedTest
+        every { trlEncoding.reportTypeOffsetRecursive } returns json.trlEncoding.reportTypeOffsetRecursive
+        every { trlEncoding.reportTypeOffsetSelfReport } returns json.trlEncoding.reportTypeOffsetSelfReport
+        every { testConfig.transmissionRiskLevelEncoding } returns trlEncoding
+
+        val trlFilters = mutableListOf<RiskCalculationParametersOuterClass.TrlFilter>()
+        for (jsonFilter: JsonTrlFilter in json.trlFilters) {
+            val filter: RiskCalculationParametersOuterClass.TrlFilter = mockk()
+            every { filter.dropIfTrlInRange.min } returns jsonFilter.dropIfTrlInRange.min
+            every { filter.dropIfTrlInRange.max } returns jsonFilter.dropIfTrlInRange.max
+            every { filter.dropIfTrlInRange.minExclusive } returns jsonFilter.dropIfTrlInRange.minExclusive
+            every { filter.dropIfTrlInRange.maxExclusive } returns jsonFilter.dropIfTrlInRange.maxExclusive
+            trlFilters.add(filter)
+        }
+        every { testConfig.transmissionRiskLevelFilters } returns trlFilters
+    }
+
+    private fun jsonToExposureWindow(json: JsonWindow): ExposureWindow {
+        val exposureWindow: ExposureWindow = mockk()
+
+        every { exposureWindow.calibrationConfidence } returns json.calibrationConfidence
+        every { exposureWindow.dateMillisSinceEpoch } returns timeStamper.nowUTC.millis - (DateTimeConstants.MILLIS_PER_DAY * json.ageInDays).toLong()
+        every { exposureWindow.infectiousness } returns json.infectiousness
+        every { exposureWindow.reportType } returns json.reportType
+        every { exposureWindow.scanInstances } returns json.scanInstances.map { scanInstance ->
+            jsonToScanInstance(
+                scanInstance
+            )
+        }
+
+        logExposureWindow(exposureWindow, "⊞ EXPOSURE WINDOW MOCK ⊞")
+
+        return exposureWindow
+    }
+
+    private fun jsonToScanInstance(json: JsonScanInstance): ScanInstance {
+        val scanInstance: ScanInstance = mockk()
+        every { scanInstance.minAttenuationDb } returns json.minAttenuation
+        every { scanInstance.secondsSinceLastScan } returns json.secondsSinceLastScan
+        every { scanInstance.typicalAttenuationDb } returns json.typicalAttenuation
+        return scanInstance
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/ExposureWindowsJsonInput.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/ExposureWindowsJsonInput.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3c0a77a3a27bdc197a2ca75cd885303a3ec587eb
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/ExposureWindowsJsonInput.kt
@@ -0,0 +1,14 @@
+package de.rki.coronawarnapp.nearby.windows.entities
+
+import com.google.gson.annotations.SerializedName
+import de.rki.coronawarnapp.nearby.windows.entities.cases.TestCase
+import de.rki.coronawarnapp.nearby.windows.entities.configuration.DefaultRiskCalculationConfiguration
+
+data class ExposureWindowsJsonInput(
+    @SerializedName("__comment__")
+    val comment: String,
+    @SerializedName("defaultRiskCalculationConfiguration")
+    val defaultRiskCalculationConfiguration: DefaultRiskCalculationConfiguration,
+    @SerializedName("testCases")
+    val testCases: List<TestCase>
+)
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/JsonScanInstance.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/JsonScanInstance.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a9ea714b7e7f440c4e71c5e27f8f8c912a3c575c
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/JsonScanInstance.kt
@@ -0,0 +1,12 @@
+package de.rki.coronawarnapp.nearby.windows.entities.cases
+
+import com.google.gson.annotations.SerializedName
+
+data class JsonScanInstance(
+    @SerializedName("minAttenuation")
+    val minAttenuation: Int,
+    @SerializedName("secondsSinceLastScan")
+    val secondsSinceLastScan: Int,
+    @SerializedName("typicalAttenuation")
+    val typicalAttenuation: Int
+)
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/JsonWindow.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/JsonWindow.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7394b329509f98a10e6d7c368dc3033b62c442ef
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/JsonWindow.kt
@@ -0,0 +1,16 @@
+package de.rki.coronawarnapp.nearby.windows.entities.cases
+
+import com.google.gson.annotations.SerializedName
+
+data class JsonWindow(
+    @SerializedName("ageInDays")
+    val ageInDays: Int,
+    @SerializedName("calibrationConfidence")
+    val calibrationConfidence: Int,
+    @SerializedName("infectiousness")
+    val infectiousness: Int,
+    @SerializedName("reportType")
+    val reportType: Int,
+    @SerializedName("scanInstances")
+    val scanInstances: List<JsonScanInstance>
+)
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/TestCase.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/TestCase.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fa8f45277f640f11580b310bfea5cc2a12fd2eab
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/TestCase.kt
@@ -0,0 +1,20 @@
+package de.rki.coronawarnapp.nearby.windows.entities.cases
+
+import com.google.gson.annotations.SerializedName
+
+data class TestCase(
+    @SerializedName("description")
+    val description: String,
+    @SerializedName("expAgeOfMostRecentDateWithHighRisk")
+    val expAgeOfMostRecentDateWithHighRisk: Long?,
+    @SerializedName("expAgeOfMostRecentDateWithLowRisk")
+    val expAgeOfMostRecentDateWithLowRisk: Long?,
+    @SerializedName("expTotalMinimumDistinctEncountersWithHighRisk")
+    val expTotalMinimumDistinctEncountersWithHighRisk: Int,
+    @SerializedName("expTotalMinimumDistinctEncountersWithLowRisk")
+    val expTotalMinimumDistinctEncountersWithLowRisk: Int,
+    @SerializedName("expTotalRiskLevel")
+    val expTotalRiskLevel: Int,
+    @SerializedName("exposureWindows")
+    val exposureWindows: List<JsonWindow>
+)
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
new file mode 100644
index 0000000000000000000000000000000000000000..7445d86e786d7250f7cf57dc4c5498eba97fabeb
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/DefaultRiskCalculationConfiguration.kt
@@ -0,0 +1,20 @@
+package de.rki.coronawarnapp.nearby.windows.entities.configuration
+
+import com.google.gson.annotations.SerializedName
+
+data class DefaultRiskCalculationConfiguration(
+    @SerializedName("minutesAtAttenuationFilters")
+    val minutesAtAttenuationFilters: List<JsonMinutesAtAttenuationFilter>,
+    @SerializedName("minutesAtAttenuationWeights")
+    val minutesAtAttenuationWeights: List<JsonMinutesAtAttenuationWeight>,
+    @SerializedName("normalizedTimePerDayToRiskLevelMapping")
+    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>
+)
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonMinutesAtAttenuationFilter.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonMinutesAtAttenuationFilter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a01efc1f6022a2a68a8a20e25aeefc6320591b2b
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonMinutesAtAttenuationFilter.kt
@@ -0,0 +1,10 @@
+package de.rki.coronawarnapp.nearby.windows.entities.configuration
+
+import com.google.gson.annotations.SerializedName
+
+data class JsonMinutesAtAttenuationFilter(
+    @SerializedName("attenuationRange")
+    val attenuationRange: Range,
+    @SerializedName("dropIfMinutesInRange")
+    val dropIfMinutesInRange: Range
+)
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonMinutesAtAttenuationWeight.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonMinutesAtAttenuationWeight.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3af598da7dc5c6d71bf0bbeeac85788f038a90ee
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonMinutesAtAttenuationWeight.kt
@@ -0,0 +1,10 @@
+package de.rki.coronawarnapp.nearby.windows.entities.configuration
+
+import com.google.gson.annotations.SerializedName
+
+data class JsonMinutesAtAttenuationWeight(
+    @SerializedName("attenuationRange")
+    val attenuationRange: Range,
+    @SerializedName("weight")
+    val weight: Double
+)
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonNormalizedTimeToRiskLevelMapping.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonNormalizedTimeToRiskLevelMapping.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4f0fc786998558caf43be1d5cee1e6026f7bb919
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonNormalizedTimeToRiskLevelMapping.kt
@@ -0,0 +1,10 @@
+package de.rki.coronawarnapp.nearby.windows.entities.configuration
+
+import com.google.gson.annotations.SerializedName
+
+data class JsonNormalizedTimeToRiskLevelMapping(
+    @SerializedName("normalizedTimeRange")
+    val normalizedTimeRange: Range,
+    @SerializedName("riskLevel")
+    val riskLevel: Int
+)
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTrlEncoding.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTrlEncoding.kt
new file mode 100644
index 0000000000000000000000000000000000000000..00a3e5af78270ef34fb384909f1f69ddab92f2ba
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTrlEncoding.kt
@@ -0,0 +1,18 @@
+package de.rki.coronawarnapp.nearby.windows.entities.configuration
+
+import com.google.gson.annotations.SerializedName
+
+data class JsonTrlEncoding(
+    @SerializedName("infectiousnessOffsetHigh")
+    val infectiousnessOffsetHigh: Int,
+    @SerializedName("infectiousnessOffsetStandard")
+    val infectiousnessOffsetStandard: Int,
+    @SerializedName("reportTypeOffsetConfirmedClinicalDiagnosis")
+    val reportTypeOffsetConfirmedClinicalDiagnosis: Int,
+    @SerializedName("reportTypeOffsetConfirmedTest")
+    val reportTypeOffsetConfirmedTest: Int,
+    @SerializedName("reportTypeOffsetRecursive")
+    val reportTypeOffsetRecursive: Int,
+    @SerializedName("reportTypeOffsetSelfReport")
+    val reportTypeOffsetSelfReport: Int
+)
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTrlFilter.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTrlFilter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6e529bc6af34385922152903db58c5a1521c725e
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTrlFilter.kt
@@ -0,0 +1,8 @@
+package de.rki.coronawarnapp.nearby.windows.entities.configuration
+
+import com.google.gson.annotations.SerializedName
+
+data class JsonTrlFilter(
+    @SerializedName("dropIfTrlInRange")
+    val dropIfTrlInRange: Range
+)
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/Range.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/Range.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a4d686a74cd8183a8261ec35bf8487b0ad2db005
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/Range.kt
@@ -0,0 +1,14 @@
+package de.rki.coronawarnapp.nearby.windows.entities.configuration
+
+import com.google.gson.annotations.SerializedName
+
+data class Range(
+    @SerializedName("min")
+    val min: Double,
+    @SerializedName("minExclusive")
+    val minExclusive: Boolean,
+    @SerializedName("max")
+    val max: Double,
+    @SerializedName("maxExclusive")
+    val maxExclusive: Boolean
+)
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
new file mode 100644
index 0000000000000000000000000000000000000000..4338b8f94fc85935bc7a060cc7a52a0f5340dae0
--- /dev/null
+++ b/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json
@@ -0,0 +1,1025 @@
+{
+  "__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
+        }
+      }
+    ],
+    "minutesAtAttenuationWeights": [
+      {
+        "attenuationRange": {
+          "min": 0,
+          "max": 55,
+          "maxExclusive": true
+        },
+        "weight": 1
+      },
+      {
+        "attenuationRange": {
+          "min": 55,
+          "max": 63,
+          "maxExclusive": true
+        },
+        "weight": 0.5
+      }
+    ],
+    "normalizedTimePerEWToRiskLevelMapping": [
+      {
+        "normalizedTimeRange": {
+          "min": 0,
+          "max": 15,
+          "maxExclusive": true
+        },
+        "riskLevel": 1
+      },
+      {
+        "normalizedTimeRange": {
+          "min": 15,
+          "max": 9999
+        },
+        "riskLevel": 2
+      }
+    ],
+    "normalizedTimePerDayToRiskLevelMapping": [
+      {
+        "normalizedTimeRange": {
+          "min": 0,
+          "max": 15,
+          "maxExclusive": true
+        },
+        "riskLevel": 1
+      },
+      {
+        "normalizedTimeRange": {
+          "min": 15,
+          "max": 9999
+        },
+        "riskLevel": 2
+      }
+    ],
+    "trlEncoding": {
+      "infectiousnessOffsetStandard": 0,
+      "infectiousnessOffsetHigh":  4,
+      "reportTypeOffsetRecursive": 4,
+      "reportTypeOffsetSelfReport": 3,
+      "reportTypeOffsetConfirmedClinicalDiagnosis": 2,
+      "reportTypeOffsetConfirmedTest": 1
+    },
+    "transmissionRiskLevelMultiplier": 0.2
+  },
+  "testCases": [
+    {
+      "description": "drop Exposure Windows that do not match minutesAtAttenuationFilters (< 10 minutes)",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 2,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 299
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 0,
+      "expAgeOfMostRecentDateWithLowRisk": null,
+      "expAgeOfMostRecentDateWithHighRisk": null,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0
+    },
+    {
+      "description": "keep Exposure Windows that match minutesAtAttenuationFilters (>= 10 minutes)",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 2,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithHighRisk": null,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0
+    },
+    {
+      "description": "drop Exposure Windows that do not match minutesAtAttenuationFilters (>= 73 dB)",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 2,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 73,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 73,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 0,
+      "expAgeOfMostRecentDateWithLowRisk": null,
+      "expAgeOfMostRecentDateWithHighRisk": null,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0
+    },
+    {
+      "description": "keep Exposure Windows that match minutesAtAttenuationFilters (< 73 dB)",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 2,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 72,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 72,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithHighRisk": null,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0
+    },
+    {
+      "description": "drop Exposure Windows that do not match trlFilters (<= 2)",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 2,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 0,
+      "expAgeOfMostRecentDateWithLowRisk": null,
+      "expAgeOfMostRecentDateWithHighRisk": null,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0
+    },
+    {
+      "description": "keep Exposure Windows that match trlFilters (> 2)",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 3,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithHighRisk": null,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0
+    },
+    {
+      "description": "identify Exposure Window as Low Risk based on normalizedTime (< 15)",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 1,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 299
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithHighRisk": null,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0,
+      "expNumberOfExposureWindowsWithLowRisk": 1,
+      "expNumberOfExposureWindowsWithHighRisk": 0
+    },
+    {
+      "description": "identify Exposure Window as High Risk based on normalizedTime (>= 15)",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 1,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 2,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 0,
+      "expAgeOfMostRecentDateWithLowRisk": null,
+      "expAgeOfMostRecentDateWithHighRisk": 1,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 1,
+      "expNumberOfExposureWindowsWithLowRisk": 1,
+      "expNumberOfExposureWindowsWithHighRisk": 0
+    },
+    {
+      "description": "identify the most recent date with Low Risk",
+      "exposureWindows": [
+        {
+          "ageInDays": 3,
+          "reportType": 3,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        },
+        {
+          "ageInDays": 2,
+          "reportType": 3,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        },
+        {
+          "ageInDays": 4,
+          "reportType": 3,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 3,
+      "expAgeOfMostRecentDateWithLowRisk": 2,
+      "expAgeOfMostRecentDateWithHighRisk": null,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0
+    },
+    {
+      "description": "count Exposure Windows with same Date/TRL/CallibrationConfidence only once towards distinct encounters with Low Risk",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 3,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        },
+        {
+          "ageInDays": 1,
+          "reportType": 3,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithHighRisk": null,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0
+    },
+    {
+      "description": "count Exposure Windows with same Date/TRL but different CallibrationConfidence separately towards distinct encounters with Low Risk",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 3,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        },
+        {
+          "ageInDays": 1,
+          "reportType": 3,
+          "infectiousness": 1,
+          "calibrationConfidence": 1,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 2,
+      "expAgeOfMostRecentDateWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithHighRisk": null,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0
+    },
+    {
+      "description": "count Exposure Windows with same Date/CallibrationConfidence but different TRL separately towards distinct encounters with Low Risk",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 3,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        },
+        {
+          "ageInDays": 1,
+          "reportType": 4,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 2,
+      "expAgeOfMostRecentDateWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithHighRisk": null,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0
+    },
+    {
+      "description": "count Exposure Windows with same TRL/CallibrationConfidence but different Date separately towards distinct encounters with Low Risk",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 3,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        },
+        {
+          "ageInDays": 2,
+          "reportType": 3,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 2,
+      "expAgeOfMostRecentDateWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithHighRisk": null,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0
+    },
+    {
+      "description": "determine High Risk in total if there are sufficient Exposure Windows with a Low Risk",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 3,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        },
+        {
+          "ageInDays": 1,
+          "reportType": 3,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        },
+        {
+          "ageInDays": 1,
+          "reportType": 3,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 2,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithLowRisk": null,
+      "expAgeOfMostRecentDateWithHighRisk": 1,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0
+    },
+    {
+      "description": "identify the most recent date with High Risk",
+      "exposureWindows": [
+        {
+          "ageInDays": 3,
+          "reportType": 4,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            }
+          ]
+        },
+        {
+          "ageInDays": 2,
+          "reportType": 4,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            }
+          ]
+        },
+        {
+          "ageInDays": 4,
+          "reportType": 4,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 2,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 0,
+      "expAgeOfMostRecentDateWithLowRisk": null,
+      "expAgeOfMostRecentDateWithHighRisk": 2,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 3
+    },
+    {
+      "description": "count Exposure Windows with same Date/TRL/CallibrationConfidence only once towards distinct encounters with High Risk",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 4,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            }
+          ]
+        },
+        {
+          "ageInDays": 1,
+          "reportType": 4,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 2,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 0,
+      "expAgeOfMostRecentDateWithLowRisk": null,
+      "expAgeOfMostRecentDateWithHighRisk": 1,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 1
+    },
+    {
+      "description": "count Exposure Windows with same Date/TRL but different CallibrationConfidence separately towards distinct encounters with High Risk",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 4,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            }
+          ]
+        },
+        {
+          "ageInDays": 1,
+          "reportType": 4,
+          "infectiousness": 2,
+          "calibrationConfidence": 1,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 2,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 0,
+      "expAgeOfMostRecentDateWithLowRisk": null,
+      "expAgeOfMostRecentDateWithHighRisk": 1,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 2
+    },
+    {
+      "description": "count Exposure Windows with same Date/CallibrationConfidence but different TRL separately towards distinct encounters with High Risk",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 4,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            }
+          ]
+        },
+        {
+          "ageInDays": 1,
+          "reportType": 3,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 2,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 0,
+      "expAgeOfMostRecentDateWithLowRisk": null,
+      "expAgeOfMostRecentDateWithHighRisk": 1,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 2
+    },
+    {
+      "description": "count Exposure Windows with same TRL/CallibrationConfidence but different Date separately towards distinct encounters with High Risk",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 4,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            }
+          ]
+        },
+        {
+          "ageInDays": 2,
+          "reportType": 4,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 2,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 0,
+      "expAgeOfMostRecentDateWithLowRisk": null,
+      "expAgeOfMostRecentDateWithHighRisk": 1,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 2
+    },
+    {
+      "description": "determine High Risk in total if there is at least one Exposure Window with High Risk",
+      "exposureWindows": [
+        {
+          "ageInDays": 2,
+          "reportType": 3,
+          "infectiousness": 1,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        },
+        {
+          "ageInDays": 1,
+          "reportType": 4,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            },
+            {
+              "typicalAttenuation": 30,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 420
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 2,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithLowRisk": 2,
+      "expAgeOfMostRecentDateWithHighRisk": 1,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 1
+    },
+    {
+      "description": "handle empty set of Exposure Windows",
+      "exposureWindows": [],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 0,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0,
+      "expAgeOfMostRecentDateWithLowRisk": null,
+      "expAgeOfMostRecentDateWithHighRisk": null
+    },
+    {
+      "description": "handle empty set of Scan Instances (should never happen)",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 2,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": []
+        }
+      ],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 0,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0,
+      "expAgeOfMostRecentDateWithLowRisk": null,
+      "expAgeOfMostRecentDateWithHighRisk": null
+    },
+    {
+      "description": "handle a typicalAttenuation of zero (should never happen)",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 3,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 0,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 70,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 1,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0,
+      "expAgeOfMostRecentDateWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithHighRisk": null,
+      "expNumberOfExposureWindowsWithLowRisk": 1,
+      "expNumberOfExposureWindowsWithHighRisk": 0
+    },
+    {
+      "description": "handle secondsSinceLastScan of zero (should never happen)",
+      "exposureWindows": [
+        {
+          "ageInDays": 1,
+          "reportType": 3,
+          "infectiousness": 2,
+          "calibrationConfidence": 0,
+          "scanInstances": [
+            {
+              "typicalAttenuation": 70,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 0
+            },
+            {
+              "typicalAttenuation": 70,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            },
+            {
+              "typicalAttenuation": 70,
+              "minAttenuation": 25,
+              "secondsSinceLastScan": 300
+            }
+          ]
+        }
+      ],
+      "expTotalRiskLevel": 1,
+      "expTotalMinimumDistinctEncountersWithLowRisk": 1,
+      "expTotalMinimumDistinctEncountersWithHighRisk": 0,
+      "expAgeOfMostRecentDateWithLowRisk": 1,
+      "expAgeOfMostRecentDateWithHighRisk": null,
+      "expNumberOfExposureWindowsWithLowRisk": 1,
+      "expNumberOfExposureWindowsWithHighRisk": 0
+    }
+  ]
+}