# 指示書R — ローカルDBで動かす第3歩（`prismaSource.cleanLogs()` を実DB経由にする）

| 項目 | 内容 |
|---|---|
| 実行環境 | ★**Cursor 内の Claude が実装します**★（`.claude/commands/d-watch.md` の役割分担表を参照） |
| 実行状態 | ★**実行済み（2026-09-10。D-099）**★ |
| 実行順 | ★**指示書P（D-097）の続き**★。★指示書M / P が作った `scripts/seed-local-db.ts` / `lib/db/prisma.ts` / `lib/db/local-guard.ts` / `lib/db/use-fixtures.ts` / `lib/db/ds-status.ts` / `lib/format/date.ts` の上に積む作業★です。★`prismaSource.facilities()` / `schedules()` / `dsDevices()` / `dsAlerts()` は1文字も変えません★。★触るコードが重なる未実行の指示書は、着手直前に自分で確認すること★（作成時点の未実行は ★指示書I（`lib/auth/` / `proxy.ts`）／指示書J（`lib/settings/`）★ の2本）。★`docs/decisions.md` / `docs/handoff.md` は共通で触ります。同時に走らせないこと★ |
| 取得する決定番号 | ★**着手直前に `grep -n "^### D-0" docs/decisions.md \| tail -3` で最終番号を実測すること**★。★作成時点の最終は `D-097`（`docs/decisions.md:4238`）なので、最初の空き番号は `D-098` の見込み★。★2026-09-07 に `D-068` の二重採番が実際に起きています（D-070 冒頭）。必ず自分で確認してください★ |
| 前提 | ★**D-092**★（`docs/decisions.md:3599`。★本指示書の設計根拠。「1時間前の状態」は行として記録されている★） / ★**D-093**★（`:3719`。`clean_statuses` は「今の状態」用 / `precheck` は任意の分数） / ★**D-091 項1**★（`:3459`。出発判定は DB の値を使う） / ★**D-090 項1〜5**★（`:3372` 付近。`cleanLogs` の未決5件） / ★**D-026**★（`:122`。連続はスケジュール存在日） / ★**D-065 Q2**★（`:512`。エラーがあればそれ、無ければ standby） / ★**D-077**★（`:1847`。エラー種別を変換しない） / ★**D-082 / D-088**★（`:2392` / `:3084`。B2 の暫定マッチ値） / **D-049**（`:297`。`planned` はスケジュール由来） / **D-042**（`:226`） / **D-050**（`:308`） / **D-056**（`:353`） |
| 段階数 | ★**5段階**★（段階0〜段階4） |
| 着手前の基準 | ★**着手直前に自分で `npm test` / `npx tsc --noEmit` / `npm run lint` を実行し、その実測値を基準にすること**★（作成時点の参考値は D-097 の「33ファイル / 631テスト / `tsc` 0 / `lint` 0」だが、★鵜呑みにしない★） |
| 作成 | 2026-09-10（作成時点の決定事項最終番号 **D-097** / `docs/handoff.md` の最新は **追記27**（`docs/handoff.md:2187`）） |

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

> ★**この指示書を読んでいるあなたへ**★ — `.claude/commands/d-watch.md` の「実装は Cursor 側」は
> **2つの環境の役割分担**です。**Cursor の中で動いている Claude は実装担当**です。
> ★調査や改訂ではなく実装を行ってください。★
> ★★**うまくいかなければ途中で止めて構いません**★★ — `.env.local` の `USE_FIXTURES` を
> `true` に戻すだけで完全に元の状態に戻ります。★DB に書いたデータも `dwatch_local` の中だけ★。

---

## ★★冒頭に読むべきこと — なぜ `security_off` を含めないのか★★

★**本指示書の範囲は `prismaSource.cleanLogs()` の1本だけです。`dsAlerts()` には触りません。**★

★D-090 項2 の当初計画では、第3歩を「`dsAlerts` の `security_off` ＋ `cleanLogs`」の2本立てに
していました。★**`security_off` は外します**★。

理由は ★**D-091 項3（`docs/decisions.md:3503`）のユーザー判断**★です。原文:

> ★セキュリティが無効であっても異常ではないのでアラートを出すようなものでもないので、
> 「未対応 / 対応中 / 対応済み」を切り替えも削除する可能性があります
> D-secアラートはサーバーとの生死報告が途切れたなどのエラーのみでアラートを出すのみにできるように
> 内部で調整します★

- ★★これは「保留」であって「廃止確定」ではありません★★。★内部調整の結果を待ちます★
- ★調整中のものを実装すると、確定後に捨てる作業になります★。★D-090 項9 / 項10（対応カラムが
  存在しない）も、`security_off` 自体が消えれば消滅します★
- ★したがって `dsAlerts()` は指示書P が実装した `disconnected` のみのまま。1文字も変えないこと★
- ★帰結（第2歩から変わらない）★: `/ds-alerts` は5件のまま。`/dashboard` のセキュリティバナーは
  出ないまま。★意図した挙動であり、本指示書で直す対象ではありません★（D-097 §2）

---

## ★★着手前確認（1件）— ★回答済み（2026-09-10）★★★

### ★`status_1h` / `status_30m` で「エラーが無いとき」に何を入れるか★

★**結論: B案 `Standby.`（原文どおり。★末尾のピリオド込み★）**★。★決定は `docs/decisions.md` D-098
（`docs/decisions.md:4342`）に記録済み★。★段階1 に着手してよい★。

**事実**（★すべて現物で裏を取ってから使うこと★）:

- ★D-065 Q2（`docs/decisions.md:517`）★: 「エラーが発生していれば `robot_error_reports` に
  記録される。★エラーが無ければ standby（待機）★」。★D-077（生値方針。`docs/decisions.md:1847`）★:
  ★DB から来た値をそのまま表示する。日本語に変換しない★
- ★★文言は DB に無い★★ — ★`dwatch_local` の DDL にカラムコメントは1つも無く
  （`information_schema.columns.column_comment` が全て空）、状態名のマスタテーブルも存在しない★。
  ★DB が持っているのは数値だけ★
- ★★文言の出所は TeamWorks から受領した `robots.status` のコード表★★ —
  `docs/handoff/TeamWorks回答_第2回_20260903.md:56` の ★`'0'=>'Standby.',`★（★ピリオド込みが原文★）。
  ★このコード表は `:50` の見出しで「★原文のまま。改変しないこと★」と明記して保存されている★
  （同ファイル `:104-105` に `-200` の `'Lost Loaction.'`（誤記）、`300` の `'Vacuum Idle..'`
  （ピリオド2つ）を原文のまま記録した注記がある）
- ★D-070 D-2（`docs/decisions.md:1132`）★: 「待機中・充電中は `robots.status`」。
  ★「エラーが無いとき」の状態の出所が `robots.status` である以上、当てる文言もその値表から採る★
- fixtures の既定は ★`"待機中"`★（`lib/fixtures/clean-logs.ts:55-56`）。★ただし同じ箇所のコメントが
  「日本語6値は fixtures 由来の暫定値であり、実データから来る値ではない」と明記している★
  （`lib/config/index.ts:141` にも同旨）
- ★`lib/theme-status.ts:15-19` の色分けは完全一致★。★日本語6値以外はすべてグレーに
  フォールバックする（例外もテスト失敗も出ない）★。★色分けの対応表そのものは
  `lib/config/index.ts:147-153` の `ROBOT_STATUS_TONES`（`待機中: "ok"`＝緑）★

**選択肢**（★検討時の3案。比較として残す★）:

