# 指示書AI — 要レスキュー店舗（3条件）と、詳細ログの期間上限

**作成: 2026-09-15 / ブランチ: `develop` / 前提: D-134（`docs/decisions.md` の最終決定番号。★着手時に `grep -nE "^### D-(0|1)[0-9][0-9]" docs/decisions.md | tail -3` で実測し直すこと★）**

---

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

> ```
> D-Watch の実装をお願いします。ブランチは develop です。
>
> ■ 最初に読むもの（この順で、全部）
>   1. CLAUDE.md（全文。特に §4.1 判定基準 / §4.3 端数処理 / §4.5 エスカレーション /
>      §7 禁止事項 / §9 実装規約）
>   2. docs/handoff/指示書AI_要レスキュー店舗と詳細ログの期間上限.md（全文）
>   3. 指示書AI が名前を挙げている既存ファイル（lib/db/clean-log-mapping.ts の
>      ConsecutiveInput 周辺、lib/db/consecutive-source.ts、lib/judge/escalation.ts、
>      lib/judge/kpi.ts、lib/config/index.ts、components/features/dashboard/DashboardScreen.tsx）
>
> ■ やること
>   指示書AI の 段階1 から 段階9 までを、番号順に実行してください。
>   段階を飛ばさないでください。特に 段階3（判定の純粋関数）は
>   「期待値表をテストに落としてから実装する」順序を崩さないでください。
>
> ■ 段階ごとに必ずやること
>   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/dashboard/aggregate.ts / lib/judge/advice.ts / lib/db/prisma.ts
>
> ■ 絶対にやらないこと
>   ・生SQL（$queryRaw / $executeRaw / *Unsafe / Prisma.raw）— ESLint が落とします
>   ・閾値のハードコード — 必ず lib/config/index.ts に置いてください
>   ・メインDBのスキーマ変更・マイグレーション作成
>   ・表示用に丸めた値で判定すること
>   ・新しい .module.css に font-family を書くこと（日本語フォントが落ちて漢字が中華字形になります）
>   ・仕様が不明なところを推測で実装すること
>
> ■ 迷ったら
>   推測で埋めず、手を止めて「ここが決まっていません」と報告してください。
>   指示書AI の §13（実装者へ — 特に踏みやすい落とし穴）に、
>   例外もテスト失敗も出ないまま壊れる箇所を10個挙げてあります。着手前に読んでください。
>
> ■ 動作確認
>   dev server は npm run dev で立ててください（.env.local は既にテストDBを向いています）。
>   ★テストDBのデータは 2026-09-13 で終わっています★。今日の日付で開くと全部0件になるので、
>   /api/dashboard?date=2026-09-13 と ?date=2026-09-12 で確かめてください。
>   期待される結果は指示書AI の §12 に書いてあります。
>
> ■ 終わったら
>   次を報告してください。
>   ・段階ごとのコミットハッシュ
>   ・tsc / lint / test の結果（テスト本数も）
>   ・指示書どおりにできなかったところと、その理由
>   ・判断に迷って自分で決めたところ（あれば全部）
> ```

---

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

ユーザーから新要件が4件来ました（2026-09-15）。★**そのうち、いま着手できる2件だけ**★を扱います。

| 要件 | この指示書で | 理由 |
|---|---|---|
| ★要レスキュー店舗★ | ★**やる（3条件）**★ | 独立している |
| ★詳細ログの期間上限を2年に★ | ★**やる**★ | 設定値1本と検証の変更で済む |
| ★CSVの上限と、出せないことの表示★ | ★**やる**★ | ★上を広げた結果として要る（ユーザー指示 2026-09-15）★ |
| 分類（ホールディングス/屋号/店舗）の復活 | ★やらない★ | ★TeamWorks への確認待ち★（`TeamWorks確認事項_第9回_20260915.md` の 6章・7章） |
| 総清掃面積の新画面 | ★やらない★ | ★分類の絞り込みに乗るため、上が先★ |

★★着手前に必ず `CLAUDE.md` を全文読むこと。★★ 特に §4.1（判定基準）/ §4.3（端数処理）/ §4.5（エスカレーション）/ §7（禁止事項）/ §9（実装規約）。

---

## 1. ★ユーザーが決めたこと（原文と、その帰結）★

### 1-1. 要レスキューの4条件 → ★3条件にする★

ユーザー原文（2026-09-15）:

> ・ダッシュボードの変更
>  要レスキュー店舗をダッシュボード上の詳細ログの上に表示
>  定義は出発失敗3日異常、連続問題レベル3、完了率急落前日比OO％、未実施3日連続
>  当該日の清掃詳細ログの情報を元に表示

★**「出発失敗」と「未実施」は、現在のコードでは同じ判定です**★
（`notExecutedFromHistory()`、`lib/db/clean-log-mapping.ts:152-157`）。

ユーザーに確認したところ「別物」との回答でしたが、★**実データを調べた結果、別物にできませんでした**★。

- `clean_status_histories.started_at` は、テーブル定義書に ★**「清掃スケジュールの清掃開始日時を格納する」**★
  と書かれている（＝ `clean_timetables.start_at` の写し）
- 実測: 本清掃538行すべてで `TIMESTAMPDIFF(MINUTE, t.start_at, h.started_at) = 0`。
  ★30分以上の遅延は0件★
- 実開始時刻が入りそうな `clean_task_reports` は ★0行★
- `robot_instruct_histories` / `section_robot_statuses` も ★0行★
- `clean_statuses.clean_status_trans`（1=清掃指示 で止まる＝出発失敗を表せる唯一の列）は
  ★履歴テーブルに存在しない★ため、過去を遡れない

★**したがって、猶予30分の計算をしようとしても材料がありません。**★
★D-091（「予定＋猶予30分を当方で計算しない」）を覆すかどうか以前の問題です。★

★**ユーザー決定（2026-09-15）: 3条件で先に実装する。**★
TeamWorks から実開始時刻の所在について回答が来たら、4つ目の条件として足す
（照会済み: `TeamWorks確認事項_第9回_20260915.md` 11章）。

**確定した3条件:**

| # | 条件 | 判定 |
|---|---|---|
| ★R-1★ | ★未実施が3日以上連続★（＝出発失敗と同義） | `completion_rate === null` が3スケジュール日以上連続 |
| ★R-2★ | ★連続問題レベル3★ | `escalation_level === "L3"` |
| ★R-3★ | ★完了率が前日比20%以上の急落★ | 後述 |

### 1-2. 急落の閾値と数え方

