# 指示書X — `clean_timetables.precheck` を「分数そのもの」として読む（コード表を廃止する）

| 項目 | 内容 |
|---|---|
| 実行環境 | ★**Cursor 内の Claude が実装します**★（`.claude/commands/d-watch.md` の役割分担表を参照） |
| 実行状態 | ★**完了（D-113。2026-09-11）**★ |
| 実行順 | ★★**本書（指示書X）を指示書W より先に実行すること**★★（★理由は下の「実行順」節★）。★★**2本を同時に走らせないこと**★★。★**TeamWorks 第7回回答（2026-09-11 受領）の A5-1〜A5-5 を反映し、あわせて訂正版DDL（0911版）に追従する作業**★。★指示書S（D-105）が作った `lib/db/precheck-mapping.ts` と、指示書R（D-102）が作った `lib/db/index.ts` の `status_1h` / `status_30m`、指示書M〜V が育てた `scripts/seed-local-db.ts` の上に積みます★。★★`lib/fixtures/` / `d-watch-api.yaml` は**1文字も変えません**★★（★API契約は既に「任意の分数を取りうる」と書かれており、本書の変更に追従済み。段階0 で確認する★）。★★`prisma/schema.prisma` は**段階3 の `db pull` で更新します**★★（★手書きはしない。D-056★） |
| 対象 | ★★`precheck` の**値の意味が現行実装と真逆**になっていたのを直す★★。★`1` を「60分前」と読んでいたが、正しくは「**1分前**」★ |
| 取得する決定番号 | ★**着手直前に `grep -nE "^### D-(0\|1)[0-9][0-9]" docs/decisions.md \| tail -3` で最終番号を実測すること**★。★★`^### D-0` だけでは3桁台（D-100 以降）を拾えない★★。★2026-09-07 に `D-068` の二重採番が実際に起きています★。★作成時点の最終番号は **D-112**（`docs/decisions.md:6706`。★別担当が `08acc89` でコミット済み★）★で、★★指示書W が **D-113** を取る見込み★★なので、★本書の実行記録は **D-114** の見込み★。★★ただし実行順によって変わるので、必ず自分で実測してから採番すること★★ |
| 前提 | ★★**D-112**（`docs/decisions.md:6706`。★第7回回答の受領記録。**本書の出所そのもの**★。★★項1 が「現行コードは真逆の変換をしている」と既に指摘しており、本書はその修正です★★。★着手時に行番号を実測し、内容が本書 §「TeamWorks 第7回回答（原文）」と食い違っていたら**実装を止めて確認を求めること**★）★★ / ★**D-091 項4**（`docs/decisions.md:3546`。★「0なら0分前(本番清掃)、60なら60分前とする予定」というユーザー原文★）★ / ★**D-093 項2**（`:3719` 以降。★分数ごとの enum も固定ラベルも持たない＝案A の確定★。★案C「60/30 以外はその他に丸める」は**否定済み**★）★ / ★**D-105**（`:5435`。指示書S の実行記録。★項1 の「新旧2つの値域を1つの式で吸収する」/ 項13-1・13-2 が本書で**論点ごと消滅**する箇所★）★ / **D-077**（`:1847`。エラー値を変換しない）/ **D-089 項3・D-104 項2**（★「その他」「不明」という語を使わない★）/ **D-100**（MariaDB 10.11 とローカル MySQL 8.4 の差。★生SQLを増やさない★）/ **D-111**（`:6584`。指示書W の前提。★本書とはコードが重ならないが、`CLEAN_TIMETABLE_PRECHECK.main` を共有する★）/ **D-042**（`:226`。書き込み許可4カラム）/ **D-050**（`:308`。段階ごとに検証してからコミット）/ **D-056**（`:353`。`schema.prisma` は生成物）/ ★★**D-094**（`:3822`。0909版DDL の照合）/ **D-095**（`:3978`。`dwatch_local` を DDL から作り直した前例）/ **D-096**（`:4117`。指示書Q の実行記録。★段階3 の手順の下敷き★）/ **D-059**（`:392`。実DDL を正とし `db pull` の生成結果を採用する）★★ |
| 段階数 | ★**7段階**★（段階0〜段階6）。★コミットは段階1 / 2 / 3 / 4 / 6 の**5つ**★ |
| ★着手前確認★ | ★★**なし**★★ — ★値域の規則は TeamWorks の第7回回答（A5-1〜A5-5）で確定しており、★★「混在しません」「0以外の予約語はありません」と明言されています★★★。★★ユーザーの回答を待たずに段階0 から通しで実行してよい★★。★★「事前チェックは今後も1清掃につき2回か」も**確定済み**です★★ → ★★ユーザー回答（2026-09-11）「**これは2回だけです、3回以上にはしません**」★★。★したがって `status_1h` / `status_30m` の**2列固定でよい**（後述「申し送り」①）★ |
| 着手前の基準 | ★**着手直前に自分で `npm test` / `npx tsc --noEmit` / `npm run lint` を実行し、その実測値を基準にすること**★。★作成時点の実測（2026-09-11。★鵜呑みにしない★）: `npm test` = **45ファイル / 928テスト（全パス）** / `tsc` = **0** / `lint` = **0**★ |
| 作成 | 2026-09-11（★改訂 2026-09-11: 訂正版DDL 0911版の受領を受けて段階3 を追加、事前チェック2回固定の確定を反映★）。★作成時点の決定事項最終番号 **D-112**（`docs/decisions.md:6706`）/ `docs/handoff.md` の最新は **追記33**（`docs/handoff.md:2774`）。★着手時に実測すること★★ |

> 実行後はこの表と、文書末尾の「実行状態」表を更新してください。

> ★**この指示書を読んでいるあなたへ**★ — `.claude/commands/d-watch.md` の「実装は Cursor 側」は
> **2つの環境の役割分担**です。**Cursor の中で動いている Claude は実装担当**です。
> ★調査や改訂ではなく実装を行ってください。★
> ★★**うまくいかなければ段階の切れ目（コミット単位）で止めて構いません**★★ —
> ★段階1（変換関数）→ 段階2（`status_1h` / `status_30m`）→ 段階3（DB作り直しと `db pull`）→
> 段階4（シードと `rounds` の削除）の順に、
> ★どこで止めても `tsc` / `lint` / `test` が緑になる形★に並べてあります★。
> ★★ただし段階3 は `dwatch_local` を DROP して空にするので、★段階3 で止めるならローカルDBが空のままになることを
> 作業メモに残すこと★（★段階4 の再シードで埋まります★）★★。

---

## ★★冒頭に読むべきこと — 何が起きているか★★

★★現行実装は `clean_timetables.precheck` の `1` を「60分前」、`2` を「30分前」と読んでいます。
これは TeamWorks の指示と**真逆**です。★★

TeamWorks 第7回回答（★2026-09-11 受領。原文★）:

```
A5-1. 認識合っています.
A5-2. 既にclean_timetablesに格納するデータは本清掃開始〇分前の〇部分の数値が入るように修正済みです.
A5-3. 既存・新規も含めてすべて〇分前の〇が格納されます. もし1や2が格納されていたら1分, 2分前としてください.
A5-4. 混在しません.
A5-5. 0以外の予約語はありません. すべて本清掃を基準に何分前開始の値が格納されます.
```

★★読み取れること（★これが本書の全部です★）★★:

| # | 確定した事実 | 現行実装 | 直し方 |
|---|---|---|---|
| ★1★ | ★`0` = 本清掃★ | ★同じ★ | ★変えない★（`CLEAN_TIMETABLE_PRECHECK.main`） |
| ★2★ | ★`0` 以外は ★**その数値がそのまま「分前」**★★ | ★`1` → 60分前 / `2` → 30分前★（★**真逆**★） | ★★生値をそのまま返す★★ |
| ★3★ | ★★`1` が入っていたら **1分前**★★（A5-3） | ★60分前と読む★ | ★★**1** を返す★★ |
| ★4★ | ★★新旧は**混在しない**★★（A5-4） | ★「混在に耐える」設計になっている（D-105 項1）★ | ★★吸収する仕掛けごと**消す**★★ |
| ★5★ | ★★`0` 以外に**予約語は無い**★★（A5-5） | ★コード表（`rounds`）を引いている★ | ★★対応表を引く処理を**消す**★★ |

★★帰結: `CLEAN_TIMETABLE_PRECHECK.rounds`（＝コード表）は**不要になります**★★。★本書はこれを削除します★。

---

## ★★この変更で「意味が変わる値」と「変わらない値」★★

★★ここが本書のいちばん大事な節です。★★

| `precheck` の値 | ★現行の解釈★ | ★★新しい解釈★★ | 変わるか |
|---|---|---|---|
| `0` | 本清掃（`precheck_minutes = null`） | ★同じ★ | ★変わらない★ |
| ★`1`★ | ★★60分前★★ | ★★**1分前**★★ | ★★**変わる（真逆）**★★ |
| ★`2`★ | ★★30分前★★ | ★★**2分前**★★ | ★★**変わる（真逆）**★★ |
| `60` | 60分前（コード表に無いので生値） | ★60分前★ | ★変わらない★ |
| `30` | 30分前（コード表に無いので生値） | ★30分前★ | ★変わらない★ |
| `20` | 20分前（生値） | ★20分前★ | ★変わらない★ |
| `255` | 255分前（生値） | ★255分前★ | ★変わらない★ |

★★つまり「実DB（既に 0/60/30）に対しては、直す前も直した後も答えが同じ」★★です。
★★変わるのは `1` / `2` が入っていたときだけ★★ — ★そして `1` / `2` が入っているのは
★★ローカルの `dwatch_local` だけ★★です（★実測: `0`=259 / `1`=253 / `2`=253★）。

★★だから段階4（シードの作り直し）が要ります★★ — ★★ローカルDBを実DBと同じ形にしないと、
「正しく直った」ことを実測で確認できません★★。

---

## ★調査済みの事実（★現物で裏を取ってから使うこと★）★

### 1. ★★`PRECHECK_MINUTES_BEFORE` は他でも使われている → **消さない**（★実測 2026-09-11★）★★

★★本書で最も踏みやすい間違いは「`precheck` の変換に使わなくなったから設定値ごと消す」ことです。★★
★★消してはいけません。★★ ★以下の消費者がいます（`grep -rn "PRECHECK_MINUTES_BEFORE"` の実測）★:

| # | 消費者 | 用途 | 本書での扱い |
|---|---|---|---|
| ★1★ | ★`components/features/system/SystemScreen.tsx:32` / `:131`★ | ★★システム管理画面の「事前チェック設定」の表示★★（「第1回チェック / 60分前」「第2回チェック / 30分前」）。★★＝ **DFA 側の設定値**（何分前にチェックするか）★★ | ★★**残す。1文字も変えない**★★ |
| ★2★ | ★`lib/fixtures/prechecks.ts:1` / `:8`★ | ★fixtures の分数を直書きしないための別名★ | ★★**残す。`lib/fixtures/` は変更禁止**★★ |
| ★3★ | ★`scripts/seed-local-db.ts:33` / `:804`★ | ★事前チェック行の `start_at`（本清掃の何分前か）を作る★ | ★★**残す**★★。★★段階4 では、この同じ値を `precheck` カラムにも入れるようになる★★ |
| ★4★ | ★`lib/db/index.ts`（★段階2 で**新たに使う**★）★ | ★★どの `precheck` 行を `status_1h` / `status_30m` に割り当てるかの**選択キー**★★ | ★★段階2 で追加★★ |
| ★5★ | ★`lib/db/precheck-mapping.ts:15` / `:88`★ | ★★`precheck` コード → 分数の**変換表**★★ | ★★**この用途だけ消す**（段階1）★★ |
| 6 | `lib/prechecks/__tests__/label.test.ts` / `lib/prechecks/__tests__/query.test.ts` | テストの入力値を直書きしないための参照 | ★残す★ |

★★言い換えると★★: ★`PRECHECK_MINUTES_BEFORE` は「★**DFA がどのタイミングでチェックしたいか**★」を表す設定値として残り、
★★「DBの値を分数に読み替える対応表」という役割だけを失います★★。
★★docstring にこの区別を明記すること★★（★段階4★）。

### 2. ★ローカルDB（`dwatch_local`）の現状（★2026-09-11 実測★）★

| 項目 | 値 |
|---|---|
| ★`clean_timetables.precheck` の分布★ | ★★`0` = **259** / `1` = **253** / `2` = **253**★★（★★古い値のまま★★） |
| `robot_error_reports` の件数 | ★14★ |
| ★`robot_error_reports.error_id` の形★ | ★★`RER-<本清掃id>-<precheckコード>`（例 `RER-1011-1` / `RER-1011-2`）★★ |
| `db.cleanLogs()` の件数 | ★259★ |
| `db.prechecks()` の件数 | ★★27（`precheck` **14** / `launch_failed` **13**）★★ |
| ★`prechecks()` の `precheck_minutes` の内訳★ | ★★`60` = **8件** / `30` = **6件** / `null` = **13件**★★ |
| ★`cleanLogs()` の `status_1h` の内訳★ | ★`Standby.` = 251 / セキュリティ有効 2 / オフライン 3 / `Handrail Lifted` 1 / 充電中 1 / 緊急停止 1（★**非 Standby. = 8件**★）★ |
| ★`cleanLogs()` の `status_30m` の内訳★ | ★`Standby.` = 253 / セキュリティ有効 2 / オフライン 3 / `Handrail Lifted` 1（★**非 Standby. = 6件**★）★ |

