Files
magistr/SCHEDULE_PROPOSAL.md

107 lines
19 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.
# Концепция динамической генерации расписания
Данный документ представляет собой подробное архитектурное описание новой системы управления расписанием. Система переходит от статического хранения каждой отдельной пары к параметрическому: мы сохраняем **правила проведения** дисциплины и **календарную сетку**, а фактическое расписание на любую дату вычисляется «на лету» (генерируется).
---
## 1. Подробное описание компонентов системы
Новая архитектура строится на строгом разделении данных на три логических слоя: Календарь (основа отсчета времени), Правила (шаблоны занятий) и Генератор (движок рендеринга фактического расписания).
### 1.1 Справочная база времени (Календарный учебный график)
Чтобы система понимала, *когда* можно ставить пары, а когда нет, вводится понятие календарного графика. Он состоит из трех взаимосвязанных сущностей:
* **Академические периоды (Учебные года и Семестры).** Мы задаем жесткую дату начала каждого семестра и года (например, `02.09.2024`). Именно от этой точки отсчитывается "Неделя 1", которая по умолчанию классифицируется как первая нечетная (верхняя). Это избавляет систему от уязвимостей, связанных с плавающими днями начала учебы, високосными годами и смещениями дней недели.
* **Справочник исключений (Праздники и Выходные).** В этой таблице хранятся конкретные даты `YYYY-MM-DD`, когда университет юридически или физически закрыт (например, государственные праздники). Если по правилу пара должна быть в этот день, алгоритм будет знать, что её нужно пропустить без штрафов и ошибок.
* **Матрица учебного графика.** Это цифровая копия эксель-таблицы (Курс/Специальность -> Номер недели -> Тип деятельности). Типы могут включать `THEORY` (Теория, пары идут в штатном режиме), `EXAM` (Э - экзаменационная сессия), `VACATION` (К - каникулы), `PRACTICE` (У, П - практика). Если, например, у 3-го курса на 18-й неделе стоит статус `EXAM`, алгоритм даже не будет пытаться генерировать для них теоретические лекции, а отобразит блок «Экзаменационная сессия».
### 1.2 Движок правил (Schedule Rules)
Старый подход подразумевал, что каждая пара в базе (каждая клеточка) это изолированная запись `lessons` ("понедельник, 1-я пара, математика"). Новая система вводит сущность сводного **Правила Дисциплины**. Одно правило описывает расписание целого курса по конкретному предмету для конкретной студенческой группы.
**Базовые параметры (Лимиты Правила):**
* `subject_id` — ID преподаваемой дисциплины.
* `group_id` — ID группы (или потока).
* `startDate` — Дата или номер недели, с которой предмет начинает читаться (поскольку не все предметы идут строго с 1-й недели семестра).
* `totalHours` — Полный объем выделенных академических часов. Это важнейший **лимитатор**, который обеспечивает автоматическую остановку генерации: как только заявленные часы будут вычитаны, предмет перестает отображаться в расписании студентов на последующих неделях.
**Массив паттернов (Слоты правила):**
Само "тело" правила разбивается на подчиненные слоты. Если предмет идет в Пн и Ср, это будет 2 слота внутри одного Правила. Слот содержит:
* `dayOfWeek`: день недели (1-7, Пн-Вс).
* `parity`: тип четности оборачивания (1 - каждую неделю, 2 - по четным/нижняя, 3 - по нечетным/верхняя).
* `lessonOrder`: порядковый номер пары в дне (1, 2, 3 и т.д.).
* `subgroup_id`: флаг подгруппы (Вся группа / Подгруппа A / Подгруппа B). *Это гарантирует, что мы сможем ставить разным подгруппам пересекающиеся занятия в разных аудиториях без алгоритмических конфликтов.*
### 1.3 Генератор (Рендерер) расписания
Это слой бизнес-логики (служба `ScheduleGeneratorService` в Java), который работает исключительно в оперативной памяти бэкенда и производит расчет расписания "on-demand" (по требованию) при запросе от клиента фронтенда.
**Пошаговый алгоритм работы генератора:**
1. Фронтенд (Интерфейс пользователя) запрашивает: *"Дай мне расписание группы ИТ-21 на конкретный период, например, с 14 октября по 20 октября"*.
2. Генератор вычисляет, что 14 октября соответствует, к примеру, 7-й неделе семестра (вычисление от `startDate` календаря).
3. Он сверяется с *Матрицей учебного графика*. Если у ИТ-21 сейчас стоит `VACATION` (Каникулы) или `PRACTICE` (Практика, выходящая за пределы ВУЗа), он сразу возвращает пустой ответ или ответ со статусом периода.
4. Если статус недели позволяет проводить занятия (`THEORY`), генератор поднимает из Базы Данных все активные **Правила** для группы ИТ-21.
5. **Механика Лимитатора часов:** Для каждого правила алгоритм "симулирует" или "проматывает" прогон времени с даты старта этого правила до текущей запрошенной недели. Он вычитает успешно проведенные часы, игнорируя даты, попавшие в справочник праздников.
6. Если у предмета лимит `totalHours` достиг значения `0`, программа понимает, что курс вычитан, и предмет не отображается. Если часы еще остались, алгоритм проецирует шаблоны (слоты правила) на запрошенную текущую неделю с учетом четности, аудиторий и подгрупп, отдавая готовый JSON массив в браузер пользователя.
---
## 2. Архитектурные Решения и Открытые Вопросы
На основе обсуждений были задокументированы следующие концептуальные решения по архитектуре:
### Утвержденные решения
1. **Реакция на праздники (Динамическое вытеснение / Сдвиг):**
*Решение:* **Алгоритм должен переносить пару, пара в праздничный день не сгорает.**
Алгоритм будет воспринимать праздник просто как "пропуск хода", не отнимая проведенные часы от `totalHours`. Соответственно, предмет будет автоматически забирать время со смещением в будущее (продлевая дату окончания вычитки), пока преподаватель честно не выработает положенный объем часов.
2. **Массивы vs Связанные таблицы (Использование строгой Нормализации БД):**
*Решение:* **Мы делаем нормализацию через связанные таблицы в PostgreSQL, так как это правильнее с архитектурной точки зрения.**
Мы не будем использовать сырые массивы (типа `INTEGER[]`) или JSONB колонки внутри одной строки с правилом. Будет реализована структура отношения "One-to-Many":
* Главная таблица: `schedule_rules` (хранит лимиты и даты старта).
* Подчиненная таблица: `schedule_rule_slots` (хранит конкретный день, четность, номер пары, прикрепленные к ID главного правила через Foreign Key).
Это позволит базе данных легко и быстро строить сложные выборки в стиле "Покажи загруженность кабинета №21 во вторник на второй паре по четным неделям", исключая тяжелый парсинг JSON.
3. **Поддержка Подгрупп внутри слотов:**
*Решение:* **Модель обязательно должна учитывать деление на подгруппы.**
В таблицы параметров слотов (`schedule_rule_slots`) и в бизнес-логику генератора будет введено поле `subgroup_id` (Id подгруппы). Алгоритм генератора сможет рендерить два предмета для одной группы одновременно и без конфликтов, если они ассоциированы с разными подгруппами одной материнской группы.
### Открытые вопросы (Требуют обсуждения с коллегами)
1. **Смена аудиторий и преподавателей в рамках одного Правила:**
*Вопрос:* Как хранить в базе ситуацию, когда у предмета "Программирование" лекции в понедельник читает лектор Иванов (Аудитория 100), а лабораторные в среду ведет практик Петров (Аудитория 102В)?
* **Вариант А (независимые правила):** Считать их абсолютно разными независимыми Правилами. Одно правило — на лекции Иванова (со своим собственным лимитом лекционных часов `totalHours`), другое правило — на лабораторные Петрова (со своим лимитом практических часов). У каждого — свои слоты.
* **Вариант Б (слоты с обогащенным контекстом):** Считать это одним большим Правилом по предмету "Программирование", но при этом хранить `teacher_id`, `lesson_format` и `classroom_id` не в главной строке Правила, а в каждой строке параметрических Слотов (`schedule_rule_slots`). Таким образом расходуя общий `totalHours` дисциплины.
*Статус:* **Открытый архитектурный вопрос. Решение будет принято после обсуждения.**
---
## 3. Подробный План Действий по Реализации
Интеграция новой архитектуры затронет весь стек приложения (DB -> Backend -> API -> Frontend). Работу предлагается вести строго поэтапно:
### Этап 1. База Данных (Flyway Миграции и Дата-Скрипты)
* **Схема Календарного графика:**
* `academic_years` (id, title, start_date, end_date).
* `holidays` (id, date, academic_year_id, description).
* `academic_calendar_matrix` (id, academic_year_id, target_group_id, week_number, activity_type_enum).
* **Схема Движка Правил:**
* `schedule_rules` (id, subject_id, target_group_id, active_from_date, total_academic_hours).
* `schedule_rule_slots` (id, schedule_rule_id, day_of_week, parity_type, lesson_order_id, subgroup_id, [аудитория_id?, преподаватель_id? *зависит от решения открытого вопроса*]).
* **Скрипт Миграции (Data ETL):** Написание SQL/Java скрипта, который вычитает "старые" статические записи из таблицы `lessons`, скомпонует их по логическим правилам и перенесет в новые таблицы `schedule_rules`, чтобы не потерять текущую информацию. Только после этого можно удалять старую таблицу `lessons`.
### Этап 2. Бэкенд и Вычислительное Ядро (Java + Spring Boot)
* `AcademicDateService.java` — сервис утилит для календарной математики (перевод дат в недели, получение типа четности недели, проверка попадания дня в справочник `holidays`).
* `ScheduleRuleRepository.java` — JPA репозитории для извлечения графа правил из базы данных, с оптимизацией N+1 проблемы через JOIN со слотами.
* `ScheduleGeneratorService.java` — Сердце системы. Основной метод: `List<RenderedLesson> buildSchedule(Group group, LocalDate startDate, LocalDate endDate)`. Реализует всю бизнес-логику из пункта 1.3 (вычитание часов, пропуск праздников).
* Адаптация валидаторов пересечения аудиторий: теперь валидатор должен работать не на уровне "каждой пары", а симулировать весь семестр на этапе сохранения нового Правила в панели администратора.
### Этап 3. Обновление REST API (Контроллеры)
* Эндпоинт получения расписания (`GET /api/schedule`) нужно перевести на диапазонную модель. Теперь он должен принимать параметры: `?groupId=123&startDate=2024-10-14&endDate=2024-10-20`. Ответ сервера должен представлять собой массив объектов с закрепленными полными датами `YYYY-MM-DD` вместо абстрактных названий дней недели.
* Создание CRUD-контроллеров для админки панели управления:
* `/api/admin/calendar/matrix` (настройка каникул и сессий по курсам/неделям).
* `/api/admin/calendar/holidays` (добавление исключений).
* `/api/admin/schedule-rules` (управление жизненным циклом Правил и их слотами).
### Этап 4. Интерфейсы Frontend (Vanilla JS + HTML)
* **Страницы просмотра (Студенты и Преподаватели):**
* Реализация переключателя календарных дат (Date Picker или кнопки-перелистывания недель).
* Логика, которая при свайпе или клике запрашивает у API конкретный диапазон дат и перерисовывает DOM дерево.
* **Панель Администратора (SPA интерфейсы):**
* **Вкладка "Учебный график":** Визуальная сетка-матрица (52 недели по горизонтали, Курсы/Специальности по вертикали), где админ может закрашивать пересечения разными цветами, назначая статусы (Практика, Каникулы, Теория).
* **Вкладка "Конструктор Правил":** Глобально новый визуальный инструмент расписания. Админ выбирает Группу и Дисциплину, задает `totalHours`, а затем динамически добавляет строчки массива слотов через кнопу "Добавить занятие" со списками (Selects) для Дня Недели, Времени, Подгруппы и Четности.