| 案 | 入れる値 | 帰結 |
|---|---|---|
| A | ★空文字列 `""`★ | ★★当方は値を1つも作りません。D-077 の生値方針に最も忠実★★。★`lib/format/raw-label.ts` の `formatRawLabel()` が既に空値を `(値なし)` と表示する仕組みを持っており、★D-089 項7（`docs/decisions.md:3263`）が「同じ問題は `status_1h` / `status_30m` にも起こりうる。次にこれらを扱う指示書で流用を検討すること」と申し送っている★★。★ただし現状 `status_1h` の表示には未適用なので、画面には空のバッジが出ます（★本指示書では画面を触らないので、そのまま記録する★）★ |
| ★**B（採用）**★ | ★**`"Standby."`**★（★末尾のピリオド込み★） | ★`robots.status = 0` のコード表の★原文★（`docs/handoff/TeamWorks回答_第2回_20260903.md:56`）であり、★当方が作った値ではない★★。★D-065 Q2 の回答文言（standby）とも一致する★。★英語識別子なので、生値（`EmergencyStop` 等）と見た目が揃う★ |
| C | `"待機中"` | ★fixtures と一致するので `USE_FIXTURES` の true/false 突き合わせが楽になり、★色分けも効く★★ |

★**回答（2026-09-10。ユーザー原文を引用）**★:

> これは清掃詳細ログなどで本番清掃が完了した際に正常に終了したときに
> 表示されるものと同じ文言でいいです

★★当方はこの回答を受けて、一度 fixtures の既定値 `"待機中"` を根拠に「C案」と結論した。
これは誤りだった★★。★ユーザーから根拠の取り方そのものを訂正された（同日。原文）★:

> fixturesはモックのイメージなので基本はDBの値を正として考えてください
> ローカルで参照できるDBを作成してもらったはずなので、そちらを使用して確認してください

★ローカルDB（`dwatch_local`。D-095）と受領資料を調べ直した結果、上記「事実」のとおり
★文言の出所は TeamWorks のコード表だけ★であることが判明した。★**最終回答（同日。原文）**★:

> 原文どおり Standby. で進めてください

★**採用: B案（`"Standby."`。ピリオド込み）**★。

★**A案・C案を採らなかった理由**★:

- ★A（空文字列）★: ★★情報が消える★★ — 「エラーが無い（正常）」のか「値が取れなかった」のかを
  ★区別できなくなる★。`formatRawLabel()` が当てる `(値なし)` は「カラムが空である」という
  別の事実を述べる文言であり、正常を意味しない
- ★C（`"待機中"`）★: ★★fixtures 由来でしかなく、DB にも TeamWorks の回答にも存在しない★★。
  ★当方が作った日本語であり、D-077 が避けてきた「英語識別子を日本語に変換する」ことの
  再生産になる★。★色分けが効くという利点は、値の正しさとは無関係の副次的な都合にすぎない★

★**D-077（生値方針）との整合**★: ★`Standby.` は先方公式のコード表の原文であり、
当方による分類・推測を一切含まない★。★他のエラー種別（`EmergencyStop` 等）と同じく
英語識別子がそのまま画面に出るので、画面上の見た目が一貫する★。

★**方法論の教訓（`docs/decisions.md` D-098 項2）**★: ★★値・型・値域を判断するときは
fixtures を根拠にしないこと★★。★先に見るのは ①ローカルDB `dwatch_local`（実DDL 0909 と
列レベルで一致。D-095）、②TeamWorks の回答文書★。★`CLAUDE.md:227`「型・値域・カラム名は
実DDLが正」／`CLAUDE.md:80` `:223`（fixtures は「開発用データ」「サンプルデータの正」であって
値そのものの正ではない）★。★D-080・D-092 と同じく、答えは受領済みの資料の中に既にあった★。

★**実装時の注意（3件。段階1・段階2 の判断事項）**★:

1. ★`Standby.` を複数箇所に直書きせず、★1箇所の定数にまとめること★★
   （指示書O が `EMPTY_VALUE_LABEL`（`lib/format/raw-label.ts:19`）を作ったのと同じ形）。
   ★段階2 の作業★
2. ★★色分けがグレーになる — 段階2 の判断事項★★。`lib/config/index.ts:147-153` の
   `ROBOT_STATUS_TONES` は★日本語6文字列の完全一致で引く★ため、★`Standby.` は一致せず
   グレー（`c.txD`）にフォールバックする★（`lib/theme-status.ts:15-19`。★例外もテスト失敗も
   出ない★）。★これは D-077 の想定挙動そのもので、本書の「完了条件（全体）」6
   「`status_1h` に英語識別子が出て、色分けがグレーになること」と一致する★。
   ★ただし「正常な行が全部グレー」は見た目に効く★ので、
   ★`ROBOT_STATUS_TONES` に `Standby.` を足すかを段階2 で判断すること★。
   ★色を足すのは「値の変換」ではなく「表示の装飾」なので D-077 には触れない★。
   ★足す場合も `lib/config/` の設定値に足すだけにとどめ、`lib/theme-status.ts` のロジックは変えないこと★
3. ★★fixtures 側をどうするか — 段階1 の判断事項★★。`lib/fixtures/clean-logs.ts:55-56` の既定値は
   現在 `"待機中"`。★指示書K（D-088）で `error_type` を英語識別子に寄せたのと同じく、
   fixtures も `Standby.` に寄せるのが一貫する★。★ただし fixtures を変えると当日分10行の均衡
   （D-049。`docs/decisions.md:297`）や既存テストに影響しうる★。★★しかも禁止事項12 が
   `lib/fixtures/` の削除・改変そのものを禁じている★★ので、★寄せると判断するなら
   ★禁止事項12 の解除をユーザーに確認してから★段階1 で行うこと★。
   ★確認が取れない場合は寄せない（＝現状維持）★。★寄せない場合、段階3 の確認1
   （`USE_FIXTURES` の true/false 突き合わせ）で `status_1h` に差が出るのは想定内★として記録すること

> ★**注記（確認事項には格上げしていないが、認識合わせが要る点）**★:
> ★後述「調査済みの事実」5 のとおり、fixtures の当日分10行のうち1行（施設12）は
> **構造的にシードできません**★。★`/dashboard` は fixtures の `9 / 10 完了` に対し、
> DB 経由では ★`9 / 9 完了`★ になる見込みです★。★これは指示書P の `DS-012`（着手前確認A案。
> D-097 §4）と同型の問題で前例があるため、段階1 の設計判断として扱います★。
> ★認識が違うようなら、段階1 に入る前に確認すること★。

---

## この指示書の位置づけ — ★残り8メソッド中1つだけ★

`lib/db/index.ts` の `prismaSource`（`:154-340`）は、`facilities()` / `schedules()`（指示書M）と
`dsDevices()` / `dsAlerts()`（指示書P）の4本が実装済み。★残り8本は `notImplemented()` のまま★。
★本作業で実装するのは ★`cleanLogs()`（`lib/db/index.ts:155-157`）★ の1本だけ★です。

★使われる画面★: ★F-02 清掃詳細ログ★（`/detail-log`）+ ★F-01 ダッシュボード★ + ★F-03 アラート★
（`app/api/clean-logs/route.ts` / `.../export/route.ts` / `.../[id]/advice/route.ts` /
`app/api/dashboard/route.ts:57`）。

★これ1本を選んだ理由★（D-090 項2）:

- ★`cleanLogs` を先に仕上げると、B2 / B3 の発火と色分けの検証がここで初めて可能になる★（D-082 / D-088）
- ★`prechecks`（第4歩）は同じ join を流用でき、`continuousProblems`（第5歩）は `cleanLogs` の
  連続日数算出に依存しており単独では書けない★

★**第2歩と比べて質が違います**★: 第2歩は「変換だけ」でしたが、★第3歩は
**手動 join ＋ judge の呼び出し ＋ 連続日数の全期間走査**を含みます★。★段階を飛ばさないこと★。

---

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

### 1. ★現在のシード状況（2026-09-10 実測。★自分で数え直すこと★）★

| テーブル | 行数 |
|---|---|
| `groups` / `customers` / `devices` | 1 / 1 / 1 |
| `facilities` / `sections` / `robots` | 12 / 14 / 14 |
| `clean_schedules` | 12 |
| `controls` / `control_reports` / `control_error_histories` | 11 / 14 / 5 |
| ★`clean_timetables` / `clean_statuses` / `clean_status_histories` / `robot_error_reports`★ | ★**すべて 0**★ |