★★実測コマンド★★（★着手直前に自分で取り直すこと★）:

```
"/c/Program Files/MySQL/MySQL Server 8.4/bin/mysql.exe" -u root --default-character-set=utf8mb4 dwatch_local -e "
SELECT precheck, COUNT(*) c FROM clean_timetables GROUP BY precheck ORDER BY precheck;
SELECT COUNT(*) FROM robot_error_reports;
SELECT error_id FROM robot_error_reports ORDER BY error_id LIMIT 5;"
```

### 3. ★★段階4 の前後で「変わる数字」と「変わらない数字」（★先に予告してから実測すること★）★★

★★この表が本書の合否判定です。★★ ★★特に #5・#6 が「変わらない」ことが、「正しく直った」ことの証明です★★ —
★シードが `60` / `30` を入れ、変換関数がそれを生値で返すので、★★往復して同じ分数に戻る★★。

| # | 項目 | ★着手前★ | ★★段階4（再シード）後の期待★★ |
|---|---|---|---|
| ★1★ | `clean_timetables.precheck` の分布 | `0`=259 / `1`=253 / `2`=253 | ★★`0`=**259** / `60`=**253** / `30`=**253**★★（★`1` / `2` は **0件**★） |
| ★2★ | `clean_timetables` の総行数 | 765 | ★★**765**（変わらない）★★ |
| ★3★ | `robot_error_reports` の件数 | 14 | ★★**14**（変わらない）★★ |
| ★4★ | ★`robot_error_reports.error_id` の値★ | `RER-1011-1` / `RER-1011-2` … | ★★`RER-1011-**60**` / `RER-1011-**30**` …（★★**変わる**★★）★★ |
| ★5★ | ★★`db.prechecks()` の件数と内訳★★ | 27（precheck 14 / launch_failed 13） | ★★**27（14 / 13）— 変わらない**★★ |
| ★6★ | ★★`prechecks()` の `precheck_minutes` の内訳★★ | `60`=8 / `30`=6 / `null`=13 | ★★**`60`=8 / `30`=6 / `null`=13 — 変わらない**★★ ← ★★**これが「正しく直った」証明**★★ |
| ★7★ | `db.cleanLogs()` の件数 | 259 | ★★**259**（変わらない）★★ |
| ★8★ | ★`cleanLogs()` の `status_1h` の非 `Standby.` 件数★ | 8 | ★★**8**（変わらない）★★ |
| ★9★ | ★`cleanLogs()` の `status_30m` の非 `Standby.` 件数★ | 6 | ★★**6**（変わらない）★★ |
| ★10★ | `clean_status_histories.clean_error` の分布 | `0`=231 / `1`=13 / `2`=5 / `3`=5 / `4`=5 | ★★**変わらない**★★（★動いていたらシードの通番を壊している★） |
| ★11★ | `npm test` | 45ファイル / 928テスト | ★★**増える**★★（★減らないこと★） |
| ★12★ | `npx tsc --noEmit` / `npm run lint` | 0 / 0 | ★変わらない★ |

★★#4 だけが「変わってよい」値です★★ — ★`PrecheckItem.id` は `robot_error_reports.error_id` の生値（D-105 項2）なので、
★★アラート画面の行の id 文字列が `RER-1011-1` → `RER-1011-60` に変わります★★。
★これはローカル検証データの採番規則が実DBの値域に追従した結果であり、意図した変化です★。
★★段階6 の記録に必ず書くこと★★。

### 4. ★★段階1・2・3 の間はローカルDBの表示が一時的に崩れる（★想定内★）★★

★段階2 まで実装した時点で、コードは `precheck = 60 / 30` の行を探すようになりますが、
ローカルDBにはまだ `1` / `2` しか入っていません★。★★その間だけ★★:

- ★`prechecks()` の `precheck_minutes` が ★**`1` と `2`**★ になる（★＝ A5-3 のとおりに正しく読んでいる★）
- ★`cleanLogs()` の `status_1h` / `status_30m` が ★**全行 `Standby.`**★ になる（★60 / 30 の行が見つからないため★）

★★これは「壊れた」のではなく「古いデータを新しい規則で正しく読んだ」結果です★★。

★★段階3（DBの作り直し）を挟むと、今度は**DBが空になります**★★ — ★★`prechecks()` も `cleanLogs()` も **0件**★★。
★★これも想定内です★★。★★段階4 の再シードで解消します★★。

★`tsc` / `lint` / `test` はこの間（段階1・2・3）もすべて緑のままです★
（★テストは純粋関数と手書きの入力だけで書かれており、DBを見ないため★）。

★★段階2 または段階3 のコミットで止める場合★★は、★★ローカルDBがどの状態か（古い値のまま／空）をコミットメッセージか作業メモに残すこと★★。

### 5. ★落とし穴（★踏むとテストが通ったまま壊れます★）★

| # | 落とし穴 |
|---|---|
| ★1★ | ★★`PRECHECK_MINUTES_BEFORE` を消すこと★★ — ★★システム管理画面の「事前チェック設定」が消え、fixtures とシードが壊れます★★（上の調査1）。★消すのは `CLEAN_TIMETABLE_PRECHECK.rounds` **だけ**★ |
| ★2★ | ★★`CLEAN_TIMETABLE_PRECHECK.main` を消すこと★★ — ★`lib/db/index.ts` の5箇所・`scripts/seed-local-db.ts` の2箇所・`lib/db/precheck-mapping.ts` が使っています★。★★未実行の**指示書W** も `CLEAN_TIMETABLE_PRECHECK.main` を使う前提で書かれています★★（`docs/handoff/指示書W_…:664`。★★**読むだけ。編集しないこと**★★）。★`main` は設定値のまま残す（`0` を直書きしない。`CLAUDE.md` §7）★ |
| ★3★ | ★★`0` / `1` / `2` / `60` / `30` をコードに直書きすること★★ — ★`0` は `CLEAN_TIMETABLE_PRECHECK.main`、`60` / `30` は `PRECHECK_MINUTES_BEFORE` から読む★（`CLAUDE.md` §7）。★★テストの中でも直書きしないこと★★（★既存テスト `lib/db/__tests__/precheck-mapping.test.ts` の作法に揃える★）。★唯一の例外は「設定値以外の分数」を検証する `20` / `255` / `1` / `2`（★設定値と一致させると検証が成立しない★。前例: `lib/fixtures/prechecks.ts:90`）★ |
| ★4★ | ★★未知の値を「その他」「不明」に丸めること★★ — ★D-077 / D-089 項3 / D-104 項2★。★★TeamWorks は「0以外の予約語は無い」と明言している（A5-5）ので、丸める先そのものが存在しません★★ |
| ★5★ | ★★`status_1h` / `status_30m` という**フィールド名**を変えること★★ — ★★API契約（`d-watch-api.yaml`）です。本書の範囲外★★。★中身の作り方だけ変える★ |
| ★6★ | ★★`d-watch-api.yaml` を変えること★★ — ★★変える必要がありません★★。`:1188` は既に「`clean_timetables.precheck` は設定値に応じた任意の分数を取りうるため、分数ごとの enum は持たない」と書いてあり、★本書の変更に追従済み★（★段階0 で自分の目で確認すること★） |
| ★7★ | ★★段階1・2 の途中で `prisma/schema.prisma` を書き換えること★★ — ★★`schema.prisma` は `db pull` の生成物で、型・属性を手で書き換えません★★（`CLAUDE.md` §7 / D-056）。★`:581-583` / `:611-613` の「0: 本清掃 / 1: 1回目 / 2: 2回目」というコメントは ★**もう正しくありません**★ が、★★直すのは段階3-6（`db pull` 後のコメント移植）です★★（★コメントは `db pull` が生成しないので、人が書くのが正規の手順★）。★★段階3 より前に触らないこと★★ |
| ★8★ | ★★`lib/fixtures/` を1行でも変えること★★ — ★fixtures は `precheck_minutes`（＝ API の値）を直接持っており、DBのコード値を持っていません★。★★本書の変更の影響を受けません★★。★`USE_FIXTURES=true` の挙動は1つも変わらないのが正しい★ |
| ★9★ | ★★`??` を `\|\|` に書き換えること★★ — ★`0` が別の値に化けます★。★★本書は `0`（本清掃）が意味を持つコードを触るので特に危険★★ |
| ★10★ | ★★段階4 でシードの本ループに行を差し込むこと★★ — ★`historyIndex` / `errorReportCount` の通番の剰余で `clean_error` と対応状況を決定的に散らしています★。★★本書は**行を足しません**。既存行の `precheck` カラムの**値だけ**を変えます★★（★行数も順序も変えない★） |
| ★11★ | ★★シードで乱数を使うこと・id を暗黙採番にすること★★ — ★冪等が壊れます★。★★2回流して同じ結果になることを実測すること★★ |
| ★12★ | ★★`lib/db/precheck-mapping.ts` の他の関数（`resolutionStatusFromRobotErrorStatus` / `robotErrorStatusFromResolutionStatus` / `detectedAtFromError`）に手を付けること★★ — ★★範囲外。1文字も変えない★★ |
| ★13★ | ★★事前チェックが3回以上になったときの器を今作ること★★ — ★★範囲外。★★**3回以上にはならないと確定済み**★★（★ユーザー回答 2026-09-11「これは2回だけです、3回以上にはしません」★。申し送り①）★★。★★器を作る必要はありません★★。★本書は「1清掃につき事前チェック2回」を前提に進める★ |

---

## ★禁止事項（この指示書の全段階に効きます）★

| # | ★してはいけないこと★ | 理由 |
|---|---|---|
| ★1★ | ★★**`git add -A` を使うこと**★★ | `CLAUDE.md` §7。★過去に別作業の途中ファイルを巻き込む事故が起きている★。★★必ずファイルパスを明示すること★★ |
| ★2★ | ★★**`README.md` / `docs/code-map.md` / `docs/handoff/管理画面_設計検討_20260910.md` / `docs/handoff/指示書W_連続問題施設を実DBから作る.md` / `.claude/worktrees/**` に触ること**★★ | ★★**すべて別作業のもの**★★（★`README.md` は未コミットの変更あり、`docs/code-map.md` と `管理画面_設計検討_20260910.md` は未追跡（`git status` に `M` / `??` で出る）、`指示書W` は `08acc89` でコミット済みだが**別担当が実行する未実行の指示書**★）。★`docs/code-map.md:881-882` / `:1899` / `管理画面_設計検討:216` には `rounds` の記述が残りますが、★★本書では直しません★★（申し送り③）★。★**本指示書のコミットに含めない・編集しない・削除しない**★ |
| ★3★ | ★★**`docs/decisions.md` を段階6 より前に編集すること**★★ | ★★別担当が **D-112** を追記済み（`08acc89`）、**D-113**（指示書W）を追記する見込みです★★。★段階6 でも「自分の追記だけを足す」こと。★★着手時に未コミットの他人の追記が無いか `git status` で確認し、あれば巻き込むことをコミットメッセージに明記すること★★★ |
| ★4★ | ★★**メインDBのスキーマ変更（`prisma migrate` / `db push` / `prisma/schema.prisma` の型・属性の手書き）**★★ | `CLAUDE.md` §7 / D-056 / D-059。★★本書はスキーマ変更を1つも作りません★★（★`precheck` は `UnsignedTinyInt` のままで 0〜255 を表現できる★）。★★`prisma db pull` は**段階3 でのみ実行します**★★ — ★これはスキーマ「変更」ではなく、TeamWorks が作った DDL を**取り込むだけ**の操作です★（D-056 / D-059。★段階3 以外では実行しないこと★）。★★`prisma/schema.prisma` のコメントの移植・書き直しは段階3-6 の正規手順で、この禁止事項の対象外★★ |
| ★5★ | ★★**書き込み許可4カラム（`status` / `comment` / `responded_at` / `resolved_at`）以外への DB 書き込み**★★ | D-042。★段階4 の投入は `scripts/seed-local-db.ts`（＝ローカル検証用シード）からであり、アプリの実行時書き込みではない★。★★メインDB / `dwatch_ddl` に接続しないこと★★ |
| ★6★ | ★★**`0` / `1` / `2` / `60` / `30` をコードやテストに直書きすること**★★ | `CLAUDE.md` §7 / 落とし穴3 |
| ★7★ | ★★**未知の値を「その他」「不明」に丸めること・7分類に変換すること**★★ | D-077 / D-089 項3 / D-104 項2 / 落とし穴4。★★A5-5「0以外の予約語はありません」★★ |
| ★8★ | ★★**`PRECHECK_MINUTES_BEFORE` を削除・改名すること**★★ | ★DFA 側の設定値として別の用途で使われています★（調査1。★システム管理画面 / fixtures / シード★）。★消すのは `CLEAN_TIMETABLE_PRECHECK.rounds` だけ★ |
| ★9★ | ★★**`CLEAN_TIMETABLE_PRECHECK.main` を削除すること・`0` を直書きに戻すこと**★★ | 落とし穴2。★★未実行の指示書W も参照します★★ |
| ★10★ | ★★**`status_1h` / `status_30m` のフィールド名を変えること**★★ | ★API契約（`d-watch-api.yaml`）。本書の範囲外★ |
| ★11★ | ★★**`d-watch-api.yaml` / `lib/types/index.ts` を変えること**★★ | ★★変える必要がありません★★（落とし穴6）。★変えたくなったら、それは本書の解釈が間違っているサインなので**止めて確認を求めること**★ |
| ★12★ | ★**`lib/fixtures/` を1行でも変更すること**★ | `CLAUDE.md` §9.5 / 落とし穴8 |
| ★13★ | ★**`lib/db/prisma.ts` / `local-guard.ts` / `use-fixtures.ts` / `ds-status.ts` / `clean-log-mapping.ts` を変更すること**★ | ★完成しており、そのまま効く★。★★`robotStatusFromErrors()` は `clean-log-mapping.ts` から**import して使う**（コピーしない。D-103）★★ |
| ★14★ | ★**`prismaSource` の `cleanLogs` / `prechecks` 以外のメソッドに手を付けること**★ | ★範囲外★ |
| ★15★ | ★**テストを DB 依存にすること**★ | ★遅く・不安定になり、DB が空の環境で落ちる★。★テストは純粋関数と手書きの入力だけ★ |
| ★16★ | ★**`npm run dev` を Bash で起動すること**★ | ★Browser pane の `preview_start` を使う★。★無ければ curl で代替し、その旨を記録する★ |
| ★17★ | ★**`.env.local` をコミットすること / DB の認証情報を文書やコードに書くこと**★ | `CLAUDE.md` §9.3。★検証後は `USE_FIXTURES=true` に戻す★ |
| ★18★ | ★★**一時スクリプトを `main().catch(...)` だけで終わらせること**★★ | ★★成功時にも必ず `process.exit(0)` を書くこと★★ — ★Prisma の接続が残り、★★実際に4時間39分ハングした事故が起きています★★★ |

---

## 作業の順序（★崩さないこと★）

★段階0（前提の確認。★読むだけ・コミットなし★）
→ 段階1（★`precheckMinutesFromCode()` を「生値をそのまま返す」に直す★。★対応表を引く処理を消す★）
→ 段階2（★`status_1h` / `status_30m` の割り当て規則を `PRECHECK_MINUTES_BEFORE` 基準にする★）
→ ★★段階3（訂正版DDL（0911版）で `dwatch_local` を作り直し、`db pull` / `generate` をやり直す）★★
→ 段階4（★シードを `0` / `60` / `30` にし、★★`CLEAN_TIMETABLE_PRECHECK.rounds` を削除★★、再シードする★）
→ 段階5（実測での検証。★コミットなし★）
→ 段階6（記録と push）★

★★段階1・2・3・4・6 でそれぞれ1コミット（計5コミット）★★。
★**段階ごとに `npx tsc --noEmit` / `npm run lint` / `npm test` を実行し、
通ってからコミットすること**★（D-050。`docs/decisions.md:308`）。

> ★★**期待値表 → テスト → 実装 の順序を崩さないこと**★★（`CLAUDE.md` §6-2 / §9.4）。
> ★★特に段階1 は「テストを先に書き、**落ちることを確認してから**実装する」こと★★ —
> ★★既存テスト（`lib/db/__tests__/precheck-mapping.test.ts:26-30`）は
> `rounds` を使って自分自身を検証しているので、実装を反転させても
> 「`rounds` を `[60,30]` にする」だけで**通ってしまいます**★★。
> ★先に「`1` → `1`」という落ちるテストを書かないと、壊れたまま緑になります★。

> ★★**なぜこの順番なのか**★★
> - ★段階1 が先頭なのは、★★これが仕様の本体（値の意味の反転）★★だから。★ここだけで実DBに対しては正しくなる★
> - ★段階2 を分けるのは、★★`status_1h` / `status_30m` は「変換」ではなく「割り当て」という別の問題★★だから。
>   ★1コミットにまとめると、段階5 の実測でどちらが効いたか分からなくなる★
> - ★★段階3（DBの作り直し）を段階2 と段階4 の間に置くのは、★DROP するとデータが消えるので、
>   「着手前の実測（段階0）」を取り終えたあと、かつ「再シード（段階4）」の直前でなければならない★★★から。
>   ★★DBを作り直したあと空のまま放置する時間を最短にする★★（★段階3 → 段階4 は続けて実行すること★）
> - ★段階4 を最後の実装段階に置くのは、★★`CLEAN_TIMETABLE_PRECHECK.rounds` の消費者が
>   段階1・2 で1つずつ減り、段階4 で最後の1つ（シード）が消えて初めて安全に削除できる★★から。
>   ★逆順にすると `tsc` が落ちます★

> ★★**段階の並びは実装者が判断して入れ替えてよい**★★（★上は推奨順です★）。
> ★★ただし1つだけ動かせない制約があります★★ —
> ★★**DBを作り直す前に、既存の実測値を必ず控えておくこと**★★（段階0 / 段階3-2）。
> ★DROP した瞬間に取り返しがつきません★。

---

## ★★実行順 — 本書を指示書W より先に実行すること★★

★★本書（指示書X）を、指示書W（`docs/handoff/指示書W_連続問題施設を実DBから作る.md`）より**先に**実行してください★★。

★理由★: ★★ローカルDB（`dwatch_local`）の `precheck` が実DBと違う値（`1` / `2`）のままだからです★★。
★先に本書で `0` / `60` / `30` に揃えないと、★★指示書W の検証が本番とずれた土台の上で行われます★★。
★本書は段階3 でDBそのものも訂正版DDL（0911版）に作り直すので、なおさら先に済ませる必要があります★。

★★**2本を同時に走らせないこと**★★ —
★`docs/decisions.md` / `docs/handoff.md` を両方が触ります★。
★★また本書の段階3 は `dwatch_local` を DROP するので、指示書W が同時にDBを見ていると壊れます★★。
★指示書W は ★**読むだけ。編集しないこと**★★（禁止事項2）。

---

## 段階0 — 前提の確認（★読むだけ・コミットなし★）

### 読むもの

| # | 対象 | 見る点 |
|---|---|---|
| 1 | ★`CLAUDE.md` 全文★ | ★特に ★**§4.8（エラー種別は変換しない）**★ / ★**§7（禁止事項。閾値のハードコード禁止・推測で実装しない）**★ / §9.4（判定ロジックの作法）/ §9.3（`any` 禁止）★ |
| 2 | ★`docs/decisions.md` の ★**D-091 項4**（`:3546`）★★ | ★★ユーザー原文「0なら0分前(本番清掃)、60なら60分前とする予定」★★。★本書が実現する変更の出所★ |
| 3 | ★★**D-093 項2**（`:3719` 以降。★見出しを実測すること★）★★ | ★★案A（`precheck_minutes` を数値で持ちラベルを組み立てる）が確定した経緯★★。★★案C（60/30 以外を「その他」に丸める）が**否定済み**であること★★ |
| 4 | ★**D-105**（`:5435`）の ★項1★ / ★項13-1・13-2★★ | ★★本書で**論点ごと消滅する**箇所★★（段階6 で記録する）。★項1 の「新旧2つの値域を1つの式で吸収している」/ 項13-1「新旧混在の有無」/ 項13-2「設定値と DB のどちらが正か」★ |
| 5 | ★★**D-112**（`docs/decisions.md:6706`。第7回回答の受領。★行番号を実測すること★）★★ | ★★A5-1〜A5-5 の原文が本書 §「TeamWorks 第7回回答」と一致することを確認する★★。★★食い違っていたら実装を止めて確認を求めること★★。★★項1 が「現行コードは真逆」と既に整理しているので、本書の直し方と突き合わせること★★ |
| 6 | ★**D-111**（`:6584`）★ | ★指示書W の前提★。★`CLEAN_TIMETABLE_PRECHECK.main` を共有するので、削除しないことの裏付け★ |
| 7 | ★`docs/handoff.md` の末尾（★追記33★。`:2774`）★ | ★指示書V の完了内容と申し送り★ |
| 8 | ★`docs/handoff/指示書V_連続日数を施設単位に揃え当期の分母から未来を外す.md`★ | ★★段階の切り方・期待値表の書き方・記録の作法★★（★本書はこれに揃えてあります★） |
| 9 | ★`lib/config/index.ts:27-54` 全文★ | ★`PRECHECK_MINUTES_BEFORE`（`:34`）と `CLEAN_TIMETABLE_PRECHECK`（`:49-54`）の docstring★。★★「D-091 項4 / D-093 項2 で 0/60/30 に変わる予定」という記述が**実現した**こと★★ |
| 10 | ★`lib/db/precheck-mapping.ts` 全文（127行）★ | ★`precheckMinutesFromCode()`（`:82-99`）★。★★`:65-80` の「新旧2つの値域を1箇所で吸収する」という長い説明が**丸ごと不要になる**こと★★ |
| 11 | ★`lib/db/index.ts:267-313`（`cleanLogs()` の事前チェック行の索引と `status_1h` / `status_30m`）★ / ★`:430-490`（`prechecks()` の `precheck_minutes`）★ | ★`precheckByParent`（`:268-275`。★キーが `precheck` の生値★）/ ★`:307` の `CLEAN_TIMETABLE_PRECHECK.rounds.map(...)` による2要素分割代入★ |
| 12 | ★`scripts/seed-local-db.ts:190-223`（採番と設計判断）★ / ★`:787-830`（本清掃行と事前チェック行の投入）★ | ★`:804` の `PRECHECK_MINUTES_BEFORE.entries()` / `:807` の `CLEAN_TIMETABLE_PRECHECK.rounds[i]` / ★`:829` の `errorKey = RER-${log.id}-${precheckCode}`★★ |
| 13 | ★`lib/prechecks/label.ts` 全文（39行）★ | ★★`precheckSeverityLabel()` は `precheck_minutes` から `"{n}分前異常"` を組み立てるだけ★★。★★本書で1文字も変えません★★（★`1` なら「1分前異常」と出るのが正しい★） |
| 14 | ★`lib/db/__tests__/precheck-mapping.test.ts` 全文（113行）★ | ★★`:26-30` が `rounds` を使って自分自身を検証しており、実装を反転させても通ってしまうこと★★を自分の目で確認する（★上の「なぜこの順番なのか」の警告★） |
| 15 | ★`components/features/system/SystemScreen.tsx:120-142`★ | ★★`PRECHECK_MINUTES_BEFORE` の別用途（DFA 側の設定値の表示）★★。★★これがあるので設定値は消せない★★ |
| ★16★ | ★★`docs/handoff/指示書Q_スキーマを最新DDL0909に追従させ再シードする.md` 全文★★ | ★★段階3 の手順の下敷き★★。★特に 2-1（`db pull` に `--force` を付けない）/ 2-2（★コメント101行の移植★）/ 2-3（`@updatedAt` を DDL で確かめて戻す）/ 2-4（冒頭コメントの更新）/ 2-5（Laravel 基盤10テーブルを消さない）★ |
| ★17★ | ★`docs/handoff/指示書E_スキーマを実DDLに一致.md` の **手順3**（`:92-136`）と **手順4**（`:139-156`）★ | ★`db pull` が出力しないものを手で戻す作法★ |
| ★18★ | ★**D-094**（`:3822`）/ **D-095**（`:3978`）/ **D-096**（`:4117`）★ | ★★0909版DDL のときに「照合 → DB作り直し → `db pull` → 再シード」をどうやったかの記録★★。★段階3 は同じ道を通ります★ |
| ★19★ | ★★訂正版DDL `C:\Users\daich\Downloads\clean_watch_20260911.sql`★★（★2026-09-11 受領。★44,923 bytes★） | ★★0909版との `diff` を自分で取り、段階3-1 の表と一致することを確認する★★。★食い違ったら止めて確認を求めること★ |
| ★20★ | ★`docs/handoff/TeamWorks確認事項_第7回_20260910.md:180-205`★ | ★★A-2 / A-3b / B-2 / B-3b の4件が何を依頼したものかを確認する★★（★0911版で反映されたのは A-3b と B-2 の2件だけ★） |

### ★確認すること（★変えないことの確認★）★

| # | 対象 | ★確認内容★ |
|---|---|---|
| ★1★ | ★`d-watch-api.yaml:1186-1194`★ | ★★「`clean_timetables.precheck` は設定値に応じた任意の分数を取りうるため、分数ごとの enum は持たない」と既に書いてあること★★ → ★★**YAML の変更は不要**★★（禁止事項11）。★違っていたら止めて確認を求めること★ |
| ★2★ | ★`lib/types/index.ts` の `PrecheckItem.precheck_minutes`★ | ★`number \| null` であること★ → ★★型の変更も不要★★ |
| ★3★ | ★`lib/fixtures/prechecks.ts`★ | ★`precheck_minutes` を直接持っており、DBのコード値を持たないこと★ → ★★fixtures の変更は不要★★ |

### 接続先の確認とこの段階の完了条件

- ★`.env.local` の `DATABASE_URL` が `dwatch_local` を指していることを確認する★。
  ★**値そのものを画面・ログ・文書に出さないこと**★（`CLAUDE.md` §9.3）
- ★`.env.local` に `ALLOW_DIRECT_ACCESS=true` があることを確認する★（D-101 項2。★無いと段階5 で詰まる★）
- ★★着手前の基準値を自分で実測して控えた★★（`npm test` のファイル数・テスト数 / `tsc` / `lint`）
- ★★DBの現状を実測して控えた★★（上の調査2 のコマンド）

> ★★**ここで控えるのは「あとで比べるため」だけではありません**★★ —
> ★★段階3 で `dwatch_local` を **DROP** します★★。★★控え損ねた値は取り返しがつきません★★。
> ★★**DBを作り直す前に既存の実測値を控えておくことは必須です**★★（段階3-2）。
> ★段階の並びを入れ替える場合も、この制約だけは崩さないこと★。

- ★★`prisma/schema.prisma` の控えを取った★★（★段階3-2 #3・#4。`git show HEAD:prisma/schema.prisma` / コメント行数 / モデル数★）
- ★★`db.prechecks()` / `db.cleanLogs()` の着手前の内訳を控えた★★（★段階5 の突き合わせに使う★）:

```
USE_FIXTURES=false DATABASE_URL="mysql://root@localhost:3306/dwatch_local" npx tsx ./scripts/tmp-measure.ts
```

★★一時スクリプトの雛形（★成功時にも `process.exit(0)` を必ず書くこと★。禁止事項18）★★:

```ts
import { db } from "../lib/db";

async function main() {
  const pre = await db.prechecks();
  const byMin: Record<string, number> = {};
  for (const p of pre) byMin[String(p.precheck_minutes)] = (byMin[String(p.precheck_minutes)] ?? 0) + 1;
  console.log("prechecks", pre.length, JSON.stringify(byMin));
  const logs = await db.cleanLogs();
  const count = (k: "status_1h" | "status_30m") =>
    logs.reduce((a: Record<string, number>, l) => { a[l[k]] = (a[l[k]] ?? 0) + 1; return a; }, {});
  console.log("cleanLogs", logs.length, JSON.stringify(count("status_1h")), JSON.stringify(count("status_30m")));
}

// ★★成功時にも必ず exit する（`main().catch(...)` だけだと Prisma の接続が残ってハングする）★★
main()
  .then(() => process.exit(0))
  .catch((e) => { console.error(e); process.exit(1); });
```

- ★★一時スクリプトは**コミットしないこと**★★（★検証後に削除する。`git status` に残さない★）
- ★★決定番号を実測した★★（`grep -nE "^### D-(0|1)[0-9][0-9]" docs/decisions.md | tail -3`）。
  ★★`D-112` / `D-113` が既に入っているかを確認し、★空いている次の番号★を段階6 で使うこと★★

---

## 段階1 — ★`precheckMinutesFromCode()` を「生値をそのまま返す」に直す★（1コミット）

### 1-0. ★何を消すのか★

`lib/db/precheck-mapping.ts:82-99`（★実測。原文★）:

```ts
export function precheckMinutesFromCode(precheckCode: number): number | null {
  if (precheckCode === CLEAN_TIMETABLE_PRECHECK.main) return null;
  const round = CLEAN_TIMETABLE_PRECHECK.rounds.indexOf(          // ← ★消す★
    precheckCode as (typeof CLEAN_TIMETABLE_PRECHECK.rounds)[number]
  );
  if (round !== -1) {                                            // ← ★消す★
    const minutes = PRECHECK_MINUTES_BEFORE[round];              // ← ★消す★
    if (minutes === undefined) { throw new Error(...); }         // ← ★消す★
    return minutes;
  }
  return precheckCode;                                           // ← ★これだけが残る★
}
```

★★残るのは2行です★★:

```ts
if (precheck === CLEAN_TIMETABLE_PRECHECK.main) return null;
return precheck;
```

★★`PRECHECK_MINUTES_BEFORE` の import もこのファイルからは消えます★★（★★設定値そのものは消さない★★。禁止事項8）。
★長さ不一致の例外（`:89-94`）も、対応表が無くなるので消えます★。

### 1-1. ★★期待値表（★これをそのままテストにすること。実装より先に書く★）★★

対象: `precheckMinutesFromCode(precheck: number): number | null`

| # | 入力 `precheck` | ★期待★ | 備考 |
|---|---|---|---|
| ★1★ | ★`CLEAN_TIMETABLE_PRECHECK.main`（＝ `0`）★ | ★★`null`★★ | ★本清掃行。事前チェック行ではないので分数を持たない★ |
| ★2★ | ★★`1`★★ | ★★**`1`**（＝**1分前**）★★ | ★★**従来は `60` に化けていた値**★★。★A5-3「もし1や2が格納されていたら1分, 2分前としてください」★ |
| ★3★ | ★★`2`★★ | ★★**`2`**（＝**2分前**）★★ | ★★**従来は `30` に化けていた値**★★ |
| ★4★ | ★`PRECHECK_MINUTES_BEFORE[0]`（＝ `60`）★ | ★`60`★ | ★実DBに実際に入っている値（A5-2）★ |
| ★5★ | ★`PRECHECK_MINUTES_BEFORE[1]`（＝ `30`）★ | ★`30`★ | ★同上★ |
| ★6★ | ★`20`★ | ★`20`★ | ★★設定値以外の分数★★（D-093 項2 のユーザー原文「20だったら20分前になります」） |
| ★7★ | ★`255`★ | ★`255`★ | ★`UnsignedTinyInt` の上限。★丸めない・「その他」に寄せない★★ |
| ★8★ | ★★`-1`★★ | ★★`-1`★★ | ★★負の値も**そのまま返す**（下の 1-2 の判断）★★ |
| ★9★ | ★`0` 以外の全整数（`1`〜`255` を総当たり）★ | ★★入力と同じ値★★ | ★★プロパティテスト。`CLEAN_TIMETABLE_PRECHECK.main` だけが `null`★★ |

### 1-2. ★★負の値・想定外の値の扱い（★この方針で実装すること★）★★

★★決定: `CLEAN_TIMETABLE_PRECHECK.main` 以外は、**負の値も含めて生値をそのまま返す**。例外を投げない。丸めない。★★

★根拠★:

1. ★★TeamWorks は「0以外の予約語はありません。すべて本清掃を基準に何分前開始の値が格納されます」と明言している（A5-5）★★ —
   ★★丸める先（「その他」）も、弾く根拠（予約値の範囲）も**存在しない**★★
2. ★DBのカラムは `UnsignedTinyInt`（`prisma/schema.prisma:584`）なので ★**負の値はそもそも格納できない**★。
   ★負の値が来るとしたら Prisma より手前の異常であり、この関数が握りつぶす話ではない★
3. ★★`resolutionStatusFromRobotErrorStatus()` が例外を投げるのとは事情が違う★★ —
   ★あちらは `0/1/2` を**3値の enum に変換する**ので、取り違えると「対応済みなのに未解消と出る」という
   ★意味の反転★が起きる★。★★こちらは連続量で、生値がそのまま画面に出れば人が気づける★★
   （★`cleanErrorLabel()` が `String(code)` を返すのと同じ扱い。`lib/config/index.ts:119-129`★）
4. ★画面には `precheckSeverityLabel()`（`lib/prechecks/label.ts:38`）が `"-1分前異常"` と出す★ —
   ★★見慣れない表示が出るほうが「データがおかしい」と気づける（fail-loud）★★

★★例外を投げる案は採らない★★ — ★1行の異常データで `/alerts` 全体が 500 になる★
（`lib/db/index.ts:438-440` が同じ理由で「例外にせず飛ばす」を選んでいる）。

### 1-3. テストを書く（★実装より先★）

- ★`lib/db/__tests__/precheck-mapping.test.ts` の `describe("precheckMinutesFromCode")`（`:17-59`）を書き換える★
- ★★`:26-30` の「rounds の各値は PRECHECK_MINUTES_BEFORE の同じ並び位置の分数になる」は**消すのではなく、期待値を反転させたテストに置き換えること**★★:
  - ★★新: 「`1` は1分前 / `2` は2分前（★★D-112（第7回 A5-3）で**従来と真逆**になった★★）」★★
  - ★★テスト名とコメントに「意図した仕様変更である」ことと出所（A5-3）を書くこと★★（★指示書V 決定2 と同じ作法★）
- ★`:39-47` の「切替後の値域でも同じ分数になる」は ★**通ったまま**★ になる★ —
  ★★コメントの「rounds が [60,30] になっても並び位置から同じ分数が返る」という説明は**もう成立しない**ので書き換えること★★
  （★新: 「`PRECHECK_MINUTES_BEFORE` の値は設定値であると同時に実DBに入る値でもある。生値をそのまま返すので一致する」★）
- ★`:49-58` の「本清掃コードだけが null で、それ以外は必ず数値」は ★**強化する**★ —
  ★★`toBe(code)`（入力と同じ値）まで確認すること★★（★現状は `typeof result === "number"` しか見ていない★）
- ★★この時点で #2・#3 が**落ちる**ことを確認してから実装に進む★★

### 1-4. 実装

`lib/db/precheck-mapping.ts`:

1. ★`precheckMinutesFromCode()` を「`main` なら `null`、それ以外は生値」に直す★
2. ★`PRECHECK_MINUTES_BEFORE` の import を消す★（★★`CLEAN_TIMETABLE_PRECHECK` の import は残す★★）
3. ★★docstring（`:58-81`）を全面的に書き換える★★:
   - ★★消す★★: 「新旧2つの値域を1箇所で吸収する」「現行DDL … 回次のコード値」「この2通りを1つの式で扱えるのは…」（`:65-77`）
   - ★★書く★★:
     - ★`clean_timetables.precheck` は ★**本清掃開始の何分前か**★ を表す数値（★TeamWorks 第7回 A5-2 / A5-5。D-112★）
     - ★`CLEAN_TIMETABLE_PRECHECK.main`（＝本清掃）だけが `null`★
     - ★★`1` は「1回目」ではなく「**1分前**」★★（A5-3。★★従来の実装と真逆だったので、間違えないよう明記する★★）
     - ★★新旧の混在は無い（A5-4）ので、吸収する仕掛けは持たない★★
     - ★未知の値を「その他」に丸めない（D-077 / D-089 項3）/ 負の値もそのまま返す（上の 1-2 の理由を1〜2行で）★
4. ★★引数名を `precheckCode` から `precheck` に変えること★★（★もうコードではない★）。
   ★★関数名 `precheckMinutesFromCode` は**変えない**★★ —
   ★呼び出し側（`lib/db/index.ts:445`）に `precheckMinutes` という局所変数があり、素直な短い名前が取れない★。
   ★★名前だけの変更を本筋のコミットに混ぜるとレビューしづらい★★ので、
   ★★docstring の冒頭に「関数名の `Code` は歴史的な名前。現在の `precheck` はコード値ではなく分数そのもの」と1行書くこと★★
   （★改名は申し送り④に残す★）

### この段階の完了条件

- ★期待値表9ケースがテストになっており、全部通る★
- ★★`lib/db/precheck-mapping.ts` から `PRECHECK_MINUTES_BEFORE` の参照が消えている★★
  （★`lib/config/index.ts` の定義そのものは残っている★）
- ★★`CLEAN_TIMETABLE_PRECHECK.rounds` はまだ消さない★★（★`lib/db/index.ts:307` と `scripts/seed-local-db.ts:807` が使っている★）
- ★`npx tsc --noEmit` = 0 / `npm run lint` = 0 / `npm test` 全パス★
- ★`git diff --stat` が ★`lib/db/precheck-mapping.ts` / `lib/db/__tests__/precheck-mapping.test.ts` の2ファイルだけ★★
- ★★コミット（ファイルパスを明示。`git add -A` 禁止）★★
  - 例: `git commit -m "fix: 指示書X 段階1 precheckを分数そのものとして読む(1は1分前)"`

---

## 段階2 — ★`status_1h` / `status_30m` の割り当て規則を決める★（1コミット）

### 2-0. ★何が問題か★

`lib/db/index.ts:305-313`（★実測。原文★）:

```ts
const precheckRows = precheckByParent.get(key(row.id));
// ★precheck の順（60分前 → 30分前）は lib/config の定義に従う（0/1/2 を直書きしない）★
const [status1h, status30m] = CLEAN_TIMETABLE_PRECHECK.rounds.map((code) => {   // ← ★rounds が消える★
  const precheckRow = precheckRows?.get(code);
  const errors = precheckRow ? (errorsByTimetableId.get(key(precheckRow.id)) ?? []) : [];
  return robotStatusFromErrors(errors);
});
```

★`precheckByParent` は「本清掃の id → （`precheck` の生値 → 事前チェック行）」の入れ子 Map★（`:268-275`）。
★★`rounds` が無くなるので、★どの `precheck` 値の行を `status_1h` に、どれを `status_30m` に割り当てるか★を
新しく決める必要があります★★。

### 2-1. ★★割り当て規則（★これをそのまま実装すること★）★★

| # | 規則 |
|---|---|
| ★1★ | ★★`status_1h` には `precheck === PRECHECK_MINUTES_BEFORE[0]`（＝ `60`）の行を割り当てる★★ |
| ★2★ | ★★`status_30m` には `precheck === PRECHECK_MINUTES_BEFORE[1]`（＝ `30`）の行を割り当てる★★ |
| ★3★ | ★★一致する行が無ければ `robotStatusFromErrors([])`（＝ `ROBOT_STATUS_STANDBY` ＝ `Standby.`）★★ — ★★既存の挙動と同じ★★。★`ROBOT_STATUS_STANDBY` を直書きせず、既存関数に空配列を渡す形を保つこと★ |
| ★4★ | ★★`PRECHECK_MINUTES_BEFORE` の**並び順が列の順**★★（`[0]` → `status_1h` / `[1]` → `status_30m`）。★★添字を直書きせず、配列を `map` して分割代入する現行の形を保つこと★★ |
| ★5★ | ★★`PRECHECK_MINUTES_BEFORE` に3つ目以降があっても、`status_1h` / `status_30m` の2列しか無いので**3件目以降は捨てられる**★★ → ★★申し送り①に書くこと★★ |

★★2列固定でよいことは確定しています★★ —
★★ユーザー回答（2026-09-11）「**これは2回だけです、3回以上にはしません**」★★。
★★したがって `status_1h` / `status_30m` の2列固定で設計してよく、配列化（`precheck_statuses: [{minutes, status}]` 等）は不要です★★。
★★API契約（`d-watch-api.yaml`）を改訂する必要もありません★★（禁止事項11）。

★★ただし規則5 の「3件目が黙って捨てられる」という**実装上の性質そのものは残ります**★★ —
★★将来 `PRECHECK_MINUTES_BEFORE` を3要素にしたら壊れます★★（★例外もテスト失敗も出ないまま3件目が消える★）。
★★だから期待値表 #8 でこの挙動をテストに固定し、申し送り①に残すこと★★
（★「今は起きない」ことと「起きたら静かに壊れる」ことは別の話です★）。

★★なぜこの規則か★★:

- ★★フィールド名（`status_1h` / `status_30m`）は API 契約で固定されており、変えられません★★（禁止事項10）。
  ★★事前チェックは2回で固定と確定したので、2列のままで足ります★★
- ★`PRECHECK_MINUTES_BEFORE` は ★**DFA が何分前にチェックしたいか**★ の設定値（調査1）★ —
  ★★「60分前のチェック結果を `status_1h` の列に出す」は変換ではなく**画面の列の割り当て**★★なので、
  ★D-077（値を変換しない）に触れません★
- ★★設定値以外の分数（例 `20`）の行は、どちらの列にも出ません★★ —
  ★これは既存の挙動と同じ（現行も `rounds` に無い値は拾わない）★

### 2-2. ★★純粋関数に切り出すこと★★

★★`cleanLogs()` の中に書かず、純粋関数として切り出してテストで固定すること★★（`CLAUDE.md` §6-2 / §9.4）。

- ★置き場所: ★**`lib/db/precheck-mapping.ts`**★（★`precheck` の値の読み方を扱う関数はここに集めてある★）
- ★名前は実装者判断★（例: `statusColumnsFromPrecheckRows()`）
- ★引数は「`precheck` の値 → その行のエラー配列」の `ReadonlyMap<number, RobotErrorLike[]>`★
  （★★`clean_timetables` の行そのものを渡すと DB の型に依存してテストが書けなくなる★★ —
  ★呼び出し側で Map に詰め替えてから渡すこと★）
- ★戻り値は `[status1h, status30m]` のタプル★
- ★★`robotStatusFromErrors()` は `lib/db/clean-log-mapping.ts` から import して使うこと★★
  （★★コピーしない。規則を2箇所に書かない。D-103★★。★`RobotErrorLike` 型も同ファイルから★）

### 2-3. ★★期待値表（★これをそのままテストにすること。実装より先に書く★）★★

★共通の前提★: `PRECHECK_MINUTES_BEFORE = [60, 30]`。★表では `E` = 何らかのエラー1件（`error_type` は生値）、
`S` = `ROBOT_STATUS_STANDBY`（`Standby.`）と書きます★。★★テストでは分数を直書きせず
`PRECHECK_MINUTES_BEFORE[0]` / `[1]` を使うこと★★（禁止事項6）。

| # | 入力（`precheck` 値 → エラー配列） | ★期待 `status_1h`★ | ★期待 `status_30m`★ | 意図 |
|---|---|---|---|---|
| ★1★ | `{}`（★事前チェック行が1つも無い★） | ★`S`★ | ★`S`★ | ★既存の挙動（エラーが無い＝待機）★ |
| ★2★ | `{60: [], 30: []}` | ★`S`★ | ★`S`★ | ★行はあるがエラーが無い★ |
| ★3★ | `{60: [E1], 30: []}` | ★`E1` の生値★ | ★`S`★ | ★60分前だけ異常★ |
| ★4★ | `{60: [], 30: [E2]}` | ★`S`★ | ★`E2` の生値★ | ★30分前だけ異常★ |
| ★5★ | `{60: [E1], 30: [E2]}` | ★`E1` の生値★ | ★`E2` の生値★ | ★★列が入れ替わっていないこと★★ |
| ★6★ | ★★`{1: [E1], 2: [E2]}`（★旧コード値の行しか無い★）★★ | ★★`S`★★ | ★★`S`★★ | ★★**再シード前のローカルDBがこうなる**（調査4）★★。★★`1` を60分前に読み替えないこと★★ |
| ★7★ | `{20: [E1]}` | ★`S`★ | ★`S`★ | ★設定値以外の分数はどちらの列にも出ない★ |
| ★8★ | `{60: [E1], 30: [E2], 20: [E3]}` | ★`E1`★ | ★`E2`★ | ★★3件目が捨てられる（申し送り①）★★ |
| ★9★ | `{60: [E1, E1b]}` | ★`robotStatusFromErrors([E1, E1b])` と同じ値★ | ★`S`★ | ★★複数エラーの畳み方は既存関数に委ねる（自分で書かない）★★ |

★★#6 を必ず入れること★★ — ★★これが「真逆だった変換を消した」ことの証明です★★。

### 2-4. 実装

`lib/db/precheck-mapping.ts`:

1. ★2-2 の純粋関数を追加する★
2. ★docstring に 2-1 の規則5項目と「なぜこの規則か」を書く★。★★特に「3件目以降は捨てられる」ことと、
   「フィールド名は API 契約なので変えられない」ことを明記する★★

`lib/db/index.ts`:

3. ★`:307-313` を新しい純粋関数の呼び出しに置き換える★
   - ★`precheckRows`（`Map<number, timetable行>`）から「`precheck` 値 → エラー配列」の Map に詰め替える★
   - ★★`errorsByTimetableId` の索引の作り方（`:246-265`）は変えない★★
4. ★★`CLEAN_TIMETABLE_PRECHECK.rounds` への参照がこのファイルから消えていること★★を確認する
   （★`CLEAN_TIMETABLE_PRECHECK.main` の5箇所は残る★）
5. ★コメント（`:306` の「precheck の順（60分前 → 30分前）は lib/config の定義に従う」）を、
   ★★新しい規則を説明するものに書き換える★★

### この段階の完了条件

- ★期待値表9ケースがテストになっており、全部通る★
- ★★`lib/db/index.ts` から `CLEAN_TIMETABLE_PRECHECK.rounds` の参照が消えている★★
- ★★`status_1h` / `status_30m` という**フィールド名が変わっていない**★★（禁止事項10）
- ★★`clean-log-mapping.ts` を1文字も変えていない★★（★`robotStatusFromErrors()` は import して使う★）
- ★`npx tsc --noEmit` = 0 / `npm run lint` = 0 / `npm test` 全パス★
- ★`git diff --stat` が ★`lib/db/precheck-mapping.ts` / `lib/db/index.ts` / `lib/db/__tests__/precheck-mapping.test.ts` の3ファイルだけ★★
- ★★この時点でローカルDBの `status_1h` / `status_30m` が**全行 `Standby.`** になること（調査4）を承知しておくこと★★。
  ★★段階4 で解消します。ここで「壊れた」と判断して段階1・2 を戻さないこと★★
- ★★コミット（ファイルパスを明示）★★
  - 例: `git commit -m "fix: 指示書X 段階2 事前チェックの列割り当てをPRECHECK_MINUTES_BEFOREに合わせる"`

---

## 段階3 — ★訂正版DDL（0911版）で `dwatch_local` を作り直し、`db pull` し直す★（1コミット）

### 3-0. ★★なぜこの段階があるのか★★

★2026-09-11、TeamWorks から ★★訂正版DDL★★ を受領しました★ —
`C:\Users\daich\Downloads\clean_watch_20260911.sql`（★実測: **44,923 bytes** / `-- Dump completed on 2026-09-11 16:50:36`★）。

★★どのみち段階4 で `precheck` の値を入れ替えるために再シードします★★。
★★同じタイミングでDBごと作り直すのがいちばん効率的★★なので、この段階を挟みます。

### 3-1. ★★0909版との差分（★監督が `diff` で実測済み。着手時に自分でも取り直すこと★）★★

```
diff "C:\Users\daich\Downloads\clean_watch_20260909.sql" "C:\Users\daich\Downloads\clean_watch_20260911.sql"
```

★★実質的なスキーマ差分は **2件だけ** です★★:

| # | 差分 | 出所 |
|---|---|---|
| ★1★ | ★★`facility_contacts.email` が `varchar(255)` → **`varchar(100)`**★★ | ★第7回 **A-3b** が反映された★ |
| ★2★ | ★★`robot_task_versions.clean_schedule_id` に **索引（`KEY`）と外部キー（`CONSTRAINT`）が追加**された★★ | ★第7回 **B-2** が反映された★ |

★★残りの差分は AUTO_INCREMENT の現在値と `-- Dump completed` 行だけ★★ —
★★データ量の違いであってスキーマではありません★★。★★テーブル数は **39** で変わりません★★
（`grep -c "^CREATE TABLE"` で両方 39。★自分で確認すること★）。

★★未反映のまま残ったもの（★4回目の依頼になるが、開発は止まらない★）★★:

| # | 項目 | ★0911版での実測★ |
|---|---|---|
| ★A-2★ | ★履歴テーブルの `id` を AUTO_INCREMENT で採番★ | ★★`clean_status_histories` は**今も主キーが無く、一意キー（`clean_status_histories_id_unique`）のみ**★★。`clean_timetable_histories` は `PRIMARY KEY` はあるが AUTO_INCREMENT は無い |
| ★B-3b★ | ★`security_status_histories.reported_at` に DEFAULT★ | ★`timestamp NOT NULL`（DEFAULT なし）のまま★ |

- ★★この2件は本書では扱いません★★（★TeamWorks 側の作業★）。★段階6 の記録に「4回目の依頼になる」ことだけ残すこと★
- ★★シードは A-2 の影響を既に吸収しています★★（`id` を明示採番している。落とし穴11）。★作り直しても変わりません★

### 3-2. ★★作り直す前に、既存の実測値を控えること（★必須★）★★

★★DROP した瞬間に取り直せなくなります★★。★段階0 で控えた値が手元にあることを確認してから進むこと★:

| # | 控えるもの | 取り方 |
|---|---|---|
| ★1★ | ★`clean_timetables.precheck` の分布 / 総行数 / `robot_error_reports` 件数 / `error_id` の形 / `clean_status_histories.clean_error` の分布★ | ★調査2 の `mysql.exe` コマンド★ |
| ★2★ | ★`db.prechecks()` / `db.cleanLogs()` の件数と内訳★ | ★段階0 の一時スクリプト★ |
| ★3★ | ★★`prisma/schema.prisma` の現物★★ | ★★`git show HEAD:prisma/schema.prisma > <リポジトリ外の作業パス>/schema.head.prisma`★★（★`docs/` に置かないこと★。指示書E `:75`） |
| ★4★ | ★`prisma/schema.prisma` のコメント行数・モデル数★ | ★`grep -c "^\s*//" prisma/schema.prisma` / `grep -c "^model " prisma/schema.prisma`（★作成時点の実測: コメント **153行** / モデル **39**。★鵜呑みにせず自分で数えること★）★ |

★★段階0 で控えていなかったら、ここで戻って取ること★★。★DROP は取り返しがつきません★。

### 3-3. ★`dwatch_local` を 0911版DDL から作り直す★

★★前例があります★★ — ★0909版を投入した記録が ★**D-095**（`docs/decisions.md:3978` 付近。「実施したこと（2026-09-09）」）★ と
★**指示書Q**（`docs/handoff/指示書Q_スキーマを最新DDL0909に追従させ再シードする.md`。D-096 で実行済み）★ にあります★。
★★着手前に両方読み、同じ手順を踏むこと★★（★手順を発明しないこと★）。

★MySQL CLI★:

```
"/c/Program Files/MySQL/MySQL Server 8.4/bin/mysql.exe" -u root --default-character-set=utf8mb4
```

- ★★`--default-character-set=utf8mb4` は**必須**★★（★付けないと日本語が化ける★）
- ★パスワードなし★（★`-p` を付けないこと★）
- ★★対象は `dwatch_local` **だけ**★★ — ★★`dwatch_ddl` / `dwatch_ddl_0907` / `dwatch_ddl_0909` に触らないこと★★（D-095）。
  ★★TeamWorks の本番DBには**絶対に接続しないこと**★★（`CLAUDE.md` §7 / 禁止事項5）

