Files
magistr/DYNAMIC_SCHEDULE_IMPLEMENTATION.md

312 lines
15 KiB
Markdown
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.
# Реализация динамического расписания
Этот файл фиксирует текущий результат внедрения динамической системы расписания: что уже добавлено в проект, как связаны база данных, backend, API и frontend, как работает генератор расписания и какие ограничения остаются для следующего этапа.
## Статус внедрения
Реализован первый рабочий срез динамического расписания. Новая модель теперь является базовой схемой проекта и создаётся сразу в `V1__init.sql`.
Старые таблицы `lessons` и `schedule_data`, старые контроллеры, DTO, модели, репозитории и админские вызовы старых API удалены. Новая модель расписания является единственным базовым способом хранения и генерации расписания.
## Что добавлено
### База данных
Динамическая схема встроена в базовую Flyway-миграцию `V1__init.sql`.
Она создаёт следующие таблицы:
| Таблица | Назначение |
|---------|------------|
| `time_slots` | Настраиваемая сетка пар: номер, время начала, время окончания, длительность |
| `academic_years` | Учебные годы |
| `semesters` | Семестры учебного года, от `start_date` считается первая неделя |
| `holidays` | Праздники и исключённые даты, когда занятия не проводятся |
| `academic_calendar_matrix` | Матрица учебного графика по семестру, курсу, специальности и неделе |
| `schedule_rules` | Правила проведения дисциплины с лимитом академических часов |
| `schedule_rule_groups` | Привязка правила к одной или нескольким группам |
| `schedule_rule_slots` | Конкретные шаблонные слоты правила: день, чётность, пара, преподаватель, аудитория, тип и формат |
Миграция также создаёт тестовые данные сразу в новой модели:
1. Создаёт базовые временные слоты.
2. Создаёт учебные годы `2024-2025` и `2025-2026`.
3. Заполняет матрицу учебного графика базовым статусом `THEORY`.
4. Создаёт тестовые `schedule_rules` для групп и дисциплин.
5. Создаёт привязки групп в `schedule_rule_groups`, включая потоковую лекцию.
6. Создаёт шаблонные занятия в `schedule_rule_slots`.
### Backend
Добавлены JPA-модели для новой схемы:
- `TimeSlot`
- `AcademicYear`
- `Semester`
- `Holiday`
- `AcademicCalendarMatrix`
- `ScheduleRule`
- `ScheduleRuleSlot`
- `AcademicActivityType`
- `ScheduleParity`
Добавлены DTO для API:
- `TimeSlotDto`
- `AcademicYearDto`
- `SemesterDto`
- `HolidayDto`
- `AcademicCalendarMatrixDto`
- `ScheduleRuleDto`
- `ScheduleRuleSlotDto`
- `RenderedLessonDto`
Добавлены репозитории для новых таблиц:
- `TimeSlotRepository`
- `AcademicYearRepository`
- `SemesterRepository`
- `HolidayRepository`
- `AcademicCalendarMatrixRepository`
- `ScheduleRuleRepository`
- `ScheduleRuleSlotRepository`
Добавлены сервисы:
| Сервис | Назначение |
|--------|------------|
| `AcademicDateService` | Календарная математика: семестр по дате, номер недели, чётность, праздник, курс группы, тип активности |
| `ScheduleGeneratorService` | Генерация расписания группы или преподавателя на диапазон дат |
Старый ручной слой занятий удалён: в backend больше нет `LessonsController`, `ScheduleDataController`, сущностей `Lesson`/`ScheduleData`, их DTO, репозиториев и валидаторов старых строк расписания.
### API
Добавлен новый endpoint просмотра расписания:
```http
GET /api/schedule?groupId=1&startDate=2026-04-27&endDate=2026-05-03
GET /api/schedule?teacherId=2&startDate=2026-04-27&endDate=2026-05-03
```
Правило запроса: нужно передать ровно один идентификатор — `groupId` или `teacherId`.
Ответ — массив `RenderedLessonDto`, где у каждого занятия есть:
- полная дата занятия;
- номер дня недели;
- название дня;
- номер недели семестра;
- чётность;
- временной слот;
- дисциплина;
- преподаватель;
- аудитория;
- тип и формат занятия;
- группы;
- лимит часов правила;
- уже списанные часы перед занятием;
- остаток часов после занятия.
Добавлены административные API:
| API | Назначение |
|-----|------------|
| `/api/admin/time-slots` | CRUD временных слотов |
| `/api/admin/calendar/years` | CRUD учебных годов |
| `/api/admin/calendar/years/{academicYearId}/semesters` | Семестры учебного года |
| `/api/admin/calendar/semesters/{id}` | Обновление семестра |
| `/api/admin/calendar/holidays` | CRUD праздников |
| `/api/admin/calendar/matrix` | Получение и массовое сохранение матрицы учебного графика |
| `/api/admin/schedule-rules` | CRUD правил расписания |
### Авторизация
Ответ `POST /api/auth/login` расширен полем `userId`.
Frontend сохраняет его в `localStorage`, чтобы кабинет преподавателя мог запрашивать личное расписание через:
```http
GET /api/schedule?teacherId={userId}&startDate=...&endDate=...
```
### Frontend
Заглушки кабинетов заменены на базовые экраны просмотра расписания.
Админский экран `schedule` показывает текущие `schedule_rules` и сетку `time_slots` через новые admin API.
#### Кабинет студента
Файл: `frontend/student/index.html`.
Текущая модель пользователя-студента не содержит прямой связи с учебной группой, поэтому экран работает через выбор группы:
1. Загружает список групп из `/api/groups`.
2. Сохраняет выбранную группу в `localStorage.studentGroupId`.
3. Строит недельный диапазон дат.
4. Запрашивает расписание через `GET /api/schedule?groupId=...`.
5. Показывает занятия по дням недели.
#### Кабинет преподавателя
Файл: `frontend/teacher/index.html`.
Экран использует `localStorage.userId`, который появляется после нового ответа авторизации:
1. Определяет текущую неделю.
2. Позволяет переключать неделю стрелками или через выбор даты.
3. Запрашивает расписание через `GET /api/schedule?teacherId=...`.
4. Показывает занятия преподавателя в недельной сетке.
5. В занятии отображаются дисциплина, время, тип, аудитория и группы.
## Как работает генерация расписания
Генерация выполняется на backend по запросу клиента. Фактические занятия не хранятся отдельными строками на каждую дату, а вычисляются из правил.
### Расписание группы
Алгоритм `buildScheduleForGroup(groupId, startDate, endDate)`:
1. Проверяет корректность диапазона дат.
2. Находит группу.
3. Для каждой даты диапазона определяет семестр.
4. Вычисляет номер недели семестра.
5. Вычисляет текущий курс группы по `year_start_study`.
6. Проверяет матрицу учебного графика.
7. Пропускает дату, если это праздник или не `THEORY`.
8. Загружает правила группы для найденного семестра.
9. Для каждого правила считает, сколько академических часов уже проведено до текущей даты.
10. Если лимит `total_academic_hours` ещё не исчерпан, проецирует подходящие слоты правила на текущую дату.
### Расписание преподавателя
Алгоритм `buildScheduleForTeacher(teacherId, startDate, endDate)` похож, но правила ищутся не по группе, а по преподавателю в `schedule_rule_slots.teacher_id`.
В ответе занятие дополнительно содержит все группы, привязанные к правилу. Это поддерживает потоковые лекции.
### Чётность недели
Чётность вычисляется от `semesters.start_date`.
Настройка:
```properties
app.schedule.even-week-is-upper=false
```
По умолчанию:
- нечётная неделя семестра соответствует `ODD`;
- чётная неделя семестра соответствует `EVEN`.
Если `app.schedule.even-week-is-upper=true`, соответствие инвертируется.
### Праздники
Праздник считается пропуском занятия:
- занятие не отображается в расписании;
- академические часы не списываются;
- дисциплина фактически продолжается дольше, пока не будет исчерпан лимит часов.
### Лимит часов
Каждый `schedule_rules.total_academic_hours` задаёт общий лимит академических часов дисциплины.
Генератор симулирует период от `active_from_date` до запрошенной даты и считает только реально проведённые слоты:
- один слот = 2 академических часа;
- праздники не считаются;
- недели с активностью не `THEORY` не считаются;
- после достижения лимита правило перестаёт выводиться.
## Поток данных
Новая базовая БД сразу создаёт рабочую модель так:
```text
V1__init.sql
-> time_slots
-> academic_years / semesters / holidays / academic_calendar_matrix
-> schedule_rules
-> schedule_rule_groups
-> schedule_rule_slots
```
Для просмотра:
```text
frontend
-> GET /api/schedule
-> ScheduleController
-> ScheduleGeneratorService
-> schedule_rules + schedule_rule_slots + calendar tables
-> RenderedLessonDto[]
```
## Что проверено
Проверка backend:
```bash
docker run --rm -v /mnt/HDD/magistr/magistr/backend:/app -w /app maven:3.9-eclipse-temurin-17 mvn -q -DskipTests compile
```
Результат: компиляция backend прошла успешно.
Проверка миграции:
1. Поднят временный PostgreSQL-контейнер на образе `postgres:alpine3.23`.
2. Применён `V1__init.sql`.
3. Проверены контрольные количества строк новой модели.
Контрольный результат:
| Объект | Количество |
|--------|------------|
| `schedule_rules` | 4 |
| `schedule_rule_groups` | 5 |
| `schedule_rule_slots` | 4 |
| `time_slots` | 7 |
| `academic_calendar_matrix` | 132 |
| `lessons` | таблица отсутствует |
| `schedule_data` | таблица отсутствует |
Также выполнен:
```bash
git diff --check
```
Результат: замечаний по whitespace нет.
## Ограничения текущего среза
Этот срез создаёт рабочее ядро динамического расписания, но ещё не закрывает весь продуктовый объём.
Ограничения:
- строгая проверка конфликтов преподавателей, аудиторий и групп при сохранении правил ещё не реализована;
- студент пока выбирает группу вручную, потому что в модели пользователя нет связи `student -> group`.
## Следующий этап
Рекомендуемый следующий этап:
1. Реализовать backend-валидацию конфликтов:
- преподаватель не может вести разные занятия одновременно;
- аудитория не может быть занята двумя разными занятиями одновременно;
- группа не может быть на двух занятиях одновременно, кроме корректного деления по подгруппам.
2. Добавить модель связи студента с учебной группой, чтобы кабинет студента не требовал ручного выбора группы.
3. Добавить отдельное управление подгруппами и подключить выбор `subgroupId` в конструкторе правил.
## Связанные документы
- `SCHEDULE_PROPOSAL.md` — исходная концепция динамического расписания.
- `SCHEDULE_TASKS.md` — исходная декомпозиция задач.
- `docs/API.md` — описание REST API.
- `docs/DATABASE.md` — описание схемы БД.
- `docs/BUSINESS_LOGIC.md` — бизнес-правила.
- `docs/FRONTEND.md` — frontend-архитектура.