Newer
Older
client_dwatch / docs / handoff / 指示書X_precheckを分数そのものとして読む.md

指示書X — clean_timetables.precheck を「分数そのもの」として読む(コード表を廃止する)

項目 内容
実行環境 ★Cursor 内の Claude が実装します★(.claude/commands/d-watch.md の役割分担表を参照)
実行状態 ★完了(D-113。2026-09-11)★
実行順 ★★本書(指示書X)を指示書W より先に実行すること★★(★理由は下の「実行順」節★)。★★2本を同時に走らせないこと★★。★TeamWorks 第7回回答(2026-09-11 受領)の A5-1〜A5-5 を反映し、あわせて訂正版DDL(0911版)に追従する作業★。★指示書S(D-105)が作った lib/db/precheck-mapping.ts と、指示書R(D-102)が作った lib/db/index.ts の status_1h / status_30m、指示書M〜V が育てた scripts/seed-local-db.ts の上に積みます★。★★lib/fixtures/ / d-watch-api.yaml は1文字も変えません★★(★API契約は既に「任意の分数を取りうる」と書かれており、本書の変更に追従済み。段階0 で確認する★)。★★prisma/schema.prisma は段階3 の db pull で更新します★★(★手書きはしない。D-056★)
対象 ★★precheck の値の意味が現行実装と真逆になっていたのを直す★★。★1 を「60分前」と読んでいたが、正しくは「1分前」★
取得する決定番号 ★**着手直前に `grep -nE "^### D-(0\ 1)[0-9][0-9]" docs/decisions.md \ tail -3で最終番号を実測すること**★。★★^### D-0だけでは3桁台(D-100 以降)を拾えない★★。★2026-09-07 にD-068の二重採番が実際に起きています★。★作成時点の最終番号は **D-112**(docs/decisions.md:6706。★別担当が08acc89` でコミット済み★)★で、★★指示書W が D-113 を取る見込み★★なので、★本書の実行記録は D-114 の見込み★。★★ただし実行順によって変わるので、必ず自分で実測してから採番すること★★
前提 ★★D-112(docs/decisions.md:6706。★第7回回答の受領記録。本書の出所そのもの★。★★項1 が「現行コードは真逆の変換をしている」と既に指摘しており、本書はその修正です★★。★着手時に行番号を実測し、内容が本書 §「TeamWorks 第7回回答(原文)」と食い違っていたら実装を止めて確認を求めること★)★★ / ★D-091 項4(docs/decisions.md:3546。★「0なら0分前(本番清掃)、60なら60分前とする予定」というユーザー原文★)★ / ★D-093 項2(:3719 以降。★分数ごとの enum も固定ラベルも持たない=案A の確定★。★案C「60/30 以外はその他に丸める」は否定済み★)★ / ★D-105(:5435。指示書S の実行記録。★項1 の「新旧2つの値域を1つの式で吸収する」/ 項13-1・13-2 が本書で論点ごと消滅する箇所★)★ / D-077(:1847。エラー値を変換しない)/ D-089 項3・D-104 項2(★「その他」「不明」という語を使わない★)/ D-100(MariaDB 10.11 とローカル MySQL 8.4 の差。★生SQLを増やさない★)/ D-111(:6584。指示書W の前提。★本書とはコードが重ならないが、CLEAN_TIMETABLE_PRECHECK.main を共有する★)/ D-042(:226。書き込み許可4カラム)/ D-050(:308。段階ごとに検証してからコミット)/ D-056(:353。schema.prisma は生成物)/ ★★D-094(:3822。0909版DDL の照合)/ D-095(:3978。dwatch_local を DDL から作り直した前例)/ D-096(:4117。指示書Q の実行記録。★段階3 の手順の下敷き★)/ D-059(:392。実DDL を正とし db pull の生成結果を採用する)★★
段階数 ★7段階★(段階0〜段階6)。★コミットは段階1 / 2 / 3 / 4 / 6 の5つ★
★着手前確認★ ★★なし★★ — ★値域の規則は TeamWorks の第7回回答(A5-1〜A5-5)で確定しており、★★「混在しません」「0以外の予約語はありません」と明言されています★★★。★★ユーザーの回答を待たずに段階0 から通しで実行してよい★★。★★「事前チェックは今後も1清掃につき2回か」も確定済みです★★ → ★★ユーザー回答(2026-09-11)「これは2回だけです、3回以上にはしません」★★。★したがって status_1h / status_30m の2列固定でよい(後述「申し送り」①)★
着手前の基準 ★着手直前に自分で npm test / npx tsc --noEmit / npm run lint を実行し、その実測値を基準にすること★。★作成時点の実測(2026-09-11。★鵜呑みにしない★): npm test = 45ファイル / 928テスト(全パス) / tsc = 0 / lint = 0★
作成 2026-09-11(★改訂 2026-09-11: 訂正版DDL 0911版の受領を受けて段階3 を追加、事前チェック2回固定の確定を反映★)。★作成時点の決定事項最終番号 D-112(docs/decisions.md:6706)/ docs/handoff.md の最新は 追記33(docs/handoff.md:2774)。★着手時に実測すること★★

実行後はこの表と、文書末尾の「実行状態」表を更新してください。

★この指示書を読んでいるあなたへ★ — .claude/commands/d-watch.md の「実装は Cursor 側」は 2つの環境の役割分担です。Cursor の中で動いている Claude は実装担当です。 ★調査や改訂ではなく実装を行ってください。★ ★★うまくいかなければ段階の切れ目(コミット単位)で止めて構いません★★ — ★段階1(変換関数)→ 段階2(status_1h / status_30m)→ 段階3(DB作り直しと db pull)→ 段階4(シードと rounds の削除)の順に、 ★どこで止めても tsc / lint / test が緑になる形★に並べてあります★。 ★★ただし段階3 は dwatch_local を DROP して空にするので、★段階3 で止めるならローカルDBが空のままになることを 作業メモに残すこと★(★段階4 の再シードで埋まります★)★★。


★★冒頭に読むべきこと — 何が起きているか★★

★★現行実装は clean_timetables.precheck の 1 を「60分前」、2 を「30分前」と読んでいます。 これは TeamWorks の指示と真逆です。★★

TeamWorks 第7回回答(★2026-09-11 受領。原文★):

A5-1. 認識合っています.
A5-2. 既にclean_timetablesに格納するデータは本清掃開始〇分前の〇部分の数値が入るように修正済みです.
A5-3. 既存・新規も含めてすべて〇分前の〇が格納されます. もし1や2が格納されていたら1分, 2分前としてください.
A5-4. 混在しません.
A5-5. 0以外の予約語はありません. すべて本清掃を基準に何分前開始の値が格納されます.

★★読み取れること(★これが本書の全部です★)★★:

# 確定した事実 現行実装 直し方
★1★ ★0 = 本清掃★ ★同じ★ ★変えない★(CLEAN_TIMETABLE_PRECHECK.main)
★2★ ★0 以外は ★その数値がそのまま「分前」★★ ★1 → 60分前 / 2 → 30分前★(★真逆★) ★★生値をそのまま返す★★
★3★ ★★1 が入っていたら 1分前★★(A5-3) ★60分前と読む★ ★★1 を返す★★
★4★ ★★新旧は混在しない★★(A5-4) ★「混在に耐える」設計になっている(D-105 項1)★ ★★吸収する仕掛けごと消す★★
★5★ ★★0 以外に予約語は無い★★(A5-5) ★コード表(rounds)を引いている★ ★★対応表を引く処理を消す★★

★★帰結: CLEAN_TIMETABLE_PRECHECK.rounds(=コード表)は不要になります★★。★本書はこれを削除します★。


★★この変更で「意味が変わる値」と「変わらない値」★★

★★ここが本書のいちばん大事な節です。★★

precheck の値 ★現行の解釈★ ★★新しい解釈★★ 変わるか
0 本清掃(precheck_minutes = null) ★同じ★ ★変わらない★
★1★ ★★60分前★★ ★★1分前★★ ★★変わる(真逆)★★
★2★ ★★30分前★★ ★★2分前★★ ★★変わる(真逆)★★
60 60分前(コード表に無いので生値) ★60分前★ ★変わらない★
30 30分前(コード表に無いので生値) ★30分前★ ★変わらない★
20 20分前(生値) ★20分前★ ★変わらない★
255 255分前(生値) ★255分前★ ★変わらない★

★★つまり「実DB(既に 0/60/30)に対しては、直す前も直した後も答えが同じ」★★です。 ★★変わるのは 1 / 2 が入っていたときだけ★★ — ★そして 1 / 2 が入っているのは ★★ローカルの dwatch_local だけ★★です(★実測: 0=259 / 1=253 / 2=253★)。

★★だから段階4(シードの作り直し)が要ります★★ — ★★ローカルDBを実DBと同じ形にしないと、 「正しく直った」ことを実測で確認できません★★。


★調査済みの事実(★現物で裏を取ってから使うこと★)★

1. ★★PRECHECK_MINUTES_BEFORE は他でも使われている → 消さない(★実測 2026-09-11★)★★

★★本書で最も踏みやすい間違いは「precheck の変換に使わなくなったから設定値ごと消す」ことです。★★ ★★消してはいけません。★★ ★以下の消費者がいます(grep -rn "PRECHECK_MINUTES_BEFORE" の実測)★:

# 消費者 用途 本書での扱い
★1★ ★components/features/system/SystemScreen.tsx:32 / :131★ ★★システム管理画面の「事前チェック設定」の表示★★(「第1回チェック / 60分前」「第2回チェック / 30分前」)。★★= DFA 側の設定値(何分前にチェックするか)★★ ★★残す。1文字も変えない★★
★2★ ★lib/fixtures/prechecks.ts:1 / :8★ ★fixtures の分数を直書きしないための別名★ ★★残す。lib/fixtures/ は変更禁止★★
★3★ ★scripts/seed-local-db.ts:33 / :804★ ★事前チェック行の start_at(本清掃の何分前か)を作る★ ★★残す★★。★★段階4 では、この同じ値を precheck カラムにも入れるようになる★★
★4★ ★lib/db/index.ts(★段階2 で新たに使う★)★ ★★どの precheck 行を status_1h / status_30m に割り当てるかの選択キー★★ ★★段階2 で追加★★
★5★ ★lib/db/precheck-mapping.ts:15 / :88★ ★★precheck コード → 分数の変換表★★ ★★この用途だけ消す(段階1)★★
6 lib/prechecks/__tests__/label.test.ts / lib/prechecks/__tests__/query.test.ts テストの入力値を直書きしないための参照 ★残す★

★★言い換えると★★: ★PRECHECK_MINUTES_BEFORE は「★DFA がどのタイミングでチェックしたいか★」を表す設定値として残り、 ★★「DBの値を分数に読み替える対応表」という役割だけを失います★★。 ★★docstring にこの区別を明記すること★★(★段階4★)。

2. ★ローカルDB(dwatch_local)の現状(★2026-09-11 実測★)★

項目 値
★clean_timetables.precheck の分布★ ★★0 = 259 / 1 = 253 / 2 = 253★★(★★古い値のまま★★)
robot_error_reports の件数 ★14★
★robot_error_reports.error_id の形★ ★★RER-<本清掃id>-<precheckコード>(例 RER-1011-1 / RER-1011-2)★★
db.cleanLogs() の件数 ★259★
db.prechecks() の件数 ★★27(precheck 14 / launch_failed 13)★★
★prechecks() の precheck_minutes の内訳★ ★★60 = 8件 / 30 = 6件 / null = 13件★★
★cleanLogs() の status_1h の内訳★ ★Standby. = 251 / セキュリティ有効 2 / オフライン 3 / Handrail Lifted 1 / 充電中 1 / 緊急停止 1(★非 Standby. = 8件★)★
★cleanLogs() の status_30m の内訳★ ★Standby. = 253 / セキュリティ有効 2 / オフライン 3 / Handrail Lifted 1(★非 Standby. = 6件★)★

