# `prismaSource` 実装前チェックリスト

**作成: 2026-09-08**
**根拠: D-074 / D-077 / D-078 / D-079 / ★D-081★ / ★D-082★（`docs/decisions.md`）**
**起票のきっかけ: 他メンバーからの申し送り（D-079 項3「実装上の申し送り」）**

> ★**B2/B3 が日本語文字列の完全一致で判定している件、重要な指摘です。**★
> ★**実データ接続後に「エラーも出さずに発火しなくなる」は、テストでも気づけないタイプの事故です。
> `prismaSource` の必須確認項目として確実に残してください。**★

★**「確実に残す」ことが求められたため、他の文書に埋め込まず独立した文書にしました。**★
`prismaSource` に着手する担当者は、★**コードを1行書く前に本文書を通読すること**★。

---

## A. この文書の目的

★**`prismaSource`（実DB接続）に着手する前と着手中に、必ず確認することの一覧です。**★

★**ここに集めてあるのは「型エラーもテスト失敗も出ないまま、実データで静かに壊れる」箇所です。**★

- `tsc` は通ります。`npm test` も緑になります。`lint` も通ります。**それでも壊れます。**
- 理由は単純で、★**壊れる箇所がすべて「文字列の値そのもの」に依存している**★ためです。
  型は `string` のまま合っているので、コンパイラにも fixtures ベースのテストにも見えません。
- fixtures の値は ★**当方が書いた日本語**★ であり、DB から来る値ではありません
  （D-077 項3。`status_1h` / `status_30m` の生成元は `lib/fixtures/clean-logs.ts:53-54` の1箇所だけ）。
  ★**実データ由来の値は、このコードベースを一度も流れたことがありません。**★

したがって ★**「テストが緑だから大丈夫」は本件では成立しません。**★
★**実データの値の分布を数えることだけが検証手段です**★（→ §C）。

★**本文書は「優先度の高低」ではなく「気づけない箇所」を集めたものです**★（D-082）。
★**優先度が低いものも載っています。**★ 載っている＝今すぐ直せ、という意味ではありません。
★載せる基準は「放置しても例外もテスト失敗も出ないため、忘れると誰も気づけないこと」★ です。
★**したがって、優先度が下がった項目でも本文書からは消さないでください。**★

★**本文書は「保留質問」も持っています**★（→ §H）。
★第6回の確認書から取り下げた質問を、実データで確認してから聞き直すために預けてあります★（D-081）。
★**取り下げではなく延期です。数えた結果しだいで TeamWorks に送る必要があります。**★

---

## B. ★静かに壊れる箇所の一覧（最重要）★

★行番号は 2026-09-08 に実測して確認済み★。ファイルを編集したらここも直すこと。

