Merge branch 'dev' into chore/development-workflow
This commit is contained in:
commit
8389414ea4
31 changed files with 1907 additions and 124 deletions
|
@ -28,9 +28,9 @@ curricula_page:
|
|||
contact: ''
|
||||
teaser: https://www.youtube.com/embed/2B6gZ9HdQ1Y
|
||||
basics_ai:
|
||||
title: Basisprincipes van AI
|
||||
sub_title: Basisprincipes van AI
|
||||
description: 'Onder dit lesthema bundelen we verschillende activiteiten waarin de basisprincipes van artificiële intelligentie (AI) aan bod komen. Leerlingen leren wat AI is, hoe het werkt en hoe het kan worden toegepast in verschillende domeinen.'
|
||||
title: Grundlagen der KI
|
||||
sub_title: Grundlagen der KI
|
||||
description: 'Dieses Thema bündelt verschiedene Aktivitäten, in denen die grundlegenden Prinzipien der künstlichen Intelligenz (KI) behandelt werden. Die Schüler lernen, was KI ist, wie sie funktioniert und wie sie in verschiedenen Bereichen angewendet werden kann.'
|
||||
contact: ''
|
||||
kiks:
|
||||
title: KI und Klima
|
||||
|
|
|
@ -28,10 +28,11 @@ curricula_page:
|
|||
contact: ''
|
||||
teaser: https://www.youtube.com/embed/2B6gZ9HdQ1Y
|
||||
basics_ai:
|
||||
title: Basisprincipes van AI
|
||||
sub_title: Basisprincipes van AI
|
||||
description: 'Onder dit lesthema bundelen we verschillende activiteiten waarin de basisprincipes van artificiële intelligentie (AI) aan bod komen. Leerlingen leren wat AI is, hoe het werkt en hoe het kan worden toegepast in verschillende domeinen.'
|
||||
title: Basics of AI
|
||||
sub_title: Basics of AI
|
||||
description: 'This theme brings together various activities covering the basic principles of Artificial Intelligence (AI). Students learn what AI is, how it works, and how it can be applied in different domains.'
|
||||
contact: ''
|
||||
|
||||
kiks:
|
||||
title: AI and Climate
|
||||
sub_title: KIKS
|
||||
|
|
|
@ -28,9 +28,9 @@ curricula_page:
|
|||
contact: ''
|
||||
teaser: https://www.youtube.com/embed/2B6gZ9HdQ1Y
|
||||
basics_ai:
|
||||
title: Basisprincipes van AI
|
||||
sub_title: Basisprincipes van AI
|
||||
description: 'Onder dit lesthema bundelen we verschillende activiteiten waarin de basisprincipes van artificiële intelligentie (AI) aan bod komen. Leerlingen leren wat AI is, hoe het werkt en hoe het kan worden toegepast in verschillende domeinen.'
|
||||
title: Principes de base de l’IA
|
||||
sub_title: Principes de base de l’IA
|
||||
description: 'Ce thème rassemble différentes activités portant sur les principes fondamentaux de l’intelligence artificielle (IA). Les élèves apprennent ce qu’est l’IA, comment elle fonctionne et comment elle peut être appliquée dans divers domaines.'
|
||||
contact: ''
|
||||
kiks:
|
||||
title: 'IA et changement climatique'
|
||||
|
|
|
@ -8,7 +8,7 @@ interface Translations {
|
|||
};
|
||||
}
|
||||
|
||||
export function getThemes(req: Request, res: Response) {
|
||||
export function getThemesHandler(req: Request, res: Response) {
|
||||
const language = (req.query.language as string)?.toLowerCase() || 'nl';
|
||||
const translations = loadTranslations<Translations>(language);
|
||||
const themeList = themes.map((theme) => ({
|
||||
|
@ -21,8 +21,14 @@ export function getThemes(req: Request, res: Response) {
|
|||
res.json(themeList);
|
||||
}
|
||||
|
||||
export function getThemeByTitle(req: Request, res: Response) {
|
||||
export function getHruidsByThemeHandler(req: Request, res: Response) {
|
||||
const themeKey = req.params.theme;
|
||||
|
||||
if (!themeKey) {
|
||||
res.status(400).json({ error: 'Missing required field: theme' });
|
||||
return;
|
||||
}
|
||||
|
||||
const theme = themes.find((t) => t.title === themeKey);
|
||||
|
||||
if (theme) {
|
||||
|
|
|
@ -19,7 +19,7 @@ export class Submission {
|
|||
learningObjectVersion: number = 1;
|
||||
|
||||
@PrimaryKey({ type: 'integer', autoincrement: true })
|
||||
submissionNumber!: number;
|
||||
submissionNumber?: number;
|
||||
|
||||
@ManyToOne({
|
||||
entity: () => Student,
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import express from 'express';
|
||||
import { getThemes, getThemeByTitle } from '../controllers/themes.js';
|
||||
import { getThemesHandler, getHruidsByThemeHandler } from '../controllers/themes.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Query: language
|
||||
// Route to fetch list of {key, title, description, image} themes in their respective language
|
||||
router.get('/', getThemes);
|
||||
router.get('/', getThemesHandler);
|
||||
|
||||
// Arg: theme (key)
|
||||
// Route to fetch list of hruids based on theme
|
||||
router.get('/:theme', getThemeByTitle);
|
||||
router.get('/:theme', getHruidsByThemeHandler);
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -39,14 +39,18 @@ async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise<Ma
|
|||
* Convert the given learning path entity to an object which conforms to the learning path content.
|
||||
*/
|
||||
async function convertLearningPath(learningPath: LearningPathEntity, order: number, personalizedFor?: PersonalizationTarget): Promise<LearningPath> {
|
||||
// Fetch the corresponding learning object for each node since some parts of the expected response contains parts
|
||||
// With information which is not available in the LearningPathNodes themselves.
|
||||
const nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject> = await getLearningObjectsForNodes(learningPath.nodes);
|
||||
|
||||
const targetAges = Array.from(nodesToLearningObjects.values()).flatMap((it) => it.targetAges || []);
|
||||
|
||||
const keywords = Array.from(nodesToLearningObjects.values()).flatMap((it) => it.keywords || []);
|
||||
// The target ages of a learning path are the union of the target ages of all learning objects.
|
||||
const targetAges = [...new Set(Array.from(nodesToLearningObjects.values()).flatMap((it) => it.targetAges || []))];
|
||||
// The keywords of the learning path consist of the union of the keywords of all learning objects.
|
||||
const keywords = [...new Set(Array.from(nodesToLearningObjects.values()).flatMap((it) => it.keywords || []))];
|
||||
|
||||
const image = learningPath.image ? learningPath.image.toString('base64') : undefined;
|
||||
|
||||
// Convert the learning object notes as retrieved from the database into the expected response format-
|
||||
const convertedNodes = await convertNodes(nodesToLearningObjects, personalizedFor);
|
||||
|
||||
return {
|
||||
|
@ -67,34 +71,55 @@ async function convertLearningPath(learningPath: LearningPathEntity, order: numb
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function converting a single learning path node (as represented in the database) and the corresponding
|
||||
* learning object into a learning path node as it should be represented in the API.
|
||||
*
|
||||
* @param node Learning path node as represented in the database.
|
||||
* @param learningObject Learning object the learning path node refers to.
|
||||
* @param personalizedFor Personalization target if a personalized learning path is desired.
|
||||
* @param nodesToLearningObjects Mapping from learning path nodes to the corresponding learning objects.
|
||||
*/
|
||||
async function convertNode(
|
||||
node: LearningPathNode,
|
||||
learningObject: FilteredLearningObject,
|
||||
personalizedFor: PersonalizationTarget | undefined,
|
||||
nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject>
|
||||
): Promise<LearningObjectNode> {
|
||||
const lastSubmission = personalizedFor ? await getLastSubmissionForCustomizationTarget(node, personalizedFor) : null;
|
||||
const transitions = node.transitions
|
||||
.filter(
|
||||
(trans) =>
|
||||
!personalizedFor || // If we do not want a personalized learning path, keep all transitions
|
||||
isTransitionPossible(trans, optionalJsonStringToObject(lastSubmission?.content)) // Otherwise remove all transitions that aren't possible.
|
||||
)
|
||||
.map((trans, i) => convertTransition(trans, i, nodesToLearningObjects));
|
||||
return {
|
||||
_id: learningObject.uuid,
|
||||
language: learningObject.language,
|
||||
start_node: node.startNode,
|
||||
created_at: node.createdAt.toISOString(),
|
||||
updatedAt: node.updatedAt.toISOString(),
|
||||
learningobject_hruid: node.learningObjectHruid,
|
||||
version: learningObject.version,
|
||||
transitions,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function converting pairs of learning path nodes (as represented in the database) and the corresponding
|
||||
* learning objects into a list of learning path nodes as they should be represented in the API.
|
||||
* @param nodesToLearningObjects
|
||||
* @param personalizedFor
|
||||
*
|
||||
* @param nodesToLearningObjects Mapping from learning path nodes to the corresponding learning objects.
|
||||
* @param personalizedFor Personalization target if a personalized learning path is desired.
|
||||
*/
|
||||
async function convertNodes(
|
||||
nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject>,
|
||||
personalizedFor?: PersonalizationTarget
|
||||
): Promise<LearningObjectNode[]> {
|
||||
const nodesPromise = Array.from(nodesToLearningObjects.entries()).map(async (entry) => {
|
||||
const [node, learningObject] = entry;
|
||||
const lastSubmission = personalizedFor ? await getLastSubmissionForCustomizationTarget(node, personalizedFor) : null;
|
||||
return {
|
||||
_id: learningObject.uuid,
|
||||
language: learningObject.language,
|
||||
start_node: node.startNode,
|
||||
created_at: node.createdAt.toISOString(),
|
||||
updatedAt: node.updatedAt.toISOString(),
|
||||
learningobject_hruid: node.learningObjectHruid,
|
||||
version: learningObject.version,
|
||||
transitions: node.transitions
|
||||
.filter(
|
||||
(trans) => !personalizedFor || isTransitionPossible(trans, optionalJsonStringToObject(lastSubmission?.content)) // If we want a personalized learning path, remove all transitions that aren't possible.
|
||||
)
|
||||
.map((trans, i) => convertTransition(trans, i, nodesToLearningObjects)), // Then convert all the transition
|
||||
};
|
||||
});
|
||||
const nodesPromise = Array.from(nodesToLearningObjects.entries()).map((entry) =>
|
||||
convertNode(entry[0], entry[1], personalizedFor, nodesToLearningObjects)
|
||||
);
|
||||
return await Promise.all(nodesPromise);
|
||||
}
|
||||
|
||||
|
@ -112,9 +137,10 @@ function optionalJsonStringToObject(jsonString?: string): object | null {
|
|||
* Helper function which converts a transition in the database representation to a transition in the representation
|
||||
* the Dwengo API uses.
|
||||
*
|
||||
* @param transition
|
||||
* @param index
|
||||
* @param nodesToLearningObjects
|
||||
* @param transition The transition to convert
|
||||
* @param index The sequence number of the transition to convert
|
||||
* @param nodesToLearningObjects Map which maps each learning path node of the current learning path to the learning
|
||||
* object it refers to.
|
||||
*/
|
||||
function convertTransition(
|
||||
transition: LearningPathTransition,
|
||||
|
|
|
@ -12,7 +12,6 @@ import learningObjectExample from '../../test-assets/learning-objects/pn-werking
|
|||
import learningPathExample from '../../test-assets/learning-paths/pn-werking-example.js';
|
||||
import databaseLearningPathProvider from '../../../src/services/learning-paths/database-learning-path-provider.js';
|
||||
import { expectToBeCorrectLearningPath } from '../../test-utils/expectations.js';
|
||||
import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository.js';
|
||||
import learningObjectService from '../../../src/services/learning-objects/learning-object-service.js';
|
||||
import { Language } from '../../../src/entities/content/language.js';
|
||||
import {
|
||||
|
@ -59,7 +58,6 @@ async function initPersonalizationTestData(): Promise<{
|
|||
learningObjectHruid: learningContent.branchingObject.hruid,
|
||||
learningObjectLanguage: learningContent.branchingObject.language,
|
||||
learningObjectVersion: learningContent.branchingObject.version,
|
||||
submissionNumber: 0,
|
||||
submitter: studentA,
|
||||
submissionTime: new Date(),
|
||||
content: '[0]',
|
||||
|
@ -76,7 +74,6 @@ async function initPersonalizationTestData(): Promise<{
|
|||
learningObjectHruid: learningContent.branchingObject.hruid,
|
||||
learningObjectLanguage: learningContent.branchingObject.language,
|
||||
learningObjectVersion: learningContent.branchingObject.version,
|
||||
submissionNumber: 1,
|
||||
submitter: studentB,
|
||||
submissionTime: new Date(),
|
||||
content: '[1]',
|
||||
|
@ -106,7 +103,6 @@ function expectBranchingObjectNode(
|
|||
}
|
||||
|
||||
describe('DatabaseLearningPathProvider', () => {
|
||||
let learningObjectRepo: LearningObjectRepository;
|
||||
let example: { learningObject: LearningObject; learningPath: LearningPath };
|
||||
let persTestData: { learningContent: ConditionTestLearningPathAndLearningObjects; studentA: Student; studentB: Student };
|
||||
|
||||
|
@ -114,7 +110,6 @@ describe('DatabaseLearningPathProvider', () => {
|
|||
await setupTestApp();
|
||||
example = await initExampleData();
|
||||
persTestData = await initPersonalizationTestData();
|
||||
learningObjectRepo = getLearningObjectRepository();
|
||||
});
|
||||
|
||||
describe('fetchLearningPaths', () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue