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

Converted risk level transaction to task (EXPOSUREAPP-2842) #1412

Merged
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
5f34a98
converted risk level transaction to task
chris-cwa Oct 15, 2020
c5d5403
simplified
chris-cwa Oct 15, 2020
863d43f
fixed config: timeout is now duration
chris-cwa Oct 15, 2020
054d300
less is more
chris-cwa Oct 15, 2020
b1419c3
arrangement + empty lines
chris-cwa Oct 16, 2020
daa31bf
Merge branch 'release/1.6.x' into feature/2909_refactor_risk_level_tr…
chris-cwa Oct 16, 2020
b29d9a5
Added additional checks to notification of exposure/test-result to ch…
kolyaopahle Oct 16, 2020
4008698
Merge branch 'release/1.6.x' into feature/2909_refactor_risk_level_tr…
chris-cwa Oct 19, 2020
9a18b89
Merge branch 'release/1.6.x' into feature/2909_refactor_risk_level_tr…
chris-cwa Oct 20, 2020
7f915c8
submit risk level task requests
chris-cwa Oct 20, 2020
e83c17a
- unreachable catch blocks
chris-cwa Oct 20, 2020
2794b20
housekeeping
chris-cwa Oct 20, 2020
b4417c3
Merge branch 'fix/release1.6.x_housekeeping' into feature/2909_refact…
chris-cwa Oct 20, 2020
01aa0c6
satisfied ktlint + detekt
chris-cwa Oct 20, 2020
bd46e26
fixed flow: isRefreshing
chris-cwa Oct 20, 2020
c79f7cf
Merge branch 'release/1.6.x' into feature/2909_refactor_risk_level_tr…
chris-cwa Oct 20, 2020
81e872c
fixed flavour for testers
chris-cwa Oct 20, 2020
8581cb3
refactored benchmark
chris-cwa Oct 21, 2020
2c67ef0
fixed package declaration
chris-cwa Oct 21, 2020
9ad5874
Merge branch 'release/1.6.x' into feature/2909_refactor_risk_level_tr…
chris-cwa Oct 21, 2020
f85b995
unit tests
chris-cwa Oct 21, 2020
d32c7b5
Merge branch 'feature/2909_refactor_risk_level_transaction' of https:…
chris-cwa Oct 21, 2020
1f6590e
fixed injection
chris-cwa Oct 21, 2020
61fa73f
fixed tests
chris-cwa Oct 21, 2020
b56798f
Merge branch 'release/1.6.x' into feature/2909_refactor_risk_level_tr…
chris-cwa Oct 23, 2020
31d9215
Merge branch 'release/1.6.x' into feature/2909_refactor_risk_level_tr…
harambasicluka Oct 23, 2020
79d38ec
Merge branch 'release/1.6.x' into feature/2909_refactor_risk_level_tr…
chris-cwa Oct 26, 2020
eaf9aab
comments from PR
chris-cwa Oct 26, 2020
edaddd4
Merge branch 'release/1.6.x' into feature/2909_refactor_risk_level_tr…
chris-cwa Oct 26, 2020
21ff6ec
Merge branch 'release/1.6.x' into fix/3182-3181-notifications-in-inva…
kolyaopahle Oct 26, 2020
beaf21a
Merge remote-tracking branch 'origin/release/1.6.x' into feature/2909…
chris-cwa Oct 27, 2020
634b89c
Merge branch 'feature/2909_refactor_risk_level_transaction' of https:…
chris-cwa Oct 27, 2020
447a411
Merge remote-tracking branch 'origin/fix/3182-3181-notifications-in-i…
chris-cwa Oct 27, 2020
e87caef
fixed injection
chris-cwa Oct 27, 2020
29eaec1
Merge remote-tracking branch 'origin/release/1.6.x' into feature/2909…
chris-cwa Oct 28, 2020
e60c4a5
fixed lint issues
chris-cwa Oct 28, 2020
8b13dd6
fixed another merge conflict
chris-cwa Oct 28, 2020
593b678
- unused class
chris-cwa Oct 28, 2020
994d697
map task factory
chris-cwa Oct 28, 2020
856a7c4
Merge branch 'release/1.6.x' into feature/2909_refactor_risk_level_tr…
chris-cwa Oct 28, 2020
da4c360
fulfilled comment style wishes
chris-cwa Oct 28, 2020
db9c305
Merge branch 'feature/2909_refactor_risk_level_transaction' of https:…
chris-cwa Oct 28, 2020
83a6942
fixed tests suspended by changes from develop branch
chris-cwa Oct 28, 2020
cfbb12f
Merge remote-tracking branch 'origin/release/1.6.x' into feature/2909…
chris-cwa Oct 28, 2020
9bed8c9
Merge branch 'release/1.7.x' into feature/2909_refactor_risk_level_tr…
chris-cwa Oct 29, 2020
6ca3f35
Merge branch 'release/1.7.x' into feature/2909_refactor_risk_level_tr…
harambasicluka Oct 29, 2020
21b376c
Update Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/Defaul…
chris-cwa Oct 30, 2020
270d081
changes from PR
chris-cwa Oct 30, 2020
2b87f03
fixed a test
chris-cwa Oct 30, 2020
971f4fa
show logs, ktlint issues
chris-cwa Oct 30, 2020
3c5de43
made log tag non-nullable
chris-cwa Oct 30, 2020
965e966
improvements from reviewers
chris-cwa Oct 30, 2020
1a16472
reduced static access
chris-cwa Oct 30, 2020
5e42700
Merge remote-tracking branch 'origin/release/1.7.x' into feature/2909…
chris-cwa Oct 30, 2020
fc1977a
Saint Pipeline, please give me the green light
chris-cwa Oct 30, 2020
1bf9119
Merge remote-tracking branch 'origin/release/1.7.x' into feature/2909…
chris-cwa Oct 30, 2020
cf2dbe0
Merge branch 'release/1.7.x' into feature/2909_refactor_risk_level_tr…
chris-cwa Oct 30, 2020
0104284
toString()
chris-cwa Nov 2, 2020
53d809e
ktlint + di
chris-cwa Nov 2, 2020
180d613
fixed test
chris-cwa Nov 2, 2020
7eb2318
Merge branch 'release/1.7.x' into feature/2909_refactor_risk_level_tr…
chris-cwa Nov 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package de.rki.coronawarnapp.risk

