# 指示書AS — 完了率を先方の式に合わせる・清掃結果を表示名にする・予定外タスクを完了率から外す

**日付**: 2026-10-02 / **担当**: Cursor / **前提**: D-155（本書の根拠）/ D-151 / 指示書AQ / 指示書AR / `CLAUDE.md` §4.2・§4.3・§4.7・§6・§7・§9.4

★**渡す時期**: 指示書AR・指示書AT（4カラム UPDATE）と一緒に渡す★（D-155 項6）。
★**当てる順番: AR を先に終えてから AS**★（同じファイルのコメントを両方が直すため。§10 を参照）。

★§2 の未決は監督が決定済み（2026-10-02。D-156）★。

---

## 0. 何をするか

1. ★清掃完了率の出し方を、TeamWorks のサマリと同じ式にする★（D-155 項1。ユーザー判断「一旦B」）。
   - 1回ごとの完了率 ＝ `min(clean_area ÷ task_area × 100, 100)` を ★小数第1位まで切り捨て★
   - 複数回の完了率 ＝ 1回ごとの完了率の ★単純平均★（丸めない。判定は生値、表示だけ整数に丸める）
   - ★`clean_task_reports.clean_ratio` 列は完了率に使わない★
2. ★総清掃面積（F-08）の完了率と実施回数から、予定外のタスク（`clean_timetable_id` が NULL）を外す★。面積の合計には含める（D-155 項3）。
3. ★清掃結果（`clean_task_reports.status`）を数字ではなく表示名で出す★（D-155 項2）。判定には使わない。
4. 上の3つで古くなった説明（コメント・API 定義・fixtures の説明）を直す。

★**暫定の仕様である**★（「一旦」）。第11回で TeamWorks から訂正後の `clean_ratio` の式を受け取ったら見直す。
★だから「1回ごとの完了率を決める関数」は ★1つだけ★ にし、あとで `clean_ratio` 列に戻すときに ★その1関数を直せば済む★ 形にする。

---

## 1. 背景 — なぜ変えるのか

| いつ | 何が起きたか |
|---|---|
| 2026-09-30（D-151） | 完了率を「`clean_task_reports.clean_ratio` 列の単純平均」にした。面積比（Σ`clean_area` ÷ Σ`task_area`）は廃止 |
| 2026-10-02（第10回 N-3 の回答） | TeamWorks: ★`clean_ratio` 列は「計算式が間違っている。訂正したら回答する」★。★サマリ（`*_summaries.clean_ratio`）は「清掃1回ごとに `clean_area / task_area * 100`（100より大きい値は100、小数点第2位以下切捨て）を計算し、合算して平均」★。★`precheck = 0` の予定に紐づく行だけが対象で、`clean_timetable_id` が NULL の行は対象外★ |
| 2026-10-02（D-155） | ★当期（アプリがその場で集計する分）もサマリと同じ式にする★。式が違うと、★前期比（サマリ）と当期（アプリ）の差が、別の式どうしの差になる★ |

★**廃止したものと採用するものを取り違えないこと**★（いちばん間違えやすい）:

| | 中身 | 状態 |
|---|---|---|
| 面積比（D-151 で廃止） | ★期間の合計どうしを割る★ Σ`clean_area` ÷ Σ`task_area` | ★廃止のまま★。使わない |
| 面積で重み付けした平均 | `task_area` を重みにした平均 | ★使わない★ |
| ★1回ごとの面積比の単純平均（本書で採用）★ | ★1回ずつ割って、上限100・小数第1位まで切り捨て★、それを ★清掃1回＝1票★ で平均 | ★採用★ |

いまのコードには「面積から再計算しない」「面積比は `clean_ratio` と相関しない」という説明が多数ある。
★1回ごとの面積比は、本書で正しい計算になる★ので、★それらの説明は直す必要がある★（段階6）。
ただし「Σ面積 ÷ Σ面積は使わない」「面積で重み付けしない」という説明は ★今後も正しい★ので残す。

### 承知している弱点（変えないこと）

`clean_area` は同じ場所を複数回清掃した面積の総和（第10回 N-4）。ユーザー提供CSV 189行では ★68.3% の行が `clean_area > task_area`★ で、★これらは全部 100% になる★。★◎が多めに出る★。ユーザーはこれを承知で採用した（D-155 項1）。★上限100を外したり、別の補正を入れたりしないこと★。

---

## 2. ★監督が決めたこと（2026-10-02。D-156）★

### 【未決1】`task_area = 0` で、レポートの行はある清掃をどう扱うか

★本書の式では、`task_area = 0` の清掃は「1回ごとの完了率 = null」になる★（D-155 項1。分子にも分母にも入れない）。
ところが ★いまのコードは「完了率が null ⟺ 出発していない（未実施）」を前提にしている★
（`lib/db/clean-log-mapping.ts:186-190` の不変条件）。この前提で「未実施」を数えている箇所が3つある:

| 箇所 | いまの数え方 | `task_area = 0` の清掃がどうなるか |
|---|---|---|
| `lib/judge/rescue.ts:153`（要レスキュー R-1「未実施3日連続」） | `completion_rate === null` を未実施とする | ★出発したのに「未実施」に数えられる★ |
| `lib/db/report-mapping.ts:647`（`launchFailedDaysInRun()`。連続問題施設の「うち{m}日は出発失敗」） | 同上 | ★出発したのに「出発失敗の日」に数えられる★ |
| `lib/db/clean-log-mapping.ts:323-325`（`isProblemCompletion()`。連続日数） | null を「問題あり」とする | 問題日に数えられる（判定は × なので、これは整合する） |

詳細ログの1行は ★`departed = true`（✓）・清掃結果は表示名・完了率 `-`・判定 ×★ になる（`judgeGrade(null)` が × を返すため。`lib/judge/grade.ts:30`）。アドバイスは R1・R2・B1 のどれも出ない（`lib/judge/advice.ts:61-83`）。

**選択肢**:

| 案 | 内容 | 帰結 |
|---|---|---|
| ★A（推奨）★ | ★「未実施」は `departed` で数える★。`ConsecutiveInput`（`lib/db/clean-log-mapping.ts:328`）と `RescueRowInput`（`lib/judge/rescue.ts:68`）に `departed: boolean` を足し、R-1 と `launchFailedDaysInRun()` は `!departed` を見る | 意味どおりになる。★判定ロジックの入力が1つ増える★ので、表5 の期待値を先にテストで固定する |
| C（最小） | いまの数え方のまま。不変条件が崩れることだけ docstring に書く | 変更が小さい。★`task_area = 0` の清掃が R-1・出発失敗日に誤って数えられる★（頻度は不明。第11回で `task_area = 0` の扱いを確認中） |

★本書の段階3-4 は案A で書いてある★。★監督が案C を選んだら、段階3-4 の「案Aのみ」と書いた作業と表5 を飛ばす★。

> 監督の決定: ★**案A**★（2026-10-02）。理由: CLAUDE.md §4.7「未実施 ＝ 本清掃に対応するレポート行が無いこと」に合う。★レポート行がある清掃を出発失敗に数えると、現場に誤った連絡（R1・R-1）が飛ぶ★。段階3-4 と表5 はそのまま行うこと。

### 監督の決定（作成者が挙げた残りの論点）

| # | 論点 | 決定 |
|---|---|---|
| 2 | F-08 で期間指定が無いとき、完了率用の行を保持期間（2年）ぶん取ってくる | ★このまま採る★。ただし ★取った行数と所要時間をログに出す★（`judge` 系の既存イベントに `rate_rows` / `elapsed_ms` を足す）。実データで重ければ別途手当てする |
| 3 | 事前チェックの予定・存在しない予定に紐づく行（表4 F3/F4） | ★面積の合計には含め、完了率と実施回数からは外す（表4 のとおり）★ |
| 7 | F-08 で「面積はあるが実施回数0」の施設が出る | ★画面の注記を1行足す★: 「清掃面積には予定外の清掃を含みます。完了率と実施回数は予定された本清掃だけです」（EN: "Area includes unscheduled runs. Completion rate and run count cover scheduled main runs only."。英訳は暫定） |
| 8 | EN 表示で出発失敗のセルが "Launch failed" になる | ★それでよい★（i18n を通すのが正しい） |
| 6 | 面積が負の値 | 特別扱いしない（本書のとおり） |
| 4 / 5 / 9 | `clean_timetable_histories` の扱い / CLAUDE.md §4.2 の分母の書き方 / `docs/code-map.md` | 監督が扱う（第11回・CLAUDE.md・別作業）。★Cursor は触らない★ |

