# 指示書AU — 第11回の回答を反映する（`task_area = 0` は 0%・当期KPIをサマリの式に・「屋号」を「顧客」に・古いコメント4件）

**日付**: 2026-10-05 / **担当**: Cursor / **基点**: develop `74cffd5`（指示書AR・AS・AT は実装済み）
**前提**: D-155 / D-156 / D-157 / ★D-158（本書の根拠）★ / `CLAUDE.md` §4.2・§4.3・§4.7・§6・§7・§9.4 /
TeamWorks 第11回の回答（原文 `docs/handoff/TeamWorks回答_第11回_20261005.md`。設問は `docs/handoff/TeamWorks確認事項_第11回_20261002_送付版.md`）

★**`CLAUDE.md` §4.2 は監督が直してある**★（`CLAUDE.md:159-182`）。本書はそれにコードを合わせる作業。
★**期待値表（§3 の表1・表2）は監督が確定した正。変えないこと**★（`CLAUDE.md` §0-2・§6-2・§9.4）。

---

## 0. 何をするか

| # | 回答 | やること | 段階 |
|---|---|---|---|
| 1 | ★P-5=B★ | ★`task_area = 0` の清掃の1回ごとの完了率を `null` から `0.0` にする★（`runCompletionRate()`）。null を前提にした説明・テストを直す | 段階2 |
| 2 | ★P-6=A/A/B★ | ★当期（アプリがその場で集計する期間）の稼働率・出発成功率・エラー発生率を、サマリと同じ式にする★ | 段階3 |
| 3 | P-7=C | 予定を `clean_timetables` と `clean_timetable_histories` から読む | ★段階3b★ |
| 4 | ★P-15=B★ | ★画面の見出し「屋号」を「顧客」にする★（「全屋号」→「全顧客」ほか）。★データの説明の「屋号（`customers.id`）」も「顧客」に（§11 Q2）★ | 段階4 |
| 5 | （D-157）・P-4 | ★回答が来たら直すと控えていた古いコメント4件★と、★status 6 の説明3か所（§11 Q4）★を直す | 段階5 |
| 6 | （P-6 の帰結） | ★fixtures の作り値で稼働率 > 出発成功率になっている1行★を直す（§11 Q5） | 段階3（3-4 の表の下） |

★変えないもの★: 確定期間（サマリ `*_summaries`）の値の読み方（そのまま読む）／完了率の平均の取り方（`meanCleanRatio()`）／
判定（◎○△×・L1〜L3・アドバイス・要レスキュー）の規則／未実施を `departed` で数えること（指示書AS 案A。D-156 項1）。

---

## 1. 背景 — 回答で何が分かったか

### 1-1. P-5（`task_area = 0` の行）＝ B「0% として数える」

いまは `runCompletionRate()` が `task_area === 0` で `null` を返し（`lib/judge/kpi.ts:148`）、分子にも分母にも入れていない（D-155 の暫定。D-156 項1）。
★これを `0.0` にする★。帰結:

- ★`completion_rate === null` ⟺「レポートの行が無い（未実施）」が、再び両向きに成り立つ★（指示書AS で崩れた不変条件が戻る）
- ★それでも未実施は `departed` で数え続ける★（AS 案A を残す。理由は D3）
- 完了率の件数（`cleanRatioCount` / `clean_ratio_count`）は、実施と数えた回数と常に同じ値になる（`task_area = 0` も件数に入るため）

### 1-2. P-6（サマリの3つの率）＝ A / A / B

| 指標 | ★新しい式（サマリと同じ）★ | いまの当期の式 |
|---|---|---|
| 稼働率 | ★正常終了（`status = 4`）の回数 ÷ 予定回数★ | レポート行がある回数 ÷ 予定回数（★出発成功率と同じ値になっていた★） |
| 出発成功率 | ★開始できた回数（レポート行がある回数）÷ 予定回数★（★時刻の条件なし★） | 同じ（「定刻出発」と書いてあるが、実際は時刻を見ていない） |
| エラー発生率 | ★エラー回数 ÷ ★予定回数★★ | エラー回数 ÷ 実施回数 |

★確定期間はサマリの値をそのまま読んでいる★（`kpiFromSummaryRatios()`。`lib/db/report-mapping.ts:231-241`）ので、
★変えるのは当期だけ★。式が揃うので、前期比（サマリ）と当期（アプリ）の差が同じ式どうしの差になる。

### 1-3. P-15（`customers` の単位）＝ B「法人」

`customers` は法人の単位で確定。★画面の見出し語「屋号」を「顧客」に変える★（D-158 当方の対応3）。
★DB の物理名（`customers` / `customer_name`）・API のキー（`customer_id`）は変えない★（見出し語だけ）。

---

## 2. 決定（Cursor は従うこと）

| # | 決定 | 理由 |
|---|---|---|
| D1 | ★`runCompletionRate()` は `task_area === 0` のとき `0` を返す★。★戻り値の型を `number`（null なし）にする★ | P-5=B。null を返す場合がもう無いので、型で示す（呼び出し側の「null なら足さない」分岐が要らなくなったことを `tsc` が教えてくれるわけではないので、§段階2 の一覧で手で消す） |
| D2 | ★`clean_area > 0` かつ `task_area = 0` も `0`★（`Infinity` / `NaN` / `100` にしない） | P-5=B は「0% として数える」。割り算をしないで返す |
| D3 | ★未実施は引き続き `departed` で数える★（`ConsecutiveInput.departed` / `RescueRowInput.departed` / `launchFailedDaysInRun()` / `foldRescueDays()` は中身を変えない） | ★完了率の規則がまた変わっても、未実施の数え方が巻き込まれないため★（AS 案A の理由のうち「規則を2つの意味に兼用しない」はいまも正しい）。監督指示 |
| D4 | ★稼働率の分子＝「その本清掃の（最新の）レポート行があり、その `status` が `CLEAN_TASK_STATUS.completed`」の回数★。★`4` を直書きしない★（`lib/config/index.ts:348-361` に既にある） | P-6=A。`CLAUDE.md` §7（数値の直書き禁止）の趣旨 |
| D5 | ★「正常終了か」を決める述語は1つだけ★: `lib/judge/kpi.ts` に `isCompletedTask(report: { status: number } \| undefined): boolean` を足す（`lib/judge/index.ts` は `export *` なので自動で出る） | 規則を2箇所に書かない（D-103 項1 と同じ考え方）。判定層は `lib/db/` を import しないので構造的な型にする（`runCompletionRate()` と同じ作法） |
| D6 | ★`CleanStatsBucket` に `completed: number`（正常終了の回数）を足す★。★既存の `executed` は名前を変えず、意味を「開始できた回数（レポート行がある回数）＝出発成功率の分子」と書き直す★ | 名前を変えると機械的な差分がレビュー（daichi。D-154）を読みにくくする（指示書AS D7 と同じ判断）。`executed` を数える規則（`isNotExecuted` 述語）はいまのまま正しい |
| D7 | ★`CleanStatsReportLike` に `status: number` を足す★ | 当期の2経路（`reportKpis()` / `facilityPerformance()`）は `cleanTaskReport.findMany` を ★`select` なし（全列）★で引いている（`lib/db/index.ts:2085` / `:2352`）ので、`status` は既に載っている。★`select` を足さないこと★（足すなら `status` を落とさない） |
| D8 | `lib/judge/kpi.ts` の3関数は名前を変えず、引数名と docstring を変える: `operationRate(completed, planned)` / `launchRate(started, planned)` / ★`errorRate(errors, planned)`★ | 中身はどれも `rate(分子, 分母)` のまま。変わるのは呼び出し側（`kpiFromCleanStats()`）が渡すもの |
| D9 | ★エラー発生率は100%で切らない★（エラー件数が予定回数より多ければ100を超える） | サマリの式（`error_count ÷ task_count`）に上限は無い。1回の清掃に複数のエラーが紐づきうる |
| D10 | ★未来の予定（`isFutureSchedule()`）は、予定・開始・正常終了・エラーのどれにも足さない★（いまと同じ） | 指示書V 不具合A・決定1。変えない |
| D11 | ★確定期間（サマリ）の値は1つも変えない★。`kpiFromSummaryRatios()` / `SummaryRatios` / `periodOfSummaryRow()` に触らない | D-158。サマリは既に新しい式 |
| D12 | ★画面の見出し語は `lib/i18n.ts` のキー（日本語原文）で持つ★。キーを `屋号` → `顧客` に改名し、★すべての `t("屋号…")` 呼び出しを新しいキーに直す★（旧キーは残さない） | 旧キーを残すと、片方だけ直した画面が日本語で「屋号」のまま出ても気づけない。重複キーは lint が落とす |
| D13 | ★英語は仮★: 「顧客」"Customer" / 「全顧客」"All Customers"（★大文字は既存の "All Holdings" にそろえる。§11 Q7★）ほか（段階4 の表）。★英語は後でユーザーがまとめて見直す★とコメントに書く | 監督指示（暫定） |

---

## 3. ★期待値表（正）★

★**表1・表2 は監督が確定した正である。Cursor は値を変えないこと**★（`CLAUDE.md` §0-2・§6-2・§9.4）。
★**段階1 で、実装より先にこの表をテストにする**★。表とテストが食い違ったら、テストを表に合わせる。実装の都合で表を直さない。

### 表1（`runCompletionRate` の変更分。AS 表1 の R9 を差し替え、R12 を追加。他の R1〜R11 はそのまま）★正・変えない★

| # | clean_area | task_area | 期待値 | 確かめること |
|---|---|---|---|---|
| R9 | 10 | 0 | 0.0 | 対象面積0は 0%（P-5=B）。旧: null |
| R12 | 0 | 0 | 0.0 | 両方0も 0% |

### 表2（当期 KPI。予定・開始・正常終了・エラーは本清掃・予定 ID ありの行だけで数える。生値で持ち、表示は四捨五入）★正・変えない★

