добавил архитектурное описание новой системы управления расписанием

This commit is contained in:
Zuev
2026-04-04 23:56:39 +03:00
parent ac69a57290
commit c7594c4380

106
SCHEDULE_PROPOSAL.md Normal file
View File

@@ -0,0 +1,106 @@
# Концепция динамической генерации расписания
Данный документ представляет собой подробное архитектурное описание новой системы управления расписанием. Система переходит от статического хранения каждой отдельной пары к параметрическому: мы сохраняем **правила проведения** дисциплины и **календарную сетку**, а фактическое расписание на любую дату вычисляется «на лету» (генерируется).
---
## 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) для Дня Недели, Времени, Подгруппы и Четности.