Перейти к содержимому

Снимки проекта для цитируемых наборов данных и журналов аудита

Зафиксируйте конфигурацию, данные и разметку проекта Potato под id, который можно процитировать в статье, сравните два снимка и дайте читателям хеш для проверки.

potato snapshot фиксирует конфигурацию, файлы данных и разметку проекта под id, который можно процитировать в статье или в карточке набора данных. Каждый снимок записывает SHA-256 хеш содержимого по этим файлам и ответам, и любой, у кого есть файлы снимка, может пересчитать хеш и убедиться, что его копия совпадает с процитированной. Снимок также сохраняет альфу Криппендорфа для каждой схемы разметки, то есть согласованность на день создания снимка. Снимки появились в Potato 2.12.0. Каждая команда читает проект с диска, независимо от того, запущен ли сервер.

Исследование продолжает меняться после того, как статья написана. Приходят новые разметчики, а коды исправляются после арбитража. Журнал аудита (audit trail) в качественных исследованиях — это запись данных, решений о кодировании и шагов анализа, по которой читатель может судить, заслуживает ли анализ доверия (Nowell et al., 2017). Серия снимков даёт проекту кодирования датированную запись с хешем для каждого раунда вместе с согласованностью между кодировщиками в этом раунде. Команда машинного обучения может теми же командами версионировать размеченный набор данных. Карточка модели затем может указать id снимка с метками, на которых обучалась модель.

  • Команды. Подкоманды create, list, show, diff, tag, verify и deposit вызываются через potato snapshot.
  • Хранение. Каждый снимок — это одна папка внутри snapshots/ в каталоге задачи; теги хранятся в snapshots/tags.json, а депозиты в Zenodo — в snapshots/deposits.json.
  • Целостность. Potato записывает файлы только для чтения и переименовывает папку в итоговое место лишь после того, как записан каждый файл.
  • Публикация. На странице Publish Dataset админ-панели есть карточка Snapshots, два маршрута API администратора выводят список снимков и создают снимки, а по желанию снимок можно депонировать в Zenodo с DOI.

Команды

Каждая команда принимает файл конфигурации проекта:

bash
potato snapshot create config.yaml --note "pilot round finished" --tag pilot
potato snapshot list config.yaml
potato snapshot show config.yaml pilot
potato snapshot diff config.yaml pilot latest
potato snapshot tag config.yaml 20261102-175533 paper-v1
potato snapshot verify config.yaml paper-v1
potato snapshot verify --dir ./downloaded-snapshot
potato snapshot deposit config.yaml paper-v1 --check

В клоне исходного кода Potato те же аргументы принимает python potato/flask_server.py snapshot. В таблице перечислены аргументы и флаги каждой команды:

КомандаАргументы и флагиЧто делает команда
createconfig, --note TEXT (по умолчанию пусто), --tag NAME, --forceФиксирует проект. Если хеш содержимого совпадает с хешем последнего снимка, create ничего не записывает и сообщает об этом. --force всё равно записывает новый снимок. --tag, заданный при неизменном содержимом, указывает на существующий снимок
listconfig, --jsonВыводит снимки от новых к старым с тегами, числом объектов, разметчиков и ответов, а также с примечанием
showconfig, ref (по умолчанию latest), --jsonПечатает хеши одного снимка, версию Potato, предыдущий снимок, депозиты и альфу по каждой схеме
diffconfig, old, new (по умолчанию latest), --jsonСообщает, что изменилось между двумя снимками
tagconfig, ref, nameНаправляет имя на снимок. Имя, которое уже указывало на другой снимок, переносится
verifyconfig и ref (по умолчанию latest) или --dir FOLDERЗаново хеширует снимок и сверяет с манифестом. С --dir проверяет папку с файлами снимка и не требует конфигурации
depositconfig, ref (по умолчанию latest), --token, --production, --publish, --annotator-ids-public, --checkЗагружает файлы снимка в Zenodo