ユーザー原文:

> 数値は一旦20％を閾値にしてください、ただし変更は可能にしてください

差（ポイント）か比（％）かを確認したところ:

> 比で

★**確定:**★

```
(前回の完了率 − 当日の完了率) ÷ 前回の完了率 ≧ 0.20
```

★**＝ 完了率が前回の8割を切ったら「急落」。**★

★20 は設定値。ハードコード禁止（`CLAUDE.md` §7）。★

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

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

| # | 決定 | 根拠 |
|---|---|---|
| ★A★ | ★「前日」は**カレンダー前日ではなく、前回のスケジュール存在日**★ | `CLAUDE.md` §4.5 / D-026 が「連続はスケジュール存在日の連続」と定めている。急落だけカレンダー日にすると規則が二本立てになる。★実データに1件だけ差の出る例がある — 施設7が 09-07 → 09-09（09-08 は予定なし）。カレンダー前日だと算出不可、前回スケジュール日だと 94.7% → 72.5% で急落として拾う★ |
| ★B★ | ★前回の完了率が **0%** のときは急落としない★ | 0 で割れない。下がりようもない |
| ★C★ | ★前回が**未実施（null）**のときは急落判定から外す★ | 比べる相手が無い。未実施は R-1 で拾う |
| ★D★ | ★当日が**未実施（null）**のときも急落判定から外す★ | 同上。★「未実施を 0% とみなして急落にする」ことはしない★ — `completionRateFromHistory()` が未実施を 0 ではなく `null` にしているのは意図的（`lib/db/clean-log-mapping.ts:168-173`） |
| ★E★ | ★完了率が**上がった**ときは拾わない★ | 「急落」なので低下方向のみ。絶対値で見ない |

### 1-4. 施設に複数ロボットがあるときの完了率

ユーザー確認済み（「それでOK」）:

★**面積で加重する。**★

```
施設の1日の完了率 = Σclean_area ÷ Σtask_area × 100
```

★`CLAUDE.md` §4.2 が「清掃完了率 ＝ Σ清掃面積 ÷ Σ清掃対象面積」と面積ベースを定めているため。★
★★単純平均にしないこと★★ — 1000㎡の区画と100㎡の区画を同じ重みで扱うことになる。

★★注意: これは既存の `completion_rate`（1行＝1清掃エリアごとの率）とは**別の数**です。★★
★混同を防ぐため、名前を変えること★（後述 §5 の `facilityDayRate`）。

### 1-5. 当該日に予定が無い施設の扱い

ユーザー原文:

> 出す、がダッシュボードの割合計算には含めない

★**確定:**★

- ★**当該日に清掃予定が無い施設も、要レスキューには出す**★
  （各施設の「当該日以前で最も新しいスケジュール存在日」の状態で判定する）
- ★★ダッシュボードの数値カード（成功 / 失敗 / 未実施 / 成功率）は一切変更しない★★
  — `lib/dashboard/aggregate.ts` と `app/api/dashboard/route.ts` の `summary` に手を入れない

★この指定は重要です。★ 実データに例があります:

> ★施設7「メガバリューマート緑野店」は 2026-09-12 時点で問題4日連続（L3）だが、2026-09-13 には予定が無い★

★「当該日のログだけ」を母集団にすると、一番出すべき施設が消えます。★

### 1-6. 詳細ログの期間

ユーザー原文:

> ・詳細ログは2年分見れるように
>  31日なのを2年をデフォに、変更可能はそのまま

確認したところ:

> 上限だけ、未指定で開いた場合や、店舗指定のみの場合は上限無し、絞り込むときの上限を2年

★**確定:**★

| 状況 | 上限 |
|---|---|
| `date_from` と `date_to` の**両方が指定されている** | ★**2年**★（今の31日を置き換え） |
| 日付が**指定されていない** | ★**上限なし**★ |
| 店舗（施設名）だけ指定されている | ★**上限なし**★（＝日付が無いので上記と同じ） |
| 画面を開いたときの既定 | ★**今日1日のまま**★（変更しない） |

★★「デフォ」を画面の初期表示のことだと解釈しないこと★★ — 現在の画面の既定は
`DetailLogScreen.tsx:43-44` の `useState(todayJst())` で ★今日1日★ です。31日は既定ではなく上限です。

---

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

| 対象 | 理由 |
|---|---|
| `README.md` | ★別作業が進行中★ |
| `docs/code-map.md` | ★同上★ |
| `docs/handoff/管理画面_設計検討_20260910.md` | ★同上★ |
| `.claude/worktrees/**` | ★別ブランチの作業コピー★ |
| `d-watch-mock.jsx` | ★モックは原型として保存する★（D-120 / D-121 の前例。§8 参照） |
| `lib/dashboard/aggregate.ts` | ★§1-5。数値カードを変えない★ |
| `lib/judge/advice.ts` | ★R1〜R4 のアドバイス生成。要レスキューとは別物★ |
| `lib/db/prisma.ts` | ★着手時点で未コミットの変更が乗っている★。触らない |

★★禁止★★（`CLAUDE.md` §7 と、これまでの決定）:

- ❌ ★`git add -A`★ — 必ずファイルパスを明示する
- ❌ ★生SQL★（`$queryRaw` / `$executeRaw` / `*Unsafe` / `Prisma.raw`）— ESLint が error で落とす（D-122）
- ❌ ★閾値のハードコード★ — 必ず `lib/config/index.ts` に置く
- ❌ ★メインDBのスキーマ変更・マイグレーション作成★
- ❌ ★表示用に丸めた値で判定すること★
- ❌ ★モックに残っている実在の住所・電話番号をそのまま使うこと★
- ❌ ★仕様が不明な箇所を推測で実装すること★ — 止まって確認する

---

## 3. 段階1 — 設定値を足す

### 対象: `lib/config/index.ts`

`ESCALATION_THRESHOLDS`（`:20-25`）の直後に足す。

```ts
/**
 * 完了率の「急落」判定（ユーザー指示 2026-09-15 / 指示書AI）
 *
 * ★比（％）で見る。差（ポイント）ではない★ — ユーザー確定（2026-09-15「比で」）。
 *   (前回の完了率 − 当日の完了率) ÷ 前回の完了率 ≧ dropRatio
 *   ＝ 完了率が前回の (1 - dropRatio) 倍を下回ったら急落。
 *
 * ★差で見ると、元から完了率が低い施設を拾えない★ —
 *   前日30% → 当日18% は「12ポイント低下」だが「40%低下」。
 *   ユーザーは後者を拾う側を選んだ。
 *
 * ★「前回」はカレンダー前日ではなく、前回のスケジュール存在日★
 *   （D-026 / `CLAUDE.md` §4.5 の「連続」の数え方に揃えた。指示書AI 決定A）。
 */
export const COMPLETION_DROP = {
  /** 前回比でこの割合以上下がったら「急落」（0.2 = 20%） */
  dropRatio: 0.2,
} as const;
```

