feat(backend): LearningPathPersonalizingService geïmplementeerd
This commit is contained in:
parent
cd0a3a8a7b
commit
a30c4d0d32
5 changed files with 129 additions and 116 deletions
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ export class UnauthorizedException extends Error {
|
|||
*/
|
||||
export class ForbiddenException extends Error {
|
||||
status = 403;
|
||||
|
||||
constructor(message: string = 'Forbidden') {
|
||||
super(message);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue