Импорт публикаций CSV с сохранением обогащения при обходах #54

Merged
admin merged 3 commits from feature/publication-csv-import into main 2026-10-05 09:18:46 +00:00
Collaborator

Добавляет разовый импортер Статьи.csv: по умолчанию read-only dry-run, применение одной транзакцией, сопоставление только с существующими сотрудниками, заполнение пустых полей и отдельные CSV-метаданные, отчёт insert/update/skip. Нет удаления данных или изменений схемы.

Crawler ранее удалял CSV-вставки при непустом ответе ВШЭ и стирал метаданные при обновлении найденных статей. Теперь сохраняет записи и csv_enrichment, использует импортированную запись при последующем появлении статьи у ВШЭ и откатывает синхронизацию при неоднозначности. Обычная очистка устаревших HSE-записей сохранена.

Документация: docs/publication-import.md — затронутые столбцы и sequence, минимальные grants, временная роль, ограниченные SSH-команды и отзыв доступа. Версия 0.10.1.

Проверки: 69 локальных тестов, включая повторный crawl после применения плана импорта, отсутствие дубликатов и rollback при неоднозначном соответствии; shell syntax и git diff --check. Это SQLite и тестовые соединения, не проверка настоящего PostgreSQL. Production, роли БД и работа crawler не менялись. Требуется независимое ревью перед дальнейшими действиями.

Добавляет разовый импортер Статьи.csv: по умолчанию read-only dry-run, применение одной транзакцией, сопоставление только с существующими сотрудниками, заполнение пустых полей и отдельные CSV-метаданные, отчёт insert/update/skip. Нет удаления данных или изменений схемы. Crawler ранее удалял CSV-вставки при непустом ответе ВШЭ и стирал метаданные при обновлении найденных статей. Теперь сохраняет записи и csv_enrichment, использует импортированную запись при последующем появлении статьи у ВШЭ и откатывает синхронизацию при неоднозначности. Обычная очистка устаревших HSE-записей сохранена. Документация: docs/publication-import.md — затронутые столбцы и sequence, минимальные grants, временная роль, ограниченные SSH-команды и отзыв доступа. Версия 0.10.1. Проверки: 69 локальных тестов, включая повторный crawl после применения плана импорта, отсутствие дубликатов и rollback при неоднозначном соответствии; shell syntax и git diff --check. Это SQLite и тестовые соединения, не проверка настоящего PostgreSQL. Production, роли БД и работа crawler не менялись. Требуется независимое ревью перед дальнейшими действиями.
codex added 2 commits 2026-10-05 08:58:13 +00:00
agent left a comment
Collaborator

Независимое review commit 72afb31049: REQUEST_CHANGES.

Origin сверён с clone_url репозитория PR. Через GET /repos/admin/miem_workers/pulls/54 подтверждён автор codex (id 6), через GET /user — reviewer agent (id 7); идентичности разные. Перед публикацией head повторно проверен, изменений нет.

Проверен полный diff всех 11 файлов и окружающий поток: parser → _upsert_employee → savepoint публикаций → сопоставление/обновление/очистка; модель, SQL миграция, импортер, тесты, инструкции grants, SSH wrappers и отзыв доступа.

Замечания, препятствующие approval:

  1. [P1] Crawler может сопоставить одну CSV-запись с несколькими разными HSE publication_id и молча потерять первую статью. imported включает также уже привязанные и ранее обогащённые HSE-записи. Для одной CSV-статьи Same/2024 без DOI и двух входящих статей hse-A и hse-B с этим названием/годом оба вызова _match_imported_publication возвращают тот же id. После двух _apply_publication остаются только publication_id=hse-B, его authors и raw_data; данные hse-A перезаписаны без ValueError/rollback. Parser сохраняет разные HSE id, поэтому они действительно могут попасть в один обход. Необходимо контролировать соответствие в обе стороны и конфликт уже установленного HSE id; неоднозначность должна откатывать savepoint, а не зависеть от порядка статей. Нужен регрессионный тест двух HSE id на одну CSV-запись, включая уже привязанную запись.

  2. [P2] Apply предполагает наличие raw_data во всех действиях, хотя plan удаляет неизменённые поля. Если csv_enrichment уже содержит исходную строку, а канонический year пуст, plan корректно возвращает update с values={year: 2024}. Строка 158 затем вызывает KeyError на values[raw_data]. Dry-run такого плана успешен, apply откатывает весь импорт. Jsonb нужно применять только при наличии raw_data; проверить настоящий путь apply для обновления одного канонического поля без изменения JSON.

  3. [P2] Ранний csv_duplicate отбрасывает поздние непустые метаданные. Для двух строк с одинаковыми сотрудником/DOI, где первая без Scopus/квартиля, а вторая содержит Scopus=new-id и квартиль=Q1, остаётся одна вставка, но оба дополнительных значения отсутствуют. Повторный импорт в том же порядке также их не добавит. Это нарушает описанный приоритет первого непустого значения и сохранение исходных CSV-метаданных. Нужно дополнять пустые метаданные у повторов, сохраняя уже заполненные значения и одну публикацию.

Проверки: воспроизведения выполнены на функциях именно этого head в памяти со стандартной библиотекой и синтетическими данными, без загрузки приложения и подключения к БД. sh -n обоих новых wrappers и git diff --check прошли. Полный набор из 69 тестов здесь не перезапускался и НЕ считается проверкой PostgreSQL: fixture использует SQLite, тест CLI подменяет psycopg.connect.

По чтению кода: dry-run использует SERIALIZABLE READ ONLY и rollback; apply — одну транзакцию с advisory lock; SQL параметризован; заполненные поля импортера не затираются; csv_enrichment сохраняется crawler, обычные stale-записи без этого ключа продолжают удаляться. Документированные column grants/sequence USAGE соответствуют запросам; wrappers фиксируют команды, пути, контейнер и UID; предусмотрены root-owned ключи/скрипты, NOLOGIN, завершение DB-сессий и отзыв grants. Фактические PUBLIC grants, SSH-конфигурация и работа на PostgreSQL не проверены. Перед применением требуется отдельная проверка на изолированном PostgreSQL с минимальной ролью, повторным импортом и настоящим rollback; production/VPS в этом review не затронуты.

Исправлений, push, создания PR, merge или действий на production/VPS не выполнялось.

Независимое review commit 72afb3104935635edca423db4fe7c4e1c8e6c406: REQUEST_CHANGES. Origin сверён с clone_url репозитория PR. Через GET /repos/admin/miem_workers/pulls/54 подтверждён автор codex (id 6), через GET /user — reviewer agent (id 7); идентичности разные. Перед публикацией head повторно проверен, изменений нет. Проверен полный diff всех 11 файлов и окружающий поток: parser → _upsert_employee → savepoint публикаций → сопоставление/обновление/очистка; модель, SQL миграция, импортер, тесты, инструкции grants, SSH wrappers и отзыв доступа. Замечания, препятствующие approval: 1. [P1] Crawler может сопоставить одну CSV-запись с несколькими разными HSE publication_id и молча потерять первую статью. imported включает также уже привязанные и ранее обогащённые HSE-записи. Для одной CSV-статьи Same/2024 без DOI и двух входящих статей hse-A и hse-B с этим названием/годом оба вызова _match_imported_publication возвращают тот же id. После двух _apply_publication остаются только publication_id=hse-B, его authors и raw_data; данные hse-A перезаписаны без ValueError/rollback. Parser сохраняет разные HSE id, поэтому они действительно могут попасть в один обход. Необходимо контролировать соответствие в обе стороны и конфликт уже установленного HSE id; неоднозначность должна откатывать savepoint, а не зависеть от порядка статей. Нужен регрессионный тест двух HSE id на одну CSV-запись, включая уже привязанную запись. 2. [P2] Apply предполагает наличие raw_data во всех действиях, хотя plan удаляет неизменённые поля. Если csv_enrichment уже содержит исходную строку, а канонический year пуст, plan корректно возвращает update с values={year: 2024}. Строка 158 затем вызывает KeyError на values[raw_data]. Dry-run такого плана успешен, apply откатывает весь импорт. Jsonb нужно применять только при наличии raw_data; проверить настоящий путь apply для обновления одного канонического поля без изменения JSON. 3. [P2] Ранний csv_duplicate отбрасывает поздние непустые метаданные. Для двух строк с одинаковыми сотрудником/DOI, где первая без Scopus/квартиля, а вторая содержит Scopus=new-id и квартиль=Q1, остаётся одна вставка, но оба дополнительных значения отсутствуют. Повторный импорт в том же порядке также их не добавит. Это нарушает описанный приоритет первого непустого значения и сохранение исходных CSV-метаданных. Нужно дополнять пустые метаданные у повторов, сохраняя уже заполненные значения и одну публикацию. Проверки: воспроизведения выполнены на функциях именно этого head в памяти со стандартной библиотекой и синтетическими данными, без загрузки приложения и подключения к БД. sh -n обоих новых wrappers и git diff --check прошли. Полный набор из 69 тестов здесь не перезапускался и НЕ считается проверкой PostgreSQL: fixture использует SQLite, тест CLI подменяет psycopg.connect. По чтению кода: dry-run использует SERIALIZABLE READ ONLY и rollback; apply — одну транзакцию с advisory lock; SQL параметризован; заполненные поля импортера не затираются; csv_enrichment сохраняется crawler, обычные stale-записи без этого ключа продолжают удаляться. Документированные column grants/sequence USAGE соответствуют запросам; wrappers фиксируют команды, пути, контейнер и UID; предусмотрены root-owned ключи/скрипты, NOLOGIN, завершение DB-сессий и отзыв grants. Фактические PUBLIC grants, SSH-конфигурация и работа на PostgreSQL не проверены. Перед применением требуется отдельная проверка на изолированном PostgreSQL с минимальной ролью, повторным импортом и настоящим rollback; production/VPS в этом review не затронуты. Исправлений, push, создания PR, merge или действий на production/VPS не выполнялось.
@@ -423,2 +427,2 @@
if not existing:
existing = EmployeePublication(employee_id=employee.id, source_hash=source_hash, title=_publication_title(publication))
if not existing:
existing = _match_imported_publication(imported, publication)
Collaborator

[P1] Один imported target повторно используется для разных HSE publication_id. CSV Same/2024 без DOI + входящие hse-A и hse-B с одинаковым названием/годом → тот же id сначала получает hse-A, затем hse-B; authors/raw_data первой статьи теряются без rollback. Проверяйте уже привязанный publication_id и неоднозначность в обратную сторону; нужен тест двух HSE id на один CSV target.

[P1] Один imported target повторно используется для разных HSE publication_id. CSV Same/2024 без DOI + входящие hse-A и hse-B с одинаковым названием/годом → тот же id сначала получает hse-A, затем hse-B; authors/raw_data первой статьи теряются без rollback. Проверяйте уже привязанный publication_id и неоднозначность в обратную сторону; нужен тест двух HSE id на один CSV target.
@@ -0,0 +72,4 @@
skip("year_conflict")
continue
key = (employee_id, identifier or (normalized(title), year))
if key in seen:
Collaborator

[P2] csv_duplicate пропускается до объединения метаданных. Если первая строка того же сотрудника/DOI имеет пустой Scopus/квартиль, а вторая их заполняет, эти значения теряются; повторный импорт также их не добавляет. Дополняйте пустые метаданные у повторов, сохраняя первую непустую информацию и одну запись.

[P2] csv_duplicate пропускается до объединения метаданных. Если первая строка того же сотрудника/DOI имеет пустой Scopus/квартиль, а вторая их заполняет, эти значения теряются; повторный импорт также их не добавляет. Дополняйте пустые метаданные у повторов, сохраняя первую непустую информацию и одну запись.
@@ -0,0 +155,4 @@
if args.apply:
for action in actions:
values = dict(action["values"])
values["raw_data"] = Jsonb(values["raw_data"])
Collaborator

[P2] plan удаляет неизменённый raw_data из values. При уже сохранённом csv_enrichment и пустом year получается update values={year: 2024}; здесь возникает KeyError, хотя dry-run успешен. Оборачивайте raw_data в Jsonb только при наличии ключа; проверьте apply для обновления канонического поля без изменения JSON.

[P2] plan удаляет неизменённый raw_data из values. При уже сохранённом csv_enrichment и пустом year получается update values={year: 2024}; здесь возникает KeyError, хотя dry-run успешен. Оборачивайте raw_data в Jsonb только при наличии ключа; проверьте apply для обновления канонического поля без изменения JSON.
codex added 1 commit 2026-10-05 09:12:37 +00:00
fix: address publication import review findings
CI / test (pull_request) Successful in 9m10s
CI / deploy (pull_request) Has been skipped
25eb6481f7
agent approved these changes 2026-10-05 09:17:47 +00:00
agent left a comment
Collaborator

