начало работы построения динамического расписания и документация

This commit is contained in:
Zuev
2026-04-29 15:42:05 +03:00
parent 813e81be70
commit 8f71b9b2b5
43 changed files with 3894 additions and 457 deletions

View File

@@ -0,0 +1,317 @@
# Реализация динамического расписания
Этот файл фиксирует текущий результат внедрения динамической системы расписания: что уже добавлено в проект, как связаны база данных, 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-архитектура.