★★`0.2` と書くこと。`20` と書いて後で100で割らないこと★★ — 単位が二重になると、次に値を変える人が
どちらの単位か分からなくなる。

### 検証

```bash
npx tsc --noEmit
```

---

## 4. 段階2 — 連続日数の入力に面積を足す

### 4-1. なぜ要るか

急落判定（R-3）は ★施設×日の面積加重完了率★ を必要とします（§1-4）。
現在の `ConsecutiveInput`（`lib/db/clean-log-mapping.ts:286-301`）は `completion_rate` しか持たず、
★面積を持っていません★。

★★R-1（未実施の連続）と R-2（L3）は、既存の `completion_rate` だけで判定できます★★ —
`completion_rate === null` が「未実施＝出発失敗」と同値であることは、
`lib/db/clean-log-mapping.ts:176-180` の不変条件として既に書かれています。★新しい列は要りません。★

面積が要るのは **R-3 だけ**です。

### 4-2. ★この変更のコストを理解してから着手すること★

`loadConsecutiveInputs()` の docstring（`lib/db/consecutive-source.ts` の `select` の直上）には
こう書かれています。

> ★★`select` の列を増やさないこと★★ — ここに列を足すとそのまま本番
>   （保持2年・約26万行）の転送量になります。

★**この指示書は、その禁を意図的に破ります。**★ 根拠:

- 足すのは `clean_area` / `task_area` の2列（いずれも `double`、8バイト）
- 本番想定 26万行 × 16バイト = ★**約4MB**★
- ★R-3 は施設×日の面積加重を必要とし、率の再合成では代用できない★
  （率の平均は面積加重にならない）

★★docstring を書き換えること★★ — 「増やすな」とだけ書いてあると、次の人が
「なぜ2列だけ増えているのか」を判断できません。★増やしてよい条件と、増やした理由を書く★。

### 4-3. 変更する箇所

**(1) `lib/db/consecutive-source.ts`**

- `ConsecutiveHistoryRow` に `clean_area: number` / `task_area: number` を足す
- 履歴の `select` に `clean_area: true, task_area: true` を足す
  （★`clean_timetables` 側の `select` は増やさない★）
- `consecutiveInputsFromRows()` の `.map()` で `ConsecutiveInput` に詰める。
  ★履歴が無い行は `clean_area: 0` / `task_area: 0`★（`?? 0`）

**(2) `lib/db/clean-log-mapping.ts`**

`ConsecutiveInput` に足す:

```ts
  /**
   * 面積の生値（急落判定の面積加重に使う。指示書AI 段階2）。
   *
   * ★履歴が無い行・未実施の行は 0★（実データでは `clean_error = 1` の行が
   *   `task_area = 0` / `clean_area = 0` になっている）。
   * ★★`completion_rate` を面積から再計算しないこと★★ — `completion_rate` は
   *   1行（＝1清掃エリア）の率で、未実施は `null`。この2つは別の数です。
   */
  clean_area: number;
  task_area: number;
```

**(3) fixtures 側**

`lib/fixtures/` から `ConsecutiveInput` を組み立てている箇所があれば同じ2列を足す。
★無ければ何もしない（探すこと）★。

### 4-4. 既存テストへの影響

`ConsecutiveInput` を組み立てているテストが落ちます。★必須の2列なので全部直す必要があります★。

★★`clean_area` / `task_area` を optional（`?`）にして逃げないこと★★ —
optional にすると、詰め忘れた呼び出し側が `undefined` のまま通り、
★面積加重が静かに 0/0 になって率が `null` に化けます（例外もテスト失敗も出ません）★。

### 検証

```bash
npx tsc --noEmit
npm run lint
npm test
```

★この段階では**テストが落ちたまま**でも構いません★（次の段階で期待値を足すため）。
★ただし「どのテストがなぜ落ちているか」を把握してから次に進むこと。★

---

## 5. 段階3 — 判定を純粋関数にする（★テストが先★）

### 5-1. 置き場所

★**新規ファイル `lib/judge/rescue.ts`**★ と `lib/judge/__tests__/rescue.test.ts`。

★`lib/judge/` に置く理由★: `CLAUDE.md` §9.1 が「判定ロジック（◎○△×、L1〜L3、KPI計算、丸め）」を
`lib/judge/` に置くと定めており、要レスキューはその仲間です。
★DBアクセスを混ぜないこと★（§9.4「判定関数は純粋関数にする」）。

★`vitest.config.ts` の `include` は `lib/**` と `app/**` だけで、`components/**` のテストは走りません★
（D-120 項2）。★判定を画面側に書くと、テストが1本も走りません。★

### 5-2. ★期待値表を先に作ること★

`CLAUDE.md` §6-2 / §9.4:

> 判定ロジックには必ずテストを書く。★期待値表を先に作り、それをテストで固定する★

★★実装より先にこの表をテストに落とすこと。★★

#### 表1: R-1（未実施が3日以上連続）

「スケジュール存在日」を古い順に並べたときの、当該日時点の連続数。

| # | 入力（★は未実施） | 期待 |
|---|---|---|
| 1-1 | `[○]` | ★false★（1日） |
| 1-2 | `[★]` | ★false★（1日） |
| 1-3 | `[★, ★]` | ★false★（2日） |
| 1-4 | ★`[★, ★, ★]`★ | ★★true★★（3日） |
| 1-5 | `[★, ★, ★, ★]` | ★true★（4日以上も拾う。「3日以上」） |
| 1-6 | `[★, ○, ★, ★]` | ★false★（途中で切れて2日） |
| 1-7 | `[★, ★, ★, ○]` | ★false★（★当該日が正常なら、過去に3日連続があっても拾わない★） |
| 1-8 | `[]`（行なし） | ★false★ |
| 1-9 | ★予定のない日を挟む★ `[★(9/7), ★(9/9), ★(9/10)]`（9/8 は予定なし） | ★★true★★（★カレンダー日ではなくスケジュール存在日で数える。D-026★） |
| 1-10 | 同一日に2台、片方だけ未実施 | ★その日は「未実施の日」とする（OR）★。D-109 の A案に揃える |
| 1-11 | 同一日に同一ロボットが2回清掃、片方だけ未実施 | ★同上（1日に畳む）★ |

