# 指示書AG — 店舗検索を記号のゆれに強くし、画面ごとの食い違いを無くす

| 項目 | 値 |
|---|---|
| ★★状態★★ | ★★**完了（2026-09-13）**★★ — ★段階1〜6 すべて実行済み★。★決定は ★**D-129**★（`docs/decisions.md`）/ 引き継ぎは ★`docs/handoff.md` 追記43★。★詳細は末尾「7. 実行状態」★ |
| ★完了時の実測★ | ★`tsc` 0 / `eslint` 0 / `vitest` ★**51ファイル 1284件 全通過**★（着手前 1230件 → ★+54★）★ |
| 作成日 | 2026-09-13 |
| 起点 | `develop` の `f2288ad`（`docs: API契約を実装に追随させ、AEの査読結果を記録する(D-128)`） |
| ★着手前の基準★ | ★`tsc` 0 / `eslint` 0 / `vitest` 50ファイル 1230件 全通過★ |
| 依存 | ★指示書AE 完了済み★（D-121〜D-128） |
| ★次に来る指示書★ | ★AF（上限の設定と編集）→ AC（絞り込みをSQLに降ろす）★。★本書は AC より先に実施すること★（理由は段階5） |
| 着手前確認 | ★なし★（下の「着手前確認がない理由」を読むこと） |

---

## 0. この指示書の目的

★店舗（施設）を探すとき、店名の記号を打たなくても引けるようにする。★

実データが `ドン・キホーテ戸塚` / `ドン・キホーテ四街道` のとき:

| 入力 | 現状 | 本書の完了後 |
|---|---|---|
| `戸塚` | ★ヒットする★ | ヒットする（変化なし） |
| `ドン` | ★両方ヒットする★ | 両方（変化なし） |
| `ドン・キホーテ` | ★両方ヒットする★ | 両方（変化なし） |
| ★`ドンキホーテ`（中点なし）★ | ★★0件★★ | ★★両方ヒットする★★ |
| ★`ドンキ`★ | ★★0件★★ | ★★両方ヒットする★★ |

> ★**部分一致そのものは、すでに全5系統で動いている**★。
> 本書が足すのは ★**記号のゆれを吸収すること**★ と、★**画面ごとの食い違いを無くすこと**★ の2つ。

### 0-1. ★ついでに直す、もっと日常的なバグ★

★照合の規則が、すでに3種類に分裂している★。

| 系統 | ファイル | 大文字小文字 |
|---|---|---|
| 施設マスタ | `lib/facilities/query.ts:67` | ★区別しない★ |
| スケジュール | `lib/schedules/query.ts:100` | ★区別しない★ |
| D-S制御盤 | `lib/ds-devices/query.ts:65` | ★区別しない★ |
| ★清掃詳細ログ★ | ★`lib/clean-logs/query.ts:117,120,121`★ | ★★区別する★★ |
| ★レポート★ | ★`lib/reports/query.ts:192,202`★ | ★★区別する★★ |

★これが画面に出ている形★:

| 入力 | 施設マスタ | 予定 | D-S | ★清掃詳細ログ★ | ★レポート★ |
|---|---|---|---|---|---|
| `8111r`（シリアル） | ヒット | ヒット | ヒット | ★0件★ | ★0件★ |
| ★`cc1`（機種）★ | — | — | — | ★★0件★★ | — |
| `pudu`（機種） | — | — | — | ★0件★ | — |

★機種を小文字で `cc1` と打つのは自然な操作★なので、★すでに日常的に踏まれている可能性が高い★。

`lib/facilities/query.ts:108-111` のコメントは、この事故をすでに予見している:

> ★個別パラメータも q と同じく大文字小文字を区別しない★ …… 同じ画面の検索が入力欄によって
> 当たったり当たらなかったりするほうが事故になるため揃える。

★その配慮が2系統に適用されていない。★ ★本書で5系統すべてを1本の関数に寄せる。★

---

## 1. 着手前確認がない理由

★通常なら止まって確認する論点が3つあったが、すべて調査で解消した。★ ★起草時に実測済みなので、実装者は止まらずに進めてよい。★

| 論点 | なぜ止まらなくてよいか |
|---|---|
| ★どこまで正規化するか★ | ★ユーザー確認済み（2026-09-13）★ = ★「記号を無視する」のみ★。★カナ・全半角の統一と、スペース区切りのAND検索は選ばれなかった★ |
| ★どのフィールドに適用するか★ | ★実データで誤ヒットを実測して決めた★（決定事項 D-2）。推測ではない |
| ★SQLに降ろす件（AC）とぶつからないか★ | ★実測で成立を確認した★（段階5）。★照合順序では解決しないことも実測済み★（罠5） |

---

## 2. 決定事項

### D-1. ★正規化は純粋関数1本に閉じ込め、5系統すべてがそれを呼ぶ★

★置き場所は `lib/format/`★。`CLAUDE.md` §4.6 が契約フラグに課したのと同じ規律である
（「桁位置をハードコードせず純粋関数1箇所に閉じ込める」）。

★既存の `includesFold()`（`lib/facilities/query.ts:67-69`）は module-private なので、これを昇格させる★のが出発点。

> ★**1箇所だけ直してはいけない**★ — ★分裂が3種類から4種類に増えるだけ★。★5系統を同時に寄せること★。

### D-2. ★★フィールドによって正規化の強さを変える★★（本書で最も重要な決定）

★**記号を全フィールドに一律で適用してはいけない。実データで誤ヒットが出る。**★

#### 強い正規化（`foldName`）— 大文字小文字 ＋ ★記号除去★

| フィールド | 系統 | 理由 |
|---|---|---|
| ★`facility_name`★ | ★全5系統★ | ★本件の本命★。`ドン・キホーテ` の中点は表記ゆれで、★桁位置の意味を持たない★ |
| `prefecture` | 施設マスタ | 値は `東京都`/`奈良県`/`三重県`/`神奈川県`。★記号ゼロなので実質 no-op。無害★ |

#### 弱い正規化（`foldPlain`）— ★大文字小文字のみ★

| フィールド | 系統 | ★記号を除いてはいけない理由（実データ）★ |
|---|---|---|
| ★`address`★ | 施設マスタ | ★★ハイフンは番地の区切り★★。下の実例を見ること |
| ★`serial_number`★ | 全5系統 | ★★形式が未確定★★。下の実例を見ること |
| `maker` / `model` / `model_name` | 施設マスタ / 清掃詳細ログ | ★`ROBOT_MODELS`（`lib/config/index.ts:352-357`）に記号は1文字も無い★ので、記号除去は効果ゼロ。★直すべきは `cc1` が当たらないことのほう★ |

#### ★住所からハイフンを除いてはいけない — fixtures の実データで起きる★

```
東京都○○区1-2-3  →（ハイフン除去）→ 東京都○○区123
東京都○○区2-3-4  →（ハイフン除去）→ 東京都○○区234
```

★住所検索に `23` と入れると、この2件が両方ヒットする。★ ★現状はどちらもヒットしない★
（`1-2-3` にも `2-3-4` にも `23` という連続部分文字列が無いため）。

★別の言い方をすると、`1-23番地` と `12-3番地` と `1-2-3番地` が区別できなくなる★（3つとも `123`）。

#### ★シリアルから記号を除いてはいけない — 桁の境界が消える★

```
PUDU-CC1-0001  →（ハイフン除去）→ PUDUCC10001
```

★シリアル欄に `10001`（連番10001号機を探したつもり）と入れると、`CC1` の `1` と `0001` が
繋がった `PUDU-CC1-0001` にヒットする。★ ★現状は0件。★

★そして利得がほぼ無い★ — ★実データの主形式 `8111R5628060011` には記号が無く、記号除去は no-op★。
`DS-001` 形式のために上のリスクを負うことになる。

★さらに `lib/config/index.ts:309-313` が明示している★:

> ★**文字種は絞らない**★（`CLAUDE.md` §7「仕様が不明な箇所を推測で実装しない」）。
> 実データのシリアルの形式は TeamWorks のデータ収集処理が決めるもので、DFA側で確定していない

★形式が未確定のフィールドに「記号は意味を持たない」という前提を置くのは、この禁止事項そのもの。★

### D-3. ★除く記号の集合★

★`foldName` が除くのは次の4種類★。★★それ以外は除かない★★。

| 種類 | 文字 | 何のため |
|---|---|---|
| 中点 | `・`(U+30FB) `･`(U+FF65) `·`(U+00B7) `•`(U+2022) | ★`ドンキホーテ` → `ドン・キホーテ`★（本件の本命） |
| 長音 | `ー`(U+30FC) | ★`サーバ` で `サーバー` を引ける★のと同じ |
| 空白 | 半角スペース / `　`(U+3000) / タブ | ★`ドンキホーテ港南` → `MEGAドン・キホーテ 港南`★ |
| ハイフン類 | `-`(U+002D) `‐`〜`―`(U+2010-2015) `－`(U+FF0D) `_`(U+005F) | 店名の区切り |

★大文字小文字は `toLowerCase()` で揃える★（`foldName` / `foldPlain` の両方）。

### D-4. ★実装しないもの（意図的に残す穴）★

| やらないこと | 理由 |
|---|---|
| ★カナ・全半角の統一★ | ★ユーザーが選ばなかった★（2026-09-13）。`ﾄﾞﾝ` / `どん` は引けないまま |
| ★スペース区切りのAND検索★ | 同上。`ドン・キホーテ 戸塚` は★スペースが除かれて1語として扱われる★ |
| ★★英字とカナの読み替え（`MEGA` ⇄ `メガ`）★★ | ★★どの語を対にするかは仕様判断★★（`CLAUDE.md` §7）。★★実店舗名が `MEGAドン・キホーテ` なので、「メガドンキホーテ」では引けないまま★★ — ★ユーザーに伝達済み（2026-09-13）★ |
| Unicode 正規化（NFKC） | 上の「カナ・全半角」に含まれる。★入れるなら全半角とセットで一度に★ |

> ★**「メガドンキホーテ」で引けない**のは仕様である★。★★不具合として報告されたら、この行を示すこと★★。

### D-5. ★正規化は「両側」に掛ける★

★入力側だけ正規化しても意味がない。★ ★データ側も同じ関数を通してから比較する。★

```ts
// ✅ 正しい
foldName(row.facility_name).includes(foldName(query))

// ❌ 間違い（データ側の中点が残るので、結局ヒットしない）
row.facility_name.includes(foldName(query))
```

### D-6. ★fixtures では本件を再現できない★

★`lib/fixtures/` の施設名には記号が1文字も含まれていない★（`lib/fixtures/facilities.ts:6-7` が
「実在の店舗名を含めない」と明記し、`ドン・キホーテ` → `バリューマート` に置換してある）。

★したがってテストは各 `__tests__/query.test.ts` の合成データで組む★。
★`lib/fixtures/` は変更しないこと★（`lib/db/clean-log-mapping.ts:279` に変更禁止の記述がある）。

★合成データには必ず中点入りの名前を含めること★。
★中点が入っていることだけが要件★なので、`ドソ・キホーテ` のような1文字違いの架空名でよい。

---

## 3. 段階

> ★段階ごとにコミットすること。★ ★決定番号は着手直前に自分で実測してから採番すること★
> （`grep -nE "^### D-(0|1)[0-9][0-9]" docs/decisions.md | tail -3`。★本書の起草時点の最終番号は D-128★）。

### 段階1. ★先に、現状の挙動をテストで固定する★

★★`lib/clean-logs/query.ts` の検索には、テストが1件も無い★★。

`lib/clean-logs/__tests__/query.test.ts` の `it` は全5件で、
★`facility_name` / `model` / `serial` の検索を固定しているテストは1件も存在しない★
（2件は日付範囲、1件は `result=problem` の grade 絞り込み）。

★このまま正規化を入れると、間違えても赤くならない。★ ★誰も気づけない。★

**やること**: `lib/clean-logs/__tests__/query.test.ts` に、★いまの挙動をそのまま固定する★テストを足す。

