# データモデル (Data Model)

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

## 全体像 (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 にする

## 表の定義 (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` |

### 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 度取り込むと，同じ行を指す（重複登録しない）．ただし別の撮影・別の種類として登録しようとした場合は停止する

### 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`（目録とは別のファイル・別のフォルダ）．実際の患者 ID が分かるデータを取り込むときに使う（現在の舌色データも対象）．

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

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

## 拡張の例 (Extension Examples)

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