| # | 予定 | レポート行あり（開始） | うち status=4 | エラー件数 | 稼働率 | 出発成功率 | エラー発生率 |
|---|---|---|---|---|---|---|---|
| K1 | 10 | 9 | 7 | 2 | 70.0 | 90.0 | 20.0 |
| K2 | 0 | 0 | 0 | 0 | null | null | null |
| K3 | 5 | 0 | 0 | 0 | 0.0 | 0.0 | 0.0 |
| K4 | 4 | 4 | 4 | 0 | 100.0 | 100.0 | 0.0 |
| K5 | 3 | 3 | 2（残り1行は status=5） | 3 | 66.666… | 100.0 | 100.0 |
| K6 | 2 | 2 | 1（残り1行は status=6） | 0 | 50.0 | 100.0 | 0.0 |
| K7 | 2 ＋ 予定外1行（status=4・予定 ID NULL） | 2 | 2 | 0 | 100.0 | 100.0 | 0.0（予定外は数えない） |
| K8 | 1 | 1 | 1（status=4・clean_area=0） | 0 | ★100.0★ | 100.0 | 0.0 |

- ★K8: 正常終了は status で数える。面積は見ない（P-3=A は完了率0%の話）★。この清掃の完了率は 0.0（×）だが、稼働率には正常終了1回として入る。
- ★エラー発生率は上限で切らない（D9）★。K5 はエラー件数が予定回数と同じなのでちょうど 100.0。★予定回数より多ければ100を超える★（例: 予定3・エラー4 → 133.33…）。100 で止めないこと。
- ★K2 は「予定が1件も無い」★ので3つとも null（0% にしない。`lib/judge/kpi.ts:22-23`）。★K3 は「予定はあったが全部出発失敗」★なので 0.0。★エラー発生率も 0.0★（旧式では実施0回で null だった）。
- ★K6 の status=6★ は Pudu の値で意味は先方も不明（P-4）。★4 以外なので正常終了に数えない★。

**テストの書き方の注意（表2）**:
- ★`aggregateCleanStatsByFacility()` → `sumCleanStatsBuckets()` → `kpiFromCleanStats()` を通して★ `ReportKpi` の `value` を確かめる（`lib/db/__tests__/report-mapping.test.ts` に新しい `describe`）。自前で割り算を書かない。
- 予定はすべて期間内・過去（`now` より前）にする。1施設でよい。前期（`prev`）は `undefined`。
- 「開始」した予定にだけレポート行を入れる。★「うち status=4」以外の行の status は表に書いた値（K5=5 / K6=6）、K1 の残り2行は 5 と 2 にする★（4 以外なら何でもよいが、テストの中で固定する）。面積は 100/100（K8 だけ 0/100）。
- エラー件数は `errorCount` の Map（本清掃 id → 件数）で渡す。K1 は2件を別々の予定に、K5 は3件を1つの予定に寄せてよい。
- ★K7 は `latestReportByTimetableId()` を通して★レポートの Map を作る（予定外＝`clean_timetable_id: null` の行が Map に入らないことまで含めて確かめる）。予定 ID が NULL のエラー行も `errorCountByMainTimetable()` を通して落ちることを1件足してよい。
- 生値の比較は `toBe`。★K5 の稼働率だけ `toBeCloseTo(200 / 3, 10)`★。
- ★追加で1件★: K1〜K8 のすべてで `稼働率 ≤ 出発成功率`（正常終了は開始の部分集合）を確かめる。

### 表3（本書で表1 から導いたもの。★監督が正として確定（2026-10-05）★）

AS の期待値表のうち、`task_area = 0` に当たる行だけが変わる。★他の行は変えない★。

| 元の表 | # | 入力 | 旧の期待値 | ★新の期待値★ |
|---|---|---|---|---|
| AS 表4（F-08） | F5 | 本清掃 10/0 の1行だけ | executed_count 1 / completion_rate null | executed_count 1 / ★completion_rate 0.0★ |
| AS 表5（未実施の数え方） | U2 | 行がある・`task_area = 0` | departed true / completion_rate null / R-1 数えない / 出発失敗日 数えない / 問題日 | departed true / ★completion_rate 0.0★ / R-1 数えない / 出発失敗日 数えない / 問題日（★0.0 は ×★） |
| AS 段階3-2（当期KPI の完了率） | w11 | `task_area = 0` の行 | 実施1・完了率の件数0 | 実施1・★件数1・Σ に 0 を足す★ |
| 詳細ログの1行 | — | 行がある・`task_area = 0` | 完了率 `-`・判定 × | ★完了率 `0%`・判定 ×★（`displayRate(0)`） |

---

## 4. ★落とし穴（全段階で読むこと）★

| # | 罠 | どう避けるか |
|---|---|---|
| 4-1 | ★サマリの値まで計算し直す★ | ★確定期間はそのまま読む★（D11）。`kpiFromSummaryRatios()`（`lib/db/report-mapping.ts:231-241`）・`periodOfSummaryRow()` に触らない。★`route.test.ts` の確定期間のテスト（`app/api/reports/__tests__/route.test.ts:563-600` / `:1020-1229`）の期待値は1つも変わらないのが正しい★ |
| 4-2 | ★`status` の `4` を直書きする★ | `CLEAN_TASK_STATUS.completed`（`lib/config/index.ts:358`）を読む。`grep -rnE "status ?===? ?4([^0-9]|$)" lib app components` が0件になること（★`status === 422`（`lib/reports/block-visibility.ts:160`）に当たらない形で探す★） |
| 4-3 | ★正常終了の判定を2箇所に書く★ | `isCompletedTask()`（D5）だけ。`report-mapping.ts` の中で `report.status === …` と書かない |
| 4-4 | ★`cleanTaskReport.findMany` に `select` を足して `status` を落とす★ | いまは `select` なし（D7）。★足さない★。テストのスタブ行にも `status` を必ず入れる |
| 4-5 | ★正常終了の判定に面積を混ぜる★（「`clean_area = 0` なら正常終了でない」） | ★status だけで数える★（表2 K8）。面積は完了率の話 |
| 4-6 | ★エラー発生率を100で切る★ | 切らない（D9） |
| 4-7 | ★未実施の数え方を `completion_rate === null` に戻す★ | ★`departed` のまま★（D3）。P-5 で null ⟺ 未実施が戻っても、`launchFailedDaysInRun()`（`report-mapping.ts:648`）・`foldRescueDays()`（`lib/judge/rescue.ts:168`）は変えない |
| 4-8 | ★`runCompletionRate()` で割り算を先にしてから 0 を判定する★ | `task_area === 0` を ★割る前に★ 見て `0` を返す（`10 / 0 = Infinity` → `Math.min(Infinity, 100) = 100` になる） |
| 4-9 | ★生SQL（`$queryRaw` / `$executeRaw`）★ | ★禁止★（`CLAUDE.md` §3。eslint が止める）。本書の変更はすべて JS の中で済む |
| 4-10 | ★閾値・コード値の直書き★ | 90/70/10 は `GRADE_THRESHOLDS`、status は `CLEAN_TASK_STATUS`、上限100と桁数は `RUN_COMPLETION_RATE` |
| 4-11 | ★画面の文字列を i18n に通さない★ | JP/EN は必ず `t()`（`lib/i18n.ts`）。★新しい見出し語をコンポーネントに直書きしない★ |
| 4-12 | ★JP/EN 切替でレイアウトが崩れる★ | ★日本語は「屋号」「顧客」とも2文字、「全屋号」「全顧客」とも3文字で幅は変わらない★。英語は "Brand" → "Customer" で少し長くなる。セレクトは幅指定なし（内容の長さで決まる。例 `components/features/alerts/AlertsScreen.module.css:58-65`）で、ツールバーは `flex-wrap: wrap`。★幅を固定する CSS を新たに足さない★。段階6 の画面確認で EN の折り返しを見て、崩れていたら直さずに報告する |
| 4-13 | ★`顧客` の i18n キーが既存と重なる★ | 2026-10-05 時点で `顧客` / `全顧客` / `顧客を選ぶ` / `選べる顧客が{n}件あります` / `この条件では選べる顧客がありません` は未登録（既存は `顧客を選んでください`（`lib/i18n.ts:357`）だけ）。重複は lint が落とす |
| 4-14 | ★「屋号」を一括置換する★ | ★しない★。§7 の分類どおり、画面に出る文字列・それを直接説明するコメント・★データの説明（段階4 の 4-3 に列挙した行。§11 Q2）★だけを直す。経緯・処理の理由のコメントは残す |
| 4-15 | ★`git add -A`★ | ★禁止★（`CLAUDE.md` §7）。ファイルパスを明示する |

---

## 段階0 — 着手前

1. `git status` が clean で、develop が `74cffd5` 以降であること（`.claude/worktrees/` ではなく本体で作業する）。
2. 行番号は 2026-10-05 の `74cffd5` で確かめたもの。★段階3b（P-7）が先に当たるとずれる★ので、記述（関数名・文）で探すこと。
3. `npx tsc --noEmit -p .` と `npm test` が通る状態から始める（通らなければ着手前に報告）。
4. ★`robotErrorReport` の読み取りを `select` で必要な列だけにする（§11 Q6）★。先方の DDL では `robot_id` が NULL 可になった（第11回 P-8 b）が、Prisma のスキーマは NOT NULL のまま。★`robot_id` と `robot` リレーションを読まなければ、NULL の行が入っても読み取りで落ちない★。`grep -rn "robotErrorReport\." app lib` で全部の読み取りを洗い、`select` を付ける（書き込み `lib/db/robot-error-write.ts` は対象外）。★`prisma/schema.prisma` は変えない★（db pull は監督が行う）。テスト: スタブに `robot_id: null` の行を混ぜても落ちないこと

---

## 段階1 — 期待値表をテストにする（実装より先）

1. `lib/judge/__tests__/run-completion-rate.test.ts`:
   - 冒頭コメントの表1（`:13-27`）の R9 を表1 の行に差し替え、R12 を足す。`:8` の「★暫定★」と `:11` の「`task_area = 0` は null（分子にも分母にも入れない）」を「`task_area = 0` は 0（P-5=B。第11回）」に直す。★「本書 §3 が正。変えないこと」と出典に本書を足す★
   - `it.each`（`:74-86`）の R9 を `["R9 対象面積0は 0%（P-5=B）", 10, 0, 0.0]` に、R12 を足す
   - 表2 の M7・M8 は null を `meanCleanRatio()` に渡すテストなので ★そのまま★（null は「未実施＝レポート行が無い」の意味で残る）