★★DDL は MariaDB 10.11 のダンプです★★（★ヘッダの `Server version 5.5.5-10.11.13-MariaDB-log`★）。
★★MySQL 8.4 で通らない構文があったら、そこで止めて**その箇所（行番号と構文）を報告すること**★★（D-100。★推測で書き換えないこと★）。
★0909版は同じ形で投入できているので、同じ手順が通る見込みです★。

### 3-4. ★`db pull` と `generate` をやり直す★

```
npx prisma db pull
npx prisma generate
```

- ★★`--force` を付けないこと★★（★enrich が働かず、PascalCase モデル名と `@@map` が捨てられる★。指示書E `:86` / 指示書Q 2-1）
- ★★`--print` で別ファイルへ出す方式も取らないこと★★（同じ理由）
- ★接続先は `prisma7.config.ts` が `DATABASE_URL` から読む★ — ★★`dwatch_local` を指していることを目視で確認してから実行すること★★
- ★★`db push` / `prisma migrate` は**絶対に使わないこと**★★（禁止事項4）
- ★`lib/generated/prisma` は `.gitignore` 済み（`.gitignore:47`）なので、`generate` の出力はコミット対象になりません★

### 3-5. ★★生成結果の差分を必ず目で確認する（★型が変わっていないかを報告すること★）★★

★★`prisma/schema.prisma` は生成物なので手で書き換えません★★（`CLAUDE.md` §7 / D-056）。
★★ただし「生成結果が予想どおりか」は人が確認します★★。

★★予想される差分（★これ以外が出たら止めて報告すること★）★★:

| # | 予想される差分 | 根拠 |
|---|---|---|
| ★1★ | ★★`FacilityContact.email` が `@db.VarChar(255)` → **`@db.VarChar(100)`**★★ | ★3-1 の差分1★ |
| ★2★ | ★★`RobotTaskVersion` に `clean_schedule CleanSchedule @relation(...)` と `@@index([clean_schedule_id], map: "robot_task_versions_clean_schedule_id_foreign")` が**追加**される★★ | ★3-1 の差分2。★FK が付いたので Prisma がリレーションとして拾う★★ |
| ★3★ | ★★`CleanSchedule` に逆リレーション `robot_task_versions RobotTaskVersion[]` が**追加**される★★ | ★同上（★Prisma は両側に張る★）★ |
| ★4★ | ★★全コメント（★作成時点で153行★）が**消える**★★ | ★★`db pull` はコメントを保持しません★★（指示書Q 2-2） |

★★#1〜#3 以外の型・`@db.*` 属性・`@default` が動いていたら、★そこで止めて報告すること★★★
（★0909版から動いていないはずです★）。★★特に率・面積の `Float`（D-094）が戻っていないことを確認すること★★。

★★`robot_task_versions` はアプリコードから1箇所も参照されていません★★（★実測: `lib/` / `app/` / `scripts/` にヒット0件★）。
★★したがって #2・#3 はコードに影響しません★★。★`tsc` が落ちたら、それは想定外なので報告すること★。

### 3-6. ★★コメントを移植し直す（★ここが最大の作業です★）★★

★★`db pull` はコメントを保持しないので、コメントが全部消えます★★
（★作成時点の実測で **153行**。★3-2 #4 で自分が数えた値を使うこと★）。
★指示書Q 2-2（`docs/handoff/指示書Q_スキーマを最新DDL0909に追従させ再シードする.md:108`）と
指示書E 手順1・2（`docs/handoff/指示書E_スキーマを実DDLに一致.md:69-91`）に従って移植すること★:

1. ★3-2 #3 で取った控え（`schema.head.prisma`）と作業ツリーを diff しながら、★**コメント（`//` 行）だけ**★を移植する★
2. ★★移植して**はいけない**もの★★: ★型・`@db.*` 属性・`@default` — ★これらは `db pull` の生成結果が正★（D-056）
3. ★★移植後に `grep -c "^\s*//" prisma/schema.prisma` を数え、★控えた行数から大きく減っていないこと★を確認する★
4. ★`datasource db` ブロックの「Prisma 7 では接続URLを書けない」コメントも消えるので戻すこと★
5. ★冒頭の「db pull 実施履歴」に ★**3回目 2026-09-11 / 元DDL: `clean_watch_20260911.sql` / 投入先: `dwatch_local`**★ を足すこと★
6. ★★Laravel 基盤10テーブルと `clean_link_schedules` を削除しないこと★★（指示書Q 2-5（`docs/handoff/指示書Q_スキーマを最新DDL0909に追従させ再シードする.md:174`）/ D-058）
7. ★`@updatedAt` と `@default(now())` を受領DDL で grep して確かめ、`db pull` が落としたものを戻すこと★
   （★指示書Q 2-3（`:127`）の作法。★★推測で付けないこと★★。0909版では `ON UPDATE current_timestamp` が 20テーブル/21列だった★）

★★移植のついでに「もう正しくないコメント」を書き直すこと★★ —
★★これは生成物の手書き改変ではありません★★（★コメントは `db pull` が生成しないので、人が書くのが正規の手順です★。指示書Q 2-2）:

| # | 場所 | ★今の記述★ | ★★書き直す内容★★ |
|---|---|---|---|
| ★1★ | ★`CleanTimetable.precheck`（★作成時点 `:581-583`★）★ | 「TeamWorks回答（B-1）: 0: 本清掃 / ★**1: 1回目 / 2: 2回目**★」 | ★★`0` = 本清掃 / ★それ以外はその数値がそのまま「分前」★（TeamWorks 第7回 A5-2 / A5-5。D-112）。★`1` は「1回目」ではなく「1分前」（A5-3）★★★ |
| ★2★ | ★`CleanTimetableHistory.precheck`（★作成時点 `:611-613`★）★ | ★同上★ | ★同上★ |
| ★3★ | ★`FacilityContact.email`（★作成時点 `:808-811`★）★ | 「実DDL: varchar(255) … ★**2026-09-09時点でも未反映**★」 | ★★「第7回 A-3b が 0911版DDL で反映された（`varchar(100)`）」★★ |
| ★4★ | ★`RobotTaskVersion.clean_schedule_id`（★作成時点 `:257-259`★）★ | 「実DDLには FK も KEY も無く … ★**リレーション対象から外した**★」 | ★★「第7回 B-2 が 0911版DDL で反映され、FK と索引が付いた。`db pull` がリレーションとして拾うようになった」★★ |

> ★★起草時の申し送り②への答え★★ — ★★「`db pull` をやり直せば `precheck` のコメントが自動で直る」わけではありません★★。
> ★`db pull` はコメントを**消す**だけです★。★★直るのは、この 3-6 の移植で**人が書き直したとき**です★★。
> ★★だから 3-6 の #1・#2 は必須作業です★★（★忘れると、次に読む人がまた「1 = 1回目」と信じます★）。

### この段階の完了条件

- ★`dwatch_local` が 0911版DDL から作り直されている★（★テーブル数 **39**★。★★この時点では全テーブル0行★★）
- ★★`dwatch_ddl` / `dwatch_ddl_0907` / `dwatch_ddl_0909` / 本番DB に触っていない★★
- ★`npx prisma validate` が通る★ / ★モデル数 **39**★（`grep -c "^model " prisma/schema.prisma`）
- ★★3-5 の予想差分 #1〜#3 が出ており、それ以外の型変更が**無い**ことを目で確認した★★（★★報告に書くこと★★）
- ★★コメントを移植し、3-6 の表4件を書き直した★★（★コメント行数が控えた値から大きく減っていない★）
- ★`npx tsc --noEmit` = 0 / `npm run lint` = 0 / `npm test` 全パス★
  （★★DBは空だがテストはDBを見ないので緑のまま★★。調査4 と同じ理屈）
- ★`git diff --stat` が ★**`prisma/schema.prisma` の1ファイルだけ**★★
  （★`lib/generated/prisma` は `.gitignore` 済み★）
- ★★コミット（ファイルパスを明示。`git add -A` 禁止）★★
  - 例: `git commit -m "chore: 指示書X 段階3 schema.prismaを訂正版DDL0911に追従させる"`

> ★★この時点でローカルDBは空です★★。★★段階4 の再シードで埋まります★★ — ★ここで画面を見て「壊れた」と判断しないこと★。

---

## 段階4 — ★シードを `0` / `60` / `30` にし、`rounds` を削除して再シードする★（1コミット）

### 4-1. ★★`scripts/seed-local-db.ts` の変更（★これだけ★）★★

`:802-830` 付近（★実測。原文★）:

```ts
const statuses = precheckStatuses(log);
for (const [i, minutesBefore] of PRECHECK_MINUTES_BEFORE.entries()) {
  const precheckId = log.id + PRECHECK_TIMETABLE_ID_OFFSETS[i];
  const precheckStartAt = addMinutes(startAt, -minutesBefore);
  const precheckCode = CLEAN_TIMETABLE_PRECHECK.rounds[i];        // ← ★消す★
  …
      precheck: precheckCode,                                     // ← ★minutesBefore に変える★
  …
  const errorKey = `RER-${log.id}-${precheckCode}`;               // ← ★minutesBefore に変える★
```

★★変更点は2箇所だけ★★:

| # | 変更 | 影響 |
|---|---|---|
| ★1★ | ★`precheck: precheckCode` → ★**`precheck: minutesBefore`**★★ | ★`clean_timetables.precheck` が `60` / `30` になる★ |
| ★2★ | ★`errorKey` の `precheckCode` → ★**`minutesBefore`**★★ | ★★`error_id` が `RER-1011-1` → `RER-1011-60` に変わる★★（★調査3 の #4。★意図した変化★） |

★★他は1行も変えないこと★★:

- ★`PRECHECK_MINUTES_BEFORE.entries()` のループそのもの★（★`minutesBefore` は既にこのループが持っている★）
- ★`PRECHECK_TIMETABLE_ID_OFFSETS`（`:211`）★ — ★★`clean_timetables.id` の採番は変えない★★（★行の同一性が変わると差分が読めなくなる★）
- ★`precheckStartAt = addMinutes(startAt, -minutesBefore)`★ — ★元から正しい★
- ★本清掃行の `precheck: CLEAN_TIMETABLE_PRECHECK.main`（`:797` / `:990`）★ — ★変えない★
- ★`historyIndex` / `errorReportCount` の通番ローテーション★ — ★★行を足さないので自然に保たれる★★（落とし穴10）
- ★施設7 の検証データブロック（`:960-1016`。指示書V 段階6）★ — ★事前チェック行を作らないので無関係★

### 4-2. ★★`lib/config/index.ts` の変更★★

1. ★★`CLEAN_TIMETABLE_PRECHECK` から `rounds` を削除する★★（★★最後の消費者が段階1・2・4-1 で消えた★★）
   - ★★`main` は残す★★（禁止事項9。★`lib/db/index.ts` 5箇所 / `scripts/seed-local-db.ts` 2箇所 / `lib/db/precheck-mapping.ts` / ★未実行の指示書W★★）
   - ★★オブジェクトの形（`{ main: 0 } as const`）は保つこと★★ — ★`CLEAN_TIMETABLE_PRECHECK.main` という呼び出し側の書き方を変えないため★
2. ★★docstring（`:36-48`）を書き換える★★:
   - ★★消す★★:
     - 「0 = 本清掃 / 1 = 1回目の事前チェック / 2 = 2回目の事前チェック」という ★**コード表の説明**★
     - 「`rounds[i]` は `PRECHECK_MINUTES_BEFORE[i]` に対応する（rounds[0]=1 が 60分前、rounds[1]=2 が 30分前）」
     - ★★「D-091 項4 / D-093 項2 で『0 / 60 / 30』に変わる**予定**」「現行DDLは 0/1/2 のままなので、切り替わるまではこの表を正とする」★★
   - ★★書く★★:
     - ★★「D-091 項4 / D-093 項2 の予定は **実現した**（TeamWorks 第7回 A5-2。2026-09-11 受領。D-112）」★★
     - ★`0` = 本清掃 / ★**それ以外はその数値がそのまま「分前」**★（A5-5）
     - ★★`1` は「1回目」ではなく「1分前」（A5-3）★★
     - ★★新旧の混在は無い（A5-4）★★
     - ★変換は `lib/db/precheck-mapping.ts` の `precheckMinutesFromCode()` **1箇所だけ**★
3. ★★`PRECHECK_MINUTES_BEFORE`（`:33-34`）の docstring を足す★★ — ★★役割が変わったことを明記する★★:
   - ★★「これは **DFA 側の設定値**（何分前にロボット状態をチェックしたいか）であって、
     `clean_timetables.precheck` を読み替えるための対応表ではない」★★
   - ★消費者: システム管理画面の表示（`components/features/system/SystemScreen.tsx:131`）/
     `status_1h` / `status_30m` の列の割り当て（`lib/db/precheck-mapping.ts`）/
     シードの `start_at` と `precheck` の生成（`scripts/seed-local-db.ts`）/ fixtures（`lib/fixtures/prechecks.ts`）★
   - ★★「実際に何分前のチェックだったかは **DB の `precheck` が正**」★★（★★D-105 項13-2 の未決に決着がついた★★）

### 4-3. 再シードと実測

★★段階3 でDBを作り直したので、この時点では全テーブル0行です★★。
★★このシードが「作り直した0911版スキーマの上に、分数（`0` / `60` / `30`）で入れ直す」1回目になります★★。

