# 指示書BG — 予定外タスクを集計と表示に含める（2026-10-29）

**担当**: Claude Code（サブエージェント3本）。監督がレビューする。
**前提**: develop の `f134ac9` 以降。予定外の判定は `lib/db/timetable-link.ts` の `scheduledTimetableId()`（`clean_timetable_id` が 0 か NULL なら予定外）。

---

## 0. ユーザーの指示（2026-10-29。原文）

- 「エラー一覧以外も、予定外タスクを計算して出すようにしてください / ただし、プリチェックは除いてです」
- 監督の案（画面ごとの扱い）への回答:
  - 「清掃詳細ログは事前チェックは出す必要なないです」
  - 「ダッシュボード、連続日数・要レスキュー、清掃エリア、エラー内訳はそれでOK」
  - 「割合はA案で」（A = 予定外タスクを「予定1回・出発1回」として予定回数にも足す）
  - 「サマリは変えて貰う予定です」（確定期間は TeamWorks のサマリをそのまま読むので、アプリ側は変えない）
  - 「上記の計算や表示は全て事前チェックは抜いてください」

### 監督の解釈（ユーザーに報告済みとすること）

- 予定外タスクには事前チェックの回が無い → 予定外の行の事前チェック2列は `null`（画面「-」）。予定のある行の事前チェック列は今のまま
- 事前チェックの回（`precheck ≠ 0`）に紐づくエラー・行は、どの集計にも入れない（今と同じ）
- ステージングの実データ（2026-10-29・先方投入）: 清掃結果15件のうち9件、エラー18件のうち13件が予定外

## 1. 予定外タスクの「1回」の定義

| 項目 | 予定のある清掃 | 予定外タスク |
|---|---|---|
| 起点 | 予定の行（`clean_timetables` / 履歴の `precheck = 0`） | `clean_task_reports` の行（`scheduledTimetableId()` が null） |
| 日付・期間の振り分け | 予定の `start_at`（JST） | 清掃結果の `started_at`（JST）。★予定外は実際に始めた時刻（CLAUDE.md §4.7）★ |
| 出発 | 清掃結果の行があるか | ★常に出発済み★（行があるので） |
| 未実施・結果待ち | ありうる | ★ありえない★ |
| 1回ごとの完了率 | `runCompletionRate()` | 同じ `runCompletionRate()` |
| ◎○△× | 完了率で判定 | 同じ |
| 事前チェック | 1回目・2回目の列 | ★無し（`null`）★ |
| エラー | 予定の id に紐づく `robot_error_reports` | `clean_timetable_id` が 0 / NULL の `robot_error_reports`。期間は `errored_at ?? created_at`（`detectedAtFromError()`）の JST の日付で振り分ける。★種類（`error_type`）では絞らない★（予定のある清掃と同じ） |
| 施設・清掃エリア | 予定の行の値 | 清掃結果の `facility_id` / `section_id` |
| 削除済みの施設・清掃エリア | 除く | 同じく除く |

## 2. 画面ごとの変更

### 2-1. 清掃詳細ログ（`GET /clean-logs`・CSV・`/clean-logs/{id}/advice`・`/areas`）

- 予定外タスクを1件1行で出す。並び順・期間の絞り込みは `scheduled_at`（予定外は `started_at`）で今と同じ規則
- `CleanLogItem` に追加（★YAML を先に直す★）:
  - `unscheduled: boolean`（予定外なら true）
  - `row_key: string`（一意キー。予定のある行 `"tt-<予定id>"`、予定外 `"rp-<report_id>"`）。画面の React の key と展開状態はこれを使う
  - `id`: 予定のある行は今のまま（予定の id）。★予定外は `null`★（`report_id` は19桁で安全な整数を超えるため数値の id にしない）。`report_id: string | null` も足す
- 予定外の行: `scheduled_at` = `started_at`、`departed` = true、`result_pending` = false、`task_status` = `status`、事前チェック2列とその分数は `null`
- 画面: 「予定」の列に時刻の代わりに「予定外」（`t()` を通す。EN "Unscheduled"）。日付は `started_at` の日付
- ★事前チェックの列は残す★（ユーザー回答 2026-10-29「1で進めてください」）。予定のある行は今のまま、予定外の行は「-」（null）。
  ユーザーの「事前チェックは除いて／出す必要ない」は ★「事前チェックを別の1件として数えない」★ の意味だった（行の中の列の話ではない）。
  監督は一度「列そのものを外す」と読み違えて担当Aに伝え、同日取り消した（経緯の記録）