2. `lib/judge/__tests__/kpi.test.ts`:
   - 冒頭の式（`:13-22`）を `CLAUDE.md` §4.2 の新しい式に直す
   - `operationRate`（`:24-35`）: 引数の意味を「正常終了の回数 ÷ 予定回数」に。値のテストは変えなくてよい
   - `launchRate`（`:37-46`）: `:38-39` の「定刻出発85件」の例を「開始できた回数 ÷ 予定回数（時刻の条件なし）」に書き直す
   - ★`errorRate`（`:100-109`）を逆の仕様に直す★: 「分母は清掃予定回数」`errorRate(9, 100) === 9`／予定0は null／★上限で切らない★ `errorRate(4, 3)` が `toBeCloseTo(400 / 3, 10)`
   - ★`isCompletedTask()` のテストを足す★: `undefined` → false／status 4 → true／0・1・2・3・5・6・255 → false／★期待値の 4 は `CLEAN_TASK_STATUS.completed` から読む★
3. `lib/db/__tests__/report-mapping.test.ts` に ★表2 の K1〜K8 と「稼働率 ≤ 出発成功率」★を新しい `describe` で書く（§3 の注意どおり）
4. ★この時点ではテストが落ちる（`isCompletedTask` が無い・`completed` が無い・R9 が null を返す）のが正しい★。コミットはテストだけで1つ

---

## 段階2 — `task_area = 0` を 0% にする（P-5）

### 2-1. 本体

`lib/judge/kpi.ts:144-153` の `runCompletionRate()`:

- `if (report.task_area === 0) return 0;`（★割る前に★。4-8）
- 戻り値の型を `number` にする（D1）
- docstring（`:112-143`）を直す:
  - `:120-123`「★★暫定（D-155。ユーザー判断「一旦B」）★★ — 第11回で…見直す」→ ★確定★（第11回 P-2=A「今の式のまま」・P-1「訂正後の `clean_ratio` 列もこの式と同じ値」。D-158）。「`clean_ratio` 列に戻すときはこの関数の中身だけを直す」「呼び出し側は行をそのまま渡すこと（D6）」は ★残す★（1関数に閉じ込める作法として今も正しい）
  - `:125-126`「`task_area === 0` は `null`（…暫定・第11回で確認）」→ ★`0`（P-5=B。第11回）★。★`clean_area > 0` でも `0`★（D2）。旧は null だったことを1行
  - `:142` の `@returns` から「`task_area === 0` なら `null`」を消す
- 冒頭（`:4-24`）の `:12` の「（暫定）」を「（第11回で確定。D-158）」に

`lib/config/index.ts:27-36` の `RUN_COMPLETION_RATE` の docstring: `:34-35` の「暫定…第11回で…見直す」を ★確定（第11回 P-2=A。D-158）★ に。★値（`cap: 100` / `decimals: 1`）は変えない★。

### 2-2. 「null なら足さない」分岐を消す（★`runCompletionRate()` の戻りを直接受けている所だけ★）

| 場所 | いま | 直し方 |
|---|---|---|
| `lib/db/report-mapping.ts:488-501` | `runRate ?? 0` / `runRate === null ? 0 : 1` | `runRate` は「実施と数えた行 かつ 結果行がある」ときだけ数値、それ以外 null のまま（`:492`）。★`:500-501` はそのままでよい★（null になるのは結果行が無いときだけ）。コメント `:488-491` の「`task_area = 0` なら null で…件数には入れない」を消す |
| `lib/db/clean-area-source.ts`（`cleanAreaStatsFromRows()` の中。`:290` 以降） | `const rate = runCompletionRate(report)`。null でなければ足す | ★常に足す★（`clean_ratio_sum += rate` / `clean_ratio_count += 1`）。docstring `:251-253` の「`task_area = 0` の清掃も実施1回に数える（表4 F5）」「null でなければ」を表3 の F5 に合わせる |
| `lib/db/fixture-clean-area.ts:76-99` | `rate ?? 0` / `rate === null ? 0 : 1` / `if (rate !== null)` | ★常に足す★。コメント `:30-31` / `:84-85` の「null（`task_area = 0`）なら Σ にも件数にも足さない」を消す |
| `lib/db/clean-log-mapping.ts:203-210`（`completionRateFromReport()`） | 中身はそのまま（結果行が無ければ null、あれば `runCompletionRate()`） | ★中身は変えない★。docstring を 2-3 のとおり直す |

### 2-3. 説明を直す（★動作は変わらない。null の意味が「未実施だけ」に戻ったことを書く★）

| ファイル:行 | いま | 直し方 |
|---|---|---|
| `lib/judge/kpi.ts:59-62`（`CleanRatioTally.cleanRatioCount`） | 「未実施の清掃と `task_area = 0` の清掃は数えない」 | 「★未実施の清掃は数えない★（`task_area = 0` は 0% として数える。P-5=B）」 |
| `lib/db/clean-log-mapping.ts:171-199`（`completionRateFromReport()`） | 表の2行目「行があり `task_area = 0` → null」／不変条件「逆は成り立たない」（`:192-199`） | 表を「行が無い → null／それ以外 → `runCompletionRate()`（`task_area = 0` は 0）」の2行に。★不変条件を「`departed === false` ⟺ `completion_rate === null`（P-5=B で両向きに戻った）」★に直し、★「それでも未実施は `departed` で数える（D3。完了率の規則が変わっても未実施の数え方を巻き込まないため）」★と書く。`:180-185` の経緯は残し、D-158（確定）を1行足す |
| `lib/db/clean-log-mapping.ts:94`（`CleanTaskReportLike`） | 「（D-155。暫定）」 | 「（D-155。第11回で確定。D-158）」 |
| `lib/db/clean-log-mapping.ts:350-363`（`ConsecutiveInput`） | `:352`「未実施と `task_area = 0` の清掃は null」／`:358-361`「`task_area = 0` の清掃は `completion_rate === null` だが出発はしている」 | `:352` は「未実施は null」。`:358-361` は「未実施は `departed` で数える（D3）」の理由を、`task_area = 0` の例ではなく D3 の理由に差し替える |
| `lib/db/consecutive-source.ts:35` / `:254-255` | 「（D-155。暫定）」／「`task_area = 0` の清掃は…」 | 確定に／D3 の理由に |
| `lib/db/report-mapping.ts:293-298`（`cleanRatioCount`）/ `:433-440`（`aggregateCleanStatsByFacility()`）/ `:637-640`（`launchFailedDaysInRun()`） | 「`task_area = 0` の清掃は実施には数えるが、ここには数えない」／「逆は成り立たない」 | 「★いまは `executed` と同じ値になる★（`task_area = 0` も 0% として数えるため。P-5=B）。★それでも別に持つ★（Σ と件数の組で `meanCleanRatio()` に渡す形を変えないため）」／不変条件を両向きに、`departed` で数える理由は D3 |
| `lib/judge/rescue.ts:20-22` / `:75-79` / `:93-96` / `:166-169` | 「`task_area = 0` の清掃は `completion_rate` が null だが出発はしている」 | D3 の理由に差し替える。★`:168`（`!row.departed`）と `:170-171`（null を数えない）は中身を変えない★ |
| `lib/db/index.ts:977-980` | 「逆は成り立たない — `task_area = 0` は出発しているが null」 | 両向きに成り立つ（P-5=B）。★`report ? report.clean_ratio : null` の形に戻さないこと★（`:977-978`）は残す |
| `lib/types/index.ts:248-255`（`CleanLogItem.completion_rate`）/ `:347` / `:1272-1277`（`CleanAreaFacilityStats.clean_ratio_count`） | 「行があっても `task_area = 0` なら null」／「`executed_count` を流用せず別に持つ — `task_area = 0` は…入らない」 | 「行があって `task_area = 0` なら 0（P-5=B）」／「いまは `executed_count` と同じ値。形を変えないために別に持つ」。`:250` の「★暫定★」を確定に |
| `components/features/detail-log/AreaModal.tsx:42-43` | 「未実施・`task_area = 0`＝完了率が null の行は…」 | 「未実施＝完了率が null の行は…」 |
| `d-watch-api.yaml` `:340` / `:563` / `:567` / `:598-599` / `:918` / `:1743` / `:1746` / `:1796-1797` / `:2056` | 「`task_area = 0` は除く」「task_area = 0 のときも null」「D-155(暫定)」 | 「`task_area = 0` は 0」「(D-155。第11回 P-2・P-5 で確定。D-158)」。★`:1746`「★task_area = 0 のときも null★(このとき departed は true)」と `:1797`「task_area = 0 も null」は消す★ |

★最後に次の grep を流し、残ったものが「経緯（旧: …）」の説明だけになっていること★:

```
grep -rn "task_area = 0\|task_area === 0\|task_area=0\|task_area が 0" app lib components d-watch-api.yaml docs/fixtures.md --include=*.ts --include=*.tsx --include=*.yaml --include=*.md | grep -v "lib/generated" | grep -v __tests__
```

### 2-4. テストを直す（★面積から完了率を作っているテストだけ期待値が変わる★）

| ファイル:行 | 直し方 |
|---|---|
| `lib/db/__tests__/clean-log-mapping.test.ts:319` / `:374-378` / `:382-402` / `:420-421` | 表3 の「詳細ログの1行」: `{ clean_area: 10, task_area: 0 }` → `completion_rate` は `0`。`:420` の「逆は成り立たない」のテストは ★「両向きに成り立つ」★に書き換える |
| `lib/db/__tests__/clean-log-mapping.test.ts:583-587`（`isProblemCompletion()` の U2） | 入力が面積から来ているなら `0` → 問題（×）。期待値（問題）は変わらない。ラベルを直す |
| `lib/db/__tests__/consecutive-source.test.ts:80`（Q9）/ `:262-266`（テスト内の再実装） / `:655-697` | ★`:265` のテスト内の `r.task_area === 0 ? null` を消し、`runCompletionRate()` を呼ぶ形にする★（テストの中に規則を写さない）。Q9 の「`task_area = 0` は null」→ 0 |
| `lib/db/__tests__/report-mapping.test.ts:318` / `:689-700`（w11） | 表3 の w11（実施1・件数1・Σ に 0） |
| `lib/db/__tests__/clean-area-source.test.ts:226` / `:252`（F5） | 表3 の F5（completion_rate 0.0） |
| `lib/db/__tests__/fixture-clean-area.test.ts:145` | 「実施回数に入り、完了率の件数にも入る（0% として）」に |
| ★`completion_rate: null, departed: true` を直接渡しているテスト★ — `lib/judge/__tests__/rescue.test.ts:27` / `:227` / `:241`、`lib/db/__tests__/report-mapping.test.ts:91-93` / `:1101` / `:1119`、`lib/db/__tests__/rescue-mapping.test.ts:41` | ★入力の組み合わせは DB からは来なくなったが、テストは残す★（「未実施は `departed` で数える」ことの確認。D3）。ラベルの「`task_area = 0`」を「`departed` と `completion_rate` が食い違う行（防御）」に直すだけ。期待値は変えない |
| `lib/clean-area/__tests__/aggregate.test.ts:19` / `:183`（A-4） | 渡された Σ と件数から出すテストなので中身はそのまま。説明だけ（「`clean_ratio` から出る」→「渡された Σ と件数から出る」） |

---

## 段階3 — 当期の KPI をサマリと同じ式にする（P-6）

### 3-1. 判定層（`lib/judge/kpi.ts`）

1. 冒頭（`:4-24`）の式を `CLAUDE.md` §4.2（`CLAUDE.md:161-166`）と同じ4行に直す。「★3つの率はサマリと同じ式（第11回 P-6）★」と、旧の式（実施回数 ÷ 予定・定刻出発 ÷ 予定・エラー ÷ 実施）を1行で残す
2. `operationRate()`（`:32-35`）: 引数を `(completed, planned)` に。docstring「稼働率 = 正常終了（`status = 4`）の回数 ÷ 清掃予定回数（P-6=A）」
3. `launchRate()`（`:37-40`）: 引数を `(started, planned)` に。docstring「出発成功率 = 開始できた回数（レポート行がある回数）÷ 清掃予定回数。★時刻の条件なし（P-6=A）★」
4. `errorRate()`（`:155-158`）: 引数を `(errors, planned)` に。docstring「エラー発生率 = エラー発生回数 ÷ ★清掃予定回数★（P-6=B）。★上限で切らない（D9）★。旧: 分母は実施回数」
5. ★`isCompletedTask()` を足す★（D5）:
   ```ts
   import { CLEAN_TASK_STATUS, FACILITY_STATUS_THRESHOLDS, RUN_COMPLETION_RATE } from "@/lib/config";
   /** その清掃が正常終了したか（稼働率の分子。P-6=A）。★status だけで決める。面積は見ない★ */
   export function isCompletedTask(report: { status: number } | undefined): boolean {
     return report !== undefined && report.status === CLEAN_TASK_STATUS.completed;
   }
   ```
   docstring に: 出所（第11回 P-6=A・送付版 `:87`）／★「最新の1行」で見る★（呼び出し側が `latestReportByTimetableId()` で選んだ行を渡す）／★◎○△× の判定には使わない★（D-155 項2 は変わらない）／★面積は見ない（表2 K8）★
6. `judgeFacilityStatus()`（`:178-203`）は ★中身を変えない★。ただし docstring に「★当期の稼働率は P-6 で『正常終了 ÷ 予定』になった（以前は出発成功率と同じ値）。当期の施設ステータスは以前より低めに出る★」と1行足す

### 3-2. 集計（`lib/db/report-mapping.ts`）

1. `CleanStatsBucket`（`:245-299`）:
   - ★冒頭の docstring `:245-256` を書き直す★（D-157 の `:249` もここで直る。段階5 を参照）。新しい中身:
     - 当期の3つの率はサマリと同じ式（第11回 P-6。D-158）。どれも ★分母は `planned`★
     - `planned` ＝ 期間内・過去の本清掃の予定の数／`executed` ＝ そのうちレポート行がある数（★開始できた回数＝出発成功率の分子★）／★`completed` ＝ そのうち最新のレポート行の `status` が正常終了の数（稼働率の分子）★／`errorCount` ＝ エラーの数（エラー発生率の分子）
     - ★時刻の条件は無い★（P-6=A）。`clean_task_reports.started_at` は、予定のある清掃では予定の `start_at` と同じ値が入る（ユーザー回答 2026-10-05。D-155 項5 の N-9）ので、★定刻かどうかはそもそも区別できない★
     - ★不変条件: `completed ≤ executed ≤ planned`★（だから 稼働率 ≤ 出発成功率 ≤ 100）
   - `executed`（`:260`）の docstring「清掃実施回数（＝定刻出発回数。上記コメント参照）」→「開始できた回数（レポート行がある回数）。出発成功率の分子」
   - ★`completed: number` を足す★（`executed` の直後）
   - `errorCount`（`:266-282`）の docstring: `:278-280` は段階5 の文に差し替える。★分母は予定回数★になったことを書く
2. `emptyCleanStatsBucket()`（`:301-311`）・`addCleanStats()`（`:313-323`）に `completed` を足す
3. `CleanStatsReportLike`（`:394-416`）に ★`status: number`★ を足す（D7）。docstring に「★`status` は稼働率の分子（`isCompletedTask()`）にだけ使う★」
4. `aggregateCleanStatsByFacility()`（`:460-505`）:
   - `const completed = executed && isCompletedTask(report);`（`isCompletedTask` は `@/lib/judge` から import。`:16-24` の import に足す）
   - `completed: bucket.completed + (completed ? 1 : 0),`
   - ★未来の予定は `completed` にも足さない★（`:480-483` の `continue` の前に何も足さない。D10）
   - docstring（`:418-459`）の `:422-424`「稼働率・出発成功率が実際より下振れする」は ★エラー発生率も★ に
5. `kpiFromCleanStats()`（`:507-535`）:
   ```ts
   operation_rate: toKpiValue(operationRate(bucket.completed, bucket.planned), prev?.operation_ratio ?? null),
   launch_rate:    toKpiValue(launchRate(bucket.executed, bucket.planned),     prev?.launch_ratio ?? null),
   completion_rate: toKpiValue(meanCleanRatio(bucket), prev?.clean_ratio ?? null),   // ★変えない★
   error_rate:     toKpiValue(errorRate(bucket.errorCount, bucket.planned),    prev?.error_ratio ?? null),
   ```
   docstring `:510-511`「★エラー発生率の分母は「実施回数」のまま★（予定回数にしない）」は ★逆の仕様★。「★3つとも分母は予定回数（第11回 P-6。サマリと同じ）★」に
6. `countMainErrorTypesInRange()` の docstring `:946-947`「エラー率の分母は「清掃実施回数」…なので、分子も清掃を基準にする」→「エラー率の分母は清掃予定回数（P-6=B）なので、分子も予定（本清掃）を基準にする」。★中身は変えない★（期間の帰属は本清掃の `start_at` のまま）

### 3-3. データ層（`lib/db/index.ts`）

- `reportKpis()`（`:2042-2140`）・`facilityPerformance()`（`:2330-2400`）は ★コードを変えない★（`findMany` に `select` が無いので `status` は載っている。D7）。`aggregateCleanStatsByFacility()` の引数も変わらない
- `errorBreakdown()` の docstring `:2493`「エラー率の分母が「清掃実施回数」なので」→「清掃予定回数（P-6=B）なので」

### 3-4. 説明を直す

| ファイル:行 | 直し方 |
|---|---|
| `lib/config/index.ts:337-347`（`CLEAN_TASK_STATUS`） | `:343-344`「★★判定には使わない★★」を「★◎○△× の判定・未実施の判定には使わない。稼働率の分子（正常終了の回数。`isCompletedTask()`）にだけ使う（第11回 P-6=A）★」に。`:368-369`（6 は「第11回で確認」）は ★段階5 の C5 で直す★（§11 Q4） |
| `lib/reports/target-customer.ts:10-17` の表 | 出発成功率: 分子 `launch_count`／分母 `task_count`（★P-6=A で確定★）／合算「材料はある」。稼働率: 分子 ★列が無い（正常終了の回数）★／分母 `task_count`／★できない★。エラー発生率: 分子 `error_count`／分母 ★`task_count`（P-6=B）★／合算「材料はある」。★表の下に「4指標を揃えて出せない（稼働率・清掃完了率の材料が無い）ので、合算しない方針は変えない」と書く★（★方針は変えない★。§11 Q3） |
| `d-watch-api.yaml:740-741`（`GET /reports/kpi`） | 「稼働率の分子(清掃実施回数)とエラー率の分母に当たる列がサマリに無く」→「稼働率の分子(正常終了の回数)と清掃完了率の分子・分母(1回ごとの完了率の合計・件数)に当たる列がサマリに無く」 |
| `d-watch-api.yaml:1896-1903`（`ReportKpi`）/ `:1905-1907`（`FacilityPerformance`） | `description` に4つの式（`CLAUDE.md` §4.2）と「確定期間はサマリの値そのまま／当期はアプリが同じ式で集計(第11回 P-6。D-158)」を書く。★`$ref` の隣に `description` を書いても無視される★（OpenAPI 3.0）ので、★スキーマ本体の `description` に書く★ |
| `d-watch-api.yaml:1924-1926`（`status`） | 「当期の稼働率は正常終了 ÷ 予定」を1行 |
| `lib/types/index.ts:713-718`（`ReportKpi`） | 各フィールドに1行の docstring（4つの式）。型は変えない |
| `scripts/seed-local-db.ts:1838-1848` / `:1875-1876` | ★サマリのシード（作り値）の辻褄★: `error_count = round(task_count * error_ratio / 100)`（分母＝予定）に直し、コメント `:1844` を「分母=予定回数（第11回 P-6=B）」に。`:1843` の `executedCount` は「稼働率の分子＝正常終了の回数」と読み替えるコメントに。★シードの接続先・ガード（`lib/db/local-guard.ts`）には触らない★。★シードを流すのは任意★（流すなら `dwatch_local` だけ。ステージングには絶対に向けない。`CLAUDE.md` §3） |
| `app/api/reports/__tests__/route.test.ts:634-635` | 当期のエラー発生率: 分子1・★分母は予定2★ → ★`50`★（旧 100）。`:626-628`（稼働率・出発成功率 50）は、スタブのレポート行が `status: 4`（`:330`）なのでそのまま 50 のはず。★落ちたら status を確かめる★。★スタブのコメント `:327-329`「意味を持たない任意の生値…集計・未実施の判定には使われない」は逆になった★ので、「★稼働率の分子（正常終了）に使われる★。4 以外にすると当期の稼働率が変わる」に直す |
| `lib/db/__tests__/report-mapping.test.ts` の既存テスト | `toEqual<CleanStatsBucket>({...})` に ★`completed` を足す★（`:364-383` / `:416-424` ほか。`tsc` が全部教える）。`CleanStatsReportLike` のリテラル行に ★`status: CLEAN_TASK_STATUS.completed`★ を足す（status を見るテスト以外）。`:755-800` の `kpiFromCleanStats`: ★`:790-795` の「executed=0 のときエラー発生率も null」は逆の仕様★ → planned>0 なら 0.0、planned=0 なら null |
| `lib/judge/__tests__/kpi.test.ts` | 段階1 で済み |

★`lib/fixtures/reports.ts` の KPI（`:159-373`）は作り値で、当期も「顧客単位の集計値」として置いてある★（`lib/db/fixture-reports.ts:68-69`）ので ★式を変えても画面の数字は変わらない★。
★値は1か所だけ直す（§11 Q5）★: 新しい式では ★稼働率 ≤ 出発成功率★ が必ず成り立つが、`:256-257`（施設6 バリューマート南丘駅前店）だけが
稼働率 72.4（前期 70.1）＞ 出発成功率 65.0（前期 68.0）になっている（2026-10-05 監督が全行を走査。他の行は成り立っている）。

- ★出発成功率の側を上げる★: `launch_rate: toKpiValue(65.0, 68.0)` → ★`toKpiValue(75.0, 71.0)`★。★稼働率（72.4 / 70.1）・`status`・並び順は変えない★
- ★稼働率の側を下げないこと★（§11 Q5 の当初の例「72.4 → 60.5」は採らない）: 施設別成績は稼働率の昇順に並べてあり、
  `lib/fixtures/__tests__/reports.test.ts:212`（「稼働率の昇順（ワースト順）に並んでいる」）が守っている。
  60.5 にすると直前の施設9（68.0。`:245`）より小さくなり、このテストが落ちる。並べ替えると画面のワースト1位が変わる
- ★`lib/fixtures/__tests__/reports.test.ts` にテストを1件足す★: 施設別成績の全行と KPI の全期間で、`operation_rate` と `launch_rate` が
  ともに数値なら `value` も `prev` も ★稼働率 ≤ 出発成功率★（作り値が新しい式の不変条件を破らないことを固定する）
- ★`65.0` / `68.0` を期待値に持つテストは無い★（2026-10-05 監督が grep で確認）。落ちるテストが出たら値を確かめてから報告する

---

## 段階3b — 予定を `clean_timetables` と `clean_timetable_histories` の両方から読む（P-7。D-158 項4）

★予定（本清掃・事前チェックの行）は2つの表を合わせて読む★。★同じ `id` は1件として扱い、`clean_timetables` 側を採る★。
★実績は `clean_task_reports` だけのまま★（D-151。`clean_statuses` / `clean_status_histories` は読まない）。
★期間の絞り込みは今と同じく `start_at`★（`end_at` で絞る経路は今も無い。足さない）。

#### 3b-0. 2つのモデルの違い（`prisma/schema.prisma:587-619` / `:621-650`）

| 列 | `CleanTimetable` | `CleanTimetableHistory` | アプリの読み |
|---|---|---|---|
| `id` | BigInt UNSIGNED・★AUTO_INCREMENT★ | BigInt UNSIGNED・★AUTO_INCREMENT 無し★（`:622-624`） | 読む（突き合わせのキー） |
| `facility_id` / `section_id` / `clean_schedule_id` / `parent_id` | 同じ型・NOT NULL | 同じ | 読む |
| `start_at` / `end_at` | DateTime(0) NOT NULL | 同じ（索引名だけ `started_at`/`ended_at`。`:642-648`） | `start_at` を読む |
| `precheck` | UnsignedTinyInt・既定 0 | 同じ | 読む |
| `inactive` | UnsignedTinyInt | ★無い★ | ★読んでいない★（どの経路も `inactive` で絞らない。`lib/db/index.ts:726` / `:1193`） |
| `created_at` / `updated_at` | Timestamp / ★`updated_at` あり★ | `created_at` のみ・★`updated_at` 無い★ | どちらも読んでいない |
| リレーション | `clean_schedule` / `facility` / `section` | ★同じ3つ★ | `include: { facility, section }` はどちらにも書ける |

★アプリが使う列は全部、履歴側にもある★。★`where` に `inactive` を書くと履歴側で型エラーになる★（それでよい。書かないこと）。
★他のモデルから `clean_timetables` へのリレーションは無い★（`clean_task_reports` / `robot_error_reports` / `clean_statuses` の `clean_timetable_id` は FK 無し）ので、`include` 経由の読みは0件。

#### 3b-1. 読んでいる箇所（★全12本。これ以外に無いことを `grep -rn "cleanTimetable\." app lib` で確かめる★）

| # | 場所 | 何で絞るか | 何に使うか |
|---|---|---|---|
| 1 | `lib/db/index.ts:765` `cleanLogs()` | 本清掃・`start_at` 範囲・`id`（単一）・`facility_id IN`。`include: facility, section.robots.device`。`orderBy` あり | 詳細ログ F-02・ダッシュボード F-01・アラート F-03・`/clean-logs/{id}/advice`・`/{id}/areas` |
| 2 | `lib/db/index.ts:831` `cleanLogs()` | `parent_id IN`（塊ごと）。`select: id, parent_id, precheck` | 1 の事前チェック行（`status_1h` / `status_30m`） |
| 3 | `lib/db/index.ts:1104` `areaCompletions()` | 本清掃・`facility_id`・1日ぶんの `start_at`。`include: section` | エリア別完了率モーダル |
| 4 | `lib/db/index.ts:1230` `prechecks()` | 本清掃・`start_at` 範囲・`facility_id IN`。`include: facility, section.robots` | 事前チェックアラート・出発失敗 |
| 5 | `lib/db/index.ts:1271` `prechecks()` | `parent_id IN`（塊ごと）。`select` 3列 | 4 の事前チェック行 |
| 6 | `lib/db/index.ts:1448` `precheckItemById()` | `findUnique({ id })`。`select` 3列 | `PATCH /prechecks/{id}/status`（指示書AT） |
| 7 | `lib/db/index.ts:1457` `precheckItemById()` | `findFirst({ id: parent_id, precheck: 本清掃 })`。`select: start_at` | 6 の親（`scheduled_at`） |
| 8 | `lib/db/index.ts:2067` `reportKpis()` 当期 | 本清掃・`start_at` 範囲・`facility_id IN`。`select` 3列 | レポート KPI（当週・当月・当年） |
| 9 | `lib/db/index.ts:2333` `facilityPerformance()` 当期 | 8 と同じ | 施設別成績 |
| 10 | `lib/db/index.ts:2552` `errorBreakdown()` 当期 | 8 と同じ | エラー内訳 |
| 11 | `lib/db/consecutive-source.ts:356` `loadConsecutiveInputs()` | 本清掃・`start_at`（遡りぶんを足した範囲。無制限もありうる）。`select` 4列 | 連続日数・L1〜L3（`cleanLogs` `:1001` / `rescueRequired` `:1834` / `facilityPerformance` `:2236` / `continuousProblems` `:2457`）・要レスキュー |
| 12 | `lib/db/clean-area-source.ts:399` `loadCleanAreaFacilityStatsWithRowCount()` | `id IN`（レポートの `clean_timetable_id`）＋本清掃。`select: id` | 総清掃面積 F-08 の「本清掃に紐づく行か」（指示書AS 段階4） |

★`count` / `groupBy` / `skip` / `take` で `clean_timetables` を読む箇所は0件★（`take`/`skip` は P1 では SQL に降ろさない決定。`lib/db/index.ts:743-745`）。
★`/schedules` は `clean_schedules` だけを読む★（`lib/db/index.ts:1759-1797`）。予定表（timetable）は読まない → ★本段階の対象外★。

★12 は今のままだと静かに壊れる★: レポートの `clean_timetable_id` が履歴にしか無いと「本清掃に紐づかない行」になり、★完了率・実施回数から黙って落ちる★（面積だけ残る）。

#### 3b-2. 共通の読み口を1つ作る（`lib/db/timetable-source.ts`。新規）

1. ★純粋関数★ `mergeTimetableRows<T extends { id: bigint }>(mainRows: T[], historyRows: T[], shadowedIds: ReadonlySet<string>): T[]`
   - `mainRows` を全部採る。`historyRows` は ★`id.toString()` が `mainRows` にも `shadowedIds` にも無いものだけ★足す
   - ★`historyRows` の中で同じ id が2回出たら例外★（履歴の主キーは `id` なので起きない。起きたら呼び出しの誤り）
   - ★並び順は保証しない★（下の 3 を参照）
2. `findTimetables(prisma, args)` — ★同じ `args` を `cleanTimetable.findMany` と `cleanTimetableHistory.findMany` に `Promise.all` で渡す★
   - `args` の型は ★両モデルの FindMany 引数の両方を満たすもの★（`satisfies` で両方に通す、または `ConsecutiveSourcePrisma`（`consecutive-source.ts:105`）と同じ「最小の形」の interface を書く）。★`where` を2回書かないこと★（片方だけ直すと静かにずれる）
   - ★`args.select` / `args.include` には `id` が必ず含まれること★（型で強制する）
   - ★「`clean_timetables` 側が勝つ」を厳密に守るための影チェック★: 履歴だけに出た id を `chunkForInClause()` で分けて `cleanTimetable.findMany({ where: { id: { in } }, select: { id: true } })` を撃ち、★当たった id を `shadowedIds` にする★。
     理由: 同じ id で `start_at` や `precheck` が2表で違うと、`clean_timetables` 側は `where` から外れ、履歴側だけが当たる。影チェックが無いと★負けるはずの履歴の行が出る★。★履歴だけの id が0件ならクエリを撃たない★
   - 戻りは `mergeTimetableRows()` の結果
3. `orderBy`: ★渡してよいが、合わせた後の順序は保証されない★。1（`cleanLogs()`）は `:1017-1022` の `.sort()` が正なので問題ない。★他の11本は順序に依存しない★（集計・Map 化・ルート側の `sortPrechecks()`）。docstring の「`orderBy` はその写し」（`:798-803`）に「2表を合わせるので DB の順序はもう写しにもならない」と1行足す
4. ★`count` / `groupBy` を2表にまたがって書かない★（同じ id の重複を引けない）。必要になったら ★`findTimetables()` で id を取ってから数える★。今は該当0件
5. `precheckItemById()` 用に `findTimetableById(prisma, id, select)` を足す（`clean_timetables` を `findUnique` → 無ければ `cleanTimetableHistory.findUnique`）。7 は `findTimetableById(row.parent_id)` を引き、`precheck === 本清掃` を JS で確かめる（`findFirst` の条件をそのまま移す）

#### 3b-3. 各経路の書き換え

- 1・3・4・8・9・10・11: `prisma.cleanTimetable.findMany(args)` → `findTimetables(prisma, args)`。★`where` の中身は1文字も変えない★
- 2・5（`parent_id IN`）: 塊ごとに `findTimetables()` を呼び、★`.flat()` の後にもう一度 id で重複を落とす★
  （2表で `parent_id` が違う同じ id は別の塊に入りうるので、塊の中の重複落としだけでは足りない）。
  `findTimetables()` に `{ dedupeAcrossChunks }` を持たせるより、★timetable-source.ts に `findTimetablesByChunks(prisma, chunks, buildArgs)` を置き、全塊を2表とも引いてから1回だけ `mergeTimetableRows()` を通す★ほうが規則が1箇所で済む
- 6・7: 3b-2 の 5
- 12: `CleanAreaStatsSourcePrisma.cleanTimetable`（`clean-area-source.ts:147-153`）の隣に `cleanTimetableHistory` を足し、`findTimetables()` を使う。★`where: { id: { in }, precheck }` は両表に同じものを渡す★
- 11: `ConsecutiveSourcePrisma`（`consecutive-source.ts:105-125`）に `cleanTimetableHistory` を足す
- `robot_error_reports` / `clean_task_reports` の引き直し（`clean_timetable_id IN`）は ★変えない★（合わせた後の id で引くので、履歴の予定に紐づく行も取れる）

#### 3b-4. 性能

- ★往復は1本→2本（＋履歴だけの id があれば影チェック）★。`Promise.all` で並べる。両表とも `start_at` / `facility_id` / `parent_id` に索引あり（`:612-617` / `:641-648`）
- ★古い予定が履歴へ移るなら、行の大半（保持2年）は履歴側★。★期間無指定の経路★（`cleanLogs()` の日付なし・`CONSECUTIVE_RANGE_UNBOUNDED`・12 の期間なし）は★履歴も全期間読む★。今と同じ性質なので新たに絞らないが、`judge.*` ログの行数が増えることを docstring に書く
- `IN` の分割は既存の `chunkForInClause()`（上限 `DB_IN_CLAUSE_MAX_ITEMS = 1000`。`lib/config/index.ts:548`）をそのまま使う

#### 3b-5. fixtures とシード

- ★fixtures には予定の表の層が無い★（`lib/fixtures/` は結合済みの `CleanLogItem` を持つ。`lib/fixtures/clean-logs.ts:222` の `history` は★「過去の清掃ログ」の意味で、`clean_timetable_histories` とは無関係★。名前で取り違えないこと）。
  → ★fixtures 版のデータソースは変えない★。`USE_FIXTURES` の true/false で数字は変わらない
- `scripts/seed-local-db.ts`: ★`clean_timetable_histories` を今は一切書かず、消してもいない★（`:848-858`）。
  1. `cleanSchedule.deleteMany` より前（`:854` の隣）に `cleanTimetableHistory.deleteMany({})` を足す（FK が `clean_schedules` に向いているため）
  2. 次の3種を入れる（★id は既存の採番と衝突しないことをコードで検査する。`:1525-1532` の dangling 検査に履歴も足す★）:
     (a) ★履歴だけの予定★: 過去の本清掃1件とその事前チェック2行を `clean_timetables` に入れず履歴に入れる。`clean_task_reports` を1行紐づける
     (b) ★両方にある同じ予定（中身も同じ）★
     (c) ★両方にある同じ id で中身が違う★（履歴側の `start_at` を1日ずらす）
  3. 末尾の件数ログ（`:2062`）に `clean_timetable_histories=` を足す

#### 3b-6. テスト（★実装より先に書く★）

`lib/db/__tests__/timetable-source.test.ts`（新規。スタブの prisma で）:

| # | 入力 | 期待 |
|---|---|---|
| T1 | 本表だけに id=1 | id=1 が1件 |
| T2 | 履歴だけに id=2 | id=2 が1件（影チェックで本表に無い） |
| T3 | 両方に id=3（同じ中身） | ★1件★。中身は本表のもの |
| T4 | 両方に id=4（`start_at` が違う。どちらも期間内） | ★1件・本表の `start_at`★ |
| T5 | id=5: 本表は期間外、履歴は期間内 | ★0件★（影チェックで履歴を捨てる） |
| T6 | 履歴だけの id が0件 | 影チェックのクエリを撃たない |
| T7 | 2表に同じ `where` が渡る | 両方の呼び出し引数が `toEqual` で一致 |
| T8 | `parent_id IN` の塊をまたいで同じ id | flat 後に1件 |

既存のスタブ（★`cleanTimetableHistory` が無いと `undefined` で落ちる★）: `app/api/{clean-logs,clean-logs/[id],dashboard,prechecks,prechecks/[id]/status,reports,clean-area}/__tests__/route.test.ts`・`lib/db/__tests__/{consecutive-source,clean-area-source,report-mapping}.test.ts`。
履歴は空配列を返すスタブを足し、★`calls.cleanTimetable` の引数を見ているテストには `calls.cleanTimetableHistory` も同じ引数であることを足す★。
経路ごとに1本ずつ「履歴だけの予定が出る」ケースを足す（12 は「履歴だけの予定に紐づくレポートが完了率・実施回数に入る」）。

#### 3b-7. 監督の決定（2026-10-05）

| # | 論点 | 決定 |
|---|---|---|
| Q1 | 履歴の id は本表の id か | ★**本表の id をコピーしている（ユーザー回答 2026-10-05）**★。★id で重複を除く設計で正しい★。同じ id で `facility_id` / `section_id` / `start_at` のどれかが違ったら `[db]` の warn を出す（Q2） |
| Q2 | 同じ id の中身が違うとき | ★本表を採り、warn ログを出す★（イベント名は既存の `db.*` の流儀に合わせる。id・どの列が違ったか・両方の値を出す。認証情報は出さない） |
| Q3 | 期間の基準 | ★`start_at` のまま★（期間の振り分けの基準を変えない。CLAUDE.md §4.7） |
| Q4 | 履歴の予定に紐づくエラー行も PATCH で書けるようになる | ★それでよい★（実在するアラートなので） |
| Q5 | `/schedules` | ★対象外★（`clean_schedules` だけを読む） |
| Q6 | 移る時期が未実測 | ★実装はどちらの表にあっても読めるので影響しない★。接続できたら行数・移る時期を実測して D-158 に記録する（監督） |

---

## 段階4 — 画面の見出し「屋号」を「顧客」にする（P-15）

### 4-1. i18n（`lib/i18n.ts`）— ★キーごと改名する（D12）★

| 行 | いま | ★新★ |
|---|---|---|
| `:54` | `屋号: "Brand"` | `顧客: "Customer"` |
| `:56` | `全屋号: "All Brands"` | `全顧客: "All Customers"`（§11 Q7） |
| `:358` | `"選べる屋号が{n}件あります": "{n} brands to choose from"` | `"選べる顧客が{n}件あります": "{n} customers to choose from"` |
| `:359` | `屋号を選ぶ: "Choose a brand"` | `顧客を選ぶ: "Choose a customer"` |
| `:360` | `この条件では選べる屋号がありません: "No brands available for this filter"` | `この条件では選べる顧客がありません: "No customers available for this filter"` |

- コメント `:44-52` を書き直す: 「★第11回 P-15=B（`customers` は法人の単位）で確定。2026-10-05 に見出し語を『屋号』から『顧客』に変えた（D-158）★」。旧の経緯（2026-09-15「屋号でよいです…」）は1行で残す。★英語は仮（D13）★
- コメント `:364` / `:375` の `屋号` を `顧客` に（キー名を指しているため）
- `:357` の `顧客を選んでください: "Select a customer"` は ★そのまま★

### 4-2. 呼び出し側