```
npm run seed:local
```

- ★★シードが 0911版スキーマで通らなかったら止めて報告すること★★ —
  ★スキーマ差分は `facility_contacts.email` の桁縮小（255→100）と `robot_task_versions` の FK 追加の2件だけです★。
  ★★どちらもシードに影響しない見込み★★ —
  ★シードが入れるメールアドレスは `lib/fixtures/facilities.ts` 由来で ★**すべて 30文字未満**★（実測。`store009@example.com` 等）★、
  ★`robot_task_versions` は ★**シードが1行も投入していない**★（実測: `scripts/seed-local-db.ts` にヒット0件）★

★★再シード後に実測して控える★★:

```
"/c/Program Files/MySQL/MySQL Server 8.4/bin/mysql.exe" -u root --default-character-set=utf8mb4 dwatch_local -e "
SELECT precheck, COUNT(*) c FROM clean_timetables GROUP BY precheck ORDER BY precheck;
SELECT COUNT(*) AS total FROM clean_timetables;
SELECT COUNT(*) AS reports FROM robot_error_reports;
SELECT error_id FROM robot_error_reports ORDER BY error_id LIMIT 5;
SELECT clean_error, COUNT(*) FROM clean_status_histories GROUP BY clean_error ORDER BY clean_error;"
```

★★期待★★:

| # | 項目 | ★期待★ |
|---|---|---|
| ★1★ | `precheck` の分布 | ★★`0` = **259** / `60` = **253** / `30` = **253**★★（★★`1` / `2` が **0件**★★） |
| ★2★ | `clean_timetables` の総行数 | ★★**765**（着手前と同じ）★★ |
| ★3★ | `robot_error_reports` の件数 | ★★**14**（着手前と同じ）★★ |
| ★4★ | `error_id` の形 | ★★`RER-<id>-60` / `RER-<id>-30`★★（★`-1` / `-2` が無いこと★） |
| ★5★ | `clean_error` の分布 | ★★`0`=231 / `1`=13 / `2`=5 / `3`=5 / `4`=5（★着手前と同じ★）★★（★動いていたら落とし穴10★） |
| ★6★ | ★2回シードしても同じ★ | ★もう一度 `npm run seed:local` を流し、★1〜5 が同じ値★になること★（★冪等。落とし穴11★） |

### この段階の完了条件

- ★★`CLEAN_TIMETABLE_PRECHECK.rounds` がリポジトリのコードから消えている★★
  （`grep -rn "CLEAN_TIMETABLE_PRECHECK.rounds" --include=*.ts --include=*.tsx .` で ★**ヒット0件**★。
  ★★`docs/` のヒットは残ってよい★★ — ★`docs/code-map.md` / `管理画面_設計検討_20260910.md` は禁止事項2、
  過去の指示書・`docs/decisions.md` は追記型の台帳なので過去の記述を消さない★）
- ★★`PRECHECK_MINUTES_BEFORE` は残っており、`SystemScreen.tsx` / `lib/fixtures/prechecks.ts` に差分が無い★★
- ★`npx tsc --noEmit` = 0 / `npm run lint` = 0 / `npm test` 全パス★（★テストは増減しない段階★）
- ★4-3 の6項目を実測した★
- ★`git diff --stat` が ★`lib/config/index.ts` / `scripts/seed-local-db.ts` の2ファイルだけ★★
  （★★`lib/fixtures/` に差分が無いこと★★。★★`prisma/schema.prisma` は段階3 でコミット済みなので、この段階では**新たな差分が出ないこと**★★）
- ★★コミット（ファイルパスを明示）★★
  - 例: `git commit -m "chore: 指示書X 段階4 シードのprecheckを分数にしてroundsを削除する"`

---

## 段階5 — ★実測での検証（★コミットなし★）★

### 5-1. ★データ層の検証★

★★`.env.local` を書き換えず、コマンドラインで env を渡すこと★★（★戻し忘れを防ぐ★）。
★★一時スクリプトには成功時にも必ず `process.exit(0)` を書くこと★★（禁止事項18。段階0 の雛形を使う）。

```
USE_FIXTURES=false DATABASE_URL="mysql://root@localhost:3306/dwatch_local" npx tsx ./scripts/tmp-measure.ts
```

★★期待値（★1つでも外れたら実装が違う★）★★:

| # | 項目 | ★期待★ |
|---|---|---|
| ★1★ | `db.cleanLogs()` の件数 | ★★**259**（着手前と同じ）★★ |
| ★2★ | `db.prechecks()` の件数 | ★★**27**（着手前と同じ）★★ |
| ★3★ | `severity` の内訳 | ★★`precheck` **14** / `launch_failed` **13**（着手前と同じ）★★ |
| ★4★ | ★★`precheck_minutes` の内訳★★ | ★★`60` = **8** / `30` = **6** / `null` = **13**（★★**着手前と完全に同じ**★★）★★ ← ★★**これが「正しく直った」証明**★★ |
| ★5★ | ★`precheck_minutes` に `1` / `2` が**出ないこと**★ | ★★0件★★（★出たら再シードが効いていない★） |
| ★6★ | ★`status_1h` の内訳★ | ★★`Standby.` 251 / セキュリティ有効 2 / オフライン 3 / `Handrail Lifted` 1 / 充電中 1 / 緊急停止 1（★**非 Standby. = 8**。着手前と同じ**★）★★ |
| ★7★ | ★`status_30m` の内訳★ | ★★`Standby.` 253 / セキュリティ有効 2 / オフライン 3 / `Handrail Lifted` 1（★**非 Standby. = 6**。着手前と同じ**★）★★ |
| ★8★ | ★`PrecheckItem.id`★ | ★★`RER-<id>-60` / `RER-<id>-30` に変わっている★★（★★これだけが変わってよい値★★。調査3 #4） |

★★#6・#7 が全行 `Standby.` になっていたら再シードが効いていません★★（調査4）。
★★#4 が `1`=8 / `2`=6 になっていたら段階4 の 4-1 を入れ忘れています★★。

- ★★一時スクリプトは検証後に削除すること★★（★`git status` に残さない★）

### 5-2. ★画面（回帰していないこと）★

| 画面 | 期待 |
|---|---|
| ★`/alerts`★ | ★200 / ★**27件**（`launch_failed` 13）★。★★種別ラベルが「60分前異常」「30分前異常」「出発失敗」のまま★★（★「1分前異常」が出たら再シードが効いていない★） |
| ★`/detail-log`★ | ★200 / ★**259件**★。★1時間前・30分前の状態列が着手前と同じ見た目★ |
| ★`/system`★ | ★★200 / 「第1回チェック 60分前」「第2回チェック 30分前」が**消えていない**★★（★★`PRECHECK_MINUTES_BEFORE` を消していないことの確認★★） |
| ★`/dashboard`★ | ★200 / **9 / 9**★（D-099） |
| ★`/reports`★ | ★200（KPI・施設別・エラー内訳）★。★連続問題施設セクションは ★★指示書W の実行状況次第★★（★未実行なら 500・実行済みなら 200。★どちらでも本書の範囲外★）★ |
| `/facilities` / `/schedules` / `/ds-logs` / `/ds-alerts` | ★200。回帰していないこと★ |

- ★★**404 が出たら Edge Runtime バンドル破壊の回帰を疑うこと**★★（D-086）。★500 = 未実装 / 404 = ビルドが壊れている★
- ★Browser pane（`preview_start`）が使えなければ curl で代替し、★目視できなかった項目を必ず記録する★★
- ★★`next dev` は `.env.local` を共有する別セッションに影響します（D-108 項8）★★ —
  ★`next build && next start -p <別ポート>` で自分専用のサーバーを起てるほうが安全★

### 5-3. ★`USE_FIXTURES=true` での確認★

- ★★fixtures 側の件数・分数・ラベルが**1つも変わっていないこと**★★（禁止事項12）
  - ★`lib/fixtures/prechecks.ts` は `precheck_minutes` を直接持っており、DBのコード値を経由しない★
  - ★★だから本書の変更の影響を受けないのが**正しい**★★（★変わったら fixtures を触ってしまっている★）
  - ★`/alerts` に「20分前異常」（`lib/fixtures/prechecks.ts:90` の境界値行）が出ることも確認する★

### この段階の完了条件

- ★5-1 の8項目・5-2 の回帰確認・5-3 をすべて実測した★
- ★★一時スクリプトを削除した★★
- ★★`.env.local` が `USE_FIXTURES=true` のままであることを確認した★★
- ★`git status` に `lib/` `scripts/` `docs/` 以外の差分が無いことを確認した★
  （★`README.md` / `docs/code-map.md` / `docs/handoff/管理画面_設計検討_20260910.md` /
  `docs/handoff/指示書W_…` は ★**着手前と同じ状態のまま**★ であること。禁止事項2）

---

## 段階6 — 記録と push（1コミット）

### 6-1. `docs/decisions.md` に追記（★`D-114` の見込み。★着手直前に実測した番号を使うこと★）

★★`D-112` は別担当が「第7回回答の受領」として追記済み（`docs/decisions.md:6706`。`08acc89`）、
`D-113` は指示書W が追記する見込みです★★ —
★★その番号を奪わないこと★★。★★必ず `grep -nE "^### D-(0|1)[0-9][0-9]" docs/decisions.md | tail -3` で実測してから採番すること★★。

★見出しの形式は既存に揃える★: `### D-114 \`[確定]\` \`precheck\` を分数そのものとして読み、訂正版DDL(0911)に追従する（コード表を廃止した）`

★最低限入れる項目★:

| # | 項目 |
|---|---|
| 1 | ★★TeamWorks 第7回 A5-1〜A5-5 の**原文を引用**する★★（★★引用は原文ママ。要約に置き換えないこと（D-077 の思想）★★） |
| 2 | ★★現行実装が**真逆**だったこと★★: ★`1` を60分前・`2` を30分前と読んでいたが、正しくは1分前・2分前★ |
| 3 | ★★新しい規則: `0` = 本清掃（`precheck_minutes = null`）/ それ以外は生値がそのまま分数★★。★変換は `precheckMinutesFromCode()` 1箇所だけ★ |
| 4 | ★★`CLEAN_TIMETABLE_PRECHECK.rounds`（コード表）を**削除**した★★。★`main` は残した理由（`lib/db/index.ts` / シード / ★指示書W★ が使う）★ |
| 5 | ★★`PRECHECK_MINUTES_BEFORE` は**残した**★★。★★役割が「DBの値の読み替え表」から「**DFA 側の設定値**（何分前にチェックするか）」＋「`status_1h` / `status_30m` の**列の割り当てキー**」に変わった★★。★消費者4箇所を列挙する★ |
| 6 | ★★`status_1h` / `status_30m` の割り当て規則★★（段階2 の 2-1 の5項目）。★フィールド名は API 契約なので変えていない★ |
| 7 | ★★負の値・想定外の値は**生値をそのまま返す**（例外を投げない・丸めない）と決めたこと、およびその根拠4点★★（段階1 の 1-2） |
| 8 | ★★**D-105 項1 の「新旧2つの値域を1つの式で吸収している」という設計を撤回した**★★ — ★A5-4「混在しません」により、吸収する必要が無くなった★ |
| 9 | ★★**D-105 項13-1（新旧混在の有無）の未決が消滅した**★★（A5-4 で「混在しない」と確定） |
| 10 | ★★**D-105 項13-2（`PRECHECK_MINUTES_BEFORE` と DB の `precheck` のどちらが正か）の未決が消滅した**★★ — ★★**DB の `precheck` が正**。設定値は DFA 側の希望値であり、実際に何分前だったかは DB を見る★★ |
| 11 | ★★**指示書S のレビューで挙がった「新旧混在に耐えるという主張が成立していない」という指摘は、論点そのものが消滅した**★★ — ★★混在しないので、耐える必要が無い★★。★★「指摘が誤りだった」のではなく「前提が変わって不要になった」と書くこと★★ |
| 12 | ★★**D-105 項13-3（`precheck` に入りうる値の範囲）も A5-5 で解決**★★（★「0以外の予約語はありません」★）。★★ただし `UnsignedTinyInt` なので **255分前（4時間15分前）までしか表現できない**制約は残る★★ |
| 13 | ★シードを `0` / `60` / `30` に直し、再シードしたこと★。★★`error_id` が `RER-<id>-1` → `RER-<id>-60` に変わった（＝ `PrecheckItem.id` が変わった）★★ |
| 14 | ★段階5 の実測値（★`prechecks()` 27件・`precheck_minutes` 60=8 / 30=6 / null=13 が**着手前と完全に同じ**であること★ / `cleanLogs()` 259 / `status_1h` 非 Standby. 8 / `status_30m` 非 Standby. 6 / `clean_error` 分布不変 / テスト数）★ |
| 15 | ★★変えなかったもの★★: ★`d-watch-api.yaml`（既に「任意の分数」と書いてある）/ `lib/types/` / `lib/fixtures/` / `lib/prechecks/label.ts` / `clean-log-mapping.ts`★（★★`prisma/schema.prisma` は `db pull` で更新した。項16 参照★★） |
| 16 | ★★訂正版DDL（`clean_watch_20260911.sql`。2026-09-11 受領）で `dwatch_local` を作り直し、`db pull` / `generate` をやり直したこと★★。★0909版との差分は ★**`facility_contacts.email` の `varchar(255)` → `varchar(100)`（第7回 A-3b）**★ と ★**`robot_task_versions.clean_schedule_id` への索引・外部キー追加（第7回 B-2）**★ の2件のみ（残りは AUTO_INCREMENT の現在値）。★テーブル数 39 は不変★。★★未反映のまま残った第7回 A-2（履歴テーブルの `id` の AUTO_INCREMENT）と B-3b（`security_status_histories.reported_at` の DEFAULT）は**4回目の依頼になるが、開発は止まっていない**★★ |
| 17 | ★★`prisma/schema.prisma` の生成結果を目で確認した内容★★（★予想どおりの3差分のみで、他の型は動いていないこと★）。★★`db pull` はコメントを保持しないため移植し直し、そのときに「1: 1回目 / 2: 2回目」「`email` は未反映」「`clean_schedule_id` に FK 無し」の3種のコメントを書き直したこと★★ |
| 18 | ★申し送り（下記「申し送り」節をそのまま移す）★ |