#### 表2: R-2（L3）

★既存の `judgeEscalationLevel()`（`lib/judge/escalation.ts:52-59`）をそのまま使う。★
★★新しく数え直さないこと★★（D-109 の再発防止「連続日数の算出は1本に集約する」）。

| # | 連続問題日数 | 期待 |
|---|---|---|
| 2-1 | 2 | ★false★（L2） |
| 2-2 | 3 | ★★true★★（L3） |
| 2-3 | 10 | ★true★ |
| 2-4 | 0 | ★false★ |

#### 表3: 面積加重の施設日完了率（`facilityDayRate`）

| # | 入力（clean_area / task_area の組） | 期待 |
|---|---|---|
| 3-1 | `[(900, 1000)]` | ★90★ |
| 3-2 | `[(900, 1000), (100, 1000)]` | ★50★（= 1000/2000）★単純平均も50なので、これだけでは加重を確かめられない★ |
| 3-3 | ★`[(900, 1000), (10, 100)]`★ | ★★82.7…★★（= 910/1100）。★単純平均なら 50 になる★ — ★この1本で加重を固定する★ |
| 3-4 | `[(0, 1000)]` | ★0★（0%で実施した。★`null` ではない★） |
| 3-5 | `[(0, 0)]`（未実施のみ） | ★★null★★（0除算を捏造しない） |
| 3-6 | `[]` | ★null★ |
| 3-7 | `[(0, 0), (900, 1000)]`（未実施1台＋正常1台） | ★90★（= 900/1000。★未実施行は分母に 0 を足すだけ★） |
| 3-8 | `[(500, 0)]`（分母0だが分子あり） | ★★null★★（★∞ や NaN を返さない★） |

★`lib/judge/kpi.ts:36` の `completionRate()` をそのまま使えば 3-5 / 3-6 / 3-8 は自動的に満たされます★
（内部の `rate()` が分母0で `null` を返す）。★自前で割り算を書かないこと。★

#### 表4: R-3（急落）— ★`dropRatio = 0.2` のとき★

| # | 前回 → 当日 | 低下 | 期待 | 根拠 |
|---|---|---|---|---|
| 4-1 | 100 → 80 | ちょうど20% | ★★true★★ | ★境界は「以上」★ |
| 4-2 | 100 → 80.1 | 19.9% | ★false★ | |
| 4-3 | 100 → 79.9 | 20.1% | ★true★ | |
| 4-4 | 30 → 18 | 40% | ★true★ | ★差なら12ポイントで拾えない。比を選んだ意味がここに出る★ |
| 4-5 | 80 → 60 | 25% | ★true★ | |
| 4-6 | 60 → 80 | ★上昇★ | ★★false★★ | ★決定E★ |
| 4-7 | ★0 → 0★ | — | ★★false★★ | ★決定B。0で割らない★ |
| 4-8 | ★0 → 50★ | — | ★false★ | ★決定B★ |
| 4-9 | ★null → 50★（前回が未実施） | — | ★★false★★ | ★決定C★ |
| 4-10 | ★50 → null★（当日が未実施） | — | ★★false★★ | ★決定D。R-1 で拾う★ |
| 4-11 | ★null → null★ | — | ★false★ | |
| 4-12 | 前回が存在しない（初日） | — | ★★false★★ | 比べる相手が無い |
| 4-13 | ★予定のない日を挟む★ 94.7(9/7) → 72.5(9/9) | 23.4% | ★★true★★ | ★決定A。9/8 に予定が無くても 9/7 と比べる★ |

★★4-1 の境界を必ずテストに入れること★★ — `CLAUDE.md` §9.4 は
「90.0 / 90.1 / 70.0 / 70.1」のように★境界の両側を必ず含める★と定めている。

#### 表5: 3条件の合成

| # | R-1 | R-2 | R-3 | 期待 |
|---|---|---|---|---|
| 5-1 | false | false | false | ★要レスキューでない★ |
| 5-2 | true | false | false | ★要レスキュー★（理由: 未実施3日以上） |
| 5-3 | false | true | false | ★要レスキュー★（理由: L3） |
| 5-4 | false | false | true | ★要レスキュー★（理由: 急落） |
| 5-5 | ★true★ | ★true★ | ★true★ | ★要レスキュー。★★理由は3つとも返す★★（1つに絞らない）★ |

★★理由を配列で返すこと★★ — 「なぜレスキューが要るのか」が運用担当の判断材料そのものです。
★1つに丸めると、現場に何を伝えるかが決まりません。★

### 5-3. 返す形

```ts
/** 要レスキューの理由（複数該当したら全部入る） */
export type RescueReason = "not_executed_streak" | "escalation_l3" | "completion_drop";

export interface RescueJudgement {
  /** 3条件のいずれかに当たったか */
  required: boolean;
  /** 当たった理由（★空配列なら required は false★） */
  reasons: RescueReason[];
  /** 表示用の付帯情報。★判定には使わない★ */
  not_executed_days: number;
  consecutive_problem_days: number;
  /** 面積加重。未実施・データなしは null */
  completion_rate: number | null;
  previous_completion_rate: number | null;
  /** 低下の比（0.25 = 25%低下）。算出できないときは null */
  drop_ratio: number | null;
}
```

★★文言（日本語）をこの層で作らないこと★★ — D-111 の決定です。

> 連続問題施設の summary は★事実だけ★を組み立てる（★原因も助言も書かない★）。
> テンプレート文字列のまま返し、数値はフロントが差し込む（EN切替で和文が残るのを避けるため）

★`RescueReason` は識別子（enum）で返し、日本語は画面側の `t()` で作ること。★

---

## 6. 段階4 — データを取る

### 対象: `lib/db/index.ts`

### 6-1. ★入力は `loadConsecutiveInputs()` から取ること★

★★新しい全期間クエリを書かないこと。★★

`lib/db/clean-log-mapping.ts` の `computeConsecutiveProblemDays()` の docstring に明記されています:

> ★入力を作る責務は `loadConsecutiveInputs()`（`lib/db/consecutive-source.ts`）の1本に切り出してある★ —
> ★★`cleanLogs()` / `facilityPerformance()` / `continuousProblems()` はその1本を呼ぶこと★★
> （D-109 / D-103 の再発防止。連続日数を2箇所で別々に計算しない）

★要レスキューも、この1本を通します。★ ★4本目の呼び出し元になります。★

