# 指示書AK — 分類（ホールディングス / 屋号 / 店舗）を復活させる

**作成: 2026-09-15 / ブランチ: `develop` / 前提: 指示書AJ 完了（D-137）**

---

## ★Cursor への指示文（ここから下をそのまま貼ってください）★

> ```
> D-Watch の実装をお願いします。ブランチは develop です。
>
> ■ 最初に読むもの（この順で、全部）
>   1. CLAUDE.md（全文。特に §2 責任分界 / §4.2 KPI / §4.6 契約フラグ / §5 モックが正 /
>      §7 禁止事項 / §9 実装規約）
>   2. docs/handoff/指示書AK_分類を復活させる.md（全文）
>   3. docs/decisions.md の D-053 / D-011 / D-102 / D-133
>      （grep -nE "^### D-(0|1)[0-9][0-9]" docs/decisions.md で位置を出してから読む。
>        ★番号順に並んでいません★）
>   4. lib/facilities/resolve-by-name.ts（全文。★今回の実装が真似る作法そのもの★）
>
> ■ やること
>   指示書AK の 段階1 から 段階9 までを、★番号順に★実行してください。
>   ★段階6（確定期間KPIの安全弁）だけは、段階2〜4 が終わるまで着手しないでください★ —
>   顧客で絞る手段が無い状態で安全弁を外すと、静かに誤った数字が出ます。
>
> ■ 段階ごとに必ずやること
>   1. npx tsc --noEmit    → 0 であること
>   2. npm run lint        → 0 であること
>   3. npm test            → 全パス
>   4. コミットする（★段階ごとに1コミット★。まとめない）
>
> ■ コミットの決まり
>   ・git add -A は使わないでください。必ずファイルパスを明示してください
>   ・コミットメッセージは日本語。「なぜそうしたか」を1〜2行入れてください
>   ・push はしないでください
>
> ■ 絶対に触らないファイル
>   README.md / docs/code-map.md /
>   docs/handoff/管理画面_設計検討_20260910.md / .claude/worktrees/** /
>   d-watch-mock.jsx / lib/judge/**（判定ロジックは今回変えません）
>   lib/db/prisma.ts
>
> ■ 絶対にやらないこと
>   ・生SQL（$queryRaw / $executeRaw / *Unsafe / Prisma.raw）— ESLint が落とします
>   ・メインDBのスキーマ変更・マイグレーション作成
>   ・customers.contract の参照（facilities.contract が正）
>   ・contract を数値として比較すること（varchar(10) の桁位置です）
>   ・新しい CSS に font-family を書くこと
>   ・仕様が不明なところを推測で実装すること
>
> ■ 迷ったら
>   推測で埋めず、手を止めて「ここが決まっていません」と報告してください。
>   指示書AK の §12（実装者へ — 特に踏みやすい落とし穴）に、
>   例外もテスト失敗も出ないまま壊れる箇所を挙げてあります。着手前に読んでください。
>
> ■ 動作確認
>   ★テストDBには グループ1件・顧客1件・施設12件 しか入っていません★。
>   そのままでは3階層の絞り込みを確かめられないので、
>   段階8 で scripts/seed-local-db.ts を拡張してローカルDBで確かめます。
>   ★テストDB（133.125.238.144）には書き込まないでください★。
>   期待される結果は指示書AK の §10 に書いてあります。
>
> ■ 終わったら
>   ・段階ごとのコミットハッシュ
>   ・tsc / lint / test の結果（テスト本数も）
>   ・★§10 の動作確認を実際に行った結果★
>   ・指示書どおりにできなかったところと、その理由
>   ・判断に迷って自分で決めたところ（あれば全部）
> ```

---

## 0. この指示書の位置づけ

ユーザーの新要件4件のうち、★3件目★です。

| 要件 | 状態 |
|---|---|
| 要レスキュー店舗 | ★完了★（D-135 / D-137） |
| 詳細ログの期間上限2年 + CSV上限 | ★完了★（D-136） |
| ★分類の復活★ | ★★この指示書★★ |
| 総清掃面積の新画面 | ★次★（★この指示書の絞り込みに乗るので、順番が後★） |

### 0-1. ★TeamWorks 待ちの部分と、待たずに作れる部分★

`TeamWorks確認事項_第9回_20260915.md` で3件を照会中です。

