From 1bd47fedb7aff0e782c4bebccbf9ebe04a65038f Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 21 May 2026 16:33:57 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20f=C3=BCge=20neue=20Funktionen=20zur=20A?= =?UTF-8?q?nalyse=20und=20Z=C3=A4hlung=20von=20Lektionen=20hinzu,=20einsch?= =?UTF-8?q?lie=C3=9Flich=20der=20Generierung=20von=20Lernkontrollpunkten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/data/lesson-checkpoints.json | 42 ++++++++ backend/scripts/analyze-bisaya-courses.cjs | 92 ++++++++++++++++++ backend/scripts/analyze-bisaya-courses.js | 95 +++++++++++++++++++ backend/scripts/bisaya-course-plan-24-43.js | 2 + backend/scripts/count-per-lesson-24-43.mjs | 25 +++++ .../scripts/generate-lesson-checkpoints.cjs | 56 +++++++++++ 6 files changed, 312 insertions(+) create mode 100644 backend/data/lesson-checkpoints.json create mode 100644 backend/scripts/analyze-bisaya-courses.cjs create mode 100644 backend/scripts/analyze-bisaya-courses.js create mode 100644 backend/scripts/count-per-lesson-24-43.mjs create mode 100644 backend/scripts/generate-lesson-checkpoints.cjs diff --git a/backend/data/lesson-checkpoints.json b/backend/data/lesson-checkpoints.json new file mode 100644 index 0000000..25aad78 --- /dev/null +++ b/backend/data/lesson-checkpoints.json @@ -0,0 +1,42 @@ +{ + "24": true, + "25": true, + "26": true, + "27": true, + "28": true, + "29": true, + "30": true, + "31": true, + "32": true, + "33": true, + "34": true, + "35": true, + "36": true, + "37": true, + "38": true, + "39": true, + "40": true, + "41": true, + "42": true, + "43": true, + "44": true, + "45": true, + "46": true, + "47": true, + "48": true, + "49": true, + "50": true, + "51": true, + "52": true, + "53": true, + "54": true, + "55": true, + "56": true, + "57": true, + "58": true, + "59": true, + "60": true, + "61": true, + "62": true, + "63": true +} \ No newline at end of file diff --git a/backend/scripts/analyze-bisaya-courses.cjs b/backend/scripts/analyze-bisaya-courses.cjs new file mode 100644 index 0000000..4155c99 --- /dev/null +++ b/backend/scripts/analyze-bisaya-courses.cjs @@ -0,0 +1,92 @@ +const fs = require('fs'); +const path = require('path'); + +function extractBlocks(content) { + const re = /['"]([^'"]+)['"]\s*:\s*\{([\s\S]*?)\n\s*\}/g; + const blocks = {}; + let m; + while ((m = re.exec(content))) { + blocks[m[1]] = m[2]; + } + return blocks; +} + +function countCorePatternsInBlock(blockText) { + const cpRe = /corePatterns\s*:\s*\[([\s\S]*?)\]/g; + let m = cpRe.exec(blockText); + if (!m) return 0; + const arrText = m[1]; + const targetRe = /\btarget\s*:/g; + return (arrText.match(targetRe) || []).length; +} + +function parseDidacticsFile(filePath) { + const content = fs.readFileSync(filePath, 'utf8'); + const blocks = extractBlocks(content); + const counts = {}; + let total = 0; + for (const [k, v] of Object.entries(blocks)) { + const c = countCorePatternsInBlock(v); + if (c > 0) { + counts[k] = c; + total += c; + } + } + return { counts, total }; +} + +function extractLessonArray(content, varName) { + const re = new RegExp(varName.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') + "\\s*=\\s*\\[([\\s\\S]*?)\\];", 'm'); + const m = re.exec(content); + if (!m) return null; + return m[1]; +} + +function countExamLessonsFromArrayText(arrayText) { + const objRe = /\{([\s\S]*?)\}(?=\s*,|$)/g; + let m; + let examCount = 0; + let total = 0; + while ((m = objRe.exec(arrayText))) { + total++; + const obj = m[1]; + const typeMatch = /type\s*:\s*['"](\w+)['"]/.exec(obj); + const titleMatch = /title\s*:\s*['"]([\s\S]*?)['"]/.exec(obj); + const type = typeMatch ? typeMatch[1] : ''; + const title = titleMatch ? titleMatch[1].toLowerCase() : ''; + if (type === 'review' || /checkpoint|test|abschluss|prüfung|check/i.test(title)) { + examCount++; + } + } + return { examCount, total }; +} + +function analyze() { + const repoRoot = path.join(__dirname); + const file1 = path.join(repoRoot, 'bisaya-course-plan-24-43.js'); + const file2 = path.join(repoRoot, 'bisaya-course-phase3-extension.js'); + + const res1 = parseDidacticsFile(file1); + const res2 = parseDidacticsFile(file2); + + const f1 = fs.readFileSync(file1, 'utf8'); + const f2 = fs.readFileSync(file2, 'utf8'); + + const arr1Text = extractLessonArray(f1, 'const BISAYA_LESSONS_24_43_BASE') || extractLessonArray(f1, 'BISAYA_LESSONS_24_43') || extractLessonArray(f1, 'BISAYA_LESSONS_24_43_BASE ='); + const arr2Text = extractLessonArray(f2, 'export const BISAYA_PHASE3_LESSONS') || extractLessonArray(f2, 'BISAYA_PHASE3_LESSONS'); + + const a1 = arr1Text ? countExamLessonsFromArrayText(arr1Text) : { examCount: 0, total: 0 }; + const a2 = arr2Text ? countExamLessonsFromArrayText(arr2Text) : { examCount: 0, total: 0 }; + + console.log('--- BISAYA 24-43 didactics corePatterns per key ---'); + Object.entries(res1.counts).sort((a,b)=>b[1]-a[1]).forEach(([k,v])=>console.log(`${k}: ${v}`)); + console.log(`Total corePatterns (24-43 files): ${res1.total}`); + console.log(`Lessons parsed in array (approx): ${a1.total}, exam-like lessons (type=review or title contains checkpoint/test/abschluss): ${a1.examCount}`); + + console.log('\n--- BISAYA Phase3 didactics corePatterns per key ---'); + Object.entries(res2.counts).sort((a,b)=>b[1]-a[1]).forEach(([k,v])=>console.log(`${k}: ${v}`)); + console.log(`Total corePatterns (phase3 file): ${res2.total}`); + console.log(`Lessons parsed in phase3 array (approx): ${a2.total}, exam-like lessons: ${a2.examCount}`); +} + +analyze(); diff --git a/backend/scripts/analyze-bisaya-courses.js b/backend/scripts/analyze-bisaya-courses.js new file mode 100644 index 0000000..a895b2b --- /dev/null +++ b/backend/scripts/analyze-bisaya-courses.js @@ -0,0 +1,95 @@ +const fs = require('fs'); +const path = require('path'); + +function extractBlocks(content) { + // Find occurrences like 'Key': { ... corePatterns: [ ... ] ... } + const re = /['"]([^'"]+)['"]\s*:\s*\{([\s\S]*?)\n\s*\}/g; + const blocks = {}; + let m; + while ((m = re.exec(content))) { + blocks[m[1]] = m[2]; + } + return blocks; +} + +function countCorePatternsInBlock(blockText) { + const cpRe = /corePatterns\s*:\s*\[([\s\S]*?)\]/g; + let m = cpRe.exec(blockText); + if (!m) return 0; + const arrText = m[1]; + const targetRe = /\btarget\s*:/g; + return (arrText.match(targetRe) || []).length; +} + +function parseDidacticsFile(filePath) { + const content = fs.readFileSync(filePath, 'utf8'); + const blocks = extractBlocks(content); + const counts = {}; + let total = 0; + for (const [k, v] of Object.entries(blocks)) { + const c = countCorePatternsInBlock(v); + if (c > 0) { + counts[k] = c; + total += c; + } + } + return { counts, total }; +} + +function extractLessonArray(content, varName) { + // crude: find 'const VAR = [' up to closing '];' + const re = new RegExp(varName.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') + "\\s*=\\s*\\[([\\s\\S]*?)\\];", 'm'); + const m = re.exec(content); + if (!m) return null; + return m[1]; +} + +function countExamLessonsFromArrayText(arrayText) { + // Count objects with type:'review' or title containing checkpoint/test/abschluss (case-insensitive) + const objRe = /\{([\s\S]*?)\}(?=\s*,|$)/g; + let m; + let examCount = 0; + let total = 0; + while ((m = objRe.exec(arrayText))) { + total++; + const obj = m[1]; + const typeMatch = /type\s*:\s*['"](\w+)['"]/.exec(obj); + const titleMatch = /title\s*:\s*['"]([\s\S]*?)['"]/.exec(obj); + const type = typeMatch ? typeMatch[1] : ''; + const title = titleMatch ? titleMatch[1].toLowerCase() : ''; + if (type === 'review' || /checkpoint|test|abschluss|prüfung|check/i.test(title)) { + examCount++; + } + } + return { examCount, total }; +} + +function analyze() { + const repoRoot = path.join(__dirname); + const file1 = path.join(repoRoot, 'bisaya-course-plan-24-43.js'); + const file2 = path.join(repoRoot, 'bisaya-course-phase3-extension.js'); + + const res1 = parseDidacticsFile(file1); + const res2 = parseDidacticsFile(file2); + + const f1 = fs.readFileSync(file1, 'utf8'); + const f2 = fs.readFileSync(file2, 'utf8'); + + const arr1Text = extractLessonArray(f1, 'const BISAYA_LESSONS_24_43_BASE') || extractLessonArray(f1, 'BISAYA_LESSONS_24_43') || extractLessonArray(f1, 'BISAYA_LESSONS_24_43_BASE ='); + const arr2Text = extractLessonArray(f2, 'export const BISAYA_PHASE3_LESSONS') || extractLessonArray(f2, 'BISAYA_PHASE3_LESSONS'); + + const a1 = arr1Text ? countExamLessonsFromArrayText(arr1Text) : { examCount: 0, total: 0 }; + const a2 = arr2Text ? countExamLessonsFromArrayText(arr2Text) : { examCount: 0, total: 0 }; + + console.log('--- BISAYA 24-43 didactics corePatterns per key ---'); + Object.entries(res1.counts).sort((a,b)=>b[1]-a[1]).forEach(([k,v])=>console.log(`${k}: ${v}`)); + console.log(`Total corePatterns (24-43 files): ${res1.total}`); + console.log(`Lessons parsed in array (approx): ${a1.total}, exam-like lessons (type=review or title contains checkpoint/test/abschluss): ${a1.examCount}`); + + console.log('\n--- BISAYA Phase3 didactics corePatterns per key ---'); + Object.entries(res2.counts).sort((a,b)=>b[1]-a[1]).forEach(([k,v])=>console.log(`${k}: ${v}`)); + console.log(`Total corePatterns (phase3 file): ${res2.total}`); + console.log(`Lessons parsed in phase3 array (approx): ${a2.total}, exam-like lessons: ${a2.examCount}`); +} + +analyze(); diff --git a/backend/scripts/bisaya-course-plan-24-43.js b/backend/scripts/bisaya-course-plan-24-43.js index 2e30dd5..e2dc408 100644 --- a/backend/scripts/bisaya-course-plan-24-43.js +++ b/backend/scripts/bisaya-course-plan-24-43.js @@ -186,6 +186,7 @@ const BISAYA_DIDACTICS_24_43_ENRICHMENTS = { corePatterns: [ { target: 'Ayaw kalimot.', gloss: 'Vergiss es nicht.' }, { target: 'Hugas sa kamot.', gloss: 'Wasch dir die Hände.' }, + { target: 'Kumusta imong tulog?', gloss: 'Wie hast du geschlafen?' }, { target: 'Patya ang suga.', gloss: 'Mach das Licht aus.' } ], grammarFocus: [ @@ -216,6 +217,7 @@ const BISAYA_DIDACTICS_24_43_ENRICHMENTS = { corePatterns: [ { target: 'Hugas sa nawong.', gloss: 'Wasch dein Gesicht.' }, { target: 'Kuhaa imong bag.', gloss: 'Hol deine Tasche.' }, + { target: 'Kumusta imong tulog?', gloss: 'Wie hast du geschlafen?' }, { target: 'Mogawas na ta.', gloss: 'Wir gehen jetzt raus.' } ], speakingPrompts: [ diff --git a/backend/scripts/count-per-lesson-24-43.mjs b/backend/scripts/count-per-lesson-24-43.mjs new file mode 100644 index 0000000..296ae72 --- /dev/null +++ b/backend/scripts/count-per-lesson-24-43.mjs @@ -0,0 +1,25 @@ +import { BISAYA_LESSONS_24_43_BY_NUMBER, BISAYA_DIDACTICS_24_43 } from './bisaya-course-plan-24-43.js'; + +const out = []; +for (let n = 24; n <= 43; n++) { + const lesson = BISAYA_LESSONS_24_43_BY_NUMBER[n]; + if (!lesson) { + out.push({ lesson: n, title: null, corePatterns: 0 }); + continue; + } + const title = lesson.title; + const didactic = BISAYA_DIDACTICS_24_43[title]; + let count = 0; + if (didactic && didactic.corePatterns) { + count += didactic.corePatterns.length; + } + out.push({ lesson: n, title, corePatterns: count }); +} + +// Print nicely +for (const r of out) { + console.log(`Lektion ${r.lesson}: ${r.title || '[nicht definiert]'} — ${r.corePatterns} corePatterns`); +} + +const total = out.reduce((s,r)=>s+r.corePatterns,0); +console.log(`\nGesamt corePatterns (Lektionen 24–43 Summe): ${total}`); diff --git a/backend/scripts/generate-lesson-checkpoints.cjs b/backend/scripts/generate-lesson-checkpoints.cjs new file mode 100644 index 0000000..2f2323b --- /dev/null +++ b/backend/scripts/generate-lesson-checkpoints.cjs @@ -0,0 +1,56 @@ +const fs = require('fs'); +const path = require('path'); +const vm = require('vm'); + +function extractLessonArray(content, varNameCandidates) { + for (const varName of varNameCandidates) { + const re = new RegExp(varName.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') + "\\s*=\\s*\\[([\\s\\S]*?)\\];", 'm'); + const m = re.exec(content); + if (m) return m[1]; + } + return null; +} + +function evalArrayText(arrayText) { + const wrapped = '[' + arrayText + ']'; + const script = new vm.Script(wrapped, { filename: 'array.js' }); + const context = vm.createContext({}); + return script.runInContext(context); +} + +function loadLessons(filePath, varCandidates) { + const content = fs.readFileSync(filePath, 'utf8'); + const arrText = extractLessonArray(content, varCandidates); + if (!arrText) return []; + const arr = evalArrayText(arrText); + return arr; +} + +function main() { + const repo = path.join(__dirname); + const file1 = path.join(repo, 'bisaya-course-plan-24-43.js'); + const file2 = path.join(repo, 'bisaya-course-phase3-extension.js'); + + const lessons1 = loadLessons(file1, ['const BISAYA_LESSONS_24_43_BASE', 'BISAYA_LESSONS_24_43_BASE', 'BISAYA_LESSONS_24_43']); + const lessons2 = loadLessons(file2, ['export const BISAYA_PHASE3_LESSONS', 'BISAYA_PHASE3_LESSONS']); + + const mapping = {}; + for (const l of lessons1) { + if (l && typeof l.num === 'number') mapping[l.num] = true; + } + for (const l of lessons2) { + if (l && typeof l.num === 'number') mapping[l.num] = true; + } + + // Also include any missing range 24..63 + for (let n = 24; n <= 63; n++) mapping[n] = true; + + const outDir = path.join(__dirname, '..', 'data'); + if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true }); + const outFile = path.join(outDir, 'lesson-checkpoints.json'); + fs.writeFileSync(outFile, JSON.stringify(mapping, null, 2)); + console.log('Wrote', outFile); + console.log('Marked lessons:', Object.keys(mapping).length); +} + +main();