diff --git a/app/admin.py b/app/admin.py index 87ff552..045ca0c 100644 --- a/app/admin.py +++ b/app/admin.py @@ -169,7 +169,7 @@ def council(request: Request, q: str = "", offset: int = 0, sort: str = "total_c authenticated = optional_user(request, settings, db) try: result = council_candidates(db, q=q, offset=offset, sort=sort, direction=direction, specialty=specialty, - branch=branch) + branch=branch, previous_specialty=request.query_params.get("previous_specialty")) except CouncilInputError as exc: return _render(request, "council.html", {"error": True, "public_page": True, "input_error": str(exc)}, status_code=422) except (SQLAlchemyError, OSError, ValueError, KeyError): @@ -303,7 +303,7 @@ def export_council(request: Request, q: str = "", sort: str = "total_count", dir db: Session = Depends(get_db)): try: result = council_candidates(db, q=q, sort=sort, direction=direction, paginate=False, specialty=specialty, - branch=branch) + branch=branch, previous_specialty=request.query_params.get("previous_specialty")) content = council_xlsx(result) except CouncilInputError as exc: return _render(request, "council.html", {"error": True, "public_page": True, "input_error": str(exc)}, status_code=422) diff --git a/app/data/k1_specialties.json b/app/data/k1_specialties.json index 8553634..c0bc4d7 100644 --- a/app/data/k1_specialties.json +++ b/app/data/k1_specialties.json @@ -9,51 +9,97 @@ "specialties": [ { "code": "1.2.2", - "name": "Математическое моделирование, численные методы и комплексы программ" + "name": "Математическое моделирование, численные методы и комплексы программ", + "branches": [ + "технические", + "физико-математические" + ] }, { "code": "2.2.2", - "name": "Электронная компонентная база микро- и наноэлектроники, квантовых устройств" + "name": "Электронная компонентная база микро- и наноэлектроники, квантовых устройств", + "branches": [ + "технические", + "физико-математические" + ] }, { "code": "2.3.6", - "name": "Методы и системы защиты информации, информационная безопасность" + "name": "Методы и системы защиты информации, информационная безопасность", + "branches": [ + "технические", + "физико-математические" + ] }, { "code": "2.3.1", - "name": "Системный анализ, управление и обработка информации, статистика" + "name": "Системный анализ, управление и обработка информации, статистика", + "branches": [ + "технические", + "физико-математические" + ] }, { "code": "2.3.7", - "name": "Компьютерное моделирование и автоматизация проектирования" + "name": "Компьютерное моделирование и автоматизация проектирования", + "branches": [ + "технические", + "физико-математические" + ] }, { "code": "1.3.8", - "name": "Физика конденсированного состояния" + "name": "Физика конденсированного состояния", + "branches": [ + "технические", + "физико-математические" + ] }, { "code": "2.2.15", - "name": "Системы, сети и устройства телекоммуникаций" + "name": "Системы, сети и устройства телекоммуникаций", + "branches": [ + "технические", + "физико-математические" + ] }, { "code": "1.1.2", - "name": "Дифференциальные уравнения и математическая физика" + "name": "Дифференциальные уравнения и математическая физика", + "branches": [ + "физико-математические" + ] }, { "code": "2.2.13", - "name": "Радиотехника, в том числе системы и устройства телевидения" + "name": "Радиотехника, в том числе системы и устройства телевидения", + "branches": [ + "технические", + "физико-математические" + ] }, { "code": "2.3.2", - "name": "Вычислительные системы и их элементы" + "name": "Вычислительные системы и их элементы", + "branches": [ + "технические" + ] }, { "code": "2.2.14", - "name": "Антенны, СВЧ-устройства и их технологии" + "name": "Антенны, СВЧ-устройства и их технологии", + "branches": [ + "технические", + "физико-математические" + ] }, { "code": "2.3.5", - "name": "Математическое и программное обеспечение вычислительных систем, комплексов и компьютерных сетей" + "name": "Математическое и программное обеспечение вычислительных систем, комплексов и компьютерных сетей", + "branches": [ + "технические", + "физико-математические" + ] } ], "journals": [ diff --git a/app/services/council.py b/app/services/council.py index 758aecc..7e0c40b 100644 --- a/app/services/council.py +++ b/app/services/council.py @@ -253,7 +253,8 @@ def _unique_articles(articles: list[dict], start: date, end: date, corrections=N def council_candidates(db: Session, today: date | None = None, q: str = "", offset: int = 0, sort: str = "total_count", direction: str = "desc", paginate: bool = True, specialty: str = "", - branch: str = "технические", employee_id: int | None = None) -> dict: + branch: str = "технические", employee_id: int | None = None, + previous_specialty: str | None = None) -> dict: if branch and branch not in BRANCHES: raise CouncilInputError("Неизвестная отрасль науки") today = today or datetime.now(ZoneInfo("Europe/Moscow")).date() @@ -261,6 +262,15 @@ def council_candidates(db: Session, today: date | None = None, q: str = "", offs specialties = _k1_specialties()["specialties"] if specialty and specialty not in {s["code"] for s in specialties}: raise CouncilInputError("Неизвестная специальность ВАК") + branches = list(dict.fromkeys(name for s in specialties if not specialty or s["code"] == specialty + for name in s["branches"])) + if branch and branch not in branches: + previous_branches = {name for s in specialties if previous_specialty == "" or s["code"] == previous_specialty + for name in s["branches"]} + if previous_specialty is not None and previous_specialty != specialty and branch in previous_branches: + branch = branches[0] + else: + raise CouncilInputError("Отрасль оценки не относится к выбранным специальностям ВАК") specialty_name = next((s["name"] for s in specialties if s["code"] == specialty), "") degree_sections = Employee.current_data["sections"] if db.get_bind().dialect.name == "postgresql": @@ -421,13 +431,14 @@ def council_candidates(db: Session, today: date | None = None, q: str = "", offs limit = 25 offset = min(max(0, offset), ((matched - 1) // limit) * limit) if matched else 0 criteria_groups = {} - for name, (minimum, monographs) in BRANCHES.items(): + for name in branches: + minimum, monographs = BRANCHES[name] criteria_groups.setdefault((minimum, monographs), []).append(name) return {"criteria_groups": [{"names": ", ".join(names), "minimum": minimum, "monographs": monographs} for (minimum, monographs), names in criteria_groups.items()], "journal_names": sorted({name for c in candidates for a in c["articles"] for name in a["journals"]}), "revision": revision, "candidates": candidates[offset:offset + limit] if paginate else candidates, "start": start, "end": end, "total": total, "specialties": specialties, "specialty": specialty, "specialty_name": specialty_name, - "branch": branch, "branches": list(BRANCHES), + "branch": branch, "branches": branches, "catalog_metadata": catalog()["metadata"], "catalog_issues": catalog()["issues"], "category_metadata": [{"file": path.name, **{key: data[key] for key in ("source", "source_sha256", "source_rows")}} for path in (K1_JOURNALS, K2_JOURNALS) diff --git a/app/static/council.js b/app/static/council.js index 880c2aa..06acf4b 100644 --- a/app/static/council.js +++ b/app/static/council.js @@ -1,4 +1,12 @@ (() => { + const specialty = document.querySelector('#council-specialty'); + const branch = document.querySelector('#council-branch'); + specialty?.addEventListener('change', () => { + const names = JSON.parse(specialty.selectedOptions[0].dataset.branches); + const previous = branch.value; + branch.replaceChildren(...names.map(name => new Option(name[0].toUpperCase() + name.slice(1), name))); + if (names.includes(previous)) branch.value = previous; + }); const dialog = document.querySelector('[data-council-dialog]'); if (!dialog) return; const form = dialog.querySelector('[data-edit-form]'); diff --git a/app/templates/council.html b/app/templates/council.html index a2f8e8a..0484638 100644 --- a/app/templates/council.html +++ b/app/templates/council.html @@ -5,7 +5,7 @@

Диссертационный совет

-

Подбор сотрудников в диссертационный совет по техническим наукам. Публикации за пять календарных лет, включая текущий год, сопоставляются с данными о квартилях и категориях журналов. Для доктора наук минимум зависит от отрасли оценки; для кандидата наук или PhD — 20 работ. Предварительный подсчёт и подтверждённые работы показаны отдельно.

+

Подбор сотрудников в диссертационный совет по техническим и физико-математическим наукам. Публикации за пять календарных лет, включая текущий год, сопоставляются с данными о квартилях и категориях журналов. Для доктора наук минимум зависит от отрасли оценки; для кандидата наук или PhD — 20 работ. Предварительный подсчёт и подтверждённые работы показаны отдельно.

@@ -26,7 +26,7 @@

Q1, Q2, К1, К2 и RSCI — предварительные показатели. Квартили определяются по данным ВШЭ, применимым данным каталогов и проверенным основаниям; категории К1/К2 — по сопоставлению журнала со справочниками ВАК. RSCI учитывается при наличии отдельной записи об этом основании; отметка РИНЦ сама по себе его не подтверждает. Сведения из профиля сотрудника дополняются внешними источниками.

ВСЕГО — число уникальных работ с основаниями Q1/Q2/К1/RSCI; для доктора наук учитывается также К2. Пересечения между основаниями не увеличивают итог. Для кандидата наук или PhD одной категории К2 недостаточно. Известные другие виды материалов не входят в этот подсчёт без отдельного подтверждения их допустимости.

«Подтверждено» — работы с проверенными видом материала, периодом и допустимым основанием. Известная статья автоматически засчитывается по Q1/Q2, если источник связывает её с журналом, квартиль применим к году публикации и нет конфликтов идентичности. Отдельное ручное подтверждение уже известных типа статьи и квартиля не требуется. Точная дата публикации не требуется; текущая категория журнала не подтверждает его категорию в год выхода работы. «Требует проверки» означает, что для вывода не хватает подтверждений: например, степени, года или основания зачёта работы. Это не означает, что у сотрудника нет нужных публикаций. Соответствие публикационному минимуму не означает автоматического включения в совет.

-

Порог зависит от отрасли науки, по которой сотрудник включается в совет. Сейчас расчёт выполняется по отрасли «{{ branch }}». Код специальности журнала сам по себе не меняет отрасль оценки.

+

Порог зависит от отрасли науки, по которой сотрудник включается в совет. Сейчас расчёт выполняется по отрасли «{{ branch }}». Доступные отрасли ограничены выбранной специальностью ВАК.

Связь журнала со специальностью можно изменить в редакторе; она применяется ко всем статьям этого журнала. Это связь тематики журнала, а не заключение о содержании каждой статьи.

Фильтр «Специальность ВАК» подбирает сотрудников с хотя бы одной публикацией за пять лет в подходящем журнале К1 либо журнале с ручной тематической привязкой. Связь определяется по журналу из Перечня ВАК на 15.09.2026, а не по содержанию статьи. Квартильные каталоги содержат ограниченные подборки и метрики 2025 года; они не подтверждают другие годы или текущую индексацию. Аналитическая связь журнала со специальностью и отдельное рассмотрение статьи показаны в публикациях.

@@ -34,6 +34,7 @@