- アドバイス・清掃エリア別（`/{id}/advice`・`/{id}/areas`）は予定の id が要るので ★予定外の行では出さない★（▼ を出さない。判定列の位置合わせ（`8d4a72e`）は見えない ▼ で保つ）
- 連続日数・L1〜L3 は予定外の行も施設の日ごとの結果に入れて数える（2-3）

### 2-2. ダッシュボード

- 清掃結果（成功・失敗・未実施・結果待ち・成功率）: 予定外の行も対象。直近モードの「清掃エリアごとの直近1件」の候補にも入れる（開始＝`started_at`、終了＝`ended_at`。終了が `null` なら開始で代用）
- 予定外は成功（◎○）か失敗（△×）のどちらか。未実施・結果待ちにはならない
- 進み具合（A案）: 対象日に始まった予定外タスクを、予定件数にも出発件数にも1件ずつ足す
- `problem_logs` も予定外の △× を含む

### 2-3. 連続日数・要レスキュー・連続問題（レポート）

- 予定外の行も、施設のその日の結果に入れる。問題日 ＝ その日の行のどれかが「完了率 70 未満か未実施」（今と同じ OR）
- 予定外だけがある日も「予定がある日」として数える（連続を作る日になる）
- 要レスキューの「未実施3日連続」には影響しない（予定外は未実施にならない）。完了率の急落の日の平均には予定外も入る

### 2-4. レポート（今の期間。確定期間はサマリのまま）

A案。`aggregateCleanStatsByFacility()` などで、予定外タスクを次のように足す:

| 指標 | 予定外タスク1件の寄与 |
|---|---|
| 予定回数（`planned`） | +1 |
| 開始できた回数（`executed`） | +1 |
| 正常終了（`completed`、`status = 4`） | `status = 4` なら +1 |
| 清掃完了率 | 1回ごとの完了率を合計と件数に入れる |
| エラー発生回数 | 予定外のエラー（1 の定義）を期間内の件数だけ足す |

- 施設別の成績・前期比も同じ集計を使う
- エラー内訳（今の期間）にも予定外のエラーを足す。★エラー内訳の合計 ＝ エラー発生率の分子★を保つ（`aa4292d` の不変条件）
- ★確定期間は変えない★（TeamWorks のサマリ。ユーザー「サマリは変えて貰う予定」）

### 2-5. 清掃エリア（総清掃面積）

- 実施回数・完了率に予定外の行を入れる（面積は今も入っている）
- 重複の番人（1つの予定に2件以上）は予定のある行だけのまま

## 3. 期待値表（先に固める。監督が書いた。テストはこれをそのまま使う）

前提: 今 = 2026-10-29 10:00 JST、`RESULT_WAIT_MINUTES` = 120。施設 F1・清掃エリア S1。

### 表U1 — 1件の扱い

| # | 行 | 期待 |
|---|---|---|
| U1-1 | 予定外・`started_at` 10-28 21:00・完了率 95.9 | 詳細ログに1行。`unscheduled` true・`id` null・`row_key` "rp-…"・`departed` true・判定 ◎・事前チェック null |
| U1-2 | 予定外・`clean_area` 0 | 完了率 0・判定 × ・出発 true（未実施ではない） |
| U1-3 | `clean_timetable_id` = 0 と NULL | どちらも予定外として同じ扱い |
| U1-4 | 予定外・`task_area` 0 | 完了率 0（今の `runCompletionRate()` と同じ） |
| U1-5 | 予定外・削除済みの施設 | 出さない・数えない |
| U1-6 | 予定のある行 | 今と同じ（`unscheduled` false・`row_key` "tt-<id>"） |

### 表U2 — レポート（今の期間・A案）。施設 F1・期間 10-28 の1日

| 材料 | 内容 |
|---|---|
| 予定のある清掃 | 3回: 正常終了で完了率 80 ／ 正常終了で完了率 60 ／ 未実施 |
| 予定外 | 2回: `status` 4・完了率 100 ／ `status` 6・完了率 20 |
| エラー | 予定の id に紐づく2件 ＋ 予定外（id 0）に紐づく1件（`errored_at` 10-28 22:00）＋ 事前チェックの回に紐づく1件 |

