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; | ||||
|  |  | |||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger