Skip to content

Commit

Permalink
Feature/max min strategy (#31)
Browse files Browse the repository at this point in the history
feat: added (Weighted)MinimumKpiCalculationStrategy, renamed AggregationKpiCalculationStrategy to WeightedAverageKPICalculationStrategy, added in range check for KPI scores

- we now calculate the weighted max as score * weight, but propagate the score
- renamed AggregationKPICalculationStrategy.kt to WeightedAverageKPICalculationStrategy.kt
- renamed RatioKPICalculationStrategy.kt to WeightedRatioKPICalculationStrategy.kt
- feat: added WeightedMinimumKPICalculationStrategy

* fix: min and max strategies return Empty() if edges are empty

* feat: check the score of all KpiCalculationResults to guarantee that they are in the range of 0...100

* test: added rangeCheck test for KpiCalculationResults without a score

* chore: renamed validity check function and added javadoc

* fix: use PkgIdentifier and PURL to support non-OS targets

* chore: fixed naming issues and explicitly return Empty() for max and min strategy
  • Loading branch information
janniclas authored Oct 30, 2024
1 parent a64f48a commit 7d8d122
Show file tree
Hide file tree
Showing 19 changed files with 363 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ object CveAdapter {
AdapterResult.Success.Kpi(
RawValueKpi(
kind = KpiId.VULNERABILITY_SCORE,
score = (it.severity * 10).toInt(),
score = 100 - (it.severity * 10).toInt(),
)
)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ import io.github.oshai.kotlinlogging.KotlinLogging
internal fun getKpiCalculationStrategy(strategyId: KpiStrategyId): KpiCalculationStrategy {
return when (strategyId) {
KpiStrategyId.RAW_VALUE_STRATEGY -> RawValueKpiCalculationStrategy
KpiStrategyId.RATIO_STRATEGY -> RatioKPICalculationStrategy

KpiStrategyId.AGGREGATION_STRATEGY -> AggregationKPICalculationStrategy
KpiStrategyId.WEIGHTED_RATIO_STRATEGY -> WeightedRatioKPICalculationStrategy

KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY -> WeightedAverageKPICalculationStrategy

KpiStrategyId.MAXIMUM_STRATEGY -> MaximumKPICalculationStrategy

KpiStrategyId.WEIGHTED_MAXIMUM_STRATEGY -> MaximumKPICalculationStrategy
KpiStrategyId.MINIMUM_STRATEGY -> MaximumKPICalculationStrategy

KpiStrategyId.WEIGHTED_MAXIMUM_STRATEGY -> WeightedMaximumKPICalculationStrategy

KpiStrategyId.WEIGHTED_MINIMUM_STRATEGY -> WeightedMinimumKPICalculationStrategy
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ internal object MaximumKPICalculationStrategy : BaseKpiCalculationStrategy() {

override fun internalCalculateKpi(edges: Collection<KpiHierarchyEdge>): KpiCalculationResult {

val max = if (edges.isEmpty()) 0 else edges.maxOf { it.to.score }
val max = edges.maxOfOrNull { it.to.score }

if (max == null) {
return KpiCalculationResult.Empty()
}

return KpiCalculationResult.Success(score = max)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2024 Fraunhofer IEM. All rights reserved.
*
* Licensed under the MIT license. See LICENSE file in the project root for details.
*
* SPDX-License-Identifier: MIT
* License-Filename: LICENSE
*/

package de.fraunhofer.iem.spha.core.strategy

import de.fraunhofer.iem.spha.core.hierarchy.KpiHierarchyEdge
import de.fraunhofer.iem.spha.model.kpi.KpiStrategyId
import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiCalculationResult
import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiNode
import io.github.oshai.kotlinlogging.KotlinLogging

private val logger = KotlinLogging.logger {}

internal object MinimumKPICalculationStrategy : BaseKpiCalculationStrategy() {

override val kpiStrategyId: KpiStrategyId
get() = KpiStrategyId.MINIMUM_STRATEGY

override fun internalCalculateKpi(edges: Collection<KpiHierarchyEdge>): KpiCalculationResult {

val min = edges.minOfOrNull { it.to.score }

if (min == null) {
return KpiCalculationResult.Empty()
}

return KpiCalculationResult.Success(score = min)
}

/** There is no validity requirement for this strategy. */
override fun internalIsValid(node: KpiNode, strict: Boolean): Boolean {

if (node.edges.size == 1) {
logger.warn {
"Minimum KPI calculation strategy for node $node is planned " +
"for a single child."
}
}

return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ import io.github.oshai.kotlinlogging.KotlinLogging

private val logger = KotlinLogging.logger {}

internal object AggregationKPICalculationStrategy : BaseKpiCalculationStrategy() {
internal object WeightedAverageKPICalculationStrategy : BaseKpiCalculationStrategy() {

override val kpiStrategyId: KpiStrategyId
get() = KpiStrategyId.AGGREGATION_STRATEGY
get() = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY

/**
* This function calculates the aggregate sum of all given children. If a child is empty it is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ internal object WeightedMaximumKPICalculationStrategy : BaseKpiCalculationStrate

override fun internalCalculateKpi(edges: Collection<KpiHierarchyEdge>): KpiCalculationResult {

val max = if (edges.isEmpty()) 0 else edges.maxOf { it.to.score * it.actualWeight }.toInt()
val max = edges.maxByOrNull { it.to.score * it.actualWeight }?.to?.score

if (max == null) {
return KpiCalculationResult.Empty()
}

return KpiCalculationResult.Success(score = max)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2024 Fraunhofer IEM. All rights reserved.
*
* Licensed under the MIT license. See LICENSE file in the project root for details.
*
* SPDX-License-Identifier: MIT
* License-Filename: LICENSE
*/

package de.fraunhofer.iem.spha.core.strategy

import de.fraunhofer.iem.spha.core.hierarchy.KpiHierarchyEdge
import de.fraunhofer.iem.spha.model.kpi.KpiStrategyId
import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiCalculationResult
import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiNode
import io.github.oshai.kotlinlogging.KotlinLogging

private val logger = KotlinLogging.logger {}

internal object WeightedMinimumKPICalculationStrategy : BaseKpiCalculationStrategy() {

override val kpiStrategyId: KpiStrategyId
get() = KpiStrategyId.WEIGHTED_MINIMUM_STRATEGY

override fun internalCalculateKpi(edges: Collection<KpiHierarchyEdge>): KpiCalculationResult {

val min = edges.minByOrNull { it.to.score * it.actualWeight }?.to?.score

if (min == null) {
return KpiCalculationResult.Empty()
}

return KpiCalculationResult.Success(score = min)
}

/** There is no validity requirement for this strategy. */
override fun internalIsValid(node: KpiNode, strict: Boolean): Boolean {

if (node.edges.size == 1) {
logger.warn {
"Maximum KPI calculation strategy for node $node is planned " +
"for a single child."
}
}

return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ import io.github.oshai.kotlinlogging.KotlinLogging

private val logger = KotlinLogging.logger {}

internal object RatioKPICalculationStrategy : BaseKpiCalculationStrategy() {
internal object WeightedRatioKPICalculationStrategy : BaseKpiCalculationStrategy() {

override val kpiStrategyId: KpiStrategyId
get() = KpiStrategyId.RATIO_STRATEGY
get() = KpiStrategyId.WEIGHTED_RATIO_STRATEGY

/** Returns smallerValue / biggerValue, regardless in which order the values are given. */
override fun internalCalculateKpi(edges: Collection<KpiHierarchyEdge>): KpiCalculationResult {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class KpiCalculatorTest {
val root =
KpiNode(
kpiId = KpiId.ROOT,
kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY,
kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY,
edges =
listOf(
KpiEdge(
Expand Down Expand Up @@ -106,7 +106,7 @@ class KpiCalculatorTest {
val root =
KpiNode(
kpiId = KpiId.ROOT,
kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY,
kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY,
edges =
listOf(
KpiEdge(
Expand Down Expand Up @@ -166,14 +166,14 @@ class KpiCalculatorTest {
val root =
KpiNode(
kpiId = KpiId.ROOT,
kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY,
kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY,
edges =
listOf(
KpiEdge(
target =
KpiNode(
kpiId = KpiId.SIGNED_COMMITS_RATIO,
kpiStrategyId = KpiStrategyId.RATIO_STRATEGY,
kpiStrategyId = KpiStrategyId.WEIGHTED_RATIO_STRATEGY,
edges =
listOf(
KpiEdge(
Expand Down Expand Up @@ -247,7 +247,7 @@ class KpiCalculatorTest {
val root =
KpiNode(
kpiId = KpiId.ROOT,
kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY,
kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY,
edges =
listOf(
KpiEdge(
Expand Down Expand Up @@ -284,7 +284,7 @@ class KpiCalculatorTest {
val root =
KpiNode(
kpiId = KpiId.ROOT,
kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY,
kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY,
edges =
listOf(
KpiEdge(
Expand Down Expand Up @@ -338,7 +338,7 @@ class KpiCalculatorTest {
val root =
KpiNode(
kpiId = KpiId.ROOT,
kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY,
kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY,
edges =
listOf(
KpiEdge(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class HierarchyValidatorTest {
rootNode =
KpiNode(
kpiId = KpiId.ROOT,
kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY,
kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY,
edges = emptyList(),
)
)
Expand All @@ -43,7 +43,7 @@ class HierarchyValidatorTest {
rootNode =
KpiNode(
kpiId = KpiId.ROOT,
kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY,
kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY,
edges =
listOf(
KpiEdge(
Expand Down Expand Up @@ -72,7 +72,7 @@ class HierarchyValidatorTest {
rootNode =
KpiNode(
kpiId = KpiId.ROOT,
kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY,
kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY,
edges =
listOf(
KpiEdge(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal fun getNodeWithErrorResult(plannedWeight: Double): KpiHierarchyNode {
val node =
KpiNode(
kpiId = KpiId.NUMBER_OF_COMMITS,
kpiStrategyId = KpiStrategyId.RATIO_STRATEGY,
kpiStrategyId = KpiStrategyId.WEIGHTED_RATIO_STRATEGY,
edges =
listOf(
KpiEdge(
Expand All @@ -52,7 +52,7 @@ internal fun getNodeIncompleteResult(plannedWeight: Double): KpiHierarchyNode {
val node =
KpiNode(
kpiId = KpiId.SECRETS,
kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY,
kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY,
edges =
listOf(
KpiEdge(
Expand Down Expand Up @@ -104,7 +104,7 @@ class AbstractKpiCalculationTest {
val nodeCorrectChildren =
KpiNode(
kpiId = KpiId.ROOT,
kpiStrategyId = KpiStrategyId.RATIO_STRATEGY,
kpiStrategyId = KpiStrategyId.WEIGHTED_RATIO_STRATEGY,
edges =
listOf(
KpiEdge(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ class AggregationKpiCalculationStrategyTest {
fun calculateEmpty() {

val calcRelaxed =
AggregationKPICalculationStrategy.calculateKpi(
WeightedAverageKPICalculationStrategy.calculateKpi(
hierarchyEdges = listOf(),
strict = false,
)
val calcStrict =
AggregationKPICalculationStrategy.calculateKpi(hierarchyEdges = listOf(), strict = true)
WeightedAverageKPICalculationStrategy.calculateKpi(
hierarchyEdges = listOf(),
strict = true,
)

assertEquals(true, calcRelaxed is KpiCalculationResult.Empty)
assertEquals(true, calcStrict is KpiCalculationResult.Empty)
Expand Down Expand Up @@ -72,9 +75,9 @@ class AggregationKpiCalculationStrategyTest {
)

val calcRelaxed =
AggregationKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = false)
WeightedAverageKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = false)
val calcStrict =
AggregationKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = true)
WeightedAverageKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = true)

assert(calcRelaxed is KpiCalculationResult.Success)
assert(calcStrict is KpiCalculationResult.Success)
Expand Down
Loading

0 comments on commit 7d8d122

Please sign in to comment.