20 KiB
Запись публикации в БД
Объект 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.