# 資料結構與格式契約

## 層級

`catalog → provenance + sources[] + records[]`

`record → 身分與版本 + facts[] + length_m + interpretations[] + images[]`

`fact/evidence → source_id → sources.id`

主資料符合 `schema/catalog.schema.json`；單筆符合 `schema/record.schema.json`；每行 JSONL 符合 `schema/jsonl.schema.json`。JSON Schema 2020-12 使用 required、additionalProperties=false、type、enum、const、pattern 與最低值限制。跨紀錄唯一性、來源外鍵及權利限制由額外驗證處理。

| 欄位 | 語意與規則 |
|---|---|
| schema_version | 語意版本；不相容欄位變更升主版 |
| dataset_id / id | 資料集與紀錄固定識別碼；勿依中文名合併 |
| franchise | st / sw / ex / lis，與 franchise_zh 配對 |
| name_zh / name_en / aliases | 顯示名稱、英文辨識名、另名陣列；中文譯名不是官方認證 |
| entity_kind | vessel 單艦、ship_type 艦型、station 空間站 |
| continuity | 時間線、作品版本、必要改裝階段；不要跨版本合併 |
| selection_origin | current_curated_addition 本次補建；recovered_original 須實際讀到原文才能使用 |
| role_tag | 策展分類；role_tag_status 固定 editorial_inference |
| summary_zh / summary_status / summary_evidence | 摘要、認知狀態與證據；並非整筆所有欄位都 confirmed |
| facts[] | key、value、status、evidence[]、note；新增戰力等欄位也須逐項證據 |
| length_m | value 為非負數或 null，公尺；附 status、evidence、note |
| interpretations[] | 推論文字、inferred 狀態及 basis；與官方設定分開 |
| images[] | 本地示意圖及外部參考頁的權利紀錄；詳見 RIGHTS.md |
| reviewed_at | YYYY-MM-DD，本次審閱日，不是作品世界中的日期 |

狀態：`confirmed` 有來源支持；`inferred` 推論；`disputed` 來源衝突；`unknown` 未知。確認欄位至少一筆 evidence。推論須寫推論依據；爭議須記載衝突來源。未知數值必須 null，不能 0、空字串或編造。沒有時間／版本一致的證據時維持未知。本版無數值推估，但結構保留 inferred 以容納未來有方法與依據的估算。

## 各格式

- JSON：UTF-8、無 BOM；完整 catalog 物件，唯一主資料。
- JSONL：UTF-8、無 BOM、LF；每行一個 `{schema_version,dataset_id,provenance,record,sources}`。sources 是該筆所需來源子集，不是只有艦船文字，移轉單行也可追溯。
- CSV：UTF-8 BOM（方便 Windows Excel）、CRLF、逗號分隔，所有欄位雙引號包覆，內部雙引號加倍。固定欄序：id,franchise,name_zh,name_en,entity_kind,continuity,role_tag,summary_zh,length_m,length_status,record_json,sources_json。length_m 空格位實際為空字串，代表 null，需搭配 length_status。record_json 與 sources_json 是完整 JSON 字串，保留巢狀結構，不能當純摘要丟棄。整包 provenance/schema_version 由 JSON 或 JSONL 取得；CSV 是紀錄級交換，不是完整 catalog 備份。匯入先用 CSV parser，再 JSON.parse 兩個 JSON 欄位，勿以字串 split(',')。
- YAML：YAML 1.2，UTF-8、無 BOM、字串使用 JSON 相容引號；與 JSON 結構相同，無自訂 tag。請用 safe_load，日期保留字串。

`data/manifest.json` 提供筆數、版本與四個資料檔 SHA-256，供搬移後比對。`public/data/` 是同一檔案的靜態副本。

## MySQL 對應

fleet_ships.id 為主鍵；franchise 為索引；name_zh、name_en、continuity 是查詢摘要欄位；payload 用 UTF-8 JSON 字串保存完整 record。fleet_metadata 以 catalog 一列保存其餘 catalog 欄位與 active_ids 清單。使用 LONGTEXT 可相容 MySQL 和 XAMPP 常見 MariaDB；合法 JSON 由匯入器 json_decode 檢查，完整 Schema 請於匯入前以 verify.py 驗證。

匯入用交易及 upsert，重跑不新增重複 ID；未在新資料集的舊列保留但不在 active_ids，API 不輸出。需真正刪除時先備份並另行維護，本匯入器不清庫。來源放在 metadata，API 以完整 catalog 回傳；不是另建來源外鍵表。

## API

GET `api/ships.php` → 完整 catalog；GET `api/ships.php?id=st-voyager` → 一筆 records 陣列，保留來源目錄。400 非法 ID，404 無紀錄，405 非 GET，503 資料庫未設定或無法連線。錯誤不回傳憑證。此為小型資料集唯讀 API，未實作登入或公網限流。