| # | 入力 | いまの期待値 |
|---|---|---|
| 1 | `facility_name` に施設名の一部 | ヒットする |
| 2 | `facility_name` に存在しない語 | 0件 |
| 3 | `model` に `CC1` | ヒットする |
| 4 | ★`model` に `cc1`★ | ★★0件（いまはこうなる）★★ |
| 5 | `serial` にシリアルの一部（大文字含む） | ヒットする |
| 6 | ★`serial` を小文字にしたもの★ | ★★0件（いまはこうなる）★★ |
| 7 | 3つを同時指定 | AND で効く |
| 8 | すべて空文字 | 素通し（全件） |

> ★**#4 と #6 は「いまはこうなる」を固定するテスト**★。★段階3 で期待値が反転する★。
> ★★反転させる時に、テストを消さずに期待値を書き換えること★★ — ★消すと「昔はこうだった」が失われる★。
> ★書き換えたテストには「段階3 で反転させた」とコメントを残すこと★。

**コミット**: `test: 清掃詳細ログの検索の現状挙動をテストで固定する`

---

### 段階2. ★正規化の純粋関数を作る★

**新規**: `lib/format/search-text.ts`

```ts
/** 大文字小文字だけを揃える（記号は残す） */
export function foldPlain(value: string): string

/** 大文字小文字を揃え、さらに記号（中点・長音・空白・ハイフン類）を除く */
export function foldName(value: string): string

/** haystack と needle の両方を foldName に通してから部分一致を見る */
export function includesFoldName(haystack: string, needle: string): boolean

/** haystack と needle の両方を foldPlain に通してから部分一致を見る */
export function includesFoldPlain(haystack: string, needle: string): boolean
```

★`includesXxx()` を用意するのは、呼び出し側が「片側だけ正規化する」間違い（D-5）をできなくするため★。

★関数コメントに書くこと★（全社ルール7）:
- ★なぜ2種類あるのか★ = ★フィールドによって記号の意味が違うから★（D-2 の実例を引く）
- ★どの文字を除くのか★ = D-3 の表
- ★★なぜシリアルと住所に `foldName` を使ってはいけないのか★★ = D-2 の実例（`1-2-3`/`2-3-4` と `PUDU-CC1-0001`）
- ★カナ・全半角・英字カナ読み替えを入れていないこと★（D-4）

**新規**: `lib/format/__tests__/search-text.test.ts`

★期待値表を先に作ること★（`CLAUDE.md` §6-2）。★境界を外さないこと★:

| 入力（needle） | 対象（haystack） | `includesFoldName` | `includesFoldPlain` |
|---|---|---|---|
| `ドンキホーテ` | `ドン・キホーテ戸塚` | ★true★ | false |
| `ドン・キホーテ` | `ドン・キホーテ戸塚` | true | true |
| `戸塚` | `ドン・キホーテ戸塚` | true | true |
| `ドンキ` | `ドン・キホーテ戸塚` | ★true★ | false |
| `ドンキホーテ港南` | `MEGAドン・キホーテ 港南` | ★true★ | false |
| `cc1` | `PUDU CC1` | true | ★true★ |
| `PUDUCC1` | `PUDU CC1` | ★true★ | ★false★ |
| `8111r` | `8111R5628060011` | true | ★true★ |
| ★`23`★ | ★`東京都○○区1-2-3`★ | ★★true（だから住所には使わない）★★ | ★★false★★ |
| ★`10001`★ | ★`PUDU-CC1-0001`★ | ★★true（だからシリアルには使わない）★★ | ★★false★★ |
| 空文字 | 何でも | ★true（素通し）★ | ★true★ |
| `ﾄﾞﾝ` | `ドン・キホーテ戸塚` | ★false（D-4。入れていない）★ | false |
| `どん` | `ドン・キホーテ戸塚` | ★false（D-4）★ | false |
| ★`メガドンキホーテ`★ | ★`MEGAドン・キホーテ 港南`★ | ★★false（D-4。残る穴）★★ | false |

★除く記号1つずつのテストも書くこと★（中点4種・長音・空白3種・ハイフン類も個別に）。

**コミット**: `feat: 検索用の文字列正規化を lib/format に1本作る`

---

### 段階3. ★5系統を1本に寄せる★

