diff --git a/docs/API.md b/docs/API.md index c4bd4fe..39ed207 100644 --- a/docs/API.md +++ b/docs/API.md @@ -902,7 +902,7 @@ API возвращает `409 Conflict`; соседние интервалы и | Метод | URL | Назначение | |-------|-----|------------| -| `GET` | `/api/edu-office/schedule/quality?semesterId=&versionId=` | Итоговая оценка, метрики и объяснимый список проблем выбранной версии | +| `GET` | `/api/edu-office/schedule/quality?semesterId=&versionId=` | Итоговая оценка, метрики и объяснимый список проблемных правил выбранной версии | | `GET` | `/api/edu-office/schedule/quality/recommendations?semesterId=&versionId=&scheduleRuleSlotId=&lessonDate=` | Проверенные локальные варианты улучшения опубликованного занятия | Доступ имеют только `ADMIN` и `EDUCATION_OFFICE`. `versionId` необязателен: без него @@ -929,7 +929,7 @@ override текущей публикации. Для них доступны о "score": 84, "rawPenalty": 17, "lessonCount": 36, - "totalProblemCount": 5, + "totalProblemCount": 1, "problemsTruncated": false, "metrics": [ { @@ -943,11 +943,16 @@ override текущей публикации. Для них доступны о ], "problems": [ { - "id": "GROUP_GAP:2026-09-14:31:18", + "id": "RULE:24", "type": "GROUP_GAP", "severity": "MEDIUM", - "title": "Окно в расписании группы", - "penalty": 2, + "title": "Правило «Архитектура систем» снижает качество", + "description": "3 занятия этого правила снижают оценку: Окно в расписании группы; Избыточно крупная аудитория", + "penalty": 8, + "scheduleRuleId": 24, + "affectedLessonCount": 3, + "problemTypes": ["GROUP_GAP", "ROOM_OVERSIZED"], + "issueLabels": ["Окно в расписании группы", "Избыточно крупная аудитория"], "scheduleRuleSlotId": 31, "lessonDate": "2026-09-14", "optimizable": true @@ -957,8 +962,11 @@ override текущей публикации. Для них доступны о ``` `score` находится в диапазоне от 0 до 100 и нормализует сумму объяснимых штрафов -относительно числа занятий. Поле `totalProblemCount` содержит полный размер результата, -а массив `problems` ограничен 300 элементами; `problemsTruncated` сообщает об усечении. +относительно числа занятий. Нарушения сначала рассчитываются для каждого фактического +занятия, затем объединяются по `scheduleRuleId`: `penalty` хранит сумму всех штрафов правила, +`affectedLessonCount` — число затронутых занятий, `problemTypes` и `issueLabels` — все его +категории и объяснения. Поле `totalProblemCount` содержит число проблемных правил, а массив +`problems` ограничен 300 элементами; `problemsTruncated` сообщает об усечении. Рекомендации подбирают другой эффективный временной слот того же учебного дня либо подходящую активную аудиторию. Занятия, уже закреплённые ручным override, исключаются. diff --git a/docs/BUSINESS_LOGIC.md b/docs/BUSINESS_LOGIC.md index 5d67741..32cd66e 100644 --- a/docs/BUSINESS_LOGIC.md +++ b/docs/BUSINESS_LOGIC.md @@ -447,9 +447,11 @@ constraint. При чтении API разворачивает периоды о Неравномерность дневной нагрузки выводится отдельной диагностической метрикой в процентах и не добавляет скрытого штрафа к итоговой оценке. -В ответе сохраняются исходный штраф, вклад каждого критерия и конкретные проблемы с -датой, занятием и затронутой сущностью. Это делает оценку воспроизводимой и позволяет -фильтровать проблемы по типу и серьёзности. +Внутренне штраф рассчитывается для каждого фактического занятия, поэтому повторяющиеся +пары одного правила увеличивают его вклад в оценку. Перед выдачей нарушения объединяются +по правилу: одна карточка содержит суммарный штраф, число затронутых занятий, все типы и +краткие причины. Это сохраняет воспроизводимость оценки, но направляет пользователя к +изменению базового правила вместо ручного исправления каждой пары. Для проблемы опубликованной версии помощник перебирает другие слоты эффективной сетки того же дня и активные аудитории достаточной вместимости. Каждый вариант повторно проходит @@ -457,9 +459,10 @@ constraint. При чтении API разворачивает периоды о показывает улучшения и компромиссы. Занятия с ручным override считаются закреплёнными и не получают рекомендаций. Применение возможно только после подтверждения пользователя через обычный механизм `schedule_overrides`; автоматической публикации и полного solver в MVP -нет. Для черновика доступны те же оценка, метрики и объяснимые проблемы, но рекомендации -не создаются: пользователь исправляет правила в изолированном конструкторе и повторяет -проверку до публикации. Архив анализируется только для чтения. +нет. Интерфейс анализа для опубликованной версии и черновика открывает выбранное правило +в конструкторе соответствующей версии; черновик остаётся изолированным до публикации. +Архив анализируется только для чтения. API локальных рекомендаций сохраняется для +совместимости и точечных сценариев, но реестр качества работает на уровне правил. ## Привязка преподаватель ↔ дисциплина diff --git a/docs/FRONTEND.md b/docs/FRONTEND.md index 5b10a80..501b897 100644 --- a/docs/FRONTEND.md +++ b/docs/FRONTEND.md @@ -77,7 +77,7 @@ frontend/ │ │ ├── schedule-view.js # Просмотр расписаний и запуск разовой правки из карточки │ │ ├── schedule-override-panel.js # Боковая панель и реестр разовых изменений │ │ ├── teacher-absences.js # Отсутствия, согласование пожеланий и заявок на изменение -│ │ ├── schedule-quality.js # Оценка, фильтры и локальные рекомендации +│ │ ├── schedule-quality.js # Оценка, фильтры и переход к редактированию правил │ │ ├── schedule-versions.js # Черновики, публикация, восстановление, diff и аудит │ │ ├── schedule.js # Конструктор правил и подсказки пожеланий преподавателей │ │ ├── academic-calendar-grid.js # Расчёт ISO-недели дневной сетки @@ -183,7 +183,7 @@ frontend/ | `teacher-absences` | Запросы преподавателей: отсутствия и мастер замены, согласование семестровых пожеланий, заявки на перенос, аудиторию или отмену | `/api/teacher-absences`, `/api/teacher-preferences`, `/api/teacher-change-requests`, `/api/users/teachers` | | `schedule` | Конструктор правил: по умолчанию редактирует опубликованное расписание, позволяет переключиться на черновик или создать его из выбранной версии; содержит подсказки пожеланий и визуальную матрицу групп | `/api/admin/schedule-rules`, `/api/edu-office/schedule/versions`, `/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` | +| `schedule-quality` | Диагностика выбранной версии семестра: оценка, метрики и проблемные правила с переходом к их редактированию | `/api/edu-office/schedule/quality`, `/api/admin/schedule-rules` | | `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` | @@ -209,14 +209,13 @@ frontend/ действий. Каждая карточка показывает исходное и запрошенное состояние, результат предварительной проверки и хронологию решения. - Вкладка `schedule-quality` доступна администратору и учебному отделу. После выбора - семестра и версии она выводит круговую оценку от 0 до 100, карточки метрик и реестр проблем с - фильтрами по серьёзности и типу. Для проблемы, связанной с конкретным незакреплённым - занятием опубликованной версии, кнопка `Подобрать улучшение` запрашивает проверенные варианты времени и - аудитории. Карточка кандидата показывает изменение оценки, улучшения и компромиссы; - override создаётся только после явного подтверждения, затем анализ запускается заново. - Для `DRAFT` экран рассчитывает те же метрики и проблемы напрямую по правилам черновика, - но направляет пользователя в конструктор и не создаёт override. `ARCHIVED` анализируется - только для чтения. Экран не предлагает автоматически менять занятия, уже отредактированные вручную. + семестра и версии она выводит круговую оценку от 0 до 100, карточки метрик и реестр правил + с фильтрами по серьёзности и типу. Все проблемные занятия одного правила показаны одной + карточкой; в ней выводятся число затронутых занятий, категории и суммарный штраф. Кнопка + `Изменить правило` открывает выбранную версию в конструкторе и сразу заполняет форму этим + правилом. Сценарий одинаков для `PUBLISHED` и `DRAFT`; `ARCHIVED` анализируется только для + чтения. Переход между вкладками передаёт ID версии и правила через одноразовые ключи + `magistr.schedule.openVersionId` и `magistr.schedule.openRuleId` в `localStorage`. - Вкладка `schedule-versions` доступна администратору и учебному отделу и оформлена как отдельный контур публикации. Верхняя карточка показывает версию, которую видят конечные пользователи; ниже расположены черновики, сравнение правил и занятий, архив и журнал. diff --git a/frontend/admin/css/schedule-quality.css b/frontend/admin/css/schedule-quality.css index 1cdf65f..8821c68 100644 --- a/frontend/admin/css/schedule-quality.css +++ b/frontend/admin/css/schedule-quality.css @@ -503,6 +503,38 @@ font-size: 0.7rem; } +.quality-rule-issues, +.quality-rule-detail-list { + display: flex; + flex-wrap: wrap; + gap: 0.35rem; +} + +.quality-rule-issues > span, +.quality-rule-detail-list > span { + padding: 0.22rem 0.45rem; + color: var(--text-secondary); + background: color-mix(in srgb, var(--quality-amber) 9%, transparent); + border: 1px solid color-mix(in srgb, var(--quality-amber) 24%, var(--bg-card-border)); + border-radius: 999px; + font-size: 0.66rem; + line-height: 1.2; +} + +.quality-rule-detail-list { + display: grid; + margin: 1rem 0; +} + +.quality-rule-detail-list > span { + border-radius: var(--radius-sm); + font-size: 0.76rem; +} + +.quality-edit-rule { + width: 100%; +} + .quality-penalty { color: var(--quality-red); font-family: Georgia, 'Times New Roman', serif; diff --git a/frontend/admin/js/views/schedule-quality.js b/frontend/admin/js/views/schedule-quality.js index 4314eb0..56605a9 100644 --- a/frontend/admin/js/views/schedule-quality.js +++ b/frontend/admin/js/views/schedule-quality.js @@ -186,7 +186,7 @@ function renderProblems() { `).join('')} Правило #${escapeHtml(String(problem.scheduleRuleId || '—'))} - · ${escapeHtml(String(affectedLessonCount))} ${escapeHtml(lessonWord(affectedLessonCount))} снижают оценку + · затронуто ${escapeHtml(String(affectedLessonCount))} ${escapeHtml(lessonWord(affectedLessonCount))} · суммарный штраф @@ -238,7 +238,7 @@ function renderRuleDetails(problem) {
Правило #${escapeHtml(String(problem.scheduleRuleId || '—'))}

${escapeHtml(problem.subjectName || problem.title)}

-

${escapeHtml(String(affectedLessonCount))} ${escapeHtml(lessonWord(affectedLessonCount))} снижают оценку; суммарный штраф −${escapeHtml(String(problem.penalty))}.

+

Затронуто ${escapeHtml(String(affectedLessonCount))} ${escapeHtml(lessonWord(affectedLessonCount))}; суммарный штраф −${escapeHtml(String(problem.penalty))}.

${issueLabels.map(label => `${escapeHtml(label)}`).join('')} diff --git a/frontend/admin/js/views/schedule.js b/frontend/admin/js/views/schedule.js index bee4a44..dcce48d 100644 --- a/frontend/admin/js/views/schedule.js +++ b/frontend/admin/js/views/schedule.js @@ -491,12 +491,25 @@ export async function initSchedule() { } renderRules(); renderVisualSchedule(); + openRequestedRule(); } catch (error) { rulesTbody.innerHTML = `Ошибка загрузки: ${escapeHtml(error.message)}`; setVisualState('Ошибка загрузки правил расписания'); } } + function openRequestedRule() { + const requestedRuleId = localStorage.getItem('magistr.schedule.openRuleId'); + if (!requestedRuleId) return; + localStorage.removeItem('magistr.schedule.openRuleId'); + const rule = rules.find(item => String(item.id) === requestedRuleId); + if (!rule) { + showAlert('schedule-rule-alert', 'Не удалось найти выбранное правило в этом расписании.', 'error'); + return; + } + fillRuleForm(rule); + } + function renderRules() { if (!rules.length) { rulesTbody.innerHTML = 'Правила расписания не созданы'; @@ -2079,6 +2092,13 @@ export async function initSchedule() { return new Date(year, month - 1, day); } + function localIsoDate(date) { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + } + function compactWeekRanges(weeks, maxWeeks, parity) { const uniqueWeeks = [...new Set((weeks || []).map(Number).filter(Boolean))].sort((a, b) => a - b); if (!uniqueWeeks.length) return ''; diff --git a/frontend/admin/views/schedule-quality.html b/frontend/admin/views/schedule-quality.html index 5f61fbe..c8c9933 100644 --- a/frontend/admin/views/schedule-quality.html +++ b/frontend/admin/views/schedule-quality.html @@ -5,8 +5,8 @@

Качество видно до перестановок

Анализатор находит окна, перегруженные дни, неудачные аудитории и нарушения - пожеланий. Черновик можно оценить до публикации, не показывая промежуточное - расписание студентам и преподавателям. + пожеланий. Все проблемные занятия объединяются по правилу, а их штрафы + суммируются. Черновик можно оценить до публикации.

@@ -55,8 +55,8 @@
- Карта неудобств -

Что снижает оценку

+ Карта правил +

Правила, снижающие оценку

@@ -89,8 +89,8 @@ diff --git a/frontend/tests/schedule-quality.test.mjs b/frontend/tests/schedule-quality.test.mjs index bdb44b7..0fb2155 100644 --- a/frontend/tests/schedule-quality.test.mjs +++ b/frontend/tests/schedule-quality.test.mjs @@ -17,7 +17,7 @@ test('оценка качества получает понятный диапа test('фильтр проблем одновременно учитывает важность и категорию', () => { const problems = [ - { type: 'GROUP_GAP', severity: 'HIGH' }, + { type: 'GROUP_GAP', problemTypes: ['GROUP_GAP', 'ROOM_OVERSIZED'], severity: 'HIGH' }, { type: 'ROOM_OVERSIZED', severity: 'LOW' }, { type: 'TEACHER_SOFT_UNWANTED', severity: 'HIGH' } ]; @@ -28,7 +28,7 @@ test('фильтр проблем одновременно учитывает в ); assert.deepEqual( filterQualityProblems(problems, '', 'ROOM'), - [problems[1]] + [problems[0], problems[1]] ); }); @@ -47,6 +47,8 @@ test('метрики и вкладка анализа подключены к SP assert.doesNotMatch(capabilities, /DEPARTMENT:[\s\S]*schedule-quality/); assert.match(main, /'schedule-quality':[\s\S]*schedule-quality\.js/); assert.match(index, /data-tab="schedule-quality"/); - assert.match(view, /Локальные улучшения/); - assert.match(script, /Применить после подтверждения/); + assert.match(view, /Правила, снижающие оценку/); + assert.match(script, /Изменить правило/); + assert.match(script, /magistr\.schedule\.openRuleId/); + assert.doesNotMatch(script, /quality\/recommendations/); }); diff --git a/frontend/tests/schedule-versions.test.mjs b/frontend/tests/schedule-versions.test.mjs index c4e864f..441905a 100644 --- a/frontend/tests/schedule-versions.test.mjs +++ b/frontend/tests/schedule-versions.test.mjs @@ -48,6 +48,8 @@ test('контур версий подключён к SPA, а конструкт assert.match(constructor, /scheduleVersionId:\s*Number\(scheduleVersionSelect\.value\)/); assert.match(constructor, /basedOnVersionId:\s*Number\(base\.id\)/); assert.match(constructor, /magistr\.schedule\.openVersionId/); + assert.match(constructor, /magistr\.schedule\.openRuleId/); + assert.match(constructor, /function localIsoDate\(date\)/); assert.match(constructor, /data-slot-id/); assert.match(view, /id="schedule-version-create-draft"/); assert.match(quality, /versionId=.*encodeURIComponent\(versionId\)/); diff --git a/graphify-out/memory/query_20260812_184153_в_качестве_расписания_если_правило_снижает_оценку.md b/graphify-out/memory/query_20260812_184153_в_качестве_расписания_если_правило_снижает_оценку.md new file mode 100644 index 0000000..05a5882 --- /dev/null +++ b/graphify-out/memory/query_20260812_184153_в_качестве_расписания_если_правило_снижает_оценку.md @@ -0,0 +1,17 @@ +--- +type: "query" +date: "2026-08-12T18:41:53.854324+00:00" +question: "в качестве расписания если правило снижает оценку качества расписания - то отображаются все пары которые это качество снижают, а должно отображаться одно правило, чтобы можно было поменять его, а не каждую пару по отдельности. баллы при этом должны суммироваться в зависимости от количества пар в правиле" +contributor: "graphify" +outcome: "dead_end" +--- + +# Q: в качестве расписания если правило снижает оценку качества расписания - то отображаются все пары которые это качество снижают, а должно отображаться одно правило, чтобы можно было поменять его, а не каждую пару по отдельности. баллы при этом должны суммироваться в зависимости от количества пар в правиле + +## Answer + +Query expanded to (from graph vocab, 7 tokens): [schedule, rule, lesson, service, dto, controller, generator]. Graph traversal returned generic Service nodes about authentication and tenant infrastructure and did not identify the quality-analysis flow, so the result was not used as source evidence. The implementation was verified directly in ScheduleQualityService, ScheduleQualityAnalysisDto, schedule-quality.js and schedule.js. + +## Outcome + +- Signal: dead_end \ No newline at end of file