---

## 3. 決定（Cursor は従うこと）

| # | 決定 | 理由 |
|---|---|---|
| D1 | ★1回ごとの完了率は `lib/judge/kpi.ts` の新しい関数 `runCompletionRate()` だけが決める★。シグネチャ: `runCompletionRate(report: { clean_area: number; task_area: number }): number \| null` | ★暫定の式なので、戻すときに1関数で済むようにする★（`CLAUDE.md` §4.2）。判定層（`lib/judge/`）は DB 層を import しないので、引数は構造的な型にする（`CleanRatioTally` と同じ作法。`kpi.ts:46-47`） |
| D2 | ★上限 `100` と小数の桁数 `1` は `lib/config/index.ts` に定数として置く★（例: `RUN_COMPLETION_RATE = { cap: 100, decimals: 1 } as const`。出所 = 第10回 N-3 を docstring に書く） | `CLAUDE.md` §7（数値の直書き禁止）の趣旨に合わせる。式の部品が1箇所に集まる |
| D3 | ★切り捨ての前に浮動小数の誤差を吸収する★。方法は次の1つに決める（§5-1）: `Math.floor(Math.round(raw * 10 ** (decimals + 5)) / 10 ** 5) / 10 ** decimals`（`decimals = 1` なら `Math.floor(Math.round(raw * 1e6) / 1e5) / 10`） | `0.57 / 1 * 100` は `56.99999999999999` になり、そのまま切り捨てると 56.9 になる（表1 R6）。★1e-6 ポイント未満の差は誤差とみなす★ |
| D4 | ★`task_area === 0` なら `null`★（分子にも分母にも入れない） | D-155 項1。★暫定・第11回で確認★。`task_area` は NOT NULL（`prisma/schema.prisma:525`）なので null の扱いは要らない |
| D5 | ★平均の取り方は変えない★ — `meanCleanRatio()`（`kpi.ts:88-91`）をそのまま使う。★平均は丸めない★。★判定は生値、表示だけ `displayRate()`（`Math.round`）★ | `CLAUDE.md` §4.3。切り捨ては「1回ごとの値」だけ |
| D6 | ★`clean_ratio` 列は完了率に使わない。ただし型・`select`・fixtures からは消さない★（読まないだけ） | ★戻すときに `runCompletionRate()` の1関数を直せば済むようにするため★。呼び出し側は ★行（`clean_ratio` も持っている）をそのまま渡し★、呼び出し側で `clean_area` / `task_area` だけに分解しないこと |
| D7 | ★内部の名前（`meanCleanRatio` / `CleanRatioTally` / `cleanRatioSum` / `cleanRatioCount` / `clean_ratio_sum` / `clean_ratio_count`）は変えない★。docstring で「1回ごとの完了率の合計・件数」と意味を書き直す | 名前を変えると16ファイル・約200行の機械的な差分が中身の変更に混ざり、レビュー（daichi。D-154）で読めなくなる。★暫定の式なので、戻すときにも名前を戻さずに済む★ |
| D8 | ★`judge.areas` ログの分子・分母の項目名は、最終的に `overall_completion_rate_sum` / `overall_completion_rate_count` にする★（§10） | 中身は「`items[].completion_rate` の合計と件数」。式の名前（`clean_ratio`）を項目名に入れると、式を戻したり変えたりするたびに名前と中身がずれる |
| D9 | ★総清掃面積（F-08）の完了率と実施回数は、「`clean_timetable_id` が NULL でなく、その予定が本清掃（`precheck = 0`）である行」だけで数える★。★面積の合計は全行（予定外を含む）★ | D-155 項3。サマリと同じ（N-3）。他の画面は既に本清掃の id で引いているので変更不要（§5-4） |
| D10 | ★清掃結果の表示名は `lib/config/index.ts` に表で置き、`lib/i18n.ts` で英訳する★。★表に無い値は生値の数字のまま★（「その他」に潰さない）。★API の `task_status` は数字のまま★ | D-155 項2。`CLEAN_ERROR_LABELS`（`lib/config/index.ts:298-316`）と同じ形。英語は仮（ユーザーが後でまとめて見直す） |
| D11 | ★CSV の清掃結果は日本語の表示名★ | CSV は見出し（`lib/clean-logs/csv.ts:63-79`）も「出発／未出発」（`:103`）も日本語固定で、言語を受け取っていない。それに合わせる |
| D12 | ★清掃結果（`status`）は判定に使わない★ | D-155 項2。中断・異常終了でも完了率で ◎○△× を出す。`CLAUDE.md` §4.4 の「中断で返る＝失敗」は実データを見て見直す（本書の範囲外） |

---

## 4. ★期待値表（正）★

★**表1・表2 は監督が確定した正である。Cursor は値を変えないこと**★（`CLAUDE.md` §0-2・§6-2・§9.4）。
★**段階1 で、実装より先にこの表をテストにする**★。表とテストが食い違ったら、テストを表に合わせる。実装の都合で表を直さない。

### 表1 — 1回ごとの完了率 `runCompletionRate`

| # | clean_area | task_area | 期待値 | 確かめること |
|---|---|---|---|---|
| R1 | 50 | 100 | 50.0 | 基本 |
| R2 | 100 | 100 | 100.0 | ちょうど100 |
| R3 | 150 | 100 | 100.0 | 上限で100に切る |
| R4 | 89.99 | 100 | 89.9 | 四捨五入ではなく切り捨て |
| R5 | 89.95 | 100 | 89.9 | 切り捨て（四捨五入なら90.0） |
| R6 | 0.57 | 1 | 57.0 | 浮動小数の誤差（56.99999…）で56.9にしない |
| R7 | 0.7 | 1 | 70.0 | 同上 |
| R8 | 0 | 100 | 0.0 | 清掃面積0 |
| R9 | 10 | 0 | null | 対象面積0は分子・分母の外 |
| R10 | 1377.39502 | 1377.39502 | 100.0 | 実データの桁 |
| R11 | 898.892509 | 1377.39502 | 65.2 | 65.26…を切り捨て |

### 表2 — 平均（判定は生値の平均で行う。表示は Math.round）

| # | 1回ごとの値 | 平均（生値） | 表示 | 判定 |
|---|---|---|---|---|
| M1 | [100.0, 80.0] | 90.0 | 90% | ◎ |
| M2 | [89.9, 90.0] | 89.95 | 90% | ○（表示は90だが判定は○。§4.3） |
| M3 | [100.0, 40.0] | 70.0 | 70% | ○ |
| M4 | [70.0, 69.9] | 69.95 | 70% | △ |
| M5 | [10.0] | 10.0 | 10% | △ |
| M6 | [9.9] | 9.9 | 10% | × |
| M7 | [null, 50.0] | 50.0 | 50% | △（nullは数えない） |
| M8 | [null] / [] | null | — | 未実施と同じ扱い（×・出発失敗の表示は既存どおり） |
| M9 | [150/100→100.0, 150/100→100.0, 0/100→0.0] | 66.666… | 67% | △ |

**テストの書き方の注意**:
- 平均は ★`meanCleanRatio()` に Σ と件数を渡して出す★（テストの中で自前の割り算を書かない）。null は Σ にも件数にも入れない
- 「表示」は `displayRate()`（`lib/judge/grade.ts:43-46`）の戻り。★M8 の「—」は `displayRate(null)` の `"-"`★
- 「判定」は `judgeGrade(平均の生値)`。★M8 は `judgeGrade(null)` = `×`★
- M9 は ★面積から `runCompletionRate()` を通して1回ごとの値を作ってから★平均する（表1 と表2 をつなぐケース）
- 生値の比較は `toBe`（M2 の 89.95、M4 の 69.95 は JS でそのまま出る。2026-10-02 に node で確認済み）。M9 だけ `toBeCloseTo(200 / 3, 10)`

### 表3 — 清掃結果の表示名（`formatCleanTaskStatus`。出所: 第10回 N-2 の原文。D-155 項2）

| 入力（`task_status`） | 日本語（画面・CSV） | 英語（画面。★仮★） |
|---|---|---|
| `null`（レポートの行が無い） | 出発失敗 | Launch failed（既存キー `lib/i18n.ts:208`） |
| 0 | 清掃開始前 | Not started |
| 1 | 清掃中 | Cleaning |
| 2 | 清掃中断 | Interrupted |
| 3 | 清掃キャンセル | Cancelled |
| 4 | 清掃正常終了 | Completed |
| 5 | 清掃異常終了 | Ended abnormally |
| 6（CSV実測にある・意味は未受領） | `6` | `6` |
| 255 | `255` | `255` |

★日本語は N-2 の原文から1文字も変えないこと★（言い換え禁止。`lib/config/index.ts:291-295` と同じ約束）。
★「その他」「不明」に潰さないこと★（D-089 項3 / D-104 項2）。

### 表4 — 総清掃面積（F-08）の施設1件ぶんの集計（本書で定めた。表1・D9 から導いたもの）

純粋関数（§段階4 の `cleanAreaStatsFromRows()`）に対するテスト。施設はすべて同じ1施設。

| # | その施設のレポート行 | `clean_area` 合計 | `task_area` 合計 | `executed_count` | `completion_rate`（`aggregateCleanArea()` の結果） |
|---|---|---|---|---|---|
| F1 | 予定外（`clean_timetable_id` NULL）100/100 の1行だけ | 100 | 100 | 0 | null |
| F2 | 本清掃 50/100 ＋ 予定外 100/100 | 150 | 200 | 1 | 50.0 |
| F3 | 事前チェック（`precheck ≠ 0`）の予定に紐づく 80/100 の1行だけ | 80 | 100 | 0 | null |
| F4 | 予定が見つからない id（dangling）80/100 の1行だけ | 80 | 100 | 0 | null |
| F5 | 本清掃 10/0 の1行だけ（`task_area = 0`） | 10 | 0 | 1 | null |
| F6 | 本清掃 150/100 ＋ 本清掃 0/100 | 150 | 200 | 2 | 50.0（(100.0 + 0.0) ÷ 2） |
| F7 | 本清掃の行の `clean_ratio` 列が 12.3、面積は 50/100 | 50 | 100 | 1 | ★50.0★（★`clean_ratio` 列を読んでいないことの確認★） |

### 表5 — 未実施の数え方（★【未決1】が案A のときだけ★）

| # | 清掃 | `departed` | `completion_rate` | R-1 の未実施日 | 出発失敗の日（`launchFailedDaysInRun`） | 連続日数の問題日（`isProblemCompletion`） |
|---|---|---|---|---|---|---|
| U1 | レポートの行が無い | false | null | 数える | 数える | 問題 |
| U2 | ★行がある・`task_area = 0`★ | ★true★ | ★null★ | ★数えない★ | ★数えない★ | 問題（判定 × と同じ） |
| U3 | 行がある・50/100 | true | 50.0 | 数えない | 数えない | 問題（△） |
| U4 | 行がある・100/100 | true | 100.0 | 数えない | 数えない | 問題なし |
| U5 | 同じ施設・同じ日に U1 と U4 | — | — | その日を1日数える（OR） | その日を1日数える（OR） | 問題 |

---

## 5. ★落とし穴（全段階で読むこと）★

| # | 罠 | どう避けるか |
|---|---|---|
| 5-1 | ★浮動小数の誤差で切り捨てが1段下がる★（`0.57/1*100 = 56.999…` → 56.9） | D3 の式を使う。★`Math.floor(raw * 10) / 10` と書かない★。★`toFixed()` は四捨五入なので使わない★。表1 R6・R7 で固定する |
| 5-2 | ★平均した値を切り捨てる・丸める★ | 切り捨ては1回ごとの値だけ（D5）。★`meanCleanRatio()` の戻りに手を加えない★。M2・M4 で固定する |
| 5-3 | ★`periodOfSummaryRow()`（`lib/db/report-mapping.ts:99`）に触る★ | ★触らない★。年次行が月次に化けて500になる（`CLAUDE.md` §4.7） |
| 5-4 | ★サマリの `clean_ratio`（`*_summaries`）まで計算し直す★ | ★サマリはそのまま読む★（`kpiFromSummaryRatios()`。`report-mapping.ts:230-240`）。★サマリの `clean_ratio` は既に本書と同じ式★（N-3）。`SummaryRatios.clean_ratio`（`:221`）は変えない |
| 5-5 | ★生SQL（`$queryRaw` / `$executeRaw`）で F-08 を集計する★ | ★禁止★（eslint が止める。`CLAUDE.md` §3）。上限・切り捨ては Prisma の `groupBy` で書けないので、★行を取り寄せて JS で計算する★（段階4） |
| 5-6 | ★閾値・式の数字を直書きする★ | 90/70/10 は `GRADE_THRESHOLDS`、上限100と桁数は D2 の定数から読む |
| 5-7 | ★判定のルールを変える★ | ★変えるのは「完了率の材料」だけ★。`judgeGrade()`・L1〜L3・アドバイスの条件は変えない（【未決1】案A の「未実施の数え方」だけは例外。表5） |
| 5-8 | ★`clean_area` / `task_area` を Decimal として扱う★ | ★Prisma の型は `Float`（`number`）★（`prisma/schema.prisma:525-526`。D-094 で `double(12,6)` に変わった）。`Number()` 変換も `Decimal` の import も要らない |
| 5-9 | ★呼び出し側で `report.clean_area / report.task_area` を書く★ | ★必ず `runCompletionRate()` を通す★。`grep -rn "clean_area /\|clean_area\s*\/" lib app` で0件にする（`runCompletionRate()` の中だけ） |
| 5-10 | ★`clean_timetable_id` が NULL の行を F-08 の番人と同じ扱いにし忘れる★ | 番人（`countDuplicatedTimetables()`。`lib/db/clean-area-source.ts:292-305`）は ★変えない★。完了率用の取り寄せも ★同じ `{ not: null }` で除く★（NULL は1グループにまとまる。同 `:272-283`） |
| 5-11 | ★`include` でレポート→予定を引く★ | ★使わない★（`clean_timetable_id` に FK が無い。`IN` が膨らむ。`clean-area-source.ts:56-62`）。予定は ★id を集めて `chunkForInClause()`（`lib/db/chunked.ts`）で分けて★別クエリで引く |
| 5-12 | ★作業ツリーの `CleanLogTable.tsx` が「変更あり（M）」に見える★ | 2026-10-02 時点で ★`git diff` の中身は0行★（改行コードだけの差と思われる）。★着手前に `git diff components/features/detail-log/CleanLogTable.tsx` を見て、自分の変更以外を混ぜないこと★ |
| 5-13 | ★`git add -A`★ | ★禁止★（`CLAUDE.md` §7）。ファイルパスを明示する |

---

## 6. 段階ごとの作業

★各段階の終わりに `npx tsc --noEmit -p .` と `npm test` を通すこと★。
★段階3 で fixtures を使うテストが落ちたら、段階6 の fixtures の作業を先にやってよい★（順番を入れ替えてよいのは fixtures だけ）。
行番号は 2026-10-02 の develop（`ef34043`）で確かめたもの。★AR を先に当てるとずれる★ので、記述（関数名・文）で探すこと。

### 段階1 — 期待値表をテストにする（実装より先）

1. ★新しいテストファイル `lib/judge/__tests__/run-completion-rate.test.ts`★ を作る:
   - 表1 の11行を `it.each` で。期待値は `toBe`（R9 は `toBeNull()`）
   - 表2 の9行（M8 は `[null]` と `[]` の2件に分ける）。§4 の「テストの書き方の注意」に従う
   - ★ファイル冒頭のコメントに表1・表2 をそのまま貼り、「本書 §4 が正。変えないこと」と書く★
2. 表3 のテストを `lib/format/__tests__/clean-result.test.ts`（既存）に足す。英語は `translate("en", formatCleanTaskStatus(x))` で確かめる
3. 表4 のテストは段階4、表5 のテストは段階3-4 で書く（関数がまだ無いため）。★ただし段階1 の時点で `it.todo` で行だけ並べておく★（書き忘れ防止）
4. ★この時点ではテストが落ちる（`runCompletionRate` が無い）のが正しい★。コミットはテストだけで1つ

### 段階2 — `runCompletionRate()` を作る（`lib/judge/kpi.ts`）

1. `lib/config/index.ts` に D2 の定数を足す（`GRADE_THRESHOLDS`（`:18`）の近く）。docstring に出所（第10回 N-3 の原文「100より大きい値は100、小数点第2位以下切捨て」。`docs/handoff/TeamWorks回答_第10回_20261002.md:73`）と「暫定（D-155）」を書く
2. `lib/judge/kpi.ts` に `runCompletionRate()` を足す。中身は D3・D4 の通り:
   - `task_area === 0` → `null`
   - `raw = clean_area / task_area * 100` → `Math.min(raw, cap)` → D3 の切り捨て
   - docstring に書くこと: ★「1回ごとの完了率を決めるのはこの関数だけ」★／★暫定（D-155）。`clean_ratio` 列に戻すときはこの関数の中身だけを直す★／D3 の誤差吸収の理由（R6）／承知している弱点（§1）／★Σ面積 ÷ Σ面積（廃止）とは別物★
3. `lib/judge/kpi.ts` の既存の説明を直す:
   - 冒頭 `:4-21` — `:9` の式を「Σ 1回ごとの完了率 ÷ 件数（1回ごとの完了率 = `runCompletionRate()`）」に。`:12-14` は AR 追補2 で一部消える。残りを「完了率は D-155 で TeamWorks のサマリと同じ式にした（暫定）」に書き直す
   - `CleanRatioTally`（`:39-54`）— `cleanRatioSum` は「★1回ごとの完了率（`runCompletionRate()` の戻り）の合計★」、`cleanRatioCount` は「その件数。★未実施と `task_area = 0` の清掃は数えない★」（D7。名前は変えない）
   - `meanCleanRatio()` の docstring（`:56-87`）— 「`clean_task_reports.clean_ratio` の単純平均」を「1回ごとの完了率の単純平均」に。`:80-83` の「★その2つを割って『完了率』と呼ばないこと★（実測で `clean_ratio` と相関しない）」は ★いまの仕様と逆★ なので、「Σ`clean_area` ÷ Σ`task_area`（期間の合計どうし）で割らないこと。1回ごとに割るのは `runCompletionRate()` の仕事」に直す。`:65-72`（単純平均にする理由）は残す
   - ★`meanCleanRatio()` の中身（`:88-91`）は変えない★
4. 段階1 の表1・表2 のテストが通ることを確かめる

### 段階3 — 完了率を出しているすべての経路を `runCompletionRate()` に通す

★完了率を出している経路は次の表ですべて★（2026-10-02 に `grep -rn "clean_ratio" app lib components scripts` の全件を分類して確かめた。分類は §8）。

| # | 経路 | 変えるもの |
|---|---|---|
| 3-1 | 清掃詳細ログの1行・アラート・エリア別完了率の行・連続日数・要レスキュー（すべて `completionRateFromReport()` 経由） | `lib/db/clean-log-mapping.ts` |
| 3-2 | レポートKPI（当期） | `lib/db/report-mapping.ts` |
| 3-3 | 総清掃面積 F-08 | 段階4 |
| 3-4 | 要レスキュー R-1 と出発失敗の日（【未決1】案A のみ） | `lib/db/clean-log-mapping.ts` / `lib/db/consecutive-source.ts` / `lib/judge/rescue.ts` / `lib/db/rescue-mapping.ts` / `lib/db/report-mapping.ts` |

#### 3-1. `lib/db/clean-log-mapping.ts`

- `CleanTaskReportLike`（`:91-107`）に `clean_area: number` / `task_area: number` を足す。★`clean_ratio` は残す★（D6。docstring に「完了率には使っていない。`runCompletionRate()` を `clean_ratio` 列に戻すときのために残している」と書く）
  - docstring `:101-102`「★`clean_area` / `task_area` を足さないこと★」は ★いまの仕様と逆★ なので書き直す
  - `:94` の `prisma/schema.prisma:534` は ★古い★（いまは `:531`）。直す
- `completionRateFromReport()`（`:194-201`）— 行があれば ★`return runCompletionRate(report)`★。行が無ければ `null`（いまと同じ）
  - docstring（`:163-193`）を書き直す。表を「行が無い → null／行があり `task_area = 0` → null／それ以外 → `runCompletionRate()`」の3行にする。「面積から再計算しないこと」（`:171-179`）は ★逆★ なので消し、D-155 の経緯（D-151 の `clean_ratio` 列 → D-155 で1回ごとの面積比。暫定）を書く
  - ★不変条件（`:186-190`）を書き直す★: 「`departed === false` ⟹ `completion_rate === null`。★逆は成り立たない★（`task_area = 0` の清掃は `departed === true` で `completion_rate === null`）」。【未決1】の決定（案A／案C）と、その帰結をここに書く
  - 「0%で実施」と「未実施」は別物（`:181-184`）は ★残す★（`clean_area = 0`、`task_area > 0` なら 0.0。表1 R8）
- テスト `lib/db/__tests__/clean-log-mapping.test.ts`: 行の形 `{ clean_ratio: … }` だけで作っているもの（`:173`・`:255-256`・`:282`・`:289` ほか）に面積を足す。★`clean_ratio` 列と面積比が食い違う行★（例: `clean_ratio: 12.3, clean_area: 50, task_area: 100` → 50.0）を1件以上入れ、★列を読んでいないこと★を固定する。`task_area = 0` → null も足す

#### 3-2. `lib/db/report-mapping.ts`（レポートKPI の当期）

- `aggregateCleanStatsByFacility()` の `:482-484` / `:492-493`:
  ```ts
  const runRate = executed && report !== undefined ? runCompletionRate(report) : null;
  // …
  cleanRatioSum: bucket.cleanRatioSum + (runRate ?? 0),
  cleanRatioCount: bucket.cleanRatioCount + (runRate === null ? 0 : 1),
  ```
  （`runCompletionRate` は `@/lib/judge/kpi` から import。★`lib/db/clean-log-mapping.ts` は import しない★ — このファイルは未実施の判定を引数で受け取る作りで、依存を増やさない。`:442-448`）
- ★`executed` と `planned` の数え方は変えない★（`task_area = 0` の清掃も実施1回に数える。完了率の件数にだけ入らない）
- docstring を直す: `CleanStatsBucket.cleanRatioSum` / `cleanRatioCount`（`:282-295`）、`CleanStatsReportLike`（`:391-411`。★`clean_ratio` は残す★。D6）、`aggregateCleanStatsByFacility()`（`:428-437`）、`kpiFromCleanStats()`（`:504-505`）
- ★`kpiFromSummaryRatios()`（`:225-240`）と `SummaryRatios`（`:217-223`）は変えない★（5-4）
- `:265-280`（`errorCount` の説明）は完了率と無関係。★触らない★
- テスト `lib/db/__tests__/report-mapping.test.ts`: `clean_ratio` だけで行を作っているものに面積を足す。`clean_ratio` 列と面積比が食い違う行で「面積比のほうが使われる」ことを1件固定する。`task_area = 0` の行は `executed` に入り完了率の件数に入らないことを1件固定する

#### 3-3. 経路の確認だけ（コードの中身は変えない。コメントだけ直す）

`completionRateFromReport()` を通っているので、3-1 で自動的に新しい式になる。★行に面積の列が載っているかだけ確かめた★:

| 経路 | どこで行を読むか | 面積の列 |
|---|---|---|
| 清掃詳細ログ | `lib/db/index.ts:802-809`（`findMany`、`select` なし＝全列） | 載っている |
| エリア別完了率 | `lib/db/index.ts:1076-1082`（同上） | 載っている |
| アラート | `lib/db/index.ts:1243-1249`（同上。完了率は出さない） | — |
| 連続日数・要レスキュー | `lib/db/consecutive-source.ts:370-392`（`select` あり） | ★`clean_area` / `task_area` は既に `select` にある★（`:387-388`） |

- `lib/db/consecutive-source.ts`: ★`select` は変えない★（`clean_ratio` も残す。D6。テストが `select` のキーを完全一致で見ている）。コメントを直す: `:34-37` / `:72-79` / `:130-132` / `:249-252` / `:283-288` / `:382-386`。要点は「★面積は R-3 の重み付けではなく、`runCompletionRate()` で1回ごとの完了率を出すために読んでいる★（`:265-277` の『列を増やしてよい条件』の条件3 を満たし直した）」
- `lib/db/clean-log-mapping.ts` の `ConsecutiveInput.clean_area` / `task_area`（`:343-362`）: docstring の「`completion_rate` を面積から再計算しないこと」（`:351-354`）は ★逆★。「`completion_rate` は `runCompletionRate()` で作ったもの。★ここで割り直さないこと★（規則が2箇所に散る）」に直す
- `lib/judge/rescue.ts`: 中身は変えない（`completion_rate` を受け取るだけ）。コメントを直す: `:20-23` / `:50` / `:72` / `:78-84` / `:113-118` / `:139-142`。★「面積加重にしないこと」は残す★（今も正しい）
- `lib/db/rescue-mapping.ts:39-50`: 「R-3 は `clean_ratio` の単純平均」を「1回ごとの完了率の単純平均」に。★「`completion_rate` から面積を捏造しないこと」（`:49`）は残す★
- `lib/clean-logs/areas.ts:85-113` / `:129-137`: 中身は変えない（AR 段階4 で分子・分母の数え方が整理される）。表（`:89-92`）の「`clean_task_reports.clean_ratio` の生値」を「1回ごとの完了率（`runCompletionRate()`）」に。`:94-98` の経緯は残す（AR §2）
- `lib/db/index.ts`: `:541`（R-3 の説明）/ `:695`（「`clean_ratio` から `judgeGrade()`」→「完了率から」）/ `:931-933`（「`report ? report.clean_ratio : null` の形に戻さないこと」は ★今も正しい★。不変条件の書き方だけ 3-1 に合わせる）/ `:1020-1026`（AR 段階1-5 で直る。AS では「行の完了率は `runCompletionRate()`」に合わせるだけ）/ `:1107-1112`
- `lib/types/index.ts` の docstring: `:109-110` / `:247-252` / `:296-305` / `:312-319` / `:344` / `:1092-1096`

#### 3-4. 未実施を `departed` で数える（★【未決1】が案A のときだけ★）

1. ★先に表5 をテストにする★（`lib/judge/__tests__/rescue.test.ts` に U1〜U5、`lib/db/__tests__/report-mapping.test.ts` に `launchFailedDaysInRun()` の U1〜U5、`lib/db/__tests__/consecutive-source.test.ts` に `departed` の詰め方）
2. `ConsecutiveInput`（`lib/db/clean-log-mapping.ts:328`）に `departed: boolean` を足す（必須。optional にしない）
   - `consecutiveInputsFromRows()`（`lib/db/consecutive-source.ts:242-256`）: `departed: !notExecutedFromReport(report)`
   - `rescueInputsFromCleanLogs()`（`lib/db/rescue-mapping.ts:51-68`）: `departed: log.departed`
3. `RescueRowInput`（`lib/judge/rescue.ts:68-76`）に `departed: boolean` を足す。`foldRescueDays()` の `:153` を `const notExecuted = !row.departed;` に。★`completion_rate` の Σ と件数は null を数えないまま★（`:154-155`）
4. `launchFailedDaysInRun()`（`lib/db/report-mapping.ts:647`）を `const isFailed = !row.departed;` に。docstring `:629-630` の不変条件の記述を直す
5. ★`isProblemCompletion()`（`lib/db/clean-log-mapping.ts:323-325`）は変えない★（表5 U2 のとおり、null は問題日のまま）
6. ★`lib/judge/rescue.ts:16` の表（「R-1 … `completion_rate === null` が3スケジュール日以上連続」）を「`departed === false`」に直す★

### 段階4 — 総清掃面積（F-08）を1回ごとの完了率にし、予定外を完了率・実施回数から外す

いまは `groupBy` + `_sum` で施設ごとに `clean_ratio` を合計している（`lib/db/clean-area-source.ts:249-263`）。
★上限100・切り捨ては `groupBy` で書けず、生SQL は禁止（5-5）★なので、★完了率と実施回数の材料は行を取り寄せて JS で作る★。
★面積の合計は今までどおり `groupBy` で作る★（予定外を含む全行。行を運ばない）。

#### 4-1. `lib/db/clean-area-source.ts`

1. `CleanAreaFacilityGroupRow`（`:73-77`）と `groupBy` の引数（`:102-111` / `:253-261`）から ★`clean_ratio` の `_sum` と `_count` を外す★（使わなくなる）。面積の `_sum` は残す
2. 完了率用の行の型を足す: `{ facility_id: bigint; clean_timetable_id: bigint | null; clean_area: number; task_area: number; clean_ratio: number }`（★`clean_ratio` は読まないが `select` に入れる。D6★）
3. `CleanAreaStatsSourcePrisma`（`:102-111`）に次の2つを足す（テストからスタブを渡せる最小の形。既存の作法どおり）:
   - `cleanTaskReport.findMany({ where: CleanAreaWhere & { clean_timetable_id: { not: null } }, select: { facility_id, clean_timetable_id, clean_area, task_area, clean_ratio } })`
     — ★`where` は集計・番人と同じものに `{ not: null }` を足しただけ★（`countDuplicatedTimetables()` の `:299` と同じ形）
   - `cleanTimetable.findMany({ where: { id: { in: bigint[] }, precheck: number }, select: { id: true } })`
     — ★`precheck` は `CLEAN_TIMETABLE_PRECHECK.main`（`lib/config/index.ts:226-229`）。`0` を直書きしない★
4. ★純粋関数 `cleanAreaStatsFromRows(groupRows, reports, mainTimetableIds: ReadonlySet<string>)` を足す★（`cleanAreaStatsFromGroupRows()`（`:219-232`）を置き換える）:
   - 面積の合計は `groupRows` から（`_sum` の null は 0。いまと同じ）
   - `reports` のうち ★`clean_timetable_id` が null でなく、`mainTimetableIds` に入っているものだけ★（★「本清掃の集合に入っている」という肯定条件で数える。`errorCountByMainTimetable()`（`report-mapping.ts:377-389`）と同じ考え方★。dangling・事前チェックの行はこれで自然に落ちる）:
     - `executed_count += 1`
     - `const rate = runCompletionRate(report)`。null でなければ `clean_ratio_sum += rate` / `clean_ratio_count += 1`
   - ★`groupRows` に無い施設の行が `reports` に来たら例外を投げる★（同じ `where` を使っていれば起きない。起きたら `where` の食い違いなので、黙って足さない）
   - `facility_id` は `Number()` に通す（`:197-199` の罠。いまと同じ）
   - 戻りの型 `CleanAreaFacilityStats` は変えない（`clean_ratio_sum` は `number | null` のままでよいが、常に数値を入れる）
5. `loadCleanAreaFacilityStats(prisma, where)`（`:249-263`）— ★関数名とシグネチャは変えない★（`:242`）。中で:
   1. `groupBy`（面積）と `findMany`（完了率用の行）を `Promise.all` で
   2. 行の `clean_timetable_id` を重複なしで集め、`chunkForInClause()` で分けて `cleanTimetable.findMany`（★塊が0個ならクエリを撃たない★。`lib/db/chunked.ts:12-15`）
   3. 戻った id を `toString()` して `Set` にし、4. の純粋関数に渡す
6. docstring を直す: ファイル冒頭 `:17-26`（「完了率に面積を使わない」→「1回ごとの完了率に面積を使う。Σ面積 ÷ Σ面積 は使わない」）、`:194-218`（`executed_count` の意味が「本清掃に紐づくレポートの行数」に変わった。★予定外を含まない★）、`:234-247`（★「`groupBy` を使う（行を運ばない）」は面積だけになった。完了率用の行は運ぶ★。理由: 上限・切り捨てが `groupBy` で書けず、生SQL は禁止）
7. `countDuplicatedTimetables()`（`:265-305`）は ★変えない★。`:285-287`「除いた NULL 行の面積は集計側では足されている」は今も正しい

#### 4-2. `lib/db/index.ts` の `cleanArea()`（`:1782-1861`）

- 呼び出し（`:1846-1854`）は変えなくてよい（`loadCleanAreaFacilityStats()` の中で済む）
- docstring `:1786-1794` を直す: ★「行を運ばない」「行を取り寄せてJSで足すと期間を広げた瞬間に破綻します」は、完了率用の取り寄せには当てはまらなくなった★。★なぜ運ぶことにしたか（上限・切り捨て・生SQL 禁止）★と、★運ぶのは `clean_timetable_id` が NULL でない行の5列だけ★であることを書く。★期間を指定しないと全期間（保持2年）を運ぶ★ことも書く（性能は §11 で監督が判断する）

#### 4-3. fixtures 版（`lib/db/fixture-clean-area.ts:49-88`）

- `:71-74` / `:83-84` の `clean_ratio_sum` に `row.completion_rate` を足している所を、★`runCompletionRate(row)` を通した値★にする（null なら Σ にも件数にも足さない）。`executed_count` は今のまま（`completion_rate !== null` の行＝レポートがある行を数える）
- ★fixtures（`lib/fixtures/areas.ts`）には予定外の行が無い★ことを docstring に書く（予定外を外す規則は 4-1 の純粋関数のテスト＝表4 で確かめる）
- ★`USE_FIXTURES` の true/false で数字が変わらないこと★は、段階6 で fixtures の面積を作り直した後に `lib/db/__tests__/fixture-clean-area.test.ts` で確かめる

#### 4-4. 型・API 定義

- `lib/types/index.ts` の `CleanAreaFacilityStats`（`:1202-1242`）・`CleanAreaRow`（`:1287-1303`）・`CleanAreaTotal`（`:1316-1322`）の docstring: `executed_count` は「本清掃に紐づくレポートの行数（予定外を含まない）」、`clean_area` / `task_area` は「予定外を含む全行の合計」、`completion_rate` は「1回ごとの完了率の単純平均」
- `d-watch-api.yaml`: `:876-886`（`GET /clean-area` の説明）/ `:2002-2004` / `:2016-2023` / `:2024-2027` / `:2082-2085` / `:2086-2089`。★面積の合計と実施回数・完了率で母集団が違う★ことを明記する（面積は予定外を含み、回数・完了率は含まない）

#### 4-5. テスト

- `lib/db/__tests__/clean-area-source.test.ts`: 表4 の F1〜F7 を純粋関数 `cleanAreaStatsFromRows()` に対して書く。スタブで `loadCleanAreaFacilityStats()` を通し、★`findMany` の `where` に `clean_timetable_id: { not: null }` が付くこと★、★`cleanTimetable.findMany` の `where` に `precheck: CLEAN_TIMETABLE_PRECHECK.main` が付くこと★、★行が0件なら `cleanTimetable.findMany` を呼ばないこと★を固定する
- `lib/clean-area/__tests__/aggregate.test.ts` / `app/api/clean-area/__tests__/route.test.ts` は形が変わらないので、落ちたものだけ直す

### 段階5 — 清掃結果を表示名にする

1. `lib/config/index.ts` の `cleanErrorLabel()`（`:314-316`）の後ろに足す:
   ```ts
   export const CLEAN_TASK_STATUS = {
     notStarted: 0, cleaning: 1, interrupted: 2, cancelled: 3, completed: 4, endedAbnormally: 5,
   } as const;
   export const CLEAN_TASK_STATUS_LABELS: Readonly<Record<number, string>> = { /* 表3 の日本語 */ };
   export function cleanTaskStatusLabel(status: number): string {
     return CLEAN_TASK_STATUS_LABELS[status] ?? String(status);
   }
   ```
   docstring: 出所（第10回 N-2 原文。`docs/handoff/TeamWorks回答_第10回_20261002.md:63`）／★原文から1文字も変えない★／★表に無い値は生値（CSV実測の 6 など。意味は未受領・第11回で確認）★／★判定には使わない（D-155 項2）★／`CLEAN_ERROR_LABELS` とは ★別のテーブルの別のコード表★（`clean_status_histories.clean_error`）で、★互いに読み替えない★
   - `CLEAN_ERROR` の docstring（`:231-274`。該当は `:239`）の「`status` をこのコード表で読み替えないこと（値の意味が未受領）」は「`status` には別の表（`CLEAN_TASK_STATUS_LABELS`）がある」に直す
2. `lib/format/clean-result.ts`:
   - `formatCleanTaskStatus()`（`:58-64`）: null → `CLEAN_RESULT_NOT_DEPARTED_LABEL`（いまと同じ）、それ以外 → `cleanTaskStatusLabel(status)`
   - 冒頭の docstring（`:1-45`）を書き直す。「★`status` は生値をそのまま返す★」「画面には `4` のような数字がそのまま出る。それが意図した挙動」（`:11-22`）は ★いまの仕様と逆★。★関数名を改名した理由（`:24-29`）と null を「出発失敗」と出す根拠（`:31-41`）は残す★
   - `:50-57` の `@returns` を直す
3. `lib/i18n.ts`: 表3 の6語の英訳を足す（`出発失敗`（`:208`）の近く。★「英語は仮。ユーザーが後でまとめて見直す」とコメント★）。重複キーは lint が落とすので、既存キーと重ならないことを確かめる（2026-10-02 時点で6語とも未登録）
4. `components/features/detail-log/CleanLogTable.tsx:221-226`: セルを `t(formatCleanTaskStatus(r.task_status))` にする。コメント（`:221-225`）を「表示名を `t()` に通す。表に無い値は数字のまま（辞書に無いので `t()` も数字を返す）」に直す。★`status_1h` / `status_30m` の Pill は変えない★
   - 帰結: ★英語表示で「出発失敗」も "Launch failed" になる★（いまは日本語のまま出ている）
5. `lib/clean-logs/csv.ts`: `:104` は変えない（`formatCleanTaskStatus()` が日本語の表示名を返すようになるので、そのまま日本語が出る。D11）。見出しのコメント `:72-75`「中身は `status` の生値（例 `4`）で、日本語に変換しない」を直す
6. `d-watch-api.yaml` の `task_status`（`:1682-1700`）: ★型は integer のまま★。description を書き直す — 値の意味（表3）、表に無い値は画面・CSV で数字のまま、★判定には使わない★、null の意味（いまと同じ）。`:1688-1692`（「値の意味は未受領」「推測で変換しない」）は ★古い★
7. テスト: 段階1 で書いた表3 のテスト。★既存の `lib/format/__tests__/clean-result.test.ts:57`（`0` → `"0"`）は逆の仕様なので `"清掃開始前"` に直す（`:61` の 255 → `"255"` は今も正しい）。`lib/clean-logs/__tests__/csv.test.ts:219` も同様に確かめる★。`lib/clean-logs/__tests__/csv.test.ts` で清掃結果の列が日本語の表示名になること（例: 4 → `清掃正常終了`、6 → `6`、null → `出発失敗`）

### 段階6 — fixtures・API 定義・残りのコメントを直す

#### 6-1. `lib/fixtures/clean-task-reports.ts`（★作り直し★）

いまは ★面積を `clean_ratio` から独立に作っている★（設計上の約束3。`:46-51`）。★本書の式では完了率が面積から決まる★ので、このままだと ★DB 経由（`USE_FIXTURES=false`。シードを入れたローカルDB）で `docs/fixtures.md` §2.1 の境界値（90.1 / 90.0 / 89.5 / 70.1 / 70.0 / 10.1 / 10.0 / 9.9 / 0）がすべて壊れる★。

1. `cleanTaskReportCleanArea()`（`:190-205`）を「完了率から面積を作る」形に変える。引数に完了率を足す（例: `cleanTaskReportCleanArea(taskArea, completionRate, cleanTimetableId)`）:
   - 完了率が100未満: `clean_area = round6(task_area × 完了率 ÷ 100)`
   - 完了率が100: `clean_area = round6(task_area × 係数)`、係数は `pseudoRandom()` で 1.00〜1.40（★`task_area` を超える行の再現。上限100の確認になる★）
   - ★この作り方で `runCompletionRate({ clean_area, task_area })` が元の完了率に一致すること★を 2026-10-02 に node で確かめた（`task_area` が小数第6位まである200通り × 完了率 0.0〜100.0 の0.1刻み、20万件で不一致0件）。★それでもテストで全行を確かめること★（下の 4.）