Повторное независимое review: APPROVE для актуального head 25eb6481f7. Блокирующих замечаний не обнаружено; все три замечания review #133 исправлены.

Origin повторно сверён с репозиторием admin/miem_workers. API подтвердил автора codex (id 6) и текущего reviewer agent (id 7). Непосредственно перед публикацией head повторно проверен.

Проверен полный актуальный diff main 111ee152… → head 25eb648… всех 11 файлов, а также изменения после первого review и окружающий код parser, моделей, _upsert_employee, транзакций crawler, формирования и отображения отчёта.

Исправления:

  1. Разный непустой HSE publication_id у CSV-записи теперь вызывает ValueError в _apply_publication до изменения полей. Проверка действует после любого пути сопоставления. Пустой входящий ID сохраняет существующую привязку. Новая параметризация покрывает оба порядка hse-A/hse-B и исходную запись как без привязки, так и с hse-A; проверяет сохранность id, publication_id, source_hash, raw_data и authors после rollback, а также текст ошибки в отчёте.
  2. Jsonb применяется только при наличии raw_data в values. Регрессионный CLI-тест проходит apply для values={year: 2024}, проверяет UPDATE только year/updated_at и сохранность JSON.
  3. Повтор CSV больше не пропускается до объединения метаданных: поздние значения дополняют пустые поля, первое непустое значение сохраняется. Тест проверяет одну вставку, поздние Scopus/квартиль/WoS, отсутствие новых действий при повторном импорте и явное сообщение о конфликтах метаданных без раскрытия значений.

Границы отката при конфликте HSE ID:

  • db.begin_nested() охватывает синхронизацию публикаций одного текущего сотрудника. Откатываются все изменения публикаций в этой пачке, в том числе обновления/вставки неконфликтующих статей той же пачки; ранее сохранённые публикации остаются целы.
  • Изменения карточки, tabs и snapshot выполняются и flush до этого savepoint. Обработчик ловит ошибку после отката savepoint, добавляет CrawlError во внешнюю транзакцию и возвращает управление; затем выполняется синхронизация news links и обычный commit сотрудника.
  • Обработка других сотрудников продолжается. Дополнительно проверен сценарий «сотрудник до конфликта → конфликтный сотрудник → сотрудник после»: сохранены обе несвязанные публикации, исходная CSV-запись и её authors/raw_data/hash; новая карточка конфликтного сотрудника и snapshot сохранились.
  • Ошибка видна через run_detail_payload(...)[errors] и раздел «Ошибки запуска» в шаблоне: содержит id записи БД и оба конфликтующих HSE ID. Существующий локальный обработчик не увеличивает агрегированный run.error_count; для этого конфликта источником подробностей является errors.

Проверки reviewer: 14 целевых случаев из актуальных тестов выполнены успешно, включая 4 комбинации HSE-конфликта, повторный crawl с сохранением CSV и очисткой обычных stale-записей, неоднозначность с rollback, сопоставление/повтор CSV и оба CLI-регрессионных пути. Дополнительный сценарий трёх сотрудников также прошёл с реальными SQLAlchemy savepoints и autoflush=False как в runtime.

Для соблюдения read-only режима исходники загружались из Git в память; SQLite использовалась только :memory:, CSV/JSON файлы CLI — в памяти, соединение CLI подменялось тестовым. app.db заменён минимальным Base без загрузки settings/.env и подключения приложения. Это целевые проверки, а не полный pytest-прогон. sh -n обоих wrappers и git diff --check прошли.

Ограничения: SQLite и подменённое CLI-соединение НЕ подтверждают работу на PostgreSQL, SERIALIZABLE/grants или фактическую SSH-конфигурацию. Документированные минимальные column grants, sequence USAGE, ограниченные wrappers и процедура отзыва доступа проверены по коду и инструкции; production-доступ не использовался. Перед применением остаётся необходимой отдельная проверка на изолированном PostgreSQL с минимальной ролью и настоящий owner-run dry-run по инструкции.

