Files
miem_workers/docs/publication-database.md
codex 194ed30dd4
CI / test (pull_request) Successful in 9m26s
CI / Fingerprint diagnostics (production deployment temporarily disabled) (pull_request) Has been skipped
fix: validate publication evidence identity and original journal ISSNs
2026-10-09 10:55:24 +03:00

20 KiB
Raw Permalink Blame History

Запись публикации в БД

Объект EmployeePublication, таблица employee_publications. Одна запись относится к одному сотруднику; одна работа у нескольких соавторов может иметь несколько записей с одним DOI. Типы ниже указаны для PostgreSQL; в SQLite JSONB представлен JSON.

Поле Тип Обязательное Назначение
id integer Да Первичный ключ
employee_id integer Да Внешний ключ employees.id; удаление сотрудника удаляет связанные записи
publication_id varchar(64) Нет Идентификатор публикации в источнике ВШЭ
title text Да Название
year integer Нет Год публикации
publication_type varchar(64) Нет Тип материала: ARTICLE, CHAPTER, BOOK, PREPRINT и другие
language varchar(16) Нет Язык
status integer Нет Код статуса из источника
url text Нет Основная ссылка
doi_url text Нет DOI или ссылка DOI
other_url text Нет Дополнительная ссылка
document_url text Нет Ссылка на документ
citation_text text Нет Библиографическое описание
quartiles jsonb Нет Квартили Q1–Q4 с системой, годом метрики, источником и результатом поиска
vak_categories jsonb Нет Результат проверки журнала по предоставленным спискам К1/К2, способ идентификации и источник
annotation jsonb Нет Аннотация
description jsonb Нет Структурированное описание
authors jsonb Нет Список авторов
raw_data jsonb Нет Исходные данные, включая обогащение из данных ВШЭ
source_hash varchar(64) Да Хеш исходной записи
created_at timestamptz Да Создание записи
updated_at timestamptz Да Обновление записи

Уникальны пары (employee_id, publication_id) и (employee_id, source_hash). Индексы: employee_id, publication_id, doi_url, year, publication_type. Атрибут ORM employee ссылается на объект сотрудника и отдельным столбцом не является.

Поле квартилей

NULL означает, что обогащение ещё не выполнялось. После проверки поле содержит объект со схемой версии 1. Пустой entries означает отсутствие найденного квартиля; причина сохраняется в lookup. Пустой результат не равнозначен Q4 и не доказывает отсутствие индексации.

Пример структуры (иллюстративные данные):