| 照会中 | この指示書への影響 |
|---|---|
| Q7-1: `customers` は屋号か法人か | ★**画面の見出し語だけ**★。構造は変わらない |
| Q7-5: 「屋号が無い顧客」はどの段が空くか | ★**表示の出し方だけ**★ |
| Q7-4: `group_id = 0` が FK で入らない | ★**データ投入側の話**★。読む側は `0 → null` で扱えばよい |

★★どれも構造を変えません。★★ ★DBの階層は `groups → customers → facilities` の3段しかなく、
4段目の受け皿が無いからです。★ ★したがって、**仕組みは今すぐ作れます**。★

★見出し語は `lib/i18n.ts` の1箇所に置く★ので、★回答が来たら差し替えるのは1行★です。

---

## 1. ★何を作るのか★

### 1-1. D-053 で凍結したものを解除する

★D-053（`docs/decisions.md`）の原文★:

> - **決定**: グループ・会社の絞り込み `<select>` は、マスタが揃うまで画面から削除する
> - **理由**: 固定の選択肢だけがあって何も起きないUIは、不具合と区別できないため
> - **運用**: ★**マスタが揃った時点で、グループ・会社の絞り込みは全画面まとめて実装する**★

★★「全画面まとめて」と書かれています。★★ ★1画面ずつ足さないこと。★

削除したコミットは `441c8bb`（2026-09-01）です。

### 1-2. 下地は既にあります

| | 状態 |
|---|---|
| DBの3階層 | ★`groups` → `customers.group_id` → `facilities.customer_id`。実在★ |
| Prisma のモデル | ★`Group` / `Customer` / `Facility`。実在★ |
| API契約の `group_id` / `customer_id` | ★11エンドポイントに定義済み★ |
| `GET /groups` / `GET /customers` | ★契約にはある。★**ルートが無い**★ |
| 実装 | ★受け取って無視している★（D-053 のコメントが13ファイル） |

★★つまり契約の追加ではなく、実装の穴埋めです。★★

### 1-3. ★ユーザーが決めたこと（原文）★

> ・分類をホールディングス、各グループ（ブランド）、店舗
>  分類を復活

★真ん中は屋号（ブランド）★（2026-09-15「屋号でよいです、ただ顧客によってはない場合もあります」）。

★**対応:**★

```
ホールディングス  →  屋号（ブランド）  →  店舗
   groups              customers          facilities
```

---

## 2. ★監督が決めたこと（実装前に読むこと。覆すなら理由を残すこと）★

★ユーザーに個別の確認を取らず、既存の規則との整合から決めた事項です。★

### 2-1. ★★確定期間のKPIは、対象顧客が1社に定まるときだけ出す★★

★これが今回いちばん重要な決定です。★

**背景**: `/reports/kpi` の確定期間は `customer_summaries` を読みます（D-102）。
現在は「複数顧客の行が混ざったら例外（500）」という安全弁が入っています（D-133）。

**合算できないことが実測で分かっています。**

| 指標 | サマリの分子 | サマリの分母 | 合算できるか |
|---|---|---|---|
| 清掃完了率 | `clean_area` | `task_area` | ★できる★ |
| 出発成功率 | `launch_count` | `task_count`? | ★式が未確認★ |
| ★稼働率★ | ★**列が無い**★ | `task_count`? | ★★できない★★ |
| ★エラー発生率★ | `error_count` | ★**列が無い**★ | ★★できない★★ |

★「清掃実施回数」に当たる列がサマリにありません。★
★率の平均を取るのは誤り★です（店舗数の違いが消えます）。

**★決定:**★

```
customer_id が指定されている        → その顧客で絞る
group_id が指定されている           → 傘下の顧客が1社なら、その顧客で絞る
                                      2社以上なら 422
どちらも指定なし・顧客が1社だけ     → その顧客（★P1 はここ★）
どちらも指定なし・顧客が2社以上     → ★★422★★
```

★422 のメッセージ★:

```
「顧客またはグループを指定してください。複数の顧客のデータを合算した数値は出せません。」
details: { customer_ids: [...] }   ← ★候補を返す★
```

★★500 ではなく 422 にすること★★ — ★サーバの不具合ではなく、指定が足りないという話★です。
★`details` に候補を返すこと★ — ★画面が「どれを選べばよいか」を示せます。★

★★「静かに1社を選ぶ」ことは絶対にしないこと★★（D-133 が明確に否定しています。
どの顧客かを決める根拠が無いまま選ぶと、選び間違いに誰も気づけません）。

