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

Derive check-ins time (EXPOSUREAPP-5708) #2623

Merged
merged 24 commits into from
Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
45e6dc1
Map presence tracing configuration parameters
mtwalli Mar 13, 2021
edb9ea9
Give priority to config values
mtwalli Mar 13, 2021
373436c
Use presence tracing config in QR Code creation
mtwalli Mar 13, 2021
b7d5d14
lint
mtwalli Mar 15, 2021
f91cbbe
Fix tests
mtwalli Mar 15, 2021
89afdeb
Create TimeIntervalDeriver.kt
mtwalli Mar 15, 2021
d72b9dc
Merge branch 'dev/presence_tracing_app_config' into feature/5708-deri…
mtwalli Mar 15, 2021
fc972d1
Implement time deriver
mtwalli Mar 15, 2021
9331a84
Add test class
mtwalli Mar 15, 2021
a0fcedc
Adjustment
mtwalli Mar 15, 2021
774a5aa
Add test scenarios
mtwalli Mar 15, 2021
6200391
detekt
mtwalli Mar 15, 2021
8791c60
Merge branch 'release/2.0.x' into feature/5708-derive-check-ins-time
mtwalli Mar 15, 2021
28302c6
Specify locale
mtwalli Mar 15, 2021
bfea9f0
Set time zone
mtwalli Mar 15, 2021
28f4a76
Merge branch 'release/2.0.x' into feature/5708-derive-check-ins-time
d4rken Mar 16, 2021
b8fffd1
Make inRange public
mtwalli Mar 16, 2021
c26696e
Use common in Range - specify the timestamp in seconds
mtwalli Mar 16, 2021
49356a9
lint
mtwalli Mar 16, 2021
827377d
Merge branch 'release/2.0.x' into feature/5708-derive-check-ins-time
mtwalli Mar 16, 2021
bdfcdbe
Merge branch 'release/2.0.x' into feature/5708-derive-check-ins-time
mtwalli Mar 17, 2021
28ce93c
Merge branch 'release/2.0.x' into feature/5708-derive-check-ins-time
ralfgehrer Mar 17, 2021
0e7a4b2
Merge branch 'release/2.0.x' into feature/5708-derive-check-ins-time
ralfgehrer Mar 17, 2021
9084884
Explicit types
mtwalli Mar 17, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package de.rki.coronawarnapp.eventregistration.checkins.derivetime

import de.rki.coronawarnapp.appconfig.PresenceTracingSubmissionParamContainer
import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass.Range
import timber.log.Timber
import java.util.concurrent.TimeUnit
import kotlin.math.max
import kotlin.math.roundToLong

private val INTERVAL_LENGTH_IN_SECONDS = TimeUnit.MINUTES.toSeconds(10L)

private fun alignToInterval(timestamp: Long) =
(timestamp / INTERVAL_LENGTH_IN_SECONDS) * INTERVAL_LENGTH_IN_SECONDS

private fun Range.inRange(value: Long): Boolean {
mtwalli marked this conversation as resolved.
Show resolved Hide resolved
val inRage = when {
minExclusive && value <= min -> false
!minExclusive && value < min -> false
maxExclusive && value >= max -> false
!maxExclusive && value > max -> false
else -> true
}

Timber.d(
"Value:%s isInRange:%s - Range{min:%s,max:%s,minExclusive:%s,maxExclusive:%s}",
value,
inRage,
min,
max,
minExclusive,
maxExclusive
)
return inRage
}

