본문으로 건너뛰기

인용 가능한 데이터셋과 감사 추적을 위한 프로젝트 스냅숏

논문이 인용할 수 있는 ID로 Potato 프로젝트의 구성, 데이터, 어노테이션을 고정하고, 두 스냅숏을 비교하며, 독자에게 확인용 해시를 제공합니다.

potato snapshot은 논문이나 데이터셋 카드가 인용할 수 있는 ID로 프로젝트의 구성, 데이터 파일, 어노테이션을 고정합니다. 각 스냅숏은 해당 파일과 응답에 대한 SHA-256 콘텐츠 해시를 기록하며, 스냅숏의 파일을 가진 사람은 누구나 해시를 다시 계산하여 자신의 사본이 인용된 것임을 확인할 수 있습니다. 스냅숏은 또한 그날의 일치도 상태에서 각 어노테이션 스킴의 Krippendorff의 알파를 저장합니다. 스냅숏은 Potato 2.12.0에서 출시되었습니다. 모든 명령은 서버가 실행 중이든 아니든 디스크에서 프로젝트를 읽습니다.

연구는 논문이 작성된 후에도 계속 바뀝니다. 어노테이터가 합류하고, 판정 후 코드가 수정됩니다. 질적 연구에서 감사 추적은 독자가 분석이 신뢰할 만했는지 판단할 수 있게 하는 데이터, 코딩 결정, 분석 단계의 기록입니다(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 경로가 스냅숏을 나열하고 만들며, 선택적인 기탁은 스냅숏을 DOI와 함께 Zenodo에 올립니다.

명령

모든 명령은 프로젝트의 구성 파일을 받습니다.

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과 Zenodo 기탁을 위한 deposits.json, 두 개뿐입니다. 스냅숏 폴더는 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.json데이터 파일의 항목 ID이며 파일 순서대로입니다
manifest.jsonID, 생성 시각, 노트, 작업 이름, Potato 버전, 해시, 개수, 일치도 요약, 연구 파일, 제외된 파일, 이전 스냅숏의 ID, 순번입니다

annotations.jsonl의 각 행은 라벨 응답을 "labels" 아래에, 스팬 응답을 [label, start, end] 세 값의 배열로 "spans" 아래에 보관합니다. 라벨 응답이 있는 행은 아래 줄처럼 보입니다.

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이 /로 끝나면 출력 디렉터리 안에 있습니다.
  • 비밀번호, 시크릿, 토큰, 자격 증명, API 키, 개인 키를 언급하는 구성 키 아래에 지정된 파일은 제외됩니다. 이 규칙은 ssl_key와 sp_key_file처럼 key_file로 끝나는 모든 키를 포괄합니다.
  • admin_api_key.txt는 제외됩니다.

manifest.json은 제외된 각 파일을 이유와 함께 omitted 아래에 나열하며 create가 그 목록을 출력합니다. 제외된 파일 중 어느 것도 콘텐츠 해시에 들어 있지 않습니다. 스냅숏에서 풀어낸 프로젝트에는 계정이 없으며, 자신의 사본에서 명단을 복원하지 않는 한 어노테이터는 다시 등록해야 합니다.

콘텐츠 해시

content_hash는 구성 파일, 번들에서의 이름 아래 있는 각 연구 파일, 항목 ID 목록, 어노테이션 행에 대해 이 순서로 계산한 SHA-256 다이제스트입니다. 해시는 구성, 연구 파일, 또는 어떤 응답이 바뀌면 바뀝니다. SQLite 데이터베이스는 번들에 있고 해시 밖에 있습니다.

서버는 실행되는 동안 데이터베이스를 다시 쓰므로 데이터베이스는 해시에서 제외됩니다. 다시 쓸 때마다 바뀌는 해시는 독자에게 어노테이션이 바뀌었는지 알려 줄 수 없습니다. QDA 모드의 메모, 코드북, 케이스는 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가 계수를 계산하는 방식의 Krippendorff의 알파(명목)입니다. multiselect 스킴은 항목과 라벨별로 채점됩니다. 각 스킴은 또한 Krippendorff의 해석 중 하나를 얻는데, 0.800부터는 "acceptable agreement", 0.667부터는 "tentative agreement", 0.667 미만은 "low agreement"입니다. 알파를 계산할 수 없는 스킴은 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_data 권한이 있는 세션새 매니페스트와 함께 201. 아무것도 바뀌지 않았으면 "unchanged": true와 기존 스냅숏과 함께 200. 잘못된 태그는 아무것도 쓰이지 않은 채 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_TOKENZenodo 액세스 토큰
--production꺼짐(샌드박스)sandbox.zenodo.org 대신 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 개인 키, 파싱할 수 없는 구성 파일, 8MB를 넘는 구성 파일도 기탁을 멈춥니다. 각 값을 ${ENV_VAR}로 바꾸거나 파일을 프로젝트 밖으로 옮기고, 새 스냅숏을 찍은 다음 새 스냅숏을 기탁하세요.
  • 어노테이터 ID. Potato는 응답과 번들의 모든 저장된 상태에서 어노테이터 ID를 세고, 이메일 주소처럼 보이는 ID를 지목하며, --annotator-ids-public을 전달하지 않으면 기탁을 멈춥니다. 저장된 상태에 동의와 설문 응답이 들어 있으므로, 저장된 상태가 있고 응답이 없는 어노테이터도 집계됩니다.

기탁된 스냅숏은 어노테이터 ID를 저장된 대로 유지하는 반면, python -m potato.publish는 기본적으로 어노테이터 ID를 A1부터 An까지로 매핑합니다. ID를 비공개로 유지해야 한다면 퍼블리셔를 사용하세요. 각 기탁은 snapshots/deposits.json에 기록되며, show는 각 기탁을 Zenodo URL 및 DOI와 함께 출력합니다. 스냅숏이 zenodo.org에 게시된 DOI를 가지면, 그 스냅숏으로 고정된 모든 릴리스의 데이터셋 카드가 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는 같은 콘텐츠에 대한 두 개의 create가 같은 초에 시작되었음을 뜻합니다. 두 create 중 하나가 스냅숏을 쓰며, 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