★つまり第3歩は「清掃ログ系4テーブルのシードを足す」ところから始まります★。

### 2. ★★`CleanLogItem` の組み立て方（D-092 / D-093 / D-065 Q2 で判明した仕組み）★★

★これが第3歩の核心です★:

```
clean_schedules (1件)
      │  clean_schedule_id（FK あり）
      ▼
clean_timetables ← ★1回の清掃につき3行★
      ├─ precheck = 0 … 本清掃      parent_id = 0
      ├─ precheck = 1 … 1回目(60分前) parent_id = 本清掃行の id
      └─ precheck = 2 … 2回目(30分前) parent_id = 本清掃行の id
                │
                │ clean_timetable_id  ★FK も KEY も無い。手動 join★
      ┌─────────┼──────────────────────┐
      ▼         ▼                      ▼
clean_statuses  clean_status_histories  robot_error_reports
（今の状態）    （過去。D-093 項1）      （エラー。無ければ standby）
```

| # | 事実 | 根拠 |
|---|---|---|
| 1 | ★`clean_timetables.precheck` は `0` = 本清掃 / `1` = 1回目 / `2` = 2回目★ | `prisma/schema.prisma:582-584`（TeamWorks 回答 B-1）。★D-091 項4 で `0` / `60` / `30` に変わる予定、D-093 項2 で「任意の分数」と判明。★**現行DDLは 0/1/2 のままなので、シードは現行値で作る**★★ |
| 2 | ★`clean_timetables.parent_id` は NOT NULL★。★本清掃は `0`、事前チェック行は親（本清掃）の id★ | `prisma/schema.prisma:576-578` |
| 3 | ★`clean_statuses.clean_timetable_id` で紐づく行が、その時点の清掃状態を持つ★ | `prisma/schema.prisma:429`。D-092 項1-2 |
| 4 | ★`clean_status_histories` が同じ構造で履歴を持つ★。★`clean_statuses` は「今の状態」用。過去は履歴側★ | `prisma/schema.prisma:462-499`。★D-093 項1★ |
| 5 | ★`robot_error_reports.clean_timetable_id` でエラーが紐づく★ | `prisma/schema.prisma:312`。D-092 項1-4 |
| 6 | ★エラーがあればその値、無ければ standby（待機）★ | ★D-065 Q2（`docs/decisions.md:517`）★ |

★→ `status_1h` / `status_30m` は「precheck = 1 / 2 の行に紐づくエラーの有無」で作ります★。
★D-077 の生値方針なので、`error_type`（`prisma/schema.prisma:320`）の値を**そのまま**入れます。
日本語に変換しないこと★。

### 3. ★★変換表 — これが `prismaSource.cleanLogs()` の中身そのものです★★

| 出力（`CleanLogItem`。`lib/types/index.ts:51-75`） | 入力 | 変換 |
|---|---|---|
| `id` / `facility_id` | ★`clean_timetables.id` / `.facility_id`（**本清掃行**）★ | ★`Number()`★。★D-090 項3（`id` の出所が不明）を本指示書で決める★ |
| `facility_name` / `serial_number` | `facilities.facility_name` / `robots.serial_number` | ★結合して取る（手書き文字列を使わない）★。★serial は `section` 経由（`schedules()` の `:301` と同じ作法）★ |
| `model_name` | `devices.maker_id` + `devices.model_name` | ★`robots` → `devices` を結合★。★D-090 項4（連結規則が未定義）を本指示書で決める★。★`lib/db/index.ts:275` の `facilities()` が `MAKER_NAMES[maker_id]` を使う前例に合わせ、`"PUDU CC1"` の形（半角スペース区切り）にすること★ |
| `scheduled_at` | ★`clean_timetables.start_at`（本清掃行）★ | ★`toJstIso()`（`lib/format/date.ts:36`）★ |
| ★`status_1h` / `status_30m`★ | ★precheck = 1 / 2 の行に紐づく `robot_error_reports.error_type`★ | ★生値のまま★。無ければ★着手前確認の回答値★ |
| ★`departed`★ | ★DB の値（後述 6-1）★ | ★★「予定＋30分」を当方で計算しない★★（D-091 項1） |
| `completion_rate` | ★`clean_status_histories.clean_ratio`（`Float`）★ | ★丸めない生値★。★未実施は `null`★ |
| ★`grade` / `escalation_level`★ | — | ★`judgeGrade()` / `judgeEscalationLevel()` を呼ぶ★（D-090 項16） |
| ★`consecutive_problem_days`★ | ★DB に無い★ | ★スケジュール存在日ベースで算出（後述 6-3）★ |

### 4. ★★実装上の落とし穴（★現物で確認すること★）★★

| # | 落とし穴 |
|---|---|
| ★1★ | ★`clean_statuses.clean_timetable_id`（`prisma/schema.prisma:429`）/ `clean_status_histories.clean_timetable_id`（`:472`）/ `robot_error_reports.clean_timetable_id`（`:312`）に **FK も KEY も無い**★。★Prisma のリレーションが張られていないので `include` が使えず、**別クエリ＋メモリ上の手動 join** が必要**★（D-092 項3-3。D-094 で TeamWorks に再依頼中） |
| ★2★ | ★`clean_timetables` / `clean_statuses` / `clean_status_histories` / `robot_error_reports` のいずれにも `deleted_at` が無い★。★第1歩・第2歩の癖で `where: { deleted_at: null }` を書くと**型エラーになる（＝安全側）**★。★型エラーは「このテーブルには論理削除が無い」というサイン。無理に回避しないこと★（`clean_timetables` にあるのは `inactive`。★これでも絞らない★） |
| ★3★ | ★`clean_statuses.clean_status` / `clean_status_histories.clean_status` は**符号あり**（`Int @db.TinyInt`。`:433` / `:476`）★。★`-1` が「清掃完了（セキュリティ無効）」で**最も良い状態**★（TeamWorks 回答 A-2）。★`>= 2` のような大小比較で「完了」を判定すると `-1` が漏れる★ |
| ★4★ | ★`clean_timetables.start_at` / `end_at` は `datetime`（TZ情報なし。`:580-581`）★。★API は `+09:00` 付き ISO8601★。★`toJstIso()`（`lib/format/date.ts:36`。指示書P で追加済み）を使うこと★ |
| ★5★ | ★率・面積は `double`★（`clean_ratio` / `task_area` / `clean_area`。D-094 / D-096 で追従済み）。★`Decimal` → `number` の変換は不要になった★。★D-090 項5（整数だから面積から再計算が要る）は**解消済み**★ |
| ★6★ | ★`robot_error_reports.id` / `error_id` は `varchar`（`:303-304`）★。★`Number()` が `NaN` になっても例外は出ない★。★`RobotErrorReport` には `@id` が無く `error_id @unique` だけ★ |
| ★7★ | ★`BigInt` → `Number()` を忘れると `JSON.stringify` が例外を投げる★（指示書M 変換表の1行目。`NextResponse.json` が 500 になる。★`tsc` は spread では捕まえない★） |

### 5. ★★新発見 — fixtures の清掃ログには「シードできない行」が27行ある★★

★指示書P の `DS-012` と同型の構造的不整合です。★**着手前に必ず読むこと**★。

★`clean_timetables` は `facilities` / `sections` / `clean_schedules` の3つに NOT NULL の FK を
持ちます★（`prisma/schema.prisma:588-590`）。★対応する行が無い清掃ログはシードできません★。

| # | serial | 施設 | 行数 | 欠けているもの |
|---|---|---|---|---|
| ★1★ | ★`8111R5628060166`★ | ★施設12（フレッシュタウン湖畔店）★ | ★1行★（`lib/fixtures/clean-logs.ts:92`。★**当日分・未実施(null)の境界値**★） | ★ロボットが無い（`lib/fixtures/facilities.ts:213` の `robots: []`）→ section も clean_schedule も無い★（指示書M / D-086 で除外済み） |
| ★2★ | ★`8111R5628060088`★ | 施設5（港南総本店・2台目。ロボット106） | 1行（`lib/fixtures/clean-logs.ts:158`） | ★`lib/fixtures/schedules.ts` に対応するスケジュールが無い★ |
| ★3★ | ★`8111R5628060155`★ | 施設11（バリューマート川下店・ロボット114） | ★25行★（`lib/fixtures/clean-logs.ts:177` の履歴生成。d=6〜30） | 同上 |

★合計 27行。280行 → **253行** がシード対象になります★（★自分で数え直すこと★）。

★★帰結（段階3 の期待値に直結する）★★:

| 画面 | fixtures | ★DB 経由（見込み）★ |
|---|---|---|
| `/detail-log` の総件数 | ★280件★ | ★253件★ |
| `/dashboard` の当日分 | ★10行★ | ★9行★ |
| ★`/dashboard` の `schedule_progress`★ | ★`9 / 10`★（`planned` は木曜で10件） | ★★`9 / 9`（100%）★★ — ★施設12 のスケジュール（fixtures id 33）も未シードなので `planned` も1件減る★ |

★★これは「壊れた」のではなく「fixtures 側に DB では表現できない行がある」ことの帰結です★★。
★段階4 で必ず記録すること★。

**選択肢**（★段階1 で決めて記録すること★。★推奨は A★）:

| 案 | 内容 | 帰結 |
|---|---|---|
| ★**A（推奨）**★ | ★3件とも構造的にスキップし、スキップ件数（27行）が想定と違えば例外で止める★ | ★指示書M / P と同じ作法★（`scripts/seed-local-db.ts:156-174` / `:190-208`）。★`facility_id` や serial を名指ししない★。★指示書P の `DS-012` で同型の判断をユーザーが A 案で承認済み（D-097 §4）★ |
| B / C | `8111R5628060088` / `8111R5628060155` に `clean_schedules` を足す / 別施設のスケジュールに紐づける | ★B は `/schedules` が 12件 → 14件 になり指示書M で確認済みの状態が変わる（施設11 は `monitoring_enabled: false` なので清掃スケジュールを持つのも不自然）★。★C は `clean_timetables.section_id` と `clean_schedules.section_id` が食い違うデータが生まれる★。★どちらも採らない★ |

### 6. ★決定済みで、実装に反映すべきこと★

#### 6-1. ★`departed` は DB の値を使う（D-091 項1）★

- ★当方で「予定時刻＋猶予30分」を計算しないこと★。★`LAUNCH_GRACE_MINUTES`（`lib/config/index.ts:31`）を
  `cleanLogs()` から読まないこと★
- ★ただし「DB のどの値を見るか」は未確定★です。★`clean_status`（`-1` / `0` / `1` / `2`）の4値に
  「出発しなかった」が見当たらず、★**行が作られないことが未実施を意味する可能性**★★（D-091 項1 の後段）
- ★★シードは自分で作るので、そこで方針を決めて記録すること★★。★推奨: 「本清掃行に紐づく
  `clean_status_histories` の行が存在しない ＝ 未出発」★とし、★`departed: false` の清掃は
  本清掃の `clean_timetables` 行だけを作って `clean_status_histories` を作らない★。
  ★この形なら `completion_rate` の `null` と自然に一致します★
- ★★選んだ方針を、シードとメソッドの両方にコメントで残し、「実データで確認すべきこと」として
  決定台帳にも書くこと★★

#### 6-2. ★`grade` / `escalation_level` は `prismaSource` の中で `lib/judge/` を呼ぶ★

- ★D-090 項16 で確定済み★。★fixtures 版がそうしています★ —
  `lib/fixtures/clean-logs.ts:59`（`judgeGrade`）/ `:61`（`judgeEscalationLevel`）。★`:16-22` に理由★
- ★`CLAUDE.md`「判定ロジックは切替の外側に置く」は「`lib/judge/` を fixtures 用と Prisma 用に
  分岐させるな」の意味★であり、「`lib/db/` の外で呼べ」ではありません（`lib/judge/index.ts:3-5`）

#### 6-3. ★`consecutive_problem_days` の算出（D-026）★

- ★DB に対応するカラムはありません★。★スケジュール存在日ベースで算出します★
- ★「問題あり」の定義は fixtures と揃えること★ — ★△以下（`completion_rate < GRADE_THRESHOLDS.good`）
  または未実施★（`lib/fixtures/clean-logs.ts:216-217` を実測して合わせる）
- ★`lib/judge/escalation.ts:32` の `countConsecutiveProblemDays()` が既にあります★。
  ★`:29` のとおり「スケジュール存在日のみの結果を日付昇順で渡す」契約で、
  どの日がスケジュール存在日かの判断は**呼び出し側の責任**★（`:13-15`）
- ★★全期間走査になります★★ — ★`serial_number`（＝ section）ごとに全行を日付昇順に並べ、
  各行について「その行までの連続」を数える★。★`lib/db/index.ts:66-67` の設計方針と衝突しますが、
  ★D-090 項15 のとおり「`DataSource` は引数を取らない契約なので、ここでやるしかない」★★
- ★★実装方針をコメントに残すこと★★。★年間13万行（`docs/spec.md:45`）なので、実データでは
  性能が問題になりうる旨も書くこと★（D-092 項3-3）

---

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

| # | ★してはいけないこと★ | 理由 |
|---|---|---|
| 1 | ★★**`dsAlerts()` の `security_off` に手を付けること**★★ | ★D-091 項3 で保留中★。★調整中のものを実装すると捨てる作業になる★ |
| 2 | ★★**エラー種別を日本語に変換すること**★★ | ★D-077 / D-088 の生値方針★。★7分類へのマッピングも禁止★。★`error_type` の値をそのまま入れる★ |
| 3 | ★★**出発判定を「予定＋30分」で当方が計算すること**★★ | ★D-091 項1★。★設定値を変えた瞬間に過去のログの判定まで変わる★ |
| 4 | ★**`deleted_at` が無いテーブルで論理削除の絞り込みを書くこと**★ | ★型エラーになる（＝安全側）。落とし穴2★ |
| 5 | ★**`inactive` で絞ること**★ | ★第1歩・第2歩と同じ方針★（`lib/db/index.ts:164-166`）。★仕様が未確定なので絞らない側に倒す★ |
| 6 | ★★**メインDB（TeamWorks 側）に接続・書き込みすること / `dwatch_ddl` に書き込むこと**★★ | `CLAUDE.md` §2 / §7 / D-042。★作業は `dwatch_local` の中だけ★。★接続先ホスト名は未受領★。★`dwatch_ddl` は参照専用★ |
| 7 | ★**`prisma migrate` / `db push` / `db pull` を使うこと / `prisma/schema.prisma` を手で書き換えること**★ | ★`CLAUDE.md` §7 / D-056 / D-059★。★テーブルは既にある★ |
| 8 | ★**`prismaSource.facilities()` / `schedules()` / `dsDevices()` / `dsAlerts()` を変更すること**★ | ★指示書M / P の成果物★。★本作業は「その隣に1本足す」だけ★ |
| 9 | ★**`lib/db/prisma.ts` / `local-guard.ts` / `use-fixtures.ts` / `ds-status.ts` を変更すること**★ | ★完成しており、そのまま効く★。★特に `use-fixtures.ts` は Edge Runtime バンドル破壊の修正そのもの★（D-086） |
| 10 | ★**`lib/clean-logs/query.ts` / `lib/dashboard/` / API ルート / 画面を変更すること**★ | ★本作業はデータ層の差し替え★。★**これらが1文字も変わらないまま画面が実DBで動くこと**が、変換が正しいことの証明★ |
| 11 | ★**`lib/db/` にフィルタ・ページング・ソートを持ち込むこと**★ | ★`lib/db/index.ts:64-68` の設計方針★。★ただし judge と連続日数は例外（D-090 項15/16。6-2 / 6-3 参照）★ |
| 12 | ★**`lib/fixtures/` を削除・改変すること**★ | ★`CLAUDE.md` §9.5★。★当日分10行の均衡（D-049）を崩さないこと★ |
| 13 | ★**テストを DB 依存にすること**★ | ★遅く・不安定になり、DB が空の環境で落ちる★。★テストは fixtures と純粋関数だけ★ |
| 14 | ★**閾値（90/70/10・30分・L2=2日/L3=3日）や `precheck` の値をハードコードすること**★ | `CLAUDE.md` §7。★`precheck` は D-091 項4 / D-093 項2 で変わる予定なので `lib/config/` の定数にすること★ |
| 15 | ★**シードに実在の住所・電話番号・社名・人名を入れること**★ | `CLAUDE.md` §7 / D-043 |
| 16 | ★**`git add -A` を使うこと**★ | `CLAUDE.md` §7。★必ずファイルパスを明示★ |
| 17 | ★**`.env.local` をコミットすること / DB の認証情報を文書やコードに書くこと**★ | `CLAUDE.md` §9.3。★確認後は `USE_FIXTURES=true` に戻す★ |

---

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

| 段階 | 内容 | コミット |
|---|---|---|
| 段階0 | 前提の確認（★読むだけ★） | **コミットなし** |
| 段階1 | ★清掃ログ系4テーブルのシードを足す★ | 段階1で1コミット |
| 段階2 | ★`prismaSource.cleanLogs()` を実装する★ | 段階2で1コミット |
| 段階3 | ★**動作確認（ここが本題）**★ | コミットなし |
| 段階4 | 記録と push | 段階4で1コミット + push |

★**段階ごとに `npx tsc --noEmit` / `npm run lint` / `npm test` を実行し、通ってからコミットすること**★
（D-050。`docs/decisions.md:308`）。

---

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

### 読むもの

| # | 対象 | 見る点 |
|---|---|---|
| 1 | ★D-092★（`docs/decisions.md:3599`）/ ★D-093★（`:3719`） | ★仕組みそのもの★。★D-092 項1 の5点連鎖 / 項3 の残る不明点3件★。★D-093 項1: `clean_statuses` は「今の状態」用 / 項2: `precheck` は任意の分数★ |
| 2 | ★D-091 項1★（`:3459`）/ ★D-026★（`:122`）/ ★D-065 Q2★（`:512`） | ★出発判定は DB の値★ / ★連続はスケジュール存在日★ / ★エラーが無ければ standby★ |
| 3 | ★D-090 項1〜5・項15・項16★（`:3372` 付近） | ★`cleanLogs` の未決5件と、judge / 集計を `prismaSource` でやる根拠★ |
| 4 | ★指示書P の `dsDevices()` / `dsAlerts()`★（`lib/db/index.ts:161-244`） | ★**雛形として読む**★。★遅延 import・docstring の書き方・`Number()`・`toJstIso()` の使い方をそのまま踏襲する★ |
| 5 | ★`scripts/seed-local-db.ts`★（全461行） | ★冪等の作り（`:233-242`）★ / ★構造的スキップの作法（`:156-174` / `:189-208`）★ / ★id を明示する方針（`:14-16`）★ |
| 6 | ★`lib/fixtures/clean-logs.ts`★（全233行 / ★280行データ★） | ★`row()`（`:44-63`）が judge を呼んでいること★ / ★境界値10件（`:82-93`）★ / ★B2 の `Handrail Lifted`（`:112`）★ |
| 7 | ★`lib/db/ds-status.ts`★（全61行）/ ★D-086 の「段階5で発見した想定外の不具合」★（`:2912-2930`） | ★コード値変換を純粋関数に切り出す作法（段階2 で同じ形にする）★ / ★Edge Runtime バンドル破壊。404 が出たらこれを疑う★ |

### ★接続先の確認（★これを飛ばさないこと★）★とこの段階の完了条件

- ★`.env.local` の `DATABASE_URL` が `dwatch_local` を指していることを確認する★。
  ★**値そのものを画面・ログ・文書に出さないこと**★（`CLAUDE.md` §9.3）。
  ★確認は「ローカルを向いているか」だけで十分★（`lib/db/local-guard.ts` と同じ判定）
- 上記7点を読んだ
- ★着手前確認（`status_1h` の「エラーが無いとき」の値）の回答を得た★
- ★着手前の基準値（`npm test` のファイル数・テスト数 / `tsc` / `lint`）を自分で実測して控えた★
- ★「調査済みの事実」1 の件数と、5 の27行を**自分で数え直した**★

---

## 段階1 — 清掃ログ系4テーブルのシードを足す

### 1-1. 作るもの

★`scripts/seed-local-db.ts` に**追記**する★（新規ファイルを作らない）。
★ガード（`assertLocalDwatchDatabaseUrl`。`:46`）はそのまま効きます★。

★挿入順（FK依存）★:
`clean_timetables` → `clean_statuses` → `clean_status_histories` → `robot_error_reports`

★`lib/fixtures/clean-logs.ts` の `cleanLogs` を import して変換します★
（★第1歩・第2歩と同じ方針。fixtures を単一の出所に保つ★）。

### 1-2. ★シードできない行を構造的に弾く★

★「調査済みの事実」5 のとおり27行がシードできません★。★推奨は A 案★:

- ★`serial_number` から `{facilityId, sectionId}` を引く既存の対応表（`scripts/seed-local-db.ts:143-151`）を
  再利用し、引けない行を弾く★（→ `8111R5628060166` の1行）
- ★`serial_number` → 投入済み `clean_schedules.id` の対応表を新たに作り、引けない行を弾く★
  （→ `8111R5628060088` の1行 + `8111R5628060155` の25行）
- ★★`facility_id` や serial を名指ししないこと★★（`:154` / `:187` の作法）
- ★スキップ件数と内訳を出力し、想定（27行）と違ったら例外で止めること★

### 1-3. ★`clean_timetables` — 1回の清掃につき3行★

| 列 | 値 |
|---|---|
| `id` | ★本清掃 = fixtures の `CleanLogItem.id`（1001〜1280）★。★precheck 行は衝突しない別採番★（例: `本清掃id + 100000` / `+ 200000`。★規則をコメントに書くこと★） |
| `facility_id` / `section_id` | ★対応表から引く★ |
| `clean_schedule_id` | ★serial → schedule の対応表から引く★ |
| ★`parent_id`★ | ★本清掃 = `0` / precheck 行 = 本清掃行の `id`★（`prisma/schema.prisma:576-578`） |
| ★`start_at`★ | ★本清掃 = `scheduled_at`★ / ★precheck 行 = `scheduled_at` の `PRECHECK_MINUTES_BEFORE` 分前★（`lib/config/index.ts:34` の `[60, 30]`。★直書きしない★） |
| `end_at` | ★NOT NULL★。★本清掃は適当な終了時刻、precheck 行は `start_at` と同値でよい★（★決めた規則をコメントに書くこと★） |
| ★`precheck`★ | ★`0` / `1` / `2`★（★現行DDLの値。D-091 項4 で変わる予定だが、★今は 0/1/2★★）。★`lib/config/` の定数にすること★ |

### 1-4. ★`clean_statuses` / `clean_status_histories`★

- ★D-093 項1 のとおり `clean_status_histories` が「過去の清掃結果」の置き場★。
  ★`/detail-log` は最大31日遡る画面（`docs/spec.md:40`）なので ★**履歴側を主にする**★★。
  ★`clean_statuses`（今の状態）にも★当日分だけ★同じ行を入れておくこと（第4歩で検証しやすい）★