| 箇所 | 何が起きるか | 確認方法 |
|---|---|---|
| ★`lib/judge/advice.ts:80-82`（B2）★<br>★**優先度: 中（D-082）**★ | ★**日本語文字列の完全一致で発火**★（`status1h === "ハンドルアップ" \|\| status30m === "ハンドルアップ"`）。生値方針（D-077）では DB から日本語は来ないため、★**例外もテスト失敗も出さずに発火しなくなる**★。`lib/judge/__tests__/advice.test.ts:132-146` が★日本語リテラルを直接渡している★ので ★**テストは緑のまま**★ | 実データの `error_type` に ★`Handrail Lifted`★ が実在するかを数える（→ §C-1）。D-078 項2 の暫定値で、★**24値の受領リストには含まれていない**★。★**見つからなかったら §H の保留質問1 を TeamWorks に送る**★（D-081 項4）。★**優先度は中（D-082）— アドバイスの主軸 R1〜R4 は影響を受けない**★（R1 は初回の出発失敗、R2 は単日の完了率×で発火。連続を見るのは R3/R4 だけ。`CLAUDE.md` §4.5）。★**ただし「静かに発火しなくなる」性質は変わらないので、この行を消さないこと**★ |
| ★`lib/judge/advice.ts:83-85`（B3）★<br>★**優先度: 中（D-082）**★ | 同上（`=== "オフライン"`）。★**B3 は値そのものが未定**★。`error_type` 24値にも `robots.status` 46値にも該当が無い（D-074 項6 / D-078 項2）。★**マッチ文字列は空＝発火しないままとする**★ | ★オフラインを表す値が DB に存在するかを数える★（→ §C-4）。無ければ「最終報告時刻からの導出」を別途設計する。★**それでも判断がつかなければ §H の保留質問2 を送る**★。★**優先度は中（D-082）— アドバイスの主軸 R1〜R4 は影響を受けない**★（連続を見るのは R3/R4 だけ。R1/R2 は単日で発火）。★**ただし「静かに発火しなくなる」性質は変わらないので、この行を消さないこと**★ |
| ★`lib/judge/__tests__/advice.test.ts:132-176`★ | B2/B3 のテストが★日本語リテラルを直接 `matchAdviceRules()` に渡す★ため、実装が実データで死んでも★永久に緑★。`:150-176` の「複数該当」も同じ | 期待値を★設定値経由★に書き換える（D-078 項2 の影響ファイル表）。★リテラルを直接渡すテストを残さないこと★ |
| ★`lib/theme-status.ts:5-11`★ | ★**日本語6文字列の完全一致で色を決め、それ以外は `c.txD`（グレー）にフォールバック**★（`:11` の `return c.txD`）。実データでは ★**全部グレーになる**★。`if` の連鎖なので例外も型エラーも出ない | 画面（F-02 清掃詳細ログ / F-03 アラート）を開いて★色が付いているか目視★。全行グレーなら発症している |
| ★`components/features/alerts/AlertsScreen.tsx:30-38`★ | ★**`lib/theme-status.ts` と同一ロジックの重複2実装**★（D-077 項3）。★片方だけ直すと不整合になる★ | 上と同じ。★**必ず2箇所そろえて直すこと**★ |
| ★`lib/types/index.ts:60-61`★ | `status_1h` / `status_30m` が ★**`string`**★。コメント（`:62`）に日本語6状態が並ぶだけで、★型としては何でも通る★。生値が入っても ★**型エラーは1件も出ない**★ | 上流（`prismaSource.cleanLogs()`）が入れる値を決めた時点で、★コメントを実際の値域に直す★ |
| ★`lib/reports/query.ts:41-49, 270-278`★ | 件数同値時のタイブレークを `ERROR_TYPE_ORDER.indexOf()` で決めている（`:276`）。生値では ★**全て `-1` を返し、`-1 - (-1) = 0`**★ となる。`Array.prototype.sort` は安定なので例外は出ず、★**「定義順で安定させる」が黙って「DB の返却順」に変わる**★ | ★件数が同じ `error_type` が2つ以上あるデータ★で `/api/reports/error-breakdown` を叩き、★リロードして並びが変わらないか★を確認する。変わるなら安定化キーを生値向けに決め直す（D-077 の影響箇所表） |
| ★`components/features/reports/ReportsScreen.tsx:56-64, 440`★ | `ERROR_TONE: Record<ErrorType, ...>`（`:56`）を `ERROR_TONE[item.type]`（`:440`）で引く。生値キーでは ★**`undefined` が `ProgressBar` の `tone` に渡る**★。`ErrorType` を `string` に緩めた時点で ★**型エラーも消える**★ | エラー内訳カードの★バーに色が付いているか★を目視。`undefined` 由来の無色バーが並ぶ |
| ★`prisma/schema.prisma:113`（`facilities.contract`）★ ★**2026-09-08 に仕様が確定（D-080）。指示書M・D-086で`lib/format/contract.ts`として実装済み**★ | ★**`varchar(10)` の文字列。桁位置がサービスに対応する**★（★1桁目 = D-Security / 2桁目 = D-Watch、契約済みなら `1`★。`0000000000` = 未設定 / `1100000000` = 両方）。★**数値ではない**★ — ★`CLAUDE.md` §4.6 と D-010 の「0/1/2/3」という数値表現が誤りだった★（D-080 で訂正済み）。★**数値として比較すると全施設が契約なしになり、メニューが全部消える**★。`Number("0000000000")` は ★**例外を出さずに `0`**★ になり、`contract === 3` は絶対に真にならない。★**例外もテスト失敗も出ない**★（fixtures は `monitoring_enabled` / `d_security_enabled` の boolean を直接持つのでテストは緑のまま）。`Facility.monitoring_enabled` / `d_security_enabled`（`lib/types/index.ts:334-336`）への★変換規則はまだ未実装★ | ★`SELECT contract, COUNT(*) FROM facilities GROUP BY contract` で実際の値を数える★（→ §C-8）。★**10桁の文字列が返ることを確認する**★。変換関数は ★`lib/format/` か `lib/config/` の純粋関数1箇所に閉じ込め、桁位置をハードコードしない★（D-080）。★`customers.contract` は参照禁止★ |
| ★`lib/db/index.ts:48`★ | `process.env.USE_FIXTURES !== "false"` — ★**文字列 `"false"` に完全一致しない限り fixtures のまま**★。`USE_FIXTURES=0` / `False` / `FALSE` / 末尾空白では ★**実DB に切り替わらず、しかも何のエラーも出ない**★ | 切替後に ★API のレスポンスが fixtures の店舗名（架空）でないこと★ を確認する。または一時的に `prismaSource` 側へログを1行入れて到達を確認する |
| ★`lib/reports/query.ts:332-347`（`selectKpiRow`）★ | ★**当期（進行中の週・月）はサマリテーブルに行が無いのが正常**★（`CLAUDE.md` §4.7 / D-023）。現状は当期でも★確定期間でも同じく `null` を返し、ルート側が空KPIを組み立てる★。実DB接続後、当期を開くと ★**エラーも出さずに全項目が空のレポートが出る**★ | ★当週・当月を開いて KPI が空になっていないか★を確認する。`:339-346` が「詳細テーブルからその場集計」に差し替える分岐点。★器だけ置いてある状態★ |
| `lib/format/period.ts:126`（`weekOfMonth()`） | 月跨ぎ週の帰属ルールを知っている★唯一の箇所★（D-071）。DB のサマリが月境界で分割されていなければ、★週番号がズレた行を無言で拾う★（該当なし＝空表示になり、エラーにはならない） | ★月跨ぎ週が2行に分かれて格納されているか★（→ §C-7）と、★`week=6` の行が実在するか★（→ §C-5）を数える |
| `prisma/schema.prisma:504-509`（`clean_schedules.start_time` / `end_time`）<br>★**指示書M・D-086で`lib/format/schedule.ts`の`formatEndTime()`として実装済み**★ | ★**HHMM 文字列で、時が 24〜29 を取りうる**★（`2830` は翌日4:30）。`HH:MM` として素朴にパースすると ★**壊れるが例外は出ず、日付だけ1日ずれる**★（D-060） | 実データに★時が 24 以上の行があるか★を数える（`SELECT DISTINCT LEFT(end_time,2) FROM clean_schedules`） |
| ★`prisma/schema.prisma`（`CleanSchedule.inactive`）★<br>★**指示書L・D-084で追加。指示書M・D-086で`active: row.inactive === 0`として実装済み**★ | ★**論理が反転している**★。`Int @default(0)`で ★**0 = 有効**★。API/TSは`active: boolean`のため、名前の類似から`active: Boolean(row.inactive)`と書きたくなるが ★**それでは全件反転する**★（有効なスケジュールが無効として並ぶ）。正しくは`active: row.inactive === 0`。★**`Boolean(0)`が`false`になる分、書いた瞬間にテストが落ちる可能性は高いが、`Boolean(row.inactive)`ではなく`!!row.inactive`のような書き方だと同じ罠に気づきにくい**★ | 実データで`inactive`の値の分布を数える（`SELECT inactive, COUNT(*) FROM clean_schedules GROUP BY inactive`）。実装後、fixturesと同じ有効/無効件数比になるか確認する |
| ★DB の`weeks`カラム（`clean_schedules`）★<br>★**指示書L・D-084で追加。指示書M・D-086で解決済み（`row.weeks`→`weekdays`に明示的にマッピング）**★ | ★**カラム名がAPI/TSの`weekdays`と違う**★（DBは`weeks`、`VarChar(7)`）。単純なspreadでは移せない。★フィールド名の違いに気づかず`row.weekdays`のように書くと`undefined`になり、型エラーも出ない可能性がある（Prismaのモデル定義で気づくはずだが念のため）★ | `prismaSource.schedules()`実装時、DBの列名（`weeks`）とAPI/TSのプロパティ名（`weekdays`）を対応表で確認しながらマッピングする |
| ★`Schedule.weekdays_label`★<br>★**指示書L・D-084で追加。指示書M・D-086で`lib/format/schedule.ts`の`weekdaysLabel()`として実装済み**★ | ★**DBに存在しない。表示用ラベル（「毎日」「月水金」「平日のみ」「土日のみ」「日火木土」）はサーバ側で`weeks`から生成する必要がある**★。DBから素直にselectしただけでは埋まらず、`undefined`が画面のスケジュール管理（F-09）の「曜日」列にそのまま出る（型エラーは`weekdays_label: string`が必須なら出るはずだが、生成漏れに気づきにくい） | `lib/schedules/query.ts`のdocstringに同じ4点の記載あり（指示書L）。実装時はラベル生成関数を1箇所（`lib/format/`推奨）に閉じ込め、fixturesの5パターン（毎日/月水金/平日のみ/土日のみ/日火木土）と同じ出力になることをテストで固定する |