### 6-2. ★遡り幅に注意★

`loadConsecutiveInputs()` は「表示期間 ＋ 遡りぶん」を読みます。要レスキューは:

- R-1（3日連続）には★3スケジュール日ぶん★
- R-2（L3）には★既存の遡り幅がそのまま要る★
- R-3（前回との比較）には★1スケジュール日ぶん★

★既存の遡り幅（表示上限+1 ×7 カレンダー日、既定なら217日）で足ります。★
★★遡り幅を要レスキュー用に別途縮めないこと★★ — R-2 が L3 を取りこぼします。

### 6-3. ★当該日に予定が無い施設の扱い（§1-5）★

```
各施設について、「当該日以前で最も新しいスケジュール存在日」を求め、
その日の状態で3条件を判定する。
```

★★「当該日のスケジュール存在日だけ」で絞らないこと★★ — 施設7の例（09-12 に L3、09-13 は予定なし）が
消えます。

★実装の目安★: `loadConsecutiveInputs()` の返す `ConsecutiveInput[]` を施設ごとに分け、
`scheduled_at` の日付が当該日以下のものだけを残し、その中の最終日を「評価日」とする。

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

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

★★fixtures 側を `notImplemented` にしないこと★★ — ダッシュボードは `USE_FIXTURES=true` でも開きます。
★fixtures で画面が落ちると、実装した本人が最初に踏みます。★

★fixtures 側は `lib/fixtures/` の既存データから同じ判定を通すこと★（`CLAUDE.md` §9.5:
「判定ロジックは切替の外側に置く」）。★fixtures 用に別の判定を書かない。★

### 6-5. 並び順と件数

★契約に明記すること★（D-052 / D-055 の前例: ソート順は契約に書く）。

- 並び: ★理由の重さ順ではなく、**連続問題日数の降順 → 施設ID昇順**★
  （★安定した順序にすること★。同値のときに順番が揺れると、毎朝見る人が混乱する）
- 件数: ★上限を設ける★。既存の詳細ログ（コンパクト版）が5件なので★同じ5件★に揃える。
  ★★`lib/config/index.ts` に置くこと★★（ハードコード禁止）

> ### ★★訂正（2026-09-15。指示書AJ 段階1 で撤回）★★
>
> ★上の「並び＝連続問題日数の降順」「件数＝上限5件」の2つは、指示書AJ で撤回しました。★
> この組み合わせで ★急落だけで該当する施設が構造的に一覧から落ちていました★
> （急落は「昨日まで正常 → 今日落ちた」なので連続日数が1〜2にしかならず、
> 連続日数の降順で並べると必ず L3 の後ろに来る。実データで7件中2件が黙って消えていた）。
> ★これは監督（指示書AI の作成者）の指定の誤りであり、実装者に落ち度はありません。★
>
> 現在の正:
> - 並び = ★`reasons` の件数の降順 → `consecutive_problem_days` の降順 → `facility_id` の昇順★
> - 件数 = ★API に上限を設けない★。初期表示5件＋「もっと表示」は★画面側の責務★
>   （`RESCUE_INITIAL_VISIBLE`。旧 `RESCUE_LIST_LIMIT` の改名）
>
> 詳細は `docs/handoff/指示書AJ_要レスキューの見せ方とサイドバーのスクロール.md` §2 / §4。

---

## 7. 段階5 — API契約と、ルート

### 7-1. ★YAML を先に直すこと★

`CLAUDE.md` §6-1:

> ★API契約を変えたくなったら、先に `d-watch-api.yaml` を直す。★ 実装だけ変えてYAMLと乖離させない

### 7-2. どこに生やすか

★**`GET /dashboard` のレスポンスに `rescue_required` を足す。**★ 新規エンドポイントは作らない。

★理由★: `security_banner` と同じ「ダッシュボード専用の配列」で、形が揃う。
画面の往復も増えない。

★★注意★★: `d-watch-api.yaml:170-172` に

> コンパクト版清掃詳細ログは本APIには含めない。
> `GET /clean-logs?result=problem&date_from=当日` を再利用すること

とあります。★これは「既存の一覧APIで足りるものを二重に作るな」という趣旨★であり、
要レスキューは `CleanLogItem` だけでは組み立てられない（前日比も未実施連続日数も入っていない）ので
抵触しません。★ただし、その旨を description に書き添えること★ — 次に読む人が同じ疑問を持ちます。

### 7-3. スキーマ案

```yaml
                  rescue_required:
                    type: array
                    description: |
                      要レスキュー店舗。指定日時点で3条件のいずれかに当たる施設。
                      ★指定日に清掃予定が無い施設も含む★ — 各施設の「指定日以前で最も新しい
                      スケジュール存在日」の状態で判定する(指示書AI)。
                      ★summary の数値カード(success/failed/not_executed/success_rate)には
                      影響しない★ — 予定の無い施設を分母に入れないため(ユーザー指示 2026-09-15)。
                      並びは consecutive_problem_days の降順、同値なら facility_id の昇順。
                      件数は RESCUE_LIST_LIMIT 件まで。
                    items:
                      type: object
                      properties:
                        facility_id:   { type: integer, example: 7 }
                        facility_name: { type: string,  example: "メガバリューマート緑野店" }
                        evaluated_on:  { type: string, format: date, example: "2026-09-12" }
                        reasons:
                          type: array
                          description: |
                            当たった条件。★複数該当したら全部入る★。
                            文言はクライアント側で作る(D-111。EN切替で和文が残るのを避けるため)。
                          items:
                            type: string
                            enum: [not_executed_streak, escalation_l3, completion_drop]
                        not_executed_days:        { type: integer, example: 3 }
                        consecutive_problem_days: { type: integer, example: 4 }
                        completion_rate:          { type: number, nullable: true, example: 8.5 }
                        previous_completion_rate: { type: number, nullable: true, example: 73.4 }
                        drop_ratio:               { type: number, nullable: true, example: 0.884 }
                      required: [facility_id, facility_name, evaluated_on, reasons]
```

★`completion_rate` は**丸めない生値**を返す★（`CLAUDE.md` §9.3「数値の丸めは表示層でのみ行う」）。

### 7-4. ルート

`app/api/dashboard/route.ts` の `body`（`:109-113`）に1キー足す。

★★`summary` / `schedule_progress` / `security_banner` の中身を変えないこと。★★

★型★: `lib/types/index.ts` の `DashboardResponse` に足す。★YAML のスキーマ名と揃える★（§9.2）。