ref — это latest, полный id снимка, тег или начало id либо хеша содержимого. Префикс должен соответствовать ровно одному снимку, а неоднозначный префикс останавливает команду и выводит список совпадений. Тег состоит из 1–64 букв, цифр, ., _ или -, начинается с буквы или цифры и не может быть latest. Теги работают как теги git. После create Potato меняет в хранилище только два файла: tags.json с тегами и deposits.json с депозитами в Zenodo. Папка снимка никогда не меняется после того, как create её записала.

show печатает итоговую строку снимка, хеши, версию Potato и альфу по каждой схеме:

text
20261102-175504-45d41bfa [pilot]  6 items, 2 annotators, 20 answers  pilot round finished
  created       2026-11-02T17:55:04+00:00
  content hash  45d41bfad181190c161eff442c2bd3e65bd0546f3a55c197f356db4447e72c93
  bundle sha256 addd652f15ee682301c6cab6968d8aa2f0b6d05e1e5fc6748d242d07f9f07af4
  potato        2.13.1
  alpha sentiment: 0.511
  alpha aspects: 0.718

Ответ в этих подсчётах — это метки одного разметчика для одного объекта по одной схеме. create печатает новый id и хеш содержимого, число файлов исследования помимо конфигурации, каждый файл, исключённый как учётные данные, каждую папку, которую называет конфигурация, но снимок не копирует, и предупреждение о любом файле за пределами каталога задачи.

Файлы снимка

Каждый снимок — это папка snapshots/<id>/ в каталоге задачи. Id состоит из времени создания в UTC и первых восьми символов хеша содержимого, например 20261102-175533-68e05868. В папке четыре файла:

ФайлСодержимое
bundle.tar.gzКонфигурация, файлы исследования, каталог с результатами разметки и базы данных SQLite проекта, всё внутри project/. Базы данных — это project.sqlite (заметки, кодбук, кейсы, сеансы набора текста и процесс проверки), datasets.sqlite и engine.sqlite (движок разметки)
annotations.jsonlОдна строка на каждое сочетание разметчика, объекта и схемы, с каждой меткой и каждым спаном, которые сохранил разметчик, в фиксированном порядке сортировки. diff читает annotations.jsonl
items.jsonId объектов из файлов данных в порядке следования в файлах
manifest.jsonId, время создания, примечание, название задачи, версия Potato, хеши, счётчики, сводка согласованности, файлы исследования, исключённые файлы, id предыдущего снимка и порядковый номер

Каждая строка annotations.jsonl хранит ответы-метки в "labels", а ответы-спаны в "spans" в виде троек [label, start, end]. Строка с ответом-меткой выглядит так:

json
{"annotator": "bob", "item": "r2", "schema": "sentiment", "value": {"labels": [["neutral", "true"]]}}

Файлы исследования — это файлы данных и каждый файл, на который указывает какая-либо строка в конфигурации. Сюда входят файлы фаз и опросов, файлы проверок внимания и эталонных объектов, страницы согласия и инструкций, а также пользовательские макеты. Potato также следует по ссылкам внутри YAML-файлов, на которые ссылается конфигурация, до трёх уровней вложенности. manifest.json перечисляет файлы исследования в project_files. Каждую папку, которую называет конфигурация, Potato записывает в referenced_directories и не копирует, потому что папка с аудио или изображениями может быть любого размера. Файлы в такой папке не входят в хеш содержимого. Архивируйте медиафайлы отдельно и цитируйте медиаархив рядом со снимком.

Файл исследования за пределами каталога задачи попадает в архив как external/<name>, и create печатает предупреждение. Potato отказывается запускать конфигурацию, которая указывает за пределы своего проекта, поэтому исследование, зафиксированное в таком виде, нельзя запустить как есть. Перенесите файл в каталог задачи и создайте новый снимок.

Исключённые учётные данные