- ★`clean_ratio` ← `completion_rate`★（★`Float` なのでそのまま。落とし穴5★）。
  ★`task_area` / `clean_area` は `clean_area / task_area * 100 ≒ clean_ratio` になる値にする★
  （★D-091 項2 の「面積から再計算する選択肢」を第4歩以降で試せるようにするため★）
- ★`clean_status`★: ★完了した清掃には `2`（清掃終了・セキュリティ有効）を基本とし、
  ★一部の行に `-1`（清掃完了・セキュリティ無効）を混ぜること★★
  ★★→ 落とし穴3（`-1` が最良）を段階2 で踏まないことの検証になります★★
- ★★`departed: false`（未実施）の行は `clean_status_histories` を作らない★★（6-1 の推奨方針）。
  ★この設計判断を**必ずコメントに残すこと**★
- ★`clean_status_histories.id` は AUTO_INCREMENT ではない（`prisma/schema.prisma:466`）★ →
  ★明示的に採番すること★

### 1-5. ★`robot_error_reports` — `status_1h` / `status_30m` の作り方★

- ★fixtures の `status_1h` / `status_30m` が「エラーを表す値」のとき、
  precheck = 1 / 2 の `clean_timetables` 行に紐づくエラー行を作る★
- ★「エラーを表す値」の判定★: ★着手前確認で決めた「エラーが無いときの値」＝ ★`Standby.`★ 以外★。
  ★ただし fixtures 側の既定は現在 `"待機中"` なので（`lib/fixtures/clean-logs.ts:55-56`）、
  ★シードの判定は「fixtures の既定値 `"待機中"` 以外ならエラー行を作る」でよい★★
- ★★段階1 の判断事項 — fixtures を `Standby.` に寄せるか★★（着手前確認の実装上の注意3）:
  ★寄せるのが一貫する（指示書K / D-088 で `error_type` を英語識別子に寄せたのと同じ）が、
  ★禁止事項12 が `lib/fixtures/` の改変を禁じている★し、当日分10行の均衡（D-049）や
  既存テストに影響しうる★。★★寄せると判断するならユーザーに確認してから★★。
  ★確認が取れなければ寄せない（現状維持）。その場合、段階3 の確認1 で `status_1h` に
  差が出るのは想定内として記録する★
- ★`error_type` には fixtures の値を**そのまま**入れる★（★`"Handrail Lifted"` / `"オフライン"` /
  `"充電中"` / `"緊急停止"` / `"セキュリティ有効"`★）。★変換しないこと★
- ★★`"Handrail Lifted"` を持つ行（`lib/fixtures/clean-logs.ts:112`）は必ず残すこと★★ —
  ★段階3 の B2 発火確認に使います★（D-082 / D-088。`lib/config/robot-status-match.ts:35`）
- ★NOT NULL 列★: `id` / `error_id`（★`@unique`★） / `serial_number` / `error_level` / `error_type` /
  `error_detail`。★`error_level` / `error_detail` はダミー値を入れる（実在しそうな文字列にしない）★
- ★`id` / `error_id` は `varchar(50)`★（落とし穴6）。★衝突しない規則で採番すること★

### 1-6. ★冪等にすること・検算★

★`deleteMany` を挿入の逆順で、`scripts/seed-local-db.ts:233-242` の既存ブロックの★先頭側★に足す★:

```
robot_error_reports → clean_status_histories → clean_statuses → clean_timetables
  → （既存）control_error_histories → control_reports → controls → clean_schedules → ...
```

★**2回続けて実行しても結果が同じであること**を実際に確かめること★。

★シード後にテーブル件数を数えること★（`clean_timetables` / `clean_statuses` /
`clean_status_histories` / `robot_error_reports` の `COUNT(*)`）。
★`mysql.exe` は PATH に無いのでフルパスで呼ぶ★。★接続情報は `.env.local` の `DATABASE_URL` を
使い、★この文書やコードに書かないこと★★。
★期待値は**自分で fixtures を数えて立てること**★（★参考: `clean_timetables` は
「シード対象行数 × 3」、`clean_status_histories` は「シード対象行数 − 未実施行数」★ —
★鵜呑みにせず実測すること★）。

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

- ★**2回続けて実行しても結果が同じであること**★
- ★**スキップされた行の件数（27行）と内訳が出力され、想定と違えば例外で止まること**★
- ★★**シードの設計判断がコメントに残っていること**★★ —
  ★`departed=false`（未実施）をどう表現したか★ / ★`status_1h` のエラーをどう作ったか★ /
  ★id の採番規則★ / ★`end_at` の決め方★
- ★**`prisma/schema.prisma` / `lib/fixtures/` に差分が無いこと**★（`git diff --stat` が空）
- `npx tsc --noEmit` エラー0 / `npm run lint` 0 / `npm test` 全パス
- ★**ここで1コミット**★

---

## 段階2 — `prismaSource.cleanLogs()` を実装する

### 2-1. 作るもの

★`lib/db/index.ts:155-157` の `notImplemented("cleanLogs")` を実装に置き換える★。
★`dsDevices()`（`:161-201`）と同じ形にすること★（遅延 import → クエリ → `map` で変換）。

### 2-2. ★手動 join が本体（落とし穴1）★

★`include` は使えません★。★次の手順で組み立てること★:

1. ★`clean_timetables` を `findMany`（`facility` / `section`（→ `robots` → `device`）/
   `clean_schedule` は**リレーションがあるので `include` してよい**）★
2. ★`clean_statuses` / `clean_status_histories` / `robot_error_reports` を**別クエリで全件取得**★
3. ★`clean_timetable_id` をキーに `Map` を作り、メモリ上で突き合わせる★
4. ★`precheck = 0` の行を1件の `CleanLogItem` の骨格とし、`parent_id` で precheck 1 / 2 の行を引く★

★★この方針（なぜ `include` が使えないのか）をコードコメントに残すこと★★ —
★D-094 で TeamWorks に FK 追加を再依頼中なので、FK が付いたら `include` に書き換えられます★。
★後任がその判断をできるように、根拠（`prisma/schema.prisma:312` / `:429` / `:472` に FK も KEY も無い）を書くこと★。

### 2-3. ★`status_1h` / `status_30m` の組み立て★

- ★precheck = 1 / 2 の行に紐づく `robot_error_reports.error_type` を**生値のまま**入れる★
- ★★無ければ `Standby.`★★（★末尾のピリオド込み。着手前確認の回答。D-098★）。
  ★`robots.status = 0` の TeamWorks コード表の原文であり、当方が作った値ではない★
  （`docs/handoff/TeamWorks回答_第2回_20260903.md:56`）。
  ★ただし「この行の `robots.status` を実際に読んで得た値ではない」ことはコメントに明記すること★
- ★★`Standby.` を直書きしないこと★★（着手前確認の実装上の注意1）— ★1箇所の定数にまとめる★。
  ★指示書O が `EMPTY_VALUE_LABEL`（`lib/format/raw-label.ts:19`）を作ったのと同じ形★。
  ★置き場所は `lib/format/` か `lib/config/` のどちらかにし、`lib/db/` に散らさないこと★
- ★★段階2 の判断事項 — 色分けをどうするか★★（着手前確認の実装上の注意2）:
  ★`lib/config/index.ts:147-153` の `ROBOT_STATUS_TONES` は日本語6文字列の完全一致で引くため、
  `Standby.` は一致せず★グレー（`c.txD`）にフォールバックする★（`lib/theme-status.ts:15-19`。
  ★例外もテスト失敗も出ない★）。★これは D-077 の想定挙動で、完了条件6 とも一致する★。
  ★「正常な行が全部グレー」で許容するか、`ROBOT_STATUS_TONES` に `Standby.` を足すかを
  ここで判断し、理由を記録すること★。★色を足すのは「値の変換」ではなく「表示の装飾」なので
  D-077 には触れない★。★足す場合も `lib/config/` の設定値に足すだけにとどめ、
  `lib/theme-status.ts` のロジックは変えないこと★（禁止事項10 の趣旨）