★★実測コマンド★★(★着手直前に自分で取り直すこと★):

"/c/Program Files/MySQL/MySQL Server 8.4/bin/mysql.exe" -u root --default-character-set=utf8mb4 dwatch_local -e "
SELECT precheck, COUNT(*) c FROM clean_timetables GROUP BY precheck ORDER BY precheck;
SELECT COUNT(*) FROM robot_error_reports;
SELECT error_id FROM robot_error_reports ORDER BY error_id LIMIT 5;"

3. ★★段階4 の前後で「変わる数字」と「変わらない数字」(★先に予告してから実測すること★)★★

★★この表が本書の合否判定です。★★ ★★特に #5・#6 が「変わらない」ことが、「正しく直った」ことの証明です★★ — ★シードが 60 / 30 を入れ、変換関数がそれを生値で返すので、★★往復して同じ分数に戻る★★。

# 項目 ★着手前★ ★★段階4(再シード)後の期待★★
★1★ clean_timetables.precheck の分布 0=259 / 1=253 / 2=253 ★★0=259 / 60=253 / 30=253★★(★1 / 2 は 0件★)
★2★ clean_timetables の総行数 765 ★★765(変わらない)★★
★3★ robot_error_reports の件数 14 ★★14(変わらない)★★
★4★ ★robot_error_reports.error_id の値★ RER-1011-1 / RER-1011-2 … ★★RER-1011-**60** / RER-1011-**30** …(★★変わる★★)★★
★5★ ★★db.prechecks() の件数と内訳★★ 27(precheck 14 / launch_failed 13) ★★27(14 / 13)— 変わらない★★
★6★ ★★prechecks() の precheck_minutes の内訳★★ 60=8 / 30=6 / null=13 ★★60=8 / 30=6 / null=13 — 変わらない★★ ← ★★これが「正しく直った」証明★★
★7★ db.cleanLogs() の件数 259 ★★259(変わらない)★★
★8★ ★cleanLogs() の status_1h の非 Standby. 件数★ 8 ★★8(変わらない)★★
★9★ ★cleanLogs() の status_30m の非 Standby. 件数★ 6 ★★6(変わらない)★★
★10★ clean_status_histories.clean_error の分布 0=231 / 1=13 / 2=5 / 3=5 / 4=5 ★★変わらない★★(★動いていたらシードの通番を壊している★)
★11★ npm test 45ファイル / 928テスト ★★増える★★(★減らないこと★)
★12★ npx tsc --noEmit / npm run lint 0 / 0 ★変わらない★

★★#4 だけが「変わってよい」値です★★ — ★PrecheckItem.id は robot_error_reports.error_id の生値(D-105 項2)なので、 ★★アラート画面の行の id 文字列が RER-1011-1 → RER-1011-60 に変わります★★。 ★これはローカル検証データの採番規則が実DBの値域に追従した結果であり、意図した変化です★。 ★★段階6 の記録に必ず書くこと★★。

4. ★★段階1・2・3 の間はローカルDBの表示が一時的に崩れる(★想定内★)★★

★段階2 まで実装した時点で、コードは precheck = 60 / 30 の行を探すようになりますが、 ローカルDBにはまだ 1 / 2 しか入っていません★。★★その間だけ★★:

  • ★prechecks() の precheck_minutes が ★1 と 2★ になる(★= A5-3 のとおりに正しく読んでいる★)
  • ★cleanLogs() の status_1h / status_30m が ★全行 Standby.★ になる(★60 / 30 の行が見つからないため★)

★★これは「壊れた」のではなく「古いデータを新しい規則で正しく読んだ」結果です★★。

★★段階3(DBの作り直し)を挟むと、今度はDBが空になります★★ — ★★prechecks() も cleanLogs() も 0件★★。 ★★これも想定内です★★。★★段階4 の再シードで解消します★★。

★tsc / lint / test はこの間(段階1・2・3)もすべて緑のままです★ (★テストは純粋関数と手書きの入力だけで書かれており、DBを見ないため★)。

★★段階2 または段階3 のコミットで止める場合★★は、★★ローカルDBがどの状態か(古い値のまま/空)をコミットメッセージか作業メモに残すこと★★。

5. ★落とし穴(★踏むとテストが通ったまま壊れます★)★

# 落とし穴
★1★ ★★PRECHECK_MINUTES_BEFORE を消すこと★★ — ★★システム管理画面の「事前チェック設定」が消え、fixtures とシードが壊れます★★(上の調査1)。★消すのは CLEAN_TIMETABLE_PRECHECK.rounds だけ★
★2★ ★★CLEAN_TIMETABLE_PRECHECK.main を消すこと★★ — ★lib/db/index.ts の5箇所・scripts/seed-local-db.ts の2箇所・lib/db/precheck-mapping.ts が使っています★。★★未実行の指示書W も CLEAN_TIMETABLE_PRECHECK.main を使う前提で書かれています★★(docs/handoff/指示書W_…:664。★★読むだけ。編集しないこと★★)。★main は設定値のまま残す(0 を直書きしない。CLAUDE.md §7)★
★3★ ★★0 / 1 / 2 / 60 / 30 をコードに直書きすること★★ — ★0 は CLEAN_TIMETABLE_PRECHECK.main、60 / 30 は PRECHECK_MINUTES_BEFORE から読む★(CLAUDE.md §7)。★★テストの中でも直書きしないこと★★(★既存テスト lib/db/__tests__/precheck-mapping.test.ts の作法に揃える★)。★唯一の例外は「設定値以外の分数」を検証する 20 / 255 / 1 / 2(★設定値と一致させると検証が成立しない★。前例: lib/fixtures/prechecks.ts:90)★
★4★ ★★未知の値を「その他」「不明」に丸めること★★ — ★D-077 / D-089 項3 / D-104 項2★。★★TeamWorks は「0以外の予約語は無い」と明言している(A5-5)ので、丸める先そのものが存在しません★★
★5★ ★★status_1h / status_30m というフィールド名を変えること★★ — ★★API契約(d-watch-api.yaml)です。本書の範囲外★★。★中身の作り方だけ変える★
★6★ ★★d-watch-api.yaml を変えること★★ — ★★変える必要がありません★★。:1188 は既に「clean_timetables.precheck は設定値に応じた任意の分数を取りうるため、分数ごとの enum は持たない」と書いてあり、★本書の変更に追従済み★(★段階0 で自分の目で確認すること★)
★7★ ★★段階1・2 の途中で prisma/schema.prisma を書き換えること★★ — ★★schema.prisma は db pull の生成物で、型・属性を手で書き換えません★★(CLAUDE.md §7 / D-056)。★:581-583 / :611-613 の「0: 本清掃 / 1: 1回目 / 2: 2回目」というコメントは ★もう正しくありません★ が、★★直すのは段階3-6(db pull 後のコメント移植)です★★(★コメントは db pull が生成しないので、人が書くのが正規の手順★)。★★段階3 より前に触らないこと★★
★8★ ★★lib/fixtures/ を1行でも変えること★★ — ★fixtures は precheck_minutes(= API の値)を直接持っており、DBのコード値を持っていません★。★★本書の変更の影響を受けません★★。★USE_FIXTURES=true の挙動は1つも変わらないのが正しい★
★9★ ★★?? を `\ \ に書き換えること★★ — ★0が別の値に化けます★。★★本書は0`(本清掃)が意味を持つコードを触るので特に危険★★
★10★ ★★段階4 でシードの本ループに行を差し込むこと★★ — ★historyIndex / errorReportCount の通番の剰余で clean_error と対応状況を決定的に散らしています★。★★本書は行を足しません。既存行の precheck カラムの値だけを変えます★★(★行数も順序も変えない★)
★11★ ★★シードで乱数を使うこと・id を暗黙採番にすること★★ — ★冪等が壊れます★。★★2回流して同じ結果になることを実測すること★★
★12★ ★★lib/db/precheck-mapping.ts の他の関数(resolutionStatusFromRobotErrorStatus / robotErrorStatusFromResolutionStatus / detectedAtFromError)に手を付けること★★ — ★★範囲外。1文字も変えない★★
★13★ ★★事前チェックが3回以上になったときの器を今作ること★★ — ★★範囲外。★★3回以上にはならないと確定済み★★(★ユーザー回答 2026-09-11「これは2回だけです、3回以上にはしません」★。申し送り①)★★。★★器を作る必要はありません★★。★本書は「1清掃につき事前チェック2回」を前提に進める★

★禁止事項(この指示書の全段階に効きます)★

# ★してはいけないこと★ 理由
★1★ ★★git add -A を使うこと★★ CLAUDE.md §7。★過去に別作業の途中ファイルを巻き込む事故が起きている★。★★必ずファイルパスを明示すること★★
★2★ ★★README.md / docs/code-map.md / docs/handoff/管理画面_設計検討_20260910.md / docs/handoff/指示書W_連続問題施設を実DBから作る.md / `.claude/worktrees/` に触ること**★★ ★★すべて別作業のもの★★(★README.md は未コミットの変更あり、docs/code-map.md と 管理画面_設計検討_20260910.md は未追跡(git status に M / ?? で出る)、指示書W は 08acc89 でコミット済みだが別担当が実行する未実行の指示書★)。★docs/code-map.md:881-882 / :1899 / 管理画面_設計検討:216 には rounds の記述が残りますが、★★本書では直しません★★(申し送り③)★。★本指示書のコミットに含めない・編集しない・削除しない★
★3★ ★★docs/decisions.md を段階6 より前に編集すること★★ ★★別担当が D-112 を追記済み(08acc89)、D-113(指示書W)を追記する見込みです★★。★段階6 でも「自分の追記だけを足す」こと。★★着手時に未コミットの他人の追記が無いか git status で確認し、あれば巻き込むことをコミットメッセージに明記すること★★★
★4★ ★★メインDBのスキーマ変更(prisma migrate / db push / prisma/schema.prisma の型・属性の手書き)★★ CLAUDE.md §7 / D-056 / D-059。★★本書はスキーマ変更を1つも作りません★★(★precheck は UnsignedTinyInt のままで 0〜255 を表現できる★)。★★prisma db pull は段階3 でのみ実行します★★ — ★これはスキーマ「変更」ではなく、TeamWorks が作った DDL を取り込むだけの操作です★(D-056 / D-059。★段階3 以外では実行しないこと★)。★★prisma/schema.prisma のコメントの移植・書き直しは段階3-6 の正規手順で、この禁止事項の対象外★★
★5★ ★★書き込み許可4カラム(status / comment / responded_at / resolved_at)以外への DB 書き込み★★ D-042。★段階4 の投入は scripts/seed-local-db.ts(=ローカル検証用シード)からであり、アプリの実行時書き込みではない★。★★メインDB / dwatch_ddl に接続しないこと★★
★6★ ★★0 / 1 / 2 / 60 / 30 をコードやテストに直書きすること★★ CLAUDE.md §7 / 落とし穴3
★7★ ★★未知の値を「その他」「不明」に丸めること・7分類に変換すること★★ D-077 / D-089 項3 / D-104 項2 / 落とし穴4。★★A5-5「0以外の予約語はありません」★★
★8★ ★★PRECHECK_MINUTES_BEFORE を削除・改名すること★★ ★DFA 側の設定値として別の用途で使われています★(調査1。★システム管理画面 / fixtures / シード★)。★消すのは CLEAN_TIMETABLE_PRECHECK.rounds だけ★
★9★ ★★CLEAN_TIMETABLE_PRECHECK.main を削除すること・0 を直書きに戻すこと★★ 落とし穴2。★★未実行の指示書W も参照します★★
★10★ ★★status_1h / status_30m のフィールド名を変えること★★ ★API契約(d-watch-api.yaml)。本書の範囲外★
★11★ ★★d-watch-api.yaml / lib/types/index.ts を変えること★★ ★★変える必要がありません★★(落とし穴6)。★変えたくなったら、それは本書の解釈が間違っているサインなので止めて確認を求めること★
★12★ ★lib/fixtures/ を1行でも変更すること★ CLAUDE.md §9.5 / 落とし穴8
★13★ ★lib/db/prisma.ts / local-guard.ts / use-fixtures.ts / ds-status.ts / clean-log-mapping.ts を変更すること★ ★完成しており、そのまま効く★。★★robotStatusFromErrors() は clean-log-mapping.ts からimport して使う(コピーしない。D-103)★★
★14★ ★prismaSource の cleanLogs / prechecks 以外のメソッドに手を付けること★ ★範囲外★
★15★ ★テストを DB 依存にすること★ ★遅く・不安定になり、DB が空の環境で落ちる★。★テストは純粋関数と手書きの入力だけ★
★16★ ★npm run dev を Bash で起動すること★ ★Browser pane の preview_start を使う★。★無ければ curl で代替し、その旨を記録する★
★17★ ★.env.local をコミットすること / DB の認証情報を文書やコードに書くこと★ CLAUDE.md §9.3。★検証後は USE_FIXTURES=true に戻す★
★18★ ★★一時スクリプトを main().catch(...) だけで終わらせること★★ ★★成功時にも必ず process.exit(0) を書くこと★★ — ★Prisma の接続が残り、★★実際に4時間39分ハングした事故が起きています★★★