★D-2 の表のとおりにフィールドごとの強さを割り当てる★。

| ファイル | 直す行 | フィールド | 使う関数 |
|---|---|---|---|
| `lib/clean-logs/query.ts` | `:117` | `facility_name` | ★`includesFoldName`★ |
| 同上 | `:120` | `model_name` | `includesFoldPlain` |
| 同上 | `:121` | `serial_number` | `includesFoldPlain` |
| `lib/facilities/query.ts` | `:84` | `facility_name` | ★`includesFoldName`★ |
| 同上 | `:85` | `prefecture` | ★`includesFoldName`★ |
| 同上 | `:86` | `address` | ★`includesFoldPlain`★ |
| 同上 | `:89-91` | `serial_number` / `maker` / `model` | `includesFoldPlain` |
| 同上 | `:122` | `facility_name`（個別パラメータ） | ★`includesFoldName`★ |
| 同上 | `:123`付近 | `address` / `serial_number`（個別） | `includesFoldPlain` |
| `lib/schedules/query.ts` | `:100` | `facility_name`（q） | ★`includesFoldName`★ |
| 同上 | `:101` | `serial_number`（q） | `includesFoldPlain` |
| 同上 | `:127` | `facility_name`（個別） | ★`includesFoldName`★ |
| 同上 | `:133`付近 | `serial`（個別） | `includesFoldPlain` |
| `lib/ds-devices/query.ts` | `:65` | `facility_name` | ★`includesFoldName`★ |
| 同上 | `:66` | `serial_number` | `includesFoldPlain` |
| `lib/reports/query.ts` | `:192` | `facility_name` | ★`includesFoldName`★ |
| 同上 | `:202` | `serial_number` | `includesFoldPlain` |

★行番号は起草時点のもの。★ ★着手時に必ず自分で確認してから直すこと★（段階1 でテストを足すと行がずれる）。

★`lib/facilities/query.ts:67-69` の private な `includesFold()` は削除し、新しい関数に置き換える★。

**★ここで段階1 のテストの期待値が反転する★**:

| # | 入力 | 段階1 の期待値 | ★段階3 後の期待値★ |
|---|---|---|---|
| 4 | `model` に `cc1` | 0件 | ★ヒットする★ |
| 6 | `serial` を小文字に | 0件 | ★ヒットする★ |

★他の系統の既存テスト（約49件）は、記号の有無を根拠にした否定アサーションを1件も持たないので、
そのまま通るはず★。★通らないものが出たら、期待値を書き換える前に「なぜ変わったか」を説明できること★。

**★各系統に、記号ゆれのテストを1件ずつ足すこと★**（合成データで。D-6）。
★5系統すべてに足す★ — ★1系統でも漏れると、そこだけ挙動が違うまま残る★。

**コミット**: `fix: 検索の照合を5系統で1本に揃え、施設名の記号ゆれを吸収する`

---

### 段階4. ★API契約（YAML）を直す★ — `CLAUDE.md` §6-1

★検索の意味を変えるので、YAML の description を直す★。

| 箇所 | 直すもの |
|---|---|
| `d-watch-api.yaml:256-267` | `/clean-logs` の `facility_name` / `model` / `serial` |
| `:549-556` | `/reports/facility-performance` |
| `:718-721` | `/ds-devices` の `q` |
| `:848-866` | `/facilities` の `q` と個別3つ |
| `:889-898` | `/schedules` |
| ★`:932-939`★ | ★`/inspections`（未実装）★ — ★実装時に同じ正規化を通すことを description に書き残す★ |

★書く内容★:
- ★施設名は「記号（中点・長音・空白・ハイフン類）を無視した部分一致」★
- ★それ以外（シリアル・機種・住所）は「大文字小文字を区別しない部分一致」★
- ★★なぜ施設名だけ強いのかを1行添える★★（後任が「揃っていない」と思って揃えないように）

★行番号は起草時点のもの★。★指示書AE の段階4 で YAML を触っているので、着手時に自分で確認すること★。

**コミット**: `docs: 検索の照合規則を API 契約に書く`

---

### 段階5. ★指示書AC を改訂する★ — ★★本書を AC より先にやる理由★★