2. `build()` の `:342` で完了率を渡す。`clean_ratio`（`:343-344`）は ★`log.completion_rate` のまま残す★（D6。戻すときに fixtures を作り直さずに済む）。コメントを「`clean_ratio` 列は完了率に使っていない。列に戻すときのために、清掃ログの完了率と同じ値を入れてある」に
3. 冒頭の docstring（`:26-27` / `:41-51`）と `CLEAN_AREA_BELOW_TASK_AREA_SHARE`（`:177-188`）を直す。★約束3（面積を `clean_ratio` から逆算しない）は廃止★し、理由（D-155 で完了率が面積から決まるようになった）を書く。`:87-88` の `clean_ratio` の説明も
   - 帰結: ★fixtures の清掃ログに完了率100の行は無い★（最大 99.5。`lib/fixtures/clean-logs.ts:230-241`）ので、★`clean_area > task_area` の行は0件になる★。CSV の分布（68.3%）の再現はやめる
4. テスト `lib/fixtures/__tests__/clean-task-reports.test.ts`:
   - ★追加★: 全行で `runCompletionRate(row) === 対応する清掃ログの completion_rate`
   - ★削除★: `:147-154`（過半数が `task_area` 超え）/ `:156-164`（`clean_ratio` が面積比と一致しない）/ `:166-171`（面積比100%超で `clean_ratio` が低い）。★いまの仕様と逆★
   - `:50-61`（`clean_ratio` が清掃ログの完了率と同じ）は残してよい（D6）
5. `scripts/seed-local-db.ts:657` の呼び出しに完了率（`params.completionRate`）を渡す。`:645` のコメント（「決定4 でこれが `clean_ratio` になる（面積から逆算しない）」）と `:675-676` を直す。`:2127` のログ（`clean_area > task_area` の割合を CSV実測 68.3% と並べて出す）は、0% になることをコメントに書けば残してよい
   - ★シードの接続先・ガード（`lib/db/local-guard.ts`）には触らない★。★シードを流すのは任意★。流すなら `dwatch_local` だけ（ガードが他を拒否する）。★ステージングには絶対に向けない★（`CLAUDE.md` §3）

#### 6-2. `lib/fixtures/areas.ts`

- `:201` の `round1(…)` を ★`round6`（小数第6位）★ にする。`round1` だと `task_area = 120` のような値で完了率が1段ずれる（2026-10-02 に node で確認。いまの施設は 1000 / 100 なのでずれないが、面積を変えた人が踏む）
- 冒頭 `:18-24` と `:63-69` の説明を直す（「面積は全体率の計算に使われない」→「行の完了率は面積から `runCompletionRate()` で決まる。全体率は行の完了率の単純平均」）
- ★`:115-116` の「旧定義の面積ベース 802/1200 = 66.83…%」は残す★（AR §2。Σ面積 ÷ Σ面積 に戻っていないことの確認で、今も意味がある）。`:115` の「clean_ratio の単純平均」だけ「行の完了率の単純平均」に
- テスト `lib/fixtures/__tests__/areas.test.ts` に ★全行で `runCompletionRate(row) === row.completion_rate`（`completion_rate` が null の行を除く）★ を足す

#### 6-3. `docs/fixtures.md`

- §2.5（`:107-133`）: `:116-118` の「全体率は各行の `clean_ratio` の単純平均」を「行の完了率（`runCompletionRate()`）の単純平均」に
- §2.6（`:137-220`）: `:160-161`（CSV の形の説明）は実測なので残す。`:170`（ケース #2）を「`clean_area` を清掃ログの完了率から作る（`clean_ratio` 列にも同じ値）」に、`:175`（ケース #7「面積比と `clean_ratio` の乖離」）を ★削除★して「完了率100の行は `task_area` を超える面積にする（上限100の確認）。ただし fixtures の清掃ログには完了率100が無いので該当0件」に置き換える。`:212-220` に D-155 の経緯を1行

#### 6-4. `d-watch-api.yaml`（完了率の説明）

`clean_task_reports.clean_ratio` を完了率の出所として書いている所を、すべて「1回ごとの完了率（`min(clean_area ÷ task_area × 100, 100)` を小数第1位まで切り捨て。`task_area = 0` は除く）の単純平均。D-155（暫定）」に直す:
`:338-343` / `:348-350` / `:563-566` / `:595-602` / `:1701-1710`（★`:1707`「clean_ratio の生値(面積から再計算しない)」は逆★。`:1708`「行が無いとき null」に「`task_area = 0` のときも null」を足す）/ `:1757-1760`。
段階4・段階5 で直した所と合わせて、★`grep -n "clean_ratio" d-watch-api.yaml` の残りが「サマリの列（`*_summaries.clean_ratio`）」の説明か、経緯の説明だけになること★。

#### 6-5. その他の古い説明

| ファイル:行 | いま | 直し方 |
|---|---|---|
| `lib/reports/target-customer.ts:12` | 清掃完了率の合算「★できる★」（分子 `clean_area` / 分母 `task_area`） | ★「★できない★（1回ごとの完了率の合計と件数がサマリに無い。`clean_area` / `task_area` の合計で割ると Σ面積 ÷ Σ面積 になり、サマリの式と違う）」★ |
| `lib/clean-area/aggregate.ts:5` / `:16-19` / `:145` / `:158` / `:188` / `:221-222` | 「`clean_ratio` の単純平均」「`clean_ratio` の Σ」 | 「1回ごとの完了率の単純平均」「1回ごとの完了率の Σ」。★中身は変えない★ |
| `lib/judge/__tests__/rescue.test.ts:42` / `:215-231` / `:271-275` | 「`clean_ratio` の生値」「面積比162.8%の行でも clean_ratio の 61 を使う」 | テストの中身（`completion_rate` を直接渡している）は今も正しい。名前と説明を「1回ごとの完了率」に。3-8・3-9 は「判定は面積を読まず、渡された `completion_rate` だけを使う」ことの確認として残す |
| `lib/db/__tests__/consecutive-source.test.ts:72` / `:79` / `:118-122` | `clean_ratio = 50` → `completion_rate = 50` | 面積を足し（例 50/100）、期待値は表1 に従う |
| `components/features/detail-log/AreaModal.tsx:39` | AR 段階1-4 で直る | AS では「行の完了率」の出所を `runCompletionRate()` に合わせるだけ |

★最後に次の grep を流し、残ったものを §8 の分類に当てはめる★:
```
grep -rn "clean_ratio\|面積から再計算\|相関しない" app lib components scripts d-watch-api.yaml docs/fixtures.md --include=*.ts --include=*.tsx --include=*.yaml --include=*.md | grep -v "lib/generated"
```

### 段階7 — 全体の確認

1. `npx tsc --noEmit -p .` — エラー0件
2. `npm run lint` — 指摘0件（`$queryRaw` を使っていないこと・`any` が無いこと）
3. `npm test` — ★全件通過★
4. `npm run build` — 成功
5. ★画面で確かめる（`USE_FIXTURES=true`）★: `.claude/launch.json` の `d-watch-fixtures`（ポート 3100）で起動する
   - 清掃詳細ログ: 当日の境界値の行が §2.1 どおり（施設2 = 90% ◎ / 施設3 = 90% ○ / 施設5 = 70% ○ / 施設8 = 10% △ / 施設9 = 10% × / 施設10 = 0% ×）。★清掃結果の列が「清掃正常終了」などの表示名になり、6 は `6` のまま★、施設12 は「出発失敗」
   - JP/EN 切替で EN にし、清掃結果が英語（"Completed" など）、6 は `6`、出発失敗は "Launch failed"
   - 清掃結果の CSV を出し、清掃結果の列が日本語の表示名であること
   - エリア別詳細（施設7・1日前）: 全体 81% ○（行は 62 / 94 / 88）
   - 総清掃面積（`/clean-area`）: 画面が開き、完了率と実施回数が出る
   - レポート（当期）・ダッシュボードの要レスキュー: 開いてエラーが出ないこと
6. ★`judge.areas` ログ★（エリア別詳細を開いたときに標準出力に出る）に `overall_completion_rate_sum` / `overall_completion_rate_count` が出て、`raw_overall_completion_rate` と割り算が合うこと

---

## 7. テスト一覧（本書で足す・直すもの）

