feat: Some more parsers in the Absence
All checks were successful
Remote Deploy / deploy (push) Successful in 4s
All checks were successful
Remote Deploy / deploy (push) Successful in 4s
This commit is contained in:
@@ -159,7 +159,7 @@ export default async function parseThisShit(downloadedFilePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = parseAbsence(value, teacherMap);
|
const data = parseAbsence(value, teacherMap);
|
||||||
final[finalIndex]["ABSENCE"].push(data);
|
final[finalIndex]["ABSENCE"].push(...data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ export default async function parseThisShit(downloadedFilePath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.writeFileSync("db/current.json", JSON.stringify(data));
|
fs.writeFileSync("db/current.json", JSON.stringify(data, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseThisShit("downloads/table.xlsx")
|
parseThisShit("downloads/table.xlsx")
|
||||||
|
|||||||
@@ -1,106 +1,137 @@
|
|||||||
export default function parseAbsence(str, teacherMap) {
|
const LAST_HOUR = 10;
|
||||||
const LAST_HOUR = 10;
|
|
||||||
|
|
||||||
const s = (str ?? "").trim().replace(/\s+/g, " ");
|
// -------------------------------
|
||||||
const result = {
|
// Helpers
|
||||||
teacher: null, // String or null
|
// -------------------------------
|
||||||
teacherCode: null, // String or null
|
export const cleanInput = (input) => (input ?? "").trim().replace(/\s+/g, " ");
|
||||||
type: null, // "wholeDay" | "range" | "single" | "invalid"
|
export const isTeacherToken = (t) => /^[A-Za-z]+$/.test(t);
|
||||||
hours: null // { from, to } or number
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!s) {
|
export const parseSpec = (spec) => {
|
||||||
result.type = "invalid";
|
if (!spec) return null;
|
||||||
result.original = s;
|
let m;
|
||||||
return result;
|
if ((m = spec.match(/^(\d+)\+$/))) {
|
||||||
|
const from = Number(m[1]);
|
||||||
|
return from >= 1 && from <= LAST_HOUR
|
||||||
|
? { kind: "range", value: { from, to: LAST_HOUR } }
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
if ((m = spec.match(/^(\d+)-(\d+)$/))) {
|
||||||
|
const from = Number(m[1]);
|
||||||
|
const to = Number(m[2]);
|
||||||
|
return from >= 1 && to >= from && to <= LAST_HOUR
|
||||||
|
? { kind: "range", value: { from, to } }
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
if ((m = spec.match(/^(\d+)$/))) {
|
||||||
|
const hour = Number(m[1]);
|
||||||
|
return hour >= 1 && hour <= LAST_HOUR
|
||||||
|
? { kind: "single", value: hour }
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resolveTeacher = (teacherCode, teacherMap = {}) => ({
|
||||||
|
code: teacherCode,
|
||||||
|
name: teacherMap?.[teacherCode.toLowerCase()] ?? null,
|
||||||
|
});
|
||||||
|
|
||||||
|
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 };
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// Teacher list processing (modular)
|
||||||
|
// -------------------------------
|
||||||
|
const processTeacherList = (teacherListStr, spec, teacherMap) => {
|
||||||
|
let results = [];
|
||||||
|
|
||||||
|
if (teacherListStr.includes(",")) {
|
||||||
|
// Comma = spec applies to all
|
||||||
|
const teachers = teacherListStr.split(/\s*,\s*/).filter(Boolean);
|
||||||
|
results = teachers.map((t) => makeResult(t, spec, teacherMap));
|
||||||
|
} else if (teacherListStr.includes(";")) {
|
||||||
|
// Semicolon = spec applies only to last, others = whole day
|
||||||
|
const teachers = teacherListStr.split(/\s*;\s*/).filter(Boolean);
|
||||||
|
teachers.forEach((t, i) => {
|
||||||
|
const resSpec = i === teachers.length - 1 ? spec : null;
|
||||||
|
results.push(makeResult(t, resSpec, teacherMap));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Single teacher
|
||||||
|
results.push(makeResult(teacherListStr, spec, teacherMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTeacher = (t) => /^[A-Za-z]+$/.test(t);
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
const parseSpec = (spec) => {
|
// -------------------------------
|
||||||
let m;
|
// Main parser
|
||||||
// "5+" -> range 5..LAST_HOUR
|
// -------------------------------
|
||||||
if ((m = spec.match(/^(\d+)\+$/))) {
|
export default function parseAbsence(input, teacherMap = {}) {
|
||||||
const from = parseInt(m[1], 10);
|
const s = cleanInput(input);
|
||||||
if (from >= 1 && from <= LAST_HOUR) {
|
if (!s) return [];
|
||||||
return { kind: "range", value: { from, to: LAST_HOUR } };
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// "5-7" -> range 5..7
|
|
||||||
if ((m = spec.match(/^(\d+)-(\d+)$/))) {
|
|
||||||
const from = parseInt(m[1], 10);
|
|
||||||
const to = parseInt(m[2], 10);
|
|
||||||
if (from >= 1 && to >= 1 && from <= to && to <= LAST_HOUR) {
|
|
||||||
return { kind: "range", value: { from, to } };
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// "5" -> single 5
|
|
||||||
if ((m = spec.match(/^(\d+)$/))) {
|
|
||||||
const hour = parseInt(m[1], 10);
|
|
||||||
if (hour >= 1 && hour <= LAST_HOUR) {
|
|
||||||
return { kind: "single", value: hour };
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const parts = s.split(" ");
|
const results = [];
|
||||||
if (parts.length === 1) {
|
const consumed = [];
|
||||||
const t = parts[0];
|
const markConsumed = (start, end) => consumed.push([start, end]);
|
||||||
if (isTeacher(t)) {
|
const isConsumed = (i) => consumed.some(([a, b]) => i >= a && i < b);
|
||||||
result.teacherCode = t;
|
|
||||||
result.teacher = teacherMap?.[t.toLowerCase()] ?? null;
|
// Regex: teacher-list [+ optional spec]
|
||||||
result.type = "wholeDay";
|
const teacherListThenSpecRe = /([A-Za-z]+(?:[,;]\s?[A-Za-z]+)*)(?:\s*)?(\d+(?:\+|-\d+)?)/g;
|
||||||
return result;
|
let m;
|
||||||
}
|
while ((m = teacherListThenSpecRe.exec(s)) !== null) {
|
||||||
result.type = "invalid";
|
const matchStart = m.index;
|
||||||
result.original = s;
|
const matchEnd = teacherListThenSpecRe.lastIndex;
|
||||||
return result;
|
if (isConsumed(matchStart)) continue;
|
||||||
|
|
||||||
|
const [_, teacherListStr, specStr] = m;
|
||||||
|
const spec = parseSpec(specStr);
|
||||||
|
if (!spec) continue;
|
||||||
|
|
||||||
|
results.push(...processTeacherList(teacherListStr, spec, teacherMap));
|
||||||
|
markConsumed(matchStart, matchEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parts.length === 2) {
|
// Standalone teachers → whole day
|
||||||
const [a, b] = parts;
|
const teacherOnlyRe = /([A-Za-z]+(?:[,;]\s?[A-Za-z]+)*)/g;
|
||||||
|
while ((m = teacherOnlyRe.exec(s)) !== null) {
|
||||||
|
const matchStart = m.index;
|
||||||
|
const matchEnd = teacherOnlyRe.lastIndex;
|
||||||
|
if (isConsumed(matchStart)) continue;
|
||||||
|
|
||||||
// Teacher first: "Nm 5", "Nm 5-7", "Nm 5+"
|
const tList = m[1].split(/[,;]\s*/).filter(Boolean);
|
||||||
if (isTeacher(a)) {
|
tList.forEach((t) => {
|
||||||
const spec = parseSpec(b);
|
if (isTeacherToken(t)) results.push(makeResult(t, null, teacherMap));
|
||||||
if (!spec) {
|
else
|
||||||
result.type = "invalid";
|
results.push({
|
||||||
result.original = s;
|
type: "invalid",
|
||||||
return result;
|
teacher: null,
|
||||||
}
|
teacherCode: null,
|
||||||
result.teacherCode = a;
|
hours: null,
|
||||||
result.teacher = teacherMap?.[a.toLowerCase()] ?? null;
|
original: t,
|
||||||
result.type = spec.kind === "range" ? "range" : "single";
|
});
|
||||||
result.hours = spec.value;
|
});
|
||||||
return result;
|
markConsumed(matchStart, matchEnd);
|
||||||
}
|
|
||||||
|
|
||||||
// Teacher last: "5 Nm", "5-7 Nm", "5+ Nm"
|
|
||||||
if (isTeacher(b)) {
|
|
||||||
const spec = parseSpec(a);
|
|
||||||
if (!spec) {
|
|
||||||
result.type = "invalid";
|
|
||||||
result.original = s;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
result.teacherCode = b;
|
|
||||||
result.teacher = teacherMap?.[b.toLowerCase()] ?? null;
|
|
||||||
result.type = spec.kind === "range" ? "range" : "single";
|
|
||||||
result.hours = spec.value;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.type = "invalid";
|
|
||||||
result.original = s;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anything else is invalid
|
// Bare specs without teacher → invalid
|
||||||
result.type = "invalid";
|
const specOnlyRe = /\b(\d+(?:\+|-\d+)?)\b/g;
|
||||||
result.original = s;
|
while ((m = specOnlyRe.exec(s)) !== null) {
|
||||||
return result;
|
const matchStart = m.index;
|
||||||
|
if (isConsumed(matchStart)) continue;
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
type: "invalid",
|
||||||
|
teacher: null,
|
||||||
|
teacherCode: null,
|
||||||
|
hours: null,
|
||||||
|
original: m[1],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user