diff --git a/README.md b/README.md index 79120bd..514c8e1 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Compose запускает `api`, `worker` и `postgres`. API привязан Счётчики **Q1**, **Q2**, **К1** и **ВСЕГО** учитывают уникальные публикации за последние пять лет по году. Q1/Q2 берутся из отметок Scopus или Web of Science в CSV и сохранённом CSV-обогащении БД. К1 определяется по совпадению нормализованного названия журнала с итоговой категорией в предоставленном справочнике ВАК. ВСЕГО — объединение Q1 ИЛИ Q2 ИЛИ К1: работа в нескольких категориях учитывается один раз. При расхождении годов источников каждый критерий проверяется по году своей записи; неизвестный год наследуется только при единственном согласованном годе. Счётчики «В БД» и «Все публикации» включают все годы независимо от категорий. -CSV `app/data/council_articles.csv` сопоставляется по полному ФИО только при единственном совпадении. Публикации объединяются по DOI, затем по нормализованным названию и году, если нет конфликтующих DOI. Импорт CSV в БД не выдаётся за данные сайта. Для К1 поддерживаются необязательные колонки CSV «Название журнала» или «Журнал»; в исходном CSV их нет. В БД название берётся из поля `journal` в `raw_data` или короткого описания ВШЭ (`description.short`/`shortLeft`, название перед годом). Название самой публикации для определения журнала не используется. Неизвестный или несовпавший журнал не считается К1; перевод и нечёткое сопоставление не применяются. +CSV `app/data/council_articles.csv` сопоставляется по полному ФИО только при единственном совпадении. Публикации объединяются по DOI, затем по нормализованным названию и году, если нет конфликтующих DOI. Импорт CSV в БД не выдаётся за данные сайта. Для К1 поддерживаются необязательные колонки CSV «Название журнала» или «Журнал»; в исходном CSV их нет. В БД название берётся из поля `journal` в `raw_data` или короткого описания ВШЭ (`description.short`/`shortLeft`, название перед годом). Название самой публикации для определения журнала не используется. Неизвестный или несовпавший журнал не считается К1; перевод и нечёткое сопоставление не применяются. Явно указанное в PDF прежнее название журнала сохраняется как проверенный алиас; служебное примечание о переименовании хранится отдельно от названия. Справочник `app/data/k1_journals.json` содержит 773 журнала К1, номера строк, даты категорирования и SHA256 исходного «ВАК категории.pdf». При извлечении проверены все 3134 строки; для записей с историей смены категории взята последняя категория. PDF при запросах не читается. Для обновления справочника разово установите `pdfplumber` в среду разработки и выполните: @@ -155,7 +155,7 @@ node --check app/static/admin.js Тесты с данными используют временную SQLite; отдельные API smoke-тесты запускают приложение с его текущей конфигурацией. Браузерная проверка: `pip install playwright`, `python -m playwright install chromium`, затем `python tests/browser_admin.py`. Она проверяет реальный рендеринг страниц, клавиатуру, диалог, фильтры и восстановление прогресса после ошибки. Скриншоты сохраняются во временную папку; путь выводится в конце. -Версия сервиса: `0.14.1`. Админка всегда показывает версии backend и frontend в footer. +Версия сервиса: `0.14.2`. Админка всегда показывает версии backend и frontend в footer. ## Разовый импорт публикаций diff --git a/app/data/k1_journals.json b/app/data/k1_journals.json index 24619c4..f90af7d 100644 --- a/app/data/k1_journals.json +++ b/app/data/k1_journals.json @@ -3499,9 +3499,13 @@ "source_row": 698 }, { - "name": "Ученые записки Казанской академии ветеринарной медицины им. Н.Э. Баумана (до 10.04.2026 г. наименование в Перечне «Ученые записки Казанской государственной академии ветеринарной медицины им. Н.Э. Баумана»)", + "name": "Ученые записки Казанской академии ветеринарной медицины им. Н.Э. Баумана", "categorized_on": "01.01.2024", - "source_row": 699 + "source_row": 699, + "note": "до 10.04.2026 г. наименование в Перечне «Ученые записки Казанской государственной академии ветеринарной медицины им. Н.Э. Баумана»", + "aliases": [ + "Ученые записки Казанской государственной академии ветеринарной медицины им. Н.Э. Баумана" + ] }, { "name": "Ученые записки университета им. П.Ф. Лесгафта", diff --git a/app/services/council.py b/app/services/council.py index c01003f..a60fa83 100644 --- a/app/services/council.py +++ b/app/services/council.py @@ -66,7 +66,14 @@ def _journal_key(value: str) -> str: @lru_cache(maxsize=1) def _k1_journals() -> dict[str, str]: data = json.loads(K1_JOURNALS.read_text(encoding="utf-8")) - return {_journal_key(journal["name"]): journal["name"] for journal in data["journals"]} + journals = {} + for journal in data["journals"]: + for name in [journal["name"], *journal.get("aliases", [])]: + key = _journal_key(name) + if key in journals and journals[key] != journal["name"]: + raise ValueError("Ambiguous K1 journal alias") + journals[key] = journal["name"] + return journals def _journal_names(data: dict) -> list[str]: diff --git a/app/version.py b/app/version.py index b7d2ab0..9adfae6 100644 --- a/app/version.py +++ b/app/version.py @@ -1,3 +1,3 @@ -APP_VERSION = "0.14.1" -FRONTEND_VERSION = "0.14.1" -BACKEND_VERSION = "0.14.1" +APP_VERSION = "0.14.2" +FRONTEND_VERSION = "0.14.2" +BACKEND_VERSION = "0.14.2" diff --git a/pyproject.toml b/pyproject.toml index 8ea2800..882d027 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "miem-workers" -version = "0.14.1" +version = "0.14.2" description = "MIEM employees parser, admin API, and web admin" requires-python = ">=3.11" dependencies = [ diff --git a/scripts/extract_k1_journals.py b/scripts/extract_k1_journals.py index 1ee4458..7de0793 100644 --- a/scripts/extract_k1_journals.py +++ b/scripts/extract_k1_journals.py @@ -8,6 +8,7 @@ from datetime import datetime import hashlib import json from pathlib import Path +import re import pdfplumber @@ -28,8 +29,18 @@ def extract(source: Path) -> dict: rows.append(row) if not rows or [int(row[0]) for row in rows] != list(range(1, len(rows) + 1)): raise ValueError("Journal numbering is incomplete or duplicated") - journals = [{"name": " ".join(row[1].split()), "categorized_on": row[3], "source_row": int(row[0])} - for row in rows if row[2] == "К1"] + journals = [] + for row in rows: + if row[2] != "К1": + continue + name = " ".join(row[1].split()) + journal = {"name": name, "categorized_on": row[3], "source_row": int(row[0])} + # Only an explicit administrative rename note supplies an alias. + # Parentheses in journal names (e.g. bilingual titles) stay unchanged. + rename = re.fullmatch(r"(.+?) \((до \d{2}\.\d{2}\.\d{4} г\. наименование в Перечне «(.+)»)\)", name) + if rename: + journal.update(name=rename[1], note=rename[2], aliases=[rename[3]]) + journals.append(journal) if not journals: raise ValueError("No K1 journals found") return {"source": source.name, "source_sha256": hashlib.sha256(source.read_bytes()).hexdigest(), diff --git a/tests/test_api.py b/tests/test_api.py index 1bba340..a891964 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -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"] == "0.14.1" + assert response.json()["backend_version"] == "0.14.2" def test_api_employees_and_stats_require_admin_session(): diff --git a/tests/test_council.py b/tests/test_council.py index db3dd99..c8791f1 100644 --- a/tests/test_council.py +++ b/tests/test_council.py @@ -211,7 +211,7 @@ def test_public_page_data_minimization_and_safe_links(db_session, export): assert "<script>" in response.text and "