| ★`lib/db/index.ts`（`prismaSource`から生成済みPrismaクライアントへの新しい参照）★<br>★★**指示書M・D-086で発見。残り10メソッド実装時にも当てはまる**★★ | `lib/logger/index.ts`が`USE_FIXTURES`判定のために`@/lib/db`をimportしており、`lib/logger`は`instrumentation.ts`（`register()`/`onRequestError`）経由で**Edge Runtimeでもコンパイルされる**。`prismaSource`のメソッドが生成済みPrismaクライアントを動的import（`await import("./prisma")`）すると、実行されるかどうかに関わらずTurbopackがそのimport先を静的にバンドル対象として解析し、Prismaクライアントが依存する`node:path`等のNode組み込みモジュールがEdgeバンドルに引きずり込まれる。★**例外もテスト失敗も出ない。`USE_FIXTURES=false`でdev serverを起動しても`npm test`は全部緑のまま**★。実際に発生した症状は`/api/reports/*`が軒並み404（他のAPIは正常）という★**一見無関係な画面が壊れる**★形だった | `USE_FIXTURES=false`にしてdev serverを再起動し、★実装した画面だけでなく他の全画面・全APIも一通り叩く★こと。ビルドログに`A Node.js module is loaded (...) which is not supported in the Edge Runtime`の警告や`Ecmascript file had an error`が出ていないか確認する。直す場合は、Prismaクライアントに依存するモジュール（`lib/db/index.ts`本体や`lib/db/prisma.ts`）を`lib/logger`等の**Edge到達経路から** importしないよう分離する（実例: `lib/db/use-fixtures.ts`） |

★**上の表の「静かに壊れる」共通構造**★: いずれも ★**「文字列が一致しなかったときのフォールバックが正常系に見える」**★
（グレーになる / 発火しない / 空になる / 順序が変わる）。★**例外を投げる箇所が1つもありません。**★
★**ただし直近の発見（Edge Runtimeバンドル破壊）は毛色が違い、「文字列不一致」ではなく
「ビルドの依存グラフに引きずり込まれる」という構造。実行時の値ではなくimport文の到達可能性の
問題なので、fixtures/純粋関数のテストでは原理的に検出できない**★。

---

## C. ★接続直後に数えるべきクエリ★

★**接続できたら、実装の前にまずこれを流して分布を記録すること**★（D-078 項1「接続後にすぐ確認すべきこと」）。
★参照用アカウントは SELECT のみなので、これらは全て実行できます★。DB名は `clean_watch`。

★**結果は本文書に追記して残すこと**★（次の担当者が同じ調査を繰り返さないため）。

### C-1. `robot_error_reports.error_type` の値と件数分布

```sql
SELECT error_type, COUNT(*) AS cnt
FROM robot_error_reports
GROUP BY error_type
ORDER BY cnt DESC;
```

- ★**受領した24値（D-074 項1 / `docs/handoff/analysis/error-type-mapping.md`）と実態が合っているか**★
- ★**`Handrail Lifted` が実在するか**★（★空白入り★。他24値はすべて PascalCase なので
  ★**値の形が1種類ではない可能性がある**★。D-078 項2）

```sql
-- Handrail Lifted の実在確認。前後空白・大小文字ゆれも拾う
SELECT error_type, COUNT(*) AS cnt
FROM robot_error_reports
WHERE REPLACE(LOWER(TRIM(error_type)), ' ', '') LIKE '%handrail%'
GROUP BY error_type;
```

- ★24値に無い値が来ていないか★（生値方針なのでそのまま表示される。★寄せ先は作らない★）

```sql
-- 表記ゆれの検出（前後空白・空文字）
SELECT COUNT(*) FROM robot_error_reports WHERE error_type <> TRIM(error_type);
SELECT COUNT(*) FROM robot_error_reports WHERE TRIM(error_type) = '';
```

### C-2. `robot_error_reports.error_level`

```sql
SELECT error_level, COUNT(*) AS cnt
FROM robot_error_reports
GROUP BY error_level;
```

- ★**`Warning` / `Event` が本当に格納されていないか**★（D-074 項2。先方の回答は
  「ファイルに記録するが DB に格納しない予定」「後から判断するかもしれない」）
- ★**コードは4値すべてを受け付ける形にすること**★。画面の重要度表示は `Fatal` / `Error` の2段階前提
- サマリ側も同じ値域か（D-074 の Y-3 で承認済みの前提）:

```sql
SELECT error_level, COUNT(*) FROM facility_error_summaries GROUP BY error_level;
SELECT error_type,  COUNT(*) FROM facility_error_summaries GROUP BY error_type;
```

### C-3. `robots.status` の値

```sql
SELECT status, COUNT(*) AS cnt
FROM robots
GROUP BY status
ORDER BY status;
```

- ★**46値のコード表（`docs/handoff/TeamWorks回答_第2回_20260903.md:53-98`。★`103` が欠番★で
  `102` の次が `104`）のうち、実際に何が使われているか**★
  （★起票時に「47件」と数えたのは誤りで実測46件★。D-077 項2 で訂正済み）
- ★**`-200` が実在するか**★（`Lost Loaction.` / LostLocation。★変換はしない★ — D-077 項2）

```sql
SELECT COUNT(*) FROM robots WHERE status = -200;
```

- 併せて動作フェーズ側も見る:

```sql
SELECT status, clean_status, COUNT(*) FROM section_robot_statuses GROUP BY status, clean_status;
```

### C-4. ★オフラインを表す値が存在するか★

★**これが本節で最も重要な問いです**★（B3 の発火条件がここで決まる）。

- `robots.status` の46値は ★**移動・充電・清掃・帰還の動作フェーズしか表現しておらず、
  ハンドルアップも通信断も無い**★（第2回回答が自ら明記。`:107-108`）
- `error_type` 24値にも該当なし（D-074 項4）

```sql
-- 「報告が来ない」状態なので、行が立つのではなく「最終報告からの経過」で見るしかない可能性が高い
SELECT r.id, r.serial_number, MAX(cr.created_at) AS last_report
FROM robots r
LEFT JOIN control_reports cr ON cr.facility_id = r.facility_id
GROUP BY r.id, r.serial_number
ORDER BY last_report;
```

- ★**存在しなければ「最終報告時刻からの経過時間で導出」を設計する**★（D-074 項6 / D-077 項3）。
  ★**しきい値は `lib/config/` の設定値にする**★（ハードコード禁止）
- ★**値が判明するまで B3 のマッチ文字列は空＝発火しないままとする**★（D-078 項2）

### C-5. 週次サマリに `week = 6` の行があるか（D-071 / 第5回 X-1）

```sql
SELECT year, month, week, COUNT(*) AS cnt
FROM facility_summaries
WHERE week > 0
GROUP BY year, month, week
ORDER BY year, month, week;

SELECT COUNT(*) FROM facility_summaries WHERE week = 6;
SELECT MAX(week) FROM facility_summaries;
```

- 第5回 X-1 は ★**「A案。`week` の上限は 6 として実装する」で承認済み**★（D-074 項3）。
  ★指示書H（D-073）の実装はやり直し不要★。★実データで裏を取るのがここ★
- `lib/reports/query.ts:63-64` の `WEEK_MIN = 1` / `WEEK_MAX = 6` と一致するか

### C-6. `week=0` / `month=0 AND week=0` の粒度（第5回 X-2）

```sql
-- 月次のはずの行
SELECT year, month, COUNT(*) FROM facility_summaries WHERE week = 0 GROUP BY year, month;
-- 年次のはずの行
SELECT year, COUNT(*)         FROM facility_summaries WHERE month = 0 AND week = 0 GROUP BY year;
```

- ★**`week=0` が月次、`month=0 AND week=0` が年次**★という理解でよいか（D-074 項3 で承認済み）
- ★**`week=0` は予約値。API の `week` は 1 以上のみ受け付ける**★（`lib/reports/query.ts:63` の
  `WEEK_MIN` を下げないこと）。内部表現としては ★monthly のとき `week = 0`★（D-072）
- 年次は P1 では読まない

### C-7. 月跨ぎ週が2行に分かれているか（第5回 X-3）