---

## 8. 段階6 — ダッシュボードに出す

### 8-1. 入れる位置

`components/features/dashboard/DashboardScreen.tsx`:

```
:97-125  数値カード4枚のグリッド（</div> が :125）
           ★★ここに入れる★★
:127     <div className={styles.card}>   ← 清掃詳細ログ（コンパクト版）
```

★**`:125` の `</div>` と `:127` の `<div>` の間**★。ユーザー指定（「詳細ログの上に表示」）どおり。

### 8-2. ★モックに対応する画面がありません★

実測: `d-watch-mock.jsx` に「要レスキュー」「レスキュー」のヒット ★0件★。

`CLAUDE.md` §5 は「モックが画面レイアウト・文言・遷移の正」と定めています。★前例が2件あります★:

| 決定 | 何をしたか |
|---|---|
| ★D-120★ | モックに無いページ送りUIを新規に作った。「本決定は §5 の例外」と明記し、★ユーザーの原文を決定に転記★ |
| ★D-121★ | モックと違う色にした。同上＋★コード側にも理由コメントを残した★ |

★**同じ作法を踏襲すること:**★

1. ★`d-watch-mock.jsx` は1文字も書き換えない★
2. `docs/decisions.md` に ★「§5 の例外。ユーザー指示（2026-09-15）に基づく」★ と記録し、
   ★指示の原文を引用する★
3. ★`DashboardScreen.tsx` の該当ブロックの直上に、理由コメントを残す★
   — ★★書かないと、次にモックと突き合わせた人が「余計なものがある」と判断して消します★★

### 8-3. 表示の作法

- 色は ★`app/globals.css` の CSS変数★を参照する。★JS 側で色を持たない★
  （`ReportsScreen.module.css:1-5` の作法）。★新しい色を発明しない★
- ★`font-family` を新しい `.module.css` で宣言しないこと★ — 宣言すると和文フォントが落ちて
  ★中国語フォントにフォールバックし、漢字が中華字形になります★（`CLAUDE.md` §0-3）
- 文言は `useTheme()` の `t()` を通す。★JP/EN の器を壊さない★（§9.6）
- ★`reasons` から日本語を作るのは**ここ**★。段階3 の純粋関数は識別子だけを返す（D-111）
- ★0件のときは、ブロックごと出さない★（「要レスキューの店舗はありません」と出すか、
  ブロックを隠すかは実装者判断。★ただしどちらかに決めて、理由をコメントに書くこと★）

---

## 9. 段階7 — 詳細ログの期間上限を2年にする

★★段階6 までとは別のコミットにすること。★★
D-120 項6 の教訓: ★2つを同じコミットに入れると、どちらが壊したのか切り分けられない★。

### 9-1. 変える箇所

| # | ファイル:行 | 現状 | 変更 |
|---|---|---|---|
| 1 | `lib/config/index.ts:266` | `MAX_DATE_RANGE_DAYS = 31` | ★731★（2年。うるう年を含みうるので 730 ではなく 731） |
| 2 | `lib/clean-logs/query.ts:86-92` | 超過で422 | ★そのまま。定数を読んでいるので自動的に追従する★ |
| 3 | `components/features/detail-log/DetailLogScreen.tsx:129-132` | 画面側の先回り検証 | ★そのまま★ |
| 4 | `components/features/detail-log/DetailLogScreen.tsx:43-44` | `useState(todayJst())` | ★★変更しない★★（既定は今日1日のまま。§1-6） |
| 5 | `d-watch-api.yaml:253` | 「from-toは最大31日間、超過は422」 | ★文言を2年に★ |
| 6 | `d-watch-api.yaml:1251` | `example: "date_from〜date_toは31日以内で指定してください"` | ★同上★ |

★★定数名を変えないこと★★（`MAX_DATE_RANGE_DAYS`）。★値だけ変える。★

### 9-2. ★「日付未指定なら上限なし」は既にそうなっています★

`lib/clean-logs/query.ts:78-94` の検証は ★`date_from` と `date_to` の両方があるときだけ★走ります。
片方でも無ければ範囲の検証をしません。★**したがって「未指定なら上限なし」は追加実装が不要です。**★

★確認だけすること★ — テストで固定されているかを見て、無ければ足す:

| # | 入力 | 期待 |
|---|---|---|
| 9-1 | 両方指定・731日 | ★200★ |
| 9-2 | 両方指定・732日 | ★422★ |
| 9-3 | ★`date_from` のみ★ | ★200（上限の検証をしない）★ |
| 9-4 | ★`date_to` のみ★ | ★200★ |
| 9-5 | ★両方なし・`facility_name` のみ★ | ★200★ |

### 9-3. ★既存テストの「31日」を直す★

`MAX_DATE_RANGE_DAYS` を直接 import しているテストは追従しますが、
★`31` や `"31日以内"` を直書きしているテストは落ちます★。全部探して直すこと。

```bash
grep -rn "31日\|MAX_DATE_RANGE_DAYS" --include=*.ts --include=*.tsx --include=*.yaml .
```

### 9-4. ★★この変更で起きること（申し送りに必ず書くこと）★★

★索引が入るまで、2年を選んだときは重くなります。★ 実測に基づく見込み:

| | 現状（31日） | 2年（外挿。施設200件） |
|---|---|---|
| 主クエリが読む行 | 約1万1千行 | ★約25万行★ |
| `clean_status_histories` の走査 | 1塊 | ★254塊 × 全表走査 ≒ 6,430万行★ |
| CSVエクスポート | 55KB / 0.7秒 | ★約28MB / 約6分（下限）★ |

★★CSVは nginx の既定タイムアウト（60秒）を超えるため、2年分は落とせません（504）。★★
★これは索引だけでは直りません★ — `LIMIT` がSQLに降りていないこと、
`ORDER BY start_at DESC, id ASC` の向きが混在していて `filesort` が消えないことが原因です。

★★この問題は段階8 で「押す前に分かる」形にします。★★

★★依頼済み★★: 索引7件（`TeamWorks確認事項_第9回_20260915.md` 1章）。
★★別課題★★: `LIMIT` の押し下げと並び順の変更（D-131 決定D-4 の前提が崩れたため。
★同時にやらないこと★ — D-131 申し送り⑤が「ページ番号をURLに持たせる件と同時にやるな」と
書いているのと同じ理由で、切り分けができなくなる）。

---

## 10. 段階8 — ★CSVに上限を設け、出せないことを押す前に伝える★

★★ユーザー指示（2026-09-15。原文）★★:

> あとCSVで出力できる期間の限界が来たらこの期間だとCSV出力できませんとわかるようにしてください

★段階7 で期間の上限を2年に広げた結果、CSVは現実的に落とせない大きさになりえます。★
★「押してから6分待たされて 504 が返る」のが最悪の形です。★ ★押す前に分かるようにします。★

### 10-1. ★上限は「日数」ではなく「行数」で決めること★

★★日数で決めないこと。★★ 理由:

| 絞り込み | 2年ぶんの行数（外挿） | 落とせるか |
|---|---|---|
| ★1店舗だけ★ | ★約1,300行★ | ★余裕で落とせる★ |
| 全店舗（施設200件） | ★約25万行★ | ★落とせない★ |

★同じ「2年」でも、絞り込み次第で200倍違います。★
★日数で切ると、1店舗の2年ぶん（軽い）まで巻き添えで禁止することになります。★

★★ユーザーは「店舗指定のみなら上限なし」と明示しています（§1-6）。★★
★行数で決めれば、その指定と自然に両立します。★

### 10-2. 設定値

`lib/config/index.ts` の `MAX_DATE_RANGE_DAYS` の直後に足す。

```ts
/**
 * CSVエクスポートの行数上限（ユーザー指示 2026-09-15 / 指示書AI 段階8）
 *
 * ★日数ではなく行数で決めている★ — 同じ2年でも、1店舗なら約1,300行、
 *   全店舗（施設200件）なら約25万行で、200倍違うため（指示書AI 10-1）。
 *
 * ★★この値は暫定です。実データと索引が入ったら測り直すこと★★ —
 *   現在の根拠は「31日・502行で0.73秒」という実測1点からの外挿にすぎない。
 *   ★DB側は塊ごとの全表走査になるので、行数に対して二乗で効く★
 *   （`clean_status_histories.clean_timetable_id` に索引が無いため。第9回 1-2 で依頼中）。
 *   20,000行なら 20塊 × 20,000行 = 40万行の走査で収まる見込み。
 *
 * ★CSVの大きさの目安★: 1行あたり 111 バイト（実測。BOM・CRLF・10列）。
 *   20,000行 ≒ 2.2MB。
 */
export const MAX_CSV_ROWS = 20_000;
```

### 10-3. サーバ側 — `app/api/clean-logs/export/route.ts`

★絞り込みを適用したあと、CSVを組み立てる**前**に行数を見る。★

- 行数が `MAX_CSV_ROWS` 以下 → これまでどおりCSVを返す
- 超える → ★**422**★ を返す（`{ error: { code, message, details } }`。`CLAUDE.md` §9.3）

```
message: "この期間はCSVで出力できません。対象が{n}件で、上限の{max}件を超えています。期間を短くするか、店舗で絞り込んでください。"
details: { total: n, max: MAX_CSV_ROWS }
```

★★500 ではなく 422 にすること★★ — 利用者の指定が上限を超えているという話で、
サーバの不具合ではありません。★`details` に実数を入れること★ — 「あと何件減らせばよいか」が分かります。

★★CSVを組み立ててから長さで判断しないこと★★ — 25万行ぶんの文字列を作ってから捨てるのでは、
★メモリを使い切る危険が残ったまま★です。判断は組み立ての前。

★`d-watch-api.yaml` の `/clean-logs/export` に 422 の宣言を足すこと★（先にYAML。§6-1）。

### 10-4. 画面側 — ★★押す前に分かるようにする（ここが本題）★★

`components/features/detail-log/DetailLogScreen.tsx`

★★画面は既に件数を持っています。★★ 一覧を取ったときの `meta.total` です。
★**CSVのために問い合わせを増やす必要はありません。**★

- `meta.total > MAX_CSV_ROWS` のとき:
  - ★CSVエクスポートのボタンを**押せなくする**★（`disabled`）
  - ★ボタンの近くに理由を出す★:
    「★この期間だとCSV出力できません★（{n}件／上限{max}件）。期間を短くするか、店舗で絞り込んでください。」
- 上限以内のとき: これまでどおり

★★理由を出さずに `disabled` だけにしないこと★★ —
★押せないボタンは、理由が書いていないと不具合と区別がつきません★（D-053 がUIを削除した理由と同じ）。

★★文言は `t()` を通すこと★★（JP/EN の器。§9.6）。★数値は画面側で差し込む★（D-111）。

★★サーバ側の422 を消さないこと★★ — 画面で止めても、APIを直接叩く経路が残ります。
★画面は「親切」、サーバは「歯止め」で、役割が違います。★

### 10-5. 期待値表

| # | 状況 | サーバ | 画面 |
|---|---|---|---|
| 8-1 | 0件 | ★200（ヘッダ行だけのCSV）★ | ボタン有効 |
| 8-2 | 19,999件 | ★200★ | ボタン有効 |
| 8-3 | ★ちょうど 20,000件★ | ★★200★★ | ★ボタン有効★（★「上限」は含む★） |
| 8-4 | ★20,001件★ | ★★422★★ | ★★ボタン無効＋理由の表示★★ |
| 8-5 | 1店舗・2年・1,300件 | ★200★ | ★ボタン有効★（★期間が長くても行数が少なければ出せる★） |
| 8-6 | 日付未指定・全店舗 | ★件数次第★ | ★同上★ |

★★8-3 と 8-4 の両方を必ずテストに入れること★★ — 境界の片側だけだと、
`>` と `>=` の取り違えが通ってしまいます（`CLAUDE.md` §9.4）。

### 10-6. ★この段階も独立したコミットにすること★

段階6（ダッシュボード）・段階7（期間上限）・段階8（CSV上限）は、★3つとも別のコミット★。

---

## 11. 段階9 — 記録

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

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

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

★★`docs/decisions.md` は番号順に並んでいません。★★ 末尾を見て最大値を判断しないこと。

書くこと:

- 要レスキューの3条件と、★4つ目を落とした理由（実開始時刻がDBに無い）★
- ★急落を「比」にした理由と、20% の出所（ユーザー原文を引用）★
- ★監督が決めた5件（決定A〜E）とその根拠★
- ★面積加重にした理由★
- ★モックに無いUIを作ったこと（★`CLAUDE.md` §5 の例外★。D-120 / D-121 と同じ扱い）。ユーザー原文を転記★
- ★`select` の列を増やした理由と、そのコスト（約4MB）★
- 期間上限を2年にしたこと。★既定は今日1日のままであること★
- ★CSVの上限を**日数ではなく行数**にした理由（同じ2年でも絞り込み次第で200倍違う）★と、
  ★`MAX_CSV_ROWS = 20_000` が**実測1点からの外挿にすぎない暫定値**であること★
