Skip to content

fix(measures): 有効性評価の範囲を EffectivenessScale へ寄せ、写経の取り残しを塞ぐ - #182

Merged
izumacha merged 2 commits into
mainfrom
claude/dreamy-brown-n0kypy
Aug 28, 2026
Merged

izumacha merged 2 commits into
mainfrom
claude/dreamy-brown-n0kypy

Conversation

@izumacha

Copy link
Copy Markdown
Owner

課題

9685a03(#173)で優先度の段階・ラベル・配色を MeasurePriorityScale へ集約しましたが、隣の「有効性評価」には EffectivenessScale を見ていない経路が 2 つ残っていました。

EffectivenessScale の doc は自ら「同じ語彙・同じ段階数が次の 5 箇所へ写経されていた」と列挙し、段階を 5 → 7 に増やす変更を想定例として挙げています。しかし下記 2 か所はその一覧から漏れていました。

1. IncidentMeasuresController.RateMeasure が範囲を直書き

src/IncidentInsight.Web/Controllers/IncidentMeasuresController.cs:208

if (effectivenessRating < 1 || effectivenessRating > 5)
{
    TempData["Warning"] = "有効性評価は1〜5の値を指定してください。";

同じ項目を扱う 3 か所のうち、ここだけが尺度を見ていません:

経路 範囲の出どころ
詳細画面のラジオ(Views/Incidents/Details.cshtml:625) EffectivenessScale.All ✅
PreventiveMeasuresController.Review(ReviewViewModel) [Range(EffectivenessScale.Min, EffectivenessScale.Max)] ✅
IncidentMeasuresController.RateMeasure 1 / 5 を直書き ❌

具体的な壊れ方: Max を 7 に上げると、詳細画面のモーダルはラジオ 6・7 を描画するのに、送信すると RateMeasure が「有効性評価は1〜5の値を指定してください。」で弾き、評価が黙って捨てられます。一方 /PreventiveMeasures/Review/{id} から同じ値を送ると保存される、という経路間の食い違いになります。

2. エンティティ側の属性も直書き

src/IncidentInsight.Web/Models/PreventiveMeasure.cs:96

[Range(1, 5)]
[Display(Name = "有効性評価(1〜5)")]

同じファイルのすぐ下の Priority は #173 で MeasurePriorityScale.Min/Max/DisplayName へ寄せ済みで、隣り合う 2 つで方針が割れていました。

3. これを検出できなかったテスト側の穴

上記がビルドも 461 件のテストも緑のまま通っていた理由:

  • 範囲外テストは下限(0)しか回していなかった — 上限を広げても 0 は弾かれ続けるので緑
  • 成功経路 RateMeasure_NoRecurrence_SetsSuccess は評価値を 5 と直書き — 上限を 7 にしても 5 は有効なままなので緑

つまり「受け付ける側の写経漏れ」を見る検証が 1 つも無い状態でした。

変更内容

  • RateMeasure の範囲判定・警告文言を EffectivenessScale.Min / .Max から引く(文言は補間で組み立て、既存の言い回しは維持)
  • PreventiveMeasure.EffectivenessRating の [Range] / [Display] を尺度から引く(Priority と同じ形に揃える)
  • 範囲外テストを下限・上限両方の [Theory] にし、期待する範囲も尺度から組み立てる
  • 成功経路を EffectivenessScale.Max で評価し、その値が実際に保存されることまで確認する

[Range] / [Display] は DataAnnotations のみでスキーマに影響しないため、マイグレーションは不要です。

検証(mutation テスト)

「実行日に依存しない」だけでなく「バグを実際に検出できる」ことを機械的に裏付けました。尺度の Max を一時的に 7 へ変えて:

状態 結果
Max=7 + 修正前コントローラ(1〜5 直書き) ❌ 1 failed — 検出網が働く
Max=7 + 修正後コントローラ(尺度参照) ✅ 7 passed
Max=5 へ戻して全体 ✅ 462 passed / 0 failed

dotnet build は 0 Warning / 0 Error。(修正前のベースラインは 461 passed で、追加した Theory ケース 1 件ぶん増えています。)

対象外とした関連事項

レビュー中に、IncidentsController.Delete / PreventiveMeasuresController.Delete のコメントが「部署スコープも考慮」「部署一致/管理者系」と書いているのに対し、Policies.CanDeleteIncident は RequireRole(Admin, RiskManager) のみで SameDepartmentRequirement を持たないことを確認しました(Program.cs:242)。

現状は Admin/RiskManager しか到達できないため実害はありませんが、コメントを信じて将来 Staff へ広げると他部署インシデントの削除が素通りします。ポリシー自体の変更は認可の振る舞いを変えるため見送り、コメントを実態(ロール判定のみ/リソースを渡すのは将来ポリシーへ部署要件を足したとき自動追随させるため)へ訂正するに留めました。


Generated by Claude Code

9685a03 で優先度の段階・ラベル・配色を MeasurePriorityScale へ集約したが、隣の
有効性評価には EffectivenessScale を見ていない経路が 2 つ残っていた。

1. IncidentMeasuresController.RateMeasure が範囲を 1〜5 と直書きしていた。
   詳細画面のラジオは EffectivenessScale.All から生成され、並行経路の
   PreventiveMeasuresController.Review は ReviewViewModel の
   [Range(EffectivenessScale.Min, EffectivenessScale.Max)] で検証しているため、
   同じ項目の 3 か所のうちここだけが尺度を見ていない状態だった。
   段階を 5 → 7 に増やすと(EffectivenessScale の doc が想定している変更)、
   詳細画面はラジオ 6・7 を描くのにこの経路だけが弾き、評価が黙って捨てられる。
   同じ値が /PreventiveMeasures/Review からは保存されるという経路間の食い違いになる。

2. PreventiveMeasure.EffectivenessRating の [Range(1, 5)] と
   [Display(Name = "有効性評価(1〜5)")] も直書きのままだった。すぐ下の Priority は
   既に尺度から引いているので、隣り合う 2 つで方針が割れていた。

あわせて、この取り残しを検出できなかったテスト側の穴も塞いだ。範囲外テストは
下限(0)しか無く、成功経路も評価値を 5 と直書きしていたため、上限を広げても
両方とも緑のまま通っていた。範囲外を下限・上限の Theory にし、成功経路は
EffectivenessScale.Max で評価して保存されることまで確認する。

検証: 尺度の Max を一時的に 7 へ変えて mutation テストを実施。
  - Max=7 + 修正前コントローラ(1〜5 直書き)→ 1 failed(検出できる)
  - Max=7 + 修正後コントローラ(尺度参照)  → 7 passed
  - Max=5 へ戻して全体 → 462 passed / 0 failed(ビルド 0 warning)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXb7LwvtQ9p5CfCpKbnuM7
@vercel

vercel Bot commented Aug 28, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
incident-insight Error Error Aug 28, 2026 8:53am
incident-insight-jjt5 Error Error Aug 28, 2026 8:53am

セルフレビューの指摘対応。前のコミットは受け付ける側の境界を上限しか押さえておらず、
下限を 1 つ内側へずらす取り違え(< を <= にする等)がどのテストにも観測されないまま
通っていた。実際に変異させて 462 件すべて緑のまま通ることを確認している。

そのとき捨てられるのは ★1「効果なし」=対策が効かなかったことを示す評価で、再発検知の
KPI に直接効く値なので、無言で拒否されるのは実害が大きい。範囲外テストは弾かれる側
(Min-1)しか見ないため、この経路は成功側でしか押さえられない。

成功経路を下限・上限の Theory にし、両端が受け付けられて保存されることを確認する。

あわせてレビューで見つかった、同じ「写経の取り残し」に当たるコメント 2 件を直した。
- PreventiveMeasuresController.Delete の Include の直上に、部署スコープの認可判定に
  必要だと述べる古いコメントが残っていた。前コミットで 8 行下に正しい説明を足したため、
  上から読むと先に誤った主張に当たる状態になっていた。2 つを 1 つにまとめた。
- Views/Incidents/Details.cshtml のラジオ生成の直上に「1〜5の5段階(デフォルトは中央の3)」
  と段階数と既定値を直書きしたコメントが残っていた。次の行が既に
  「EffectivenessScale から引く」と正しく述べており、尺度を変えると描画だけが追随して
  この説明文が取り残される。本 PR が塞いでいる失敗の仕方そのものなので数値を落とした。

検証: 下限変異(< → <=)・上限変異(> → >=)のどちらでも 1 件落ちることを確認。
変異なしで 463 passed / 0 failed、ビルド 0 warning。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXb7LwvtQ9p5CfCpKbnuM7

Copy link
Copy Markdown
Owner Author

CI 状況の報告: Vercel の 2 件は本 PR の失敗ではありません

失敗しているチェック

チェック 状態
Vercel – incident-insight ❌ failure
Vercel – incident-insight-jjt5 ❌ failure

本 PR のリポジトリ CI は全て緑

チェック 状態
build-and-test ✅ success
Docker image build & smoke test ✅ success
Vercel Preview Comments ✅ success

CLAUDE.md §14 が本リポジトリの検証として定めているのは .github/workflows/ の GitHub Actions(dotnet build + dotnet test)で、こちらは head 19ea388 で全て成功しています。ローカルでも dotnet build(0 Warning / 0 Error)と dotnet test(463 passed / 0 failed)を確認済みです。

本 PR の変更が原因ではないと判断した根拠

同じ 2 件が、本 PR と無関係な過去の PR でも同一に失敗しており、その PR はそのままマージされています。

本 PR の初回コミット d3cada9(コメントとテストの変更のみ)の時点でも同じ 2 件が失敗しており、コミット内容と無関係に毎回失敗しています。

また本リポジトリは ASP.NET Core 8 / EF Core の .NET アプリケーションで、Node のビルド成果物を持ちません。Vercel はこの構成をビルドできないため、この 2 つの Vercel プロジェクト連携はコミット内容によらず常に失敗する状態にあります。本 PR の差分(C# の定数参照化・コメント・xUnit のテスト)はデプロイ構成に一切触れていません。

対応方針

修正は行いません。 塞ぐには Vercel 側のプロジェクト連携を解除するか、リポジトリに Vercel 用のビルド設定を追加する必要があり、いずれもリポジトリ/Vercel アカウントの構成変更であってコードの変更ではありません。本 PR の目的(有効性評価の尺度の一元化)と無関係な変更を混ぜないため(CLAUDE.md §6「変更は最小スコープに保つ」)、ここでは手を入れず事実の報告に留めます。

再実行しても構成が変わらない以上同じ結果になるため、再実行も行っていません。

補足(本 PR とは別件の提案): この 2 件は毎回赤くなるため、「CI の赤」が本物の退行を意味しなくなっています。恒久対応としては Vercel 連携の解除が妥当と考えますが、アカウント側の操作になるためご判断ください。


Generated by Claude Code

@izumacha
izumacha merged commit 6740787 into main Aug 28, 2026
3 of 5 checks passed
@izumacha
izumacha deleted the claude/dreamy-brown-n0kypy branch August 28, 2026 08:55
izumacha pushed a commit that referenced this pull request Aug 30, 2026
PR #184 の 2 巡目レビュー指摘 3 件を反映する。

1. EnumLabels.AuditEntityJa が導出できない 4 つ目の写しだった
   AllowedEntityNames を宣言から導出するようにしたぶん、そのすぐ隣で使う日本語ラベルの
   変換表だけが手書きの写しとして残った。JapaneseAuditEntity は辞書に無いキーを元の値の
   まま返すフォールバックを持つため、監査対象を足してラベルを書き忘れても例外にならず、
   監査ログ画面の 3 箇所(ドロップダウン / 一覧の各行 / 詳細)に CLR の型名が英語のまま出る。
   ビルドも全テストも緑のまま通る。

   フォールバック自体は残す(監査対象から外したエンティティの過去行を表示するときに、
   例外で画面を落とすより元の値を出す方が安全)。代わりに AuditEntityLabelCoverageTests が
   「ラベル表は監査対象を全網羅する」ことを機械的に固定する。

2. fluent の HasMaxLength() が裸の数値の抜け道になっていた
   前コミットで上限の充足判定を EF のモデル(GetMaxLength())へ寄せたため、fluent で
   設定した上限も「上限あり」として通るようになった。ところが裸の数値を検出する
   FieldLengthsTests は CLR の [MaxLength] 属性しか見ていないので、「上限はある(緑)/
   その値は FieldLengths 由来ではない(誰も見ていない)」という状態が作れてしまう。
   実際 ApplicationDbContext の 20 / 50 が既にその状態だった。

   ——エスケープハッチを足したぶん既存の検出網が黙って狭くなるという、この PR 自身が
   塞いだのとまったく同じ形。属性側と同じ許容集合でモデル側も見る検査を足し、
   20 / 50 を FieldLengths.EnumCode / EnumCodeJapanese として名前付き定数にした
   (値は変えていないのでスキーマは不変。マイグレーション不要)。

3. PartitionStringColumns のコメントが実際の挙動を過大に述べていた
   「呼び出し側が同じ走査を 2 回しない」と書いていたが、2 つの薄い射影はそれぞれ独立に
   呼ぶので両方使えば 2 回走る。実際の挙動と、メモ化せず素直に再計算する判断の理由へ直した。

なお同レビューが挙げた EffectivenessScale の文言と EfCorePackageAlignmentTests の 2 件は
本 PR の差分ではなくマージ済みコミット(#182 / #181)由来のため、ここでは扱わない。

変異 2 通り(ラベル表から 1 件落とす / fluent の上限を裸の 48 へ戻す)で、それぞれ対応する
検査だけが赤になることを実測済み。
dotnet restore --locked-mode / build (警告 0) / test 484 件 / npm run typecheck すべて緑。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AAEwHDpPsNzmEnxHPmzKf5

This branch had an error being deployed

2 failed deployments
Preview – incident-insight-jjt5 — 19ea388f Deployed Aug 28, 2026 by vercel[bot]
Preview – incident-insight — 19ea388f Deployed Aug 28, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants