Commit c1884045 authored by mamunozgil's avatar mamunozgil
Browse files

Refactor for backtracking manifesto

parent c873f92d
This commit is part of merge request !1. Comments created here will be created in the context of that merge request.
Showing with 321 additions and 308 deletions
+321 -308
...@@ -3,134 +3,188 @@ package de.hftstuttgart.dtabackend.utils; ...@@ -3,134 +3,188 @@ package de.hftstuttgart.dtabackend.utils;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import com.fasterxml.jackson.core.exc.StreamReadException;
import com.fasterxml.jackson.databind.DatabindException;
import de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile; import de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile;
import de.hftstuttgart.dtabackend.models.ICompetencyProfile; import de.hftstuttgart.dtabackend.models.ICompetencyProfile;
import de.hftstuttgart.dtabackend.models.Result; import de.hftstuttgart.dtabackend.models.Result;
import de.hftstuttgart.dtabackend.models.ResultSummary; import de.hftstuttgart.dtabackend.models.ResultSummary;
import de.hftstuttgart.dtabackend.models.TestCompetencyProfile; import de.hftstuttgart.dtabackend.models.TestCompetencyProfile;
import java.io.FileNotFoundException;
public class CompetencyAssessmentUtil { public class CompetencyAssessmentUtil {
private static final Logger LOG = LogManager.getLogger(CompetencyAssessmentUtil.class); private static final Logger LOG = LogManager.getLogger(CompetencyAssessmentUtil.class);
public static String TEST_COMPETENCY_MANIFEST_FILE_NAME="competency-tests.mft"; public static final String TEST_COMPETENCY_MANIFEST_FILE_NAME = "competency-tests.mft";
public static String EXERCISE_COMPETENCY_MANIFEST_FILE_NAME="exercise-tests.mft"; public static final String EXERCISE_COMPETENCY_MANIFEST_FILE_NAME = "exercise-tests.mft";
/*public static void main(String[] args) throws StreamReadException, DatabindException, MalformedURLException, IOException { /**
ResultSummary summary=ExecuteTestUtil.generateResult("1", Path.of(args[0]), Path.of(args[1])); * Retrieves the base directory where the test competency manifest is located
System.out.println(summary.successfulTestCompetencyProfile); * by traversing upwards until it finds the file "exercise-tests.mft".
} *
* @param startDir Starting directory path to begin the search
* @return Path of the base directory if found; otherwise, null
*/ */
public static float[] sumTestCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles) { public static Path getBaseDirectory(Path startDir) {
float[] tcpTotalProfile=new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS]; Path currentDir = startDir;
for(TestCompetencyProfile currentProfile: testCompetencyProfiles) { while (currentDir != null) {
tcpTotalProfile=ICompetencyProfile.competencySum(tcpTotalProfile, currentProfile.competencyAssessments); Path manifestPath = currentDir.resolve(EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
if (Files.exists(manifestPath)) {
return currentDir;
} }
return tcpTotalProfile; currentDir = currentDir.getParent();
} }
LOG.warn("Base directory with " + EXERCISE_COMPETENCY_MANIFEST_FILE_NAME + " not found starting from " + startDir);
public static float[] sumSuccessfulCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary, boolean success) { return null;
float[] sumSuccessful=new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
for(Result currentResult: resultSummary.results) {
boolean isSuccess = Integer.valueOf(currentResult.state).equals(Result.State.SUCCESS.ordinal());
if (isSuccess == success) {
TestCompetencyProfile currentProfile=new TestCompetencyProfile();
currentProfile.testPackageName=(currentResult.packageName!=null)?currentResult.packageName:"";
currentProfile.testClassName=(currentResult.className!=null)?currentResult.className:"";
currentProfile.testName=(currentResult.name!=null)?currentResult.name:"";
int testIndex=testCompetencyProfiles.indexOf(currentProfile);
if(testIndex!=-1) {
sumSuccessful=ICompetencyProfile.competencySum(sumSuccessful, testCompetencyProfiles.get(testIndex).competencyAssessments);
} }
/**
* Reads and loads test competency profiles from the base directory.
*
* @param exercisePath Path to the starting exercise directory
* @return List of TestCompetencyProfile instances loaded from the manifest
*/
public static List<TestCompetencyProfile> readTestCompetencyProfiles(Path exercisePath) {
Path baseDir = getBaseDirectory(exercisePath);
if (baseDir == null) {
LOG.error("Unable to locate the base directory for reading test competency profiles.");
return null;
} }
return readTestCompetencyProfiles(baseDir, TEST_COMPETENCY_MANIFEST_FILE_NAME);
} }
return sumSuccessful;
/**
* Reads and loads exercise competency profiles from the specified exercise directory.
*
* @param exercisePath Path to the exercise directory
* @return List of ExerciseCompetencyProfile instances loaded from the manifest
*/
public static List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath) {
return readExerciseCompetencyProfiles(exercisePath, EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
} }
public static List<TestCompetencyProfile> readTestCompetencyProfiles(Path testPath, String fileName) { private static List<TestCompetencyProfile> readTestCompetencyProfiles(Path baseDir, String fileName) {
List<TestCompetencyProfile> testCompetencyProfiles=new ArrayList<TestCompetencyProfile>(); List<TestCompetencyProfile> testCompetencyProfiles = new ArrayList<>();
try { Path manifestPath = baseDir.resolve(fileName);
BufferedReader testCompetencyManifest=new BufferedReader(new FileReader(new File(testPath.toFile(), fileName)));
String testEntry=testCompetencyManifest.readLine(); try (BufferedReader testCompetencyManifest = new BufferedReader(new FileReader(manifestPath.toFile()))) {
while(testEntry!=null) String testEntry = testCompetencyManifest.readLine();
{ while (testEntry != null) {
String[] testEntyComponents=testEntry.split(ICompetencyProfile.COMPETENCY_SEPARATOR); String[] testEntryComponents = testEntry.split(ICompetencyProfile.COMPETENCY_SEPARATOR);
TestCompetencyProfile currentProfile=new TestCompetencyProfile(); TestCompetencyProfile currentProfile = new TestCompetencyProfile();
currentProfile.testPackageName=testEntyComponents[0]; currentProfile.testPackageName = testEntryComponents[0];
currentProfile.testClassName=testEntyComponents[1]; currentProfile.testClassName = testEntryComponents[1];
currentProfile.testName=testEntyComponents[2]; currentProfile.testName = testEntryComponents[2];
for(int competencyIndex=0; competencyIndex<ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) { for (int competencyIndex = 0; competencyIndex < ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
currentProfile.competencyAssessments[competencyIndex]=Float.valueOf(testEntyComponents[competencyIndex+3]); currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(testEntryComponents[competencyIndex + 3]);
} }
testCompetencyProfiles.add(currentProfile); testCompetencyProfiles.add(currentProfile);
testEntry=testCompetencyManifest.readLine(); testEntry = testCompetencyManifest.readLine();
} }
testCompetencyManifest.close(); LOG.info("Loaded " + testCompetencyProfiles.size() + " test competency profiles from " + manifestPath);
LOG.info("Added "+testCompetencyProfiles.size()+" test competency profiles from test competency manifest. Optional agent functionality enabled.");
} catch (FileNotFoundException e) {
LOG.info("Test competency manifest file for agent feedback not found. Skipping optional functionality.");
testCompetencyProfiles=null;
} catch (IOException e) { } catch (IOException e) {
LOG.info("Test competency manifest file for agent feedback unreadable. Skipping optional functionality."); LOG.error("Error reading test competency manifest file at " + manifestPath, e);
testCompetencyProfiles=null;
} }
return testCompetencyProfiles; return testCompetencyProfiles;
} }
public static List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath, String fileName) { private static List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath, String fileName) {
List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = new ArrayList<>(); List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = new ArrayList<>();
Path manifestPath = exercisePath.resolve(fileName);
try (BufferedReader exerciseCompetencyManifest = new BufferedReader(new FileReader(new File(exercisePath.toFile(), fileName)))) { try (BufferedReader exerciseCompetencyManifest = new BufferedReader(new FileReader(manifestPath.toFile()))) {
String exerciseEntry = exerciseCompetencyManifest.readLine(); String exerciseEntry = exerciseCompetencyManifest.readLine();
while (exerciseEntry != null) { while (exerciseEntry != null) {
String[] exerciseEntyComponents = exerciseEntry.split(ExerciseCompetencyProfile.COMPETENCY_SEPARATOR); String[] exerciseEntryComponents = exerciseEntry.split(ExerciseCompetencyProfile.COMPETENCY_SEPARATOR);
ExerciseCompetencyProfile currentProfile = new ExerciseCompetencyProfile(); ExerciseCompetencyProfile currentProfile = new ExerciseCompetencyProfile();
currentProfile.exerciseTopicName = exerciseEntryComponents[0];
currentProfile.exerciseTopicName = exerciseEntyComponents[0]; currentProfile.exerciseName = exerciseEntryComponents[1];
currentProfile.exerciseName = exerciseEntyComponents[1]; currentProfile.exerciseURL = exerciseEntryComponents[2];
currentProfile.exerciseURL = exerciseEntyComponents[2];
for (int competencyIndex = 0; competencyIndex < ExerciseCompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) { for (int competencyIndex = 0; competencyIndex < ExerciseCompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(exerciseEntyComponents[competencyIndex+3]); currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(exerciseEntryComponents[competencyIndex + 3]);
} }
currentProfile.difficulty = Float.parseFloat(exerciseEntryComponents[19]);
currentProfile.difficulty = Float.parseFloat(exerciseEntyComponents[19]);
exerciseCompetencyProfiles.add(currentProfile); exerciseCompetencyProfiles.add(currentProfile);
exerciseEntry = exerciseCompetencyManifest.readLine(); exerciseEntry = exerciseCompetencyManifest.readLine();
} }
exerciseCompetencyManifest.close(); LOG.info("Loaded " + exerciseCompetencyProfiles.size() + " exercise competency profiles from " + manifestPath);
LOG.info("Added " + exerciseCompetencyProfiles.size() + " test competency profiles from exercise competency manifest.");
} catch (FileNotFoundException e) {
LOG.info("Exercise competency manifest file not found.");
} catch (IOException e) { } catch (IOException e) {
LOG.info("Exercise competency manifest file unreadable."); LOG.error("Error reading exercise competency manifest file at " + manifestPath, e);
} }
return exerciseCompetencyProfiles; return exerciseCompetencyProfiles;
} }
/**
* Converts an array of floats into a semicolon-separated string.
*
* @param array Array of float values to pack
* @return Semicolon-separated string of float values
*/
public static String packFloats(float[] array) { public static String packFloats(float[] array) {
return IntStream.range(0, array.length) return IntStream.range(0, array.length)
.mapToObj(i -> String.valueOf(array[i])) .mapToObj(i -> String.valueOf(array[i]))
.collect(Collectors.joining(";")); .collect(Collectors.joining(";"));
} }
/**
* Sums the competency profiles across all test competency profiles.
*
* @param testCompetencyProfiles List of test competency profiles
* @return An array representing the sum of competencies
*/
public static float[] sumTestCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles) {
float[] tcpTotalProfile = new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
for (TestCompetencyProfile currentProfile : testCompetencyProfiles) {
tcpTotalProfile = ICompetencyProfile.competencySum(tcpTotalProfile, currentProfile.competencyAssessments);
}
return tcpTotalProfile;
}
/**
* Sums only the successful test competency profiles, based on the results summary.
*
* @param testCompetencyProfiles List of test competency profiles
* @param resultSummary The result summary containing test results
* @param success Indicates whether to sum successful (true) or unsuccessful (false) profiles
* @return An array representing the sum of competencies for successful or unsuccessful profiles
*/
public static float[] sumSuccessfulCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary, boolean success) {
float[] sumSuccessful = new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
for (Result currentResult : resultSummary.results) {
boolean isSuccess = Integer.valueOf(currentResult.state).equals(Result.State.SUCCESS.ordinal());
if (isSuccess == success) {
TestCompetencyProfile currentProfile = new TestCompetencyProfile();
currentProfile.testPackageName = (currentResult.packageName != null) ? currentResult.packageName : "";
currentProfile.testClassName = (currentResult.className != null) ? currentResult.className : "";
currentProfile.testName = (currentResult.name != null) ? currentResult.name : "";
int testIndex = testCompetencyProfiles.indexOf(currentProfile);
if (testIndex != -1) {
sumSuccessful = ICompetencyProfile.competencySum(sumSuccessful, testCompetencyProfiles.get(testIndex).competencyAssessments);
}
}
}
return sumSuccessful;
}
/**
* Checks if all elements in the competency profile array are zero (indicating full success).
*
* @param competencyProfile Array of competency values
* @return True if all values are zero; false otherwise
*/
public static boolean isFullSuccess(float[] competencyProfile) {
for (float value : competencyProfile) {
if (value != 0.0f) {
return false;
}
}
return true;
}
} }
...@@ -7,7 +7,6 @@ import com.github.dockerjava.api.model.Bind; ...@@ -7,7 +7,6 @@ import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.Volume; import com.github.dockerjava.api.model.Volume;
import de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile; import de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile;
import de.hftstuttgart.dtabackend.models.ICompetencyProfile;
import de.hftstuttgart.dtabackend.models.Recommendation; import de.hftstuttgart.dtabackend.models.Recommendation;
import de.hftstuttgart.dtabackend.models.ResultSummary; import de.hftstuttgart.dtabackend.models.ResultSummary;
import de.hftstuttgart.dtabackend.models.TestCompetencyProfile; import de.hftstuttgart.dtabackend.models.TestCompetencyProfile;
...@@ -22,10 +21,7 @@ import java.net.MalformedURLException; ...@@ -22,10 +21,7 @@ import java.net.MalformedURLException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
...@@ -38,217 +34,180 @@ public class ExecuteTestUtil { ...@@ -38,217 +34,180 @@ public class ExecuteTestUtil {
private final String assignmentBasePath; private final String assignmentBasePath;
private final Path testTmpPathHost; private final Path testTmpPathHost;
public ExecuteTestUtil( public ExecuteTestUtil(Environment env, DockerUtil dockerUtil) {
Environment env,
DockerUtil dockerUtil
) {
this.dockerUtil = dockerUtil; this.dockerUtil = dockerUtil;
this.assignmentBasePath = Paths.get(
// set base path for assignments to be stored
Path p = Paths.get(
env.getProperty("data.dir"), env.getProperty("data.dir"),
env.getProperty("data.dir.test.folder.name")); env.getProperty("data.dir.test.folder.name"))
this.assignmentBasePath = p.toAbsolutePath().toString(); .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")); this.testTmpPathHost = Paths.get(env.getProperty("host.tests.tmp.dir"));
} }
public ResultSummary runTests(String assignmentId, Path workDirectory) throws IOException, InterruptedException { public ResultSummary runTests(String assignmentId, Path workDirectory) throws IOException, InterruptedException {
// Define paths
// define paths for the test, the submission and where the result is to be expected afterwards Path testPath = workDirectory.resolve("test");
Path testPath = Paths.get(workDirectory.toAbsolutePath().toString(), "/test"); Path srcPath = workDirectory.resolve("src");
Path srcPath = Paths.get(workDirectory.toAbsolutePath().toString(), "/src"); Path resultPath = workDirectory.resolve("result");
Path resultPath = Paths.get(workDirectory.toAbsolutePath().toString(), "/result");
// Clone test to temporary directory
// clone stored test to tmpdir LOG.debug("Copying pre-downloaded unittest repo");
LOG.debug("copying pre-downloaded unitttest repo"); FileUtil.copyFolder(Paths.get(assignmentBasePath, assignmentId), testPath);
FileUtil.copyFolder(
Paths.get(assignmentBasePath, assignmentId), // Copy configuration file
testPath); LOG.debug("Copy test config");
Files.copy(Paths.get(assignmentBasePath, assignmentId + ".txt"), workDirectory.resolve("config.txt"));
LOG.debug("copy test config");
Files.copy(
Paths.get(assignmentBasePath, assignmentId + ".txt"),
Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt"));
Files.createDirectory(resultPath); Files.createDirectory(resultPath);
LOG.info("reading test config"); // Load container image configuration
Matcher config = RegexUtil.extractConfig( LOG.info("Reading test config");
new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile()), Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX)); String image = loadImageConfig(workDirectory);
String image="";
if(config==null) // Define paths to mount in the container
{ Path testPathHost = testTmpPathHost.resolve(workDirectory.getFileName()).resolve(testPath.getFileName());
config = RegexUtil.extractConfig( Path srcPathHost = testTmpPathHost.resolve(workDirectory.getFileName()).resolve(srcPath.getFileName());
new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile()), Pattern.compile(RegexUtil.TESTCONFIGREGEX)); Path resultPathHost = testTmpPathHost.resolve(workDirectory.getFileName()).resolve(resultPath.getFileName());
if(config==null)
{
throw new RuntimeException("couldn't find repo config for unittest 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 container with mounts
dockerUtil.runContainer( dockerUtil.runContainer(
image, image,
new Bind(testPathHost.toAbsolutePath().toString(), new Volume("/data/test")), new Bind(testPathHost.toString(), new Volume("/data/test")),
new Bind(srcPathHost.toAbsolutePath().toString(), new Volume("/data/src")), new Bind(srcPathHost.toString(), new Volume("/data/src")),
new Bind(resultPathHost.toAbsolutePath().toString(), new Volume("/data/result")) new Bind(resultPathHost.toString(), new Volume("/data/result"))
); );
ResultSummary resultSummary = generateResult(assignmentId, resultPath, testPathHost); return generateResult(assignmentId, resultPath, testPathHost);
}
return resultSummary; private String loadImageConfig(Path workDirectory) throws FileNotFoundException {
Matcher config = RegexUtil.extractConfig(
new FileInputStream(workDirectory.resolve("config.txt").toFile()),
Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX));
if (config == null) {
config = RegexUtil.extractConfig(
new FileInputStream(workDirectory.resolve("config.txt").toFile()),
Pattern.compile(RegexUtil.TESTCONFIGREGEX));
if (config == null) {
throw new RuntimeException("Couldn't find repo config for unittest image extraction");
}
return config.group(4);
}
return config.group(5);
} }
private ResultSummary generateResult(String assignmentId, Path resultPath, Path testPathHost) private ResultSummary generateResult(String assignmentId, Path resultPath, Path testPathHost)
throws IOException, StreamReadException, DatabindException, MalformedURLException { throws IOException, StreamReadException, DatabindException, MalformedURLException {
// define expected result file File resultFile = resultPath.resolve("result.json").toFile();
File resultFile = Paths.get(resultPath.toAbsolutePath().toString(), "result.json").toFile();
// check if result file is there
if (!resultFile.exists() || !resultFile.isFile()) { if (!resultFile.exists() || !resultFile.isFile()) {
LOG.error(String.format("Could not find result file in %s", resultFile.getAbsolutePath())); 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(); ObjectMapper objectMapper = new ObjectMapper();
ResultSummary resultSummary = objectMapper.readValue(resultFile.toURI().toURL(), ResultSummary.class); 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 at " + resultSummary.timestamp + " with " + resultSummary.results.size() + " test results.");
LOG.info("Checking for optional test competency profile information for paedagogical agent functionality..."); LOG.info("Checking for optional test competency profile information...");
List<TestCompetencyProfile> testCompetencyProfiles=CompetencyAssessmentUtil.readTestCompetencyProfiles(testPathHost, CompetencyAssessmentUtil.TEST_COMPETENCY_MANIFEST_FILE_NAME); List<TestCompetencyProfile> testCompetencyProfiles = CompetencyAssessmentUtil.readTestCompetencyProfiles(testPathHost);
if(testCompetencyProfiles!=null) {
LOG.info("Found optional test competency profiles, generating agent profile data..."); if (testCompetencyProfiles != null) {
resultSummary.overallTestCompetencyProfile=CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumTestCompetencyProfiles(testCompetencyProfiles)); LOG.info("Found test competency profiles, generating profile data...");
resultSummary.successfulTestCompetencyProfile=CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, true)); 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 = testPathHost.resolve(CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
Path exerciseManifestFile = Paths.get(testPathHost.toAbsolutePath().toString(), CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
if (Files.exists(exerciseManifestFile)) { if (Files.exists(exerciseManifestFile)) {
LOG.info("Found optional exercise competency profiles, generating recommendations..."); LOG.info("Found exercise competency profiles, generating recommendations...");
resultSummary.recommendations = recommendNextExercises(assignmentId, testPathHost, testCompetencyProfiles, resultSummary); resultSummary.recommendations = recommendNextExercises(assignmentId, testPathHost, testCompetencyProfiles, resultSummary);
} }
} }
return resultSummary; return resultSummary;
} }
/*
* exercise recommendation part
*/
public List<Recommendation> recommendNextExercises(String assignmentId, Path testPathHost, List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary) public List<Recommendation> recommendNextExercises(String assignmentId, Path testPathHost, List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary)
throws FileNotFoundException { 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); String testRepoURL = RegexUtil.extractConfig(
new FileInputStream(Paths.get(assignmentBasePath, assignmentId + ".txt").toFile()),
Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX)).group(1);
int currentTopicIndex=0; List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = CompetencyAssessmentUtil.readExerciseCompetencyProfiles(testPathHost);
float currentDifficulty=0.0f;
//build course topic order int currentTopicIndex = 0;
float currentDifficulty = 0.0f;
Map<String, Integer> topicOrder = new HashMap<>(); Map<String, Integer> topicOrder = new HashMap<>();
int order = 1; int order = 1;
// Determine currentTopicIndex and set currentDifficulty based on exercise profiles
for (ExerciseCompetencyProfile e : exerciseCompetencyProfiles) { for (ExerciseCompetencyProfile e : exerciseCompetencyProfiles) {
if (!topicOrder.containsKey(e.exerciseTopicName)) { if (!topicOrder.containsKey(e.exerciseTopicName)) {
topicOrder.put(e.exerciseTopicName, order++); topicOrder.put(e.exerciseTopicName, order++);
} }
if (e.exerciseURL.equals(testRepoURL)) { if (e.exerciseURL.equals(testRepoURL)) {
currentTopicIndex = order; currentTopicIndex = order;
currentDifficulty = e.difficulty; currentDifficulty = e.difficulty; // Directly assign to currentDifficulty
} }
} }
//filter exercises according to success // Sum competencies for unsuccessful tests
float[] unsuccessful = CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, false); float[] unsuccessful = CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, false);
// Filter exercises based on topics and difficulty
List<ExerciseCompetencyProfile> filteredExercises = filterExercisesByTopicsAndDifficulty( List<ExerciseCompetencyProfile> filteredExercises = filterExercisesByTopicsAndDifficulty(
exerciseCompetencyProfiles, topicOrder, currentTopicIndex, testRepoURL, exerciseCompetencyProfiles, topicOrder, currentTopicIndex, testRepoURL, currentDifficulty, unsuccessful);
currentDifficulty, unsuccessful, resultSummary);
//compute recommendations // Generate recommendations without using lambda expressions
List<Recommendation> recommendedExercises = new ArrayList<>(); List<Recommendation> recommendedExercises = new ArrayList<>();
for (ExerciseCompetencyProfile exerciseProfile : filteredExercises) { for (ExerciseCompetencyProfile profile : filteredExercises) {
Recommendation recommendation = new Recommendation(exerciseProfile.exerciseTopicName, exerciseProfile.exerciseURL, exerciseProfile.exerciseName, float score = calculateScore(profile, unsuccessful, topicOrder, currentDifficulty);
exerciseProfile.difficulty, calculateScore(exerciseProfile, unsuccessful, topicOrder, currentDifficulty)); Recommendation recommendation = new Recommendation(
profile.exerciseTopicName,
profile.exerciseURL,
profile.exerciseName,
profile.difficulty,
score);
recommendedExercises.add(recommendation); 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()); // Sort the recommendations using Collections.sort and a comparator
Collections.sort(recommendedExercises, Recommendation.COMPARE_BY_SCORE);
return recommendedExercises; return recommendedExercises;
} }
public static List<ExerciseCompetencyProfile> filterExercisesByTopicsAndDifficulty(List<ExerciseCompetencyProfile> exerciseCompetencyProfiles, public static List<ExerciseCompetencyProfile> filterExercisesByTopicsAndDifficulty(List<ExerciseCompetencyProfile> profiles,
Map<String, Integer> topicOrder, int currentTopicIndex, String testRepoURL, float currentDifficulty, float[] unsuccessful, Map<String, Integer> topicOrder, int currentTopicIndex, String testRepoURL, float currentDifficulty, float[] unsuccessful) {
ResultSummary resultSummary) {
//filter out all advanced topics in any case List<ExerciseCompetencyProfile> filteredExercises = profiles.stream()
//option for later: include next topic if fullsuccess and current difficulty == max difficulty .filter(profile -> topicOrder.get(profile.exerciseTopicName) <= currentTopicIndex)
List<ExerciseCompetencyProfile> filteredExercises = exerciseCompetencyProfiles.stream()
.filter(testProfile -> topicOrder.get(testProfile.exerciseTopicName) <= currentTopicIndex)
.collect(Collectors.toList()); .collect(Collectors.toList());
//filter by difficulty according to success if (CompetencyAssessmentUtil.isFullSuccess(unsuccessful)) {
if (isFullSuccess(unsuccessful)) { filteredExercises = filteredExercises.stream()
filteredExercises = filteredExercises.stream().filter(profile -> profile.difficulty >= currentDifficulty && !testRepoURL.equals(profile.exerciseURL)).collect(Collectors.toList()); .filter(profile -> profile.difficulty >= currentDifficulty && !testRepoURL.equals(profile.exerciseURL))
.collect(Collectors.toList());
} else { } else {
filteredExercises = filteredExercises.stream().filter(profile -> profile.difficulty <= currentDifficulty).collect(Collectors.toList()); filteredExercises = filteredExercises.stream()
.filter(profile -> profile.difficulty <= currentDifficulty)
.collect(Collectors.toList());
} }
return filteredExercises; return filteredExercises;
} }
public static boolean isFullSuccess(float[] unsuccessful) { public static float calculateScore(ExerciseCompetencyProfile profile, float[] unsuccessful, Map<String, Integer> topicOrder, float currentDifficulty) {
for (float value : unsuccessful) {
if (value != 0.0f) {
return false;
}
}
return true;
}
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; float score = 1.0f;
//competency profile difference to not fully achieved competencies component for (int i = 0; i < profile.competencyAssessments.length - 1; i++) {
for (int i = 0; i < exerciseProfile.competencyAssessments.length-1; i++) { score += profile.competencyAssessments[i] * unsuccessful[i];
score += exerciseProfile.competencyAssessments[i] * unsuccessful[i];
} }
//difficulty component score *= profile.difficulty * (0.5f + Math.abs(currentDifficulty - profile.difficulty));
score = score * (exerciseProfile.difficulty*(0.5f+Math.abs(currentDifficulty-exerciseProfile.difficulty))); score *= topicOrder.getOrDefault(profile.exerciseTopicName, 1);
//topic component
score *= topicOrder.get(exerciseProfile.exerciseTopicName);
score = Math.round(score * 10.0f) / 10.0f; return Math.round(score * 10.0f) / 10.0f;
return score;
} }
} }
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment