# データモデル (Data Model)

目録（`catalog.sqlite`）の表の定義．スキーマはパッケージ内の `src/tiasdata/catalog/migrations/catalog/` の連番 SQL で管理し，適用済みの版を `PRAGMA user_version` に記録する（[ENV_01](../02_ENV/ENV_01_技術スタック.md)）．対応表（`identity.sqlite`）は別の系列 `migrations/identity/` で同様に管理する．外部キー制約を有効にする（`PRAGMA foreign_keys = ON`）．

## スキーマで強制する規則 (Enforced by Schema)

以下は SQL の制約（CHECK・トリガ）で強制し，コードの誤りでも破れないようにする（2026-10-06 の判断）．

- **書式**: 研究用 ID（`S`・`C` ＋ 6 桁），SHA-256（小文字 64 桁），拡張子，種類名・データセット名（英小文字で始まる snake_case），日時は UTC の `YYYY-MM-DDTHH:MM:SSZ`（**秒まで．小数秒は拒否**），撮影日は `YYYY-MM-DD`
- **主キー**: ID・名前の主キーは NULL を許さない（SQLite は INTEGER 以外の主キーに NULL を入れられるため `NOT NULL` を明示する）
- **種類の整合**: `assets.kind` はファイルの種類，`annotations.kind` は所見の種類だけを指せる．所見の種類は `value_schema_version`（1 以上）を必ず持ち，ファイルの種類は持たない
- **所見は書き換えない**: `annotations` は UPDATE・DELETE を拒否する．例外は訂正のときに `superseded_by` を NULL から一度だけ埋めること（自分より後の行だけを指せる）．値は JSON として正しいこと
- **凍結した版は書き換えない**: `dataset_versions`・`dataset_members`・`dataset_member_annotations` は UPDATE・DELETE を拒否する．そのため版の行は，`manifest.csv` の SHA-256 を計算してから INSERT する
- **対応表**: 1 つの研究用 ID は 1 人の患者にだけ対応する（`identity_map.subject_id` は一意）

## 全体像 (Overview)

```text
subjects（症例）
└── captures（撮影）
    ├── assets（ファイル．舌画像・カラーチャート画像・派生データ）
    │     ▲ derivation_outputs ── derivations（由来: プログラム・版）── derivation_inputs ▶ assets
    └── annotations（所見．舌色等．撮影またはファイルに付く）

datasets ── dataset_versions ── dataset_members（ファイル）── dataset_member_annotations（所見）
                              └── deliveries（受け渡しの記録）

kinds（ファイルの種類・所見の種類の定義）
```

## ID の書式 (Identifiers)

| 対象 | 書式 | 例 |
| --- | --- | --- |
| 症例（研究用 ID） | `S` ＋ 6 桁の連番 | `S000123` |
| 撮影 | `C` ＋ 6 桁の連番 | `C000456` |
| ファイル | 中身の SHA-256（16 進 64 文字．小文字） | `3f9a…` |
| データセット | 英小文字・数字・`_`（英小文字で始まる） | `tongue_color` |
| データセットの版 | 1 から始まる整数 | `1` |

- 研究用 ID は基盤が振る．実際の患者 ID・カルテ番号は目録に入れない（対応表 `identity.sqlite` にだけ置く）
- 現在の舌色データは 1 患者 1 枚ではない（PLAN_01「現在あるデータ」）．取り込みでは研究室 PC の対応表の xlsx（画像の番号 → 患者 ID・何枚目）を読み，**患者 ID ごとに症例を 1 つ**，**画像 1 枚ごとに撮影を 1 つ**作る．患者 ID は対応表 `identity.sqlite` にだけ入れる
- 同じ患者の複数枚が同じ日の撮影か別の日の撮影かは，現在のデータからは分からないため，撮影日（`captured_on`）は NULL にする
- 研究用 ID は番号順に振る．症例 `S` は目録と対応表の両方の最大番号 + 1 から，撮影 `C` は目録の最大番号 + 1 から（一度振った ID は変えない）
- 患者 ID は数字の値を先頭の 0 をそろえた文字列にする（`123`・`123.0`・`"0123"` は同じ患者．Excel の数値のセルは先頭の 0 がもともと落ちるため）
- 対応表 xlsx の読み方（2026-10-06 のユーザー判断）: シートは 1 枚に限る．1 行目は見出し．番号（1 列目）が整数でない行（空行を除く）は使わず，件数を報告する．使う番号について，対応表に無い・同じ番号の行が複数ある（患者 ID が同じでも）・患者 ID が数字でない場合は停止する．撮影にひも付けない番号（同じ中身ですべて要確認の番号）はこれらの確認の対象外．撮影にひも付く要確認の番号は対象
- 撮影の `device` は `TIAS`．所見の `source` は `tiasjudgemodel_folder`．由来は画像 1 枚につき 1 行で，出力の `role` は `cropped`

## 表の定義 (Tables)

### kinds（種類の定義）

ファイルの種類と所見の種類を 1 つの表で管理する．種類の追加はこの表への行の追加と，`src/tiasdata/kinds/` への検証用の型の追加で行う．

| 列 | 型 | 内容 |
| --- | --- | --- |
| `kind` | TEXT PK | 種類名（例: `tongue_crop_masked`・`tongue_color`） |
| `category` | TEXT | `asset`（ファイル）または `annotation`（所見） |
| `description` | TEXT | 説明 |
| `value_schema_version` | INTEGER | 所見の値の型の版（`annotation` のみ．値の形を変えたら上げる） |

最初に用意する種類:

| `kind` | `category` | 内容 |
| --- | --- | --- |
| `tongue_raw` | `asset` | 撮影した舌画像（補正前） |
| `color_chart` | `asset` | カラーチャート画像 |
| `tongue_crop_masked` | `asset` | 色補正後の画像を舌の枠で切り出し，マスク外を黒 (0,0,0) にしたもの（DeepTIAS Image&Calib の `2-cropped` × `3-resized`．現在の舌色データ） |
| `tongue_mask` | `asset` | 舌のマスク画像 |
| `tongue_color` | `annotation` | 舌色 5 分類＋程度．値は `{"label": <slug>, "severity": "strong" \| "mild" \| "normal"}`．slug は `pale`・`light_red`・`red`・`dark_red`・`purple`（TIASJudgeModel の SPEC_01「クラス定義」と同じ）．`light_red` の程度は `normal` だけ，それ以外の 4 つは `strong` か `mild`（元のデータでも淡紅は「淡紅（正常）」だけ）．未知のキー・値は拒否する |
| `review` | `annotation` | 要確認の印（2026-10-08．catalog/0002）．値は `{"reason": str, "resolve": str, "since": "YYYY-MM-DD", "numbers": [int, ...]}`（numbers は取り込み元での番号．1 個以上・昇順・重複なし）．source は `review_list`．撮影またはファイルに付く（SPEC_03「要確認リスト」） |

### subjects（症例）

| 列 | 型 | 内容 |
| --- | --- | --- |
| `subject_id` | TEXT PK | 研究用 ID |
| `created_at` | TEXT | 登録日時（ISO 8601） |
| `note` | TEXT | 備考 |

### captures（撮影）

| 列 | 型 | 内容 |
| --- | --- | --- |
| `capture_id` | TEXT PK | 撮影 ID |
| `subject_id` | TEXT FK | 症例 |
| `captured_on` | TEXT NULL | 撮影日（日付まで．不明なら NULL．現在のデータは不明） |
| `device` | TEXT NULL | 撮影装置（例: `TIAS`） |
| `note` | TEXT | 備考 |

### assets（ファイル）

| 列 | 型 | 内容 |
| --- | --- | --- |
| `sha256` | TEXT PK | 中身の SHA-256 |
| `kind` | TEXT FK | ファイルの種類（`kinds.category = 'asset'`） |
| `capture_id` | TEXT FK NULL | 撮影（撮影にひも付かないファイルは NULL） |
| `ext` | TEXT | 拡張子（`png` 等．小文字） |
| `size_bytes` | INTEGER | サイズ |
| `source` | TEXT | 取り込み元の名前（設定ファイルの `sources.<名前>`） |
| `source_name` | TEXT | 取り込み元での相対パス（現在のデータのファイル名は個人をたどれる情報を含まないことを 2026-10-02 にユーザーが確認） |
| `registered_at` | TEXT | 登録日時 |

- ストア上のパスは `store/<sha256 の先頭 2 文字>/<sha256>.<ext>` で決まるため，目録には保存しない
- 同じ中身のファイルを 2 度取り込むと，同じ行を指す（重複登録しない）．ただし別の撮影・別の種類・**別の拡張子**として登録しようとした場合は停止する（ストアでは拡張子が違うと別のファイルになるが，assets は SHA-256 で 1 行のため）
- 取り込み元で同じ中身が複数の番号にあり，すべて要確認の場合は，`capture_id` を NULL にしてファイルだけを登録し，所見はファイル（`annotations.sha256`）に付ける（患者が確定していないため．SPEC_03「要確認リスト」）

### derivations・derivation_inputs・derivation_outputs（由来）

派生データを「どのファイルから，どのプログラムのどの版で」作ったかを記録する．

| 表 | 列 | 内容 |
| --- | --- | --- |
| `derivations` | `derivation_id` INTEGER PK | — |
| | `tool` | プログラム名（例: `DeepTIAS`） |
| | `tool_version` | プログラムの版（例: `2021-12-exe`・`2.7.1`・git のコミット）．不明なら `unknown` |
| | `mode` | 実行モード・設定（例: `Image&Calib`．JSON で詳細を持ってよい） |
| | `performed_at` | 実行日時（不明なら NULL） |
| | `note` | 備考 |
| `derivation_inputs` | `derivation_id`・`sha256`・`role` | 入力ファイルと役割（例: `tongue`・`chart`） |
| `derivation_outputs` | `derivation_id`・`sha256`・`role` | 出力ファイルと役割（例: `cropped`・`mask`・`analysis`） |