Поскольку снимок создаётся для цитирования и передачи другим, архив не включает учётные данные трёх видов:

  • Список учётных записей и хеши их паролей не включаются. Список — это authentication.user_config_path, если этот ключ задан, а иначе user_config.json рядом с каталогом результатов или внутри каталога результатов, если output_annotation_dir заканчивается на /.
  • Не включается файл, названный в ключе конфигурации, имя которого содержит password, secret, token, credential, API key или private key. Правило охватывает ssl_key и каждый ключ, оканчивающийся на key_file, например sp_key_file.
  • admin_api_key.txt не включается.

manifest.json перечисляет каждый исключённый файл с причиной в omitted, и create печатает этот список. Ни один исключённый файл не входит в хеш содержимого. У проекта, распакованного из снимка, нет учётных записей, и разметчикам придётся зарегистрироваться заново, если вы не восстановите список из своей копии.

Хеш содержимого

content_hash — это дайджест SHA-256 по файлу конфигурации, каждому файлу исследования под его именем в архиве, списку id объектов и строкам разметки, именно в таком порядке. Хеш меняется, когда меняется конфигурация, файл исследования или любой ответ. Базы данных SQLite входят в архив, но не входят в хеш.

Базы данных не входят в хеш, потому что сервер перезаписывает базы данных во время работы. Хеш, который менялся бы при каждой перезаписи, не позволил бы читателю понять, изменилась ли разметка. Заметки, кодбук и кейсы QDA Mode хранятся в project.sqlite. Правка одной лишь заметки или определения кода оставляет хеш содержимого прежним, и create сообщает, что ничего не изменилось. Чтобы зафиксировать редакцию кодбука, которая не изменила ни одного ответа, передайте --force.

verify выполняет три проверки:

  1. bundle.tar.gz должен совпадать с bundle_sha256 в манифесте.
  2. annotations.jsonl должен совпадать с annotations_sha256.
  3. Конфигурация и файлы исследования, распакованные из архива и захешированные вместе с id объектов и строками разметки, должны воспроизвести content_hash.

Читатель, у которого есть только четыре файла, может выполнить те же проверки с --dir:

text
$ potato snapshot verify --dir ./downloaded-snapshot
20261102-175533-68e05868: OK
  content hash 68e058683e7088e3a344d56d7e39172b3d7d3b9b469ba9e1a4a1876b4296b4d2
  Compare it with the hash the paper or dataset card cites.

Успешная проверка показывает лишь то, что файлы согласуются с манифестом в той же папке. Любой другой снимок тоже проходит собственную проверку. Сравните напечатанный хеш с хешем, который приводит статья. Для изменённого или повреждённого файла verify печатает по одной строке на каждую проблему, например annotations.jsonl does not match annotations_sha256, и завершается с кодом 1.

Сравнение двух снимков

diff сравнивает два снимка ответ за ответом и подсчитывает добавленные, удалённые и изменённые ответы. Ответы сопоставляются по разметчику, объекту и схеме. Отчёт также перечисляет добавленные или удалённые объекты и разметчиков, сообщает, изменилась ли конфигурация, и приводит альфу по каждой схеме до и после:

text
$ potato snapshot diff config.yaml pilot paper-v1
20261102-175504-45d41bfa -> 20261102-175533-68e05868
Items: +0 -0
Annotations: +0 -0 changed 1
  changed  bob  r2  sentiment
Alpha aspects: 0.718 -> 0.718 (+0.000)
Alpha sentiment: 0.511 -> 0.298 (-0.213)

Между двумя снимками из примера один разметчик изменил один ответ, и альфа по схеме sentiment из шести объектов упала с 0.511 до 0.298. Текстовый отчёт называет до 20 строк каждого вида. --json выдаёт полные счётчики с теми же строками в машиночитаемом виде. Для двух снимков с одинаковыми хешами содержимого diff печатает Same content.