- ★同じ precheck 行に複数のエラーがある場合の規則を決めて記録すること★
  （★推奨: `errored_at` の最新1件★）

### 2-4. ★`grade` / `escalation_level` / `consecutive_problem_days`★

- ★`lib/judge/` を呼ぶ★（6-2）。★`prismaSource` の中で呼ぶことは D-090 項16 で確定済み★
- ★`consecutive_problem_days` は 6-3 の方針で算出★。★`countConsecutiveProblemDays()`
  （`lib/judge/escalation.ts:32`）に「スケジュール存在日のみ・日付昇順」で渡すこと★
- ★★「問題あり」の定義が fixtures（`lib/fixtures/clean-logs.ts:216-217`）と一致していること★★

### 2-5. ★変換ロジックは純粋関数に切り出してテストを書く★

- ★`lib/db/ds-status.ts` と同じ作法★。★DB 依存のテストは書かない（禁止事項13）★
- ★切り出す候補★: ★`model_name` の連結★ / ★`status_1h` の組み立て（エラー配列 → 文字列）★ /
  ★`consecutive_problem_days` の算出（日付昇順の配列 → 連続日数の配列）★
- ★期待値表を先に作ること★（`CLAUDE.md` §9.4）。★境界値を必ず含める★

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

- `npx tsc --noEmit` エラー0 / `npm run lint` 0 / `npm test` 全パス
- ★`lib/clean-logs/query.ts` / `lib/dashboard/` / `app/api/` / 画面に差分が無いこと★（禁止事項10）
- ★`prismaSource` の既存4メソッドに差分が無いこと★（禁止事項8）
- ★手動 join の理由・`status_1h` の既定値・連続日数の算出方針がコメントに残っていること★
- ★**ここで1コミット**★

---

## 段階3 — ★★動作確認（ここが本題）★★

### 3-1. 起動

- ★**`.env.local` の `USE_FIXTURES` を `false` にする**★
  （★`lib/db/use-fixtures.ts` は `process.env.USE_FIXTURES !== "false"` なので、
  ★**文字列 `false` 以外はすべて fixtures 扱い**★。`FALSE` や `0` では切り替わりません）
- ★**`npm run dev` を Bash で起動しないこと。Browser pane の `preview_start` を使う**★
  （`.claude/launch.json` に `d-watch`（`npm run dev` / port 3000）があります）
- ★**Browser pane が黒画面 / 0×0 になる既知の問題**★: ★`preview_start` に `url` を渡して開き直す★
- ★**指示書P では Browser pane が使えず `curl` で代替しました**★（D-097 §5）。
  ★同じ状況なら curl でよいが、★**その旨と「目視できなかった項目」を必ず記録すること**★★
- ★環境変数を変えたら dev サーバを再起動し、反映されていることを確認すること★

### 3-2. ★★確認1 — `USE_FIXTURES` の true / false でレスポンスを突き合わせる★★

★★これが「変換が正しい」ことの一番強い証拠です。飛ばさないこと★★（第1歩・第2歩と同じ方法）。

- ★`GET /api/clean-logs`（同一クエリ）を `USE_FIXTURES=true` と `false` の両方で取得し、
  **差分を突き合わせること**★
- ★**差は「調査済みの事実」5 の27行だけであること**★ — ★総件数 280件 → 253件★
- ★★差が27行以外に出たら変換のバグです★★。★1件ずつ理由を説明できること★
- ★`id` がずれていないこと★（★シードが fixtures の id をそのまま使っている前提★）

### 3-3. ★確認2 — `/detail-log`（F-02）と `/dashboard`（F-01）★

| # | 項目 |
|---|---|
| 1 | ★`/detail-log` が実DB経由で表示されること（HTTP 200）★。★`scheduled_at` が `+09:00` 付き（落とし穴4）/ `model_name` が `"PUDU CC1"` の形★ |
| 2 | ★★`status_1h` に**英語識別子**が出ること★★（例 `Handrail Lifted`）。★**日本語（「ハンドルアップ」等）が出たら変換してしまっています**★ |
| 3 | ★★`lib/theme-status.ts` の色分けがグレーになること★★ — ★生値は日本語6文字列に一致しないため。★**想定挙動であり、バグではない**★★（`lib/theme-status.ts:9-12`） |
| 4 | ★`grade`（◎○△×）が境界値どおり★ — ★90.1 → ◎ / 90.0 → ○ / 89.5 → ○ / 10.0 → △ / 9.9 → ×★（`CLAUDE.md` §9.4） |
| 5 | ★`escalation_level`（L1〜L3）が fixtures と一致★ — ★特にスケジュール非存在日を挟む施設4のケース（`lib/fixtures/clean-logs.ts:137-139`。★カレンダー5日だが連続3日でL3★）★ |
| 6 | ★★`/api/dashboard` が **200** になること★★（★第2歩では 500 だった★）。★`schedule_progress` が ★`planned: 9, completed: 9`★（fixtures は `9 / 10`）。★差の理由を説明できること★★ |
| 7 | ★セキュリティバナーは**出ないまま**★（★`security_off` は未実装。D-097 §2。★想定内★） |

### 3-4. ★★確認3 — B2 の発火★★

★★これが第3歩でしか確認できない項目です（D-090 項2）。飛ばさないこと★★。

- ★`GET /api/clean-logs/{id}/advice` を、★`"Handrail Lifted"` を持つ行★（fixtures では
  `lib/fixtures/clean-logs.ts:112` の施設2・1日前）に対して呼ぶ★
- ★★レスポンスに `B2` が含まれること★★（`lib/judge/advice.ts:86-88` /
  `lib/config/robot-status-match.ts:35`）
- ★`B3` は**発火しないこと**★ — ★`B3_OFFLINE_STATUSES` が空配列だから★
  （`:46`）。★これが現在の期待挙動★。★`R1`〜`R4` / `B1` も fixtures 版と一致すること★

### 3-5. ★確認4 — 他画面★

| 画面 | 期待 |
|---|---|
| ★`/detail-log` / `/dashboard`（本作業）／`/facilities` / `/schedules` / `/ds-logs` / `/ds-alerts`（指示書M / P）★ | ★**200。回帰していないこと**★ |
| `/alerts`（`prechecks`）/ `/reports`（`reportKpis` ほか） | ★500★（第4歩・第5歩） |

- ★エラーメッセージが `[db] xxx のPrisma実装はまだありません。…` であること★（`lib/db/index.ts:148-152`）
- ★★**404 が出たら Edge Runtime バンドル破壊の回帰を疑うこと**★★（D-086。`:2912-2930`）。
  ★500 = 想定どおりの未実装 / 404 = ビルドが壊れている★

### 3-6. ★ログの確認と `USE_FIXTURES=true` への復帰★

- ★`read_console_messages` でブラウザ側のエラー0件★（★使えなければ「確認できなかった」と記録★）
- ★`preview_logs` でサーバ側のエラーを確認★（★`A Node.js module is loaded` が出ていないこと★）
- ★`.env.local` を `true` に戻し、dev サーバを再起動する★
- ★**全画面が従来どおり動くこと**を確認する★（★特に `/detail-log` 280件 /
  `/dashboard` が `9 / 10`★）。★これを確認して初めて「壊していない」と言えます★

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

- ★3-2 の突き合わせを実施し、差が27行だけであることを実測した★
- ★3-4 の B2 発火を実測した★
- ★確認できなかった項目があれば、その理由と代替手段を控えた★
- ★`USE_FIXTURES=true` に戻して全画面が従来どおり動くことを確認した★

---

## 段階4 — 記録と push

### `docs/decisions.md` に追記

★**着手直前に `grep -n "^### D-0" docs/decisions.md | tail -3` で最終番号を実測すること**★
（作成時点の最終は `D-097`。★空き番号は自分で確かめる★）。

★記録すること★:

1. ★★`departed` を DB のどの値で判定したか、およびその根拠★★（6-1。★D-091 項1 の「実データで
   確認すべきこと」がどこまで詰まったか★）