{
  "schema_version": 1,
  "entries": [
    {
      "system": "Scopus",
      "quartile": "Q2",
      "doi": "10.1234/example",
      "metric": "CiteScore",
      "metric_year": 2024,
      "publication_year": 2024,
      "category": "Applied Mathematics",
      "journal": "Название журнала",
      "issns": ["0006-3835"],
      "source": "Название проверенного источника",
      "source_url": "https://example.org/metrics",
      "checked_on": "2026-10-08"
    }
  ],
  "lookup": {
    "doi": "10.1234/example",
    "status": "found",
    "requested_year": 2024,
    "checked_on": "2026-10-08"
  }
}
  • entries сохраняет все найденные категории и системы отдельно, без выбора одного «лучшего» квартиля вместо остальных. Возможные системы: Scopus, Web of Science, SCImago. Метрики CiteScore, JCR и SJR не подменяют друг друга. При наличии только лучшего квартиля SJR используется метрика SJR best quartile, категория остаётся null.
  • Каждое основание хранит собственный doi. При дополнении старого payload основания без этого ключа получают DOI из прежнего lookup до его обновления; при отсутствии прежнего DOI сохраняется null. Новый общий lookup не перепривязывает прежние записи. DOI данных ВШЭ берётся из их собственных исходных метаданных.
  • Перед использованием в счётчиках основание сверяется с текущими DOI, журналом и ISSN публикации. Конфликт исключает это основание и выводится как причина перепроверки; доказательства сохраняются. Совпадающий ISSN позволяет подтвердить переименование журнала, но не снимает конфликт DOI. Основание без проверяемой привязки не засчитывается по одному совпадению года. Это также относится к обратному чтению raw_data.csv_enrichment.
  • Значения Квартиль scopus и Квартиль wos из raw_data.csv_enrichment переносятся в отдельные записи Q1–Q4. Их точная метрика и год метрики неизвестны: metric и metric_year остаются null. Год публикации не объявляется годом метрики. Исходное обогащение не удаляется.
  • При поиске новых значений журнал определяется по DOI через Crossref, а при отсутствии записи — через публичный DataCite API. ISSN связывает журнал с рейтингом. Проверяются идентичность DOI, тип материала и соответствие года публикации данным DOI. Книге или главе не присваивается журнальный квартиль по одному похожему названию. Препринт DataCite не получает квартиль журнала связанной статьи автоматически.
  • Для старого ISSN допускается подтверждённая преемственность названия по официальному списку Scopus за август 2026. Нужны явная связь Continued as/Continues и единственное связанное название, совпадающее с журналом в ответе DOI. Похожесть названий не используется. Исходные ISSN Crossref сохраняются; дополнительные ISSN для рейтинга и ссылка на историю записываются отдельно в lookup, ссылка journal_identity_source_url также сохраняется в найденных квартилях.
  • Для каждой системы сначала используется метрика за год публикации. При явно разрешённом --allow-latest и отсутствии исторической метрики допускается последний доступный год: он остаётся в metric_year, а результат имеет статус found_latest. Все категории CiteScore/JCR и SJR сохраняются вместе; разные системы могут иметь разные годы метрики. Это последний год в доступных проверенных источниках, а не обещание глобально самого нового рейтинга.
  • Источники: проверяемые каталоги проекта (CiteScore/JCR) и исторические SJR-данные Universidad de Extremadura за 2009–2025. Портал указывает SCImago как автора и поддерживающую организацию и публикует данные по CC BY 3.0; атрибуция и ссылка сохраняются в каждой записи. SJR хранится с системой SCImago, даже несмотря на происхождение исходных данных из Scopus.

lookup также может содержать полученные по DOI тип материала, названия журнала, ISSN, годы и ссылку на регистратора DOI. Состояния поиска:

Статус Значение
found Найдена метрика за год публикации
found_latest Использован другой, последний доступный год
missing_doi / invalid_doi DOI отсутствует или некорректен
doi_not_found / doi_lookup_failed DOI не найден в Crossref и DataCite / источник не ответил успешно; DOI может относиться к другому регистратору
doi_identity_conflict Ответ источника относится к другому DOI
not_journal_article DOI относится к материалу, не определённому как журнальная статья
missing_publication_year / publication_year_conflict Год неизвестен / отличается от годов источника DOI
missing_journal_issn Журнал по ISSN не установлен
journal_identity_conflict Идентификаторы связываются с несколькими журналами
ranking_category_conflict Сведения о категориях SJR противоречат друг другу
historical_quartile_unavailable Проверенные источники не дали допустимой метрики

Миграция и обогащение

Миграции migrations/010_publication_quartiles.sql и migrations/011_publication_vak_categories.sql добавляют nullable JSONB-поля без удаления или перезаписи данных. Инициализация приложения также добавляет поля на существующей схеме PostgreSQL/SQLite. Обогащение не запускается при старте API или обработке HTTP-запроса.

Перед изменением заполненной БД создать и проверить резервную копию. Подключение инструмента — только через DATABASE_URL из окружения; .env и файлы с credentials инструмент не читает. Не передавать секреты в аргументах и не коммитить отчёты, кэши и резервные копии.

Сначала выполнить предварительное сравнение без записи, затем перенос известных значений:

python scripts/enrich_publication_quartiles.py --cache-dir .impeccable/quartiles/cache --report .impeccable/quartiles/hse-plan.json
python scripts/enrich_publication_quartiles.py --apply --cache-dir .impeccable/quartiles/cache --report .impeccable/quartiles/hse-applied.json

Затем проверить DOI у записей без квартиля. Для предварительного сравнения убрать --apply:

python scripts/enrich_publication_quartiles.py --apply --lookup-doi --all-systems --check-vak --sjr-history --allow-latest --workers 4 --cache-dir .impeccable/quartiles/cache --report .impeccable/quartiles/doi-applied.json