作業の順序(★崩さないこと★)

★段階0(前提の確認。★読むだけ・コミットなし★) → 段階1(★precheckMinutesFromCode() を「生値をそのまま返す」に直す★。★対応表を引く処理を消す★) → 段階2(★status_1h / status_30m の割り当て規則を PRECHECK_MINUTES_BEFORE 基準にする★) → ★★段階3(訂正版DDL(0911版)で dwatch_local を作り直し、db pull / generate をやり直す)★★ → 段階4(★シードを 0 / 60 / 30 にし、★★CLEAN_TIMETABLE_PRECHECK.rounds を削除★★、再シードする★) → 段階5(実測での検証。★コミットなし★) → 段階6(記録と push)★

★★段階1・2・3・4・6 でそれぞれ1コミット(計5コミット)★★。 ★段階ごとに npx tsc --noEmit / npm run lint / npm test を実行し、 通ってからコミットすること★(D-050。docs/decisions.md:308)。

★★期待値表 → テスト → 実装 の順序を崩さないこと★★(CLAUDE.md §6-2 / §9.4)。 ★★特に段階1 は「テストを先に書き、落ちることを確認してから実装する」こと★★ — ★★既存テスト(lib/db/__tests__/precheck-mapping.test.ts:26-30)は rounds を使って自分自身を検証しているので、実装を反転させても 「rounds を [60,30] にする」だけで通ってしまいます★★。 ★先に「1 → 1」という落ちるテストを書かないと、壊れたまま緑になります★。

★★なぜこの順番なのか★★

  • ★段階1 が先頭なのは、★★これが仕様の本体(値の意味の反転)★★だから。★ここだけで実DBに対しては正しくなる★
  • ★段階2 を分けるのは、★★status_1h / status_30m は「変換」ではなく「割り当て」という別の問題★★だから。 ★1コミットにまとめると、段階5 の実測でどちらが効いたか分からなくなる★
  • ★★段階3(DBの作り直し)を段階2 と段階4 の間に置くのは、★DROP するとデータが消えるので、 「着手前の実測(段階0)」を取り終えたあと、かつ「再シード(段階4)」の直前でなければならない★★★から。 ★★DBを作り直したあと空のまま放置する時間を最短にする★★(★段階3 → 段階4 は続けて実行すること★)
  • ★段階4 を最後の実装段階に置くのは、★★CLEAN_TIMETABLE_PRECHECK.rounds の消費者が 段階1・2 で1つずつ減り、段階4 で最後の1つ(シード)が消えて初めて安全に削除できる★★から。 ★逆順にすると tsc が落ちます★

★★段階の並びは実装者が判断して入れ替えてよい★★(★上は推奨順です★)。 ★★ただし1つだけ動かせない制約があります★★ — ★★DBを作り直す前に、既存の実測値を必ず控えておくこと★★(段階0 / 段階3-2)。 ★DROP した瞬間に取り返しがつきません★。


★★実行順 — 本書を指示書W より先に実行すること★★

★★本書(指示書X)を、指示書W(docs/handoff/指示書W_連続問題施設を実DBから作る.md)より先に実行してください★★。

★理由★: ★★ローカルDB(dwatch_local)の precheck が実DBと違う値(1 / 2)のままだからです★★。 ★先に本書で 0 / 60 / 30 に揃えないと、★★指示書W の検証が本番とずれた土台の上で行われます★★。 ★本書は段階3 でDBそのものも訂正版DDL(0911版)に作り直すので、なおさら先に済ませる必要があります★。

★★2本を同時に走らせないこと★★ — ★docs/decisions.md / docs/handoff.md を両方が触ります★。 ★★また本書の段階3 は dwatch_local を DROP するので、指示書W が同時にDBを見ていると壊れます★★。 ★指示書W は ★読むだけ。編集しないこと★★(禁止事項2)。


段階0 — 前提の確認(★読むだけ・コミットなし★)

読むもの

# 対象 見る点
1 ★CLAUDE.md 全文★ ★特に ★§4.8(エラー種別は変換しない)★ / ★§7(禁止事項。閾値のハードコード禁止・推測で実装しない)★ / §9.4(判定ロジックの作法)/ §9.3(any 禁止)★
2 ★docs/decisions.md の ★D-091 項4(:3546)★★ ★★ユーザー原文「0なら0分前(本番清掃)、60なら60分前とする予定」★★。★本書が実現する変更の出所★
3 ★★D-093 項2(:3719 以降。★見出しを実測すること★)★★ ★★案A(precheck_minutes を数値で持ちラベルを組み立てる)が確定した経緯★★。★★案C(60/30 以外を「その他」に丸める)が否定済みであること★★
4 ★D-105(:5435)の ★項1★ / ★項13-1・13-2★★ ★★本書で論点ごと消滅する箇所★★(段階6 で記録する)。★項1 の「新旧2つの値域を1つの式で吸収している」/ 項13-1「新旧混在の有無」/ 項13-2「設定値と DB のどちらが正か」★
5 ★★D-112(docs/decisions.md:6706。第7回回答の受領。★行番号を実測すること★)★★ ★★A5-1〜A5-5 の原文が本書 §「TeamWorks 第7回回答」と一致することを確認する★★。★★食い違っていたら実装を止めて確認を求めること★★。★★項1 が「現行コードは真逆」と既に整理しているので、本書の直し方と突き合わせること★★
6 ★D-111(:6584)★ ★指示書W の前提★。★CLEAN_TIMETABLE_PRECHECK.main を共有するので、削除しないことの裏付け★
7 ★docs/handoff.md の末尾(★追記33★。:2774)★ ★指示書V の完了内容と申し送り★
8 ★docs/handoff/指示書V_連続日数を施設単位に揃え当期の分母から未来を外す.md★ ★★段階の切り方・期待値表の書き方・記録の作法★★(★本書はこれに揃えてあります★)
9 ★lib/config/index.ts:27-54 全文★ ★PRECHECK_MINUTES_BEFORE(:34)と CLEAN_TIMETABLE_PRECHECK(:49-54)の docstring★。★★「D-091 項4 / D-093 項2 で 0/60/30 に変わる予定」という記述が実現したこと★★
10 ★lib/db/precheck-mapping.ts 全文(127行)★ ★precheckMinutesFromCode()(:82-99)★。★★:65-80 の「新旧2つの値域を1箇所で吸収する」という長い説明が丸ごと不要になること★★
11 ★lib/db/index.ts:267-313(cleanLogs() の事前チェック行の索引と status_1h / status_30m)★ / ★:430-490(prechecks() の precheck_minutes)★ ★precheckByParent(:268-275。★キーが precheck の生値★)/ ★:307 の CLEAN_TIMETABLE_PRECHECK.rounds.map(...) による2要素分割代入★
12 ★scripts/seed-local-db.ts:190-223(採番と設計判断)★ / ★:787-830(本清掃行と事前チェック行の投入)★ ★:804 の PRECHECK_MINUTES_BEFORE.entries() / :807 の CLEAN_TIMETABLE_PRECHECK.rounds[i] / ★:829 の errorKey = RER-${log.id}-${precheckCode}★★
13 ★lib/prechecks/label.ts 全文(39行)★ ★★precheckSeverityLabel() は precheck_minutes から "{n}分前異常" を組み立てるだけ★★。★★本書で1文字も変えません★★(★1 なら「1分前異常」と出るのが正しい★)
14 ★lib/db/__tests__/precheck-mapping.test.ts 全文(113行)★ ★★:26-30 が rounds を使って自分自身を検証しており、実装を反転させても通ってしまうこと★★を自分の目で確認する(★上の「なぜこの順番なのか」の警告★)
15 ★components/features/system/SystemScreen.tsx:120-142★ ★★PRECHECK_MINUTES_BEFORE の別用途(DFA 側の設定値の表示)★★。★★これがあるので設定値は消せない★★
★16★ ★★docs/handoff/指示書Q_スキーマを最新DDL0909に追従させ再シードする.md 全文★★ ★★段階3 の手順の下敷き★★。★特に 2-1(db pull に --force を付けない)/ 2-2(★コメント101行の移植★)/ 2-3(@updatedAt を DDL で確かめて戻す)/ 2-4(冒頭コメントの更新)/ 2-5(Laravel 基盤10テーブルを消さない)★
★17★ ★docs/handoff/指示書E_スキーマを実DDLに一致.md の 手順3(:92-136)と 手順4(:139-156)★ ★db pull が出力しないものを手で戻す作法★
★18★ ★D-094(:3822)/ D-095(:3978)/ D-096(:4117)★ ★★0909版DDL のときに「照合 → DB作り直し → db pull → 再シード」をどうやったかの記録★★。★段階3 は同じ道を通ります★
★19★ ★★訂正版DDL C:\Users\daich\Downloads\clean_watch_20260911.sql★★(★2026-09-11 受領。★44,923 bytes★) ★★0909版との diff を自分で取り、段階3-1 の表と一致することを確認する★★。★食い違ったら止めて確認を求めること★
★20★ ★docs/handoff/TeamWorks確認事項_第7回_20260910.md:180-205★ ★★A-2 / A-3b / B-2 / B-3b の4件が何を依頼したものかを確認する★★(★0911版で反映されたのは A-3b と B-2 の2件だけ★)

★確認すること(★変えないことの確認★)★

# 対象 ★確認内容★
★1★ ★d-watch-api.yaml:1186-1194★ ★★「clean_timetables.precheck は設定値に応じた任意の分数を取りうるため、分数ごとの enum は持たない」と既に書いてあること★★ → ★★YAML の変更は不要★★(禁止事項11)。★違っていたら止めて確認を求めること★
★2★ ★lib/types/index.ts の PrecheckItem.precheck_minutes★ ★`number \ null` であること★ → ★★型の変更も不要★★
★3★ ★lib/fixtures/prechecks.ts★ ★precheck_minutes を直接持っており、DBのコード値を持たないこと★ → ★★fixtures の変更は不要★★

接続先の確認とこの段階の完了条件

  • ★.env.local の DATABASE_URL が dwatch_local を指していることを確認する★。 ★値そのものを画面・ログ・文書に出さないこと★(CLAUDE.md §9.3)
  • ★.env.local に ALLOW_DIRECT_ACCESS=true があることを確認する★(D-101 項2。★無いと段階5 で詰まる★)
  • ★★着手前の基準値を自分で実測して控えた★★(npm test のファイル数・テスト数 / tsc / lint)
  • ★★DBの現状を実測して控えた★★(上の調査2 のコマンド)

