Files
psu-schedule/server/timetable.js

114 lines
4.2 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;
// A whole semester with its session fits easily; the cap only guards against abuse.
const MAX_SEMESTER_DAYS = 200;
const DAY_MS = 86_400_000;
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, range, today }) {
if (!schedule.groups.includes(group)) {
const error = new Error(`В документе нет группы ${group}.`);
error.status = 404;
throw error;
}
let count = Math.min(Math.max(Number(days) || 7, 1), MAX_DAYS);
// range=semester: every day of the semester `today` falls in, so a client can keep
// the whole term offline. Without a known semester it keeps the plain from/days window.
if (range === "semester") {
const current = semesterView(calendar, schedule, today || from);
if (current) {
from = current.semester.start;
const span = Math.round((Date.parse(current.semester.end) - Date.parse(from)) / DAY_MS) + 1;
count = Math.min(Math.max(span, 1), MAX_SEMESTER_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,
};
}