2. ★`status_1h` / `status_30m` をどう組み立てたか★（precheck 行 → エラーの手動 join）と、
   ★★エラーが無いときに何を入れたか★★（着手前確認の回答と、D-077 の生値方針との整合の説明）
3. ★`consecutive_problem_days` の算出方法★（スケジュール存在日ベース。★全期間走査であること★）
4. ★`CleanLogItem.id` を何にしたか★（D-090 項3 の解決）/ ★`model_name` の連結規則★（同 項4）
5. ★★D-090 の未決（項1〜5）のうち何が解決したか★★ — ★項1（`status_1h`）/ 項2（`departed`）/
   項3（`id`）/ 項4（`model_name`）/ 項5（`completion_rate`。★D-094 の小数化で解消済み★）★
6. ★★シードできなかった27行の内訳と、`/dashboard` が `9 / 9` になったこと★★
   （★fixtures の `9 / 10` との差の理由★）。★B2 を実測できたか★（D-082 / D-088）
7. ★実データで確認すべきこととして残るもの★ — ★`departed` を表すカラム★ /
   ★`clean_statuses` に完了済みの行が残るか（D-093 項1）★ /
   ★`precheck` の 0/1/2 → 0/60/30 の切替時期（D-091 項4 / D-093 項2）★ /
   ★`error_type` の実値と `Handrail Lifted` の実在（D-090 項18）★ /
   ★手動 join の性能（年間13万行。D-092 項3-3）★
8. ★段階3 で確認できなかった項目があれば、その理由と限界★（★Browser pane が無い場合★）
9. ★第4歩に残したもの★（D-090 項2 の分割表を引くこと）と ★実測値★
   （`npm test` のファイル数・テスト数 / `tsc` / `lint`。★着手前の基準値との差★）

### `docs/handoff.md` に追記

★最新は 追記27（`docs/handoff.md:2187`）★。次は ★追記28★ の見込み。★着手直前に自分で確認すること★。

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

- ★`git add` は必ずファイルパスを明示。`git add -A` は禁止★（禁止事項16）
- ★`.env.local` をコミットしないこと★（禁止事項17）。★`USE_FIXTURES=true` に戻したことを確認してから★
- `git push origin develop`（★`main` へは push しない★）
- ★完了条件★: `docs/decisions.md` に決定番号を追記した（★上記9項目すべて記録★）/
  `docs/handoff.md` に追記した / ★`origin/develop` へ push 済み★ /
  ★文書末尾の「実行状態」表を更新した★

---

## ★やらないこと（第4歩以降）★

★本指示書の範囲外です。手を出さないこと★（D-090 項2 の分割表）:

| 対象 | いつ |
|---|---|
| ★`dsAlerts` の `security_off` 側★ | ★D-091 項3 の内部調整の結果待ち。★第3歩では扱わない★★ |
| `prechecks` / `reportKpis` / `facilityPerformance` / `errorBreakdown` / ★`PrecheckSeverity` の型変更（D-093 項2-2 の推奨A）★ | 第4歩 |
| `continuousProblems` | 第5歩 |
| ★点検記録2本（`inspections` / `inspectionDetail`）★ | ★最後★（D-072 項7。`docs/decisions.md:1052`） |
| ★`status_1h` への `formatRawLabel()` の適用（D-089 項7 の申し送り）★ | ★別の指示書★（★本作業は画面を触らない★） |

---

## 完了条件（全体）

1. ★**着手前確認1件（`status_1h` の「エラーが無いとき」の値）の回答を得てから段階1 に入った**★
2. 段階0〜4 をすべて実施した（★途中で止めた場合は、どこで止めたかを実行状態表に書くこと★）
3. `npx tsc --noEmit` エラー0 / `npm run lint` 0 / `npm test` 全パス
4. ★**`USE_FIXTURES=false` で `/detail-log` と `/dashboard` が 200 で表示されること**★
5. ★★**`USE_FIXTURES` の true / false で `/api/clean-logs` を突き合わせ、差が27行だけであることを
   実測したこと**★★
6. ★★**`status_1h` に英語識別子が出て、色分けがグレーになること（生値方針の帰結）**★★
7. ★★**`"Handrail Lifted"` を持つ行で B2 が発火することを実測したこと**★★
8. ★**`/dashboard` の `schedule_progress` が `9 / 9` になり、fixtures の `9 / 10` との差の理由を
   説明できること**★。★シードが冪等で、スキップ件数（27行）を assert していること★
9. ★**`USE_FIXTURES=true` に戻して全画面が従来どおり動くこと**★
10. ★**`prisma/schema.prisma` / `lib/fixtures/` / `lib/clean-logs/` / `lib/dashboard/` /
    API ルート / 画面 に差分が無いこと**★
11. ★**`lib/db/prisma.ts` / `local-guard.ts` / `use-fixtures.ts` / `ds-status.ts` /
    `prismaSource` の既存4メソッド に差分が無いこと**★
12. `docs/decisions.md` / `docs/handoff.md` の更新 / ★**`origin/develop` へ push 済み**★ /
    **文書末尾の「実行状態」表を更新した**

---

## 実行状態

| 段階 | 状態 | コミット |
|---|---|---|
| ★**着手前の確認**★ | ★**回答済み（2026-09-10）**★（`status_1h` の「エラーが無いとき」の値 = ★**B案 `Standby.`**★。D-098） | — |
| 段階0 前提の確認 | ★**完了**★ | コミットなし |
| 段階1 清掃ログ系4テーブルのシード | ★**完了**★ | `977c9c3` |
| 段階2 `prismaSource.cleanLogs()` | ★**完了**★ | `97eb295` |
| 段階3 動作確認 | ★**完了**★（Browser pane 無し。curl で代替） | コミットなし |
| 段階4 記録と push | ★**完了**★ | 本コミット |

**実行日**: ★2026-09-10★
**取得した決定番号**: ★**D-099**★（着手直前に `grep -n "^### D-0" docs/decisions.md | tail -3` で
最終が `D-098`（`docs/decisions.md:4342`）であることを実測して採番。
着手前確認の決定は **D-098**）
**着手前の実測値**: ★`npx tsc --noEmit` 0 / `npm run lint` 0 / `npm test` 33ファイル・631テスト★
**`status_1h` のエラー無し時の値**: ★**`Standby.`**★（★ピリオド込み。D-098★）
**シードできなかった行数**: ★**27行** / 280行中★（見込みどおり。内訳:
`8111R5628060166`×1（ロボット無し）/ `8111R5628060088`×1 + `8111R5628060155`×25（スケジュール無し））
**`/detail-log` の件数（fixtures / DB）**: ★**280件 → 253件**★（見込みどおり。DB 側にだけある行は0件）
**`/dashboard` の `schedule_progress`（fixtures / DB）**: ★**`9 / 10`（90%）→ `9 / 9`（100%）**★（見込みどおり）
**完了後の実測値**: ★`npx tsc --noEmit` 0 / `npm run lint` 0 /
`npm test` **34ファイル・658テスト** 全パス（+1ファイル / +27テスト）★
**★見込みと違った点★**: ★3-2 の「差は27行だけ」に対し、`consecutive_problem_days` /
`escalation_level` にも**4行**の差が出た★（fixtures の連続日数が手書きで、全行を通した
計算結果ではないため。★変換のバグではない★）。内訳は D-099 §6-1。
★うち1行は「完了率ちょうど 70.0 を問題ありとするか」という新しい論点★（D-099 §3 末尾）
**★段階2 の判断★**: ★`ROBOT_STATUS_TONES` に `Standby.` は足さなかった★（理由は D-099 §2-2）
**★段階1 の判断★**: ★fixtures は `Standby.` に寄せなかった★（禁止事項12 の解除確認が取れないため。
指示書の既定どおり）
**詳細**: `docs/decisions.md` ★D-099★ を参照