### 6-2. `docs/handoff.md` に追記（★追記34 の見込み★）

- ★見出しは `## 2026-09-1X（追記34）`★（★番号は実測すること★。★★別担当・指示書W が先に使っていたら次の番号★★）
- ★冒頭の「最新の追記」ブロック（`docs/handoff.md:6` 付近）も更新すること★
- ★入れる内容★:
  - ★★「`clean_timetables.precheck` は **分数そのもの**。`0` だけが本清掃。`1` は『1回目』ではなく『**1分前**』」★★
  - ★★「コード表（`CLEAN_TIMETABLE_PRECHECK.rounds`）は削除した。★**新旧混在は無い**★（TeamWorks A5-4）」★★
  - ★★「`PRECHECK_MINUTES_BEFORE` は**残っている**が、役割は『DFA 側の設定値』＋『`status_1h` / `status_30m` の列の割り当て』。★DBの値を読み替える表ではない★」★★
  - ★「`status_1h` / `status_30m` は `PRECHECK_MINUTES_BEFORE` に一致する `precheck` 行を割り当てる。★一致が無ければ `Standby.`★」★
  - ★★「`dwatch_local` は **0911版DDL**（`clean_watch_20260911.sql`）から作り直した。`schema.prisma` は3回目の `db pull` 済み」★★
  - ★★「0909版からの差分は `facility_contacts.email` の `varchar(100)` 化（第7回 A-3b）と `robot_task_versions.clean_schedule_id` の索引・外部キー追加（第7回 B-2）の2件だけ。テーブル数 39 は不変」★★
  - ★★「第7回 A-2（履歴テーブルの `id` の AUTO_INCREMENT）と B-3b（`security_status_histories.reported_at` の DEFAULT）は**まだ未反映**。次に依頼すると4回目になる」★★
  - ★★「事前チェックは**2回で固定**と確定した（ユーザー回答 2026-09-11）。`status_1h` / `status_30m` の2列でよい」★★
  - ★次に触る人が踏みやすい落とし穴（★`PRECHECK_MINUTES_BEFORE` を消さない★ / ★`CLEAN_TIMETABLE_PRECHECK.main` を消さない★ / ★★`db pull` はコメントを消すので、再 pull したら移植し直す★★）★
  - ★申し送り★

### 6-3. push とこの段階の完了条件

- ★★コミット（ファイルパスを明示。`docs/decisions.md` と `docs/handoff.md` のみ）★★
  - 例: `git commit -m "docs: 指示書X完了(D-114)を記録する"`
  - ★★作成時点では `D-112` はコミット済み（`08acc89`）ですが、着手時に**別担当の未コミットの追記**が
    `docs/decisions.md` に入っている可能性があります★★ — ★★`git add docs/decisions.md` はそれごと巻き込みます★★。
    ★着手直前に `git status` / `git diff docs/decisions.md` を確認し、★★他人の追記があれば、その担当が先に
    コミットするのを待つか、巻き込むことを承知のうえでコミットメッセージに明記すること★★★
  - ★★コミット前に `git pull --rebase` の要否を確認し、★競合したら自分の追記だけを残して番号を取り直すこと★★★
- ★`git push origin develop`★
- ★★本書の冒頭表と末尾「実行状態」表を更新すること★★
- ★★`README.md` / `docs/code-map.md` / `docs/handoff/管理画面_設計検討_20260910.md` /
  `docs/handoff/指示書W_連続問題施設を実DBから作る.md` / `.claude/worktrees/**` に差分が無いことを最後に確認すること★★

---

## ★★申し送り★★

★★以下は本指示書では直しません。★★ ★D-114 に列挙して残すこと★。

| # | 論点 | 状態 |
|---|---|---|
| ★①★ | ★★`PRECHECK_MINUTES_BEFORE` を3要素にすると**3件目が黙って捨てられる**★★ — ★`CleanLogItem` は `status_1h` / `status_30m` の ★**2つ固定**★（API契約）。★★例外もテスト失敗も出ないまま消えます★★。★段階2 の期待値表 #8 でこの挙動をテストに固定してある★ | ★★**確定：事前チェックは2回で固定**★★（★ユーザー回答 2026-09-11「**これは2回だけです、3回以上にはしません**」★）。★★したがって `status_1h` / `status_30m` の**2列固定でよく、API契約の改訂は不要**★★。★★ただし「3要素にしたら静かに壊れる」という性質は残るので、将来 `PRECHECK_MINUTES_BEFORE` を増やす人への注意として残す★★ |
| ★②★ | ★★`prisma/schema.prisma` のコメントが古い★★ — 「TeamWorks回答（B-1）: 0: 本清掃 / 1: 1回目 / 2: 2回目」は ★**もう正しくない**★ | ★★**本書の段階3-6 で対応済み**★★（★0911版DDL の受領を受けて `db pull` をやり直し、コメント移植のときに書き直した★）。★★`db pull` が自動で直すのではない★★ — ★`db pull` はコメントを**消す**だけで、正しい文言は人が書く★（★これは生成物の手書き改変ではない。指示書Q 2-2 / D-056★） |
| ★③★ | ★`docs/code-map.md:881-882` / `:1899` / `:2288` と `docs/handoff/管理画面_設計検討_20260910.md:215-216` に `rounds` の記述が残る★ | ★★本書では触らない（禁止事項2。別作業の未追跡ファイル）★★。★その担当に伝えること★ |
| ★④★ | ★関数名 `precheckMinutesFromCode()` の `Code` が実態と合わない★（★もうコード表を引かない★） | ★★本書では改名しない★★（★名前だけの変更を本筋のコミットに混ぜない★）。★docstring に断り書きを入れてある★。★次に `lib/db/precheck-mapping.ts` を触るときに一緒に直すのが安全★ |
| ★⑤★ | ★★`precheck` は `UnsignedTinyInt`（0〜255）なので、**255分前（4時間15分前）より前のチェックは表現できない**★★ | ★★TeamWorks に伝えるかは未決★★（D-105 項13-3）。★現行の運用（60分前 / 30分前）では問題にならない★ |
| ★⑥★ | ★★`precheck = 1`（1分前）のような**現実的でない値**が来ても、そのまま「1分前異常」と表示される★★ | ★★これは意図した挙動★★（A5-3 / D-077）。★★おかしな値が画面に出ることで気づけるのが狙い★★。★「妥当な範囲」を当方で決めて弾かないこと★ |
| ★⑦★ | ★★実データの `precheck` を実際に見て確認していない★★ — ★A5-2 は「修正済み」と言っているが、★★DFA はまだ実DBに繋いでいない★★ | ★実DB接続時に `SELECT precheck, COUNT(*) FROM clean_timetables GROUP BY precheck` を最初に流すこと★。★★`1` / `2` が出てきたら「1分前 / 2分前」と読むのが正しい（A5-3）★★ |

---

## 完了条件（全体）

| # | 条件 |
|---|---|
| ★1★ | ★`precheckMinutesFromCode()` が「`CLEAN_TIMETABLE_PRECHECK.main` なら `null`、それ以外は生値」だけになっている★ |
| ★2★ | ★★`precheckMinutesFromCode(1) === 1` / `(2) === 2`（＝**従来と真逆**）がテストで固定されている★★ |
| ★3★ | ★期待値表9ケース（段階1）＋9ケース（段階2）がすべてテストになっている★ |
| ★4★ | ★負の値（`-1`）が例外にならず `-1` を返すことがテストで固定され、その判断の根拠が docstring に書かれている★ |
| ★5★ | ★★`CLEAN_TIMETABLE_PRECHECK.rounds` がコードから消えている（`.ts` / `.tsx` で grep 0件）★★ |
| ★6★ | ★★`CLEAN_TIMETABLE_PRECHECK.main` が残っており、`0` の直書きが1つも無い★★ |
| ★7★ | ★★`PRECHECK_MINUTES_BEFORE` が残っており、`SystemScreen.tsx` / `lib/fixtures/prechecks.ts` に差分が無い★★ |
| ★8★ | ★`status_1h` / `status_30m` の割り当てが純粋関数に切り出され、テストで固定されている★ |
| ★9★ | ★★`status_1h` / `status_30m` の**フィールド名が変わっていない**★★ |
| ★10★ | ★★旧コード値（`{1: [E], 2: [E]}`）が `Standby.` になる（60分前に読み替えない）ことがテストで固定されている★★ |
| ★11★ | ★シードの `precheck` が `0` / `60` / `30` になり、再シード後の分布が `0`=259 / `60`=253 / `30`=253★ |
| ★12★ | ★★2回シードしても同じ結果（冪等）。乱数を使っていない・id を明示採番している★★ |
| ★13★ | ★★`db.prechecks()` = **27件**、`precheck_minutes` が `60`=8 / `30`=6 / `null`=13 で**着手前と完全に同じ**★★ |
| ★14★ | ★★`db.cleanLogs()` = **259件**、`status_1h` の非 `Standby.` = **8**、`status_30m` の非 `Standby.` = **6** で着手前と同じ★★ |
| ★15★ | ★`clean_status_histories.clean_error` の分布が着手前と同じ（`0`=231 / `1`=13 / `2`=5 / `3`=5 / `4`=5）★ |
| ★16★ | ★`npx tsc --noEmit` = 0 / `npm run lint` = 0 / `npm test` 全パス（★テスト数は着手前より増えている★）★ |
| ★17★ | ★★`d-watch-api.yaml` / `lib/types/` / `lib/fixtures/` / `lib/prechecks/label.ts` / `lib/db/clean-log-mapping.ts` に差分が無い★★ |
| ★17b★ | ★★`dwatch_local` が訂正版DDL（`clean_watch_20260911.sql`）から作り直され、`prisma/schema.prisma` が `db pull` で追従している★★（★テーブル数 39 / モデル数 39 / `npx prisma validate` が通る★）。★★生成結果の差分が「`FacilityContact.email` の `VarChar(100)` 化」「`RobotTaskVersion` のリレーション追加」「`CleanSchedule` の逆リレーション追加」の3件だけであることを目で確認し、報告した★★ |
| ★17c★ | ★★`schema.prisma` のコメントを移植し直し、`precheck` の「1: 1回目 / 2: 2回目」/ `FacilityContact.email` の「未反映」/ `RobotTaskVersion.clean_schedule_id` の「FK 無し」の3種を書き直した★★（★コメント行数が着手前の実測値から大きく減っていない★） |
| ★18★ | ★`README.md` / `docs/code-map.md` / `docs/handoff/管理画面_設計検討_20260910.md` / `docs/handoff/指示書W_…` / `.claude/worktrees/**` に触っていない★ |
| ★19★ | ★★`docs/decisions.md`（★実測した空き番号。`D-112` / `D-113` を奪っていない★）と `docs/handoff.md` に記録した★★。★★D-105 項13-1・13-2 の未決消滅と、指示書S レビュー指摘の論点消滅を明記した★★ |
| ★20★ | ★5コミット（段階1 / 2 / 3 / 4 / 6）で push した★ |
| ★21★ | ★★一時スクリプトを削除し、`.env.local` が `USE_FIXTURES=true` に戻っている★★ |

---

## 実行状態

| 項目 | 内容 |
|---|---|
| 状態 | ★**完了**★ |
| 実行日 | 2026-09-11 |
| 決定番号 | ★**D-113**★（指示書W が未実行だったため、当初見込みの D-114 ではなく D-113 を採った） |
| コミット | `d8064a1`（段階1）/ `1fafea7`（段階2）/ `0b48ce4`（段階3）/ `fc99231`（段階4）/ 本コミット（段階6） |
| 実測値 | `tsc`0 / `lint`0 / `npm test` 45ファイル941テスト / `precheck_minutes`内訳(60=8/30=6/null=13)が着手前と完全一致 / `precheck`分布が0=259/60=253/30=253に変化。詳細は `docs/decisions.md` の **D-113** / `docs/handoff.md` **追記34** を参照 |
| ★DDL★ | ★`clean_watch_20260911.sql` から `dwatch_local` を作り直し、`db pull`（3回目）/ `generate` 済み。0909版との実質差分は `facility_contacts.email`（varchar100化）と `robot_task_versions.clean_schedule_id` のFK追加の2件のみ★ |