★P1 の対象顧客は1社だけ★（`CLAUDE.md` §1）なので、★この422は当面発火しません★。
★2社目が入った日に、画面が「選んでください」と言うようになります。★

★却下した案と理由（記録に残すこと）★:

| 案 | なぜ却下したか |
|---|---|
| 全顧客を合算する | ★4指標のうち2つを正しく合算する材料がDBに無い★（上の表） |
| 率の加重平均で近似する | ★TeamWorks が計算した値ではなくなる★。検算可能性（D-022 の設計意図）を失う |
| 500 のまま | ★2社目が入った瞬間、レポート画面が常時500になる★。運用が止まる |
| 顧客が1社のときだけ暗黙に選ぶ | ★候補が1つなら選び間違いようが無いので採用★（上の決定に含めた）。★2社以上で暗黙に選ぶのは却下★ |

### 2-2. 絞り込みは「施設IDの配列」に解決してから渡す

★既存の作法に乗せます★（`lib/facilities/resolve-by-name.ts`、指示書AC / AG の帰結）。

```
group_id / customer_id  →  対象の施設IDの配列  →  where: { facility_id: { in: [...] } }
```

★★`null` と `[]` を区別すること★★:

| 値 | 意味 |
|---|---|
| `null` | ★条件なし（全件）★ |
| `[]` | ★0件ヒット★ |
| `[1,2,3]` | その施設だけ |

★★必ず `!== null` で分岐すること★★ — ★`in: undefined` は条件が消えて**全件**になります★。
★`?.length` で判定すると、「0件ヒット」が「全件表示」に化けます。例外もテスト失敗も出ません。★

★既存の正しい書き方が2箇所にあります★。★そのまま真似ること★:

- `lib/db/index.ts` の `cleanLogs()`（`facility_ids !== null ? { facility_id: { in: ... } } : {}`）
- `lib/db/index.ts` の `facilityPerformance()`

★★施設名の検索と併用されたときは、両方の積集合を取ること★★（AND）。
★どちらか片方を捨てないこと。★

### 2-3. 施設名の照合とは違い、DBで完結させてよい

★施設名検索がアプリ側照合になっているのは、「中点を無視する比較が MariaDB に無い」からです★
（実測済み。`lib/facilities/resolve-by-name.ts` の冒頭コメント）。

★`group_id` / `customer_id` は整数の完全一致★なので、★その制約はありません★。
★全件をメモリに読んで照合する必要はありません。★

★真似るのは「`null` / `[]` の戻り値の契約」と「`in` に渡す形」だけ★です。

### 2-4. ★200件の壁は、今回は素通しする★

★`eq_range_index_dive_limit = 200`★（MariaDB 10.11 で実測）。
★`IN` の要素数が200を超えると、索引ダイブをやめて統計推定に切り替わります。★
★遅くなるだけで、エラーは出ません。★

★グループの絞り込みは「1グループ＝傘下の全店」なので、施設名検索より200を超えやすい★です。

★**決定: 今回は素通しする。**★

★理由★: P1 の施設数は200件程度の想定で、★本番の施設数の見積もりが資料に1件も無い★
（第9回 8章で照会中）。★測れない相手に対策を入れても、効いたかどうかが分かりません。★

★★申し送りに書くこと★★: 施設が200件を超えたら実行計画を測り直す。
★JOIN（リレーションフィルタ）に切り替える案があるが、このプロジェクトに前例が0件で、
Prisma 7 + MariaDB adapter がどんなSQLを出すか未実測★。

### 2-5. 見出し語

★ユーザーの言葉を使う★。★`lib/i18n.ts` の1箇所に置くこと★。

| 語 | EN |
|---|---|
| ホールディングス | Holdings |
| 屋号 | Brand |
| 全ホールディングス | All Holdings |
| 全屋号 | All Brands |

★★既存の「全グループ」「全会社」を再定義しないこと★★（`lib/i18n.ts` に既にあります）。
★古い語を消すかどうかは実装者判断★ですが、★消すなら他に使っていないことを確かめること★。

★★TeamWorks の回答（Q7-1）で `customers` が法人だと分かったら、この表だけ差し替えます。★★
★だから1箇所に集めておくこと。★

---

## 3. ★触ってはいけないもの★

| 対象 | 理由 |
|---|---|
| `README.md` / `docs/code-map.md` / `docs/handoff/管理画面_設計検討_20260910.md` / `.claude/worktrees/**` | ★別作業★ |
| `d-watch-mock.jsx` | ★モックは原型として保存する★ |
| ★`lib/judge/**`★ | ★判定ロジックは今回変えません★ |
| `lib/db/prisma.ts` | ★未コミットの変更が乗っている★ |
| ★テストDB（`133.125.238.144`）への書き込み★ | ★★読み取り専用。SELECT 以外を投げないこと★★ |

★★禁止★★（`CLAUDE.md` §7）:

- ❌ ★`git add -A`★ — 必ずファイルパスを明示する
- ❌ ★生SQL★（ESLint が error で落とす。D-122）
- ❌ ★メインDBのスキーマ変更・マイグレーション作成★
- ❌ ★`customers.contract` の参照★（★`facilities.contract` が正★。D-010 / D-080）
- ❌ ★`contract` を数値として比較すること★ — ★`varchar(10)` の桁位置です★。
  ★`Number("0000000000")` は例外を出さずに `0` になり、★★全施設が「契約なし」になってメニューが
  全部消えるのに、例外もテスト失敗も出ません★★★（D-080）
- ❌ ★新しいCSSへの `font-family`★
- ❌ ★推測での実装★

---

## 4. 段階1 — マスタAPI 2本

### 4-1. YAML は既にあります

`d-watch-api.yaml` の `/groups` と `/customers` は★定義済み★です。★先に読むこと。★

★ただし、そのままでは足りない点が5つ★あります。★どう埋めるかを決めてから実装すること。★

| # | 足りない点 | どうするか |
|---|---|---|
| 1 | ★`meta` が無い★（`CLAUDE.md` §9.3 は「一覧APIは必ず `meta` を返す」） | ★セレクトリスト用なのでページングは要らない★。★`meta` を足すか、YAML に「セレクト用なので `meta` を返さない」と明記するか、どちらかに決めて理由を書くこと★ |
| 2 | ★エラー応答の宣言が無い★ | ★他のルートも全部同じ状態★（別課題として記録済み）。★今回だけ足すと書式が揃わない★。★足さないことを選んでよいが、選んだ理由を書くこと★ |
| 3 | ★`group_id` に「未設定を明示する」値が無い★ | ★`group_id=0` を API に漏らさないこと★（D-011 は「API層で 0 → null に変換する」と定めている）。★「ホールディングスなしの顧客だけ」を引く手段は今回作らない★ |
| 4 | ★`inactive` / `deleted_at` の扱いが未定義★ | ★`deleted_at IS NULL` で絞る★（`db.facilities()` と揃える）。★`inactive` では絞らない★。★YAML の description に書くこと★ |
| 5 | ★ソート順が未定義★ | ★`id` の昇順★。★契約に明記すること★（D-052 / D-055 の前例） |

### 4-2. 実装

- `app/api/groups/route.ts` / `app/api/customers/route.ts`
- ★手本は `app/api/schedules/route.ts`★（`items` + `meta` だけの一覧で、形が近い）
- ★認証はルートに書かない★ — `proxy.ts` が横断して処理します
- ★`GET` だけ★。★POST / PUT / DELETE を足さないこと★（マスタのCRUDは D-Security 側の画面。`docs/spec.md`）

### 4-3. `DataSource` への追加

`lib/db/index.ts` の `interface DataSource` に足し、
★`fixtureSource` と `prismaSource` の**両方**に実装すること★。

★★fixtures 側を `notImplemented` にしないこと★★ — ★`USE_FIXTURES=true` で画面が落ちます。★

★`lib/fixtures/` に `groups.ts` / `customers.ts` が存在しません。新設すること。★
★施設12件をどの顧客に割り当てるかは実装者判断★ですが、
★★1グループ・1顧客にしないこと★★ — ★それでは3階層のUIを fixtures で確かめられません。★

### 4-4. ★`group_id = 0` の扱い★

```
DB: customers.group_id = 0   （NOT NULL。0 = グループなしのセンチネル。D-011）
API: group_id = null
```

★変換は1箇所に閉じ込めること★（`lib/format/` か `lib/config/` の純粋関数。D-080 の作法）。
★画面やルートに `=== 0` を散らさないこと。★

★★「グループ0」という行を作らないこと★★ — ★グループ一覧に `id=0` が現れてはいけません。★

---

## 5. 段階2 — クエリパーサに `group_id` / `customer_id` を足す

### 5-1. 対象

| ファイル |
|---|
| `lib/prechecks/query.ts` |
| `lib/ds-devices/query.ts` |
| `lib/ds-alerts/query.ts` |
| `lib/reports/query.ts` |
| `lib/facilities/query.ts` |
| `lib/clean-logs/query.ts` |
| `lib/schedules/query.ts` |

★「D-053。マスタ未整備のため無視する」というコメントを消し、実際に読むようにする。★

### 5-2. ★既存テスト5本が必ず落ちます★

```
lib/prechecks/__tests__/query.test.ts
lib/ds-devices/__tests__/query.test.ts
lib/ds-alerts/__tests__/query.test.ts
lib/reports/__tests__/query.test.ts
lib/facilities/__tests__/query.test.ts
```

すべて `it("group_id / customer_id を渡してもパース結果に含まれない（D-053）")` です。

★★`it` ごと消さないこと★★ — ★テストが1本も残らないと、次に誰かが「無視する」実装に戻しても
気づけません。★

★反転させて、さらに強くすること★:

| # | 入力 | 期待 |
|---|---|---|
| 2-1 | `group_id=1` | ★`1` が入る★（★`toHaveProperty` ではなく値まで見ること★） |
| 2-2 | `customer_id=2` | ★`2` が入る★ |
| 2-3 | ★未指定★ | ★`null`★ |
| 2-4 | ★`group_id=0`★ | ★★422★★（★0 はセンチネルで有効なIDではない★） |
| 2-5 | ★`group_id=1abc`★ | ★★422★★（★`parseInt` の前方一致で `1` にしないこと★。D-123 で踏んだ罠） |
| 2-6 | `group_id=-1` | ★422★ |
| 2-7 | ★`group_id=1.5`★ | ★422★ |
| 2-8 | ★巨大な整数★（`Number.MAX_SAFE_INTEGER` 超） | ★422★ |

★★2-5 は過去に実際に踏んだ罠です★★（D-123）。★必ずテストに入れること。★
★既存の `parsePathId()`（`lib/api/path-params.ts`）が `^\d+$` + `Number.isSafeInteger` で
やっているので、★同じ作法を使うこと★。

### 5-3. YAML の description を直す

`d-watch-api.yaml` の `GroupId` / `CustomerId`:

> グループで絞り込み。★グループ・会社マスタ未整備のため現在は受け取るが無視する。実DB接続時に対応する★

★この記述を書き換えること。★ ★`minimum: 1` を足すこと★（0 はセンチネルなので有効なIDではない）。

★★`group_id` と `customer_id` を同時に指定したときの扱いを決めて、契約に書くこと★★ —
★資料のどこにも定義がありません。★ ★AND（両方を満たす施設）が素直★ですが、
★決めたら必ず YAML に書くこと★（次の人が推測しないように）。

---

## 6. 段階3 — 施設IDへの解決

### 6-1. 新しい純粋関数

`lib/facilities/` に足す（`resolve-by-name.ts` の隣）。

```
groupId / customerId  →  対象の施設IDの配列 | null
```

★戻り値の契約は `resolveFacilityIdsByName()` と完全に同じにすること★:

| 状況 | 戻り値 |
|---|---|
| どちらも未指定 | ★`null`★（条件なし） |
| 指定されたが該当0件 | ★`[]`★ |
| 該当あり | ID の配列 |

### 6-2. ★施設名との併用★

★施設名の絞り込みと同時に指定されたら、**積集合**を取ること★（AND）。

| 施設名 | グループ | 期待 |
|---|---|---|
| `null` | `null` | ★`null`★ |
| `[1,2,3]` | `null` | `[1,2,3]` |
| `null` | `[2,3,4]` | `[2,3,4]` |
| ★`[1,2,3]`★ | ★`[2,3,4]`★ | ★★`[2,3]`★★ |
| ★`[1]`★ | ★`[2]`★ | ★★`[]`★★（★`null` にしないこと★） |
| `[]` | `[1,2]` | ★`[]`★ |

★★最後の2行が要です★★ — ★積集合が空になったとき `[]` を返すこと★。
★`null` にすると全件表示に化けます。★

### 6-3. 読み取りの注意

- ★施設マスタは12件程度なので、1回読んでメモリで解決してよい★
- ★★施設ごとにループでクエリを撃たないこと★★
- ★`deleted_at` で絞るかどうか★: ★`facilityNameIndex()` は絞っていません★
  （★削除済み施設のログが見えなくなるのを避けるため。罠7★）。★同じ扱いにすること。★
  ★違う扱いにするなら、理由をコメントに書くこと。★

