feat: explain council selection rules on the first XLSX sheet
CI / test (pull_request) Successful in 10m32s
CI / Fingerprint diagnostics (production deployment temporarily disabled) (pull_request) Has been skipped

This commit is contained in:
codex committed 2026-10-12 17:51:43 +03:00
1 parent 2baa2e1aa1
commit 962e936ad1
7 files changed
+79 -11

No files matched your search

+5 -4
View File
@@ -1,19 +1,20 @@
## Сделано
- Ветка `feature/council-xlsx-selection`, создана от обновлённого `main` (`7c02b08`) после слияния PR #71. Версия приложения/frontend/backend `1.4.0`.
- Ветка `feature/council-xlsx-selection`, создана от обновлённого `main` (`7c02b08`) после слияния PR #71. Версия приложения/frontend/backend `1.4.1`.
- «Всего» в XLSX использует `candidate.total_count`, как сайт: Q1/Q2/К1/RSCI, у доктора также К2; самостоятельный К2 у кандидата/PhD исключается, пересечения не увеличивают итог.
- Первый лист «Подбор диссовета»: специальность ВАК, допустимая отрасль с автоматическим выбором совместимой, поиск, степень, статус, сортировка, счётчики, подтверждённый зачёт, минимум и профиль ВШЭ. Без макросов и новых зависимостей.
- Для переключения фильтров выгружаются все сотрудники с учёной степенью и их публикации. Начальные фильтры берутся с сайта; подробные листы сохраняют названия и столбцы. Данные являются снимком; изменения Excel не отправляются на сайт.
- Исходные строки остаются текстом; формулы записываются отдельно. Формат и поведение описаны в README и docs/council-evidence.md.
- На первом листе добавлена памятка под списком и ссылки к ней/обратно к фильтрам. Памятка поясняет работу с листом, счётчики, К2, подтверждения и пороги, ограничения специальностей, источников, снимка и Excel; правила расчёта и адреса фильтров не менялись.
- Проверки: полный pytest — 416 passed, 11 skipped; синтаксис четырёх JS-файлов и двух shell-скриптов CI — passed; `git diff --check main` — passed.
- В установленном Windows Excel 16.0 проверены 14 сценариев на синтетических данных и пустой файл: результаты, сортировка, поиск (включая буквальный *), специальности, совместимость отраслей, степень, статусы и пороги совпали с сервисом сайта. Артефакты: `.impeccable/review/council-xlsx/sample-checked.xlsx`, `cases.json`, `check.ps1` (игнорируются Git).
- В установленном Windows Excel 16.0 проверены 14 сценариев на синтетических данных и пустой файл: результаты, сортировка, поиск (включая буквальный *), специальности, совместимость отраслей, степень, статусы и пороги совпали с сервисом сайта. Дополнительно проверены переходы к памятке и к фильтрам; памятка экспортирована в PDF, проверено наличие разделов и завершающей ссылки. Артефакты: `.impeccable/review/council-xlsx/sample-checked.xlsx`, `memo-preview.pdf`, `cases.json`, `check.ps1` (игнорируются Git).
## Остановились на
- Реализация готова к коммиту и публикации нового PR в `main`. Текущая ветка ещё не опубликована.
- Открыт PR #72: https://git.vakanaut.ru/admin/miem_workers/pulls/72, исходная реализация — `2baa2e1`. Пользователь попросил добавить памятку на первый лист; дополнение готово к коммиту и push в тот же PR.
- PostgreSQL-тесты пропущены без отдельной тестовой БД; production не изменялся. Тестирование Excel проводилось только на синтетическом наборе.
## Следующий шаг
- Коммит, `git push -u origin feature/council-xlsx-selection`, поиск существующего PR и создание единственного PR в `main`, если отсутствует.
- Сохранить дополнение коммитом и отправить `git push -u origin feature/council-xlsx-selection` в существующий PR #72.
- После публикации остановиться и ждать независимого ревью в отдельной сессии и дальнейших указаний пользователя. Не выполнять ревью из авторской сессии и не сливать PR.
+1 -1
View File
@@ -134,7 +134,7 @@ Compose запускает `api`, `worker` и `postgres`. API привязан
Подтверждения хранятся отдельно от перезаписываемых парсером данных, в `app/data/council_evidence.json`; по умолчанию файл пустой. Формат, привязка к сотруднику/работе, правила конфликта, монографии и нормативные уточнения: [docs/council-evidence.md](docs/council-evidence.md). Непубличные документы и контакты на страницу не выводятся. РИНЦ или поле `hse_list` не подтверждают RSCI. Неизвестные виды, конфликтующие годы и неподтверждённые основания не создают положительного заключения.
Доступны поиск, сортировка, страницы по 25 сотрудников и XLSX. Первый лист «Подбор диссовета» работает в Excel Microsoft 365 / Excel 2021 и новее без макросов: выбор специальности ВАК, допустимой отрасли, поиск по ФИО, фильтры по степени и статусу, сортировка по ФИО и счётчикам. Начальные фильтры взяты с сайта. При несовместимой отрасли после смены специальности применяется первая допустимая; она показана рядом. Список показывает Q1/Q2/К1/К2/RSCI, «Всего», подтверждённый зачёт, минимум и статус; ссылки ведут на публичные профили ВШЭ. Для переключения фильтров в файл включены все сотрудники с учёной степенью, даже если страница сайта была отфильтрована. Компактные листы «Сотрудники» (12 столбцов) и «Публикации» (14 столбцов) также содержат полный набор данных, а правила и исходные фильтры вынесены в «Параметры». Служебные данные и справочники находятся на скрытых листах. Внешние ссылки проверяются; исходные строки не выполняются как формулы Excel. Фильтр специальности по журналам К1 и ручным тематическим связям остаётся вспомогательным подбором, а не условием соответствия пунктам 5–6. XLSX хранит снимок на момент выгрузки: новые данные и ручные подтверждения требуют повторной выгрузки; изменения фильтров в Excel не отправляются на сайт.
Доступны поиск, сортировка, страницы по 25 сотрудников и XLSX. Первый лист «Подбор диссовета» работает в Excel Microsoft 365 / Excel 2021 и новее без макросов: выбор специальности ВАК, допустимой отрасли, поиск по ФИО, фильтры по степени и статусу, сортировка по ФИО и счётчикам. Начальные фильтры взяты с сайта. На первом листе ссылка «Открыть памятку по расчёту и использованию» ведёт к инструкции под списком: определения счётчиков и статусов, правило К2, ограничения специальностей и источников, различия снимка XLSX и сайта, работа с формулами. Из памятки можно вернуться к фильтрам. При несовместимой отрасли после смены специальности применяется первая допустимая; она показана рядом. Список показывает Q1/Q2/К1/К2/RSCI, «Всего», подтверждённый зачёт, минимум и статус; ссылки ведут на публичные профили ВШЭ. Для переключения фильтров в файл включены все сотрудники с учёной степенью, даже если страница сайта была отфильтрована. Компактные листы «Сотрудники» (12 столбцов) и «Публикации» (14 столбцов) также содержат полный набор данных, а правила и исходные фильтры вынесены в «Параметры». Служебные данные и справочники находятся на скрытых листах. Внешние ссылки проверяются; исходные строки не выполняются как формулы Excel. Фильтр специальности по журналам К1 и ручным тематическим связям остаётся вспомогательным подбором, а не условием соответствия пунктам 5–6. XLSX хранит снимок на момент выгрузки: новые данные и ручные подтверждения требуют повторной выгрузки; изменения фильтров в Excel не отправляются на сайт.
В XLSX «Научная степень» — текущая выбранная степень, а «Всего» совпадает с предварительным счётчиком сайта: уникальное объединение Q1/Q2/К1/RSCI, для доктора наук также К2. Для кандидата/PhD самостоятельный К2 исключается; работа с другим подходящим основанием учитывается один раз. «Всего из ЛК» и «Общее количество всех публикаций без дублей» учитывают все годы. Отсутствующий тип материала обозначается «не указан», положительное сообщение о периоде объединяется в «В пределах пяти лет». Версия 1.0.0 меняет набор и порядок столбцов несовместимо с прежними шаблонами обработки XLSX.
+56
View File
@@ -1,12 +1,14 @@
"""Offline council selection and detailed XLSX report from the site's assessment."""
from io import BytesIO
from textwrap import fill
from openpyxl import Workbook
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE
from openpyxl.styles import Alignment, Font, PatternFill
from openpyxl.utils import get_column_letter
from openpyxl.worksheet.datavalidation import DataValidation
from openpyxl.worksheet.hyperlink import Hyperlink
from openpyxl.workbook.defined_name import DefinedName
from app.version import BACKEND_VERSION, FRONTEND_VERSION
@@ -227,3 +229,57 @@ def _selection(workbook, result, selection_results):
workbook.calculation.forceFullCalc = True
workbook.calculation.calcMode = "auto"
source.sheet_state = options.sheet_state = "hidden"
memo_row = 12 + max(1, len(numbers))
sheet["A7"] = "Открыть памятку по расчёту и использованию"
sheet["A7"].hyperlink = Hyperlink(ref="A7", location=f"'Подбор диссовета'!A{memo_row}")
sheet["A7"].font = Font(name="Calibri", size=11, color="0563C1", underline="single")
memo = [
("Памятка: как пользоваться подбором",
"1. Меняйте зелёные поля: специальность ВАК и отрасль, поиск по ФИО, степень, статус, сортировку и направление. Список обновляется автоматически.\n"
"2. После смены специальности посмотрите поле «Применяется»: если прежняя отрасль несовместима, используется первая допустимая.\n"
"3. Для просмотра всех сотрудников выберите «Все специальности», «Все степени» и «Все статусы», очистите поиск. Чтобы искать потенциальных кандидатов, оставьте «Все статусы»: сведения могут требовать проверки.\n"
"4. «Найдено» показывает число строк текущего подбора. «Профиль ВШЭ» открывает внешний сайт и требует подключения к Интернету."),
("Какие данные включены",
"Файл содержит всех сотрудников с обнаруженной или документально подтверждённой степенью доктора, кандидата наук или PhD на момент выгрузки. Отсутствие публикаций не исключает сотрудника. Из истории степеней выбирается высшая текущая: доктор выше кандидата/PhD. Уволенные сотрудники также могут присутствовать; актуальность работы и возможность участия проверяйте на сайте и по документам."),
("Период и виды работ",
f"Оценка охватывает пять календарных лет: {result['start'].year}–{result['end'].year}. День и месяц не учитываются. Границы зафиксированы при выгрузке и не сдвигаются при открытии файла в следующем году. Известные другие виды материалов по умолчанию исключаются из счётчиков; для их допуска нужно отдельное проверенное основание. Неизвестный вид может попадать в предварительный счётчик, но для подтверждённого зачёта нужно установить вид работы."),
("Q1, Q2, К1, К2 и RSCI",
"Каждый счётчик показывает уникальные работы с соответствующим основанием в пятилетнем периоде. Q1/Q2 — применимые сведения о квартиле из источников и каталогов; К1/К2 — сопоставление журнала со справочником ВАК либо проверенная запись. RSCI требует отдельного основания: отметка РИНЦ его не заменяет. Одна работа может одновременно входить в несколько счётчиков; складывать их для получения итога нельзя."),
("Как считается «Всего»",
"Предварительный итог, как на сайте: уникальные работы Q1 ИЛИ Q2 ИЛИ К1 ИЛИ RSCI; для доктора наук также К2. Для кандидата/PhD работа с одним К2 не входит в итог. Если у неё есть другое допустимое основание, она учитывается по нему. Пересечения учитываются один раз. Само попадание в «Всего» не подтверждает выполнение требований к составу совета."),
("«Подтверждено» и подтверждение степени",
"«Подтверждено» — работы с допустимым видом, согласованным годом в периоде и проверенным применимым основанием без существенных конфликтов источников. Известная статья с журналом и Q1/Q2 за её год может засчитываться автоматически. Для К1/К2/RSCI требуется отдельная запись проверки работы. Ручная правка журнала или квартиля сама по себе не подтверждает зачёт. «Степень подтверждена» проверяется отдельно; обнаруженная степень в профиле не заменяет документальное подтверждение."),
("«Минимум» и «Статус»",
"Для доктора минимум: 9 подтверждённых публикаций по технической отрасли, 11 по физико-математической. Для кандидата/PhD — 20. Положительный статус требует достаточного подтверждённого зачёта и подтверждения высшей текущей степени. «Требует проверки» может означать нехватку подтверждений или найденных работ; это не доказательство отсутствия публикаций. Статус оценивает публикационные критерии и не является решением о включении в совет."),
("«ЛК сотрудника ВШЭ» и «Все публикации»",
"Эти счётчики охватывают все годы и категории. «ЛК сотрудника ВШЭ» — уникальные работы из личного кабинета. «Все публикации» — объединение личного кабинета и выгрузки ВШЭ без дублей. Они не равны пятилетнему «Всего» и не сравниваются с публикационным минимумом."),
("Специальности ВАК и ручная связь",
f"Подбор ограничен {len(specialties)} специальностями сайта. Сотрудник отбирается, если хотя бы одна подходящая работа за пять лет имеет справочную связь журнала К1 или ручную связь с выбранной специальностью. Числовые счётчики при этом включают все работы сотрудника по своим правилам, а не только работы этой специальности. «Не подтверждены» означает отсутствие кодов по этим правилам, а не отсутствие научной специальности у сотрудника. Ручная связь задаётся на сайте для журнала и применяется ко всем его статьям; она не подтверждает содержание каждой статьи по паспорту специальности."),
("Что не проверяется автоматически",
"Полнота публикаций, содержание каждой статьи по паспорту, актуальность работы сотрудника и все остальные условия включения в диссовет требуют отдельной проверки. Подробные причины и источники смотрите на сайте. Текущая категория ВАК не доказывает категорию в год статьи. Каталоги имеют ограниченный охват и годы метрик; они не подтверждают текущую индексацию или отсутствие журнала в других базах."),
("Почему цифры могут расходиться",
"Сравнивайте одинаковые период, фильтры и снимок данных. Неполные профили, совпадающие ФИО, отсутствие DOI, разные названия журналов и конфликты годов могут мешать присвоению или объединению работ. При одинаковых ФИО данные выгрузки ВШЭ не присваиваются автоматически. Необычные символы и пробелы поиска в Excel могут обрабатываться иначе, чем на сайте. «Сотрудники» и «Публикации» содержат полный набор и не фильтруются вместе с первым листом; «Параметры» фиксируют исходные настройки выгрузки."),
("Обновление данных и работа в Excel",
"XLSX — снимок: обновления сайта и ручные подтверждения попадут в него только при новой выгрузке. Изменения в Excel не отправляются на сайт и не создают подтверждений. Используйте Excel Microsoft 365 / Excel 2021 или новее с автоматическим пересчётом формул; макросы для подбора не нужны. Если список не обновился, включите автоматический расчёт в настройках формул. Меняйте зелёные поля, сохраняйте формулы таблицы и скрытые служебные листы, иначе подбор может перестать работать."),
]
for title, text in memo:
sheet.merge_cells(start_row=memo_row, start_column=1, end_row=memo_row, end_column=6)
cell = sheet.cell(memo_row, 1, title)
cell.font = Font(name="Calibri", size=12, bold=True, color="FFFFFF")
cell.fill = PatternFill("solid", fgColor="263640")
cell.alignment = Alignment(vertical="center")
sheet.row_dimensions[memo_row].height = 28
memo_row += 1
text = "\n".join(fill(paragraph, width=90, break_on_hyphens=False) for paragraph in text.splitlines())
sheet.merge_cells(start_row=memo_row, start_column=1, end_row=memo_row, end_column=6)
cell = sheet.cell(memo_row, 1, text)
cell.font = Font(name="Calibri", size=11, color="263640")
cell.alignment = Alignment(vertical="top", wrap_text=True)
sheet.row_dimensions[memo_row].height = (text.count("\n") + 1) * 15 + 10
memo_row += 3
cell = sheet.cell(memo_row, 1, "Вернуться к фильтрам")
cell.hyperlink = Hyperlink(ref=cell.coordinate, location="'Подбор диссовета'!A1")
cell.font = Font(name="Calibri", size=11, color="0563C1", underline="single")
sheet.merge_cells(start_row=memo_row, start_column=1, end_row=memo_row, end_column=6)
sheet.row_dimensions[memo_row].height = 28
+3 -3
View File
@@ -1,3 +1,3 @@
APP_VERSION = "1.4.0"
FRONTEND_VERSION = "1.4.0"
BACKEND_VERSION = "1.4.0"
APP_VERSION = "1.4.1"
FRONTEND_VERSION = "1.4.1"
BACKEND_VERSION = "1.4.1"
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "miem-workers"
version = "1.4.0"
version = "1.4.1"
description = "MIEM employees parser, admin API, and web admin"
requires-python = ">=3.11"
dependencies = [
+1 -1
View File
@@ -18,7 +18,7 @@ def test_health_returns_versions():
response = TestClient(app).get("/api/health")
assert response.status_code == 200
assert response.json()["backend_version"] == "1.4.0"
assert response.json()["backend_version"] == "1.4.1"
def test_api_employees_and_stats_require_admin_session():
+12 -1
View File
@@ -230,7 +230,7 @@ def test_public_page_data_minimization_and_safe_links(db_session, export):
assert "javascript:" not in fragment.text and "private@example.org" not in fragment.text
assert "javascript:" not in response.text and "private@example.org" not in response.text
assert "/admin/employees/" not in response.text and "/admin/logout" not in response.text
assert "Backend 1.4.0 · Frontend 1.4.0" in response.text
assert "Backend 1.4.1 · Frontend 1.4.1" in response.text
assert "Вход в систему" in response.text
assert client.get("/admin/directory", follow_redirects=False).status_code in (302, 303, 401, 403)
assert "Сотрудники не найдены" in client.get("/admin/council?q=missing").text
@@ -362,6 +362,17 @@ def test_xlsx_selection_contains_full_data_and_preserves_initial_filters(db_sess
assert options.cell(row, 2).value in book.defined_names
assert '_xlfn.AGGREGATE' in dashboard['S10'].value
assert '$C$3' in dashboard['F4'].value and 'IFERROR' in dashboard['F4'].value
memo_address = dashboard['A7'].hyperlink.location.split('!', 1)[1]
assert dashboard[memo_address].value == 'Памятка: как пользоваться подбором'
assert dashboard[memo_address].row > 11
memo_text = ' '.join(str(dashboard.cell(row, 1).value or '').replace('\n', ' ')
for row in range(dashboard[memo_address].row, dashboard.max_row + 1))
for explanation in ('Для кандидата/PhD', 'Подтверждено', '9 подтверждённых',
'11 по физико-математической', '20', 'Не подтверждены', 'все работы сотрудника',
'Уволенные', 'автоматический расчёт', 'не отправляются на сайт'):
assert explanation in memo_text
assert any(cell.hyperlink and cell.hyperlink.location == "'Подбор диссовета'!A1"
for row in dashboard for cell in row)
assert book.calculation.calcMode == 'auto' and book.calculation.forceFullCalc
with ZipFile(BytesIO(response.content)) as archive:
assert not any('vbaProject' in name or 'externalLinks' in name for name in archive.namelist())