指示書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-") |
表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 全件の件数を報告する