Files
psu-schedule/server/timetable.js
2026-10-17 04:01:58 +03:00

102 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// A group's timetable by date, with week numbers already applied. Native and
// 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";
const MAX_DAYS = 62;
function lessonOut(lesson, group, people, date, status, calendar) {
const bell = calendar?.bells.find((b) => b.start === lesson.start);
const rooms = date ? roomsOn(people, date) : people.rooms;
return {
key: lessonKey(lesson, group),
start: lesson.start,
end: lesson.end || bell?.end || null,
pair: bell?.pair ?? null,
subject: lesson.subject,
type: lesson.type,
teachers: people.teachers,
rooms,
roomMoved: Boolean(date && rooms !== people.rooms),
distance: people.distance,
condition: lesson.condition,
variant: lesson.variant,
subgroup: lesson.subgroups.find((s) => s.group === group)?.label || null,
groups: lesson.groups,
uncertain: status === "maybe",
raw: lesson.raw,
};
}
/**
* 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.
*/
export function timetable({ file, schedule, calendar, group, from, days }) {
if (!schedule.groups.includes(group)) {
const error = new Error(`В документе нет группы ${group}.`);
error.status = 404;
throw error;
}
const count = Math.min(Math.max(Number(days) || 7, 1), MAX_DAYS);
const lessons = schedule.lessons.filter((l) => l.groups.includes(group));
const entries = lessons.map((lesson) => ({
doc: { id: file.id, schedule },
lesson,
...peopleAndRooms(lesson, schedule.academicYear),
}));
const view = semesterView(calendar, schedule, from);
const views = new Map();
const out = [];
for (let i = 0; i < count; i++) {
const date = addDays(from, i);
const week = view ? view.weeks[weekIndexOf(view, date)] || null : null;
const dayLessons = [];
if (view)
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.sort((a, b) => a.start.localeCompare(b.start));
out.push({
date,
weekday: weekday(date),
week: week ? { n: week.n, color: week.color } : null,
lessons: dayLessons,
});
}
const weekly = [1, 2, 3, 4, 5, 6, 7]
.map((day) => ({
day,
lessons: entries
.filter((e) => e.lesson.day === day && !e.lesson.date)
.map((e) => lessonOut(e.lesson, group, e, null, "yes", calendar))
.sort((a, b) => a.start.localeCompare(b.start)),
}))
.filter((d) => d.lessons.length);
return {
group,
document: {
id: file.id,
name: file.name,
faculty: file.faculty || schedule.source.faculty || "",
course: file.course || schedule.source.course || "",
modified: file.modified || schedule.source.modified || null,
},
period: schedule.period,
semester: view?.semester || null,
note: view
? null
: "Семестр этого расписания не найден в таблице недель: даты не рассчитаны, смотрите строки Word (weekly).",
subgroups: subgroupsOf(schedule, group),
bells: calendar?.bells || [],
from,
days: out,
weekly,
notes: schedule.notes,
generatedAt: new Date().toISOString(),
};
}