feat(parser): update group parsing to handle special cases and improve regex patterns

feat(parser): enhance details extraction for lessons, including handling of subgroups and room moves

feat(timetable): integrate room and teacher retrieval based on week context

test(parser): add tests for group parsing with special characters and room move scenarios

test(formats): implement tests for various lesson formats and their expected outputs

feat(rooms): introduce room management functions for handling moves and week-based plans
This commit is contained in:
2026-10-21 00:34:06 +03:00
parent 3ae7b38fca
commit 67bb2ea6fb
9 changed files with 468 additions and 76 deletions

View File

@@ -2,7 +2,7 @@
// third-party clients use it instead of re-implementing the white/green week rules.
import { semesterView, addDays, weekday, weekIndexOf } from "../shared/weeks.js";
import { lessonKey, subgroupsOf } from "../shared/lessons.js";
import { occurrence, peopleAndRooms, roomsOn } from "./directory.js";
import { occurrence, peopleAndRooms, roomsOn, teachersOn, movedOn } from "./directory.js";
import { apiError } from "./errors.js";
const MAX_DAYS = 62;
@@ -10,9 +10,10 @@ const MAX_DAYS = 62;
const MAX_SEMESTER_DAYS = 200;
const DAY_MS = 86_400_000;
function lessonOut(lesson, group, people, date, status, calendar) {
// On a date: the room and teacher of that day and week ("1-9н. 502н", a move on 26.10).
function lessonOut(lesson, group, people, date, status, calendar, week = null) {
const bell = calendar?.bells.find((b) => b.start === lesson.start);
const rooms = date ? roomsOn(people, date) : people.rooms;
const rooms = date ? roomsOn(people, date, week) : people.rooms;
return {
key: lessonKey(lesson, group),
start: lesson.start,
@@ -20,9 +21,9 @@ function lessonOut(lesson, group, people, date, status, calendar) {
pair: bell?.pair ?? null,
subject: lesson.subject,
type: lesson.type,
teachers: people.teachers,
teachers: date ? teachersOn(people, week) : people.teachers,
rooms,
roomMoved: Boolean(date && rooms !== people.rooms),
roomMoved: Boolean(date && movedOn(people, date)),
distance: people.distance,
condition: lesson.condition,
variant: lesson.variant,
@@ -37,9 +38,23 @@ function lessonOut(lesson, group, people, date, status, calendar) {
* days: one entry per date from `from`, with the lessons that take place that day.
* weekly: the Word rows by weekday, for when dates cannot be calculated.
*/
const groupKey = (g) => g.toLowerCase().replace(/[\s\-–]/g, "");
export function timetable({ file, schedule, calendar, group, from, days, range, today, session: extra = [] }) {
if (!schedule.groups.includes(group)) {
throw apiError(404, "group_not_found", `В документе нет группы ${group}.`);
// The same group written another way: "24-ИЯ(РА)и-1" for "24-ИЯ (РА)и-1".
const same = schedule.groups.find((g) => groupKey(g) === groupKey(group));
// A name parser 13 split into groups: a client may still keep "24-ИЯ(РА)".
const split = schedule.groups.filter((g) => groupKey(g).startsWith(groupKey(group)));
if (same) group = same;
else
throw apiError(
404,
"group_not_found",
split.length
? `Группа ${group} теперь разделена: ${split.join(", ")}. Выберите свою в списке групп.`
: `В документе нет группы ${group}.`,
);
}
let count = Math.min(Math.max(Number(days) || 7, 1), MAX_DAYS);
const lessons = schedule.lessons.filter((l) => l.groups.includes(group));
@@ -90,7 +105,7 @@ export function timetable({ file, schedule, calendar, group, from, days, range,
for (const entry of entries) {
const status = occurrence(entry, date, calendar, views);
if (status === "yes" || status === "maybe")
dayLessons.push(lessonOut(entry.lesson, group, entry, date, status, calendar));
dayLessons.push(lessonOut(entry.lesson, group, entry, date, status, calendar, week));
}
// Session events printed with their own date need no week table.
for (const entry of extra)