```sql
-- 2026-09-06(日) 6:00 のバッチが 2026/8月第6週 と 2026/9月第1週 の2行を作っているか
SELECT year, month, week, facility_id, task_count, clean_area, task_area
FROM facility_summaries
WHERE year = 2026 AND ((month = 8 AND week = 6) OR (month = 9 AND week = 1))
ORDER BY month, week, facility_id;
```

- 1行しか無ければ ★**月境界で分割していない**★ ことになり、`lib/format/period.ts:126` の
  `weekOfMonth()`（D-071 方式）と帰属がズレる。★**その場合は実装ではなく照会に戻ること**★

### C-8. `facilities.contract` の値の分布

```sql
SELECT contract, COUNT(*) AS cnt
FROM facilities
GROUP BY contract
ORDER BY cnt DESC;

SELECT LENGTH(contract), COUNT(*) FROM facilities GROUP BY LENGTH(contract);
```

- ★**実DDL は `VarChar(10)` で既定 `"0000000000"`**★（`prisma/schema.prisma:113`）
- ★**桁ごとの意味は 2026-09-08 に確定した（D-080）。TeamWorks への照会は不要**★
  （出典: `docs/handoff/analysis/table-definitions-01-master.md:122`）。
  ★1桁目 = D-Security / 2桁目 = D-Watch、契約済み（有効）なら `1`★。
  `0000000000` = 未設定 / `1000000000` = D-Securityのみ / `0100000000` = D-Watchのみ /
  `1100000000` = 両方。3桁目以降は予備（★定義書に明記なし。P1では読まない★）
- ★**このクエリで確認するのは「10桁の文字列が返ること」と実際の値の分布**★。
  ★数値として比較すると全施設が契約なしになり、メニューが全部消える。例外もテスト失敗も出ない★
- ★`CLAUDE.md` §4.6 / D-010 にあった「0/1/2/3」という数値表現は誤り★。D-080 で訂正済み
- ★**`customers.contract` は参照禁止**★（削除依頼済み。D-010 / `CLAUDE.md` §4.6 / §7）

### C-9. 併せて確認する（ついでに片付くもの）

```sql
-- 最新DDLの反映状況（A-1 / A-3 / B-3b。D-066 で未反映と判明していた3件）
--   → prisma db pull の diff を見れば分かる（§D-3）
-- clean_schedules の end_time が 24 時以上を取るか（D-060）
SELECT DISTINCT LEFT(end_time, 2) AS hh FROM clean_schedules ORDER BY hh;
-- 書き込み許可4カラムの現状（更新用アカウント到着後の検証用に、先に分布を取っておく）
SELECT status, COUNT(*) FROM robot_error_reports        GROUP BY status;
SELECT status, COUNT(*) FROM control_error_histories    GROUP BY status;
```

---

## H. ★実データを見た結果によって、TeamWorks に聞くべきこと（保留質問）★

★**位置は §C の直後ですが、既存の節番号を変えないため記号は `H` としました**★（A〜G はそのまま）。

★**2026-09-08 / D-082 により、本節の優先度は「中」に下がりました**★（★B2/B3 は当面「生値をそのまま表示するだけ」とし、実データで値が確定してから直す。アドバイスの主軸 R1〜R4 は影響を受けない★）。★**ただし保留質問そのものは維持します — 忘れると誰も気づけないためです。**★

★**ここにあるのは、第6回の確認書（`docs/handoff/TeamWorks確認事項_第6回_20260908.md`）から
取り下げた質問です**★（D-081）。
★**取り下げたのではなく延期しました。**★
参照用アカウントで実データを数えれば当方で確認できるため第6回から外しましたが、
★**数えた結果しだいでは、あらためて TeamWorks に送る必要があります**★。

| # | 実データで確認すること | 見つからなかったら聞くこと |
|---|---|---|
| ★1★ | `robot_error_reports.error_type` に ★`Handrail Lifted`★（または類似の値）があるか（→ §C-1） | ★**ハンドルアップは `error_type` のどの値で来るか。`error_type` に来ないなら、どのテーブル・カラムから判定するか**★ |
| 2 | ★オフラインを表す値があるか★（→ §C-4） | ★**オフラインはどこから判定するか。最終報告時刻からの導出でよいか（よい場合、しきい値の目安は何分か）**★ |

### H-1. ★「データに出てこないこと」は「存在しないこと」の証明にならない★

★**これが本節の存在理由です。**★

- 1か月分を数えて該当値が0件でも、★**単にその期間に発生しなかっただけの可能性があります**★
- したがって ★**「無かったから存在しない」と結論して実装を確定させないこと**★。
  ★分布を数えた結果は「聞くべき理由が具体化した」という意味しか持ちません★

### H-2. ★聞くときは件数と期間を添える★

- ★**「N日分・M件を見たが該当値が0件だった」という形で送ること**★。
  ★先方が「その期間には発生していないだけ」なのか「そもそも来ない」のかを即答できます★
- 併せて ★実際に出現した値の一覧★（§C-1 の `GROUP BY` の結果）を添えると、
  ★先方が「これがそれです」と指せます★
- 質問を減らすほど回答は早く返ります（D-079 項3）。★**数えてから聞くのは、質問を減らすためではなく
  1回で終わらせるためです**★

### H-3. ★これを忘れると何が起きるか★

- ★**B2（ハンドルアップ）が静かに死にます**★（§B の1行目）。
  ★D-039 により B2 は P1 スコープ内★であり、Phase B の先送りではありません