| 指標 | 期待 |
|---|---|
| 予定回数 | 3 ＋ 2 ＝ 5 |
| 開始できた回数 | 2 ＋ 2 ＝ 4 → 出発成功率 80 |
| 正常終了 | 2 ＋ 1 ＝ 3 → 稼働率 60 |
| 清掃完了率 | (80 ＋ 60 ＋ 100 ＋ 20) ÷ 4 ＝ 65 |
| エラー発生回数 | 2 ＋ 1 ＝ 3（事前チェックの1件は入れない）→ エラー発生率 60 |
| エラー内訳の合計 | 3（エラー発生回数と同じ） |

### 表U3 — ダッシュボード

| # | 状況 | 期待 |
|---|---|---|
| U3-1 | 清掃エリア S1 の直近: 予定 10-28 21:00（成功）より後に予定外 10-28 23:00（△）がある | 直近1件は予定外 → 失敗に数える |
| U3-2 | 予定外の `ended_at` が今より後（清掃中） | 直近の候補にしない（予定のある清掃の「終了を過ぎた」と同じ） |
| U3-3 | 進み具合 対象日 10-28: 予定7（出発6）＋ 予定外2 | 予定 9・出発 8 |

### 表U4 — 連続日数

| # | 施設 F1 の日ごとの結果 | 期待 |
|---|---|---|
| U4-1 | 10-26 予定 ×、10-27 予定外だけ（完了率 50）、10-28 予定 △ | 3日連続 → L3 |
| U4-2 | 10-27 に予定 ◎ と 予定外 △ | その日は問題日（OR） |
| U4-3 | 10-27 予定外だけ（完了率 95） | 問題日ではない → 連続が切れる |

## 4. 分担（ファイルを重ねない）

| 担当 | 範囲 |
|---|---|
| A | 清掃詳細ログ: `lib/db/index.ts` の `cleanLogs()` / `areaCompletions()` / advice 周り、`lib/db/clean-log-mapping.ts`、`lib/types`（`CleanLogItem`）、`d-watch-api.yaml`（`CleanLogItem` と `/clean-logs` の説明）、CSV（`lib/clean-logs/csv.ts`）、画面（`components/features/detail-log/*`、`lib/i18n.ts` の「予定外」）、fixtures の清掃ログ |
| B | レポート: `lib/db/report-mapping.ts`、`lib/db/index.ts` の `reportKpis()` / `facilityPerformance()` / `errorBreakdown()`、`d-watch-api.yaml` のレポートの説明、`app/api/reports/__tests__` |
| C | 連続日数・要レスキュー・清掃エリア・ダッシュボード: `lib/db/consecutive-source.ts`、`lib/db/clean-area-source.ts`、`lib/dashboard/*`、`app/api/dashboard/*`、`lib/judge/rescue*`・`lib/db/rescue-mapping.ts`、連続問題（`continuousProblems()`） |

- A の `CleanLogItem` の変更（`unscheduled` / `row_key` / `report_id`、`id` が null 可）は C も使う。C は A の型を前提に書いてよい（型が無ければ同じ名前で足し、A と重複したら監督がそろえる）
- `lib/db/index.ts` は A・B・C が別々の関数を触る。★自分の担当の関数以外は書き換えない★。編集の前に必ず読み直す

## 5. 共通ルール

- 期待値表（§3）を先にテストにする。数字を変えない
- 予定外の判定は `scheduledTimetableId()` だけ。`=== null` / `{ not: null }` を直書きしない
- 事前チェックの回（`precheck ≠ 0`）とそれに紐づくエラーは、どの新しい集計にも入れない
- `git add -A` 禁止。コミットしない（監督がまとめてコミットする）
- 触らない: `docs/decisions.md`・`docs/handoff/指示書AY_*`・`docs/handoff/設計_制御盤ログの取り出し_*`・`components/features/errors/ErrorContentPicker.tsx`・`lib/errors/error-content-label.ts` とそのテスト
- 完了したら tsc・lint（触ったファイル）・vitest 全件の件数を報告する