| ファイル:行 | いま | ★新★ |
|---|---|---|
| `lib/masters/options.ts:46` | `ALL_CUSTOMERS_LABEL = "全屋号"` | `"全顧客"`（docstring `:33` の「全屋号」も） |
| `components/features/clean-area/CleanAreaScreen.tsx:66` | `customer: "屋号"` | `customer: "顧客"`（粒度タブ `:356` と表の見出し `:385` に出る）。docstring `:61` の「ホールディングス / 屋号 / 店舗」も |
| `components/features/clean-area/CleanAreaScreen.tsx:238` | `aria-label={t("屋号")}` | `t("顧客")` |
| `components/features/alerts/AlertsScreen.tsx:392` | 同上 | 同上 |
| `components/features/detail-log/DetailLogScreen.tsx:370` | 同上 | 同上 |
| `components/features/ds-alerts/DsAlertsScreen.tsx:181` | 同上 | 同上 |
| `components/features/ds-logs/DsLogScreen.tsx:346` | 同上 | 同上 |
| `components/features/reports/ReportsScreen.tsx:376` | 同上 | 同上 |
| `components/features/reports/ReportsScreen.tsx:509` / `:522` / `:530` | `t("選べる屋号が{n}件あります")` / `t("屋号を選ぶ")` / `t("この条件では選べる屋号がありません")` | 4-1 の新キー |
| `d-watch-api.yaml:51` / `:905`（清掃面積の粒度） | 「ホールディングス/屋号/店舗」 | 「ホールディングス/顧客/店舗」 |
| `d-watch-api.yaml:172-177`（`GET /customers`） | 「屋号一覧」「画面の見出し語は「屋号」…TeamWorks 照会中(第9回 Q7-1)」 | 「顧客一覧」「画面の見出し語は「顧客」(第11回 P-15=B: `customers` は法人の単位。2026-10-05。D-158)。旧: 「屋号」(2026-09-15)」。★照会中の記述は消す★ |
| `d-watch-api.yaml:1680`（`Customer`） | 「屋号(`customers`)」 | 「顧客(`customers`。法人の単位。第11回 P-15)」 |

- `lib/hooks/use-classification-filter.ts:161` は `t(ALL_CUSTOMERS_LABEL)` なので ★直さなくてよい★
- ★`t()` に生の文字列 `"屋号"` を渡している所が他に無いこと★を最後に確かめる: `grep -rn "\"屋号\|屋号\"\|'屋号" app lib components --include=*.ts --include=*.tsx | grep -v __tests__` が ★0件★
### 4-3. データ・絞り込み条件の説明（★§11 Q2: 「顧客」にそろえる★）

P-15=B で `customers` は法人の単位と確定したので、「屋号（`customers.id`）」という説明は ★誤り★ になった。
★次の行の中の「屋号」だけを「顧客」に直す★（`所属屋号` → `所属顧客`、`屋号・施設は` → `顧客・施設は` など。★行の他の部分は変えない★）:

- `lib/clean-area/query.ts:58`・`lib/clean-logs/query.ts:31`・`lib/ds-alerts/query.ts:25`・`lib/ds-devices/query.ts:21`・`lib/facilities/query.ts:28`・
  `lib/prechecks/query.ts:32`・`lib/reports/query.ts:120`・`lib/schedules/query.ts:53`・`lib/api/master-id.ts:89`
  （どれも `/** 屋号（\`customers.id\`）… */`）
- `lib/types/index.ts:180` / `:196` / `:954` / `:985` / `:1299` / `:1308-1310`
- `d-watch-api.yaml:1519-1520` / `:1534` / `:2086` / `:2100-2101` / `:2287`

★経緯を書いた文（「旧: 屋号」「2026-09-15『屋号でよいです』」等）は残す★。★処理の理由を述べるコメント・fixtures の説明・テストは対象外★（§7 の表）。

- テスト `lib/masters/__tests__/options.test.ts:157-181` の `"全屋号"` は ★引数で渡しているだけなので直さなくても通る★。直すなら `"全顧客"` に（任意）。`"屋号A1"` などはテスト用の名前なのでそのまま

---

## 段階5 — D-157 に控えた古いコメント4件（＋ status 6 の C5）

★どれもコメントだけで、動作は変わらない★（C5 も同じ）。行番号は 2026-10-05（`74cffd5`）で確かめた。

| # | 場所 | いま | ★新しい文（この中身で書く）★ |
|---|---|---|---|
| C1 | `lib/db/clean-area-source.ts:424-425` | 「`clean_task_reports.clean_timetable_id` は nullable で、★TeamWorks がまだ埋めていない★（指示書AQ 決定5 — 「今後埋める前提」）」 | 「`clean_task_reports.clean_timetable_id` は nullable（`prisma/schema.prisma:524`）。★NULL は予定外のタスク★（予定を持たない実在の清掃。ユーザー回答 2026-10-01。`CLAUDE.md` §4.7）で、★埋まる予定は無い（NULL の行は常にある）★。旧: 「TeamWorks が今後埋める前提」（指示書AQ 決定5）— 誤り」。★続く `:426-429`（NULL が1つのグループにまとまる）は今も正しいので残す★ |
| C2 | `lib/fixtures/clean-task-reports.ts:24` | 「★CSV では189行すべて NULL★（今後 TeamWorks が埋める前提。決定5）」 | 「★CSV では189行すべて NULL★。★NULL は予定外のタスク★（予定のある清掃には id が入る。`CLAUDE.md` §4.7。旧: 「今後 TeamWorks が埋める前提」— 誤り）」 |
| C3 | `lib/db/report-mapping.ts:245-256`（`:249` を含む段落） | 「`clean_timetables.started_at` は常に予定時刻と同値でシードされており…稼働率と出発成功率は同じ `executed` を分子に使う」（★`clean_timetables.started_at` は存在しない列★） | ★段階3-2 の1. で段落ごと書き直す★。時刻の記述は「`clean_task_reports.started_at` は、予定のある清掃では予定の `start_at` と同じ値、予定外のタスクでは実際の開始時刻が入る（ユーザー回答 2026-10-05。`CLAUDE.md` §4.7）。★出発成功率は時刻を見ない（P-6=A）★」 |
| C4 | `lib/db/report-mapping.ts:278-280`（`errorCount` の注意） | 「`clean_error` が 2（セキュリティ有効）/ 3（D-S制御盤異常）の清掃は `robot_error_reports` の行が作られないため…『エラー発生率』は実質『ロボット異常の発生率』」（★いまは読んでいない `clean_error` 列が前提★） | 「★清掃の結果は `clean_task_reports` だけから読み、`clean_error` 列は読まない★（D-151）。★制御盤・セキュリティのエラー、清掃を開始できなかったエラーも `robot_error_reports` に入り★（第11回 P-8・P-10）、★`clean_timetable_id` も入る★（P-9=A）。★本清掃の予定の id が入った行は、区分を問わずこの件数に入る★（どの予定の id が入るかは無回答）。★区分（ロボット／制御盤／セキュリティ・清掃開始失敗）の見分けと D-Security アラートの取得元の切り替えは次フェーズ★（D-158 当方の対応5）。旧: 第8回 A6-1「`clean_error` 2/3 の清掃は行が作られない」— 第11回で変わった」 |
| C5 | ★status 6 の説明3か所（§11 Q4）★: `lib/config/index.ts:368-369`（`CLEAN_TASK_STATUS_LABELS` の docstring）・`lib/fixtures/clean-task-reports.ts:25`（表の `status` の行）・`lib/format/clean-result.ts:14` | 「意味は未受領。第11回で確認」／「★6 は未受領★」 | 「★6 は Pudu の値で、先方も意味が分からない（第11回 P-4。D-158）★。分かれば先方がデータを入れる。★数字のまま表示する★」。★`:369` の続き「「その他」「不明」に潰さないこと」は残す★。★`cleanTaskStatusLabel()` の中身・`CLEAN_TASK_STATUS_LABELS` の値は変えない★ |

---

## 段階6 — 全体の確認

1. `npx tsc --noEmit -p .` — エラー0件
2. `npm run lint` — 指摘0件（重複キー・`$queryRaw`・`any` が無いこと）
3. `npm test` — ★全件通過★（表1・表2・表3 を含む）
4. ★`USE_FIXTURES=true` で `npm run build`★ — 成功（PowerShell なら `$env:USE_FIXTURES='true'; npm run build`）
5. ★画面で確かめる（`USE_FIXTURES=true`）★: `.claude/launch.json` の ★`d-watch-fixtures`（ポート 3100）★ で起動する（Claude Code なら `preview_start` の `d-watch-fixtures`）。★確かめ終わったら、自分で起動した dev サーバーは必ず止める★
   - ★屋号→顧客★: 清掃詳細ログ・アラート・D-Sec ログ・D-Sec アラート・レポート・総清掃面積の6画面で、分類のセレクトの先頭が「全顧客」。総清掃面積の粒度タブと表の見出しが「顧客」。レポートで顧客を選ばずに KPI が 422 になる状態（fixtures は顧客4件）で「選べる顧客が4件あります」「顧客を選ぶ →」
   - ★JP/EN 切替★: EN で "All Customers" / "Customer" / "4 customers to choose from" / "Choose a customer"。★JP では幅が変わらないこと★（文字数が同じ）。★EN でツールバーが不自然に折り返さないこと★をデスクトップ幅とモバイル幅（375px）で見る。崩れていたら直さずに報告（4-12）
   - ★数字が変わらないこと★: fixtures には `task_area = 0` で実施した行が無く（`lib/db/fixture-clean-area.ts:20-25`）、レポートの KPI は作り値（`lib/fixtures/reports.ts`）なので、★詳細ログ・エリア別詳細・総清掃面積・レポートの数字は変わらないのが正しい★。指示書AS 段階7-5 の境界値（施設2 = 90% ◎ / 施設3 = 90% ○ / 施設5 = 70% ○ / 施設8 = 10% △ / 施設9 = 10% × / 施設10 = 0% ×、エリア別詳細の施設7・1日前が全体 81% ○）がそのまま出ること
   - ★P-5・P-6 の効き目は画面では見えない★（上の理由）。★単体テスト（表1〜表3）で確かめる★。任意で、シードを入れたローカルDB（`dwatch_local`。`USE_FIXTURES=false`）のレポート当期で「稼働率 ≤ 出発成功率」になっていることを見てよい（★ステージングには向けない★）
6. 最後の grep（★0件になること★）:
   ```
   grep -rnE "status ?===? ?4([^0-9]|$)|定刻出発|清掃実施回数 ÷ 清掃予定|÷ 清掃実施回数" app lib components d-watch-api.yaml --include=*.ts --include=*.tsx --include=*.yaml | grep -v lib/generated
   grep -rn "t(\"屋号\|t(\"全屋号\|\"選べる屋号\|\"屋号を選ぶ\|選べる屋号がありません" app lib components --include=*.ts --include=*.tsx
   ```

