用於可引用資料集和審計軌跡的項目快照
把 Potato 項目的配置、資料和標註凍結在一個論文可以引用的 id 下,比較兩個快照,並給讀者一個可以核對的雜湊值。
potato snapshot 把一個項目的配置、資料檔案和標註凍結在一個 id 下,論文或資料集卡片可以直接引用這個 id。每個快照都會對這些檔案和回答計算一個 SHA-256 內容雜湊,任何持有該快照檔案的人都可以重新計算雜湊,確認自己手裡的副本就是被引用的那一份。快照還會為每個標註方案儲存當天的 Krippendorff's alpha,記錄當時的一致性。快照功能隨 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。
命令
每條命令都需要項目的配置檔案:
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 接受相同的參數。下表列出每條命令的參數和選項:
| 命令 | 參數和選項 | 作用 |
|---|---|---|
create | config、--note TEXT(預設為空)、--tag NAME、--force | 凍結項目。內容雜湊與最新快照的雜湊相同時,create 不寫入任何內容,並給出提示。--force 仍會寫入一個新快照。內容未變時給出的 --tag 會指向已有的快照 |
list | config、--json | 按從新到舊列出快照,附帶標籤、條目數、標註者數、回答數以及備註 |
show | config、ref(預設 latest)、--json | 列印一個快照的雜湊、Potato 版本、上一個快照、存檔記錄以及每個方案的 alpha |
diff | config、old、new(預設 latest)、--json | 報告兩個快照之間的變化 |
tag | config、ref、name | 把一個名稱指向某個快照。若該名稱原先指向另一個快照,則會移過來 |
verify | config 和 ref(預設 latest),或 --dir FOLDER | 依據清單重新計算快照的雜湊。使用 --dir 時檢查一個裝有快照檔案的資料夾,不需要配置檔案 |
deposit | config、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 版本以及每個方案的 alpha:
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.json | id、建立時間、備註、任務名稱、Potato 版本、雜湊、計數、一致性摘要、研究檔案、被排除的檔案、上一個快照的 id 以及序號 |
annotations.jsonl 的每一行把標籤回答放在 "labels" 下,把跨度回答以 [label, start, end] 三元組的形式放在 "spans" 下。一行標籤回答如下所示:
{"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 是對配置檔案、按打包檔案中的名稱排列的每個研究檔案、條目 id 列表以及標註行依次計算的 SHA-256 摘要。配置、任何研究檔案或任何回答發生變化,雜湊都會隨之改變。SQLite 資料庫在打包檔案中,但不在雜湊範圍內。
資料庫不計入雜湊,是因為伺服器執行期間會不斷重寫這些資料庫。如果雜湊隨每次重寫而變化,讀者就無法據此判斷標註是否發生了變化。QDA 模式的備忘錄、碼本和案例都存放在 project.sqlite 中。只修改備忘錄或程式碼定義不會改變內容雜湊,create 會報告沒有任何變化。如果要凍結一次沒有改動任何回答的碼本修訂,請加上 --force。
verify 執行三項檢查:
bundle.tar.gz必須與清單中的bundle_sha256一致。annotations.jsonl必須與annotations_sha256一致。- 從打包檔案中解出的配置和研究檔案,與條目 id 和標註行一起計算雜湊後,必須重現
content_hash。
只持有這四個檔案的讀者,可以用 --dir 執行同樣的檢查:
$ 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 逐個回答比較兩個快照,並統計新增、刪除和更改的回答數。回答按標註者、條目和方案進行匹配。報告還會列出新增或刪除的條目和標註者,說明配置是否有變化,並給出每個方案變化前後的 alpha:
$ 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 方案的 alpha 從 0.511 降到了 0.298。文本報告中每類最多列出 20 行。--json 以機器可讀的形式給出完整計數以及同樣的這些行。對於內容雜湊相同的兩個快照,diff 會列印 Same content.
快照中的 alpha 是 Krippendorff's alpha(名義型),計算方式與論文模式(Paper Mode)相同。多選方案按條目和標籤分別計分。每個方案還會得到 Krippendorff 的一種解讀:從 0.800 起為“acceptable agreement”,從 0.667 起為“tentative agreement”,低於 0.667 為“low agreement”。無法計算 alpha 的方案顯示 n/a。
恢復快照
Potato 沒有恢復命令。快照的 bundle.tar.gz 解包後是一個 project/ 資料夾,其中有配置、研究檔案、標註輸出和資料庫。Potato 可以像啟動其他項目一樣,直接用解包後的配置啟動:
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 並返回新的清單。沒有任何變化時返回 200,附帶 "unchanged": true 和已有的快照。標籤無效時返回 400,且不寫入任何內容。同一快照的另一次建立正在進行時返回 409 |
POST 請求體接受 note、tag 和 force,均為可選:
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 hash68e058683e70…). Anyone holding the project folder can check that their copy is this one withpotato 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 塊:
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 沙盒:
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。
| 選項 | 預設值 | 作用 |
|---|---|---|
--token | ZENODO_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 MB 的配置檔案,同樣會讓存檔停止。請把每個這樣的值替換為${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 中刪除指向該快照的所有標籤:
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 會顯示結果。
延伸閱讀
- 論文模式 - 使用相同一致性統計量的 LaTeX 資料集報告
- QDA 模式 - 用於質性編碼的程式碼、備忘錄和案例
- 標註歷史 - 每次標註操作的時間戳
- 標註者間一致性 - 選擇和解讀一致性係數
- 記錄資料集和模型 - 用於釋出的資料集卡片和資料表
有關實現細節,請參閱源文件。
參考文獻
- 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