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