задачи 2 и 8

This commit is contained in:
Zuev
2026-08-11 18:18:08 +03:00
parent 519864b962
commit 18a97b293a
59 changed files with 6237 additions and 276 deletions

View File

@@ -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 | Назначение |