- B2/B3 のマッチ文字列は ★`lib/config/` の設定値（既定は空＝発火しない）★ です（D-078 項2）。
  ★**空のままでも例外もテスト失敗も出ません。**★
  ★誰も気づかないまま「アドバイスが出ないのが正常」になります★

### H-4. 出典

| 出典 | 内容 |
|---|---|
| ★**D-081**★ | ★本節の根拠★。第6回から第4部（エラー情報についての確認）を削除し質問を2件に絞った決定。★項4 が保留質問の表★ |
| ★**D-082**★ | ★B2/B3 は暫定値のまま進め、実データ確定後に修正する★（優先度を中に下げる決定）。★項2 が R1〜R4 の整理★／★項3 が「保留質問と §B の一覧を残し続けること」★／★項5 が修正のタイミング★ |
| D-078 項2 | B2 の暫定マッチ値 `Handrail Lifted`（★暫定。変更のおそれあり★）。B3 は値未定でマッチ文字列は空のまま |
| D-077 項1 / 項3 | エラー種別は変換せず生値で表示する / B2・B3 が実データで発火しなくなる |
| D-079 項3 | 「質問が少ないほど回答は早く返ります」 |

---

## D. ★手順★

1. ★**接続先（ホスト名・ポート）を受領する**★
   ★**2026-09-08 時点で未受領。これが無いと始まらない**★（D-078 項1 の未解決2点のうち1件目）。
   `.env.local` には `<HOST>` のプレースホルダを入れて待機させてある。
   ★**第6回の確認書の最優先事項**★（`docs/handoff/TeamWorks確認事項_第6回_20260908.md`）。
   ★参照用アカウント自体は受領済み★（`GRANT SELECT ON clean_watch.*`。DB名 `clean_watch`。D-078 項1）

2. `.env.local` の `DATABASE_URL` を有効化する
   ★**パスワードに `%` と `@` が含まれるため URL エンコードが必須**★（`%` → `%25`、`@` → `%40`）。
   ★**生のままだと Prisma が `@` 以降をホスト名と解釈して必ず失敗する**★。
   ★**具体的な値は `.env.local` のコメントに書いてある。本文書にも他の git 管理下の文書にも書かない**★
   （`CLAUDE.md` §9.3「認証情報を絶対にコミットしない」。`.env.local` は `.gitignore:34` の `.env*` で追跡外）。
   - ★**Prisma の設定が読むのは `.env` のみ。`.env.local` は明示的に読み込む必要がある**★（既知の地雷）
   - ★**Prisma 7 は `datasource` に `url` を書けない**★（`P1012`）。接続先は `prisma7.config.ts` が正
   - ★**セキュリティ上の申し送り**★: この認証情報は★平文でチャットに貼られており会話ログに残っている★。
     ★**本番接続に使う前にローテーション（パスワード再発行）を TeamWorks に依頼することを推奨**★（D-078 項1）

3. `npx prisma db pull`
   ★**スキーマの取り込みのみ。マイグレーション作成・スキーマ変更は禁止**★（`CLAUDE.md` §7 / D-056）。
   - ★**`db pull` はコメントを保持しない**★。現行 `prisma/schema.prisma` には
     TeamWorks 回答由来の注記が多数入っている（例: `:505-508` の HHMM / 24〜29 時の注意）。
     ★**再 pull したらコミットの diff を見ながらコメントを移植し直すこと**★（同ファイル冒頭の注記）
   - 現行の `schema.prisma` は ★**2026-09-03 にローカル DB `dwatch_ddl`（元DDL `clean_watch_20260902.sql`）
     に対して pull したもの**★。★**実DB への pull は初回**★
   - 差分を見れば ★**A-1 / A-3 / B-3b の反映状況が DDL の再受領を待たずに分かる**★（D-066 / D-078 項1）
   - ★以後は実DDL（pull の生成結果）を正とし、手で書き換えない★（D-059）

4. `npx prisma generate`
   ★**`lib/generated/prisma` は未生成（ディレクトリ自体が存在しない）**★（実測。`prisma/schema.prisma:3` の
   `output` 先）。★**接続作業は実質ここから始まる**★
   - ★**Prisma 7 は driver adapter が必須**★（`datasource.url`だけでは接続できない）。MySQL/MariaDB用は
     `@prisma/adapter-mariadb`（`@prisma/client`とバージョンを揃える。指示書M・D-086で追加済み）
   - ★**生成されたPrismaクライアントは拡張子なし相対import（`export * from './enums'`等）を使う**★。
     このプロジェクトの`tsconfig.json`の`moduleResolution: "bundler"`前提のコードで、素の`node`の
     ESMローダー（拡張子必須）では解決できない（実測。`node --experimental-transform-types`でも
     同じ`ERR_MODULE_NOT_FOUND`）。単発スクリプトから直接importする場合は`tsx`を使うこと
     （`npm run seed:local`が実例。指示書M・D-086）
   - ★★**ローカル検証用DB（`dwatch_local`）を使う場合、`dwatch_local`は現行`prisma/schema.prisma`と
     一致していない**★★（指示書M・D-086で発見）: `robots.task_id`/`version`、
     `clean_schedules.started_at`/`ended_at`が`dwatch_local`側にだけ存在する（余分な列はNULL許容/
     既定値付きなのでINSERTには影響しない）。★**`dwatch_local`に対して`db pull`/`db push`/`migrate`を
     実行しないこと**★（前者は実DDLの姿から乖離する。後者は余分な列をDROPしようとする）