★AC は「絞り込みをSQLに降ろす」指示書★。★施設名の絞り込みが、AC と正面からぶつかる★。

#### ★なぜぶつかるのか★

★「中点を無視して比較する」関数は MariaDB に無い。★ ★用意するには生成カラム＋索引＝スキーマ変更（`CLAUDE.md` §7 で禁止）★。
★生SQLも禁止★（D-122 の ESLint ルールが落とす）。

> ★★「MySQL の ci 照合順序なら中点も無視されるはず」は誤り。実測済み★★
> `'ドン・キホーテ戸塚' LIKE '%ドンキホーテ%'` は
> ★`utf8mb4_unicode_ci` / `utf8mb4_general_ci` / `utf8mb4_0900_ai_ci` の3種すべてで 0★
> （中点入りなら 1）。★この行を消さないこと★ — ★後任が「照合順序でできるはず」と戻ってくるのを防ぐ★。

#### ★解き方★

★施設マスタは小さい★（★fixtures / seed / ローカルDB すべて実測12件★）。だから:

1. ★施設名の照合はアプリ側で施設マスタに対して行う★
2. ★当たった施設IDの配列を `where: { facility_id: { in: [...] } }` に渡す★

★清掃ログ本体（本番想定で `clean_timetables` 約26万行）はSQLで絞ったまま★なので、
★AC の目的（読み込み行数を減らす）は損なわれない★。★むしろ日付だけで絞るより縮む★。

#### ★AC のどこを直すか★

| AC の箇所 | 直すもの |
|---|---|
| `:507-514`（D-1 引数の型） | ★`facility_id: number \| null` → `facility_ids: number[] \| null`★。★`null`＝施設名条件なし / `[]`＝0件ヒット★ を型のコメントに明記 |
| `:558-572`（D-4 Prisma の書き方） | `...(query.facility_ids !== null ? { facility_id: { in: query.facility_ids } } : {})`。★`!== null` で分岐すること★ |
| `:606-612`（D-6 APIルート側） | ★施設名→IDの解決を1ステップ挟む★記述を足す。★解決関数（`resolveFacilityIdsByName()` 等）は AC 段階2 で作る★ |
| `:616-626`（期待値表） | ★#6 を `facility_ids = [A, B]` に差し替え、★`#6b: [] → 0件`★ / `#6c: null → 全件` / `#6d: 記号違いでヒット` を足す★ |
| `:233`（C-1 の表） | 「施設名の部分一致は降ろせるがやらない」→★「施設名はIDに解決してから降ろす。機種/シリアルは据え置き」★ |
| `:859-863`（E-5） | ★`facility_id: { in: [...] }` の件数上限の実測値を書く（下の罠8）★ |
| `:943`（申し送り⑨） | 上記に合わせて書き換え |
| `:935-936`（申し送り） | ★③として `clean_timetables` に `(facility_id, start_at)` の複合索引が無いことを足す★（第9回で TeamWorks に依頼する候補） |

★AC の段階3 / 段階4 / 段階6 は影響なし★。★段階5 は `facilityPerformance()` だけが影響を受ける★
（`reportKpis` / `continuousProblems` / `errorBreakdown` に施設名フィルタは無い）。

#### ★★逆順にしてはいけない理由★★

★AC を先にやると、段階2 で確定した「作法」を正規化のためにもう一度書き換えることになる★。
★段階4・5 がその作法を真似た後なので、修正箇所が3倍に増える★。

**コミット**: `docs: 指示書ACを施設ID解決の形に改訂する`

---

### 段階6. ★記録★

- `docs/decisions.md` に決定を1本（★採番は着手直前に実測★）。書くこと:
  - ★D-2（フィールドごとに強さを変える）の実データ根拠★ — ★これが本書で一番失われやすい★
  - ★D-4（意図的に残した穴）★ — 特に ★「メガドンキホーテ」で引けないのは仕様★
  - ★照合順序では中点を無視できない実測★
  - ★`in: undefined` が全件を返す実測★（罠1）
- `docs/handoff.md` に追記（★先頭の「最新の追記」の番号も直すこと★。過去に番号ズレの事故がある）
- 本書の冒頭表と実行状態表を更新

