diff --git a/scrape/utils/parseAbsence.js b/scrape/utils/parseAbsence.js index 1e9e2f6..600e19c 100644 --- a/scrape/utils/parseAbsence.js +++ b/scrape/utils/parseAbsence.js @@ -50,7 +50,7 @@ const makeResult = (teacherCode, spec, teacherMap) => { const { name } = resolveTeacher(teacherCode, teacherMap); const type = spec ? (spec.kind === "range" ? "range" : "single") : "wholeDay"; const hours = spec ? spec.value : null; - return { teacher: name, teacherCode, type, hours }; + return { teacher: name, teacherCode: teacherCode.toLowerCase(), type, hours }; }; // ------------------------------- @@ -58,18 +58,15 @@ const makeResult = (teacherCode, spec, teacherMap) => { // ------------------------------- const processTeacherList = (teacherListStr, spec, teacherMap) => { let results = []; + const teachers = teacherListStr.split(/[,;]\s*/).filter(Boolean); - if (teacherListStr.includes(",")) { - const teachers = teacherListStr.split(/\s*,\s*/).filter(Boolean); - results = teachers.map((t) => makeResult(t, spec, teacherMap)); - } else if (teacherListStr.includes(";")) { - const teachers = teacherListStr.split(/\s*;\s*/).filter(Boolean); + if (teacherListStr.includes(";")) { teachers.forEach((t, i) => { const resSpec = i === teachers.length - 1 ? spec : null; results.push(makeResult(t, resSpec, teacherMap)); }); } else { - results.push(makeResult(teacherListStr, spec, teacherMap)); + results = teachers.map((t) => makeResult(t, spec, teacherMap)); } return results; @@ -104,6 +101,23 @@ export default function parseAbsence(input, teacherMap = {}) { markConsumed(matchStart, matchEnd); } + const teacherExkRe = /([A-Za-z]+)-exk/gi; + while ((m = teacherExkRe.exec(s)) !== null) { + const matchStart = m.index; + const matchEnd = teacherExkRe.lastIndex; + if (isConsumed(matchStart)) continue; + + const teacherCode = m[1]; + const { name } = resolveTeacher(teacherCode, teacherMap); + results.push({ + teacher: name, + teacherCode: teacherCode.toLowerCase(), + type: "exkurze", + hours: null, + }); + markConsumed(matchStart, matchEnd); + } + // Standalone teachers → whole day const teacherOnlyRe = /([A-Za-z]+(?:[,;]\s?[A-Za-z]+)*)/g; while ((m = teacherOnlyRe.exec(s)) !== null) { diff --git a/server.js b/server.js index cbd06fa..15b5580 100644 --- a/server.js +++ b/server.js @@ -20,6 +20,15 @@ app.get('/', async (_, res) => { res.json(data); }); +app.get('/versioned/v1', async (_, res) => { + const dataStr = await fs.readFile(path.join(process.cwd(), "db", "current.json"), "utf8"); + const data = JSON.parse(dataStr); + + data["status"]["currentUpdateSchedule"] = getCurrentInterval(); + + res.json(data); +}); + app.get("/status", async (_, res) => { const dataStr = await fs.readFile(path.resolve("./volume/customState.json"), {encoding: "utf8"}); const data = JSON.parse(dataStr); diff --git a/tests/teachermap.json b/tests/teachermap.json new file mode 100644 index 0000000..b8b9e3c --- /dev/null +++ b/tests/teachermap.json @@ -0,0 +1,71 @@ +{ + "ad": "Bc. Daniel Adámek", + "bo": "Ing. Anna Bodnárová", + "bc": "Ing. Lucie Brčáková", + "bu": "Mgr. Lenka Brůnová", + "bp": "Ing. Peter Budai", + "cf": "RNDr. Mgr. Petr Couf", + "cn": "Ing. Richard Černý, CSc.", + "dk": "Pavel Dočkal, DiS.", + "ex": "Ing. Jana Exnerová", + "ha": "Kateřina Haasová", + "he": "Mgr. Jan Hehl", + "hm": "Doc. Ing. Aleš Herman, PhD.", + "ht": "Ing. Jiří Herout", + "ho": "Adam Horyna", + "hr": "Mgr. Libuše Hrabalová", + "ir": "Mgr. Iulia Iarosciuc", + "ja": "Mgr. et Mgr. Martin Janečka, Ph.D.", + "jk": "David Janoušek", + "jd": "Ing. Jiří Jedlička", + "jr": "Doc. Ing. Vítězslav Jeřábek, CSc.", + "jz": "Mgr. Zbyněk Ježek", + "je": "Mgr. Ondřej Ježil", + "ju": "Ing. Tomáš Juchelka", + "kl": "Ing. Filip Kallmünzer", + "ka": "Ing. Ivana Kantnerová", + "ks": "Mgr. Svatava Klemová", + "kt": "Tomáš Klíma", + "kn": "Mgr. Marie Kmoníčková", + "kr": "Ing. Zdeněk Křída", + "ku": "Ing. Dušan Kuchařík", + "ki": "Mgr. Jan Kuchařík", + "kp": "RNDr. Olga Kvapilová", + "la": "Bc. Marek Lavička", + "lc": "Mgr. Pavel Lopocha", + "ma": "Ing. Ondřej Mandík", + "ms": "Ing. Lukáš Masopust", + "mz": "PhDr. Jakub Mazuch", + "me": "Michaela Meitnerová", + "mo": "Jan Molič", + "mr": "Mgr. Jindřiška Mrázová", + "mu": "Mgr. Martina Mušecová", + "nm": "Vratislav Němec", + "ng": "Mgr. Eva Neugebauerová", + "nv": "Ing. Jan Novotný, Ph.D.", + "pa": "Ing. Bc. Šárka Páltiková", + "pp": "Bc. Adam Papula", + "pv": "Mgr. Jan Pavlát", + "pt": "Ing. Martin Peter", + "pr": "Petr Procházka", + "rk": "RNDr. Luboš Rašek", + "re": "Mgr. Alena Reichlová", + "ry": "Mgr. Jitka Rychlíková", + "so": "Ing. Oleg Sivkov, Ph.D", + "su": "MUDr. Kristina Studénková", + "sy": "Ing. Vladislav Sýkora", + "se": "Matěj Šedivý", + "sd": "Ing. Jana Šedová", + "sv": "Ing. Jan Šváb", + "uh": "Bc. Karolína Uhlířová", + "vd": "Ing. Mgr. Vladimír Váňa, CSc.", + "vm": "Bc. Martin Váňa", + "va": "Ing. Jan Vaněk", + "vl": "Ing. Zdeněk Velich", + "vk": "Ing. Jiří Vlček", + "vc": "Ing. Antonín Vobecký", + "vn": "Ing. Zdeněk Vondra", + "we": "Mgr. David Weber", + "ze": "Ing. Jan Zelenka", + "zn": "Mgr. Tomáš Žilinčár" +} diff --git a/tests/test.js b/tests/test.js new file mode 100644 index 0000000..ed0370c --- /dev/null +++ b/tests/test.js @@ -0,0 +1,261 @@ +import fs from "fs" +import parseAbsence from "../scrape/utils/parseAbsence.js"; + +const teachermap = JSON.parse(fs.readFileSync("./teachermap.json")) + + +test("Me", [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "wholeDay", + hours: null + } +]); + +test("ad", [ + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "wholeDay", + hours: null + } +]); + +test("me ad", [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "wholeDay", + hours: null + }, + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "wholeDay", + hours: null + } +]); + +test("me 3", [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "single", + hours: 3 + } +]); + +test("ad 1", [ + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "single", + hours: 1 + } +]); + +test("me 2-4", [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "range", + hours: { from: 2, to: 4 } + } +]); + +test("ad 5,6", [ + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "range", + hours: { from: 5, to: 6 } + } +]); + +test("me 7+", [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "range", + hours: { from: 7, to: 10 } + } +]); + +test("me,ad 3-5", [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "range", + hours: { from: 3, to: 5 } + }, + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "range", + hours: { from: 3, to: 5 } + } +]); + +test("me;ad 4", [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "wholeDay", + hours: null + }, + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "single", + hours: 4 + } +]); + +test("me;ad;bo 2-3", [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "wholeDay", + hours: null + }, + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "wholeDay", + hours: null + }, + { + teacher: "Ing. Anna Bodnárová", + teacherCode: "bo", + type: "range", + hours: { from: 2, to: 3 } + } +]); + +test("me 2 ad 4-5", [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "single", + hours: 2 + }, + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "range", + hours: { from: 4, to: 5 } + } +]); + +test("3", [ + { + type: "invalid", + teacher: null, + teacherCode: null, + hours: null, + original: "3" + } +]); + +test("2-4", [ + { + type: "invalid", + teacher: null, + teacherCode: null, + hours: null, + original: "2-4" + } +]); + +test("me Xx 3", [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "wholeDay", + hours: null + }, + { teacher: null, teacherCode: 'xx', type: 'single', hours: 3 }, +]); + +test("me,ad;bo 1-2 ad 5+", [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "wholeDay", + hours: null + }, + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "wholeDay", + hours: null + }, + { + teacher: "Ing. Anna Bodnárová", + teacherCode: "bo", + type: "range", + hours: { from: 1, to: 2 } + }, + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "range", + hours: { from: 5, to: 10 } + } +]); + +test("Me-exk", [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "exkurze", + hours: null + } +]) + +function test(input, expectedOutput) { + const res = parseAbsence(input, teachermap); + + if (!deepEqual(res, expectedOutput)) { + console.error("ERROR for input: " + input); + console.log(res); + console.log(expectedOutput); + console.log(); + } +} + +function deepEqual(a, b) { + if (a === b) return true; + + if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) { + return false; + } + + // Handle arrays (ignore order) + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) return false; + + const used = new Array(b.length).fill(false); + + return a.every(itemA => + b.some((itemB, i) => { + if (used[i]) return false; // don't reuse elements + if (deepEqual(itemA, itemB)) { + used[i] = true; // mark element as used + return true; + } + return false; + }) + ); + } + + if (Array.isArray(a) !== Array.isArray(b)) return false; + + const keysA = Object.keys(a); + const keysB = Object.keys(b); + + if (keysA.length !== keysB.length) return false; + + return keysA.every(key => keysB.includes(key) && deepEqual(a[key], b[key])); +}