Альфа в снимке — это альфа Криппендорфа (номинальная), вычисленная так же, как в Paper Mode. Схема множественного выбора оценивается по каждой паре объекта и метки. Каждая схема также получает одну из интерпретаций Криппендорфа: «acceptable agreement» от 0.800, «tentative agreement» от 0.667 или «low agreement» ниже 0.667. Схема, для которой альфу вычислить нельзя, показывает n/a.

Восстановление снимка

Команды восстановления в Potato нет. bundle.tar.gz снимка распаковывается в папку project/ с конфигурацией, файлами исследования, результатами разметки и базами данных. Potato запускается на распакованной конфигурации так же, как любой другой проект:

bash
mkdir restored
tar -xzf snapshots/20261102-175533-68e05868/bundle.tar.gz -C restored
potato start restored/project/config.yaml -p 8000

У восстановленного проекта нет учётных записей и нет скопированных медиапапок. Прежде чем разметчики вернутся к работе, скопируйте папки, перечисленные в referenced_directories, из своего архива.

Снимки в админ-панели

На странице Publish Dataset админ-панели есть карточка Snapshots. Карточка выводит список снимков проекта с числом объектов, разметчиков и ответов и создаёт снимок по примечанию и необязательному тегу. Два маршрута администратора делают то же самое по HTTP:

МаршрутКто может вызвать маршрутРезультат
GET /admin/api/snapshotsключ API администратора или сеанс, которому разрешено открывать админ-панель{"snapshots": [...]}, от новых к старым, каждый с манифестом, тегами и депозитами
POST /admin/api/snapshotsключ API администратора или сеанс с правом export_data201 с новым манифестом. 200 с "unchanged": true и существующим снимком, если ничего не изменилось. 400 для недопустимого тега, при этом ничего не записывается. 409, пока выполняется другое создание того же снимка

Тело POST принимает note, tag и force, все необязательные:

bash
curl -H "X-API-Key: $KEY" http://localhost:8000/admin/api/snapshots
curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
     -d '{"note": "second coding round", "tag": "round-2"}' \
     http://localhost:8000/admin/api/snapshots

Перед каждым выпуском со страницы Publish Dataset Potato фиксирует снимок или, если ничего не изменилось, использует последний снимок. Выпуск в локальный архив, в Hugging Face Hub или в Zenodo получает снимок тем же способом. Сгенерированная карточка набора данных называет снимок в разделе о цитировании:

This release is Potato snapshot 20261102-175533-68e05868 (content hash 68e058683e70…). Anyone holding the project folder can check that their copy is this one with potato snapshot verify config.yaml 20261102-175533-68e05868.

Если фиксация не удалась, выпуск всё равно продолжается, и Potato показывает сбой как предупреждение. python -m potato.publish не фиксирует снимок, и карточка набора данных, которую пишет эта команда, не называет никакого снимка. Перед выпуском из командной строки запустите potato snapshot create и процитируйте id сами.

Депонирование снимка в Zenodo

potato snapshot deposit загружает четыре файла снимка и короткий README в новую запись Zenodo. Читатель может скачать файлы и запустить potato snapshot verify --dir на скачанной папке. Название, авторы, лицензия, ключевые слова и описание записи берутся из блока dataset_metadata конфигурации. Potato читает этот блок из конфигурации в том виде, в каком она находится на момент депонирования. Версией записи служит id снимка, а описание дополняется id снимка, счётчиками и хешем содержимого. Добавьте в конфигурацию блок dataset_metadata:

yaml
dataset_metadata:
  pretty_name: Clinic Access Interviews (coded excerpts)
  description: Interview excerpts coded for barriers to clinic access by two coders.
  license: cc-by-4.0
  keywords: [qualitative-coding, health-services]
  authors:
    - name: A. Researcher
      affiliation: University of Example
      orcid: 0000-0002-1825-0097

Если не указать иное, депозиты попадают в песочницу Zenodo как неопубликованные черновики:

