From 6f44d67846a753b2c566e94e7c0c0ea305becb37 Mon Sep 17 00:00:00 2001
From: Jan-Niclas Struewer <j.n.struewer@gmail.com>
Date: Fri, 2 Aug 2024 16:24:07 +0200
Subject: [PATCH] breaking: backup commit to prepare merge from dev branch.
 Branch is not in a working state!

---
 .gitignore                                    |  1 +
 .../iem/app/kpi/service/KPIService.kt         |  3 +-
 .../AggregationKPICalculationStrategy.kt      | 61 ------------
 .../strategy/MaximumKPICalculationStrategy.kt | 26 -----
 .../strategy/RatioKPICalculationStrategy.kt   | 22 -----
 .../iem/kpiCalculator/core/KpiCalculator.kt   | 90 +++++++++++++++++-
 .../core/hierarchy/KPIHierarchyEdgeDto.kt     |  8 --
 .../core/hierarchy/KpiCalculationNode.kt      | 94 +++++++++++++------
 .../core/hierarchy/KpiHierarchyEdge.kt        |  6 ++
 .../AggregationKPICalculationStrategy.kt      | 51 ++++++++++
 .../core/strategy/KPICalculationStrategy.kt   |  7 --
 .../strategy/MaximumKPICalculationStrategy.kt | 32 +++++++
 .../strategy/RatioKPICalculationStrategy.kt   | 40 ++++++++
 .../RawValueKPICalculationStrategy.kt         | 10 --
 .../kpiCalculator/core/KpiCalculatorTest.kt   | 27 ++++++
 .../model/kpi/hierarchy/KpiHierarchy.kt       | 26 +++++
 .../model/kpi/hierarchy/KpiHierarchyTest.kt   | 13 ---
 17 files changed, 337 insertions(+), 180 deletions(-)
 delete mode 100644 app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/strategy/AggregationKPICalculationStrategy.kt
 delete mode 100644 app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/strategy/MaximumKPICalculationStrategy.kt
 delete mode 100644 app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/strategy/RatioKPICalculationStrategy.kt
 delete mode 100644 kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KPIHierarchyEdgeDto.kt
 create mode 100644 kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiHierarchyEdge.kt
 create mode 100644 kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/AggregationKPICalculationStrategy.kt
 delete mode 100644 kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/KPICalculationStrategy.kt
 create mode 100644 kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/MaximumKPICalculationStrategy.kt
 create mode 100644 kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/RatioKPICalculationStrategy.kt
 delete mode 100644 kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/RawValueKPICalculationStrategy.kt
 create mode 100644 kpi-calculator/core/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculatorTest.kt