5. ★§C のクエリを流して分布を記録する★
   ★**結果を本文書に追記する**★。ここで §B の各項目が「発症しているか」が確定する。
   ★**実装より先にこれをやること**★（分布を知らずに書くと、静かに壊れる実装をそのまま作り込む）

6. `lib/db/index.ts` の `notImplemented()` を1つずつ実装する
   ★**まとめて書かない**★。1メソッドずつ、§F の表の順（依存の少ない順）で進める。
   ★フィルタ・ソート・集計はこの層に持ち込まない★（`lib/db/index.ts:51-54` の方針。
   ★引数を取らせない契約★ — `:65-73`）。★判定ロジックは切替の外側★（D-044）

7. ★**各メソッドについて、fixtures 版と同じ入力で同じ形の出力が返るかを突き合わせる**★
   - `USE_FIXTURES=true` / `false` の両モードで同じ API を叩き、★**レスポンスの形（キー・型・
     `meta` の有無）が一致すること**★を確認する。★値そのものは一致しない★（データが違う）ので、
     ★形と件数の整合★を見る
   - ★**一覧APIは必ず `meta: { total, page, per_page }` を返す**★（`CLAUDE.md` §9.3）
   - ★**表示用に丸めた値で判定していないこと**★を、境界値の行（完了率 90.0 / 90.1 / 70.0 / 70.1 /
     10.0 / 9.9 / 0 / 未実施）で確認する（`CLAUDE.md` §4.3 / §9.4）
   - ★**当期（当週・当月）はサマリに行が無い**★。§B の `selectKpiRow` の項を参照

---

## E. ★注意事項★

- ★**参照用アカウントは SELECT のみ**★（`GRANT SELECT ON clean_watch.*` — 全テーブルの参照）。
  ★**更新用は後日付与予定**★（ユーザー原文「※後で必要なテーブルに更新権限を付与する」）。
  ★**したがって現時点では書き込み系の実装は検証できない**★（D-Sec アラートの対応状況更新、
  エラー報告の備考など）。D-078 項1
- ★**書き込みは `robot_error_reports` / `control_error_histories` の
  `status`, `comment`, `responded_at`, `resolved_at` の4カラムのみ**★（`CLAUDE.md` §3 / §7 / D-042）。
  ★**この4カラム以外への書き込みコードを書かない**★（権限エラーになる）
- ★**メインDBへのマイグレーション作成・スキーマ変更は禁止**★（TeamWorks の領域。
  `prisma db pull` で取り込むだけ。`CLAUDE.md` §7 / D-056）
- ★**`customers.contract` の参照禁止**★。★`facilities.contract` を使う★（D-010 / `CLAUDE.md` §4.6）
- ★**表示用に丸めた値で判定しない**★（`CLAUDE.md` §4.3。判定は★丸める前の生値★で行う）
- ★**閾値のハードコード禁止**★（90/70/10、猶予30分、L2=2日/L3=3日、オフライン判定のしきい値、
  B2/B3 のマッチ文字列。すべて `lib/config/` から読む。`CLAUDE.md` §7 / D-078 項2）
- ★**エラー種別は変換しない**★（DB の生値をそのまま表示する。D-077 項1。
  `robots.status = -200` → LostLocation の変換も行わない。D-077 項2）
- ★**認証情報を git 管理下の文書に書かない**★。`.env.local` にのみ置く（`CLAUDE.md` §9.3）
- ★**`lib/fixtures/` は DB 接続後も削除しない**★（テスト・デモ用。`CLAUDE.md` §9.5）。
  ★fixtures の不変条件★: 「有効スケジュールの曜日該当件数＝当日の清掃ログ行数」
  （`lib/fixtures/__tests__/schedules.test.ts`）。★スケジュールを1件足すとダッシュボードの
  既定表示が異常系になる★
- ★**cwd のドライブレターが小文字だと Vitest が全滅する**★（既知の地雷。大文字にすること）

---

## F. 未実装メソッドの一覧

★`lib/db/index.ts:140-177` の `prismaSource` を実測★（2026-09-08）。
★**`notImplemented()` は 12メソッド。`DataSource` インターフェース（`:55-82`）の全メソッドが未実装**★
（fixtures 版 `:86-123` のみが動いている）。

「使われる画面」は ★API ルートから追跡した実測★（`grep -rn "db\.<method>" app/ lib/`）。