bash
export ZENODO_TOKEN=...
potato snapshot deposit config.yaml paper-v1 --check
potato snapshot deposit config.yaml paper-v1 --annotator-ids-public
potato snapshot deposit config.yaml paper-v1 --annotator-ids-public --production --publish

Первая команда перечисляет файлы, выполняет описанные ниже проверки и ничего не отправляет. Вторая создаёт черновик в песочнице. Третья публикует запись на zenodo.org и присваивает DOI.

ФлагПо умолчаниюДействие
--tokenZENODO_TOKENТокен доступа Zenodo
--productionвыключен (песочница)Депонировать на zenodo.org, а не на sandbox.zenodo.org
--publishвыключен (черновик)Опубликовать запись и присвоить DOI. Черновик держит зарезервированный DOI до публикации
--annotator-ids-publicвыключенПодтвердить, что id разметчиков можно опубликовать в том виде, в каком они хранятся
--checkвыключенПеречислить файлы и выполнить проверки, ничего не отправляя

Файлы уходят ровно в том виде, в каком были зафиксированы, потому что проверка хеша зависит от байтов. Перед отправкой Potato выполняет две проверки, и никакой флаг не отменяет проверку учётных данных:

  • Учётные данные. Potato разбирает конфигурацию и все остальные файлы YAML, JSON, .env или TOML в архиве, кроме файлов данных. Депонирование останавливается, если в одном из этих файлов есть ключ вроде api_key, token, password или client_secret либо ключ, оканчивающийся на _api_key, _secret, _token или _password, со значением-литералом длиной 12 символов или больше. Значение, похожее по форме на ключ известного провайдера, останавливает депонирование в любом файле, включая HTML или JavaScript пользовательского макета. Закрытый ключ PEM в любом файле, файл конфигурации, который не удаётся разобрать, и файл конфигурации больше 8 МБ тоже останавливают депонирование. Замените каждое такое значение на ${ENV_VAR} или уберите файл из проекта, создайте новый снимок и депонируйте новый снимок.
  • Id разметчиков. Potato подсчитывает id разметчиков в ответах и в каждом сохранённом состоянии в архиве, называет id, похожие на адреса электронной почты, и останавливает депонирование, если вы не передали --annotator-ids-public. Разметчик с сохранённым состоянием и без ответов тоже учитывается, потому что сохранённое состояние содержит согласие и ответы на опросы.

Депонированный снимок сохраняет id разметчиков в том виде, в каком они хранятся, а python -m potato.publish по умолчанию заменяет id разметчиков на A1–An. Если id должны остаться закрытыми, используйте инструмент публикации. Каждый депозит записывается в snapshots/deposits.json, и show печатает каждый депозит с его URL в Zenodo и DOI. Как только у снимка появляется опубликованный DOI на zenodo.org, карточка набора данных любого выпуска, зафиксированного как этот снимок, ссылается на DOI.

Удаление снимка

Файлы снимка доступны только для чтения. Сделайте файлы доступными для записи, удалите папку и удалите из snapshots/tags.json все теги, которые указывали на снимок:

bash
chmod -R u+w snapshots/20261102-175504-45d41bfa
rm -rf snapshots/20261102-175504-45d41bfa

Прерванный на полпути create оставляет папку <id>.partial, которую не читает ни одна команда. Такую папку можно удалить тем же способом. Сообщение Another snapshot create is writing … right now означает, что два создания с одинаковым содержимым начались в одну и ту же секунду. Одно из двух созданий записывает снимок, и potato snapshot list показывает результат.

Дополнительные материалы

Подробности реализации см. в исходной документации.

Литература

  1. Nowell, L. S., Norris, J. M., White, D. E., & Moules, N. J. (2017). Thematic Analysis: Striving to Meet the Trustworthiness Criteria. International Journal of Qualitative Methods, 16(1). doi:10.1177/1609406917733847