- ★★やらなかったこと★★: 分類の復活・総清掃面積（TeamWorks 待ち）

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

- ★TeamWorks への索引依頼7件が未回答であること★
- ★★`MAX_CSV_ROWS` を実データで測り直すこと★★ — ★索引が入ると通る行数が変わる★。
  ★暫定値のまま放置しない★
- ★`LIMIT` の押し下げと並び順の変更が別課題として残ること★
- ★実開始時刻の回答が来たら、4つ目の条件を足すこと★

---

## 12. 各段階の検証

★★段階ごとにコミットすること。★★ ★`git add -A` を使わず、ファイルパスを明示する。★

```bash
npx tsc --noEmit     # 0 であること
npm run lint         # 0 であること
npm test             # 全パス
```

★★「テストが通った」を安全の証明にしないこと★★ — この指示書で扱う3条件は、
★ローカルのデータでは1件も発火しません★（実測: 最終データ日 2026-09-13 を当該日としたとき
R-1 は0件。R-2 は4件、R-3 は4件）。
★テストが通ることと、本番で正しく動くことは別です。★

### 実データでの確認（★dev server は `preview_start` で立てること。`npm run dev` を Bash で叩かない★）

`.env.local` は既にテストDB（`133.125.238.144:13306 / clean_watch`）を向いており、
`USE_FIXTURES=false` です。

★★テストDBのデータは 2026-09-13 で終わっています。★★
★今日の日付で開くと全条件が0件になります。★ 確認するときは日付を指定すること:

```
/api/dashboard?date=2026-09-13   → R-2 が4件、R-3 が4件出るはず
/api/dashboard?date=2026-09-12   → R-1 が1件（施設6）、R-2 が3件出るはず
```

> ### ★★訂正（2026-09-15。指示書AJ 段階3）★★
>
> ★上の期待値には誤りがあります。★ ★元の数字は消していません★ —
> ★次に読む人が、この指示書を信じて実装の数を合わせにいくのを防ぐため★です。
> ★★実装は正しく、誤っていたのは監督が書いたこの期待値のほうです。★★
>
> | 上の記述 | 訂正 |
> |---|---|
> | `?date=2026-09-13` → ★「R-2 が4件」★ | ★★誤り。正しくは5件★★ — ★施設7を数え漏らしていました★（09-13 に予定が無いが、09-12 評価で出る施設。§1-5 でわざわざ「当該日に予定が無い施設も出す」と指定したのは、まさにこの施設のためでした） |
> | `?date=2026-09-13` → 「R-3 が4件」 | ★4件で正しい★。★ただし当時は上限5件だったため、API には2件しか出ていませんでした★（施設1・施設10 が上限で切られていた。指示書AJ §1-1） |
>
> ★★実測（2026-09-15。上限撤廃後の `develop`）★★:
>
> ```
> ?date=2026-09-13  rescue_required 7件
>   施設9  [escalation_l3, completion_drop] 連続6  施設6  [escalation_l3, completion_drop] 連続4
>   施設7  [escalation_l3] 連続4（evaluated_on=2026-09-12）
>   施設3  [escalation_l3] 連続3          施設5  [escalation_l3] 連続3
>   施設10 [completion_drop] 連続2        施設1  [completion_drop] 連続1
>   → R-2（escalation_l3）は★5件★、R-3（completion_drop）は★4件★、R-1 は 0 件
>
> ?date=2026-09-12  rescue_required 7件
>   施設6 [not_executed_streak, escalation_l3] / 施設9 施設7 [escalation_l3]
>   施設2 施設4 施設8 施設10 [completion_drop]
>   → R-1 は★1件（施設6）★で記述どおり。R-2 は★3件★で記述どおり
> ```

★★施設7が 2026-09-13 の結果に含まれることを必ず確かめること★★ —
★09-13 に予定が無いのに出る★のが、§1-5 の指定どおりに実装できている証拠です。
★出なければ、当該日で絞ってしまっています。★

---

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

| # | 落とし穴 | どうなるか |
|---|---|---|
| ★1★ | ★`completion_rate` を面積から再計算する★ | 1行の率（エリア単位）と施設日の率（面積加重）が混ざる。★数字が微妙にずれるだけで、例外もテスト失敗も出ない★ |
| ★2★ | ★`ConsecutiveInput` の新しい2列を optional にする★ | 詰め忘れが `undefined` のまま通り、★面積加重が 0/0 → `null` に化ける★ |
| ★3★ | ★当該日で母集団を絞る★ | ★予定の無い日のL3施設が消える★（施設7の例） |
| ★4★ | ★急落の閾値を `20` と書く★ | 後で 0.2 と 20 のどちらの単位か分からなくなる。★`0.2` で統一★ |
| ★5★ | ★未実施を 0% とみなす★ | 前日未実施の翌日が全部「大幅上昇」扱いになる。★`null` のまま扱う★ |
| ★6★ | ★連続日数を要レスキュー用に数え直す★ | D-109 が潰した「2箇所で別々に計算する」に戻る |
| ★7★ | ★遡り幅を縮める★ | ★L3 が L1 に化ける★（例外もテスト失敗も出ない） |
| ★8★ | ★日本語を判定層で作る★ | EN切替で和文が残る（D-111） |
| ★9★ | ★新しい `.module.css` に `font-family` を書く★ | ★漢字が中華字形になる★ |
| ★10★ | ★段階6 と段階7 を同じコミットにする★ | 壊れたときに切り分けられない |

---

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

| 項目 | なぜ |
|---|---|
| ★分類（ホールディングス/屋号/店舗）の復活★ | TeamWorks 確認待ち（第9回 6章・7章）。★`/reports/kpi` が2顧客で500を返す件も、これとセットでしか直せない★ |
| ★総清掃面積の新画面★ | 分類の絞り込みに乗るため、上が先 |
| ★`LIMIT` の SQL 押し下げ★ | D-131 決定D-4 の作り直し。★別課題★ |
| ★`ORDER BY` の向き揃え★ | ★レスポンスの並び順が変わるのでユーザー確認が要る★ |
| ★全ルートへの 5xx 宣言★ | 既に別課題として記録済み |
| ★出発失敗を未実施と別に判定する★ | ★データがDBに無い★（第9回 11章で照会中） |
| ★点検記録2本★ | ユーザーが保留中 |
