feat(backend): LearningPathPersonalizingService geïmplementeerd

This commit is contained in:
Gerald Schmittinger 2025-03-11 04:08:32 +01:00
parent cd0a3a8a7b
commit a30c4d0d32
5 changed files with 129 additions and 116 deletions

View file

@ -18,24 +18,24 @@
"@mikro-orm/postgresql": "^6.4.6",
"@mikro-orm/reflection": "^6.4.6",
"@mikro-orm/sqlite": "6.4.6",
"@types/cors": "^2.8.17",
"@types/js-yaml": "^4.0.9",
"axios": "^1.8.2",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^5.0.1",
"express-jwt": "^8.5.1",
"gift-pegjs": "^1.0.2",
"isomorphic-dompurify": "^2.22.0",
"express-jwt": "^8.5.1",
"jwks-rsa": "^3.1.0",
"uuid": "^11.1.0",
"js-yaml": "^4.1.0",
"marked": "^15.0.7",
"uuid": "^11.1.0",
"jsonpath-plus": "^10.3.0",
"jwks-rsa": "^3.1.0",
"loki-logger-ts": "^1.0.2",
"marked": "^15.0.7",
"response-time": "^2.3.3",
"uuid": "^11.1.0",
"winston": "^3.17.0",
"winston-loki": "^6.1.3",
"cors": "^2.8.5",
"@types/cors": "^2.8.17"
"winston-loki": "^6.1.3"
},
"devDependencies": {
"@mikro-orm/cli": "^6.4.6",

View file

@ -15,7 +15,7 @@ function getLearningObjectIdentifierFromRequest(req: Request): LearningObjectIde
return {
hruid: req.params.hruid as string,
language: (req.query.language || getEnvVar(EnvVars.FallbackLanguage)) as Language,
version: req.query.version as string
version: parseInt(req.query.version as string)
};
}

View file

@ -24,6 +24,7 @@ export class UnauthorizedException extends Error {
*/
export class ForbiddenException extends Error {
status = 403;
constructor(message: string = 'Forbidden') {
super(message);
}

View file

@ -1,5 +1,69 @@
const learningPathPersonalizingService = {
import {Student} from "../../entities/users/student.entity";
import {getSubmissionRepository} from "../../data/repositories";
import {Group} from "../../entities/assignments/group.entity";
import {Submission} from "../../entities/assignments/submission.entity";
import {LearningObjectIdentifier} from "../../entities/content/learning-object-identifier";
import {LearningPathNode} from "../../entities/content/learning-path-node.entity";
import {LearningPathTransition} from "../../entities/content/learning-path-transition.entity";
import {JSONPath} from 'jsonpath-plus';
/**
* Returns the last submission for the learning object associated with the given node and for the student or group
*/
async function getLastRelevantSubmission(node: LearningPathNode, pathFor: {student?: Student, group?: Group}): Promise<Submission | null> {
const submissionRepo = getSubmissionRepository();
const learningObjectId: LearningObjectIdentifier = {
hruid: node.learningObjectHruid,
language: node.language,
version: node.version
};
let lastSubmission: Submission | null;
if (pathFor.group) {
lastSubmission = await submissionRepo.findMostRecentSubmissionForGroup(learningObjectId, pathFor.group);
} else if (pathFor.student) {
lastSubmission = await submissionRepo.findMostRecentSubmissionForStudent(learningObjectId, pathFor.student);
} else {
throw new Error("The path must either be created for a certain group or for a certain student!");
}
return lastSubmission;
}
function transitionPossible(transition: LearningPathTransition, submitted: object | null): boolean {
if (transition.condition === "true" || !transition.condition) {
return true; // If the transition is unconditional, we can go on.
}
if (submitted === null) {
return false; // If the transition is not unconditional and there was no submission, the transition is not possible.
}
return JSONPath({path: transition.condition, json: submitted}).length === 0;
}
/**
* Service to create individual trajectories from learning paths based on the submissions of the student or group.
*/
const learningPathPersonalizingService = {
async calculatePersonalizedTrajectory(nodes: LearningPathNode[], pathFor: {student?: Student, group?: Group}): Promise<LearningPathNode[]> {
let trajectory: LearningPathNode[] = [];
// Always start with the start node.
let currentNode = nodes.filter(it => it.startNode)[0];
trajectory.push(currentNode);
while (true) {
// At every node, calculate all the possible next transitions.
let lastSubmission = await getLastRelevantSubmission(currentNode, pathFor);
let submitted = lastSubmission === null ? null : JSON.parse(lastSubmission.content);
let possibleTransitions = currentNode.transitions
.filter(it => transitionPossible(it, submitted));
if (possibleTransitions.length === 0) { // If there are none, the trajectory has ended.
return trajectory;
} else { // Otherwise, take the first possible transition.
currentNode = possibleTransitions[0].node;
trajectory.push(currentNode);
}
}
}
};
export default learningPathPersonalizingService;