diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/TestExecutionService.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/TestExecutionService.kt index b95dc43c41..973153da6d 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/TestExecutionService.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/TestExecutionService.kt @@ -13,6 +13,8 @@ import com.saveourtool.save.entities.Test import com.saveourtool.save.entities.TestExecution import com.saveourtool.save.execution.TestExecutionFilters import com.saveourtool.save.test.TestDto +import com.saveourtool.save.utils.ScoreType +import com.saveourtool.save.utils.calculateScore import com.saveourtool.save.utils.debug import com.saveourtool.save.utils.error import com.saveourtool.save.utils.getLogger @@ -239,6 +241,8 @@ class TestExecutionService(private val testExecutionRepository: TestExecutionRep matchedChecks += counters.matchedChecks expectedChecks += counters.expectedChecks unexpectedChecks += counters.unexpectedChecks + + score = toDto().calculateScore(scoreType = ScoreType.F_MEASURE) } executionRepository.save(execution) } diff --git a/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/utils/ExecutionScoreUtils.kt b/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/utils/ExecutionScoreUtils.kt new file mode 100644 index 0000000000..b70cd02024 --- /dev/null +++ b/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/utils/ExecutionScoreUtils.kt @@ -0,0 +1,70 @@ +/** + * Utilities for execution score calculation + */ + +@file:Suppress( + "MatchingDeclarationName", + "MagicNumber", + "MAGIC_NUMBER", + "FILE_NAME_MATCH_CLASS" +) + +package com.saveourtool.save.utils + +import com.saveourtool.save.execution.ExecutionDto +import com.saveourtool.save.execution.TestingType + +/** + * Set of types for different ways of score calculation + */ +enum class ScoreType { + // TODO: what else algorithms do we need? + + /** Calculate score via F-measure. */ + F_MEASURE, + ; +} + +/** + * @return precision rate + */ +fun ExecutionDto.getPrecisionRate() = calculateRate(matchedChecks, matchedChecks + unexpectedChecks) ?: 0 + +/** + * @return recall rate + */ +fun ExecutionDto.getRecallRate() = calculateRate(matchedChecks, matchedChecks + unmatchedChecks) ?: 0 + +/** + * @param scoreType + * @return score according execution [type] and [scoreType] + */ +fun ExecutionDto.calculateScore(scoreType: ScoreType): Double = when (type) { + TestingType.CONTEST_MODE -> calculateScoreForContestMode(scoreType) + // TODO: how to calculate score for other types? + else -> 0.0 +} + +private fun ExecutionDto.calculateScoreForContestMode(scoreType: ScoreType): Double = when (scoreType) { + ScoreType.F_MEASURE -> calculateFmeasure() + else -> TODO("Invalid score type for contest mode!") +} + +private fun ExecutionDto.calculateFmeasure(): Double { + val denominator = getPrecisionRate() + getRecallRate() + return if (denominator == 0) { + 0.0 + } else { + (2 * getPrecisionRate() * getRecallRate()) / (getPrecisionRate() + getRecallRate()).toDouble() + } +} + +/** + * @param numerator + * @param denominator + * @return rate based on [numerator] and [denominator] + */ +fun calculateRate(numerator: Long, denominator: Long) = denominator.takeIf { it > 0 } + ?.run { numerator.toDouble() / denominator } + ?.let { it * 100 } + ?.toInt() diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ExecutionLabels.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ExecutionLabels.kt index 9a5edf7ede..f75288434e 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ExecutionLabels.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ExecutionLabels.kt @@ -12,6 +12,9 @@ import com.saveourtool.save.execution.ExecutionDto import com.saveourtool.save.execution.ExecutionStatus import com.saveourtool.save.frontend.externals.fontawesome.faRedo import com.saveourtool.save.frontend.externals.fontawesome.fontAwesomeIcon +import com.saveourtool.save.utils.calculateRate +import com.saveourtool.save.utils.getPrecisionRate +import com.saveourtool.save.utils.getRecallRate import csstype.ClassName import csstype.Width @@ -94,12 +97,12 @@ internal class ExecutionStatisticsValues(executionDto: ExecutionDto?) { this.failedTests = executionDto?.failedTests?.toString() ?: "0" this.runningTests = executionDto?.runningTests?.toString() ?: "0" this.passRate = executionDto - ?.let { calculateRate(it.passedTests, it.allTests) } + ?.let { "${calculateRate(it.passedTests, it.allTests)}" } ?: "0" this.precisionRate = executionDto ?.let { if (isAllApplicable(it.matchedChecks, it.unexpectedChecks)) { - calculateRate(it.matchedChecks, it.matchedChecks + it.unexpectedChecks) + "${it.getPrecisionRate()}" } else { "N/A" } @@ -108,7 +111,7 @@ internal class ExecutionStatisticsValues(executionDto: ExecutionDto?) { this.recallRate = executionDto ?.let { if (isAllApplicable(it.matchedChecks, it.unmatchedChecks)) { - calculateRate(it.matchedChecks, it.matchedChecks + it.unmatchedChecks) + "${it.getRecallRate()}" } else { "N/A" } @@ -117,12 +120,6 @@ internal class ExecutionStatisticsValues(executionDto: ExecutionDto?) { } private fun isAllApplicable(vararg values: Long): Boolean = values.all { !CountWarnings.isNotApplicable(it.toInt()) } - - private fun calculateRate(numerator: Long, denominator: Long) = denominator.takeIf { it > 0 } - ?.run { numerator.toDouble() / denominator } - ?.let { it * 100 } - ?.toInt() - ?.toString() } /**