diff --git a/.gitignore b/.gitignore
index 68465b42..021d4af0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ bin
 **/.DS_Store
 src/test/testResults/*
 app/backend/tools/db/cockroach-data
+tools/db
diff --git a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/service/KPIService.kt b/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/service/KPIService.kt
index 2055ca7f..2611f5b4 100644
--- a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/service/KPIService.kt
+++ b/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/service/KPIService.kt
@@ -2,12 +2,11 @@ package de.fraunhofer.iem.app.kpi.service
 
 import de.fraunhofer.iem.app.kpi.dto.KPITreeChildResponseDto
 import de.fraunhofer.iem.app.kpi.dto.KPITreeResponseDto
-import de.fraunhofer.iem.app.kpi.strategy.AggregationKPICalculationStrategy
 import de.fraunhofer.iem.app.logger.getLogger
 import de.fraunhofer.iem.app.repository.dto.RepositoryDetailsDto
 import de.fraunhofer.iem.app.tools.occmd.enumeration.Checks
 import de.fraunhofer.iem.app.tools.occmd.json.RawResultJson
-import de.fraunhofer.iem.kpiCalculator.core.hierarchy.KpiCalculationNode
+import de.fraunhofer.iem.kpiCalculator.core.strategy.AggregationKPICalculationStrategy
 import de.fraunhofer.iem.kpiCalculator.model.kpi.KpiId
 import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpiDto
 import org.springframework.stereotype.Service
diff --git a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/strategy/AggregationKPICalculationStrategy.kt b/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/strategy/AggregationKPICalculationStrategy.kt
deleted file mode 100644
index 06b807d3..00000000
--- a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/strategy/AggregationKPICalculationStrategy.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-package de.fraunhofer.iem.app.kpi.strategy
-
-import de.fraunhofer.iem.kpiCalculator.core.hierarchy.KPIHierarchyEdgeDto
-import de.fraunhofer.iem.kpiCalculator.core.strategy.KPICalculationStrategy
-
-class AggregationKPICalculationStrategy : KPICalculationStrategy {
-
-    /**
-     * This function calculates the aggregate sum of all given children.
-     * If a child is empty it is removed from the calculation and its
-     * corresponding edge weight is distributed evenly between the
-     * remaining children.
-     * The method returns the KPIs value as well as the updated
-     * KPIHierarchyEdgeDtos with the actual used weight.
-     */
-    override fun calculateKPI(children: List<KPIHierarchyEdgeDto>): Pair<Int, List<KPIHierarchyEdgeDto>> {
-//        if (children.none { !it.to.isEmpty() } || children.isEmpty()) {
-//            throw Exception("KPI aggregation of empty children can't be calculated.")
-//        }
-//
-//        val emptyChildren: List<KPIHierarchyEdgeDto> = children.filter { it.to.isEmpty() }
-//
-//        val notEmptyChildren = children.toMutableList()
-//        notEmptyChildren.removeAll(emptyChildren)
-//
-//        val additionalWeightPerElement: Double = if (notEmptyChildren.isNotEmpty()) {
-//            val unusedWeight = emptyChildren.sumOf { it.plannedWeight }
-//            unusedWeight / notEmptyChildren.size
-//        } else {
-//            0.0
-//        }
-//
-//        val weightedEdges: MutableList<KPIHierarchyEdgeDto> = emptyChildren
-//            .map {
-//                KPIHierarchyEdgeDto(
-//                    from = it.from,
-//                    to = it.to,
-//                    plannedWeight = it.plannedWeight,
-//                    actualWeight = 0.0
-//                )
-//            }.toMutableList()
-//
-//        var value = 0
-//
-//        notEmptyChildren.forEach { child ->
-//            val actualWeight = (child.plannedWeight + additionalWeightPerElement)
-//            weightedEdges.add(
-//                KPIHierarchyEdgeDto(
-//                    from = child.from,
-//                    to = child.to,
-//                    plannedWeight = child.plannedWeight,
-//                    actualWeight = actualWeight
-//                )
-//            )
-//            value += (child.to.getValue().toFloat() * actualWeight).toInt()
-//        }
-//
-//        return Pair(value, weightedEdges)
-        TODO()
-    }
-}
diff --git a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/strategy/MaximumKPICalculationStrategy.kt b/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/strategy/MaximumKPICalculationStrategy.kt
deleted file mode 100644
index 0b7ed318..00000000
--- a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/strategy/MaximumKPICalculationStrategy.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package de.fraunhofer.iem.app.kpi.strategy
-
-import de.fraunhofer.iem.kpiCalculator.core.hierarchy.KPIHierarchyEdgeDto
-import de.fraunhofer.iem.kpiCalculator.core.strategy.KPICalculationStrategy
-
-class MaximumKPICalculationStrategy : KPICalculationStrategy {
-    // TODO: Currently it's tailored to the maximum dependency vulnerability score, this should change in the future
-    @Suppress("MagicNumber")
-    override fun calculateKPI(children: List<KPIHierarchyEdgeDto>): Pair<Int, List<KPIHierarchyEdgeDto>> {
-//        if (children.none { !it.to.isEmpty() } || children.isEmpty()) {
-//            throw Exception("KPI maximum of empty children can't be calculated")
-//        }
-//        var maximum = 0
-//        for (child in children) {
-//            if (!child.to.isEmpty()) {
-//                val childValue = child.to.getValue()
-//                if (childValue > maximum) {
-//                    maximum = childValue
-//                }
-//            }
-//        }
-//        return Pair((100 - (maximum)), children)
-//    }
-        TODO()
-    }
-}
diff --git a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/strategy/RatioKPICalculationStrategy.kt b/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/strategy/RatioKPICalculationStrategy.kt
deleted file mode 100644
index 5b552b63..00000000
--- a/app/backend/src/main/kotlin/de/fraunhofer/iem/app/kpi/strategy/RatioKPICalculationStrategy.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package de.fraunhofer.iem.app.kpi.strategy
-
-import de.fraunhofer.iem.kpiCalculator.core.hierarchy.KPIHierarchyEdgeDto
-import de.fraunhofer.iem.kpiCalculator.core.strategy.KPICalculationStrategy
-
-class RatioKPICalculationStrategy : KPICalculationStrategy {
-    @Suppress("MagicNumber")
-    override fun calculateKPI(children: List<KPIHierarchyEdgeDto>): Pair<Int, List<KPIHierarchyEdgeDto>> {
-//        if (children.size != 2) {
-//            throw Exception("Requires exactly two children")
-//        }
-//        // TODO: this is a dirty workaround to fix a copy by reference bug
-//        val copiedChildren = children.toList()
-//        val firstValue = children[0].to.getValue()
-//        val secondValue = children[1].to.getValue()
-//        if (firstValue >= secondValue) {
-//            return Pair(((secondValue.toDouble() / firstValue.toDouble()) * 100).toInt(), copiedChildren)
-//        }
-//        return Pair(((firstValue.toDouble() / secondValue.toDouble()) * 100).toInt(), copiedChildren)
-        TODO()
-    }
-}
diff --git a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculator.kt b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculator.kt
index 5ffc7910..bf9fa0c8 100644
--- a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculator.kt
+++ b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculator.kt
@@ -1,8 +1,96 @@
 package de.fraunhofer.iem.kpiCalculator.core
 
