diff --git a/.claude/rules/coding-style.md b/.claude/rules/coding-style.md new file mode 100644 index 0000000..2a8c52d --- /dev/null +++ b/.claude/rules/coding-style.md @@ -0,0 +1,116 @@ +--- +paths: ["src/**", "migrations/**", "datasets/**"] +--- + +# コーディング規約 (Coding Standards) + +Python コード(`src/**`)・マイグレーション(`migrations/**`)・データセットの定義(`datasets/**`)を編集するときは本ルールに従うこと. + +## 基本方針 (Basic Policy) + +- ruff を唯一の lint / format ツールとして使用する.設定は `pyproject.toml` の `[tool.ruff]` に集約する +- 標準(PEP 8)から逸脱しない範囲で ruff の自動修正に従う +- 別ツール(black・isort・mypy 等)は導入しない(ruff で完結させる) +- 使う側(TIASJudgeModel 等)と同じ規約にそろえる(サブモジュールとして同じ作業の中で編集されるため) + +## 適用 ruff ルール (Ruff Rules) + +`pyproject.toml` の `[tool.ruff.lint]` で `select = ["E", "F", "W", "I", "B", "UP", "N"]` を選択する.行長は 100 文字とする.実行コマンドは [ENV_04 開発コマンド](../../docs/02_ENV/ENV_04_開発コマンド.md)を参照する. + +| ルール | 内容 | +| --- | --- | +| E・W (pycodestyle) | PEP 8 形式 | +| F (pyflakes) | 未使用変数・未定義シンボル等 | +| I (isort) | import 文の並び順 | +| B (flake8-bugbear) | バグになりやすいパターン | +| UP (pyupgrade) | Python 3.12 の書き方への置き換え | +| N (pep8-naming) | 命名 | + +## 臨床データを扱うコードの規則 (Clinical Data Rules) + +本プロジェクトの最重要規則.[PLAN_01 要件定義書](../../docs/03_PLAN/PLAN_01_要件定義書.md)の制約と [SPEC_03「出力の規則」](../../docs/04_SPEC/SPEC_03_データセットと受け渡し.md)に対応する. + +- **データの場所をコードに書かない**.目録・ストア・取り込み元・計算サーバの場所は設定ファイル(`config.py` が読む)から受け取る.テスト用の既定値もコードに置かない +- **CLI の出力・ログ・例外メッセージに出すのは,件数・研究用 ID・SHA-256・種類名・所見の値・設定で付けた名前だけ**.実際の患者 ID・対応表の中身・画像の中身は出さない.元のファイル名(`source_name`)は既定では出さない +- **対応表(`identity.sqlite`)を開くのは取り込みの処理だけ**にする.他のモジュールから対応表を参照しない +- **原本(取り込み元)に書き込まない**.取り込み元は読み取り専用で開き,ストア・目録にだけ書く +- **ストアのファイルを書き換えない・消さない**.ストアへの書き込みは「同じ名前が無ければコピーし,コピー後にハッシュを照合する」だけにする +- `print` ではなく `logging` を使う.ただし CLI の結果表示(件数の表等)は標準出力に書いてよい + +## 目録(SQLite)の規則 (Catalog Rules) + +- SQL は `catalog/` の中にだけ書く.他のモジュールは `catalog/` の関数を呼ぶ +- **値は必ずプレースホルダ(`?`・`:name`)で渡す**.f-string・`%`・`+` で SQL に値を埋め込まない +- 接続ごとに `PRAGMA foreign_keys = ON` を有効にする +- 複数の表にまたがる登録・版の作成は 1 つのトランザクションで行う(`with conn:`).途中で失敗したら何も残さない +- **凍結した版(`dataset_versions`・`dataset_members`・`dataset_member_annotations`)と所見(`annotations`)を UPDATE・DELETE しない**.所見の訂正は新しい行の追加と `superseded_by` の設定だけで行う +- 日時は UTC の ISO 8601(例: `2026-10-02T09:00:00Z`)で保存する + +## マイグレーション (Migrations) + +- スキーマの変更は `migrations/NNNN_<内容>.sql`(4 桁の連番)を追加して行う.**適用済みのマイグレーションファイルは編集しない** +- 1 つのファイルは 1 つのトランザクションで適用できる内容にする +- 種類(`kinds`)の追加もマイグレーションで行い,同時に `src/tiasdata/kinds/` に値の型を追加する +- 表・列を変えたら [SPEC_02 データモデル](../../docs/04_SPEC/SPEC_02_データモデル.md)を先に更新する + +## 型ヒント (Type Hints) + +- **公開関数・クラス**(`tiasdata` から使う側に見えるもの): 引数と戻り値に型ヒント必須 +- **内部ヘルパ・テストコード**: 任意.書ける所は書く +- 目録の行・manifest の行・設定は pydantic のモデルで表す(辞書のまま受け渡さない) +- 静的型チェック(mypy / pyright)は当面導入しない + +## 公開 API の互換 (Public API Compatibility) + +- `tiasdata` の公開 API(`load`・`open_bundle`・`Bundle` 等)と `manifest.csv` の列は使う側が依存する.**名前・意味を変える・消す場合は,PR に使う側への影響を書き,SPEC_03 を更新する** +- 列や引数の追加(既存の使い方を壊さないもの)は自由に行ってよい + +## docstring + +- **公開関数・クラス**: docstring 必須.Google スタイル(`Args:` / `Returns:` / `Raises:`).日本語可 +- 例外を送出する公開関数は `Raises:` に書く + +```python +def load(name: str, version: int) -> Bundle: + """データセットの版を指定してバンドルを開く. + + Args: + name: データセット名(例: `tongue_color`). + version: 版(1 以上). + + Returns: + 開いたバンドル. + + Raises: + ValueError: バンドルが見つからない,または manifest の SHA-256 が一致しない. + """ +``` + +## 命名規則 (Naming) + +| 対象 | スタイル | 例 | +| --- | --- | --- | +| モジュール・パッケージ | snake_case | `bundle.py`・`tiasdata` | +| 関数・変数 | snake_case | `freeze_version`・`manifest_sha256` | +| クラス | PascalCase | `Bundle`・`CatalogConfig` | +| 定数 | UPPER_SNAKE_CASE | `SCHEMA_VERSION` | +| プライベート | 接頭辞 `_` | `_copy_to_store` | +| 種類名(`kinds.kind`)・データセット名 | snake_case の英小文字 | `tongue_crop_masked`・`tongue_color` | + +## コメント (Comments) + +- **WHY を書く**: 何をしているかは識別子で表し,コメントは「なぜこの実装か」「制約」「出典」に限る +- **TODO** は `# TODO(rintoHasegawa): 内容` の形で書く + +## CLI 引数 (CLI Arguments) + +- `argparse` 標準を使う.ヘルプ文は日本語可 +- 短縮形より長い形を優先する(`--version`・`--to`) +- **データのパスを引数に取らない**.取り込み元・受け渡し先は設定ファイルで付けた名前で指定する(SSH のコマンドにデータの場所が現れないようにするため) + +## 参考 (References) + +- [PEP 8 — Style Guide for Python Code](https://peps.python.org/pep-0008/) +- [Google Python Style Guide — Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) +- [ruff documentation](https://docs.astral.sh/ruff/) +- [SQLite — Foreign Key Support](https://www.sqlite.org/foreignkeys.html) diff --git a/.claude/rules/error-handling.md b/.claude/rules/error-handling.md new file mode 100644 index 0000000..6161aec --- /dev/null +++ b/.claude/rules/error-handling.md @@ -0,0 +1,55 @@ +--- +paths: ["src/**"] +--- + +# エラーハンドリング方針 (Error Handling) + +例外を raise / 捕捉するコード(`src/**`)を書くときは本ルールに従うこと.コーディング全般の規約は `.claude/rules/coding-style.md` を参照する. + +## 例外の使い分け (Exception Types) + +| 状況 | 送出する例外 | +| --- | --- | +| 利用者の入力(設定ファイル・データセットの定義・取り込み元の構成・バンドル等)の内容・形式の問題 | `ValueError` に統一して raise | +| 内部論理エラー(到達しないはずの分岐・不変条件違反等) | `RuntimeError` | +| pydantic を使うバリデーション | `pydantic.ValidationError` をそのまま伝播(握りつぶさない) | +| SQLite の制約違反(`sqlite3.IntegrityError`) | 意味のある `ValueError` に変換して再送出する(`from` で元の例外を残す) | + +## データの問題は止める (Fail Fast on Data) + +目録は再現性の根拠になるため,**静かな除外・静かな上書きをしない**. + +- 取り込み元の想定外の構成・未知の所見の値・ハッシュの不一致・参照切れは raise する +- 「とりあえず `try` で囲んで `continue`」は禁止.登録件数が静かに変わり,版の中身の解釈を誤る +- 例外: `tiasdata check`・`tiasdata stats` は現状把握のためのコマンドなので,問題を数えて最後に報告し,終了コードを 0 以外にする(止めずに全体を見る) + +## 個人情報を出さない (No PII in Messages) + +例外メッセージ・ログに,実際の患者 ID・対応表の中身・元のファイル名を含めない.位置は研究用 ID・SHA-256・件数・取り込み元の名前で示す. + +```python +raise ValueError(f"取り込み元 '{source}' のフォルダ {folder_index} 番目: 想定外の拡張子のファイルが {count} 件ある") +``` + +- 元のファイル名が無いと原因を特定できない場合だけ,CLI に `--show-source-names` のような明示の指定があるときに限り表示する + +## CLI の終了コード (Exit Codes) + +| 終了コード | 意味 | +| --- | --- | +| 0 | 成功 | +| 1 | 入力・データの問題(`ValueError`).メッセージを標準エラーに 1 行で出す(スタックトレースは出さない) | +| 2 | 引数の誤り(`argparse` の既定) | +| それ以外の例外 | スタックトレースを出して異常終了する(バグとして扱う) | + +## 方針 (Policy) + +- **最小限主義**: 独自例外クラスの階層は導入しない.上表の標準例外で表現できる範囲に留める +- **メッセージは日本語可**: 原因と対処が分かる具体的な内容にする +- **握りつぶさない**: `except:` で例外を無言で捨てない +- **境界で検証する**: 入力の検証は入口(設定の読み込み・取り込み元の走査・定義の読み込み・バンドルの読み込み)で行い,内部は検証済みの入力が来る前提で書く + +## 参考 (References) + +- [Python 公式ドキュメント — 組み込み例外](https://docs.python.org/3/library/exceptions.html) +- [Pydantic — Error Handling](https://docs.pydantic.dev/latest/errors/errors/) diff --git a/.claude/rules/lab-pc-operation.md b/.claude/rules/lab-pc-operation.md new file mode 100644 index 0000000..42e8895 --- /dev/null +++ b/.claude/rules/lab-pc-operation.md @@ -0,0 +1,23 @@ +# 研究室 PC の操作ルール (Lab PC Operation) + +研究室 PC(`ssh labpc`)や計算サーバに SSH でコマンドを送るときは,常に本ルールに従うこと.運用の背景は [ENV_02「研究室 PC の操作方針」](../../docs/02_ENV/ENV_02_環境構築手順.md). + +## 禁止 (Prohibited) + +- **データの中身を表示しない**: `type`・`Get-Content`・`cat`・`more`・`sqlite3` 等で,取り込み元・ストア・目録・対応表・バンドル(`C:\Users\Papin\clinical-data\` 配下,サーバの `~/data/` 配下)を読まない.フック(`.claude/repo-access.json`)は SSH の先の読み取りを止められないため,ここで守る +- **データの一覧を表示しない**: `dir`・`ls`・`Get-ChildItem` でデータのフォルダの中身(ファイル名)を表示しない.件数が必要なら `tiasdata stats`・`tiasdata check` を使う(CLI が無い段階で件数が必要なら,ファイル名を出さずに数だけを返すコマンドにする) +- **研究室 PC で編集・コミットしない**: 研究室 PC のリポジトリは `git pull` と `uv sync --no-dev` だけにする +- **原本・ストア・目録を手で消さない・動かさない**: 削除・移動が必要な場合は,実行するコマンドを提示してユーザーに依頼する + +## 許可 (Allowed) + +- `tiasdata` CLI の実行(出力は [SPEC_03「出力の規則」](../../docs/04_SPEC/SPEC_03_データセットと受け渡し.md)で個人情報を含まないことが保証されている) +- ツールの有無・版の確認(`where`・`--version`),`git pull`・`uv sync --no-dev` +- ツールの導入は,コマンド・入る場所を示してユーザーの承認を得てから行う + +## 書き方 (Conventions) + +- 非対話で実行する(`ssh -n labpc "..."`).パスワード入力・GUI が必要な操作は SSH で行わず,ユーザーに依頼する +- 研究室 PC の uv は絶対パス(`C:\Users\Papin\.local\bin\uv.exe`)で呼ぶ +- 出力が文字化けしたら手元で `iconv -f cp932 -t utf-8` を通す +- コマンドにデータのパスを書かない(`tiasdata` は設定ファイルで付けた名前で指定する) diff --git a/.claude/rules/project-structure.md b/.claude/rules/project-structure.md new file mode 100644 index 0000000..037db8b --- /dev/null +++ b/.claude/rules/project-structure.md @@ -0,0 +1,39 @@ +--- +paths: ["src/**", "tests/**", "migrations/**", "datasets/**"] +--- + +# プロジェクト構成ルール (Project Structure) + +ソース・テスト・マイグレーション・データセットの定義を追加・移動するときは本ルールに従うこと.設計の背景は [SPEC_01 アーキテクチャ設計](../../docs/04_SPEC/SPEC_01_アーキテクチャ設計.md)を参照する(本ルールは配置規則のみを定める). + +## ディレクトリ構成 (Layout) + +```text +src/tiasdata/ +├── __init__.py # 公開 API(load・open_bundle・Bundle・Catalog)を再 export する +├── config.py # 設定ファイルの読み込み・検証 +├── catalog/ # 目録(SQLite)の接続・マイグレーションの適用・各表の操作(SQL はここだけ) +├── store.py # ストア(ハッシュ・コピー・照合) +├── kinds/ # ファイルの種類・所見の種類の値の型 +├── importers/ # 取り込み元の形ごとの読み取り(1 つの形 = 1 モジュール) +├── datasets.py # データセットの定義の評価・版の作成 +├── bundle.py # バンドルの書き出し・読み込み・検証 +├── deliver.py # 計算サーバへの受け渡し(scp・ssh) +└── cli.py # CLI エントリポイント + +migrations/ # 目録のスキーマ変更(NNNN_<内容>.sql) +datasets/ # データセットの定義(<名前>.toml) +tests/ # src/tiasdata/ の構造をミラーする +``` + +## 配置ルール (Placement Rules) + +- **1 モジュール 1 責務**: 新しい機能は責務に合うモジュールに置く.大きくなったらサブパッケージに分ける(既存モジュールを肥大化させない) +- **依存方向**: `cli` → `datasets`・`bundle`・`deliver`・`importers` → `catalog`・`store`・`kinds`・`config` の一方向を保つ.下位から上位を import しない +- **バンドルの読み込みは目録に依存しない**: `bundle.py` の読み込み側(`load`・`open_bundle`)は `catalog`・`store`・`config` を import しない(目録の無い計算サーバで使うため.書き出し側は別関数に分ける) +- **公開 API は `__init__.py` で export したものだけ**: 使う側は `tiasdata.<名前>` で使う.内部モジュールを直接 import させない +- **取り込み元を増やすとき**: `importers/` にモジュールを追加し,設定ファイルの `importer` の名前から選べるようにする.既存の importer に分岐を足さない +- **種類を増やすとき**: `migrations/` に `kinds` の行を追加する SQL と,`kinds/` に値の型を追加する([SPEC_02「拡張の例」](../../docs/04_SPEC/SPEC_02_データモデル.md)) +- **副作用は縁に寄せる**: ファイルのコピー・SSH・DB への書き込みは `store`・`deliver`・`catalog` に閉じ込め,定義の評価・manifest の行の組み立て・照合の判定は入力 → 出力の純関数として書く(テスト容易性のため) +- **データを置かない**: 臨床データ・目録・対応表・バンドルをリポジトリの配下に置かない(`.gitignore` は保険.[ENV_02「臨床データの取り扱い」](../../docs/02_ENV/ENV_02_環境構築手順.md)) +- **テストの配置**: `tests/` は `src/tiasdata/` の構造をミラーする(詳細は `.claude/rules/testing.md`) diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md new file mode 100644 index 0000000..a6b53da --- /dev/null +++ b/.claude/rules/testing.md @@ -0,0 +1,62 @@ +--- +paths: ["tests/**", "**/test_*.py", "**/conftest.py"] +--- + +# テスト方針 (Test Policy) + +テストコード(`tests/**`・`test_*.py`・`conftest.py`)を追加・編集するときは本ルールに従うこと. + +## 基本方針 (Basic Policy) + +- フレームワーク: **pytest** +- 配置: `tests/` 配下に `src/tiasdata/` の構造をミラーする +- カバレッジ目標: **数値目標なし**.代わりに「下記の必須対象は必ず単体テストを書く」を徹底する +- **臨床データを使うテストは書かない**.テストはダミーデータ(`tmp_path` に作る小さい画像・設定ファイル・目録・ストア)だけで完結させる.手元 PC でも研究室 PC でも同じテストが通ること +- 研究室 PC の実環境での確認は,テストではなく `tiasdata check`・`tiasdata stats` を SSH 経由で実行して行う([SPEC_03「CLI」](../../docs/04_SPEC/SPEC_03_データセットと受け渡し.md)) +- 実行コマンドは [ENV_04 開発コマンド](../../docs/02_ENV/ENV_04_開発コマンド.md)を参照する + +## ファイル・関数命名 (Naming) + +- ファイル: `test_<対象モジュール>.py` +- 関数: `test_<振る舞い>`(例: `test_freeze_keeps_past_version_unchanged`) +- クラス(任意): `Test<クラス名>` で振る舞いごとにまとめてよい + +## 必ずテストを書く対象 (Mandatory) + +1. **版の凍結**: データ・所見を追加・訂正した後も,過去の版から同じ構成・同じ `manifest.csv`(SHA-256 一致)が得られること.構成が同じなら新しい版を作らないこと.**最重要**(破れると再現性が無意味になる) +2. **ストア**: 同じ中身は 1 つにまとまること.コピー後にハッシュを照合すること.既存のファイルを上書きしないこと.原本(取り込み元)を変更しないこと +3. **取り込み**: 2 回実行しても結果が同じであること.取り込み元の想定外の構成(未知のフォルダ・空のフォルダ・想定外の拡張子)で停止すること.途中で失敗したら目録に何も残らないこと +4. **所見の検証と訂正**: 未知の値・無効な組み合わせ(例: `light_red` 以外で `normal`)を拒否すること.訂正しても古い行が残り,過去の版が古い値を返すこと +5. **バンドル**: 書き出したバンドルを `open_bundle` で読めること.`manifest.csv` の SHA-256 の不一致・ファイルの欠け・中身の改変を検出すること.バンドルの読み込みが目録なしで動くこと +6. **出力の規則**: CLI の出力・ログ・例外メッセージに,対応表の患者 ID・元のファイル名(既定)が現れないこと(ダミーの患者 ID・ファイル名を仕込んで確認する) +7. **設定ファイル**: 未知のキー・必須キーの欠落が `ValidationError` になること +8. **マイグレーション**: 空の DB に全マイグレーションを適用でき,`PRAGMA user_version` が最新になること.再適用しても壊れないこと + +## テストを必須としない対象 (Optional) + +- **CLI のエントリポイント**: 主要な流れ(init → import → freeze → export)を統合テスト 1 本でカバーする +- **受け渡し(`deliver`)の SSH 部分**: コマンドの組み立てだけを検証する(下記) + +## モック方針 (Mocking) + +- **SSH・scp はモックする**.テストが研究室 PC・計算サーバに接続してはならない(組み立てたコマンド・引数を検証する) +- **ファイル I/O・SQLite はモックしない**.`tmp_path` に実ファイル・実 DB を作る +- 日時に依存するテストは日時を引数で渡すか固定する + +## /implement パイプラインでのテスト (Pipeline) + +1. **Phase 1(実装 / coder)**: 機能実装 → 人間が動作確認 +2. **Phase 2(テスト / tester)**: 本方針に従いテストを追加 +3. **Phase 3(リファクタ / refactorer)**: テストを safety net として品質改善 +4. **Phase 4**: ドキュメント更新 + +Phase 2 でテストが書きにくいと感じたら設計が悪いシグナルである.Phase 3 で副作用と純関数を分け直す. + +## テスト失敗時の運用 (On Failure) + +- 仕様変更による失敗: 先に SPEC を更新してからテストを書き直す +- バグ起因の失敗: 修正してから再実行する + +## 参考 (References) + +- [pytest documentation](https://docs.pytest.org/)