- 同じ入力から版の違うプログラムで作った出力は，別の `derivations` 行になる（DeepTIAS の旧 exe と現行ソースの結果を区別できる）
- 現在の舌色データ（`tongue_crop_masked`）は元の撮影画像が基盤に無いため，入力なし・`tool = 'DeepTIAS'`・`tool_version = 'unknown'`・`mode = 'Image&Calib'` の由来を付けて登録する（TIASJudgeModel の SPEC_04 の確認結果を `note` に書く）

### annotations（所見）

| 列 | 型 | 内容 |
| --- | --- | --- |
| `annotation_id` | INTEGER PK | — |
| `kind` | TEXT FK | 所見の種類（`kinds.category = 'annotation'`） |
| `capture_id` | TEXT FK NULL | 撮影に付く所見の場合 |
| `sha256` | TEXT FK NULL | ファイルに付く所見の場合（`capture_id` とどちらか一方だけ） |
| `value` | TEXT | 値（JSON．`kinds/` の型で検証してから保存する） |
| `annotator` | TEXT NULL | 評価者（研究用の記号．個人名は入れない） |
| `source` | TEXT | 由来（例: `tiasjudgemodel_folder`＝フォルダ名から読み取った） |
| `created_at` | TEXT | 登録日時 |
| `superseded_by` | INTEGER NULL | 訂正したときの新しい行 |

- **所見の行は書き換えない**．訂正は新しい行を追加し，古い行の `superseded_by` を埋める（過去の版が古い行を参照し続けられるようにするため）
- 現在の舌色ラベルは撮影に付ける（医師が撮影を見て付けたラベルのため）

### datasets・dataset_versions・dataset_members・dataset_member_annotations（データセット）

| 表 | 列 | 内容 |
| --- | --- | --- |
| `datasets` | `name` TEXT PK・`description`・`created_at` | — |
| `dataset_versions` | `name`・`version`（PK は 2 列） | — |
| | `definition` | 版を作ったときのデータセットの定義（TOML の内容をそのまま保存） |
| | `hub_commit` | 版を作ったときの本リポジトリのコミット |
| | `manifest_sha256` | この版の `manifest.csv` の SHA-256（指紋） |
| | `created_at`・`note` | — |
| `dataset_members` | `name`・`version`・`sha256` | 版に含まれるファイル |
| `dataset_member_annotations` | `name`・`version`・`sha256`・`annotation_id` | 版を作った時点で採用した所見 |

- **版の行と，その版の構成の行は書き換え・削除しない**（凍結）
- 版の作り方・manifest の形は [SPEC_03](SPEC_03_データセットと受け渡し.md)

### deliveries（受け渡しの記録）

計算サーバにコピーした作業用データと，データセットの版との対応を記録する．

| 列 | 内容 |
| --- | --- |
| `delivery_id` INTEGER PK | — |
| `name`・`version` | 渡したデータセットの版 |
| `server` | 受け渡し先（設定ファイルの `servers.<名前>`） |
| `path` | 受け渡し先のパス（例: `~/data/tiasdata/tongue_color/v1`） |
| `delivered_at` | 日時 |
| `verified` | 受け渡し先でハッシュを照合できたか |

## 対応表 (Identity Map)

`identity.sqlite`（目録とは別のファイル・別のフォルダ）の `identity_map` 表．実際の患者 ID が分かるデータを取り込むときに使う（現在の舌色データも対象）．

| 列 | 内容 |
| --- | --- |
| `patient_key` TEXT PK | 実際の患者 ID（取り込み元が持つもの） |
| `subject_id` TEXT | 研究用 ID |
| `created_at` | — |

- 同じ患者の別の撮影を取り込むと，同じ研究用 ID にひも付く（複数枚・経時的な撮影に対応する）
- CLI は対応表の中身を表示しない．対応表を開くのは取り込みのときと，`tiasdata backup` で複製・点検するときだけ
- 匿名化の方法・対応表の保管ルールは倫理審査の保管ルールを確認して確定する（[PLAN_01「人間が確認すること」](../03_PLAN/PLAN_01_要件定義書.md)）

## 拡張の例 (Extension Examples)

| 追加したいもの | やること |
| --- | --- |
| 苔の色・厚さ等の所見 | `kinds` に行を追加し，`kinds/` に値の型を足す．マイグレーションは種類の行の追加だけ |
| 問診・体質 | 撮影または症例に付く所見として同様に追加する（症例に付く所見が必要になったら `annotations` に `subject_id` 列を足す） |
| 経時的な撮影 | 同じ症例に撮影を追加する（対応表で同じ研究用 ID にひも付ける） |
| 別の撮影装置 | `captures.device` に記録する．装置固有の情報が必要なら所見として持つ |
| DeepTIAS の解析結果 | 出力ファイルの種類（`tongue_mask`・`analysis_csv` 等）を足し，`derivations` で入力の撮影画像とひも付ける |