**コミット**: `docs: 指示書AGの完了を記録する`

---

## 4. ★踏みうる罠★

★「エラーも出ず、テストも通ったまま、結果だけ間違う」型を先に並べる。★

| # | 罠 | 実測 | 対策 |
|---|---|---|---|
| ★1★ | ★★`in: undefined` は全件を返す★★。`ids.length ? ids : undefined` と書くと「0件ヒット」が「全件表示」に化ける | ★実測: `in: undefined` → 生成SQL `WHERE 1=1` → 12件全部★。対して ★`in: []` → `WHERE 1=0` → 0件（安全）★ | ★`number[] \| null` とし、`!== null` で分岐★。★`?.length` で分岐しない★。★期待値表に「`[]` → 0件」を必ず入れる★ |
| ★2★ | ★片側だけ正規化する★（D-5）。データ側の中点が残るので結局ヒットしない | — | ★`includesFoldName()` を使う★。`foldName()` を直接呼ばせない |
| ★3★ | ★シリアル・住所に `foldName` を使ってしまう★ | D-2 の実例（`23` / `10001`） | ★段階2 の期待値表に「だから使わない」ケースを入れてある★。★消さないこと★ |
| ★4★ | ★1系統だけ直す★ | — | ★分裂が3種類から4種類に増えるだけ★。★5系統同時★ |
| ★5★ | ★★照合順序で中点が無視されると思い込む★★ | ★3種の照合順序すべてで 0 を実測★ | ★段階5 に明記済み★ |
| 6 | ★Prisma の `contains` は `%` をエスケープしない★ | ★実測: `contains: "%"` → 12件全部★。JS の `.includes("%")` なら0件 | ★本書の案なら施設名をSQLに渡さないので回避される★。★AC で `contains` を使うなら `%` `_` `\` を明示エスケープ★ |
| ★7★ | ★★論理削除された施設のログが、検索から静かに消える★★ | `prismaSource.facilities()`（`lib/db/index.ts:753-754`）は ★`where: { deleted_at: null }`★。一方 `cleanLogs()` の `include: { facility: true }` には ★deleted_at の条件が無い★ | ★★AC で ID 解決を作るとき、母集合を `deleted_at` で絞らないこと★★ = ★いまの挙動を変えない★。★`db.facilities()` をそのまま流用すると挙動が変わる★ので、解決用の読み取りを別に用意し、★理由をコメントに残す★ |
| 8 | ★施設200件超で実行計画が静かに劣化する★（遅くなるだけでエラーは出ない） | ★ローカル MySQL 8.4 実測 `eq_range_index_dive_limit = 200`★。★MariaDB 10.11 の値は未確認★。★Prisma の `maxBindValues` は mysql で 65,535、超えると自動分割（65,536要素→3クエリを実測）★ | ★現状12件なので当面問題にならない★。★申し送りに「本番で `SHOW VARIABLES LIKE 'eq_range_index_dive_limit'` を実測」と書く★ |
| 9 | ★`USE_FIXTURES` の true/false で件数が変わる★ | AC 禁止事項6（`:884`）が明示的に禁じている | ★fixtures 版も `null`/`[]` を同じ意味で扱う★ |
| ★10★ | ★どの画面からも呼ばれていないパラメータを直し忘れる★ | ★`filterFacilitiesByFields`（`lib/facilities/query.ts:113-132`）と `filterSchedules` の個別パラメータは、どの画面からも送られていない★（API直叩き専用） | ★それでも API 契約上は存在する★。★直さないと「同じAPIのパラメータ間で挙動が違う」状態が新規に生まれる★ |

---

## 5. ★禁止事項★

- ❌ ★`lib/fixtures/` を変更すること★（変更禁止。D-6）
- ❌ ★シリアル・住所に記号除去を適用すること★（D-2）
- ❌ ★1系統だけ直すこと★（D-1）
- ❌ ★カナ・全半角の統一や英字カナ読み替えを、勝手に足すこと★（D-4。★ユーザーが選ばなかった★）
- ❌ ★生SQL★（`$queryRaw` / `$executeRaw` / `*Unsafe` / `Prisma.raw`）— ★ESLint が落とす（D-122）★
- ❌ ★メインDBのスキーマ変更★（生成カラム・索引追加を含む。`CLAUDE.md` §7）
- ❌ ★`any`★ / ★閾値のハードコード★
- ❌ ★`git add -A`★ — ★必ずファイルパスを明示する★
- ❌ ★次のファイルに触ること★: `README.md` / `docs/code-map.md` / `docs/handoff/管理画面_設計検討_20260910.md` / `.claude/**`
- ❌ ★段階1 で足したテストを、段階3 で削除すること★（★期待値を書き換える。消さない★）

---

## 6. 完了条件

- `npx tsc --noEmit` = ★0★
- `npx eslint .` = ★0★
- `npx vitest run` ★全通過★（★着手前 1230件★。増分の内訳を報告すること）
- ★5系統すべてに記号ゆれのテストがあること★
- ★`d-watch-api.yaml` が実装に追随していること★（`CLAUDE.md` §6-1）
- ★指示書AC が改訂されていること★（段階5）
- ★`git status` に、着手前から増えた無関係な変更が無いこと★

---

## 7. 実行状態

★★全段階 完了（2026-09-13）★★。★決定番号は着手直前に実測した★
（`grep -nE "^### D-(0|1)[0-9][0-9]" docs/decisions.md | tail -5` → 最終 `D-128` を確認 → ★`D-129`★ を採番）。

| 段階 | 内容 | 決定番号 | コミット | 状態 |
|---|---|---|---|---|
| 1 | 現状挙動の固定テスト | — | `9d6a5b1` | ★★完了★★（テスト +8） |
| 2 | 正規化の純粋関数 | ★D-129★ | `cd1769f` | ★★完了★★（`lib/format/search-text.ts`。テスト +40） |
| 3 | 5系統を1本に寄せる | ★D-129★ | `f4149c9` | ★★完了★★（テスト +6。★5系統すべてに記号ゆれのテストあり★） |
| 4 | API契約（YAML） | — | `47ca671` | ★★完了★★（★6箇所すべて★。`/inspections` は未実装だが契約として書き残した） |
| 5 | 指示書AC の改訂 | — | `871a5f4` | ★★完了★★（★申し送り番号の注意は下の備考★） |
| 6 | 記録 | ★D-129★ | （本段階のコミット） | ★★完了★★（`docs/decisions.md` D-129 / `docs/handoff.md` ★追記43★ / 本表） |

### ★備考★

- ★★段階5 で指示書AC の申し送り番号が1つずれました★★ — ★改訂で ⑩⑪⑫ を足したため、
  ★★本書 §3 段階5 の表が「`:943`（申し送り⑨）」と書いている項目は、改訂後の AC では★★⑩★★です★★★。
  ★本書の行番号・番号は起草時点のもの★なので、AC 側を見るときは ⑩ を読むこと。
  ★AC 側は改訂済み（`docs/handoff/指示書AC_絞り込みをSQLに降ろす.md` の冒頭「改訂」行に改訂箇所の一覧あり）★
- ★段階1 で固定した「いまはこうなる」2件（`model` に `cc1` → 0件 / `serial` 小文字 → 0件）は、
  段階3 で★★期待値を反転させた。消していない★★★（`lib/clean-logs/__tests__/query.test.ts` に
  「段階3 で反転させた」旨のコメントあり）
- ★完了条件（§6）の実測★: `tsc` 0 / `eslint` 0 / `vitest` ★51ファイル 1284件 全通過★ /
  ★5系統すべてに記号ゆれのテストあり★ / ★YAML 追随済み★ / ★AC 改訂済み★ /
  ★`git status` に着手前から増えた無関係な変更なし★（`README.md` / `docs/code-map.md` /
  `docs/handoff/管理画面_設計検討_20260910.md` は★着手前からあった他作業の変更。触っていない★）
- ★★増分の内訳★★: ★段階1 +8 / 段階2 +40 / 段階3 +6 = **+54**★（1230 → 1284）。★段階4〜6 は増減なし★