import androidx.core.app.NotificationCompat
import com.google.android.gms.nearby.exposurenotification.ExposureSummary
import de.rki.coronawarnapp.CoronaWarnApplication
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.exception.RiskLevelCalculationException
import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
import de.rki.coronawarnapp.notification.NotificationHelper
import de.rki.coronawarnapp.risk.RiskLevel.UNKNOWN_RISK_INITIAL
import de.rki.coronawarnapp.risk.RiskLevel.UNKNOWN_RISK_OUTDATED_RESULTS
import de.rki.coronawarnapp.server.protocols.ApplicationConfigurationOuterClass
import de.rki.coronawarnapp.service.applicationconfiguration.ApplicationConfigurationService
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.storage.RiskLevelRepository
import de.rki.coronawarnapp.util.TimeAndDateExtensions.millisecondsToHours
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.round

@Singleton
class DefaultRiskLevels @Inject constructor() : RiskLevels {

companion object {
chris-cwa marked this conversation as resolved.
Show resolved Hide resolved
private var TAG = DefaultRiskLevels::class.simpleName
private const val DECIMAL_MULTIPLIER = 100
}

override fun updateRepository(riskLevel: RiskLevel, time: Long) {
val rollbackItems = mutableListOf<RollbackItem>()
try {
Timber.tag(TAG).v("update the risk level with $riskLevel")
chris-cwa marked this conversation as resolved.
Show resolved Hide resolved
val lastCalculatedRiskLevelScoreForRollback =
RiskLevelRepository.getLastCalculatedScore()
chris-cwa marked this conversation as resolved.
Show resolved Hide resolved
updateRiskLevelScore(riskLevel)
rollbackItems.add {
updateRiskLevelScore(lastCalculatedRiskLevelScoreForRollback)
}

// risk level calculation date update
val lastCalculatedRiskLevelDate = LocalData.lastTimeRiskLevelCalculation()
ralfgehrer marked this conversation as resolved.
Show resolved Hide resolved
LocalData.lastTimeRiskLevelCalculation(time)
rollbackItems.add {
LocalData.lastTimeRiskLevelCalculation(lastCalculatedRiskLevelDate)
}
} catch (error: Exception) {
Timber.tag(TAG).e(error)
chris-cwa marked this conversation as resolved.
Show resolved Hide resolved

try {
Timber.tag(TAG).d("Initiate Rollback")
for (rollbackItem: RollbackItem in rollbackItems) rollbackItem.invoke()
} catch (rollbackException: Exception) {
Timber.tag(TAG).e(rollbackException)
chris-cwa marked this conversation as resolved.
Show resolved Hide resolved
}

throw error
}
}

override val calculationNotPossibleBecauseOfOutdatedResults: Boolean
get() {
// if the last calculation is longer in the past as the defined threshold we return the stale state
val timeSinceLastDiagnosisKeyFetchFromServer =
TimeVariables.getTimeSinceLastDiagnosisKeyFetchFromServer()
?: throw RiskLevelCalculationException(IllegalArgumentException("time since last exposure calculation is null"))
/** we only return outdated risk level if the threshold is reached AND the active tracing time is above the
defined threshold because [UNKNOWN_RISK_INITIAL] overrules [UNKNOWN_RISK_OUTDATED_RESULTS] */
return timeSinceLastDiagnosisKeyFetchFromServer.millisecondsToHours() >
TimeVariables.getMaxStaleExposureRiskRange() && isActiveTracingTimeAboveThreshold
}

override val calculationNotPossibleBecauseNoKeys =
(TimeVariables.getLastTimeDiagnosisKeysFromServerFetch() == null).also {
if (it)
chris-cwa marked this conversation as resolved.
Show resolved Hide resolved
Timber.tag(TAG)
.v("no last time diagnosis keys from server fetch timestamp was found")
}

override suspend fun calculationNotPossibleBecauseTracingIsOff() =
// this applies if tracing is not activated
!InternalExposureNotificationClient.asyncIsEnabled()
chris-cwa marked this conversation as resolved.
Show resolved Hide resolved

override suspend fun isIncreasedRisk(): Boolean {
val lastExposureSummary = getNewExposureSummary()
// retrieve application configuration
chris-cwa marked this conversation as resolved.
Show resolved Hide resolved
val appConfiguration = getApplicationConfiguration()
Timber.tag(TAG).v("retrieved configuration from backend")
// custom attenuation parameters to weight the attenuation
chris-cwa marked this conversation as resolved.
Show resolved Hide resolved
// values provided by the Google API
val attenuationParameters = appConfiguration.attenuationDuration
// these are the defined risk classes. They will divide the calculated
// risk score into the low and increased risk
val riskScoreClassification = appConfiguration.riskScoreClasses

// calculate the risk score based on the values collected by the Google EN API and
// the backend configuration
val riskScore = calculateRiskScore(
attenuationParameters,
lastExposureSummary
).also {
Timber.tag(TAG).v("calculated risk with the given config: $it")
}

// get the high risk score class
val highRiskScoreClass =
riskScoreClassification.riskClassesList.find { it.label == "HIGH" }
?: throw RiskLevelCalculationException(IllegalStateException("no high risk score class found"))

// if the calculated risk score is above the defined level threshold we return the high level risk score
if (withinDefinedLevelThreshold(
riskScore,
highRiskScoreClass.min,
highRiskScoreClass.max
)
) {
Timber.tag(TAG)
.v("$riskScore is above the defined min value ${highRiskScoreClass.min}")
return true
} else if (riskScore > highRiskScoreClass.max) {
harambasicluka marked this conversation as resolved.
Show resolved Hide resolved
throw RiskLevelCalculationException(
IllegalStateException("risk score is above the max threshold for score class")
)
}

return false
}

override val isActiveTracingTimeAboveThreshold: Boolean
get() {
val durationTracingIsActive = TimeVariables.getTimeActiveTracingDuration()
val durationTracingIsActiveThreshold =
TimeVariables.getMinActivatedTracingTime().toLong()

val activeTracingDurationInHours = durationTracingIsActive.millisecondsToHours()

return (activeTracingDurationInHours >= durationTracingIsActiveThreshold).also {
Timber.tag(TAG).v(
"active tracing time ($activeTracingDurationInHours h) is above threshold " +
"($durationTracingIsActiveThreshold h): $it"
)
if (it) {
Timber.tag(TAG).v("active tracing time is not enough")
}
}
}

fun calculateRiskScore(
attenuationParameters: ApplicationConfigurationOuterClass.AttenuationDuration,
exposureSummary: ExposureSummary
): Double {
/** all attenuation values are capped to [TimeVariables.MAX_ATTENUATION_DURATION] */
val weightedAttenuationLow =
attenuationParameters.weights.low
.times(exposureSummary.attenuationDurationsInMinutes[0].capped())
val weightedAttenuationMid =
attenuationParameters.weights.mid
.times(exposureSummary.attenuationDurationsInMinutes[1].capped())
val weightedAttenuationHigh =
attenuationParameters.weights.high
.times(exposureSummary.attenuationDurationsInMinutes[2].capped())

val maximumRiskScore = exposureSummary.maximumRiskScore.toDouble()

val defaultBucketOffset = attenuationParameters.defaultBucketOffset.toDouble()
val normalizationDivisor = attenuationParameters.riskScoreNormalizationDivisor.toDouble()

val attenuationStrings =
"Weighted Attenuation: ($weightedAttenuationLow + $weightedAttenuationMid + " +
"$weightedAttenuationHigh + $defaultBucketOffset)"
Timber.v(attenuationStrings)

val weightedAttenuationDuration =
weightedAttenuationLow
.plus(weightedAttenuationMid)
.plus(weightedAttenuationHigh)
.plus(defaultBucketOffset)

Timber.v("Formula used: ($maximumRiskScore / $normalizationDivisor) * $weightedAttenuationDuration")

val riskScore = (maximumRiskScore / normalizationDivisor) * weightedAttenuationDuration

return round(riskScore.times(DECIMAL_MULTIPLIER)).div(DECIMAL_MULTIPLIER)
}

fun Int.capped() =
chris-cwa marked this conversation as resolved.
Show resolved Hide resolved
if (this > TimeVariables.getMaxAttenuationDuration()) {
TimeVariables.getMaxAttenuationDuration()
} else {
this
}

fun withinDefinedLevelThreshold(riskScore: Double, min: Int, max: Int) =
chris-cwa marked this conversation as resolved.
Show resolved Hide resolved
riskScore >= min && riskScore <= max

/**
* Make a call to the backend to retrieve the current application configuration values
*
* @return the [ApplicationConfigurationOuterClass.ApplicationConfiguration] from the backend
*/
private suspend fun getApplicationConfiguration(): ApplicationConfigurationOuterClass.ApplicationConfiguration =
chris-cwa marked this conversation as resolved.
Show resolved Hide resolved
withContext(Dispatchers.Default) {
return@withContext ApplicationConfigurationService.asyncRetrieveApplicationConfiguration()
.also { Timber.tag(TAG).d("configuration from backend: $it") }
}

/**
* Updates the Risk Level Score in the repository with the calculated Risk Level
*
* @param riskLevel
*/
fun updateRiskLevelScore(riskLevel: RiskLevel) {
chris-cwa marked this conversation as resolved.
Show resolved Hide resolved
val lastCalculatedScore = RiskLevelRepository.getLastCalculatedScore()
if (RiskLevel.riskLevelChangedBetweenLowAndHigh(lastCalculatedScore, riskLevel)) {
NotificationHelper.sendNotification(
harambasicluka marked this conversation as resolved.
Show resolved Hide resolved
CoronaWarnApplication.getAppContext().getString(R.string.notification_body),
chris-cwa marked this conversation as resolved.
Show resolved Hide resolved
NotificationCompat.PRIORITY_HIGH
)
}
RiskLevelRepository.setRiskLevelScore(riskLevel)
}

/**
* If there is no persisted exposure summary we try to get a new one with the last persisted
* Google API token that was used in the [de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction]
*
* @return a exposure summary from the Google Exposure Notification API
*/
private suspend fun getNewExposureSummary(): ExposureSummary {
val googleToken = LocalData.googleApiToken()
?: throw RiskLevelCalculationException(IllegalStateException("exposure summary is not persisted"))

val exposureSummary =
InternalExposureNotificationClient.asyncGetExposureSummary(googleToken)

return exposureSummary.also {
Timber.tag(TAG)
.v("generated new exposure summary with $googleToken")
}
}
}

This file was deleted.

This file was deleted.