---

## 7. 「屋号」の出現箇所の分類（2026-10-05。`grep -rn "屋号" app lib components --include=*.ts --include=*.tsx`。テストを除き約140件）

| 分類 | 場所 | 扱い |
|---|---|---|
| ★画面に出る文字列（i18n のキー・既定の見出し語）★ | `lib/i18n.ts:54` / `:56` / `:358-360`、`lib/masters/options.ts:46`、`components/features/clean-area/CleanAreaScreen.tsx:66` | ★直す★（段階4） |
| ★画面に出る文字列の呼び出し（`t("…")`）★ | `aria-label` 6件（`AlertsScreen.tsx:392` / `CleanAreaScreen.tsx:238` / `DetailLogScreen.tsx:370` / `DsAlertsScreen.tsx:181` / `DsLogScreen.tsx:346` / `ReportsScreen.tsx:376`）、`ReportsScreen.tsx:509` / `:522` / `:530` | ★直す★ |
| ★見出し語そのものを説明するコメント★ | `lib/i18n.ts:44-52` / `:364` / `:375`、`lib/masters/options.ts:33`、`CleanAreaScreen.tsx:61` / `:374` | ★直す★ |
| ★API 定義のうち見出し語・`customers` の単位を述べる所★ | `d-watch-api.yaml:51` / `:172-177` / `:905` / `:1680` | ★直す★ |
| データ・絞り込み条件の説明（「屋号（`customers.id`）で絞り込み」など） | `lib/*/query.ts`（`clean-area:58` / `clean-logs:31` / `ds-alerts:25` / `ds-devices:21` / `facilities:28` / `prechecks:32` / `reports:120` / `schedules:53`）、`lib/api/master-id.ts:89`、`lib/types/index.ts:180` / `:196` / `:954` / `:985` / `:1299` / `:1308-1310`、`d-watch-api.yaml:1519-1534` / `:2086` / `:2100-2101` / `:2287` | ★直す★（段階4 の 4-3。★列挙した行だけ。一括置換しない。§11 Q2★） |
| 処理の理由・経緯を述べるコメント（「この屋号の昨夜の成績」「屋号が0件のときは出さない」など） | `components/features/*/*.tsx` の JSX コメント、`lib/hooks/use-classification-filter.ts`、`lib/masters/options.ts`（`:2-26` / `:105-190`）、`app/api/**/route.ts`、`lib/db/*.ts`、`lib/facilities/*.ts`、`lib/clean-area/aggregate.ts`、`lib/logger/events.ts:218`、`lib/judge/kpi.ts:51` | ★残す★ |
| fixtures の説明 | `lib/fixtures/customers.ts` / `groups.ts` / `facilities.ts` / `reports.ts:203-212` | ★残す★（顧客名の値もそのまま） |
| テスト | `app/api/**/__tests__` / `lib/**/__tests__`（約120件） | ★残す★（`t()` のキーを文字列で比べているテストは無い。2026-10-05 確認） |
| モック | `d-watch-mock.jsx`（「全会社」） | ★触らない★（自前の辞書を持ち、アプリの i18n に依存しない） |

---

## 8. テスト一覧（本書で足す・直すもの）

| ファイル | 内容 | 段階 |
|---|---|---|
| `lib/judge/__tests__/run-completion-rate.test.ts` | 表1 R9 差し替え・R12 追加 | 1 |
| `lib/judge/__tests__/kpi.test.ts` | 3つの率の分母・`errorRate` 上限なし・`isCompletedTask()` | 1 |
| `lib/db/__tests__/report-mapping.test.ts` | 表2 K1〜K8・稼働率 ≤ 出発成功率／既存の `completed`・`status` の追加／w11（表3）／`kpiFromCleanStats` のエラー発生率 | 1 / 2 / 3 |
| `lib/db/__tests__/clean-log-mapping.test.ts` | `task_area = 0` → 0／不変条件が両向き | 2 |
| `lib/db/__tests__/consecutive-source.test.ts` | Q9・テスト内の再実装を消す | 2 |
| `lib/db/__tests__/clean-area-source.test.ts` | F5（表3） | 2 |
| `lib/db/__tests__/fixture-clean-area.test.ts` | `:145` | 2 |
| `lib/judge/__tests__/rescue.test.ts` / `lib/db/__tests__/rescue-mapping.test.ts` | ラベルだけ（期待値は変えない） | 2 |
| `app/api/reports/__tests__/route.test.ts` | 当期のエラー発生率 50 | 3 |
| `lib/fixtures/__tests__/reports.test.ts` | ★作り値の全行で 稼働率 ≤ 出発成功率（`value` / `prev`）★（§11 Q5） | 3 |
| `lib/db/__tests__/timetable-source.test.ts`（新規）ほか 3b-6 の一覧 | T1〜T8・既存スタブに `cleanTimetableHistory` | 3b |
| `robotErrorReport` を読む経路のテスト | スタブに `robot_id: null` の行を混ぜても落ちない（段階0 の 4） | 0 |

---

## 9. 触ってはいけないもの

- `CLAUDE.md` / `docs/decisions.md`（監督が直す）
- `prisma/schema.prisma` / `lib/generated/prisma/**`（★DB の定義は TeamWorks の領域。`CLAUDE.md` §7★。P-8 の DDL 変更の取り込み（db pull）も本書ではやらない）
- `docs/code-map.md`（監督が直す）
- ★D-Security アラートの取得元（いまは `control_error_histories`）★ — `robot_error_reports` への切り替え（P-10）は ★次フェーズ★（D-158 当方の対応5）
- ★`robot_error_reports` の3区分（ロボット／制御盤／セキュリティ・清掃開始失敗）の見分け★ — ★次フェーズ★。`error_type` を推測で分類しない（`CLAUDE.md` §4.8）
- 4カラム UPDATE（`status` / `comment` / `responded_at` / `resolved_at`。指示書AT）
- `kpiFromSummaryRatios()` / `SummaryRatios` / `periodOfSummaryRow()`（`lib/db/report-mapping.ts:99` 付近・`:218-241`）
- `meanCleanRatio()` の中身・`judgeGrade()` / `displayRate()` / `GRADE_THRESHOLDS` / `RUN_COMPLETION_RATE` の値
- `latestReportByTimetableId()` / `notExecutedFromReport()` / `isFutureSchedule()` の中身（`lib/db/clean-log-mapping.ts`）
- `launchFailedDaysInRun()` / `foldRescueDays()` の中身（D3）
- `scripts/seed-local-db.ts` の ★接続先とガード★（`lib/db/local-guard.ts` を含む）
- `d-watch-mock.jsx`

---

## 10. 範囲外（本書ではやらない。監督が扱う）

- P-8〜P-10（エラーの集約・3区分・D-Sec アラートの取得元）— 次フェーズ
- 複数顧客の KPI の合算（出発成功率・エラー発生率は材料がそろった）— §11 Q3

---

## 11. 監督の決定（2026-10-05。D-158）

| # | 論点 | 決定 |
|---|---|---|
| Q1 | 表3 を正にするか | ★正として固定する★ |
| Q2 | 「屋号（`customers.id`）」というデータの説明 | ★「顧客」にそろえる★（P-15 で `customers` は法人と確定したので、「屋号」は誤りになった）。経緯を書いた文（「旧: 屋号」等）は残す。★対象の行は段階4 の 4-3★ |
| Q3 | 複数顧客の KPI 合算 | ★変えない★（指示書AK の方針のまま。本書は表の記述を直すだけ） |
| Q4 | 「status 6 は未受領・第11回で確認」のコメント | ★直す★: 「6 は Pudu の値で先方も意味不明（第11回 P-4）。分かれば先方がデータを入れる。数字のまま表示」 |
| Q5 | `lib/fixtures/reports.ts:256` の稼働率 > 出発成功率 | ★直す★。★出発成功率の側を上げる（65.0 → 75.0 / 前期 68.0 → 71.0）★。不変条件のテストを1件足す（段階3-4 の表の下）。★改訂（2026-10-05）★: 当初は「稼働率側を下げる（72.4 → 60.5）」としたが、稼働率の昇順を守るテスト（`lib/fixtures/__tests__/reports.test.ts:212`）が落ち、並べ替えれば画面のワースト1位が変わるため取り下げた |
| Q6 | `robot_id` が DDL では NULL 可・Prisma では NOT NULL | ★段階0 に足す: `robotErrorReport` の読み取りはすべて `select` で必要な列だけにし、`robot_id`・`robot` リレーションを読まない★（NULL の行が入っても読み取りで落ちないように）。★`prisma db pull` は監督がステージングに接続できるようになってから行う★（本書ではスキーマを変えない） |
| Q7 | EN の大文字小文字 | ★"All Customers" / "Customer"★（既存の "All Holdings" にそろえる。英訳はユーザーが後でまとめて見直す） |
| Q8 | 稼働率の式が変わり、施設ステータス（閾値 good 90 / warning 75。`FACILITY_STATUS_THRESHOLDS`。★訂正 2026-10-06: 当初「90/80」は誤り★）と稼働率ワースト順が以前より悪く出る | ★閾値は据え置く★（変えるならユーザー判断。監督からユーザーに伝える） |
| 範囲を広げた3点 | 完了率の「暫定」→「確定」の記述 / シードの `error_count` の分母 / `runCompletionRate()` の戻り値を `number` に | ★3点とも採る★ |

## 12. コミット

段階ごとに分ける（段階0 の 4 / 段階1 / 段階2 / 段階3 / 段階3b / 段階4 / 段階5）。★`git add` はファイルパスを明示する★（`git add -A` 禁止）。
メッセージには ★何を変えたか★ と ★なぜ（D-158。TeamWorks 第11回 P-5 / P-6 / P-15）★ を書く。★段階1 のコミットは「テストだけ（この時点では落ちる）」と明記する★。
★分割 PR の作成とマージは daichi がレビューしてから行う★（D-154）。