fun PresenceTracingSubmissionParamContainer.deriveTime(
startTimestamp: Long,
mtwalli marked this conversation as resolved.
Show resolved Hide resolved
endTimestamp: Long
): Pair<Long, Long>? {
val durationInSeconds = max(0, endTimestamp - startTimestamp)
Timber.d("durationInSeconds: $durationInSeconds")

val durationInMinutes = TimeUnit.SECONDS.toMinutes(durationInSeconds)
Timber.d("durationInMinutes: $durationInMinutes")

val dropDueToDuration = durationFilters.any { durationFilter ->
mtwalli marked this conversation as resolved.
Show resolved Hide resolved
durationFilter.dropIfMinutesInRange.inRange(durationInMinutes)
}
Timber.d("dropDueToDuration: $dropDueToDuration")
if (dropDueToDuration) return null

val aerosoleDecays = aerosoleDecayLinearFunctions.filter { aerosole ->
aerosole.minutesRange.inRange(durationInMinutes)
}.map { aerosole ->
aerosole.slope * durationInSeconds + TimeUnit.MINUTES.toSeconds(aerosole.intercept.toLong())
}
Timber.d("aerosoleDecays:$aerosoleDecays")
val aerosoleDecayInSeconds = aerosoleDecays.firstOrNull() ?: 0.0 // Default: zero, i.e. 'no decay'
Timber.d("aerosoleDecayInSeconds: $aerosoleDecayInSeconds")

val relevantEndTimestamp = endTimestamp + aerosoleDecayInSeconds.toLong()
val relevantStartIntervalTimestamp = alignToInterval(startTimestamp)
val relevantEndIntervalTimestamp = alignToInterval(relevantEndTimestamp)
val overlapWithStartInterval = relevantStartIntervalTimestamp + INTERVAL_LENGTH_IN_SECONDS - startTimestamp
val overlapWithEndInterval = relevantEndTimestamp - relevantEndIntervalTimestamp
Timber.d("overlapWithStartInterval: $overlapWithStartInterval")
Timber.d("overlapWithEndInterval: $overlapWithEndInterval")

val targetDurationInSeconds =
mtwalli marked this conversation as resolved.
Show resolved Hide resolved
((durationInSeconds + aerosoleDecayInSeconds) / INTERVAL_LENGTH_IN_SECONDS).roundToLong() *
INTERVAL_LENGTH_IN_SECONDS

Timber.d("targetDurationInSeconds:$targetDurationInSeconds")

return if (overlapWithEndInterval > overlapWithStartInterval) {
Timber.d(
"overlapWithEndInterval:%s > overlapWithStartInterval:%s",
overlapWithEndInterval,
overlapWithStartInterval
)
val newEndTimestamp = relevantEndIntervalTimestamp + INTERVAL_LENGTH_IN_SECONDS
val newStartTimestamp = newEndTimestamp - targetDurationInSeconds
newStartTimestamp to newEndTimestamp
} else {
Timber.d(
"overlapWithEndInterval:%s, overlapWithStartInterval:%s",
overlapWithEndInterval,
overlapWithStartInterval
)
val newEndTimestamp = relevantStartIntervalTimestamp + targetDurationInSeconds
relevantStartIntervalTimestamp to newEndTimestamp
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
package de.rki.coronawarnapp.eventregistration.checkins.derivetime

import de.rki.coronawarnapp.appconfig.PresenceTracingSubmissionParamContainer
import de.rki.coronawarnapp.server.protocols.internal.v2
.PresenceTracingParametersOuterClass.PresenceTracingSubmissionParameters.DurationFilter
import de.rki.coronawarnapp.server.protocols.internal.v2
.PresenceTracingParametersOuterClass.PresenceTracingSubmissionParameters.AerosoleDecayFunctionLinear
import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass.Range
import io.kotest.matchers.shouldBe
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import org.joda.time.format.DateTimeFormat
import org.joda.time.format.DateTimeFormatter
import org.junit.jupiter.api.Test

import testhelpers.BaseTest
import java.util.Locale
import java.util.concurrent.TimeUnit

/**
* Test scenarios reference: [https://github.com/corona-warn-app/cwa-app-tech-spec/blob/
* proposal/event-registration-mvp/test-cases/pt-derive-time-interval-data.json]
*/
internal class TimeIntervalDeriverTest : BaseTest() {

/* "defaultConfiguration": {
"durationFilters": [
{
"dropIfDurationInRange": {
"min": 0,
"max": 10,
"maxExclusive": true
}
}
],
"aerosoleDecayTime": [
{
"durationRange": {
"min": 0,
"max": 30
},
"slope": 1,
"intercept": 0
},
{
"durationRange": {
"min": 30,
"max": 9999,
"minExclusive": true
},
"slope": 0,
"intercept": 30
}
]
} */

private val timeFormat: DateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm Z")
.withZone(DateTimeZone.forID("Europe/Berlin"))

private val presenceTracingConfig = PresenceTracingSubmissionParamContainer(
durationFilters = listOf(
DurationFilter.newBuilder()
.setDropIfMinutesInRange(
Range.newBuilder()
.setMin(0.0)
.setMax(10.0)
.setMaxExclusive(true)
.build()
)
.build()
),
aerosoleDecayLinearFunctions = listOf(
AerosoleDecayFunctionLinear.newBuilder()
.setMinutesRange(
Range.newBuilder()
.setMin(0.0)
.setMax(30.0)
.build()
)
.setSlope(1.0)
.setIntercept(0.0)
.build(),
AerosoleDecayFunctionLinear.newBuilder()
.setMinutesRange(
Range.newBuilder()
.setMin(30.0)
.setMax(9999.0)
.setMinExclusive(true)
.build()
)
.setSlope(0.0)
.setIntercept(30.0)
.build()
)
)

@Test
fun `Scenario 1`() {
/*
"description": "Scenario 1",
"startDateStr": "2021-03-04 10:21+01:00",
"endDateStr": "2021-03-04 10:29+01:00",
"expStartDateStr": null,
"expEndDateStr": null
*/
presenceTracingConfig.deriveTime(
timeInSeconds("2021-03-04 10:21 +01:00"),
timeInSeconds("2021-03-04 10:29 +01:00")
) shouldBe null
}

@Test
fun `Scenario 2`() {
/*
"description": "Scenario 2",
"startDateStr": "2021-03-04 10:20+01:00",
"endDateStr": "2021-03-04 10:30+01:00",
"expStartDateStr": "2021-03-04 10:20+01:00",
"expEndDateStr": "2021-03-04 10:40+01:00"
*/
val (startTime, endTime) = presenceTracingConfig.deriveTime(
timeInSeconds("2021-03-04 10:20 +01:00"),
timeInSeconds("2021-03-04 10:30 +01:00")
)!!

timeToString(startTime) shouldBe "2021-03-04 10:20 +0100"
timeToString(endTime) shouldBe "2021-03-04 10:40 +0100"
}

@Test
fun `Scenario 3`() {
/*
"description": "Scenario 3",
"startDateStr": "2021-03-04 10:21+01:00",
"endDateStr": "2021-03-04 10:31+01:00",
"expStartDateStr": "2021-03-04 10:20+01:00",
"expEndDateStr": "2021-03-04 10:40+01:00"
*/
val (startTime, endTime) = presenceTracingConfig.deriveTime(
timeInSeconds("2021-03-04 10:21 +01:00"),
timeInSeconds("2021-03-04 10:31 +01:00")
)!!

timeToString(startTime) shouldBe "2021-03-04 10:20 +0100"
timeToString(endTime) shouldBe "2021-03-04 10:40 +0100"
}

@Test
fun `Scenario 4`() {
/*
"description": "Scenario 4",
"startDateStr": "2021-03-04 10:26+01:00",
"endDateStr": "2021-03-04 10:36+01:00",
"expStartDateStr": "2021-03-04 10:30+01:00",
"expEndDateStr": "2021-03-04 10:50+01:00"
*/
val (startTime, endTime) = presenceTracingConfig.deriveTime(
timeInSeconds("2021-03-04 10:26 +01:00"),
timeInSeconds("2021-03-04 10:36 +01:00")
)!!

timeToString(startTime) shouldBe "2021-03-04 10:30 +0100"
timeToString(endTime) shouldBe "2021-03-04 10:50 +0100"
}

@Test
fun `Scenario 5`() {
/*
"description": "Scenario 5",
"startDateStr": "2021-03-04 10:21+01:00",
"endDateStr": "2021-03-04 10:33+01:00",
"expStartDateStr": "2021-03-04 10:20+01:00",
"expEndDateStr": "2021-03-04 10:40+01:00"
*/
val (startTime, endTime) = presenceTracingConfig.deriveTime(
timeInSeconds("2021-03-04 10:21 +01:00"),
timeInSeconds("2021-03-04 10:33 +01:00")
)!!

timeToString(startTime) shouldBe "2021-03-04 10:20 +0100"
timeToString(endTime) shouldBe "2021-03-04 10:40 +0100"
}

@Test
fun `Scenario 6`() {
/*
"description": "Scenario 6",
"startDateStr": "2021-03-04 10:24+01:00",
"endDateStr": "2021-03-04 10:36+01:00",
"expStartDateStr": "2021-03-04 10:30+01:00",
"expEndDateStr": "2021-03-04 10:50+01:00"
*/
val (startTime, endTime) = presenceTracingConfig.deriveTime(
timeInSeconds("2021-03-04 10:24 +01:00"),
timeInSeconds("2021-03-04 10:36 +01:00")
)!!

timeToString(startTime) shouldBe "2021-03-04 10:30 +0100"
timeToString(endTime) shouldBe "2021-03-04 10:50 +0100"
}

@Test
fun `Scenario 7`() {
/*
"description": "Scenario 7",
"startDateStr": "2021-03-04 10:25+01:00",
"endDateStr": "2021-03-04 10:39+01:00",
"expStartDateStr": "2021-03-04 10:20+01:00",
"expEndDateStr": "2021-03-04 10:50+01:00"
*/
val (startTime, endTime) = presenceTracingConfig.deriveTime(
timeInSeconds("2021-03-04 10:25 +01:00"),
timeInSeconds("2021-03-04 10:39 +01:00")
)!!

timeToString(startTime) shouldBe "2021-03-04 10:20 +0100"
timeToString(endTime) shouldBe "2021-03-04 10:50 +0100"
}

@Test
fun `Scenario 8`() {
/*
"description": "Scenario 8",
"startDateStr": "2021-03-04 10:28+01:00",
"endDateStr": "2021-03-04 10:42+01:00",
"expStartDateStr": "2021-03-04 10:30+01:00",
"expEndDateStr": "2021-03-04 11:00+01:00"
*/
val (startTime, endTime) = presenceTracingConfig.deriveTime(
timeInSeconds("2021-03-04 10:28 +01:00"),
timeInSeconds("2021-03-04 10:42 +01:00")
)!!

timeToString(startTime) shouldBe "2021-03-04 10:30 +0100"
timeToString(endTime) shouldBe "2021-03-04 11:00 +0100"
}

@Test
fun `Scenario 9`() {
/*
"description": "Scenario 9",
"startDateStr": "2021-03-04 10:25+01:00",
"endDateStr": "2021-03-04 10:40+01:00",
"expStartDateStr": "2021-03-04 10:20+01:00",
"expEndDateStr": "2021-03-04 10:50+01:00"
*/
val (startTime, endTime) = presenceTracingConfig.deriveTime(
timeInSeconds("2021-03-04 10:25 +01:00"),
timeInSeconds("2021-03-04 10:40 +01:00")
)!!

timeToString(startTime) shouldBe "2021-03-04 10:20 +0100"
timeToString(endTime) shouldBe "2021-03-04 10:50 +0100"
}

@Test
fun `Scenario 10`() {
/*
"description": "Scenario 10",
"startDateStr": "2021-03-04 10:22+01:00",
"endDateStr": "2021-03-04 11:12+01:00",
"expStartDateStr": "2021-03-04 10:20+01:00",
"expEndDateStr": "2021-03-04 11:40+01:00"
*/
val (startTime, endTime) = presenceTracingConfig.deriveTime(
timeInSeconds("2021-03-04 10:22 +01:00"),
timeInSeconds("2021-03-04 11:12 +01:00")
)!!

timeToString(startTime) shouldBe "2021-03-04 10:20 +0100"
timeToString(endTime) shouldBe "2021-03-04 11:40 +0100"
}

private fun timeInSeconds(dateTime: String): Long {
val millis = timeFormat.parseDateTime(dateTime).millis
return TimeUnit.MILLISECONDS.toSeconds(millis)
}

private fun timeToString(timeInSecond: Long): String {
return DateTime(TimeUnit.SECONDS.toMillis(timeInSecond)).toString(timeFormat)
}
}