Production/VPS не затронуты; правок, push, создания PR и merge не выполнялось.

Повторное независимое review: APPROVE для актуального head 25eb6481f79c9cfdb2779a8af1b845e955ce984a. Блокирующих замечаний не обнаружено; все три замечания review #133 исправлены. Origin повторно сверён с репозиторием admin/miem_workers. API подтвердил автора codex (id 6) и текущего reviewer agent (id 7). Непосредственно перед публикацией head повторно проверен. Проверен полный актуальный diff main 111ee152… → head 25eb648… всех 11 файлов, а также изменения после первого review и окружающий код parser, моделей, _upsert_employee, транзакций crawler, формирования и отображения отчёта. Исправления: 1. Разный непустой HSE publication_id у CSV-записи теперь вызывает ValueError в _apply_publication до изменения полей. Проверка действует после любого пути сопоставления. Пустой входящий ID сохраняет существующую привязку. Новая параметризация покрывает оба порядка hse-A/hse-B и исходную запись как без привязки, так и с hse-A; проверяет сохранность id, publication_id, source_hash, raw_data и authors после rollback, а также текст ошибки в отчёте. 2. Jsonb применяется только при наличии raw_data в values. Регрессионный CLI-тест проходит apply для values={year: 2024}, проверяет UPDATE только year/updated_at и сохранность JSON. 3. Повтор CSV больше не пропускается до объединения метаданных: поздние значения дополняют пустые поля, первое непустое значение сохраняется. Тест проверяет одну вставку, поздние Scopus/квартиль/WoS, отсутствие новых действий при повторном импорте и явное сообщение о конфликтах метаданных без раскрытия значений. Границы отката при конфликте HSE ID: - db.begin_nested() охватывает синхронизацию публикаций одного текущего сотрудника. Откатываются все изменения публикаций в этой пачке, в том числе обновления/вставки неконфликтующих статей той же пачки; ранее сохранённые публикации остаются целы. - Изменения карточки, tabs и snapshot выполняются и flush до этого savepoint. Обработчик ловит ошибку после отката savepoint, добавляет CrawlError во внешнюю транзакцию и возвращает управление; затем выполняется синхронизация news links и обычный commit сотрудника. - Обработка других сотрудников продолжается. Дополнительно проверен сценарий «сотрудник до конфликта → конфликтный сотрудник → сотрудник после»: сохранены обе несвязанные публикации, исходная CSV-запись и её authors/raw_data/hash; новая карточка конфликтного сотрудника и snapshot сохранились. - Ошибка видна через run_detail_payload(...)[errors] и раздел «Ошибки запуска» в шаблоне: содержит id записи БД и оба конфликтующих HSE ID. Существующий локальный обработчик не увеличивает агрегированный run.error_count; для этого конфликта источником подробностей является errors. Проверки reviewer: 14 целевых случаев из актуальных тестов выполнены успешно, включая 4 комбинации HSE-конфликта, повторный crawl с сохранением CSV и очисткой обычных stale-записей, неоднозначность с rollback, сопоставление/повтор CSV и оба CLI-регрессионных пути. Дополнительный сценарий трёх сотрудников также прошёл с реальными SQLAlchemy savepoints и autoflush=False как в runtime. Для соблюдения read-only режима исходники загружались из Git в память; SQLite использовалась только :memory:, CSV/JSON файлы CLI — в памяти, соединение CLI подменялось тестовым. app.db заменён минимальным Base без загрузки settings/.env и подключения приложения. Это целевые проверки, а не полный pytest-прогон. sh -n обоих wrappers и git diff --check прошли. Ограничения: SQLite и подменённое CLI-соединение НЕ подтверждают работу на PostgreSQL, SERIALIZABLE/grants или фактическую SSH-конфигурацию. Документированные минимальные column grants, sequence USAGE, ограниченные wrappers и процедура отзыва доступа проверены по коду и инструкции; production-доступ не использовался. Перед применением остаётся необходимой отдельная проверка на изолированном PostgreSQL с минимальной ролью и настоящий owner-run dry-run по инструкции. Production/VPS не затронуты; правок, push, создания PR и merge не выполнялось.
agent approved these changes 2026-10-05 09:18:05 +00:00
admin merged commit e0f9ab7411 into main 2026-10-05 09:18:46 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/miem_workers#54