| ファイル | 内容 | 段階 |
|---|---|---|
| `lib/judge/__tests__/run-completion-rate.test.ts`（新設） | 表1・表2 | 1 |
| `lib/format/__tests__/clean-result.test.ts`（既存に追加） | 表3 | 1 / 5 |
| `lib/db/__tests__/clean-log-mapping.test.ts` | 面積から完了率／`clean_ratio` 列を読まない／`task_area = 0` | 3-1 |
| `lib/db/__tests__/report-mapping.test.ts` | 当期KPI の完了率が面積から／`task_area = 0` は実施に入り件数に入らない／（案A）`launchFailedDaysInRun` 表5 | 3-2 / 3-4 |
| `lib/judge/__tests__/rescue.test.ts` | （案A）表5 の R-1 | 3-4 |
| `lib/db/__tests__/consecutive-source.test.ts` | 面積を足す／（案A）`departed` | 3-3 / 3-4 |
| `lib/db/__tests__/clean-area-source.test.ts` | 表4 F1〜F7／`where` と `precheck`／0件でクエリを撃たない | 4 |
| `lib/db/__tests__/fixture-clean-area.test.ts` | `runCompletionRate()` 経由／数字が変わらない | 4 |
| `lib/clean-logs/__tests__/csv.test.ts` | 清掃結果が日本語の表示名 | 5 |
| `lib/fixtures/__tests__/clean-task-reports.test.ts` | 全行で面積から完了率が一致／逆の仕様のテスト3件を削除 | 6 |
| `lib/fixtures/__tests__/areas.test.ts` | 全行で面積から完了率が一致 | 6 |
| `app/api/clean-logs/[id]/__tests__/route.test.ts` | `judge.areas` の項目名（§10） | 6 |

---

## 8. `clean_ratio` の使われ方の分類（2026-10-02。`grep -rn "clean_ratio" app lib components scripts`）

| 分類 | 何か | 扱い |
|---|---|---|
| ★完了率の材料（`clean_task_reports.clean_ratio` 列）★ | `lib/db/clean-log-mapping.ts:106` / `:200`、`lib/db/report-mapping.ts:408` / `:492`、`lib/db/clean-area-source.ts:75` / `:107` / `:229` / `:258`、`lib/db/fixture-clean-area.ts:73` / `:83` | ★`runCompletionRate()` に置き換える★（段階3・4）。型・`select` からは消さない（D6） |
| ★サマリの列（`*_summaries.clean_ratio`）★ | `lib/db/report-mapping.ts:221` / `:237` / `:521`（`prev?.clean_ratio`） | ★そのまま★（5-4。既に本書と同じ式） |
| 内部の合計・件数の名前 | `cleanRatioSum` / `clean_ratio_sum` ほか（16ファイル） | ★名前は変えない★（D7）。docstring だけ直す |
| `select` の列 | `lib/db/consecutive-source.ts:116` / `:377` | ★残す★（D6） |
| fixtures・シードが列に入れる値 | `lib/fixtures/clean-task-reports.ts:88` / `:344`、`scripts/seed-local-db.ts:676` ほか | ★残す★（D6）。説明だけ直す |
| `clean_status_histories` 側のシード | `scripts/seed-local-db.ts:1280-1318` / `:1476-1478` / `:1706-1730`、サマリのシード `:1835-1885` | ★触らない★（旧テーブル・サマリの仮値。本書と無関係） |
| 説明（コメント・API 定義） | 上の段階3〜6 で挙げたもの | 直す |
| 経緯として書かれた説明（「旧:」「以前は」「廃止」） | 例 `lib/clean-logs/areas.ts:94`、`lib/fixtures/areas.ts:116` | ★残す★（AR §2 と同じ） |
| 生成物 | `lib/generated/prisma/**` | ★触らない★ |

★完了率の画面表示は、どの画面も `completion_rate` を `displayRate()` に通すだけ★で、`clean_ratio` を直接表示している画面は無い（`grep -rn "clean_ratio" components` は `AreaModal.tsx:39` のコメント1件だけ）。

---

## 9. 触ってはいけないもの

- `prisma/schema.prisma`（★DB の定義は TeamWorks の領域★。`CLAUDE.md` §7）
- `lib/generated/prisma/**`
- `scripts/seed-local-db.ts` の ★接続先とガード★（`lib/db/local-guard.ts` を含む）。★変えてよいのは 6-1 の5. の呼び出しとコメントだけ★
- `CLAUDE.md` / `docs/decisions.md`（監督が直す）
- `docs/code-map.md`（監督が直す。§11）
- ★4カラム UPDATE（`status` / `comment` / `responded_at` / `resolved_at`）★ — 指示書AT の範囲
- `periodOfSummaryRow()`（`lib/db/report-mapping.ts:99`）と `kpiFromSummaryRatios()`（`:230-240`）
- `countDuplicatedTimetables()`（`lib/db/clean-area-source.ts:292-305`）
- `judgeGrade()` / `displayRate()` / `isProblemGrade()` / `matchAdviceRules()` / `GRADE_THRESHOLDS`（判定の規則は変えない。5-7）
- `CLEAN_ERROR` / `CLEAN_ERROR_LABELS` の値（`lib/config/index.ts:275-304`。docstring の1文を直すだけ。段階5-1）
- `CLAUDE.md` §4.4 の「中断で返る＝失敗」に関わる判定（D12。実データを見てから）
- 時刻の補正（D-155 項4。アプリのコードは変えない）

---

## 10. 指示書AR との関係（★AR を先に当てること★）

| AR の段階 | 本書との関係 |
|---|---|
| 段階1-3 / 1-4 / 1-5（`areas/route.ts:126` / `AreaModal.tsx:39` / `lib/db/index.ts:1023` のコメント） | AR で「全体率は行の完了率の単純平均」に直す。★本書では「行の完了率 = `runCompletionRate()`」を足すだけ★ |
| 追補2（`lib/judge/kpi.ts:12-14` の句を削る） | AR で削ったあと、本書の段階2-3 で冒頭を書き直す |
| ★段階4（`judge.areas` の項目）★ | ★AR のとおり `overall_clean_area` / `overall_task_area` を消して `overall_clean_ratio_sum` / `overall_clean_ratio_count` を足す★。★そのあと本書で、この2つを `overall_completion_rate_sum` / `overall_completion_rate_count` に改名する★（D8） |

**AR と AS を当てたあとの `JudgeAreasLogEvent`（`lib/logger/events.ts`）の最終形**:

| 項目 | 中身 |
|---|---|
| `raw_overall_completion_rate` | 全体率（丸める前）。いまと同じ |
| ★`overall_completion_rate_sum`★ | 全体率の分子 ＝ ★`completion_rate` が null でない行の `completion_rate`（＝1回ごとの完了率）の合計★ |
| ★`overall_completion_rate_count`★ | 全体率の分母 ＝ その行数 |
| `overall_clean_area` / `overall_task_area` / `overall_clean_ratio_sum` / `overall_clean_ratio_count` | ★出さない★ |

- docstring（`lib/logger/events.ts:158-171`）に ★`raw_overall_completion_rate === overall_completion_rate_sum / overall_completion_rate_count`（分母0なら null）★ と、★項目名に式の名前を入れなかった理由（D8）★を書く
- ★本番は未稼働で、このログを集計・監視している仕組みは無い★（AR 4-2）ので、改名で壊れるものは無い。★ただし本番稼働後は改名しない★（D-151 6b と同じ理由）
- AR 段階4 のテスト（`app/api/clean-logs/[id]/__tests__/route.test.ts`）の項目名も合わせて直す。★AR と AS を同じ日に続けて当てるなら、AR 段階4 を最初から最終形の名前で入れてもよい★（最終形が上の表と同じなら可）

---

## 11. 範囲外（本書ではやらない。監督が扱う）

- `docs/code-map.md`（`:22` / `:785` / `:857-884` / `:947` / `:1603-1609` / `:2159` / `:2336` / `:2377` / `:2408` に D-151 の定義が書いてある）
- 第11回の照会（`task_area = 0` の扱い・`status = 6` の意味・訂正後の `clean_ratio`・`clean_timetable_histories` の扱い）
- 総清掃面積の取り寄せ量の実測（期間なしで全期間を運ぶ。段階4-2）
- 清掃詳細ログを予定起点・実績起点のどちらにするか（D-151 項7。保留中）

---

## 12. コミット

段階ごとに分ける（例: 段階1 / 段階2 / 段階3 / 段階4 / 段階5 / 段階6）。★`git add` はファイルパスを明示する★（`git add -A` 禁止）。
メッセージには ★何を変えたか★ と ★なぜ（D-155。TeamWorks 第10回 N-2 / N-3）★ を書く。★段階1 のコミットは「テストだけ（この時点では落ちる）」と明記する★。
