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 работ. Предварительный подсчёт и подтверждённые работы показаны отдельно.Диссертационный совет
-
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 @@