Для проверки смены ISSN добавить --scopus-title-history /путь/ext_list_Aug_2026.xlsx. Используется конкретный официальный снимок: SHA-256 11e81f686401c89fbef28de31d1880388bb7122f35c89e09db6e1848237e9afb; несоответствующий файл отклоняется. Это список названий и их истории, не источник квартилей.

--all-systems проверяет также публикации с уже известным квартилем: добавляются остальные доступные системы и предметные категории. Без этого флага проверяются записи без квартиля, ранее найденные последние метрики и заполненные основания с изменившейся или неподтверждённой привязкой. Новые доказательства дополняют список, не удаляя прежние. Несколько категорий с одинаковым Q1 сохраняются отдельно; один квартиль не заменяет остальные.

Поле категорий ВАК

vak_categories имеет schema_version: 1, массив entries, status, scope: "supplied_category_snapshot", historical_category_confirmed: false и дату checked_on. NULL — проверка ещё не проводилась. В каждом совпадении сохраняются category (К1/К2), каноническое название журнала, название и SHA-256 источника, строка источника, дата категоризации, ISSN при наличии, match_method и identity_confirmed.

Проверяются k1_journals.json и k2_journals.json, извлечённые из предоставленного документа «ВАК категории.pdf». Для К1 доступна дополнительная идентификация по ISSN из k1_specialties.json; её источник, SHA-256 и дата списка сохраняются отдельно. В проверку передаются ISSN/eISSN исходного raw_data, данных ВШЭ и актуального ответа DOI; исходные идентификаторы используются и без сетевого поиска или DOI. Ответ, сохранённый для другого DOI, не считается актуальным источником идентификаторов. Поиск К2 в текущих данных выполняется по нормализованному названию и явным административным переименованиям. Транслитерация, похожесть названий и предположение о соответствии русской и переводной версии не используются.

  • match_method: "issn", identity_confirmed: true — журнал идентифицирован по ISSN.
  • match_method: "normalized_name", identity_confirmed: false — название совпало со списком; идентичность по ISSN не подтверждена. Это предварительное совпадение.
  • status: "found" — одно совпадение; ambiguous_journal или journal_identity_conflict — неоднозначность/конфликт, категория автоматически не выбирается.
  • not_found_in_k1_k2 — совпадений в этих двух снимках нет; это не доказывает отсутствие журнала в ВАК или наличие К3. unknown_journal — данных для идентификации журнала нет.

Дата категоризации в документе не объявляется датой всего снимка, а категория по имеющемуся списку не подтверждает её наличие в год публикации. Книги и главы не становятся журнальными статьями из-за совпадения названия. Проверка К1/К2 не является подтверждением зачёта конкретной работы по критериям совета.

--check-vak сохраняет этот результат в отдельном поле. Уже заполненные отличающиеся значения не перезаписываются; число таких случаев отражается в vak_existing_value_kept. Их нужно отдельно сравнить с актуальными источниками. Проверить результаты и причины неоднозначности до использования данных в заключении.

Запросы DOI кэшируются по нормализованному DOI и переиспользуются для соавторов. Ошибки сети допускают повторную попытку; для полного обновления ранее найденных ответов использовать новый каталог кэша. Исторические рейтинги запрашиваются только для ISSN найденных журналов; ошибки источника отражаются в ranking_failures отчёта. Существующие квартильные записи не перезаписываются; повторный перенос не дублирует доказательства только из-за новой даты проверки. Перед записью инструмент проверяет, что публикация не изменилась параллельно; при изменении пропускает её и фиксирует concurrent_change_skipped. Коммит выполняется по публикациям: прерванную обработку можно возобновить. Проверить итоговый отчёт и полноту переноса после завершения.

Парсер сохраняет quartiles, обновляя остальные исходные поля публикации. Страница совета читает отдельное поле с обратной совместимостью для исходных данных ВШЭ. В предварительные Q1/Q2 совета попадают Scopus/Web of Science с применимым годом; последние метрики другого года и SJR автоматически не превращаются в исторический зачёт или документальное подтверждение критериев.

Миграция и обогащение локальной БД не обновляют production. Порядок переноса только недостающих данных в production и сохранения его заполненных значений описан в README.