задачи 2 и 8
This commit is contained in:
114
docs/API.md
114
docs/API.md
@@ -623,6 +623,7 @@ GET /api/schedule?groupId=1&startDate=2026-04-27&endDate=2026-05-03
|
||||
|
||||
```json
|
||||
{
|
||||
"scheduleVersionId": 7,
|
||||
"subjectId": 1,
|
||||
"semesterId": 1,
|
||||
"lectureAcademicHours": 32,
|
||||
@@ -652,7 +653,7 @@ CRUD доступен по:
|
||||
|
||||
| Метод | URL | Назначение |
|
||||
|-------|-----|------------|
|
||||
| `GET` | `/api/admin/schedule-rules` | Список правил, фильтры `semesterId`, `groupId` |
|
||||
| `GET` | `/api/admin/schedule-rules` | Список правил, фильтры `semesterId`, `groupId`, `versionId` |
|
||||
| `GET` | `/api/admin/schedule-rules/{id}` | Одно правило |
|
||||
| `POST` | `/api/admin/schedule-rules` | Создать правило |
|
||||
| `PUT` | `/api/admin/schedule-rules/{id}` | Обновить правило |
|
||||
@@ -660,6 +661,11 @@ CRUD доступен по:
|
||||
|
||||
`timeSlotId` должен ссылаться на базовый слот (`scopeApplyMode = DEFAULT`). Субботняя и ручные сетки не выбираются в правиле напрямую.
|
||||
|
||||
Создание, изменение и архивирование правил разрешены только внутри версии со статусом
|
||||
`DRAFT`; `scheduleVersionId` обязателен в payload. Версия должна относиться к указанному
|
||||
семестру. Без `versionId` список возвращает только правила текущей опубликованной версии,
|
||||
а конструктор всегда передаёт ID выбранного черновика.
|
||||
|
||||
`subgroupIds` можно передавать только для лабораторного слота. Каждая подгруппа должна относиться к одной из групп правила. Если лабораторная проводится у нескольких групп одновременно, в одном слоте можно передать разные подгруппы этих групп, например `[10, 22]`. Для совместимости одиночный `subgroupId` тоже принимается, но новый формат — `subgroupIds`. Для лекций и практик оба поля должны быть пустыми, иначе API вернёт ошибку валидации. В одном слоте нельзя выбрать больше одной подгруппы одной и той же группы.
|
||||
|
||||
Часы и недели начала задаются отдельно для лекций, лабораторных и практик. Каждый лимит
|
||||
@@ -841,6 +847,112 @@ API возвращает `409 Conflict`; соседние интервалы и
|
||||
}
|
||||
```
|
||||
|
||||
### Версии и публикация расписания
|
||||
|
||||
| Метод | URL | Назначение |
|
||||
|-------|-----|------------|
|
||||
| `GET` | `/api/edu-office/schedule/versions?semesterId=` | Версии семестра с авторами, датами и количеством правил |
|
||||
| `GET` | `/api/edu-office/schedule/versions/{id}` | Одна версия |
|
||||
| `POST` | `/api/edu-office/schedule/versions` | Создать пустой черновик или копию выбранной версии |
|
||||
| `GET` | `/api/edu-office/schedule/versions/{id}/validate` | Полная проверка правил черновика |
|
||||
| `GET` | `/api/edu-office/schedule/versions/{id}/diff?baseVersionId=` | Сравнение правил и сформированных занятий |
|
||||
| `POST` | `/api/edu-office/schedule/versions/{id}/publish` | Атомарно опубликовать проверенный черновик |
|
||||
| `POST` | `/api/edu-office/schedule/versions/{id}/restore` | Восстановить ранее опубликованную архивную версию |
|
||||
| `GET` | `/api/edu-office/schedule/versions/history?semesterId=` | Хронология создания, публикации, архивации и восстановления |
|
||||
|
||||
Доступ имеют только `ADMIN` и `EDUCATION_OFFICE`. Для семестра допускается ровно одна
|
||||
версия со статусом `PUBLISHED`; остальные имеют статус `DRAFT` или `ARCHIVED`. Создание
|
||||
копии переносит правила, группы, слоты и подгруппы, сохраняя `versionGroupId` для diff.
|
||||
|
||||
```json
|
||||
{
|
||||
"semesterId": 3,
|
||||
"name": "Расписание после распределения аудиторий",
|
||||
"basedOnVersionId": 11
|
||||
}
|
||||
```
|
||||
|
||||
Публикация и восстановление принимают обязательную причину:
|
||||
|
||||
```json
|
||||
{ "reason": "Согласовано учебным отделом 11.08.2026" }
|
||||
```
|
||||
|
||||
Перед публикацией backend блокирует версию, повторно проверяет весь набор активных правил,
|
||||
архивирует текущую публикацию и переводит черновик в `PUBLISHED` одной транзакцией. При
|
||||
ошибке ни один статус не меняется. Обычные endpoints просмотра и генерации выбирают только
|
||||
опубликованную версию; overrides архивных версий не попадают в актуальное расписание и
|
||||
операционный реестр.
|
||||
|
||||
### Анализ качества расписания
|
||||
|
||||
| Метод | URL | Назначение |
|
||||
|-------|-----|------------|
|
||||
| `GET` | `/api/edu-office/schedule/quality?semesterId=&versionId=` | Итоговая оценка, метрики и объяснимый список проблем выбранной версии |
|
||||
| `GET` | `/api/edu-office/schedule/quality/recommendations?semesterId=&versionId=&scheduleRuleSlotId=&lessonDate=` | Проверенные локальные варианты улучшения опубликованного занятия |
|
||||
|
||||
Доступ имеют только `ADMIN` и `EDUCATION_OFFICE`. `versionId` необязателен: без него
|
||||
анализируется текущая публикация. Для опубликованной версии анализатор рассматривает
|
||||
фактическое расписание всего семестра, включая уже применённые `schedule_overrides`, и учитывает окна
|
||||
групп и преподавателей, перегруженные дни, вместимость и избыточный размер аудитории,
|
||||
согласованные пожелания и недоступность преподавателей, а также равномерность нагрузки.
|
||||
Длинный семестр загружается внутренними интервалами не более 120 дней, поэтому публичный
|
||||
запрос не требует диапазона дат.
|
||||
|
||||
Черновик и архивная версия генерируются напрямую по собственным правилам, без подмешивания
|
||||
override текущей публикации. Для них доступны оценка, метрики и проблемы, но не локальные
|
||||
рекомендации: исправления черновика выполняются в конструкторе до публикации, архив остаётся
|
||||
только для чтения.
|
||||
|
||||
```json
|
||||
{
|
||||
"semesterId": 3,
|
||||
"semesterLabel": "2026/2027 · осенний семестр",
|
||||
"scheduleVersionId": 12,
|
||||
"scheduleVersionNumber": 2,
|
||||
"scheduleVersionName": "Расписание после распределения аудиторий",
|
||||
"scheduleVersionStatus": "DRAFT",
|
||||
"score": 84,
|
||||
"rawPenalty": 17,
|
||||
"lessonCount": 36,
|
||||
"totalProblemCount": 5,
|
||||
"problemsTruncated": false,
|
||||
"metrics": [
|
||||
{
|
||||
"code": "GROUP_GAPS",
|
||||
"label": "Окна у групп",
|
||||
"value": 3,
|
||||
"unit": "окон",
|
||||
"penalty": 6,
|
||||
"explanation": "Промежутки между занятиями одной группы"
|
||||
}
|
||||
],
|
||||
"problems": [
|
||||
{
|
||||
"id": "GROUP_GAP:2026-09-14:31:18",
|
||||
"type": "GROUP_GAP",
|
||||
"severity": "MEDIUM",
|
||||
"title": "Окно в расписании группы",
|
||||
"penalty": 2,
|
||||
"scheduleRuleSlotId": 31,
|
||||
"lessonDate": "2026-09-14",
|
||||
"optimizable": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`score` находится в диапазоне от 0 до 100 и нормализует сумму объяснимых штрафов
|
||||
относительно числа занятий. Поле `totalProblemCount` содержит полный размер результата,
|
||||
а массив `problems` ограничен 300 элементами; `problemsTruncated` сообщает об усечении.
|
||||
|
||||
Рекомендации подбирают другой эффективный временной слот того же учебного дня либо
|
||||
подходящую активную аудиторию. Занятия, уже закреплённые ручным override, исключаются.
|
||||
Каждый кандидат до выдачи проходит `ScheduleOverrideService`, содержит готовый payload
|
||||
`override`, ожидаемые `scoreDelta`, `penaltyDelta`, улучшения и компромиссы. API ничего не
|
||||
изменяет автоматически: после подтверждения пользователь применяет payload обычным
|
||||
`POST /api/edu-office/schedule/overrides`.
|
||||
|
||||
### Отсутствия преподавателей и мастер замены
|
||||
|
||||
| Метод | URL | Назначение |
|
||||
|
||||
@@ -265,8 +265,32 @@ constraint. При чтении API разворачивает периоды о
|
||||
- **Жизненный цикл:** архивные преподаватели, аудитории, группы и дисциплины не принимаются в новых правилах.
|
||||
- **Правило расписания:** `status=ARCHIVED` или дата вне `valid_from` / `valid_to` исключают правило из генерации.
|
||||
- **Доступность аудитории:** `is_available=false` запрещает новые назначения, но не удаляет историю.
|
||||
- **Конфликты слотов:** сначала попарно проверяются слоты самого нового payload, включая точные дубли, затем — активные правила того же семестра. Конфликт возникает при пересечении дня, базового временного слота, чётности (`BOTH` пересекается с любой чётностью) и активных недель слота, если совпадает преподаватель, аудитория или учебная группа. `ODD` и `EVEN` между собой не конфликтуют. Активные недели считаются из лимита часов типа занятия, недели начала, чётности и порядка слотов внутри правила; например, занятие на 1-3 неделях не конфликтует с тем же ресурсом с 4 недели. Для лабораторных занятий разные подгруппы одной группы могут идти параллельно, но занятие для всей группы конфликтует с любой её подгруппой. Backend возвращает `409 Conflict`; `conflictRule` присутствует только для конфликта с сохранённым правилом, а внутренний конфликт описывается полями и русскими причинами без искусственной записи.
|
||||
- **Конкурентная запись:** публичные методы `ScheduleRuleService` являются транзакционными. Создание сначала блокирует строку семестра, а update блокирует правило и старый/новый семестры в стабильном порядке, поэтому два backend-pod не могут одновременно пройти проверку одного семестра по устаревшему снимку.
|
||||
- **Конфликты слотов:** сначала попарно проверяются слоты самого нового payload, включая точные дубли, затем — активные правила той же версии. Конфликт возникает при пересечении дня, базового временного слота, чётности (`BOTH` пересекается с любой чётностью) и активных недель слота, если совпадает преподаватель, аудитория или учебная группа. `ODD` и `EVEN` между собой не конфликтуют. Активные недели считаются из лимита часов типа занятия, недели начала, чётности и порядка слотов внутри правила; например, занятие на 1-3 неделях не конфликтует с тем же ресурсом с 4 недели. Для лабораторных занятий разные подгруппы одной группы могут идти параллельно, но занятие для всей группы конфликтует с любой её подгруппой. Backend возвращает `409 Conflict`; `conflictRule` присутствует только для конфликта с сохранённым правилом, а внутренний конфликт описывается полями и русскими причинами без искусственной записи.
|
||||
- **Конкурентная запись:** публичные методы `ScheduleRuleService` являются транзакционными. Создание блокирует строку семестра и выбранную версию, а update — правило, версию и старый/новый семестры в стабильном порядке. Публикация блокирует версию до завершения полной проверки, поэтому другой backend-pod не может дописать правило после валидации черновика.
|
||||
|
||||
### Черновики, версии и публикация
|
||||
|
||||
Правила каждого семестра принадлежат явной версии расписания. Жизненный цикл версии:
|
||||
|
||||
1. `DRAFT` создаётся пустым или как полная копия выбранной версии.
|
||||
2. Конструктор добавляет, изменяет и архивирует правила только в выбранном черновике.
|
||||
3. Полная проверка выявляет внутренние конфликты правил; diff сопоставляет правила по
|
||||
стабильному `version_group_id` и отдельно сравнивает сформированные занятия семестра.
|
||||
4. Публикация требует причины и в одной транзакции архивирует прежнюю публикацию, затем
|
||||
переводит проверенный черновик в `PUBLISHED`.
|
||||
5. Ранее опубликованная версия получает `ARCHIVED` и может быть восстановлена такой же
|
||||
атомарной операцией с обязательной причиной.
|
||||
|
||||
На уровне БД частичный уникальный индекс допускает только одну `PUBLISHED`-версию на
|
||||
семестр. Блокировки версии и набора версий семестра не позволяют публикации пересечься с
|
||||
редактированием черновика или конкурентной публикацией. Каждое создание, архивирование,
|
||||
публикация и восстановление записывается в неизменяемый журнал с автором, временем и
|
||||
причиной.
|
||||
|
||||
Обычная генерация для студентов, преподавателей и кабинетов просмотра всегда выбирает
|
||||
только опубликованные правила. Точечные изменения привязаны к слотам конкретной версии:
|
||||
после новой публикации overrides прежней версии сохраняются как аудит, но не влияют на
|
||||
актуальное расписание и не показываются в его операционном реестре.
|
||||
|
||||
### Точечные изменения расписания
|
||||
|
||||
@@ -386,6 +410,41 @@ constraint. При чтении API разворачивает периоды о
|
||||
отозвать только собственную ожидающую заявку. История содержит автора, время, статус и
|
||||
комментарий каждого перехода.
|
||||
|
||||
### Анализ качества и локальная оптимизация расписания
|
||||
|
||||
Анализатор качества работает без собственной таблицы и не меняет правила расписания.
|
||||
Пользователь выбирает конкретную версию семестра. Для `PUBLISHED` он строит фактические
|
||||
занятия через `ScheduleQueryService`, поэтому в расчёт входят переносы, замены и отмены из
|
||||
`schedule_overrides`. `DRAFT` и `ARCHIVED` генерируются напрямую по собственным правилам,
|
||||
без точечных изменений текущей публикации. Семестр загружается частями не более 120 дней,
|
||||
но оценка рассчитывается единообразно по всему периоду.
|
||||
|
||||
Итоговая оценка от 0 до 100 формируется из объяснимых штрафов:
|
||||
|
||||
- окна групп и преподавателей между занятиями одного дня;
|
||||
- пятая и последующие пары группы или преподавателя за день;
|
||||
- нехватка мест и заметно избыточная вместимость аудитории;
|
||||
- занятие в подтверждённый строго недоступный или нежелательный интервал;
|
||||
- занятие вне предпочтительного интервала преподавателя на выбранный день недели;
|
||||
- нарушение пожеланий `NO_GAPS` и `CONSECUTIVE`.
|
||||
|
||||
Неравномерность дневной нагрузки выводится отдельной диагностической метрикой в процентах
|
||||
и не добавляет скрытого штрафа к итоговой оценке.
|
||||
|
||||
В ответе сохраняются исходный штраф, вклад каждого критерия и конкретные проблемы с
|
||||
датой, занятием и затронутой сущностью. Это делает оценку воспроизводимой и позволяет
|
||||
фильтровать проблемы по типу и серьёзности.
|
||||
|
||||
Для проблемы опубликованной версии помощник перебирает другие слоты эффективной сетки того же дня и
|
||||
активные аудитории достаточной вместимости. Каждый вариант повторно проходит
|
||||
`ScheduleOverrideService`, затем анализатор моделирует его влияние на общую оценку и
|
||||
показывает улучшения и компромиссы. Занятия с ручным override считаются закреплёнными и не
|
||||
получают рекомендаций. Применение возможно только после подтверждения пользователя через
|
||||
обычный механизм `schedule_overrides`; автоматической публикации и полного solver в MVP
|
||||
нет. Для черновика доступны те же оценка, метрики и объяснимые проблемы, но рекомендации
|
||||
не создаются: пользователь исправляет правила в изолированном конструкторе и повторяет
|
||||
проверку до публикации. Архив анализируется только для чтения.
|
||||
|
||||
## Привязка преподаватель ↔ дисциплина
|
||||
|
||||
Связь Many-to-Many через таблицу `teacher_subjects`:
|
||||
|
||||
@@ -286,10 +286,37 @@ erDiagram
|
||||
BIGINT calendar_id FK
|
||||
}
|
||||
|
||||
schedule_versions {
|
||||
BIGSERIAL id PK
|
||||
BIGINT semester_id FK
|
||||
INT version_number
|
||||
VARCHAR name
|
||||
VARCHAR status
|
||||
BIGINT based_on_version_id FK
|
||||
BIGINT restored_from_version_id FK
|
||||
TEXT change_reason
|
||||
BIGINT created_by FK
|
||||
TIMESTAMPTZ created_at
|
||||
BIGINT published_by FK
|
||||
TIMESTAMPTZ published_at
|
||||
TIMESTAMPTZ archived_at
|
||||
}
|
||||
|
||||
schedule_version_history {
|
||||
BIGSERIAL id PK
|
||||
BIGINT version_id FK
|
||||
VARCHAR action
|
||||
BIGINT actor_id FK
|
||||
TEXT reason
|
||||
TIMESTAMPTZ created_at
|
||||
}
|
||||
|
||||
schedule_rules {
|
||||
BIGSERIAL id PK
|
||||
BIGINT subject_id FK
|
||||
BIGINT semester_id FK
|
||||
BIGINT schedule_version_id FK
|
||||
BIGINT version_group_id
|
||||
VARCHAR status
|
||||
DATE valid_from
|
||||
DATE valid_to
|
||||
@@ -442,6 +469,12 @@ erDiagram
|
||||
academic_years ||--o{ semesters : "academic_year_id"
|
||||
academic_years ||--o{ academic_calendars : "academic_year_id"
|
||||
academic_years ||--o{ student_group_calendar_assignments : "academic_year_id"
|
||||
semesters ||--o{ schedule_versions : "semester_id"
|
||||
schedule_versions ||--o{ schedule_rules : "schedule_version_id"
|
||||
schedule_versions ||--o{ schedule_version_history : "version_id"
|
||||
schedule_versions o|--o{ schedule_versions : "based_on/restored_from"
|
||||
users ||--o{ schedule_versions : "created_by/published_by"
|
||||
users ||--o{ schedule_version_history : "actor_id"
|
||||
semesters ||--o{ schedule_rules : "semester_id"
|
||||
specialties ||--o{ academic_calendars : "specialty_id"
|
||||
specialty_profiles ||--o{ academic_calendars : "specialty_profile_id"
|
||||
@@ -896,12 +929,50 @@ V1 создаёт GiST exclusion constraint `ex_academic_years_no_overlap` дл
|
||||
триггеры защищают назначение при изменении группы, графика и границ учебного года; блокировки
|
||||
ссылочных строк закрывают конкурентные записи между несколькими backend-pod.
|
||||
|
||||
#### `schedule_versions` — Версии расписания семестра
|
||||
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|----------|
|
||||
| `id` | BIGSERIAL PK | ID версии |
|
||||
| `semester_id` | BIGINT FK → semesters (CASCADE) | Семестр |
|
||||
| `version_number` | INT CHECK(> 0) | Последовательный номер внутри семестра |
|
||||
| `name` | VARCHAR(160) | Пользовательское название |
|
||||
| `status` | VARCHAR(20) | `DRAFT`, `PUBLISHED` или `ARCHIVED` |
|
||||
| `based_on_version_id` | BIGINT FK → schedule_versions | Версия-основа черновика |
|
||||
| `restored_from_version_id` | BIGINT FK → schedule_versions | Публикация, которую заменили при восстановлении |
|
||||
| `change_reason` | TEXT | Причина последней публикации или восстановления |
|
||||
| `created_by` | BIGINT FK → users | Автор черновика |
|
||||
| `created_at` | TIMESTAMPTZ | Время создания |
|
||||
| `published_by` | BIGINT FK → users | Автор публикации |
|
||||
| `published_at` | TIMESTAMPTZ | Время последней публикации |
|
||||
| `archived_at` | TIMESTAMPTZ | Время архивирования |
|
||||
|
||||
Пара `semester_id + version_number` уникальна. Частичный индекс
|
||||
`uq_schedule_versions_published_semester` запрещает более одной строки `PUBLISHED` на
|
||||
семестр. Начальная загрузка V1 создаёт опубликованную версию 1 для каждого семестра и
|
||||
привязывает к ней существующие seed-правила.
|
||||
|
||||
#### `schedule_version_history` — Аудит версий расписания
|
||||
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|----------|
|
||||
| `id` | BIGSERIAL PK | ID события |
|
||||
| `version_id` | BIGINT FK → schedule_versions (CASCADE) | Версия расписания |
|
||||
| `action` | VARCHAR(30) | `CREATED`, `PUBLISHED`, `ARCHIVED` или `RESTORED` |
|
||||
| `actor_id` | BIGINT FK → users | Автор действия; `NULL` для системной инициализации |
|
||||
| `reason` | TEXT | Причина или описание события, до 2000 символов |
|
||||
| `created_at` | TIMESTAMPTZ | Время события |
|
||||
|
||||
Журнал добавляется при каждом переходе версии и выводится в обратной хронологии. Он не
|
||||
заменяет данные версии, а сохраняет отдельные факты аудита.
|
||||
|
||||
#### `schedule_rules` — Правила расписания
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|----------|
|
||||
| `id` | BIGSERIAL PK | ID |
|
||||
| `subject_id` | BIGINT FK → subjects | Дисциплина |
|
||||
| `semester_id` | BIGINT FK → semesters | Семестр |
|
||||
| `schedule_version_id` | BIGINT FK → schedule_versions (CASCADE) | Версия расписания |
|
||||
| `lecture_academic_hours` | INT | Лимит академических часов лекций |
|
||||
| `laboratory_academic_hours` | INT | Лимит академических часов лабораторных работ |
|
||||
| `practice_academic_hours` | INT | Лимит академических часов практик |
|
||||
@@ -914,7 +985,7 @@ V1 создаёт GiST exclusion constraint `ex_academic_years_no_overlap` дл
|
||||
| `version_group_id` | BIGINT | Группа версий одного правила |
|
||||
| `change_reason` | TEXT | Причина изменения |
|
||||
|
||||
`ScheduleRule` использует собственные поля жизненного цикла `status`, `valid_from` и `valid_to`: архивированное правило или правило вне периода действия не участвует в генерации расписания. В отличие от справочников на `LifecycleEntity`, таблица не содержит `active_from`/`active_to`, поэтому состояние правила проверяется по `valid_*`.
|
||||
`ScheduleRule` использует собственные поля жизненного цикла `status`, `valid_from` и `valid_to`: архивированное правило или правило вне периода действия не участвует в генерации расписания. В отличие от справочников на `LifecycleEntity`, таблица не содержит `active_from`/`active_to`, поэтому состояние правила проверяется по `valid_*`. Публичная генерация дополнительно требует статус `PUBLISHED` у связанной версии. Уникальный индекс по `schedule_version_id + version_group_id` не позволяет дважды скопировать одну логическую линию правила в одну версию.
|
||||
|
||||
Базовая схема V1 требует, чтобы лимиты лекций, лабораторных и практик были кратны двум.
|
||||
Неотрицательность каждого лимита и положительная сумма уже закреплены ограничениями V1;
|
||||
@@ -1111,19 +1182,19 @@ CHECK фиксирует допустимую форму каждого типа
|
||||
|
||||
| Файл | Описание |
|
||||
|------|----------|
|
||||
| `V1__init.sql` | Полная baseline-схема: справочники, роли, refresh-сессии JWT, PostgreSQL rate limit и аудит входа, lifecycle-поля, история кафедр, календарные графики с интервальным хранением активностей и нумерацией недель `понедельник–воскресенье`, динамическое расписание, точечные изменения с переносом даты, seed, CHECK/UNIQUE/GiST-ограничения, конкурентно безопасные триггеры и комментарии |
|
||||
| `V2__teacher_absences_and_replacement_wizard.sql` | Реестр отсутствий преподавателей, статусы согласования и журнал применённых/отклонённых решений со ссылками на обычные `schedule_overrides` |
|
||||
| `V3__teacher_preferences_and_change_requests.sql` | Пожелания преподавателей на семестр, строгая и мягкая доступность, заявки на изменение занятия и неизменяемая история решений |
|
||||
| `V1__init.sql` | Полная baseline-схема: справочники, роли, refresh-сессии JWT, PostgreSQL rate limit и аудит входа, lifecycle-поля, история кафедр, календарные графики, динамическое расписание, версии/черновики и аудит публикаций, точечные изменения, отсутствия и журнал замен, пожелания преподавателей, заявки на изменение занятий и их история, seed, CHECK/UNIQUE/GiST-ограничения, конкурентно безопасные триггеры и комментарии |
|
||||
|
||||
### Этап разработки
|
||||
|
||||
Исторические разработческие миграции V2–V7 по прямому решению владельца проекта были
|
||||
объединены в baseline `V1`. После фиксации baseline нумерация начата заново: `V2`
|
||||
добавляет отсутствия и мастер замены, а `V3` — пожелания преподавателей и заявки на
|
||||
изменение занятий, не изменяя контрольную сумму `V1`.
|
||||
Исторические разработческие миграции V2–V7, а затем повторно созданные V2 с отсутствиями
|
||||
и мастером замены и V3 с пожеланиями и заявками преподавателей по прямому решению владельца
|
||||
проекта объединены в baseline `V1`. В каталоге миграций остаётся один файл
|
||||
`V1__init.sql`.
|
||||
Интервальное хранение активностей и правильная нумерация недель календарного графика входят
|
||||
непосредственно в V1.
|
||||
Перед применением этой редакции требуется полностью пустая tenant-схема.
|
||||
Перед применением этой редакции требуется полностью пустая tenant-схема: для базы, где
|
||||
предыдущая V1 уже записана в `flyway_schema_history`, изменённая контрольная сумма вызовет
|
||||
ошибку проверки.
|
||||
|
||||
### Полный сброс БД (локально)
|
||||
|
||||
|
||||
@@ -261,6 +261,9 @@ public class AbsenceController {
|
||||
Текущее состояние проекта — единый baseline `V1__init.sql`: по прямому решению владельца
|
||||
содержимое прежних V2–V7 объединено в V1, клиентских tenant-БД нет. До отдельного решения о
|
||||
фиксации baseline новые DB-инварианты добавляются в V1 и проверяются на полностью чистой БД.
|
||||
Таблицы отсутствий, решений по заменам, пожеланий и заявок преподавателей из последних
|
||||
вариантов V2 и V3 также перенесены в этот единый baseline; отдельных файлов V2/V3 в
|
||||
проекте нет.
|
||||
|
||||
### Применение
|
||||
|
||||
|
||||
@@ -112,15 +112,19 @@
|
||||
|
||||
## 2. Черновики, версии, публикация и аудит расписания
|
||||
|
||||
**Статус: реализовано в MVP.** Добавлены изолированные версии `DRAFT/PUBLISHED/ARCHIVED`,
|
||||
полное копирование правил, валидация и diff правил/занятий, атомарная публикация,
|
||||
восстановление предыдущей публикации и неизменяемый журнал действий. Экран качества умеет
|
||||
проверять черновики до публикации; изменения в них выполняются через конструктор правил.
|
||||
|
||||
### Проблема и пользователи
|
||||
|
||||
Изменение активного правила сейчас сразу влияет на следующий запрос расписания. При большой
|
||||
переработке семестра это создаёт риск показать студентам и преподавателям промежуточное
|
||||
состояние. Учебному отделу нужен безопасный цикл «подготовить — проверить — опубликовать».
|
||||
|
||||
В таблице `schedule_rules` уже предусмотрены поля `version_group_id`, `change_reason`,
|
||||
`created_by` и `created_at`, которые можно использовать как основу, но полноценный жизненный
|
||||
цикл версий пока не реализован.
|
||||
Поля `version_group_id`, `change_reason`, `created_by` и `created_at` в `schedule_rules`
|
||||
используются вместе с отдельными таблицами версий и истории публикаций.
|
||||
|
||||
### Пользовательский сценарий
|
||||
|
||||
@@ -144,9 +148,10 @@
|
||||
|
||||
### Связь с текущей системой
|
||||
|
||||
Текущая динамическая генерация сохраняется, но получает явный контекст версии. Конструктор
|
||||
правил работает как с черновиком, так и с опубликованной версией, а кабинеты просмотра — только с опубликованной версией. Точечные изменения можно вести отдельным аудитом либо включать в пакет публикации в зависимости от
|
||||
будущих продуктовых требований.
|
||||
Текущая динамическая генерация получила явный контекст версии. Конструктор правил меняет
|
||||
только черновик, кабинеты просмотра используют только опубликованную версию, а анализатор
|
||||
качества позволяет выбрать опубликованную, черновую или архивную версию. Точечные изменения
|
||||
остаются привязаны к конкретной публикации и не переносятся в новый черновик автоматически.
|
||||
|
||||
### Дальнейшее развитие
|
||||
|
||||
@@ -396,7 +401,10 @@ feed должен отдавать только опубликованное р
|
||||
**Ожидаемый результат:** заметное сокращение ручного заполнения и ошибок ввода.
|
||||
**Сложность:** M. **Демонстрационный эффект:** высокий благодаря наглядному предпросмотру.
|
||||
|
||||
## 8. (отдельным запросом) Анализ качества и автоматическая оптимизация расписания
|
||||
## 8. Анализ качества и автоматическая оптимизация расписания
|
||||
|
||||
**Статус:** реализован MVP анализатора и локальных рекомендаций. Полный solver, фоновые
|
||||
черновики и сравнение нескольких глобальных вариантов остаются дальнейшим этапом.
|
||||
|
||||
### Проблема и пользователи
|
||||
|
||||
@@ -436,6 +444,12 @@ feed должен отдавать только опубликованное р
|
||||
конфликтного или неудобного занятия. Такой MVP позволяет проверить метрики качества,
|
||||
интерфейс объяснений и производительность на реальных данных.
|
||||
|
||||
Реализованный MVP анализирует фактическое расписание выбранного семестра с учётом ручных
|
||||
правок, формирует оценку и список проблем, а для конкретного занятия предлагает проверенные
|
||||
перестановки времени в пределах дня или аудитории. Кандидат применяется только после
|
||||
подтверждения пользователя через существующий `schedule_override`; ручные правки считаются
|
||||
закреплёнными.
|
||||
|
||||
Red Zone уже выявляет накладки и превышение вместимости, а вкладка загруженности показывает
|
||||
занятые и свободные слоты. Новый анализатор не должен дублировать эти экраны: его ценность —
|
||||
единая объяснимая оценка, мягкие критерии качества и рекомендации по улучшению.
|
||||
|
||||
@@ -33,6 +33,8 @@ frontend/
|
||||
│ ├── auth-session.test.mjs # Login/refresh/reload/logout и single-flight refresh
|
||||
│ ├── dashboard-conflicts.test.mjs # Регрессии дат и состояний проверки конфликтов
|
||||
│ ├── schedule-overrides.test.mjs # Действия, роли, недельный выбор и подбор времени разовой правки
|
||||
│ ├── schedule-quality.test.mjs # Метрики качества, фильтры проблем и доступность вкладки
|
||||
│ ├── schedule-versions.test.mjs # Статусы версий, журнал и навигация в черновик/качество
|
||||
│ ├── schedule-view-semesters.test.mjs # Выбор семестра и расчёт двухнедельного диапазона просмотра
|
||||
│ ├── teacher-absences.test.mjs # Payload мастера замены и доступность вкладки по ролям
|
||||
│ ├── teacher-preferences.test.mjs # Календарь пожеланий, заявки и подсказки конструктора
|
||||
@@ -52,7 +54,9 @@ frontend/
|
||||
│ │ ├── modals.css # Модальные окна
|
||||
│ │ ├── auditorium-workload.css # Таблицы расписаний и загруженности
|
||||
│ │ ├── departments-data.css # Стили создания кафедры/специальности
|
||||
│ │ └── teacher-absences.css # Отсутствия, пожелания и очередь заявок преподавателей
|
||||
│ │ ├── teacher-absences.css # Отсутствия, пожелания и очередь заявок преподавателей
|
||||
│ │ ├── schedule-quality.css # Диагностический экран качества расписания
|
||||
│ │ └── schedule-versions.css # Контур публикации, diff и журнал версий
|
||||
│ ├── js/
|
||||
│ │ ├── main.js # Инициализация, маршрутизация, навигация
|
||||
│ │ ├── role-capabilities.js # Единая матрица вкладок admin/settings по ролям
|
||||
@@ -72,6 +76,8 @@ frontend/
|
||||
│ │ ├── schedule-view.js # Просмотр расписаний и запуск разовой правки из карточки
|
||||
│ │ ├── schedule-override-panel.js # Боковая панель и реестр разовых изменений
|
||||
│ │ ├── teacher-absences.js # Отсутствия, согласование пожеланий и заявок на изменение
|
||||
│ │ ├── schedule-quality.js # Оценка, фильтры и локальные рекомендации
|
||||
│ │ ├── schedule-versions.js # Черновики, публикация, восстановление, diff и аудит
|
||||
│ │ ├── schedule.js # Конструктор правил и подсказки пожеланий преподавателей
|
||||
│ │ ├── academic-calendar-grid.js # Расчёт ISO-недели дневной сетки
|
||||
│ │ ├── academic-calendar-title.js # Название из кода, профиля, формы и года
|
||||
@@ -88,6 +94,8 @@ frontend/
|
||||
│ │ ├── department-workspace.html
|
||||
│ │ ├── schedule-view.html
|
||||
│ │ ├── teacher-absences.html
|
||||
│ │ ├── schedule-quality.html
|
||||
│ │ ├── schedule-versions.html
|
||||
│ │ ├── schedule.html
|
||||
│ │ ├── academic-calendar.html
|
||||
│ │ └── auditorium-workload.html
|
||||
@@ -147,7 +155,7 @@ frontend/
|
||||
| Роль | Доступные вкладки |
|
||||
|------|-------------------|
|
||||
| `ADMIN` | Все вкладки |
|
||||
| `EDUCATION_OFFICE` | Просмотр и конструктор расписаний, отсутствия и замены, календарный график, загруженность и аудитории; в settings — временные слоты и формы обучения |
|
||||
| `EDUCATION_OFFICE` | Просмотр, конструктор и анализ качества расписаний, отсутствия и замены, календарный график, загруженность и аудитории; в settings — временные слоты и формы обучения |
|
||||
| `DEPARTMENT` | Кабинет кафедры, просмотр расписаний, отсутствия и подтверждение заявок своих преподавателей |
|
||||
| `SCHEDULE_VIEWER` | Только просмотр расписаний |
|
||||
|
||||
@@ -173,6 +181,8 @@ frontend/
|
||||
| `schedule-view` | Просмотр расписаний: семестр выбирается в дополнительных фильтрах, для учебного периода строится двухнедельный диапазон, найденные расписания выбираются в переключателе, а `ADMIN` и `EDUCATION_OFFICE` редактируют конкретное занятие в боковой панели без изменения правила | `/api/schedule/semesters`, `/api/schedule/search`, `/api/edu-office/schedule/overrides`, `/api/admin/time-slots/effective` |
|
||||
| `teacher-absences` | Запросы преподавателей: отсутствия и мастер замены, согласование семестровых пожеланий, заявки на перенос, аудиторию или отмену | `/api/teacher-absences`, `/api/teacher-preferences`, `/api/teacher-change-requests`, `/api/users/teachers` |
|
||||
| `schedule` | Конструктор правил динамического расписания с подсказками согласованных пожеланий и выезжающей визуальной матрицей групп | `/api/admin/schedule-rules`, `/api/teacher-preferences`, `/api/admin/time-slots`, `/api/admin/calendar/years`, `/api/lesson-types`, `/api/subgroups` |
|
||||
| `schedule-versions` | Контур публикации: текущая версия, черновики, diff, архив, восстановление и журнал | `/api/edu-office/schedule/versions` |
|
||||
| `schedule-quality` | Диагностика выбранной версии семестра: оценка, метрики, фильтруемые проблемы; для публикации — подтверждаемое локальное улучшение | `/api/edu-office/schedule/quality`, `/api/edu-office/schedule/quality/recommendations`, `/api/edu-office/schedule/overrides` |
|
||||
| `academic-calendar` | Учебные годы, семестры, создание календарных графиков, Excel-подобный редактор дневной сетки и привязка дисциплин к семестрам графика | `/api/admin/calendar`, `/api/admin/academic-calendars`, `/api/admin/academic-calendars/{id}/subjects`, `/api/admin/calendar/activity-types`, `/api/specialties`, `/api/specialties/{id}/profiles`, `/api/education-forms`, `/api/subjects` |
|
||||
| `auditorium-workload` | Динамическая загруженность аудиторий, преподавателей и кафедр: сводная матрица по дате или совмещённая таблица выбранной сущности по чётной/нечётной неделе | `/api/classrooms`, `/api/users/teachers`, `/api/departments`, `/api/admin/time-slots`, `/api/equipments`, `/api/groups`, `/api/schedule`, `/api/admin/calendar/years` |
|
||||
|
||||
@@ -197,6 +207,22 @@ frontend/
|
||||
`ADMIN` и `EDUCATION_OFFICE`; кафедра видит записи своих преподавателей без управляющих
|
||||
действий. Каждая карточка показывает исходное и запрошенное состояние, результат
|
||||
предварительной проверки и хронологию решения.
|
||||
- Вкладка `schedule-quality` доступна администратору и учебному отделу. После выбора
|
||||
семестра и версии она выводит круговую оценку от 0 до 100, карточки метрик и реестр проблем с
|
||||
фильтрами по серьёзности и типу. Для проблемы, связанной с конкретным незакреплённым
|
||||
занятием опубликованной версии, кнопка `Подобрать улучшение` запрашивает проверенные варианты времени и
|
||||
аудитории. Карточка кандидата показывает изменение оценки, улучшения и компромиссы;
|
||||
override создаётся только после явного подтверждения, затем анализ запускается заново.
|
||||
Для `DRAFT` экран рассчитывает те же метрики и проблемы напрямую по правилам черновика,
|
||||
но направляет пользователя в конструктор и не создаёт override. `ARCHIVED` анализируется
|
||||
только для чтения. Экран не предлагает автоматически менять занятия, уже отредактированные вручную.
|
||||
- Вкладка `schedule-versions` доступна администратору и учебному отделу и оформлена как
|
||||
отдельный контур публикации. Верхняя карточка показывает версию, которую видят конечные
|
||||
пользователи; ниже расположены черновики, сравнение правил и занятий, архив и журнал.
|
||||
Новый черновик копирует выбранную опубликованную версию. Перед публикацией интерфейс
|
||||
одновременно запрашивает полную валидацию и diff, требует причину и блокирует действие
|
||||
при ошибках. Кнопки черновика сохраняют его ID в `localStorage` и открывают конструктор
|
||||
или анализ качества в нужном контексте; архивную публикацию можно восстановить с причиной.
|
||||
- Компоновка `department-workspace` использует собственные CSS-сетки `department-workspace-filter-grid` и `department-workspace-actions-grid`: фильтры периода отделены от сеток расписания, загрузка дисциплин занимает широкую колонку, формы преподавателей выравниваются справа, а списки и таблицы идут полноширинными блоками ниже.
|
||||
- Вкладка `schedule-view` показывает найденные занятия в режиме одной активной таблицы. Пользователь выбирает, что смотреть: группу, преподавателя, аудиторию или кафедру; основная кнопка `Показать` расположена в заголовке блока параметров, а пустое состояние таблицы с подсказкой об обновлении содержит дополнительную кнопку `Показать расписание`. В дополнительных фильтрах доступен семестр из справочника `/api/schedule/semesters`, предназначенного только для чтения. Для текущего семестра сохраняется текущая двухнедельная точка просмотра, а при выборе другого семестра диапазон начинается с понедельника его первой недели. Frontend запрашивает две недели и собирает найденные расписания в переключатель результатов. На странице не выводится стек таблиц: виден один выбранный результат, а остальные доступны через чипы и кнопки предыдущего/следующего расписания; чипы результатов переносятся и отделены от счётчика стабильным отступом. Для режима кафедры и роли `DEPARTMENT` расписание ограничивается кафедрой пользователя; преподавательские и студенческие отдельные страницы пока остаются самостоятельными. Таблица строится как строки пар и столбцы дней недели. Нечётная неделя отображается в верхней половине ячейки, чётная — в нижней, а одинаковые занятия в обе недели схлопываются в цельную ячейку. Бейдж диапазона недель скрывается для занятий на весь семестр, а для занятий до конца семестра показывает только неделю начала в формате `(с 5 нед.)`. На мобильной ширине вместо широкой недельной матрицы показывается один день активного расписания с переключателем дней.
|
||||
- Для `ADMIN` и `EDUCATION_OFFICE` карточка занятия содержит кнопку `Изменить`, а уже изменённая пара — индикатор разовой правки. Справа открывается полупрозрачная боковая панель с размытием содержимого под ней; внешний затемнённый слой также размывает страницу, а на мобильном устройстве панель занимает весь экран. Режим `Редактирование` сравнивает `Было по правилу / Станет`, позволяет изменить дату, эффективный временной слот, преподавателя, аудиторию, формат и комментарий, отменить занятие или удалить override через `Вернуть по правилу`. Селект аудитории получает записи из `/api/classrooms`, но показывает только поле `name`, без корпуса и этажа. По умолчанию выводятся семь дней исходной недели; кнопка `Выбрать другую дату` раскрывает календарь всего семестра, где неучебные даты отключены. После смены даты загружается эффективная сетка дня: сначала выбирается тот же ID слота, затем совпадающий интервал, иначе требуется ручной выбор. Дата или время формируют `MOVE`, а только преподаватель, аудитория или формат — `REPLACE`.
|
||||
|
||||
Reference in New Issue
Block a user