From c47948b052b833f5c16709e0a98448078e5bef39 Mon Sep 17 00:00:00 2001 From: mamunozgil <miguel.munoz-gil@hft-stuttgart.de> Date: Thu, 9 Jan 2025 12:28:18 +0100 Subject: [PATCH] Added volumes for the backend host --- .../dtabackend/utils/DockerUtil.java | 65 ++- .../dtabackend/utils/ExecuteTestUtil.java | 382 +++++++----------- 2 files changed, 214 insertions(+), 233 deletions(-) diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/DockerUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/DockerUtil.java index bf2d598..28510e0 100644 --- a/src/main/java/de/hftstuttgart/dtabackend/utils/DockerUtil.java +++ b/src/main/java/de/hftstuttgart/dtabackend/utils/DockerUtil.java @@ -5,6 +5,9 @@ import com.github.dockerjava.api.command.CreateContainerResponse; import com.github.dockerjava.api.exception.DockerException; import com.github.dockerjava.api.model.Bind; import com.github.dockerjava.api.model.HostConfig; +import com.github.dockerjava.api.model.Mount; +import com.github.dockerjava.api.model.MountType; +import com.github.dockerjava.api.model.Volume; import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientConfig; import com.github.dockerjava.core.DockerClientImpl; @@ -15,6 +18,8 @@ import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import java.io.IOException; +import java.util.Arrays; +import java.util.stream.Collectors; @Component public class DockerUtil { @@ -37,7 +42,7 @@ public class DockerUtil { dockerClient = DockerClientImpl.getInstance(dockerClientConfig, httpClient); } - public int runContainer(String image, Bind... binds) throws InterruptedException, IOException + public int runContainerWithBinds(String image, Bind... binds) throws InterruptedException, IOException { LOG.debug(String.format("pull image: %s", image)); try { @@ -87,4 +92,62 @@ public class DockerUtil { return ret; } + + public int runContainerWithVolumes(String image, Volume... volumes) throws InterruptedException, IOException { + LOG.debug(String.format("Pulling image: %s", image)); + try { + dockerClient.pullImageCmd(image) + .start() + .awaitCompletion() + .close(); + } catch (DockerException e) { + LOG.error(String.format( + "Pulling Docker image %s failed with %s, trying with local image", + image, + e.getMessage())); + } + + LOG.debug("Creating container"); + CreateContainerResponse containerResponse; + try { + // Prepare the host configuration with volumes + HostConfig hostConfig = HostConfig.newHostConfig() + .withMounts( + Arrays.stream(volumes) + .map(volume -> new Mount().withTarget(volume.getPath()).withType(MountType.VOLUME)) + .collect(Collectors.toList()) + ); + + // Create the container with the configured volumes + containerResponse = dockerClient.createContainerCmd("testcontainer") + .withImage(image) + .withHostConfig(hostConfig) + .exec(); + } catch (DockerException e) { + LOG.error(String.format( + "Creating Docker Testrunner container failed with %s", e.getMessage())); + throw e; + } + + LOG.debug(String.format("Container created: %s", containerResponse.getId())); + + LOG.debug(String.format("Starting container %s", containerResponse.getId())); + dockerClient.startContainerCmd(containerResponse.getId()).exec(); + + LOG.debug(String.format("Waiting for completion of container %s", containerResponse.getId())); + int ret = dockerClient + .waitContainerCmd(containerResponse.getId()) + .start() + .awaitCompletion() + .awaitStatusCode(); + LOG.debug(String.format("Container completed with status %d", ret)); + + LOG.debug(String.format("Deleting container %s", containerResponse.getId())); + dockerClient.removeContainerCmd(containerResponse.getId()) + .withRemoveVolumes(true) + .exec(); + + return ret; +} + } diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java index e8bf1df..f35c69a 100644 --- a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java +++ b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java @@ -3,7 +3,6 @@ package de.hftstuttgart.dtabackend.utils; import com.fasterxml.jackson.core.exc.StreamReadException; import com.fasterxml.jackson.databind.DatabindException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.dockerjava.api.model.Bind; import com.github.dockerjava.api.model.Volume; import de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile; @@ -31,12 +30,11 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; @Component -public class ExecuteTestUtil { +public class ExecuteTestUtil { private static final Logger LOG = LogManager.getLogger(ExecuteTestUtil.class); private final DockerUtil dockerUtil; private final String assignmentBasePath; - private final Path testTmpPathHost; public ExecuteTestUtil( Environment env, @@ -49,281 +47,201 @@ public class ExecuteTestUtil { env.getProperty("data.dir"), env.getProperty("data.dir.test.folder.name")); this.assignmentBasePath = p.toAbsolutePath().toString(); - - // set path of temporary directory on host _and_ inside our container, _must_ be identical - this.testTmpPathHost = Paths.get(env.getProperty("host.tests.tmp.dir")); } public ResultSummary runTests(String assignmentId, Path workDirectory) throws IOException, InterruptedException { - // define paths for the test, the submission and where the result is to be expected afterwards - Path testPath = Paths.get(workDirectory.toAbsolutePath().toString(), "/test"); - Path srcPath = Paths.get(workDirectory.toAbsolutePath().toString(), "/src"); - Path resultPath = Paths.get(workDirectory.toAbsolutePath().toString(), "/result"); + // Define paths for the test, the submission, and where the result is expected afterwards + Path testPath = Paths.get("/data/test"); // Volume mount path in the container + Path srcPath = Paths.get("/data/src"); // Volume mount path in the container + Path resultPath = Paths.get("/data/result"); // Volume mount path in the container - // clone stored test to tmpdir - LOG.debug("copying pre-downloaded unitttest repo"); + // Clone stored test to testPath + LOG.debug("Copying pre-downloaded unit test repo"); FileUtil.copyFolder( Paths.get(assignmentBasePath, assignmentId), testPath); - LOG.debug("copying exercise manifest from: %s in testPath: %s", - assignmentBasePath + assignmentId + "_checkout", - testPath.toString() ); - Files.copy(Paths.get( + LOG.debug("Copying exercise manifest"); + Files.copy(Paths.get( assignmentBasePath, assignmentId + "_checkout", - CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME), - Paths.get( - testPath.toString(), - CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME)); + CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME), + Paths.get( + testPath.toString(), + CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME)); - LOG.debug("copy test config"); + LOG.debug("Copying test config"); Files.copy( Paths.get(assignmentBasePath, assignmentId + ".txt"), Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt")); Files.createDirectory(resultPath); - LOG.info("reading test config"); + LOG.info("Reading test config"); Matcher config = RegexUtil.extractConfig( new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile()), Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX)); - String image=""; - if(config==null) - { - config = RegexUtil.extractConfig( - new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile()), Pattern.compile(RegexUtil.TESTCONFIGREGEX)); - if(config==null) - { - throw new RuntimeException("couldn't find repo config for unittest image extraction"); - } - image=config.group(4); - } - else - { - image=config.group(5); + String image = ""; + if (config == null) { + config = RegexUtil.extractConfig( + new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile()), Pattern.compile(RegexUtil.TESTCONFIGREGEX)); + if (config == null) { + throw new RuntimeException("Couldn't find repo config for unit test image extraction"); + } + image = config.group(4); + } else { + image = config.group(5); } - // define the paths to mount as Binds from Host to the test-container - Path testPathHost = Paths.get( - testTmpPathHost.toAbsolutePath().toString(), - workDirectory.getName(workDirectory.getNameCount()-1).toString(), - testPath.getName(testPath.getNameCount()-1).toString() - ); - Path srcPathHost = Paths.get( - testTmpPathHost.toAbsolutePath().toString(), - workDirectory.getName(workDirectory.getNameCount()-1).toString(), - srcPath.getName(srcPath.getNameCount()-1).toString() - ); - Path resultPathHost = Paths.get( - testTmpPathHost.toAbsolutePath().toString(), - workDirectory.getName(workDirectory.getNameCount()-1).toString(), - resultPath.getName(resultPath.getNameCount()-1).toString() - ); - // start test-container with professor given image and bind mounts for test, submission and result + // Start the test-container with professor-given image and volume mounts for test, submission, and result dockerUtil.runContainer( image, - new Bind(testPathHost.toAbsolutePath().toString(), new Volume("/data/test")), - new Bind(srcPathHost.toAbsolutePath().toString(), new Volume("/data/src")), - new Bind(resultPathHost.toAbsolutePath().toString(), new Volume("/data/result")) + new Volume("/data/test"), + new Volume("/data/src"), + new Volume("/data/result") ); - ResultSummary resultSummary = generateResult(assignmentId, resultPath, testPathHost); + ResultSummary resultSummary = generateResult(assignmentId, resultPath, testPath); return resultSummary; } - private ResultSummary generateResult(String assignmentId, Path resultPath, Path testPathHost) - throws IOException, StreamReadException, DatabindException, MalformedURLException { - // define expected result file + private ResultSummary generateResult(String assignmentId, Path resultPath, Path testPath) + throws IOException, StreamReadException, DatabindException, MalformedURLException { + // Define expected result file File resultFile = Paths.get(resultPath.toAbsolutePath().toString(), "result.json").toFile(); - // check if result file is there + // Check if result file is there if (!resultFile.exists() || !resultFile.isFile()) { LOG.error(String.format("Could not find result file in %s", resultFile.getAbsolutePath())); - throw new RuntimeException("no resultfile found"); + throw new RuntimeException("No result file found"); } - LOG.debug("parse results json"); + LOG.debug("Parse results JSON"); ObjectMapper objectMapper = new ObjectMapper(); ResultSummary resultSummary = objectMapper.readValue(resultFile.toURI().toURL(), ResultSummary.class); - LOG.debug("result json returned time "+ resultSummary.timestamp + " with "+resultSummary.results.size()+ " test results."); + LOG.debug("Result JSON returned time " + resultSummary.timestamp + " with " + resultSummary.results.size() + " test results."); - LOG.info("Checking for optional test competency profile information for paedagogical agent functionality..."); - List<TestCompetencyProfile> testCompetencyProfiles=CompetencyAssessmentUtil.readTestCompetencyProfiles(testPathHost, CompetencyAssessmentUtil.TEST_COMPETENCY_MANIFEST_FILE_NAME); + LOG.info("Checking for optional test competency profile information for pedagogical agent functionality..."); + List<TestCompetencyProfile> testCompetencyProfiles = CompetencyAssessmentUtil.readTestCompetencyProfiles(testPath, CompetencyAssessmentUtil.TEST_COMPETENCY_MANIFEST_FILE_NAME); LOG.debug(String.format( - "Reading Test Competency Profiles: basePath=%s, fileName=%s", - testPathHost, - CompetencyAssessmentUtil.TEST_COMPETENCY_MANIFEST_FILE_NAME - )); - if(testCompetencyProfiles!=null) { - LOG.info("Found optional test competency profiles, generating agent profile data..."); - resultSummary.overallTestCompetencyProfile=CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumTestCompetencyProfiles(testCompetencyProfiles)); - resultSummary.successfulTestCompetencyProfile=CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, true)); - - LOG.info("Checking for optional exercise competency profile information for paedagogical agent exercise recommendation functionality..."); - //testPathHost or assignmentBasePath - Path exerciseManifestFile = Paths.get(testPathHost.toString(), CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME); - LOG.debug(String.format( - "Constructing Path for exercise manifest: testPath=%s, fileName=%s -> Resulting Path=%s", - testPathHost.toString(), - CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME, - exerciseManifestFile.toString() - )); - if (Files.exists(exerciseManifestFile)) { - LOG.info("Found optional exercise competency profiles, generating recommendations..."); - resultSummary.recommendations = recommendNextExercises(assignmentId, testPathHost, testCompetencyProfiles, resultSummary); - } + "Reading Test Competency Profiles: basePath=%s, fileName=%s", + testPath, + CompetencyAssessmentUtil.TEST_COMPETENCY_MANIFEST_FILE_NAME + )); + if (testCompetencyProfiles != null) { + LOG.info("Found optional test competency profiles, generating agent profile data..."); + resultSummary.overallTestCompetencyProfile = CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumTestCompetencyProfiles(testCompetencyProfiles)); + resultSummary.successfulTestCompetencyProfile = CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, true)); + + LOG.info("Checking for optional exercise competency profile information for pedagogical agent exercise recommendation functionality..."); + Path exerciseManifestFile = Paths.get(testPath.toString(), CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME); + LOG.debug(String.format( + "Constructing Path for exercise manifest: testPath=%s, fileName=%s -> Resulting Path=%s", + testPath.toString(), + CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME, + exerciseManifestFile.toString() + )); + if (Files.exists(exerciseManifestFile)) { + LOG.info("Found optional exercise competency profiles, generating recommendations..."); + resultSummary.recommendations = recommendNextExercises(assignmentId, testPath, testCompetencyProfiles, resultSummary); + } } - return resultSummary; - } + return resultSummary; + } -/* - * exercise recommendation part - */ -public List<Recommendation> recommendNextExercises(String assignmentId, Path testPathHost, List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary) - throws FileNotFoundException { - LOG.debug("Starting recommendNextExercises with assignmentId: {}", assignmentId); - - // fetch repo url from original test upload - Pattern pattern = Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX); - LOG.debug("Compiled regex pattern for DTA_TESTCONFIGREGEX."); - - File file = Paths.get(assignmentBasePath, assignmentId + ".txt").toFile(); - LOG.debug("Resolved file path for assignmentId {}: {}", assignmentId, file.getAbsolutePath()); - - FileInputStream configFileStream = new FileInputStream(file); - LOG.debug("Opened FileInputStream for file: {}", file.getAbsolutePath()); - - Matcher config = RegexUtil.extractConfig(configFileStream, pattern); - LOG.debug("Extracted configuration using regex pattern."); - - String testRepoURL = config.group(1) + config.group(4); - LOG.debug("Constructed testRepoURL: {}", testRepoURL); - - List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = CompetencyAssessmentUtil.readExerciseCompetencyProfiles( - testPathHost, CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME); - LOG.debug("Read exercise competency profiles from path: {}", testPathHost.resolve(CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME)); - - int currentTopicIndex = 0; - float currentDifficulty = 0.0f; - - // build course topic order - LOG.debug("Building course topic order."); - Map<String, Integer> topicOrder = new HashMap<>(); - int order = 1; - for (ExerciseCompetencyProfile e : exerciseCompetencyProfiles) { - if (!topicOrder.containsKey(e.exerciseTopicName)) { - topicOrder.put(e.exerciseTopicName, order++); - LOG.debug("Added topic {} to topicOrder with order {}", e.exerciseTopicName, order - 1); + /* + * exercise recommendation part + */ + public List<Recommendation> recommendNextExercises(String assignmentId, Path testPathHost, List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary) + throws FileNotFoundException { + // fetch repo url from original test upload + Pattern pattern = Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX); + File file = Paths.get(assignmentBasePath, assignmentId + ".txt").toFile(); + FileInputStream configFileStream = new FileInputStream(file); + Matcher config = RegexUtil.extractConfig(configFileStream, pattern); + String testRepoURL = config.group(1); + + List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = CompetencyAssessmentUtil.readExerciseCompetencyProfiles(testPathHost, CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME); + + int currentTopicIndex=0; + float currentDifficulty=0.0f; + + //build course topic order + Map<String, Integer> topicOrder = new HashMap<>(); + int order = 1; + for (ExerciseCompetencyProfile e : exerciseCompetencyProfiles) { + if (!topicOrder.containsKey(e.exerciseTopicName)) { + topicOrder.put(e.exerciseTopicName, order++); + } + if (e.exerciseURL.equals(testRepoURL)) { + currentTopicIndex = order; + currentDifficulty = e.difficulty; + } } - if (e.exerciseURL.equals(testRepoURL)) { - currentTopicIndex = order; - currentDifficulty = e.difficulty; - LOG.debug("Matched current testRepoURL to topic: {}, index: {}, difficulty: {}", e.exerciseTopicName, currentTopicIndex, currentDifficulty); + + //filter exercises according to success + float[] unsuccessful = CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, false); + List<ExerciseCompetencyProfile> filteredExercises = filterExercisesByTopicsAndDifficulty( + exerciseCompetencyProfiles, topicOrder, currentTopicIndex, testRepoURL, + currentDifficulty, unsuccessful, resultSummary); + + //compute recommendations + List<Recommendation> recommendedExercises = new ArrayList<>(); + for (ExerciseCompetencyProfile exerciseProfile : filteredExercises) { + Recommendation recommendation = new Recommendation(exerciseProfile.exerciseTopicName, exerciseProfile.exerciseURL, exerciseProfile.exerciseName, + exerciseProfile.difficulty, calculateScore(exerciseProfile, unsuccessful, topicOrder, currentDifficulty)); + recommendedExercises.add(recommendation); + LOG.info("Recommending exercise "+recommendation.topic+"/"+recommendation.exerciseName+" with score "+recommendation.score); } + //sort the recommendations for successful or resilient learners, otherwise reverse in display + recommendedExercises.stream().sorted(Recommendation.COMPARE_BY_SCORE).collect(Collectors.toList()); + + return recommendedExercises; } - // filter exercises according to success - LOG.debug("Filtering exercises according to success."); - float[] unsuccessful = CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, false); - LOG.debug("Computed unsuccessful competency profile: {}", unsuccessful); - - List<ExerciseCompetencyProfile> filteredExercises = filterExercisesByTopicsAndDifficulty( - exerciseCompetencyProfiles, topicOrder, currentTopicIndex, testRepoURL, - currentDifficulty, unsuccessful, resultSummary); - LOG.debug("Filtered exercises count: {}", filteredExercises.size()); + public static List<ExerciseCompetencyProfile> filterExercisesByTopicsAndDifficulty(List<ExerciseCompetencyProfile> exerciseCompetencyProfiles, + Map<String, Integer> topicOrder, int currentTopicIndex, String testRepoURL, float currentDifficulty, float[] unsuccessful, + ResultSummary resultSummary) { + //filter out all advanced topics in any case + //option for later: include next topic if fullsuccess and current difficulty == max difficulty + List<ExerciseCompetencyProfile> filteredExercises = exerciseCompetencyProfiles.stream() + .filter(testProfile -> topicOrder.get(testProfile.exerciseTopicName) <= currentTopicIndex) + .collect(Collectors.toList()); + + //filter by difficulty according to success + if (isFullSuccess(unsuccessful)) { + filteredExercises = filteredExercises.stream().filter(profile -> profile.difficulty >= currentDifficulty && !testRepoURL.equals(profile.exerciseURL)).collect(Collectors.toList()); + } else { + filteredExercises = filteredExercises.stream().filter(profile -> profile.difficulty <= currentDifficulty).collect(Collectors.toList()); + } - // compute recommendations - LOG.debug("Computing recommendations from filtered exercises."); - List<Recommendation> recommendedExercises = new ArrayList<>(); - for (ExerciseCompetencyProfile exerciseProfile : filteredExercises) { - Recommendation recommendation = new Recommendation( - exerciseProfile.exerciseTopicName, exerciseProfile.exerciseURL, exerciseProfile.exerciseName, - exerciseProfile.difficulty, calculateScore(exerciseProfile, unsuccessful, topicOrder, currentDifficulty)); - recommendedExercises.add(recommendation); - LOG.info("Recommending exercise {}/{} with score {}", recommendation.topic, recommendation.exerciseName, recommendation.score); + return filteredExercises; + } + + public static boolean isFullSuccess(float[] unsuccessful) { + for (float value : unsuccessful) { + if (value != 0.0f) { + return false; + } + } + return true; } - // sort the recommendations for successful or resilient learners, otherwise reverse in display - LOG.debug("Sorting recommendations."); - recommendedExercises.stream().sorted(Recommendation.COMPARE_BY_SCORE).collect(Collectors.toList()); - - LOG.debug("Completed recommendNextExercises with {} recommendations.", recommendedExercises.size()); - return recommendedExercises; -} - - public static List<ExerciseCompetencyProfile> filterExercisesByTopicsAndDifficulty( - List<ExerciseCompetencyProfile> exerciseCompetencyProfiles, - Map<String, Integer> topicOrder, int currentTopicIndex, String testRepoURL, - float currentDifficulty, float[] unsuccessful, ResultSummary resultSummary) { - - LOG.debug("Starting filterExercisesByTopicsAndDifficulty with currentTopicIndex: {}, currentDifficulty: {}, testRepoURL: {}", - currentTopicIndex, currentDifficulty, testRepoURL); - - // Filter out all advanced topics in any case - List<ExerciseCompetencyProfile> filteredExercises = exerciseCompetencyProfiles.stream() - .filter(testProfile -> topicOrder.get(testProfile.exerciseTopicName) <= currentTopicIndex) - .collect(Collectors.toList()); - LOG.debug("Filtered exercises by topic index. Remaining exercises count: {}", filteredExercises.size()); - - // Filter by difficulty according to success - if (isFullSuccess(unsuccessful)) { - LOG.debug("Detected full success, filtering exercises with difficulty >= {} and excluding current testRepoURL.", currentDifficulty); - filteredExercises = filteredExercises.stream() - .filter(profile -> profile.difficulty >= currentDifficulty && !testRepoURL.equals(profile.exerciseURL)) - .collect(Collectors.toList()); - } else { - LOG.debug("Detected partial success, filtering exercises with difficulty <= {}.", currentDifficulty); - filteredExercises = filteredExercises.stream() - .filter(profile -> profile.difficulty <= currentDifficulty) - .collect(Collectors.toList()); - } - - LOG.debug("Filtered exercises count after difficulty filter: {}", filteredExercises.size()); - return filteredExercises; -} - -public static boolean isFullSuccess(float[] unsuccessful) { - LOG.debug("Checking for full success. Unsuccessful array: {}", unsuccessful); - for (float value : unsuccessful) { - if (value != 0.0f) { - LOG.debug("Found non-zero value in unsuccessful array: {}. Returning false for full success.", value); - return false; - } - } - LOG.debug("All values in unsuccessful array are zero. Returning true for full success."); - return true; -} - -public static float calculateScore(ExerciseCompetencyProfile exerciseProfile, float[] unsuccessful, - Map<String, Integer> topicOrder, float currentDifficulty) { - LOG.debug("Starting calculateScore for exercise: {}, difficulty: {}, currentDifficulty: {}", - exerciseProfile.exerciseName, exerciseProfile.difficulty, currentDifficulty); - - float score = 1.0f; - LOG.debug("Initial score set to 1.0"); - - // Competency profile difference to not fully achieved competencies component - for (int i = 0; i < exerciseProfile.competencyAssessments.length - 1; i++) { - float adjustment = exerciseProfile.competencyAssessments[i] * unsuccessful[i]; - score += adjustment; - LOG.debug("Adjusted score by competency index {}: {}, new score: {}", i, adjustment, score); - } - - // Difficulty component - float difficultyComponent = exerciseProfile.difficulty * (0.5f + Math.abs(currentDifficulty - exerciseProfile.difficulty)); - score = score * difficultyComponent; - LOG.debug("Applied difficulty component: {}, updated score: {}", difficultyComponent, score); - - // Topic component - float topicMultiplier = topicOrder.get(exerciseProfile.exerciseTopicName); - score *= topicMultiplier; - LOG.debug("Applied topic multiplier: {}, updated score: {}", topicMultiplier, score); - - // Round score - score = Math.round(score * 10.0f) / 10.0f; - LOG.debug("Final rounded score: {}", score); - - return score; -} + public static float calculateScore(ExerciseCompetencyProfile exerciseProfile, float[] unsuccessful, Map<String, Integer> topicOrder, float currentDifficulty) { + //ensure factor 1 for full success not to blank out score, thus offset the base + float score = 1.0f; + //competency profile difference to not fully achieved competencies component + for (int i = 0; i < exerciseProfile.competencyAssessments.length-1; i++) { + score += exerciseProfile.competencyAssessments[i] * unsuccessful[i]; + } + + //difficulty component + score = score * (exerciseProfile.difficulty*(0.5f+Math.abs(currentDifficulty-exerciseProfile.difficulty))); + + //topic component + score *= topicOrder.get(exerciseProfile.exerciseTopicName); + + score = Math.round(score * 10.0f) / 10.0f; + return score; + } } \ No newline at end of file -- GitLab