+import de.fraunhofer.iem.kpiCalculator.core.hierarchy.KpiCalculationNode
+import de.fraunhofer.iem.kpiCalculator.model.kpi.KpiId
+import de.fraunhofer.iem.kpiCalculator.model.kpi.KpiStrategyId
+import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpiDto
+import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.KpiHierarchy
+import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.KpiNode
+import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.KpiResultNode
+
+sealed class KpiResult {}
+
 object KpiCalculator {
     //XXX: Setup Logger
 
-//    fun calculateKpis(root: KpiCalculationNode):
+    fun calculateKpis(hierarchy: KpiHierarchy, rawValueKpiDtos: List<RawValueKpiDto>): KpiResultNode {
+        val root = hierarchy.rootNode
+        val connectedHierarchyRoot = connectKpiHierarchyToRawValues(root, rawValueKpiDtos)
+        depthFirstTraversal(connectedHierarchyRoot) { it.calculateKpi() }
+        return KpiCalculationNode.to(connectedHierarchyRoot)
+    }
+
+    private fun depthFirstTraversal(
+        node: KpiCalculationNode,
+        seen: MutableSet<KpiCalculationNode> = mutableSetOf(),
+        action: (node: KpiCalculationNode) -> Unit
+    ) {
+        if (seen.contains(node)) {
+            return
+        }
+
+        node.hierarchyEdges
+            .forEach { child ->
+                depthFirstTraversal(node = child.target, seen = seen, action)
+            }
+
+        action(node)
+        seen.add(node)
+    }
+
+    private fun connectKpiHierarchyToRawValues(
+        node: KpiNode,
+        rawValueKpiDtos: List<RawValueKpiDto>
+    ): KpiCalculationNode {
+
+        val kindToValues = mutableMapOf<KpiId, MutableList<RawValueKpiDto>>()
+        KpiId.entries.forEach { kindToValues[it] = mutableListOf() }
+
+        rawValueKpiDtos.forEach {
+            kindToValues[it.kind]!!.add(it)
+        }
+
+        val calculationRoot = KpiCalculationNode.from(node)
+
+        depthFirstTraversal(
+            node = calculationRoot
+        ) { currentNode ->
+            // TODO: this is complicated as we don't know how many children we will have.
+            // this scenario might reappear again so we should come up with a better solution
+            if (
+                currentNode.kind == KpiId.MAXIMAL_VULNERABILITY &&
+                currentNode.hierarchyEdges.size == 1 &&
+                currentNode.hierarchyEdges.first().target.kind == KpiId.VULNERABILITY_SCORE &&
+                !kindToValues[KpiId.VULNERABILITY_SCORE].isNullOrEmpty()
+            ) {
+                val vulnerabilityCalcNodes = kindToValues[KpiId.VULNERABILITY_SCORE]?.map {
+                    val newNode = KpiCalculationNode(
+                        kind = KpiId.VULNERABILITY_SCORE,
+                        calculationStrategy = KpiStrategyId.RAW_VALUE_STRATEGY
+                    )
+                    newNode.setScore(it.score)
+                    newNode
+                }
+
+                if (!vulnerabilityCalcNodes.isNullOrEmpty()) {
+                    currentNode.clearChildren()
+                    val weights = 1.0 / vulnerabilityCalcNodes.size
+                    vulnerabilityCalcNodes.forEach {
+                        currentNode.addChild(it, weights)
+                    }
+
+                }
+            } else {
+                val correspondingRawValue = kindToValues[currentNode.kind]
+                if (!correspondingRawValue.isNullOrEmpty()) {
+                    if (correspondingRawValue.size > 1) {
+                        println("We only expect one RAW value for every KPI")
+                    }
+                    currentNode.setScore(correspondingRawValue.first().score)
+                }
+            }
 
+        }
+        return calculationRoot
+    }
 }
diff --git a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KPIHierarchyEdgeDto.kt b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KPIHierarchyEdgeDto.kt
deleted file mode 100644
index 388207c6..00000000
--- a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KPIHierarchyEdgeDto.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package de.fraunhofer.iem.kpiCalculator.core.hierarchy
-
-data class KPIHierarchyEdgeDto(
-    val from: KpiCalculationNode,
-    val to: KpiCalculationNode,
-    val plannedWeight: Double,
-    val actualWeight: Double = plannedWeight
-)
diff --git a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiCalculationNode.kt b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiCalculationNode.kt
index f03aa75f..0d76f230 100644
--- a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiCalculationNode.kt
+++ b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiCalculationNode.kt
@@ -1,48 +1,82 @@
 package de.fraunhofer.iem.kpiCalculator.core.hierarchy
 
-import de.fraunhofer.iem.kpiCalculator.core.strategy.KPICalculationStrategy
-import de.fraunhofer.iem.kpiCalculator.core.strategy.RawValueKPICalculationStrategy
+import de.fraunhofer.iem.kpiCalculator.core.strategy.AggregationKPICalculationStrategy
+import de.fraunhofer.iem.kpiCalculator.core.strategy.MaximumKPICalculationStrategy
+import de.fraunhofer.iem.kpiCalculator.core.strategy.RatioKPICalculationStrategy
 import de.fraunhofer.iem.kpiCalculator.model.kpi.KpiId
+import de.fraunhofer.iem.kpiCalculator.model.kpi.KpiStrategyId
+import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.KpiCalculationResult
+import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.KpiNode
+import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.KpiResultEdge
+import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.KpiResultNode
 
-sealed class KpiCalculationResult {
-    class Kpi(val kind: KpiId, val score: Int) : KpiCalculationResult()
-    class Error(val reason: String) : KpiCalculationResult()
-}
 