★★ここで控えるのは「あとで比べるため」だけではありません★★ — ★★段階3 で dwatch_local を DROP します★★。★★控え損ねた値は取り返しがつきません★★。 ★★DBを作り直す前に既存の実測値を控えておくことは必須です★★(段階3-2)。 ★段階の並びを入れ替える場合も、この制約だけは崩さないこと★。

  • ★★prisma/schema.prisma の控えを取った★★(★段階3-2 #3・#4。git show HEAD:prisma/schema.prisma / コメント行数 / モデル数★)
  • ★★db.prechecks() / db.cleanLogs() の着手前の内訳を控えた★★(★段階5 の突き合わせに使う★):
USE_FIXTURES=false DATABASE_URL="mysql://root@localhost:3306/dwatch_local" npx tsx ./scripts/tmp-measure.ts

★★一時スクリプトの雛形(★成功時にも process.exit(0) を必ず書くこと★。禁止事項18)★★:

import { db } from "../lib/db";

async function main() {
  const pre = await db.prechecks();
  const byMin: Record<string, number> = {};
  for (const p of pre) byMin[String(p.precheck_minutes)] = (byMin[String(p.precheck_minutes)] ?? 0) + 1;
  console.log("prechecks", pre.length, JSON.stringify(byMin));
  const logs = await db.cleanLogs();
  const count = (k: "status_1h" | "status_30m") =>
    logs.reduce((a: Record<string, number>, l) => { a[l[k]] = (a[l[k]] ?? 0) + 1; return a; }, {});
  console.log("cleanLogs", logs.length, JSON.stringify(count("status_1h")), JSON.stringify(count("status_30m")));
}

// ★★成功時にも必ず exit する(`main().catch(...)` だけだと Prisma の接続が残ってハングする)★★
main()
  .then(() => process.exit(0))
  .catch((e) => { console.error(e); process.exit(1); });
  • ★★一時スクリプトはコミットしないこと★★(★検証後に削除する。git status に残さない★)
  • ★★決定番号を実測した★★(grep -nE "^### D-(0|1)[0-9][0-9]" docs/decisions.md | tail -3)。 ★★D-112 / D-113 が既に入っているかを確認し、★空いている次の番号★を段階6 で使うこと★★

段階1 — ★precheckMinutesFromCode() を「生値をそのまま返す」に直す★(1コミット)

1-0. ★何を消すのか★

lib/db/precheck-mapping.ts:82-99(★実測。原文★):

export function precheckMinutesFromCode(precheckCode: number): number | null {
  if (precheckCode === CLEAN_TIMETABLE_PRECHECK.main) return null;
  const round = CLEAN_TIMETABLE_PRECHECK.rounds.indexOf(          // ← ★消す★
    precheckCode as (typeof CLEAN_TIMETABLE_PRECHECK.rounds)[number]
  );
  if (round !== -1) {                                            // ← ★消す★
    const minutes = PRECHECK_MINUTES_BEFORE[round];              // ← ★消す★
    if (minutes === undefined) { throw new Error(...); }         // ← ★消す★
    return minutes;
  }
  return precheckCode;                                           // ← ★これだけが残る★
}

★★残るのは2行です★★:

if (precheck === CLEAN_TIMETABLE_PRECHECK.main) return null;
return precheck;

★★PRECHECK_MINUTES_BEFORE の import もこのファイルからは消えます★★(★★設定値そのものは消さない★★。禁止事項8)。 ★長さ不一致の例外(:89-94)も、対応表が無くなるので消えます★。

1-1. ★★期待値表(★これをそのままテストにすること。実装より先に書く★)★★

対象: precheckMinutesFromCode(precheck: number): number | null

# 入力 precheck ★期待★ 備考
★1★ ★CLEAN_TIMETABLE_PRECHECK.main(= 0)★ ★★null★★ ★本清掃行。事前チェック行ではないので分数を持たない★
★2★ ★★1★★ ★★1(=1分前)★★ ★★従来は 60 に化けていた値★★。★A5-3「もし1や2が格納されていたら1分, 2分前としてください」★
★3★ ★★2★★ ★★2(=2分前)★★ ★★従来は 30 に化けていた値★★
★4★ ★PRECHECK_MINUTES_BEFORE[0](= 60)★ ★60★ ★実DBに実際に入っている値(A5-2)★
★5★ ★PRECHECK_MINUTES_BEFORE[1](= 30)★ ★30★ ★同上★
★6★ ★20★ ★20★ ★★設定値以外の分数★★(D-093 項2 のユーザー原文「20だったら20分前になります」)
★7★ ★255★ ★255★ ★UnsignedTinyInt の上限。★丸めない・「その他」に寄せない★★
★8★ ★★-1★★ ★★-1★★ ★★負の値もそのまま返す(下の 1-2 の判断)★★
★9★ ★0 以外の全整数(1〜255 を総当たり)★ ★★入力と同じ値★★ ★★プロパティテスト。CLEAN_TIMETABLE_PRECHECK.main だけが null★★

1-2. ★★負の値・想定外の値の扱い(★この方針で実装すること★)★★

★★決定: CLEAN_TIMETABLE_PRECHECK.main 以外は、負の値も含めて生値をそのまま返す。例外を投げない。丸めない。★★

★根拠★:

  1. ★★TeamWorks は「0以外の予約語はありません。すべて本清掃を基準に何分前開始の値が格納されます」と明言している(A5-5)★★ — ★★丸める先(「その他」)も、弾く根拠(予約値の範囲)も存在しない★★
  2. ★DBのカラムは UnsignedTinyInt(prisma/schema.prisma:584)なので ★負の値はそもそも格納できない★。 ★負の値が来るとしたら Prisma より手前の異常であり、この関数が握りつぶす話ではない★
  3. ★★resolutionStatusFromRobotErrorStatus() が例外を投げるのとは事情が違う★★ — ★あちらは 0/1/2 を3値の enum に変換するので、取り違えると「対応済みなのに未解消と出る」という ★意味の反転★が起きる★。★★こちらは連続量で、生値がそのまま画面に出れば人が気づける★★ (★cleanErrorLabel() が String(code) を返すのと同じ扱い。lib/config/index.ts:119-129★)
  4. ★画面には precheckSeverityLabel()(lib/prechecks/label.ts:38)が "-1分前異常" と出す★ — ★★見慣れない表示が出るほうが「データがおかしい」と気づける(fail-loud)★★

★★例外を投げる案は採らない★★ — ★1行の異常データで /alerts 全体が 500 になる★ (lib/db/index.ts:438-440 が同じ理由で「例外にせず飛ばす」を選んでいる)。

1-3. テストを書く(★実装より先★)

  • ★lib/db/__tests__/precheck-mapping.test.ts の describe("precheckMinutesFromCode")(:17-59)を書き換える★
  • ★★:26-30 の「rounds の各値は PRECHECK_MINUTES_BEFORE の同じ並び位置の分数になる」は消すのではなく、期待値を反転させたテストに置き換えること★★:
    • ★★新: 「1 は1分前 / 2 は2分前(★★D-112(第7回 A5-3)で従来と真逆になった★★)」★★
    • ★★テスト名とコメントに「意図した仕様変更である」ことと出所(A5-3)を書くこと★★(★指示書V 決定2 と同じ作法★)
  • ★:39-47 の「切替後の値域でも同じ分数になる」は ★通ったまま★ になる★ — ★★コメントの「rounds が [60,30] になっても並び位置から同じ分数が返る」という説明はもう成立しないので書き換えること★★ (★新: 「PRECHECK_MINUTES_BEFORE の値は設定値であると同時に実DBに入る値でもある。生値をそのまま返すので一致する」★)
  • ★:49-58 の「本清掃コードだけが null で、それ以外は必ず数値」は ★強化する★ — ★★toBe(code)(入力と同じ値)まで確認すること★★(★現状は typeof result === "number" しか見ていない★)
  • ★★この時点で #2・#3 が落ちることを確認してから実装に進む★★

1-4. 実装

lib/db/precheck-mapping.ts:

  1. ★precheckMinutesFromCode() を「main なら null、それ以外は生値」に直す★
  2. ★PRECHECK_MINUTES_BEFORE の import を消す★(★★CLEAN_TIMETABLE_PRECHECK の import は残す★★)
  3. ★★docstring(:58-81)を全面的に書き換える★★:
    • ★★消す★★: 「新旧2つの値域を1箇所で吸収する」「現行DDL … 回次のコード値」「この2通りを1つの式で扱えるのは…」(:65-77)
    • ★★書く★★:
      • ★clean_timetables.precheck は ★本清掃開始の何分前か★ を表す数値(★TeamWorks 第7回 A5-2 / A5-5。D-112★)
      • ★CLEAN_TIMETABLE_PRECHECK.main(=本清掃)だけが null★
      • ★★1 は「1回目」ではなく「1分前」★★(A5-3。★★従来の実装と真逆だったので、間違えないよう明記する★★)
      • ★★新旧の混在は無い(A5-4)ので、吸収する仕掛けは持たない★★
      • ★未知の値を「その他」に丸めない(D-077 / D-089 項3)/ 負の値もそのまま返す(上の 1-2 の理由を1〜2行で)★
  4. ★★引数名を precheckCode から precheck に変えること★★(★もうコードではない★)。 ★★関数名 precheckMinutesFromCode は変えない★★ — ★呼び出し側(lib/db/index.ts:445)に precheckMinutes という局所変数があり、素直な短い名前が取れない★。 ★★名前だけの変更を本筋のコミットに混ぜるとレビューしづらい★★ので、 ★★docstring の冒頭に「関数名の Code は歴史的な名前。現在の precheck はコード値ではなく分数そのもの」と1行書くこと★★ (★改名は申し送り④に残す★)

この段階の完了条件

  • ★期待値表9ケースがテストになっており、全部通る★
  • ★★lib/db/precheck-mapping.ts から PRECHECK_MINUTES_BEFORE の参照が消えている★★ (★lib/config/index.ts の定義そのものは残っている★)
  • ★★CLEAN_TIMETABLE_PRECHECK.rounds はまだ消さない★★(★lib/db/index.ts:307 と scripts/seed-local-db.ts:807 が使っている★)
  • ★npx tsc --noEmit = 0 / npm run lint = 0 / npm test 全パス★
  • ★git diff --stat が ★lib/db/precheck-mapping.ts / lib/db/__tests__/precheck-mapping.test.ts の2ファイルだけ★★
  • ★★コミット(ファイルパスを明示。git add -A 禁止)★★
    • 例: git commit -m "fix: 指示書X 段階1 precheckを分数そのものとして読む(1は1分前)"

段階2 — ★status_1h / status_30m の割り当て規則を決める★(1コミット)

2-0. ★何が問題か★

lib/db/index.ts:305-313(★実測。原文★):

const precheckRows = precheckByParent.get(key(row.id));
// ★precheck の順(60分前 → 30分前)は lib/config の定義に従う(0/1/2 を直書きしない)★
const [status1h, status30m] = CLEAN_TIMETABLE_PRECHECK.rounds.map((code) => {   // ← ★rounds が消える★
  const precheckRow = precheckRows?.get(code);
  const errors = precheckRow ? (errorsByTimetableId.get(key(precheckRow.id)) ?? []) : [];
  return robotStatusFromErrors(errors);
});

★precheckByParent は「本清掃の id → (precheck の生値 → 事前チェック行)」の入れ子 Map★(:268-275)。 ★★rounds が無くなるので、★どの precheck 値の行を status_1h に、どれを status_30m に割り当てるか★を 新しく決める必要があります★★。

2-1. ★★割り当て規則(★これをそのまま実装すること★)★★

# 規則
★1★ ★★status_1h には precheck === PRECHECK_MINUTES_BEFORE[0](= 60)の行を割り当てる★★
★2★ ★★status_30m には precheck === PRECHECK_MINUTES_BEFORE[1](= 30)の行を割り当てる★★
★3★ ★★一致する行が無ければ robotStatusFromErrors([])(= ROBOT_STATUS_STANDBY = Standby.)★★ — ★★既存の挙動と同じ★★。★ROBOT_STATUS_STANDBY を直書きせず、既存関数に空配列を渡す形を保つこと★
★4★ ★★PRECHECK_MINUTES_BEFORE の並び順が列の順★★([0] → status_1h / [1] → status_30m)。★★添字を直書きせず、配列を map して分割代入する現行の形を保つこと★★
★5★ ★★PRECHECK_MINUTES_BEFORE に3つ目以降があっても、status_1h / status_30m の2列しか無いので3件目以降は捨てられる★★ → ★★申し送り①に書くこと★★

★★2列固定でよいことは確定しています★★ — ★★ユーザー回答(2026-09-11)「これは2回だけです、3回以上にはしません」★★。 ★★したがって status_1h / status_30m の2列固定で設計してよく、配列化(precheck_statuses: [{minutes, status}] 等)は不要です★★。 ★★API契約(d-watch-api.yaml)を改訂する必要もありません★★(禁止事項11)。

★★ただし規則5 の「3件目が黙って捨てられる」という実装上の性質そのものは残ります★★ — ★★将来 PRECHECK_MINUTES_BEFORE を3要素にしたら壊れます★★(★例外もテスト失敗も出ないまま3件目が消える★)。 ★★だから期待値表 #8 でこの挙動をテストに固定し、申し送り①に残すこと★★ (★「今は起きない」ことと「起きたら静かに壊れる」ことは別の話です★)。

★★なぜこの規則か★★:

  • ★★フィールド名(status_1h / status_30m)は API 契約で固定されており、変えられません★★(禁止事項10)。 ★★事前チェックは2回で固定と確定したので、2列のままで足ります★★
  • ★PRECHECK_MINUTES_BEFORE は ★DFA が何分前にチェックしたいか★ の設定値(調査1)★ — ★★「60分前のチェック結果を status_1h の列に出す」は変換ではなく画面の列の割り当て★★なので、 ★D-077(値を変換しない)に触れません★
  • ★★設定値以外の分数(例 20)の行は、どちらの列にも出ません★★ — ★これは既存の挙動と同じ(現行も rounds に無い値は拾わない)★

2-2. ★★純粋関数に切り出すこと★★

★★cleanLogs() の中に書かず、純粋関数として切り出してテストで固定すること★★(CLAUDE.md §6-2 / §9.4)。

  • ★置き場所: ★lib/db/precheck-mapping.ts★(★precheck の値の読み方を扱う関数はここに集めてある★)
  • ★名前は実装者判断★(例: statusColumnsFromPrecheckRows())
  • ★引数は「precheck の値 → その行のエラー配列」の ReadonlyMap<number, RobotErrorLike[]>★ (★★clean_timetables の行そのものを渡すと DB の型に依存してテストが書けなくなる★★ — ★呼び出し側で Map に詰め替えてから渡すこと★)
  • ★戻り値は [status1h, status30m] のタプル★
  • ★★robotStatusFromErrors() は lib/db/clean-log-mapping.ts から import して使うこと★★ (★★コピーしない。規則を2箇所に書かない。D-103★★。★RobotErrorLike 型も同ファイルから★)

2-3. ★★期待値表(★これをそのままテストにすること。実装より先に書く★)★★

★共通の前提★: PRECHECK_MINUTES_BEFORE = [60, 30]。★表では E = 何らかのエラー1件(error_type は生値)、 S = ROBOT_STATUS_STANDBY(Standby.)と書きます★。★★テストでは分数を直書きせず PRECHECK_MINUTES_BEFORE[0] / [1] を使うこと★★(禁止事項6)。

# 入力(precheck 値 → エラー配列) ★期待 status_1h★ ★期待 status_30m★ 意図
★1★ {}(★事前チェック行が1つも無い★) ★S★ ★S★ ★既存の挙動(エラーが無い=待機)★
★2★ {60: [], 30: []} ★S★ ★S★ ★行はあるがエラーが無い★
★3★ {60: [E1], 30: []} ★E1 の生値★ ★S★ ★60分前だけ異常★
★4★ {60: [], 30: [E2]} ★S★ ★E2 の生値★ ★30分前だけ異常★
★5★ {60: [E1], 30: [E2]} ★E1 の生値★ ★E2 の生値★ ★★列が入れ替わっていないこと★★
★6★ ★★{1: [E1], 2: [E2]}(★旧コード値の行しか無い★)★★ ★★S★★ ★★S★★ ★★再シード前のローカルDBがこうなる(調査4)★★。★★1 を60分前に読み替えないこと★★
★7★ {20: [E1]} ★S★ ★S★ ★設定値以外の分数はどちらの列にも出ない★
★8★ {60: [E1], 30: [E2], 20: [E3]} ★E1★ ★E2★ ★★3件目が捨てられる(申し送り①)★★
★9★ {60: [E1, E1b]} ★robotStatusFromErrors([E1, E1b]) と同じ値★ ★S★ ★★複数エラーの畳み方は既存関数に委ねる(自分で書かない)★★

★★#6 を必ず入れること★★ — ★★これが「真逆だった変換を消した」ことの証明です★★。

2-4. 実装

lib/db/precheck-mapping.ts:

  1. ★2-2 の純粋関数を追加する★
  2. ★docstring に 2-1 の規則5項目と「なぜこの規則か」を書く★。★★特に「3件目以降は捨てられる」ことと、 「フィールド名は API 契約なので変えられない」ことを明記する★★

lib/db/index.ts:

  1. ★:307-313 を新しい純粋関数の呼び出しに置き換える★
    • ★precheckRows(Map<number, timetable行>)から「precheck 値 → エラー配列」の Map に詰め替える★
    • ★★errorsByTimetableId の索引の作り方(:246-265)は変えない★★
  2. ★★CLEAN_TIMETABLE_PRECHECK.rounds への参照がこのファイルから消えていること★★を確認する (★CLEAN_TIMETABLE_PRECHECK.main の5箇所は残る★)
  3. ★コメント(:306 の「precheck の順(60分前 → 30分前)は lib/config の定義に従う」)を、 ★★新しい規則を説明するものに書き換える★★

この段階の完了条件

  • ★期待値表9ケースがテストになっており、全部通る★
  • ★★lib/db/index.ts から CLEAN_TIMETABLE_PRECHECK.rounds の参照が消えている★★
  • ★★status_1h / status_30m というフィールド名が変わっていない★★(禁止事項10)
  • ★★clean-log-mapping.ts を1文字も変えていない★★(★robotStatusFromErrors() は import して使う★)
  • ★npx tsc --noEmit = 0 / npm run lint = 0 / npm test 全パス★
  • ★git diff --stat が ★lib/db/precheck-mapping.ts / lib/db/index.ts / lib/db/__tests__/precheck-mapping.test.ts の3ファイルだけ★★
  • ★★この時点でローカルDBの status_1h / status_30m が全行 Standby. になること(調査4)を承知しておくこと★★。 ★★段階4 で解消します。ここで「壊れた」と判断して段階1・2 を戻さないこと★★
  • ★★コミット(ファイルパスを明示)★★
    • 例: git commit -m "fix: 指示書X 段階2 事前チェックの列割り当てをPRECHECK_MINUTES_BEFOREに合わせる"

段階3 — ★訂正版DDL(0911版)で dwatch_local を作り直し、db pull し直す★(1コミット)

3-0. ★★なぜこの段階があるのか★★

★2026-09-11、TeamWorks から ★★訂正版DDL★★ を受領しました★ — C:\Users\daich\Downloads\clean_watch_20260911.sql(★実測: 44,923 bytes / -- Dump completed on 2026-09-11 16:50:36★)。

★★どのみち段階4 で precheck の値を入れ替えるために再シードします★★。 ★★同じタイミングでDBごと作り直すのがいちばん効率的★★なので、この段階を挟みます。

3-1. ★★0909版との差分(★監督が diff で実測済み。着手時に自分でも取り直すこと★)★★

diff "C:\Users\daich\Downloads\clean_watch_20260909.sql" "C:\Users\daich\Downloads\clean_watch_20260911.sql"

★★実質的なスキーマ差分は 2件だけ です★★:

# 差分 出所
★1★ ★★facility_contacts.email が varchar(255) → varchar(100)★★ ★第7回 A-3b が反映された★
★2★ ★★robot_task_versions.clean_schedule_id に 索引(KEY)と外部キー(CONSTRAINT)が追加された★★ ★第7回 B-2 が反映された★

★★残りの差分は AUTO_INCREMENT の現在値と -- Dump completed 行だけ★★ — ★★データ量の違いであってスキーマではありません★★。★★テーブル数は 39 で変わりません★★ (grep -c "^CREATE TABLE" で両方 39。★自分で確認すること★)。

★★未反映のまま残ったもの(★4回目の依頼になるが、開発は止まらない★)★★:

# 項目 ★0911版での実測★
★A-2★ ★履歴テーブルの id を AUTO_INCREMENT で採番★ ★★clean_status_histories は今も主キーが無く、一意キー(clean_status_histories_id_unique)のみ★★。clean_timetable_histories は PRIMARY KEY はあるが AUTO_INCREMENT は無い
★B-3b★ ★security_status_histories.reported_at に DEFAULT★ ★timestamp NOT NULL(DEFAULT なし)のまま★
  • ★★この2件は本書では扱いません★★(★TeamWorks 側の作業★)。★段階6 の記録に「4回目の依頼になる」ことだけ残すこと★
  • ★★シードは A-2 の影響を既に吸収しています★★(id を明示採番している。落とし穴11)。★作り直しても変わりません★

3-2. ★★作り直す前に、既存の実測値を控えること(★必須★)★★

★★DROP した瞬間に取り直せなくなります★★。★段階0 で控えた値が手元にあることを確認してから進むこと★:

# 控えるもの 取り方
★1★ ★clean_timetables.precheck の分布 / 総行数 / robot_error_reports 件数 / error_id の形 / clean_status_histories.clean_error の分布★ ★調査2 の mysql.exe コマンド★
★2★ ★db.prechecks() / db.cleanLogs() の件数と内訳★ ★段階0 の一時スクリプト★
★3★ ★★prisma/schema.prisma の現物★★ ★★git show HEAD:prisma/schema.prisma > <リポジトリ外の作業パス>/schema.head.prisma★★(★docs/ に置かないこと★。指示書E :75)
★4★ ★prisma/schema.prisma のコメント行数・モデル数★ ★grep -c "^\s*//" prisma/schema.prisma / grep -c "^model " prisma/schema.prisma(★作成時点の実測: コメント 153行 / モデル 39。★鵜呑みにせず自分で数えること★)★

★★段階0 で控えていなかったら、ここで戻って取ること★★。★DROP は取り返しがつきません★。

3-3. ★dwatch_local を 0911版DDL から作り直す★

★★前例があります★★ — ★0909版を投入した記録が ★D-095(docs/decisions.md:3978 付近。「実施したこと(2026-09-09)」)★ と ★指示書Q(docs/handoff/指示書Q_スキーマを最新DDL0909に追従させ再シードする.md。D-096 で実行済み)★ にあります★。 ★★着手前に両方読み、同じ手順を踏むこと★★(★手順を発明しないこと★)。

★MySQL CLI★:

"/c/Program Files/MySQL/MySQL Server 8.4/bin/mysql.exe" -u root --default-character-set=utf8mb4
  • ★★--default-character-set=utf8mb4 は必須★★(★付けないと日本語が化ける★)
  • ★パスワードなし★(★-p を付けないこと★)
  • ★★対象は dwatch_local だけ★★ — ★★dwatch_ddl / dwatch_ddl_0907 / dwatch_ddl_0909 に触らないこと★★(D-095)。 ★★TeamWorks の本番DBには絶対に接続しないこと★★(CLAUDE.md §7 / 禁止事項5)

★★DDL は MariaDB 10.11 のダンプです★★(★ヘッダの Server version 5.5.5-10.11.13-MariaDB-log★)。 ★★MySQL 8.4 で通らない構文があったら、そこで止めてその箇所(行番号と構文)を報告すること★★(D-100。★推測で書き換えないこと★)。 ★0909版は同じ形で投入できているので、同じ手順が通る見込みです★。

3-4. ★db pull と generate をやり直す★

npx prisma db pull
npx prisma generate
  • ★★--force を付けないこと★★(★enrich が働かず、PascalCase モデル名と @@map が捨てられる★。指示書E :86 / 指示書Q 2-1)
  • ★★--print で別ファイルへ出す方式も取らないこと★★(同じ理由)
  • ★接続先は prisma7.config.ts が DATABASE_URL から読む★ — ★★dwatch_local を指していることを目視で確認してから実行すること★★
  • ★★db push / prisma migrate は絶対に使わないこと★★(禁止事項4)
  • ★lib/generated/prisma は .gitignore 済み(.gitignore:47)なので、generate の出力はコミット対象になりません★

3-5. ★★生成結果の差分を必ず目で確認する(★型が変わっていないかを報告すること★)★★

★★prisma/schema.prisma は生成物なので手で書き換えません★★(CLAUDE.md §7 / D-056)。 ★★ただし「生成結果が予想どおりか」は人が確認します★★。

★★予想される差分(★これ以外が出たら止めて報告すること★)★★:

# 予想される差分 根拠
★1★ ★★FacilityContact.email が @db.VarChar(255) → @db.VarChar(100)★★ ★3-1 の差分1★
★2★ ★★RobotTaskVersion に clean_schedule CleanSchedule @relation(...) と @@index([clean_schedule_id], map: "robot_task_versions_clean_schedule_id_foreign") が追加される★★ ★3-1 の差分2。★FK が付いたので Prisma がリレーションとして拾う★★
★3★ ★★CleanSchedule に逆リレーション robot_task_versions RobotTaskVersion[] が追加される★★ ★同上(★Prisma は両側に張る★)★
★4★ ★★全コメント(★作成時点で153行★)が消える★★ ★★db pull はコメントを保持しません★★(指示書Q 2-2)

★★#1〜#3 以外の型・@db.* 属性・@default が動いていたら、★そこで止めて報告すること★★★ (★0909版から動いていないはずです★)。★★特に率・面積の Float(D-094)が戻っていないことを確認すること★★。

★★robot_task_versions はアプリコードから1箇所も参照されていません★★(★実測: lib/ / app/ / scripts/ にヒット0件★)。 ★★したがって #2・#3 はコードに影響しません★★。★tsc が落ちたら、それは想定外なので報告すること★。

3-6. ★★コメントを移植し直す(★ここが最大の作業です★)★★

★★db pull はコメントを保持しないので、コメントが全部消えます★★ (★作成時点の実測で 153行。★3-2 #4 で自分が数えた値を使うこと★)。 ★指示書Q 2-2(docs/handoff/指示書Q_スキーマを最新DDL0909に追従させ再シードする.md:108)と 指示書E 手順1・2(docs/handoff/指示書E_スキーマを実DDLに一致.md:69-91)に従って移植すること★:

  1. ★3-2 #3 で取った控え(schema.head.prisma)と作業ツリーを diff しながら、★コメント(// 行)だけ★を移植する★
  2. ★★移植してはいけないもの★★: ★型・@db.* 属性・@default — ★これらは db pull の生成結果が正★(D-056)
  3. ★★移植後に grep -c "^\s*//" prisma/schema.prisma を数え、★控えた行数から大きく減っていないこと★を確認する★
  4. ★datasource db ブロックの「Prisma 7 では接続URLを書けない」コメントも消えるので戻すこと★
  5. ★冒頭の「db pull 実施履歴」に ★3回目 2026-09-11 / 元DDL: clean_watch_20260911.sql / 投入先: dwatch_local★ を足すこと★
  6. ★★Laravel 基盤10テーブルと clean_link_schedules を削除しないこと★★(指示書Q 2-5(docs/handoff/指示書Q_スキーマを最新DDL0909に追従させ再シードする.md:174)/ D-058)
  7. ★@updatedAt と @default(now()) を受領DDL で grep して確かめ、db pull が落としたものを戻すこと★ (★指示書Q 2-3(:127)の作法。★★推測で付けないこと★★。0909版では ON UPDATE current_timestamp が 20テーブル/21列だった★)

★★移植のついでに「もう正しくないコメント」を書き直すこと★★ — ★★これは生成物の手書き改変ではありません★★(★コメントは db pull が生成しないので、人が書くのが正規の手順です★。指示書Q 2-2):

# 場所 ★今の記述★ ★★書き直す内容★★
★1★ ★CleanTimetable.precheck(★作成時点 :581-583★)★ 「TeamWorks回答(B-1): 0: 本清掃 / ★1: 1回目 / 2: 2回目★」 ★★0 = 本清掃 / ★それ以外はその数値がそのまま「分前」★(TeamWorks 第7回 A5-2 / A5-5。D-112)。★1 は「1回目」ではなく「1分前」(A5-3)★★★
★2★ ★CleanTimetableHistory.precheck(★作成時点 :611-613★)★ ★同上★ ★同上★
★3★ ★FacilityContact.email(★作成時点 :808-811★)★ 「実DDL: varchar(255) … ★2026-09-09時点でも未反映★」 ★★「第7回 A-3b が 0911版DDL で反映された(varchar(100))」★★
★4★ ★RobotTaskVersion.clean_schedule_id(★作成時点 :257-259★)★ 「実DDLには FK も KEY も無く … ★リレーション対象から外した★」 ★★「第7回 B-2 が 0911版DDL で反映され、FK と索引が付いた。db pull がリレーションとして拾うようになった」★★

★★起草時の申し送り②への答え★★ — ★★「db pull をやり直せば precheck のコメントが自動で直る」わけではありません★★。 ★db pull はコメントを消すだけです★。★★直るのは、この 3-6 の移植で人が書き直したときです★★。 ★★だから 3-6 の #1・#2 は必須作業です★★(★忘れると、次に読む人がまた「1 = 1回目」と信じます★)。

この段階の完了条件

  • ★dwatch_local が 0911版DDL から作り直されている★(★テーブル数 39★。★★この時点では全テーブル0行★★)
  • ★★dwatch_ddl / dwatch_ddl_0907 / dwatch_ddl_0909 / 本番DB に触っていない★★
  • ★npx prisma validate が通る★ / ★モデル数 39★(grep -c "^model " prisma/schema.prisma)
  • ★★3-5 の予想差分 #1〜#3 が出ており、それ以外の型変更が無いことを目で確認した★★(★★報告に書くこと★★)
  • ★★コメントを移植し、3-6 の表4件を書き直した★★(★コメント行数が控えた値から大きく減っていない★)
  • ★npx tsc --noEmit = 0 / npm run lint = 0 / npm test 全パス★ (★★DBは空だがテストはDBを見ないので緑のまま★★。調査4 と同じ理屈)
  • ★git diff --stat が ★prisma/schema.prisma の1ファイルだけ★★ (★lib/generated/prisma は .gitignore 済み★)
  • ★★コミット(ファイルパスを明示。git add -A 禁止)★★
    • 例: git commit -m "chore: 指示書X 段階3 schema.prismaを訂正版DDL0911に追従させる"

★★この時点でローカルDBは空です★★。★★段階4 の再シードで埋まります★★ — ★ここで画面を見て「壊れた」と判断しないこと★。


段階4 — ★シードを 0 / 60 / 30 にし、rounds を削除して再シードする★(1コミット)

4-1. ★★scripts/seed-local-db.ts の変更(★これだけ★)★★

:802-830 付近(★実測。原文★):

const statuses = precheckStatuses(log);
for (const [i, minutesBefore] of PRECHECK_MINUTES_BEFORE.entries()) {
  const precheckId = log.id + PRECHECK_TIMETABLE_ID_OFFSETS[i];
  const precheckStartAt = addMinutes(startAt, -minutesBefore);
  const precheckCode = CLEAN_TIMETABLE_PRECHECK.rounds[i];        // ← ★消す★
  …
      precheck: precheckCode,                                     // ← ★minutesBefore に変える★
  …
  const errorKey = `RER-${log.id}-${precheckCode}`;               // ← ★minutesBefore に変える★

★★変更点は2箇所だけ★★:

# 変更 影響
★1★ ★precheck: precheckCode → ★precheck: minutesBefore★★ ★clean_timetables.precheck が 60 / 30 になる★
★2★ ★errorKey の precheckCode → ★minutesBefore★★ ★★error_id が RER-1011-1 → RER-1011-60 に変わる★★(★調査3 の #4。★意図した変化★)

★★他は1行も変えないこと★★:

  • ★PRECHECK_MINUTES_BEFORE.entries() のループそのもの★(★minutesBefore は既にこのループが持っている★)
  • ★PRECHECK_TIMETABLE_ID_OFFSETS(:211)★ — ★★clean_timetables.id の採番は変えない★★(★行の同一性が変わると差分が読めなくなる★)
  • ★precheckStartAt = addMinutes(startAt, -minutesBefore)★ — ★元から正しい★
  • ★本清掃行の precheck: CLEAN_TIMETABLE_PRECHECK.main(:797 / :990)★ — ★変えない★
  • ★historyIndex / errorReportCount の通番ローテーション★ — ★★行を足さないので自然に保たれる★★(落とし穴10)
  • ★施設7 の検証データブロック(:960-1016。指示書V 段階6)★ — ★事前チェック行を作らないので無関係★

4-2. ★★lib/config/index.ts の変更★★

  1. ★★CLEAN_TIMETABLE_PRECHECK から rounds を削除する★★(★★最後の消費者が段階1・2・4-1 で消えた★★)
    • ★★main は残す★★(禁止事項9。★lib/db/index.ts 5箇所 / scripts/seed-local-db.ts 2箇所 / lib/db/precheck-mapping.ts / ★未実行の指示書W★★)
    • ★★オブジェクトの形({ main: 0 } as const)は保つこと★★ — ★CLEAN_TIMETABLE_PRECHECK.main という呼び出し側の書き方を変えないため★
  2. ★★docstring(:36-48)を書き換える★★:
    • ★★消す★★:
      • 「0 = 本清掃 / 1 = 1回目の事前チェック / 2 = 2回目の事前チェック」という ★コード表の説明★
      • 「rounds[i] は PRECHECK_MINUTES_BEFORE[i] に対応する(rounds[0]=1 が 60分前、rounds[1]=2 が 30分前)」
      • ★★「D-091 項4 / D-093 項2 で『0 / 60 / 30』に変わる予定」「現行DDLは 0/1/2 のままなので、切り替わるまではこの表を正とする」★★
    • ★★書く★★:
      • ★★「D-091 項4 / D-093 項2 の予定は 実現した(TeamWorks 第7回 A5-2。2026-09-11 受領。D-112)」★★
      • ★0 = 本清掃 / ★それ以外はその数値がそのまま「分前」★(A5-5)
      • ★★1 は「1回目」ではなく「1分前」(A5-3)★★
      • ★★新旧の混在は無い(A5-4)★★
      • ★変換は lib/db/precheck-mapping.ts の precheckMinutesFromCode() 1箇所だけ★
  3. ★★PRECHECK_MINUTES_BEFORE(:33-34)の docstring を足す★★ — ★★役割が変わったことを明記する★★:
    • ★★「これは DFA 側の設定値(何分前にロボット状態をチェックしたいか)であって、 clean_timetables.precheck を読み替えるための対応表ではない」★★
    • ★消費者: システム管理画面の表示(components/features/system/SystemScreen.tsx:131)/ status_1h / status_30m の列の割り当て(lib/db/precheck-mapping.ts)/ シードの start_at と precheck の生成(scripts/seed-local-db.ts)/ fixtures(lib/fixtures/prechecks.ts)★
    • ★★「実際に何分前のチェックだったかは DB の precheck が正」★★(★★D-105 項13-2 の未決に決着がついた★★)

4-3. 再シードと実測

★★段階3 でDBを作り直したので、この時点では全テーブル0行です★★。 ★★このシードが「作り直した0911版スキーマの上に、分数(0 / 60 / 30)で入れ直す」1回目になります★★。

npm run seed:local
  • ★★シードが 0911版スキーマで通らなかったら止めて報告すること★★ — ★スキーマ差分は facility_contacts.email の桁縮小(255→100)と robot_task_versions の FK 追加の2件だけです★。 ★★どちらもシードに影響しない見込み★★ — ★シードが入れるメールアドレスは lib/fixtures/facilities.ts 由来で ★すべて 30文字未満★(実測。store009@example.com 等)★、 ★robot_task_versions は ★シードが1行も投入していない★(実測: scripts/seed-local-db.ts にヒット0件)★

★★再シード後に実測して控える★★:

"/c/Program Files/MySQL/MySQL Server 8.4/bin/mysql.exe" -u root --default-character-set=utf8mb4 dwatch_local -e "
SELECT precheck, COUNT(*) c FROM clean_timetables GROUP BY precheck ORDER BY precheck;
SELECT COUNT(*) AS total FROM clean_timetables;
SELECT COUNT(*) AS reports FROM robot_error_reports;
SELECT error_id FROM robot_error_reports ORDER BY error_id LIMIT 5;
SELECT clean_error, COUNT(*) FROM clean_status_histories GROUP BY clean_error ORDER BY clean_error;"

★★期待★★:

# 項目 ★期待★
★1★ precheck の分布 ★★0 = 259 / 60 = 253 / 30 = 253★★(★★1 / 2 が 0件★★)
★2★ clean_timetables の総行数 ★★765(着手前と同じ)★★
★3★ robot_error_reports の件数 ★★14(着手前と同じ)★★
★4★ error_id の形 ★★RER-<id>-60 / RER-<id>-30★★(★-1 / -2 が無いこと★)
★5★ clean_error の分布 ★★0=231 / 1=13 / 2=5 / 3=5 / 4=5(★着手前と同じ★)★★(★動いていたら落とし穴10★)
★6★ ★2回シードしても同じ★ ★もう一度 npm run seed:local を流し、★1〜5 が同じ値★になること★(★冪等。落とし穴11★)

この段階の完了条件

  • ★★CLEAN_TIMETABLE_PRECHECK.rounds がリポジトリのコードから消えている★★ (grep -rn "CLEAN_TIMETABLE_PRECHECK.rounds" --include=*.ts --include=*.tsx . で ★ヒット0件★。 ★★docs/ のヒットは残ってよい★★ — ★docs/code-map.md / 管理画面_設計検討_20260910.md は禁止事項2、 過去の指示書・docs/decisions.md は追記型の台帳なので過去の記述を消さない★)
  • ★★PRECHECK_MINUTES_BEFORE は残っており、SystemScreen.tsx / lib/fixtures/prechecks.ts に差分が無い★★
  • ★npx tsc --noEmit = 0 / npm run lint = 0 / npm test 全パス★(★テストは増減しない段階★)
  • ★4-3 の6項目を実測した★
  • ★git diff --stat が ★lib/config/index.ts / scripts/seed-local-db.ts の2ファイルだけ★★ (★★lib/fixtures/ に差分が無いこと★★。★★prisma/schema.prisma は段階3 でコミット済みなので、この段階では新たな差分が出ないこと★★)
  • ★★コミット(ファイルパスを明示)★★
    • 例: git commit -m "chore: 指示書X 段階4 シードのprecheckを分数にしてroundsを削除する"

段階5 — ★実測での検証(★コミットなし★)★

5-1. ★データ層の検証★

★★.env.local を書き換えず、コマンドラインで env を渡すこと★★(★戻し忘れを防ぐ★)。 ★★一時スクリプトには成功時にも必ず process.exit(0) を書くこと★★(禁止事項18。段階0 の雛形を使う)。

USE_FIXTURES=false DATABASE_URL="mysql://root@localhost:3306/dwatch_local" npx tsx ./scripts/tmp-measure.ts

★★期待値(★1つでも外れたら実装が違う★)★★:

# 項目 ★期待★
★1★ db.cleanLogs() の件数 ★★259(着手前と同じ)★★
★2★ db.prechecks() の件数 ★★27(着手前と同じ)★★
★3★ severity の内訳 ★★precheck 14 / launch_failed 13(着手前と同じ)★★
★4★ ★★precheck_minutes の内訳★★ ★★60 = 8 / 30 = 6 / null = 13(★★着手前と完全に同じ★★)★★ ← ★★これが「正しく直った」証明★★
★5★ ★precheck_minutes に 1 / 2 が出ないこと★ ★★0件★★(★出たら再シードが効いていない★)
★6★ ★status_1h の内訳★ ★★Standby. 251 / セキュリティ有効 2 / オフライン 3 / Handrail Lifted 1 / 充電中 1 / 緊急停止 1(★非 Standby. = 8。着手前と同じ**★)★★
★7★ ★status_30m の内訳★ ★★Standby. 253 / セキュリティ有効 2 / オフライン 3 / Handrail Lifted 1(★非 Standby. = 6。着手前と同じ**★)★★
★8★ ★PrecheckItem.id★ ★★RER-<id>-60 / RER-<id>-30 に変わっている★★(★★これだけが変わってよい値★★。調査3 #4)

★★#6・#7 が全行 Standby. になっていたら再シードが効いていません★★(調査4)。 ★★#4 が 1=8 / 2=6 になっていたら段階4 の 4-1 を入れ忘れています★★。

  • ★★一時スクリプトは検証後に削除すること★★(★git status に残さない★)

5-2. ★画面(回帰していないこと)★

画面 期待
★/alerts★ ★200 / ★27件(launch_failed 13)★。★★種別ラベルが「60分前異常」「30分前異常」「出発失敗」のまま★★(★「1分前異常」が出たら再シードが効いていない★)
★/detail-log★ ★200 / ★259件★。★1時間前・30分前の状態列が着手前と同じ見た目★
★/system★ ★★200 / 「第1回チェック 60分前」「第2回チェック 30分前」が消えていない★★(★★PRECHECK_MINUTES_BEFORE を消していないことの確認★★)
★/dashboard★ ★200 / 9 / 9★(D-099)
★/reports★ ★200(KPI・施設別・エラー内訳)★。★連続問題施設セクションは ★★指示書W の実行状況次第★★(★未実行なら 500・実行済みなら 200。★どちらでも本書の範囲外★)★
/facilities / /schedules / /ds-logs / /ds-alerts ★200。回帰していないこと★
  • ★★404 が出たら Edge Runtime バンドル破壊の回帰を疑うこと★★(D-086)。★500 = 未実装 / 404 = ビルドが壊れている★
  • ★Browser pane(preview_start)が使えなければ curl で代替し、★目視できなかった項目を必ず記録する★★
  • ★★next dev は .env.local を共有する別セッションに影響します(D-108 項8)★★ — ★next build && next start -p <別ポート> で自分専用のサーバーを起てるほうが安全★

5-3. ★USE_FIXTURES=true での確認★

  • ★★fixtures 側の件数・分数・ラベルが1つも変わっていないこと★★(禁止事項12)
    • ★lib/fixtures/prechecks.ts は precheck_minutes を直接持っており、DBのコード値を経由しない★
    • ★★だから本書の変更の影響を受けないのが正しい★★(★変わったら fixtures を触ってしまっている★)
    • ★/alerts に「20分前異常」(lib/fixtures/prechecks.ts:90 の境界値行)が出ることも確認する★

この段階の完了条件

  • ★5-1 の8項目・5-2 の回帰確認・5-3 をすべて実測した★
  • ★★一時スクリプトを削除した★★
  • ★★.env.local が USE_FIXTURES=true のままであることを確認した★★
  • ★git status に lib/ scripts/ docs/ 以外の差分が無いことを確認した★ (★README.md / docs/code-map.md / docs/handoff/管理画面_設計検討_20260910.md / docs/handoff/指示書W_… は ★着手前と同じ状態のまま★ であること。禁止事項2)

段階6 — 記録と push(1コミット)

6-1. docs/decisions.md に追記(★D-114 の見込み。★着手直前に実測した番号を使うこと★)

★★D-112 は別担当が「第7回回答の受領」として追記済み(docs/decisions.md:6706。08acc89)、 D-113 は指示書W が追記する見込みです★★ — ★★その番号を奪わないこと★★。★★必ず grep -nE "^### D-(0|1)[0-9][0-9]" docs/decisions.md | tail -3 で実測してから採番すること★★。

★見出しの形式は既存に揃える★: ### D-114 \[確定]` `precheck` を分数そのものとして読み、訂正版DDL(0911)に追従する(コード表を廃止した)`

★最低限入れる項目★:

# 項目
1 ★★TeamWorks 第7回 A5-1〜A5-5 の原文を引用する★★(★★引用は原文ママ。要約に置き換えないこと(D-077 の思想)★★)
2 ★★現行実装が真逆だったこと★★: ★1 を60分前・2 を30分前と読んでいたが、正しくは1分前・2分前★
3 ★★新しい規則: 0 = 本清掃(precheck_minutes = null)/ それ以外は生値がそのまま分数★★。★変換は precheckMinutesFromCode() 1箇所だけ★
4 ★★CLEAN_TIMETABLE_PRECHECK.rounds(コード表)を削除した★★。★main は残した理由(lib/db/index.ts / シード / ★指示書W★ が使う)★
5 ★★PRECHECK_MINUTES_BEFORE は残した★★。★★役割が「DBの値の読み替え表」から「DFA 側の設定値(何分前にチェックするか)」+「status_1h / status_30m の列の割り当てキー」に変わった★★。★消費者4箇所を列挙する★
6 ★★status_1h / status_30m の割り当て規則★★(段階2 の 2-1 の5項目)。★フィールド名は API 契約なので変えていない★
7 ★★負の値・想定外の値は生値をそのまま返す(例外を投げない・丸めない)と決めたこと、およびその根拠4点★★(段階1 の 1-2)
8 ★★D-105 項1 の「新旧2つの値域を1つの式で吸収している」という設計を撤回した★★ — ★A5-4「混在しません」により、吸収する必要が無くなった★
9 ★★D-105 項13-1(新旧混在の有無)の未決が消滅した★★(A5-4 で「混在しない」と確定)
10 ★★D-105 項13-2(PRECHECK_MINUTES_BEFORE と DB の precheck のどちらが正か)の未決が消滅した★★ — ★★DB の precheck が正。設定値は DFA 側の希望値であり、実際に何分前だったかは DB を見る★★
11 ★★指示書S のレビューで挙がった「新旧混在に耐えるという主張が成立していない」という指摘は、論点そのものが消滅した★★ — ★★混在しないので、耐える必要が無い★★。★★「指摘が誤りだった」のではなく「前提が変わって不要になった」と書くこと★★
12 ★★D-105 項13-3(precheck に入りうる値の範囲)も A5-5 で解決★★(★「0以外の予約語はありません」★)。★★ただし UnsignedTinyInt なので 255分前(4時間15分前)までしか表現できない制約は残る★★
13 ★シードを 0 / 60 / 30 に直し、再シードしたこと★。★★error_id が RER-<id>-1 → RER-<id>-60 に変わった(= PrecheckItem.id が変わった)★★
14 ★段階5 の実測値(★prechecks() 27件・precheck_minutes 60=8 / 30=6 / null=13 が着手前と完全に同じであること★ / cleanLogs() 259 / status_1h 非 Standby. 8 / status_30m 非 Standby. 6 / clean_error 分布不変 / テスト数)★
15 ★★変えなかったもの★★: ★d-watch-api.yaml(既に「任意の分数」と書いてある)/ lib/types/ / lib/fixtures/ / lib/prechecks/label.ts / clean-log-mapping.ts★(★★prisma/schema.prisma は db pull で更新した。項16 参照★★)
16 ★★訂正版DDL(clean_watch_20260911.sql。2026-09-11 受領)で dwatch_local を作り直し、db pull / generate をやり直したこと★★。★0909版との差分は ★facility_contacts.email の varchar(255) → varchar(100)(第7回 A-3b)★ と ★robot_task_versions.clean_schedule_id への索引・外部キー追加(第7回 B-2)★ の2件のみ(残りは AUTO_INCREMENT の現在値)。★テーブル数 39 は不変★。★★未反映のまま残った第7回 A-2(履歴テーブルの id の AUTO_INCREMENT)と B-3b(security_status_histories.reported_at の DEFAULT)は4回目の依頼になるが、開発は止まっていない★★
17 ★★prisma/schema.prisma の生成結果を目で確認した内容★★(★予想どおりの3差分のみで、他の型は動いていないこと★)。★★db pull はコメントを保持しないため移植し直し、そのときに「1: 1回目 / 2: 2回目」「email は未反映」「clean_schedule_id に FK 無し」の3種のコメントを書き直したこと★★
18 ★申し送り(下記「申し送り」節をそのまま移す)★

6-2. docs/handoff.md に追記(★追記34 の見込み★)

  • ★見出しは ## 2026-09-1X(追記34)★(★番号は実測すること★。★★別担当・指示書W が先に使っていたら次の番号★★)
  • ★冒頭の「最新の追記」ブロック(docs/handoff.md:6 付近)も更新すること★
  • ★入れる内容★:
    • ★★「clean_timetables.precheck は 分数そのもの。0 だけが本清掃。1 は『1回目』ではなく『1分前』」★★
    • ★★「コード表(CLEAN_TIMETABLE_PRECHECK.rounds)は削除した。★新旧混在は無い★(TeamWorks A5-4)」★★
    • ★★「PRECHECK_MINUTES_BEFORE は残っているが、役割は『DFA 側の設定値』+『status_1h / status_30m の列の割り当て』。★DBの値を読み替える表ではない★」★★
    • ★「status_1h / status_30m は PRECHECK_MINUTES_BEFORE に一致する precheck 行を割り当てる。★一致が無ければ Standby.★」★
    • ★★「dwatch_local は 0911版DDL(clean_watch_20260911.sql)から作り直した。schema.prisma は3回目の db pull 済み」★★
    • ★★「0909版からの差分は facility_contacts.email の varchar(100) 化(第7回 A-3b)と robot_task_versions.clean_schedule_id の索引・外部キー追加(第7回 B-2)の2件だけ。テーブル数 39 は不変」★★
    • ★★「第7回 A-2(履歴テーブルの id の AUTO_INCREMENT)と B-3b(security_status_histories.reported_at の DEFAULT)はまだ未反映。次に依頼すると4回目になる」★★
    • ★★「事前チェックは2回で固定と確定した(ユーザー回答 2026-09-11)。status_1h / status_30m の2列でよい」★★
    • ★次に触る人が踏みやすい落とし穴(★PRECHECK_MINUTES_BEFORE を消さない★ / ★CLEAN_TIMETABLE_PRECHECK.main を消さない★ / ★★db pull はコメントを消すので、再 pull したら移植し直す★★)★
    • ★申し送り★

6-3. push とこの段階の完了条件

  • ★★コミット(ファイルパスを明示。docs/decisions.md と docs/handoff.md のみ)★★
    • 例: git commit -m "docs: 指示書X完了(D-114)を記録する"
    • ★★作成時点では D-112 はコミット済み(08acc89)ですが、着手時に別担当の未コミットの追記が docs/decisions.md に入っている可能性があります★★ — ★★git add docs/decisions.md はそれごと巻き込みます★★。 ★着手直前に git status / git diff docs/decisions.md を確認し、★★他人の追記があれば、その担当が先に コミットするのを待つか、巻き込むことを承知のうえでコミットメッセージに明記すること★★★
    • ★★コミット前に git pull --rebase の要否を確認し、★競合したら自分の追記だけを残して番号を取り直すこと★★★
  • ★git push origin develop★
  • ★★本書の冒頭表と末尾「実行状態」表を更新すること★★
  • ★★README.md / docs/code-map.md / docs/handoff/管理画面_設計検討_20260910.md / docs/handoff/指示書W_連続問題施設を実DBから作る.md / .claude/worktrees/** に差分が無いことを最後に確認すること★★

★★申し送り★★

★★以下は本指示書では直しません。★★ ★D-114 に列挙して残すこと★。

# 論点 状態
★①★ ★★PRECHECK_MINUTES_BEFORE を3要素にすると3件目が黙って捨てられる★★ — ★CleanLogItem は status_1h / status_30m の ★2つ固定★(API契約)。★★例外もテスト失敗も出ないまま消えます★★。★段階2 の期待値表 #8 でこの挙動をテストに固定してある★ ★★確定:事前チェックは2回で固定★★(★ユーザー回答 2026-09-11「これは2回だけです、3回以上にはしません」★)。★★したがって status_1h / status_30m の2列固定でよく、API契約の改訂は不要★★。★★ただし「3要素にしたら静かに壊れる」という性質は残るので、将来 PRECHECK_MINUTES_BEFORE を増やす人への注意として残す★★
★②★ ★★prisma/schema.prisma のコメントが古い★★ — 「TeamWorks回答(B-1): 0: 本清掃 / 1: 1回目 / 2: 2回目」は ★もう正しくない★ ★★本書の段階3-6 で対応済み★★(★0911版DDL の受領を受けて db pull をやり直し、コメント移植のときに書き直した★)。★★db pull が自動で直すのではない★★ — ★db pull はコメントを消すだけで、正しい文言は人が書く★(★これは生成物の手書き改変ではない。指示書Q 2-2 / D-056★)
★③★ ★docs/code-map.md:881-882 / :1899 / :2288 と docs/handoff/管理画面_設計検討_20260910.md:215-216 に rounds の記述が残る★ ★★本書では触らない(禁止事項2。別作業の未追跡ファイル)★★。★その担当に伝えること★
★④★ ★関数名 precheckMinutesFromCode() の Code が実態と合わない★(★もうコード表を引かない★) ★★本書では改名しない★★(★名前だけの変更を本筋のコミットに混ぜない★)。★docstring に断り書きを入れてある★。★次に lib/db/precheck-mapping.ts を触るときに一緒に直すのが安全★
★⑤★ ★★precheck は UnsignedTinyInt(0〜255)なので、255分前(4時間15分前)より前のチェックは表現できない★★ ★★TeamWorks に伝えるかは未決★★(D-105 項13-3)。★現行の運用(60分前 / 30分前)では問題にならない★
★⑥★ ★★precheck = 1(1分前)のような現実的でない値が来ても、そのまま「1分前異常」と表示される★★ ★★これは意図した挙動★★(A5-3 / D-077)。★★おかしな値が画面に出ることで気づけるのが狙い★★。★「妥当な範囲」を当方で決めて弾かないこと★
★⑦★ ★★実データの precheck を実際に見て確認していない★★ — ★A5-2 は「修正済み」と言っているが、★★DFA はまだ実DBに繋いでいない★★ ★実DB接続時に SELECT precheck, COUNT(*) FROM clean_timetables GROUP BY precheck を最初に流すこと★。★★1 / 2 が出てきたら「1分前 / 2分前」と読むのが正しい(A5-3)★★

完了条件(全体)

# 条件
★1★ ★precheckMinutesFromCode() が「CLEAN_TIMETABLE_PRECHECK.main なら null、それ以外は生値」だけになっている★
★2★ ★★precheckMinutesFromCode(1) === 1 / (2) === 2(=従来と真逆)がテストで固定されている★★
★3★ ★期待値表9ケース(段階1)+9ケース(段階2)がすべてテストになっている★
★4★ ★負の値(-1)が例外にならず -1 を返すことがテストで固定され、その判断の根拠が docstring に書かれている★
★5★ ★★CLEAN_TIMETABLE_PRECHECK.rounds がコードから消えている(.ts / .tsx で grep 0件)★★
★6★ ★★CLEAN_TIMETABLE_PRECHECK.main が残っており、0 の直書きが1つも無い★★
★7★ ★★PRECHECK_MINUTES_BEFORE が残っており、SystemScreen.tsx / lib/fixtures/prechecks.ts に差分が無い★★
★8★ ★status_1h / status_30m の割り当てが純粋関数に切り出され、テストで固定されている★
★9★ ★★status_1h / status_30m のフィールド名が変わっていない★★
★10★ ★★旧コード値({1: [E], 2: [E]})が Standby. になる(60分前に読み替えない)ことがテストで固定されている★★
★11★ ★シードの precheck が 0 / 60 / 30 になり、再シード後の分布が 0=259 / 60=253 / 30=253★
★12★ ★★2回シードしても同じ結果(冪等)。乱数を使っていない・id を明示採番している★★
★13★ ★★db.prechecks() = 27件、precheck_minutes が 60=8 / 30=6 / null=13 で着手前と完全に同じ★★
★14★ ★★db.cleanLogs() = 259件、status_1h の非 Standby. = 8、status_30m の非 Standby. = 6 で着手前と同じ★★
★15★ ★clean_status_histories.clean_error の分布が着手前と同じ(0=231 / 1=13 / 2=5 / 3=5 / 4=5)★
★16★ ★npx tsc --noEmit = 0 / npm run lint = 0 / npm test 全パス(★テスト数は着手前より増えている★)★
★17★ ★★d-watch-api.yaml / lib/types/ / lib/fixtures/ / lib/prechecks/label.ts / lib/db/clean-log-mapping.ts に差分が無い★★
★17b★ ★★dwatch_local が訂正版DDL(clean_watch_20260911.sql)から作り直され、prisma/schema.prisma が db pull で追従している★★(★テーブル数 39 / モデル数 39 / npx prisma validate が通る★)。★★生成結果の差分が「FacilityContact.email の VarChar(100) 化」「RobotTaskVersion のリレーション追加」「CleanSchedule の逆リレーション追加」の3件だけであることを目で確認し、報告した★★
★17c★ ★★schema.prisma のコメントを移植し直し、precheck の「1: 1回目 / 2: 2回目」/ FacilityContact.email の「未反映」/ RobotTaskVersion.clean_schedule_id の「FK 無し」の3種を書き直した★★(★コメント行数が着手前の実測値から大きく減っていない★)
★18★ ★README.md / docs/code-map.md / docs/handoff/管理画面_設計検討_20260910.md / docs/handoff/指示書W_… / .claude/worktrees/** に触っていない★
★19★ ★★docs/decisions.md(★実測した空き番号。D-112 / D-113 を奪っていない★)と docs/handoff.md に記録した★★。★★D-105 項13-1・13-2 の未決消滅と、指示書S レビュー指摘の論点消滅を明記した★★
★20★ ★5コミット(段階1 / 2 / 3 / 4 / 6)で push した★
★21★ ★★一時スクリプトを削除し、.env.local が USE_FIXTURES=true に戻っている★★

実行状態

項目 内容
状態 ★完了★
実行日 2026-09-11
決定番号 ★D-113★(指示書W が未実行だったため、当初見込みの D-114 ではなく D-113 を採った)
コミット d8064a1(段階1)/ 1fafea7(段階2)/ 0b48ce4(段階3)/ fc99231(段階4)/ 本コミット(段階6)
実測値 tsc0 / lint0 / npm test 45ファイル941テスト / precheck_minutes内訳(60=8/30=6/null=13)が着手前と完全一致 / precheck分布が0=259/60=253/30=253に変化。詳細は docs/decisions.md の D-113 / docs/handoff.md 追記34 を参照
★DDL★ ★clean_watch_20260911.sql から dwatch_local を作り直し、db pull(3回目)/ generate 済み。0909版との実質差分は facility_contacts.email(varchar100化)と robot_task_versions.clean_schedule_id のFK追加の2件のみ★