Files
magistr/DYNAMIC_SCHEDULE_IMPLEMENTATION.md

318 lines
16 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` пока физически остаются в `V1__init.sql`, потому что связанный старый код будет удаляться отдельным шагом. Они больше не заполняются тестовыми данными и не являются источником для новой модели расписания.
## Что добавлено
### База данных
Динамическая схема встроена в базовую 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` | Генерация расписания группы или преподавателя на диапазон дат |
Старый `LessonsController` пока физически присутствует в коде и помечен как `@Deprecated`. Это технический хвост до удаления старых экранов и таблиц, а не целевой слой совместимости.
### 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
Заглушки кабинетов заменены на базовые экраны просмотра расписания.
#### Кабинет студента
Файл: `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` | 0 |
| `schedule_data` | 0 |
Также выполнен:
```bash
git diff --check
```
Результат: замечаний по whitespace нет.
## Ограничения текущего среза
Этот срез создаёт рабочее ядро динамического расписания, но ещё не закрывает весь продуктовый объём.
Ограничения:
- полноценный UI конструктора правил в админке ещё не реализован;
- визуальный редактор матрицы учебного графика в админке ещё не реализован;
- UI управления праздниками и учебными годами пока доступен только через API;
- строгая проверка конфликтов преподавателей, аудиторий и групп при сохранении правил ещё не реализована;
- студент пока выбирает группу вручную, потому что в модели пользователя нет связи `student -> group`;
- старые таблицы и контроллеры `lessons` / `schedule_data` ещё физически есть, но больше не используются для seed-расписания.
## Следующий этап
Рекомендуемый следующий этап:
1. Добавить в админку вкладку настройки временных слотов.
2. Добавить UI учебных годов, семестров и праздников.
3. Добавить визуальную матрицу учебного графика.
4. Добавить конструктор правил расписания.
5. Реализовать backend-валидацию конфликтов:
- преподаватель не может вести разные занятия одновременно;
- аудитория не может быть занята двумя разными занятиями одновременно;
- группа не может быть на двух занятиях одновременно, кроме корректного деления по подгруппам.
6. Удалить старые экраны, контроллеры, DTO, модели и репозитории `lessons` / `schedule_data`.
7. Удалить старые таблицы `lessons` и `schedule_data` из `V1__init.sql`.
## Связанные документы
- `SCHEDULE_PROPOSAL.md` — исходная концепция динамического расписания.
- `SCHEDULE_TASKS.md` — исходная декомпозиция задач.
- `docs/API.md` — описание REST API.
- `docs/DATABASE.md` — описание схемы БД.
- `docs/BUSINESS_LOGIC.md` — бизнес-правила.
- `docs/FRONTEND.md` — frontend-архитектура.