| # | メソッド | `lib/db/index.ts` | 呼び出している API ルート | 使われる画面 |
|---|---|---|---|---|
| 1 | `cleanLogs()` | `:141-143` | `app/api/clean-logs/route.ts:39` / `app/api/clean-logs/export/route.ts:68` / `app/api/clean-logs/[id]/advice/route.ts:41` / `app/api/clean-logs/[id]/areas/route.ts:36` / `app/api/dashboard/route.ts:57` | ★**F-01 ダッシュボード / F-02 清掃詳細ログ**★（CSVエクスポート・アドバイス・清掃エリア内訳を含む）。★最も影響が広い★ |
| 2 | `prechecks()` | `:144-146` | `app/api/prechecks/route.ts:44` | **F-03 アラート**（`components/features/alerts/AlertsScreen.tsx:73`） |
| 3 | `dsDevices()` | `:147-149` | `app/api/ds-devices/route.ts:42` / `app/api/ds-devices/[serial_number]/note/route.ts:80` | **F-06 D-Secログ**（備考入力を含む。`DsLogScreen.tsx:169, 217`） |
| 4 | `dsAlerts()` | `:150-152` | `app/api/ds-alerts/route.ts:43` / `app/api/dashboard/route.ts:59` | **F-07 D-Secアラート / F-01 ダッシュボード** |
| 5 | ★`facilities()`★<br>★★**実装済み（指示書M・D-086）**★★ | 実装済み | `app/api/facilities/route.ts` | **F-08 施設ロボットマスタ**。指示書Mで`USE_FIXTURES=false`での動作を確認済み |
| 6 | `schedules()`<br>★★**実装済み（指示書M・D-086）**★★ | 実装済み | `app/api/dashboard/route.ts:58` / `app/api/schedules/route.ts` | **F-01 ダッシュボード / F-09 スケジュール管理**。指示書Mで`USE_FIXTURES=false`での動作を確認済み。§Bの`inactive`反転・`weeks`カラム名・`weekdays_label`生成・`end_time`24〜29時の4項目は解決済み（`lib/format/schedule.ts`） |
| 7 | ★`inspections()`★ | `:159-161` | ★**なし**★ | ★**`inspections` 画面は `PlaceholderScreen`**★。★点検記録は独自ストア（SQLite + オブジェクトストレージ）でメインDBとは完全分離★（`CLAUDE.md` §3 / §7）。★**そもそも `prismaSource` で実装すべきものか要確認**★。D-031 のスコープは変えない（D-079 項3） |
| 8 | ★`inspectionDetail(id)`★ | `:162-164` | ★**なし**★ | 同上。★引数を取る唯一のメソッド★ |
| 9 | `reportKpis()` | `:165-167` | `app/api/reports/kpi/route.ts:55` | **F-04 レポート**（`ReportsScreen.tsx:141`）。★当期はサマリに行が無い★（§B / `selectKpiRow`） |
| 10 | `facilityPerformance()` | `:168-170` | `app/api/reports/facility-performance/route.ts:45` | **F-04 レポート**（`ReportsScreen.tsx:143`） |
| 11 | `continuousProblems()` | `:171-173` | `app/api/reports/continuous-problems/route.ts:43` | **F-04 レポート**（`ReportsScreen.tsx:146`）。★「連続」はスケジュール存在日の連続★（D-026） |
| 12 | ★`errorBreakdown()`★ | `:174-176` | `app/api/reports/error-breakdown/route.ts:55` | **F-04 レポート**（`ReportsScreen.tsx:151`）。★**生値方針の影響を最も強く受ける**★（§B の `ERROR_TYPE_ORDER` / `ERROR_TONE` の2項目。D-077 の「変換ロジックはまだ1行も書かれていない＝手戻りが最小のタイミング」） |

★**実装順の目安**★: 5 / 7 / 8 は ★呼び出し元が無いので後回し★。
★**先に 1（`cleanLogs`）を仕上げること**★ — §B の B2/B3・色分けの検証がここで初めて可能になる。

---

## G. 関連する記録

| 記録 | 内容 |
|---|---|
| ★★**D-086**★★ | ★指示書M（ローカルDBで動かす第1歩）完了記録★。`facilities()`/`schedules()`実装・ローカルDBセットアップ手順・Edge Runtimeバンドル破壊の発見と修正・`dwatch_local`が現行`schema.prisma`と一致しない事実 |
| ★**D-085**★ | 指示書M着手前確認2件（`devices.maker_id`採番=1:PUDU/2:Keenon、施設12スケジュールの構造的除外） |
| ★**D-081**★ | ★第6回からエラー情報の照会を取り下げ、質問を2件に絞った★ / ★保留質問（→ §H）★ / 「データに出てこないこと」は「存在しないこと」の証明にならない |
| ★**D-078**★ | 参照用アカウントの受領 / ★接続前に未解決な2点（ホスト名・ポート未受領、パスワードの URL エンコード）★ / ★B2 の暫定マッチ値 `Handrail Lifted`★ / ★接続後にすぐ確認すべきこと★ |
| ★**D-079**★ | ★本文書の起票根拠（項3 の実装上の申し送り）★ / プロキシ1段 / `resolution_status` は2状態化の対象外 |
| ★**D-077**★ | ★エラーは変換せず DB の生値をそのまま表示する★ / ★B2/B3 が実データで発火しなくなる（項3）★ / 影響箇所の実測表 |
| **D-074** | `error_type` 24値・`error_level` 4値の受領 / 第5回 X-1〜X-3・Y-3 の承認 / PUDU 公開ドキュメントの実閲覧結果 |
| `docs/handoff/analysis/error-type-mapping.md` | 24値の原文と意味の推測（★参考資料。実装に使わない★）。★§4-2 / §5-4 が本文書 §B の元ネタ★ |
| `docs/handoff/TeamWorks回答_第2回_20260903.md:53-98` | `robots.status` のコード表（★46件。`103` が欠番★）。★`:107-108` が「画面に出す6状態と一致しない」と自ら明記★ |
| `docs/handoff/TeamWorks確認事項_第6回_20260908.md` | ★接続先（ホスト名・ポート）の依頼を含む最新の確認書★ |
| `docs/handoff/指示書K_エラー値を変換せず生値で表示する.md` | ★段階7-B の `Handrail Lifted` 実在確認は実データ接続が要る★（D-079 帰結の表） |
| `CLAUDE.md` §3 / §4.3 / §4.6 / §4.7 / §7 / §9.3 / §9.5 | 権限・丸め・契約フラグ・集計期間・禁止事項・コーディング規約・fixtures |