---

## 7. 段階4 — 一覧系APIに絞り込みを効かせる

### 7-1. 対象

| ルート | 備考 |
|---|---|
| `/clean-logs` / `/clean-logs/export` | ★YAML は宣言済み。コードにコメントも無い★ |
| `/prechecks` | |
| `/ds-devices` | |
| `/ds-alerts` | |
| `/facilities` | ★YAML に宣言が無い。足すこと★ |
| `/schedules` | ★同上★ |
| `/dashboard` | ★要レスキューと数値カードの両方に効く★ |
| `/reports/facility-performance` | ★施設単位なので普通に効く★ |
| `/reports/continuous-problems` | ★同上★ |
| `/reports/error-breakdown` | ★★当期は施設単位。確定期間は段階6 の話★★ |

### 7-2. ★`/dashboard` の注意★

★数値カード（`summary`）にも絞り込みが効きます。★ ★それが正しい★です
（「この屋号の昨夜の成績」が見たいので）。

★★ただし、要レスキューと数値カードで母集団がずれないようにすること★★ —
★同じ `facility_ids` を両方に渡すこと。★

### 7-3. ★`facility_error_summaries` の絞り込み★

`lib/db/index.ts` の `errorBreakdown()` は `facility_error_summaries` を
★`facility_id` で絞らずに読んでいます★（D-133 の申し送り③）。

★今回、絞り込みが入るので解決できます。★ ★`ReportDbQuery` に `facility_ids` を足すこと★
（現在は `FacilityPerformanceDbQuery` にしかありません）。

---

## 8. 段階5 — `Facility` に所属を足す

### 8-1. 契約と型

`d-watch-api.yaml` の `Facility` と `lib/types/index.ts` の `Facility` に足す:

| フィールド | 型 | 備考 |
|---|---|---|
| `customer_id` | `integer` | ★NOT NULL なので nullable にしない★ |
| `customer_name` | `string` | ★一覧の「ブランド」列★。★`InspectionListItem.customer_name` に前例があり、文言を揃えること★ |
| `group_id` | `integer, nullable` | ★nullable 必須★（0 → null。D-011） |
| `group_name` | `string, nullable` | 同上 |

### 8-2. 取得

`lib/db/index.ts` の `facilities()` は既に `include: { robots: ... }` しています。
★`include: { customer: { include: { group: true } } }` を足すだけ★です。
★施設12件なので N+1 にはなりません。★

### 8-3. fixtures

`lib/fixtures/facilities.ts` は `Facility[]` を直接持っています。
★12件すべてに新しい4フィールドを足すこと。★

---

## 9. 段階6 — ★確定期間KPIの安全弁を置き換える★

★★段階2〜4 が終わるまで着手しないこと。★★
★顧客で絞る手段が無い状態で安全弁を外すと、静かに誤った数字が出ます。★

### 9-1. 現状

`lib/db/report-mapping.ts` の `customerSummariesByPeriodKey()`:

- 全行の `customer_id` が1種類かを確認し、★2種類以上なら例外★
- docstring に「★`customer_id` で `where` を絞って回避しないこと★ — ★どの顧客かを決める根拠が無い★」

★★分類が復活すると「どの顧客かを決める根拠」ができます。★★
★この docstring は書き換えること★ — ★残すと、次の人が矛盾した指示を読みます。★

### 9-2. 実装

★§2-1 の決定のとおり★:

```
customer_id が決まる  → lib/db/index.ts の customerSummary.findMany の where に customer_id を足す
決まらない            → 422
```

★同じ期間キーの重複を検知する分岐は**残すこと**★ —
★一意制約が無いので、同一顧客でも重複しうるからです。★

### 9-3. 期待値表

| # | 状況 | 期待 |
|---|---|---|
| 6-1 | `customer_id=1` 指定・顧客1社のデータ | ★200。その顧客の数字★ |
| 6-2 | `customer_id=1` 指定・DBに2社ぶんある | ★★200。顧客1だけの数字★★（★絞れているか★） |
| 6-3 | ★指定なし・顧客が1社だけ★ | ★★200★★ |
| 6-4 | ★指定なし・顧客が2社★ | ★★422★★（★500 ではない★） |
| 6-5 | 6-4 の `details` | ★候補の `customer_ids` が入る★ |
| 6-6 | `group_id` 指定・傘下1社 | ★200★ |
| 6-7 | ★`group_id` 指定・傘下2社★ | ★★422★★ |
| 6-8 | 同じ期間キーが重複 | ★例外のまま★（★この検知は残す★） |
| 6-9 | ★`customer_id` が存在しないID★ | ★200・空★（★422 にしない★。「該当なし」は正常） |