-// TODO: fixme
-open class KpiCalculationNode(val kind: KpiId, private val calculationStrategy: KPICalculationStrategy) {
+internal class KpiCalculationNode(val kind: KpiId, val calculationStrategy: KpiStrategyId) {
+
+    private var score: KpiCalculationResult = KpiCalculationResult.Empty
+    fun setScore(score: Int) {
+        this.score = KpiCalculationResult.Success(score)
+    }
 
-    private val _hierarchyEdges: MutableList<KPIHierarchyEdgeDto> = mutableListOf()
-    val hierarchyEdges: List<KPIHierarchyEdgeDto>
+    private val _hierarchyEdges: MutableList<KpiHierarchyEdge> = mutableListOf()
+    val hierarchyEdges: List<KpiHierarchyEdge>
         get() = _hierarchyEdges
 
-    fun hasChildren(): Boolean {
-        return hierarchyEdges.isNotEmpty()
+    fun addChild(node: KpiCalculationNode, weight: Double) {
+        _hierarchyEdges.add(KpiHierarchyEdge(node, weight))
     }
 
-    fun applyStrategy(): KpiCalculationResult {
-        return try {
-            val valueAndEdges = calculationStrategy.calculateKPI(this.hierarchyEdges)
-            val value = valueAndEdges.first
-            this._hierarchyEdges.clear()
-            this._hierarchyEdges.addAll(valueAndEdges.second)
+    fun clearChildren() {
+        _hierarchyEdges.clear()
+    }
 
-            KpiCalculationResult.Kpi(kind = this.kind, score = value)
-        } catch (exception: Exception) {
-            KpiCalculationResult.Error(exception.toString())
+    fun calculateKpi(): KpiCalculationResult {
+        val strategyData = hierarchyEdges.map {
+            Pair(it.target.score, it.weight)
         }
-    }
+        score = when (calculationStrategy) {
+            KpiStrategyId.RAW_VALUE_STRATEGY ->
+                score
+
+            KpiStrategyId.RATIO_STRATEGY ->
+                RatioKPICalculationStrategy.calculateKPI(strategyData)
 
+            KpiStrategyId.AGGREGATION_STRATEGY ->
+                AggregationKPICalculationStrategy.calculateKPI(strategyData)
 
-    fun addChildKPI(kpiCreateDto: KpiCalculationNode, weight: Double) {
-        val hierarchyEdge = KPIHierarchyEdgeDto(this, kpiCreateDto, weight)
-        this._hierarchyEdges.add(hierarchyEdge)
+            KpiStrategyId.MAXIMUM_STRATEGY ->
+                MaximumKPICalculationStrategy.calculateKPI(strategyData)
+        }
+
+        return score
     }
-}
 
+    companion object {
+        fun to(node: KpiCalculationNode): KpiResultNode {
+            return KpiResultNode(
+                kpiId = node.kind,
+                strategyType = node.calculationStrategy,
+                kpiResult = node.score,
+                children = node.hierarchyEdges.map {
+                    KpiResultEdge(
+                        target = to(it.target),
+                        weight = it.weight
+                    )
+                }
+            )
+        }
 
-class RawKpiCalculationNode(
-    score: Int,
-    kind: KpiId
-) : KpiCalculationNode(kind, RawValueKPICalculationStrategy(score))
+        fun from(node: KpiNode): KpiCalculationNode {
+            val calcNode = KpiCalculationNode(node.kpiId, calculationStrategy = node.strategyType)
+            val children = node.children.map { child ->
+                KpiHierarchyEdge(
+                    target = from(child.target),
+                    weight = child.weight
+                )
+            }
+            calcNode._hierarchyEdges.addAll(children)
 
+            return calcNode
+        }
+    }
+}
diff --git a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiHierarchyEdge.kt b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiHierarchyEdge.kt
new file mode 100644
index 00000000..ddb5b74b
--- /dev/null
+++ b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/hierarchy/KpiHierarchyEdge.kt
@@ -0,0 +1,6 @@
+package de.fraunhofer.iem.kpiCalculator.core.hierarchy
+
+internal data class KpiHierarchyEdge(
+    val target: KpiCalculationNode,
+    val weight: Double,
+)
diff --git a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/AggregationKPICalculationStrategy.kt b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/AggregationKPICalculationStrategy.kt
new file mode 100644
index 00000000..c4fefe35
--- /dev/null
+++ b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/AggregationKPICalculationStrategy.kt
@@ -0,0 +1,51 @@
+package de.fraunhofer.iem.kpiCalculator.core.strategy
+
+import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.KpiCalculationResult
+
+
+object AggregationKPICalculationStrategy {
+    /**
+     * This function calculates the aggregate sum of all given children.
+     * If a child is empty it is removed from the calculation and its
+     * corresponding edge weight is distributed evenly between the
+     * remaining children.
+     * The method returns the KPIs value as well as the updated
+     * KPIHierarchyEdgeDtos with the actual used weight.
+     */
+    fun calculateKPI(childScores: List<Pair<KpiCalculationResult, Double>>): KpiCalculationResult {
+
+        if (childScores.isEmpty()) {
+            return KpiCalculationResult.Empty
+        }
+
+        val successScores = childScores.filter { it.first is KpiCalculationResult.Success }
+        val failed = childScores
+            .filter { it.first !is KpiCalculationResult.Success }
+        val missingEdgeWeights = failed.sumOf { it.second }
+
+        val additionalWeight =
+            if (missingEdgeWeights == 0.0)
+                0.0
+            else
+                missingEdgeWeights / successScores.size
+
+        val aggregation =
+            if (successScores.isEmpty())
+                0
+            else
+                successScores.sumOf {
+                    (it.first as KpiCalculationResult.Success).score * (it.second + additionalWeight)
+                }.toInt()
+
+
+        if (failed.isNotEmpty()) {
+            return KpiCalculationResult.Incomplete(
+                score = aggregation,
+                reason = "There were ${failed.size} elements missing during aggregation.",
+                missingWeights = missingEdgeWeights
+            )
+        }
+
+        return KpiCalculationResult.Success(score = aggregation)
+    }
+}
diff --git a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/KPICalculationStrategy.kt b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/KPICalculationStrategy.kt
deleted file mode 100644
index 516ea616..00000000
--- a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/KPICalculationStrategy.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package de.fraunhofer.iem.kpiCalculator.core.strategy
-
-import de.fraunhofer.iem.kpiCalculator.core.hierarchy.KPIHierarchyEdgeDto
-
-fun interface KPICalculationStrategy {
-    fun calculateKPI(children: List<KPIHierarchyEdgeDto>): Pair<Int, List<KPIHierarchyEdgeDto>>
-}
diff --git a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/MaximumKPICalculationStrategy.kt b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/MaximumKPICalculationStrategy.kt
new file mode 100644
index 00000000..7fe7cf05
--- /dev/null
+++ b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/MaximumKPICalculationStrategy.kt
@@ -0,0 +1,32 @@
+package de.fraunhofer.iem.kpiCalculator.core.strategy
+
+import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.KpiCalculationResult
+
+object MaximumKPICalculationStrategy {
+    fun calculateKPI(childScores: List<Pair<KpiCalculationResult, Double>>): KpiCalculationResult {
+
+        if (childScores.isEmpty()) {
+            return KpiCalculationResult.Empty
+        }
+
+        val successScores = childScores.filterIsInstance<KpiCalculationResult.Success>()
+
+        val max =
+            if (successScores.isEmpty())
+                0
+            else
+                successScores.maxOf { it.score }
+
+        if (childScores.size != successScores.size) {
+            val failed = childScores.filter { it.first !is KpiCalculationResult.Success }
+            val missingWeights = failed.sumOf { it.second }
+            return KpiCalculationResult.Incomplete(
+                score = max,
+                reason = "Missing ${failed.size} elements.",
+                missingWeights = missingWeights
+            )
+        }
+
+        return KpiCalculationResult.Success(score = max)
+    }
+}
diff --git a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/RatioKPICalculationStrategy.kt b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/RatioKPICalculationStrategy.kt
new file mode 100644
index 00000000..16aa0128
--- /dev/null
+++ b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/RatioKPICalculationStrategy.kt
@@ -0,0 +1,40 @@
+package de.fraunhofer.iem.kpiCalculator.core.strategy
+
+import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.KpiCalculationResult
+
+object RatioKPICalculationStrategy {
+    /**
+     * Returns smallerValue / biggerValue, regardless in which order the values are given.
+     */
+    fun calculateKPI(childScores: List<Pair<KpiCalculationResult, Double>>): KpiCalculationResult {
+
+        val successScores =
+            childScores.map { it.first }.filterIsInstance<KpiCalculationResult.Success>()
+
+        if (successScores.size != 2) {
+            return KpiCalculationResult.Error(
+                "Ratio calculation strategy called " +
+                    "with ${successScores.size} valid elements, which is illegal."
+            )
+        }
+
+        val firstValue = successScores.first().score
+        val secondValue = successScores[1].score
+
+        if (firstValue == secondValue && secondValue == 0) {
+            return KpiCalculationResult.Success(0)
+        }
+        try {
+            if (firstValue >= secondValue) {
+                return KpiCalculationResult.Success(
+                    score = (secondValue.toDouble() / firstValue.toDouble()).toInt() * 100
+                )
+            }
+            return KpiCalculationResult.Success(
+                score = ((firstValue.toDouble() / secondValue.toDouble()) * 100).toInt()
+            )
+        } catch (e: Exception) {
+            return KpiCalculationResult.Error(e.message ?: e.toString())
+        }
+    }
+}
diff --git a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/RawValueKPICalculationStrategy.kt b/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/RawValueKPICalculationStrategy.kt
deleted file mode 100644
index 86c0a134..00000000
--- a/kpi-calculator/core/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/core/strategy/RawValueKPICalculationStrategy.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package de.fraunhofer.iem.kpiCalculator.core.strategy
-
-import de.fraunhofer.iem.kpiCalculator.core.hierarchy.KPIHierarchyEdgeDto
-
-class RawValueKPICalculationStrategy(private val value: Int) :
-    KPICalculationStrategy {
-    override fun calculateKPI(children: List<KPIHierarchyEdgeDto>): Pair<Int, List<KPIHierarchyEdgeDto>> {
-        return Pair(this.value, children)
-    }
-}
diff --git a/kpi-calculator/core/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculatorTest.kt b/kpi-calculator/core/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculatorTest.kt
new file mode 100644
index 00000000..dadb1a17
--- /dev/null
+++ b/kpi-calculator/core/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/core/KpiCalculatorTest.kt
@@ -0,0 +1,27 @@
+package de.fraunhofer.iem.kpiCalculator.core
+
+import de.fraunhofer.iem.kpiCalculator.model.kpi.KpiId
+import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpiDto
+import de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy.DefaultHierarchy
+import org.junit.jupiter.api.Test
+
+class KpiCalculatorTest {
+
+    @Test
+    fun calculateKpis() {
+        val rawValueKpiDtos = listOf(
+            RawValueKpiDto(kind = KpiId.VULNERABILITY_SCORE, score = 8),
+            RawValueKpiDto(kind = KpiId.VULNERABILITY_SCORE, score = 9),
+            RawValueKpiDto(kind = KpiId.CHECKED_IN_BINARIES, score = 100),
+            RawValueKpiDto(kind = KpiId.COMMENTS_IN_CODE, score = 80),
+            RawValueKpiDto(kind = KpiId.NUMBER_OF_COMMITS, score = 90),
+            RawValueKpiDto(kind = KpiId.IS_DEFAULT_BRANCH_PROTECTED, score = 100),
+            RawValueKpiDto(kind = KpiId.NUMBER_OF_SIGNED_COMMITS, score = 80),
+            RawValueKpiDto(kind = KpiId.SECRETS, score = 80),
+            RawValueKpiDto(kind = KpiId.SAST_USAGE, score = 80),
+            RawValueKpiDto(kind = KpiId.DOCUMENTATION_INFRASTRUCTURE, score = 80),
+        )
+        val res = KpiCalculator.calculateKpis(DefaultHierarchy.get(), rawValueKpiDtos)
+        println(res)
+    }
+}
diff --git a/kpi-calculator/model/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/model/kpi/hierarchy/KpiHierarchy.kt b/kpi-calculator/model/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/model/kpi/hierarchy/KpiHierarchy.kt
index be3d6900..16368bd0 100644
--- a/kpi-calculator/model/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/model/kpi/hierarchy/KpiHierarchy.kt
+++ b/kpi-calculator/model/src/main/kotlin/de/fraunhofer/iem/kpiCalculator/model/kpi/hierarchy/KpiHierarchy.kt
@@ -8,6 +8,7 @@ val SCHEMA_VERSIONS: Array<String> = arrayOf(
     "1.0.0"
 ).sortedArray()
 
+//TODO: add Hierarchy Validator
 @Serializable
 data class KpiHierarchy private constructor(val rootNode: KpiNode, val schemaVersion: String) {
     companion object {
@@ -21,3 +22,28 @@ data class KpiNode(val kpiId: KpiId, val strategyType: KpiStrategyId, val childr
 @Serializable
 data class KpiEdge(val target: KpiNode, val weight: Double)
 
+@Serializable
+data class KpiResultHierarchy private constructor(val rootNode: KpiNode, val schemaVersion: String) {
+    companion object {
+        fun create(rootNode: KpiNode) = KpiResultHierarchy(rootNode, SCHEMA_VERSIONS.last())
+    }
+}
+
+@Serializable
+data class KpiResultNode(
+    val kpiId: KpiId,
+    val kpiResult: KpiCalculationResult,
+    val strategyType: KpiStrategyId,
+    val children: List<KpiResultEdge>
+)
+
+@Serializable
+data class KpiResultEdge(val target: KpiResultNode, val weight: Double)
+
+@Serializable
+sealed class KpiCalculationResult {
+    data class Success(val score: Int) : KpiCalculationResult()
+    data class Error(val reason: String) : KpiCalculationResult()
+    data class Incomplete(val score: Int, val missingWeights: Double, val reason: String) : KpiCalculationResult()
+    data object Empty : KpiCalculationResult()
+}
diff --git a/kpi-calculator/model/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/model/kpi/hierarchy/KpiHierarchyTest.kt b/kpi-calculator/model/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/model/kpi/hierarchy/KpiHierarchyTest.kt
index 014b4c5e..b9932654 100644
--- a/kpi-calculator/model/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/model/kpi/hierarchy/KpiHierarchyTest.kt
+++ b/kpi-calculator/model/src/test/kotlin/de/fraunhofer/iem/kpiCalculator/model/kpi/hierarchy/KpiHierarchyTest.kt
@@ -2,7 +2,6 @@ package de.fraunhofer.iem.kpiCalculator.model.kpi.hierarchy
 
 import de.fraunhofer.iem.kpiCalculator.model.kpi.KpiId
 import de.fraunhofer.iem.kpiCalculator.model.kpi.KpiStrategyId
-import de.fraunhofer.iem.kpiCalculator.model.kpi.RawValueKpiDto
 import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
 import org.junit.jupiter.api.Test
@@ -45,16 +44,4 @@ class KpiHierarchyTest {
         }
     }
 
-    @Test
-    fun connectHierarchyAndData() {
-
-        val rawValueKpis = listOf(
-            RawValueKpiDto(
-                kind = KpiId.COMMENTS_IN_CODE,
-                score = 40
-            )
-        )
-        val hierarchy = DefaultHierarchy.get()
-        println(KpiHierarchy.isValid(hierarchy))
-    }
 }
-- 
GitLab