★★6-2 が要です★★ — ★絞り込みが本当に効いているかを、2社ぶんのデータで確かめます。★
★段階8 の seed 拡張が要ります。★

---

## 10. 段階7 — 画面に絞り込みを戻す

### 10-1. ★6画面まとめて★（D-053 の指定）

モックに `<select>全グループ</select><select>全会社</select>` があるのは6画面です。

| モックの行 | 画面 |
|---|---|
| `:556` | 清掃詳細ログ |
| `:610` | アラート（事前チェック） |
| `:669` | レポート |
| `:836` | D-Secログ |
| `:923` | D-Secアラート |
| `:1091` | ★点検記録★ — ★★画面自体が未実装（D-083 で非表示）。今回は対象外★★ |

★★実質5画面です。★★ ★点検記録は画面が無いので、足しようがありません。★
★対象外であることを記録に書くこと。★

★現在 D-053 のコメントが残っているのは3画面だけ★（清掃詳細ログ / レポート / 施設マスタ）で、
★残りは移植時にそもそも作られていません★。★無い画面にも足すこと。★

### 10-2. 作法

- ★選択肢は `/groups` と `/customers` から取る★。★固定の選択肢を書かないこと★
  （★それをやったのが D-053 で消された元のUIです★）
- ★ホールディングスを選んだら、屋号の選択肢がその傘下だけに絞られること★
  （`GET /customers?group_id=X`）
- ★draft（入力中）と確定（検索ボタン押下）を分けること★ —
  ★`ReportsScreen.tsx` に前例があります★。★1操作ごとに叩かないこと★
- ★取得は `apiFetch`★（`lib/api/client.ts`）。★`fetch` を画面に直書きしない★
- ★文言は `t()` を通す★。★色は CSS 変数★。★新しいCSSに `font-family` を書かない★

### 10-3. ★マスタの取得が失敗したとき★

★選択肢が取れなかったら、絞り込みUIを出さないこと。★
★★空の `<select>` を出すと、D-053 で消した「押しても何も起きないUI」に逆戻りします。★★

---

## 11. 段階8 — seed を拡張する（★ローカルDBのみ★）

### 11-1. ★テストDBには書き込まないこと★

★`133.125.238.144` は読み取り専用で扱います。★
★拡張するのは `scripts/seed-local-db.ts`（ローカルの MySQL 8.4 `dwatch_local`）です。★

### 11-2. 現状

```
groups     1件（"テストグループ001"）
customers  1件（"テスト顧客001"）
facilities 12件（全部 customer_id=1）
```

★これでは3階層の絞り込みも、§9-3 の 6-2 / 6-4 も確かめられません。★

### 11-3. 足すもの

| | 最低限 |
|---|---|
| `groups` | ★2件以上★ |
| `customers` | ★4件以上★ — ①グループA傘下 ②グループA傘下（★同一グループ2社目★）③グループB傘下 ④★グループなし★ |
| `facilities` | ★12件を複数の顧客に散らす★ |
| `customer_summaries` | ★顧客ごとに作る★ — ★★これが §9-3 の 6-4（2社で422）を再現する唯一の手段★★ |
| `facility_error_summaries` | ★複数顧客の施設に散らす★（申し送り③の再現） |

### 11-4. ★`group_id = 0` の投入について★

★ローカルは MySQL 8.4 で、FK の状況がテストDBと違う可能性があります。★
★`group_id = 0` が入らない場合は、無理に入れようとしないこと★ —
★★TeamWorks へ照会中の項目です（第9回 Q7-4）★★。

★入らなければ「グループなし」の検証は保留★とし、★そう記録すること★。
★★FK制約を外すなどの回避をしないこと。★★

### 11-5. 完了ログ

`scripts/seed-local-db.ts` の末尾に `groups=1 customers=1 ...` と★数がハードコード★されています。
★実際の件数を出すように直すこと★ — ★でないと拡張後に嘘のログが出ます。★

---

## 12. 段階9 — 記録

### 12-1. `docs/decisions.md`

★★採番の前に必ず実測すること★★:

```bash
grep -nE "^### D-(0|1)[0-9][0-9]" docs/decisions.md | tail -3
```

★番号順に並んでいません。★ 末尾を見て最大値を判断しないこと。

書くこと:

- ★D-053 を解除したこと★ — ★★D-053 の見出しに `[破棄]` または `[一部破棄]` の印と行き先を書くこと★★
  （D-024 / D-067 / D-135 と同じ作法。★これを忘れると、次の人が「絞り込みを出すな」と読みます★）
- ★★確定期間KPIを「顧客が1社に定まるときだけ出す」とした決定と、却下した4案の理由★★（§2-1）
- ★D-133 の安全弁を置き換えたこと★。★D-133 側にも印を付けること★
- ★200件の壁を素通しした理由★（測れる見積もりが無い）
- ★見出し語を `lib/i18n.ts` に集めた理由★（TeamWorks の回答で差し替えるため）
- ★点検記録の画面が対象外であること★
- ★`group_id = 0` の投入が保留であること★

### 12-2. `docs/handoff.md` に追記

- ★施設が200件を超えたら実行計画を測り直すこと★
- ★TeamWorks の Q7-1 / Q7-4 / Q7-5 が未回答であること★と、★回答で何が変わるか（見出し語だけ）★
- ★グループ単位のKPIは、傘下が2社以上になると422で止まること★ —
  ★`group_summaries` か「清掃実施回数」カラムが手に入れば合算できる（第9回 6章で照会中）★
- ★点検記録の絞り込みは、画面が復活したときに足すこと★

---

## 13. ★実装者へ — 特に踏みやすい落とし穴★

| # | 落とし穴 | どうなるか |
|---|---|---|
| ★1★ | ★`facility_ids` を `?.length` で分岐する★ | ★「0件ヒット」が「全件表示」に化ける。例外もテスト失敗も出ない★ |
| ★2★ | ★施設名とグループの積集合が空のとき `null` を返す★ | 同上 |
| ★3★ | ★`group_id = 0` を API に漏らす★ | 「グループ0」という存在しないグループが画面に出る |
| ★4★ | ★`contract` を数値として比較する★ | ★★全施設が「契約なし」になってメニューが全部消えるのに、例外もテスト失敗も出ない★★（D-080） |
| ★5★ | ★`customers.contract` を読む★ | ★削除依頼済みの列★。`facilities.contract` が正 |
| ★6★ | ★2社以上のとき静かに1社を選ぶ★ | ★選び間違いに誰も気づけない★（D-133 が明確に否定） |
| ★7★ | ★`parseInt` の前方一致で `1abc` を `1` にする★ | ★過去に実際に踏んだ罠★（D-123） |
| ★8★ | ★既存テスト5本を消す★ | 「無視する」実装に戻っても気づけない |
| ★9★ | ★fixtures 側を `notImplemented` にする★ | ★`USE_FIXTURES=true` で画面が落ちる★ |
| ★10★ | ★fixtures を1グループ1顧客のままにする★ | 3階層のUIを fixtures で確かめられない |
| ★11★ | ★空の `<select>` を出す★ | ★D-053 で消した「押しても何も起きないUI」に逆戻り★ |
| ★12★ | ★D-053 / D-133 に印を付けない★ | ★次の人が古い決定を読んで、実装を「壊れている」と判定する★ |
| ★13★ | ★テストDBに書き込む★ | ★★読み取り専用。絶対にしないこと★★ |
| ★14★ | ★施設ごとにループでクエリを撃つ★ | 施設数ぶんの往復が発生する |

---

## 14. やらないこと（★この指示書の範囲外★）

| 項目 | なぜ |
|---|---|
| ★総清掃面積の新画面★ | ★次の指示書★。この絞り込みに乗る |
| ★グループ管理・顧客管理の画面★ | ★`docs/spec.md` が「実装しない（D-Security側の画面を流用）」と定めている★ |
| ★点検記録の絞り込み★ | ★画面自体が未実装★（D-083） |
| ★複数顧客の合算★ | ★4指標のうち2つを合算する材料がDBに無い★（第9回 6章で照会中） |
| ★200件超の実行計画対策★ | ★測れる見積もりが無い★（第9回 8章で照会中） |
| ★`group_summaries` の新設★ | ★TeamWorks の領域★。第9回 6章で照会中 |
| ★`group_id = 0` を入れられるようにすること★ | ★DDLの話。DFAは直せない★（第9回 Q7-4） |
| ★スマートフォン幅のレイアウト★ | 別課題 |
