clean_status_histories.clean_error で判定する| 項目 | 内容 | ||
|---|---|---|---|
| 実行環境 | ★Cursor 内の Claude が実装します★(.claude/commands/d-watch.md の役割分担表を参照) |
||
| 実行状態 | ★★実行済み(2026-09-10。D-107)★★ | ||
| 実行順 | ★指示書S(D-105)と 942225a(isFutureSchedule() の追加)の続き★。★指示書M / P / R / S が作った scripts/seed-local-db.ts / lib/db/prisma.ts / lib/db/local-guard.ts / lib/db/use-fixtures.ts / lib/db/ds-status.ts / lib/db/clean-log-mapping.ts / lib/db/precheck-mapping.ts / lib/prechecks/label.ts の上に積む作業★です。★prismaSource.facilities() / schedules() / dsDevices() / dsAlerts() は1文字も変えません★(cleanLogs() / prechecks() は本指示書の対象です)。★触るコードが重なる未実行の指示書は、着手直前に自分で確認すること★(作成時点では、指示書T(第5歩・レポートサマリ)が docs/decisions.md / docs/handoff.md を共通で触るので、記録の段階だけは順に行うこと) |
||
| 取得する決定番号 | ★**着手直前に `grep -nE "^### D-(0\ | 1)[0-9][0-9]" docs/decisions.md \ | tail -3で最終番号を実測すること**★。★★改訂時点で別担当がD-106(★本指示書の判定方針を確定した決定★)をdocs/decisions.mdに追記中なので、最初の空き番号は **D-107** の見込み★★。★それでも**必ず自分で実測してから採番する**こと★。★★^### D-0だけでは3桁台(D-100 以降)を拾えない★★。★2026-09-07 にD-068` の二重採番が実際に起きています★ |
| 前提 | ★★D-106★★(★本指示書の判定方針を確定した決定★。★別担当が改訂時点で追記中なので、着手直前に自分で行番号と内容を確認すること★。★「履歴の行が無い過去の本清掃は出発失敗として残す」という方針の根拠★) / ★D-091 項1★(docs/decisions.md:3442。★出発判定は DB の値で行う。猶予30分を当方で計算しない★) / ★D-099 §1★(:4442。★現行の「履歴の行が無い=未出発」という推測の出所★) / ★D-103 項1★(:5222。★launch_failed と departed === false は同じ出来事。同じ純粋関数を共有する★) / ★D-104★(:5335。出発失敗の行の埋め方 / ★「その他」「不明」という語を使わない★) / ★D-105★(:5435。指示書S の実行記録) / D-077(:1847。★エラー値を変換しない★) / D-089 項3(:3225。★「その他」は使わない★) / D-092(:3599。1清掃 = 3行の仕組み) / D-093(:3719。clean_statuses の役割) / D-094(:3822。率・面積の小数化) / D-042(:226。書き込み許可4カラム) / D-050(:308。段階ごとに検証してからコミット) / D-056(:353。schema.prisma は生成物) / D-026(:122。連続はスケジュール存在日ベース) |
||
| 段階数 | ★7段階★(段階0〜段階6) | ||
| ★着手前確認★ | ★★なし★★ — ★判定に使う値・コード表・ラベルはすべてユーザー回答(2026-09-10)と受領済みの定義書で確定しています★。★★ユーザーの回答を待たずに段階0 から通しで実行してよい★★。★未決が1件残りますが(clean_error が 2/3/4 の行を画面のどこに出すか)、★★本指示書は「値を API まで運ぶ」ところで止めるので、実装は止まりません★★(→「申し送り」) |
||
| 着手前の基準 | ★着手直前に自分で npm test / npx tsc --noEmit / npm run lint を実行し、その実測値を基準にすること★(作成時点の実測は ★43ファイル / 813テスト / tsc 0 / lint 0★ だが、★鵜呑みにしない★) |
||
| 作成 | 2026-09-10(作成時点: 決定事項最終番号 D-105 / docs/handoff.md の最新は 追記30(docs/handoff.md:2434)/ HEAD 942225a) |
||
| ★改訂★ | 2026-09-10(★設計3 の方針を反転★: 「履歴の行が無い=除外」→ ★★「履歴の行が無い=出発失敗として残す」★★。★fixtures の値は1行も変更しないことを明記★。★決定番号を D-106 → D-107 にずらした★。HEAD 942225a のまま) |
実行後はこの表と、文書末尾の「実行状態」表を更新してください。
★この指示書を読んでいるあなたへ★ —
.claude/commands/d-watch.mdの「実装は Cursor 側」は 2つの環境の役割分担です。Cursor の中で動いている Claude は実装担当です。 ★調査や改訂ではなく実装を行ってください。★ ★★うまくいかなければ途中で止めて構いません★★ — ただし ★★段階3 はシードと読み出しを 同一コミットで変えます★★(★特にシードだけコミットすると/detail-logの完了率が壊れます★。 → ★「作業の順序」末尾の注記★)。 ★止めるなら段階の切れ目(コミット単位)で止めること★。
| 箇所 | 実測内容 | |
|---|---|---|
★lib/db/clean-log-mapping.ts:80★ |
`export function departedFromHistory(history: T \ | undefined): boolean { return history !== undefined; }` — ★「履歴の行が無い=未出発」★ |
★同 :63-79 のコメント★ |
★「clean_status(-1/0/1/2)に『出発しなかった』を表す値が無く、行が作られないことが未実施を意味する、という D-091 項1 後段の読みを採っている」★ / ★「実データでどのカラムが未実施を表すかは要確認」★ |
|
★lib/db/index.ts:304★ |
departed: departedFromHistory(history)(cleanLogs()) |
|
★lib/db/index.ts:461★ |
if (departedFromHistory(historyByTimetableId.get(key(row.id)))) continue;(prechecks() の launch_failed) |
|
★scripts/seed-local-db.ts:779★ |
// ★未実施(departed=false)は clean_status_histories を作らない(設計判断1)★ — ★シードもこの推測に合わせて作られている★ |
★★つまり「行の有無」で判定しているのは当方の推測であり、根拠のあるカラムを読んでいませんでした★★ (D-099 §1 が「実データでどのカラムが未実施を表すかは依然として不明」と自ら記録しています)。
★★消すのは「★行があるから出発した★」という推測のほう★★です。 ★★「★行が無いなら出発失敗★」という側は残します★★ — ★ただし推測としてではなく、 ★★「過去なのに結果行が無いのはデータ収集処理の障害だから、黙って消さずに見せる」という決定★★ として残します(→ ★設計3★)。★行の有無を両方向に使っていたのを、片方向に絞る作業だと思ってください★。
★docs/handoff/analysis/table-definitions-02-clean-error.md:213★(clean_status_histories の カラム6「結果」clean_error)の ★原文★:
0: 正常, 1: 異常(清掃未実施), 2: 異常(セキュリティ有効), 3: 異常(D-S制御盤異常), 4: 異常(清掃ロボット異常)
clean_statuses 側(同ファイル :181。カラム7)にもある★。 ★論理カラム名だけが違う(履歴側は「結果」、clean_statuses 側は「エラー」)★ — ★原文ママ★clean_error Int @default(0) @db.UnsignedTinyInt (prisma/schema.prisma:479 が履歴側 / :438 が clean_statuses 側)unsigned tinyint なので 0〜255 が入りうる★(→ ★コード表に無い値の扱いを決める。落とし穴3★)| # | 確定内容 |
|---|---|
| ★1★ | ★★清掃が実施されなかった場合も履歴の行は作成される★★(★原文: 「書いてある以上は作成すると思うので、それを前提で実装してください」★) |
| ★2★ | ★★出発失敗(未実施)は clean_error = 1 のみ★★ |
| ★3★ | ★★2(セキュリティ有効)/ 3(D-S制御盤異常)は「未実施(出発失敗)とは違う扱い」★★(★原文どおり★)。★4(清掃ロボット異常)も同様に「未実施ではない」側★ |
| ★4★ | ★clean_timetables には1週間分の予定がまとめて登録される★ |
| ★5★ | ★予定が無い場合は「清掃予定なし」であって異常ではない★ |
★確定 #1 は「定義書にそう書いてあるから、そう作られるはず」という★推論に基づく前提★です★ — ★実データでの確認は取れていません★。★★外れたときに何が起きるかを「申し送り」に最上位で書いてあります★★。
lib/config/ の1箇所に定義する★★0/1/2/3/4 をロジックに直書きしないこと★(CLAUDE.md §7 / 禁止事項4)。 ★TeamWorks の回答で分類が変わっても1箇所の変更で済む形にする★。
lib/config/index.ts に追加する(★CLEAN_TIMETABLE_PRECHECK の隣が自然★):
export const CLEAN_ERROR = {
/** 0: 正常 */
normal: 0,
/** ★1: 異常(清掃未実施) — 出発失敗(未実施)はこの値のみ(ユーザー回答 2026-09-10)★ */
notExecuted: 1,
/** 2: 異常(セキュリティ有効) — ★未実施とは違う扱い★ */
securityEnabled: 2,
/** 3: 異常(D-S制御盤異常) — ★未実施とは違う扱い★ */
controlBoardError: 3,
/** 4: 異常(清掃ロボット異常) — ★未実施とは違う扱い★ */
robotError: 4,
} as const;
★ラベル表も同じ場所に置く★(コード → ★定義書の原文★):
| コード | ★ラベル(原文ママ。★言い換え禁止★)★ |
|---|---|
| 0 | 正常 |
| 1 | 異常(清掃未実施) |
| 2 | 異常(セキュリティ有効) |
| 3 | 異常(D-S制御盤異常) |
| 4 | 異常(清掃ロボット異常) |
String(code) を返す★★ — ★生値をそのまま出す★。 ★★「その他」「不明」という語を使わないこと★★(★D-089 項3 / D-104 項2★。 「その他」は破棄済み D-067 の「未知の値をその他に寄せる」方針と紛らわしい)。 ★前例: MAKER_NAMES[maker_id] ?? String(maker_id)(lib/config/index.ts / lib/db/index.ts の facilities())と formatModelName()(lib/db/clean-log-mapping.ts:27)★CLAUDE.md §6-2)cleanLogs() と prechecks() が共有する★★置き場所は lib/db/clean-log-mapping.ts★(★新設しない★ — ★D-103 が「同じ出来事は同じ判定で出す。 中身が1行でも、定義がここ1箇所であることに意味がある」と定めた場所★)。
| # | 関数 | 中身 | 使う側 | |||
|---|---|---|---|---|---|---|
| ★1★ | ★`hasCleanResult(history: T \ | undefined): boolean`★ | history !== undefined — ★★意味は「出発したか」ではなく「清掃結果が記録されているか」★★ |
★#2 / #3 の内部でだけ使う(lib/db/index.ts からは直接呼ばない)★ |
||
| ★2★ | ★`notExecutedFromHistory(history: CleanResultLike \ | undefined): boolean`★ | ★★`!hasCleanResult(history) \ | \ | history.clean_error === CLEAN_ERROR.notExecuted★★ — ★★行が無い場合もtrue`(=出発失敗)★★(設計3) |
cleanLogs() / prechecks() |
| ★3★ | ★`completionRateFromHistory(history: (CleanResultLike & { clean_ratio: number }) \ | undefined): number \ | null`★ | ★行が無い → null / 未実施 → null / それ以外 → clean_ratio の丸めない生値★ |
cleanLogs() |
|
| ★4★ | ★latestHistoryByTimetableId<T extends { clean_timetable_id: bigint; ended_at: Date }>(histories: readonly T[]): Map<string, T>★ |
★clean_timetable_id.toString() をキーに、★ended_at が最新の1件★を残す(同着は入力順の先頭)★ |
cleanLogs() / prechecks() |
★★#1 は既存の departedFromHistory() を改称したもの★★(中身は同じ1行)。 ★★departed は !notExecutedFromHistory(history) から作る★★ — ★「行があれば出発した」という推測を、関数名ごと消すことが本指示書の目的★。
★★#2 に「行が無い」条件を含める理由★★ — ★D-103 項1 が「launch_failed と departed === false は 同じ出来事。同じ純粋関数を共有する」と定めている★。★もし departed = hasCleanResult(h) && !notExecuted(h) の ような 2項の AND を呼び出し側に書くと、cleanLogs() と prechecks() の2箇所に同じ規則が生まれる★。 ★★出発失敗の定義は notExecutedFromHistory() 1本だけにすること★★。 ★帰結: hasCleanResult() は外から呼ばれなくなるが、★改称して残す★(★「行があれば出発した」という 推測の名前を消すこと自体が目的であり、テストで意味を固定しておく★)。
★★#4 が必要な理由(★これは見落としやすい。実測で確認済み★)★★:
| 現状 | 実測 |
|---|---|
cleanLogs()(lib/db/index.ts:221-229) |
★ended_at が最新の1件を残している★ |
prechecks()(lib/db/index.ts:384-386) |
★new Map(histories.map(...)) = DB返却順の後勝ち★ |
★★「行があるか」だけを見ていた間はどちらでも同じ答えだったが、clean_error を読むと どの行を選ぶかで答えが変わる★★。★選び方を1箇所に固定しないと /detail-log と /alerts で 出発失敗の件数が食い違う★(D-103 が禁じた状態そのもの)。
★★判定式(これが本指示書の1行まとめ)★★:
departed = 履歴の行がある かつ その行の clean_error !== 1
| 選択肢 | 内容 | 採否 |
|---|---|---|
| A | 履歴の行が無い本清掃は、cleanLogs() の結果に含めない / launch_failed にもしない |
★★不採用★★ |
| B | 行は返し、departed: true / completion_rate: null にする |
★不採用★ |
| C ✅ | ★★履歴の行が無い(かつ予定時刻を過ぎている)本清掃は、これまでどおり「出発失敗」として残す★★(★現行の13件が維持される★) | ★★採用★★ |
★★C を採る理由★★:
CLAUDE.md §1)departed === false が 13件)が そのまま維持される★★。★A 案は 13件を 0件にしうる(しかも例外もテスト失敗も出ない)★departed: true という根拠の無い断定を作ることになる★★ (★出発したという証拠も無い★)。★D-098 / D-099 項2 が Standby. について 「実際に読んだ値ではない」ことをわざわざコメントに残したのと同じ問題を、 より目立たない形で作ってしまう★。★C は「出発した」と断定しない側に倒れている★departed === false ⟺ completion_rate === null は C でも同じように守られる★★ — ★履歴の行が無い行は completion_rate も null になるため★ (completionRateFromHistory(undefined) === null)。 ★lib/dashboard/aggregate.ts:39 / :96(ダッシュボードの「予定 / 実施」)と isProblemCompletion()(連続日数)がこの対応を前提にしているが、★C はこれを崩さない★
★★注意(★起草時の誤りの訂正★)★★ — ★「不変条件を守るため A を採る」という論旨は成立しない★。 ★不変条件は A でも C でも守られる(崩れるのは B だけ)★。★不変条件は A の根拠にはならないので、 A / C の選択理由として使わないこと★
★★C の帰結(★段階5 で実測し、申し送りに書くこと★)★★: ★前提(未実施でも行が作られる)が外れていても、★★出発失敗が画面から消えることはない★★★ — ★★代わりに「結果がまだ書かれていないだけの清掃」まで出発失敗として出る(過剰検知)★★。 ★★どちらに倒すかの判断として、意図的に過剰検知の側を選んでいる★★(理由1)。 ★段階5 では「clean_status_histories の行数(253)= clean_timetables の本清掃行数(253)」を 実測し、★★差があればそれは「出発失敗として出ている行」の件数である★★ことを確認する。
isFutureSchedule() は残す(★消さないこと。★C 案の下では必須★)★isFutureSchedule() が必須★★ — ★未来の予定にはまだ履歴の行が無いので、 ★★外すと未来の清掃が全部「出発失敗」になる★★★clean_error = 0(default)の履歴が付き、★未来の清掃が 「完了率0%・×」として並ぶ★。isFutureSchedule() はその事故も1行で止める★942225a(fix: 予定時刻が来ていない本清掃を出発失敗から除外する)で入ったもの★。 ★同関数の docstring(lib/db/clean-log-mapping.ts:85-112)にある 「★LAUNCH_GRACE_MINUTES を足さない★」という注意もそのまま残すこと★clean_error が 2/3/4 の行の扱い★departed は true★★(未実施ではない。ユーザー確定 #3)。★completion_rate は clean_ratio の生値★ → ★★つまり /detail-log では「出発して、その完了率で終わった清掃」として今と同じように出る★★| # | 対象 | 値 |
|---|---|---|
| ★1★ | ★cleanLogs() の件数★ |
★253★ |
| ★2★ | ★cleanLogs() の departed === false★ |
★13★(★completion_rate === null と完全一致★。D-099 §1 で実測済み) |
| ★3★ | ★prechecks() の件数★ |
★27★(★precheck 14 + launch_failed 13★) |
| ★4★ | ★/dashboard★ |
★9 / 9★(D-099) |
| ★5★ | ★テスト★ | ★43ファイル / 813テスト★(★増えるのは正しい。減ってはいけない★) |
| ★6★ | clean_timetables の行数 |
★precheck=0 が 253 / precheck=1 が 253 / precheck=2 が 253★ |
| ★7★ | clean_status_histories の行数 |
★240★(= 253 − 13)→ ★段階3 で 253 になる★ |
| ★8★ | ★clean_status_histories.clean_error の分布★ |
★全件 0★(240行すべて。★だから今は検証できない★→ 段階3 でシードに 1/2/3/4 を足す) |
★★#1〜#4 は本指示書の前後で変わってはいけません★★。 ★変わったら設計どおりに実装できていません★(★★特に #2(departed === false が 13)が 13 以外になるのは、シードだけを変えて読み出しを変えなかったとき★★ — ★13行が departed: true / completion_rate: 0 に化ける(落とし穴1)★)。
| # | 落とし穴 |
|---|---|
| ★1★ | ★★未実施行に履歴を作ると completion_rate が null → 0 に化ける★★。★現行は completion_rate: history ? history.clean_ratio : null(lib/db/index.ts:306)★なので、★行を作った瞬間に 13行が 0(=「0%で実施した」)になる★。★判定(grade)は両方 × なので画面もテストも赤くならない★。★★completionRateFromHistory() に置き換えるのが必須★★ |
| ★2★ | ★★履歴を選ぶ規則が cleanLogs() と prechecks() で違う★★(設計2 の #4)。★同一 clean_timetable_id に複数行あると答えが分かれる★ |
| ★3★ | ★clean_error は unsigned tinyint(0〜255)★。★コード表に無い値(5以上)が来ても例外は出ない★。★★「★履歴の行がある場合に★未実施と判定できるのは 1 のときだけ」という形(=== CLEAN_ERROR.notExecuted)にしておけば、未知の値は自動的に「未実施ではない」側に落ちる★★。★逆に「!== 0 なら異常」のような書き方をすると未知の値の扱いが変わるのでしないこと★。★★「履歴の行が無い」ケースはこれとは別の条件(!hasCleanResult(history))で、★そちらは未実施(出発失敗)側★★★(設計3-C) |
| ★4★ | ★clean_status_histories.started_at / ended_at は ★NOT NULL★(prisma/schema.prisma:490-491。DateTime、? が付いていない)★。★未実施行を作るとき null にできない★ → ★定義書の原文「★清掃スケジュールの清掃開始日時を格納する★」(table-definitions-02-clean-error.md:217-218)に従い、★予定の時刻をそのまま入れる★★ |
| ★5★ | ★★未実施行の task_area を 0 にすると、面積からの率の再計算が 0 / 0 になる★★(D-091 項2 が「境界の厳密さが要る場面では面積から計算する」と書いている手が使えない)。★再計算する側は先に clean_error を見ること★。★コメントに残すこと★ |
| ★6★ | ★clean_ratio は Float(double(4,1)。D-094 で小数化済み)★。★丸めないこと★(CLAUDE.md §4.3) |
| ★7★ | ★clean_status_histories.id は AUTO_INCREMENT ではない★(prisma/schema.prisma:466 付近のコメント)。★シードで明示採番する(既存の設計判断3 = 本清掃 id と同値 を崩さない)★ |
| ★8★ | ★BigInt → Number()★ を忘れると JSON.stringify が例外を投げる(tsc は捕まえない) |
| ★9★ | ★clean_status_histories / clean_statuses に deleted_at は無い★ → ★論理削除の絞り込みを書かない★ |
scripts/seed-local-db.ts。★実測して確認すること★)★| # | 箇所 | 内容 |
|---|---|---|
| 1 | ★:157-165(設計判断1)★ |
★「departed: false(未実施)は clean_status_histories の行を作らない」★ — ★★本指示書で書き換える★★ |
| 2 | ★:779★ |
if (log.completion_rate === null) continue; — ★★履歴も clean_statuses も作らずに次の行へ飛ぶ★★ |
| 3 | ★:782-785★ |
clean_status は SECURITY_DISABLED_EVERY(=5)の剰余で -1 / 2 を散らしている(★決定的・冪等★) |
| 4 | ★:209-210★ |
CLEAN_STATUS_FINISHED_SECURITY_ENABLED = 2 / CLEAN_STATUS_FINISHED_SECURITY_DISABLED = -1。★★0(清掃前)の定数は無い★★ |
| 5 | ★:788-797★ |
clean_area / task_area * 100 ≒ clean_ratio になるよう clean_area を逆算している(TASK_AREA_SQM = 1000) |
| 6 | ★:812-826★ |
clean_statuses は ★当日分だけ★ 作る(D-093 項1。「今の状態」用) |
★シードを触る作法★: ★deleteMany の逆順(:436-437 付近)を崩さない★ / ★2回続けて実行しても結果が同じであること(冪等)★ / ★ランダムを使わないこと★ / ★実在の住所・電話番号・社名・人名を入れないこと★(CLAUDE.md §7)。
| # | ★してはいけないこと★ | 理由 |
|---|---|---|
| ★1★ | ★★git add -A を使うこと★★ |
CLAUDE.md §7。★過去に別作業の途中ファイルを巻き込む事故が起きている★。★必ずファイルパスを明示★ |
| ★2★ | ★★docs/handoff/管理画面_設計検討_20260910.md に触ること★★ |
★★未追跡ファイル(別作業の途中)★★。★git status に ?? で出るが、本指示書のコミットに含めない・編集しない・削除しない★ |
| ★3★ | ★★メインDBのスキーマ変更(prisma migrate / db push / db pull / prisma/schema.prisma の手書き)★★ |
CLAUDE.md §7 / D-056 / D-059。★clean_error は既にスキーマにある(prisma/schema.prisma:438 / :479)ので、スキーマ変更は1つも要らない★ |
| ★4★ | ★★書き込み許可4カラム以外への DB 書き込み★★ | D-042。★本作業の読み出しは読むだけ★。★書き込みは scripts/seed-local-db.ts(dwatch_local のみ)だけ★。★メインDB / dwatch_ddl に接続しないこと★ |
| ★5★ | ★★コード値(0/1/2/3/4)・閾値をハードコードすること★★ | CLAUDE.md §7。★lib/config/ の CLEAN_ERROR から読む★。★シードも同じ定数を使う★(★「シードだから直書きでいい」は無し。分類が変われば両方直す必要がある★) |
| ★6★ | ★★推測で分類すること★★ | D-077 / §4.8。★7分類に寄せない★ / ★2/3/4 を出発失敗に読み替えない★ / ★ラベルを言い換えない(原文ママ)★ / ★★「その他」「不明」という語を使わない★★(D-089 項3 / D-104 項2) |
| ★7★ | ★★isFutureSchedule() を消すこと・猶予を足すこと★★ |
★設計4★。★LAUNCH_GRACE_MINUTES を lib/db/ から読まないこと★(D-091 項1。★設定値を変えると過去のログの判定まで変わる★) |
| ★8★ | ★タイムゾーン(DBがJSTかUTCか)の話に手を付けること★ | ★★別件★★。★本指示書は toJstIso() の既存の使い方を1文字も変えない★ |
| ★9★ | ★prismaSource の facilities / schedules / dsDevices / dsAlerts を変更すること★ |
★指示書M / P の成果物★。★本作業が触るのは cleanLogs / prechecks の2本だけ★ |
| ★10★ | ★サマリ系4本(reportKpis / facilityPerformance / errorBreakdown / continuousProblems)/ 点検記録2本に手を付けること★ |
★指示書T 以降の範囲★。★notImplemented のまま★ |
| ★11★ | ★lib/db/prisma.ts / local-guard.ts / use-fixtures.ts / ds-status.ts / precheck-mapping.ts を変更すること★ |
★完成しており、そのまま効く★ |
| ★12★ | ★★lib/fixtures/ の既存の値を変更すること★★ |
★CLAUDE.md §9.5★。★★許可するのは型追従(clean_error の導出フィールド追加)だけ★★。★既存の departed / completion_rate / ★status_1h / status_30m★ / 施設名 / 件数を1つも変えないこと★。★★特に :139 / :157 の status_1h: "セキュリティ有効" + departed: false は★新仕様と矛盾しない★ので、「矛盾している」と思って直さないこと★★(→ 段階4-3 の注意書き) |
| ★13★ | ★テストを DB 依存にすること★ | ★遅く・不安定になり、DB が空の環境で落ちる★。★テストは fixtures と純粋関数だけ★ |
| ★14★ | ★画面・CSV に clean_error を出すこと★ |
★★出し方が未決★★(設計5 / 申し送り)。★components/features/detail-log/ と app/api/clean-logs/export/route.ts に差分を出さない★ |
| ★15★ | ★npm run dev を Bash で起動すること★ |
★Browser pane の preview_start を使う★。★無ければ curl で代替し、その旨を記録する★ |
| ★16★ | ★.env.local をコミットすること / DB の認証情報を文書やコードに書くこと★ |
CLAUDE.md §9.3。★確認後は USE_FIXTURES=true に戻す★ |
★段階0(前提の確認。★読むだけ・コミットなし★) → 段階1(lib/config/ のコード表) → 段階2(★期待値表 → テスト → 純粋関数★。呼び出し側は変えない) → 段階3(★★シードと読み出しを同時に切り替える★★) → 段階4(clean_error を API まで運ぶ) → 段階5(実測での検証。★コミットなし★) → 段階6(記録と push)★
★★段階1・2・3・4・6 でそれぞれ1コミット★★。 ★段階ごとに npx tsc --noEmit / npm run lint / npm test を実行し、通ってからコミットすること★ (D-050。docs/decisions.md:308)。
★★なぜ段階2 と段階3 を分けるのか★★ — ★段階2 で関数を「足す」だけなら挙動が変わらないので、 ★期待値表とテストを落ち着いて作れる★。★逆に、シード(書き込み側)と読み出し側は 同時に変えなければならない★ — ★★シードだけ変えると、13行に
clean_error = 1の履歴ができるのに 読み出しは古いdepartedFromHistory()(行があれば出発)のままなので、★13行がdeparted: true/completion_rate: 0に化ける★★★(落とし穴1)。★tsc/lint/npm testは通ってしまう★ (DB を見ていないため)。 ★参考: 逆向き(読み出しだけ変える)は、★設計3-C のおかげで件数が壊れない★(★履歴が無い13行はnotExecutedFromHistory()がtrueを返すので、departed === false/launch_failed13件が維持される★)。 ★★それでも同一コミットにすること★★ — ★2/3/4 の検証データが揃わず、段階5 の実測が半分しかできないため★。
| # | 対象 | 見る点 |
|---|---|---|
| 1 | ★docs/handoff/analysis/table-definitions-02-clean-error.md:170-228★ |
★clean_statuses(:181)と clean_status_histories(:213)の clean_error の原文★。★ラベルを原文のまま写すため★ |
| 2 | ★lib/db/clean-log-mapping.ts 全文(197行)★ |
★departedFromHistory()(:80)の docstring が何を「推測」と認めているか★ / ★isFutureSchedule()(:113)の docstring★ |
| 3 | ★lib/db/index.ts:193-320(cleanLogs())★ / ★:360-506(prechecks())★ |
★履歴 Map の作り方が2箇所で違うこと(落とし穴2)を自分の目で確認する★ |
| 4 | ★scripts/seed-local-db.ts:150-230 / :770-830★ |
★設計判断1〜4★ と ★履歴・clean_statuses の作り方★ |
| 5 | ★D-091 項1(docs/decisions.md:3442)/ D-099 §1(:4442)/ D-103 項1(:5222)★ |
★「行の有無で判定する」と決めた経緯と、そこで「要確認」と残されていたもの★ |
| 6 | ★D-104(:5335)★ |
★「その他」「不明」を使わない理由★ / ★出発失敗の行の埋め方(★本指示書では変えない★)★ |
| 7 | ★lib/config/index.ts:1-80★ |
★コード表の置き方の作法(CLEAN_TIMETABLE_PRECHECK / ROBOT_STATUS_STANDBY の docstring)★ |
.env.local の DATABASE_URL が dwatch_local を指していることを確認する★。 ★値そのものを画面・ログ・文書に出さないこと★(CLAUDE.md §9.3).env.local に ALLOW_DIRECT_ACCESS=true があることを確認する★(D-101 項2。 ★無いと proxy.ts が全ルートを止めるので段階5 で詰まる★)npm test のファイル数・テスト数 / tsc / lint)"/c/Program Files/MySQL/MySQL Server 8.4/bin/mysql.exe" -u root --default-character-set=utf8mb4 dwatch_local -e " SELECT clean_error, COUNT(*) FROM clean_status_histories GROUP BY clean_error; SELECT COUNT(*) AS main_rows FROM clean_timetables WHERE precheck = 0; SELECT COUNT(*) AS histories FROM clean_status_histories;"
★作成時点の実測★: clean_error は ★0 が240件のみ★ / main_rows = ★253★ / histories = ★240★。
lib/config/ に clean_error のコード表を定義する★lib/config/index.tsCLEAN_ERROR(設計1 のオブジェクト)を追加する★ROBOT_STATUS_TONES / MAKER_NAMES のような Record か、CLEAN_ERROR と対になる定数)| # | 書く内容 |
|---|---|
| 1 | ★出所★ — ★docs/handoff/analysis/table-definitions-02-clean-error.md:213(履歴側)/ :181(clean_statuses 側)の原文★ |
| 2 | ★★1(異常(清掃未実施))だけが出発失敗(未実施)★★(ユーザー回答 2026-09-10) |
| 3 | ★2 / 3 / 4 は「未実施とは違う扱い」であり、★どこに出すかは未決★(申し送り) |
| 4 | ★ラベルは原文ママ。★言い換え禁止★(D-077 の思想)★ |
| 5 | ★コード表に無い値は String(code)。★「その他」「不明」に寄せない★(D-089 項3 / D-104 項2)★ |
| 6 | ★unsigned tinyint(0〜255)なので5以上が入りうる★ |
| 7 | ★1箇所に閉じ込める理由★ — ★lib/db/(DB→API)と scripts/seed-local-db.ts(fixtures→DB)の |
両方が同じ値を必要とする★(ROBOT_STATUS_STANDBY と同じ形) |
lib/config/__tests__/ が無ければ新設してよい★(★既存の慣例を確認すること★)。 ★lib/db/__tests__/clean-log-mapping.test.ts に混ぜないこと★(★別の関心事★)5 / 255)で String(code) が返ること★ / ★★「その他」「不明」という語がラベル表に出てこないこと★★npx tsc --noEmit エラー0 / npm run lint 0 / npm test 全パス0/1/2/3/4 の直書きが lib/config/index.ts 以外に増えていないこと★★CLAUDE.md §6-2 / §9.4★: ★期待値表を先に作り、それをテストで固定してから実装する★。 ★目視確認だけで済ませないこと★。
★notExecutedFromHistory(history)★ — ★★この7ケースを必ず全部テストにすること★★
| # | 入力 | 期待 | 意味 |
|---|---|---|---|
| 1 | { clean_error: 0 } |
false |
正常 |
| ★2★ | ★{ clean_error: 1 }★ |
★true★ |
★異常(清掃未実施) = 出発失敗★ |
| 3 | { clean_error: 2 } |
false |
★セキュリティ有効。未実施とは違う扱い★ |
| 4 | { clean_error: 3 } |
false |
★D-S制御盤異常。同上★ |
| 5 | { clean_error: 4 } |
false |
★清掃ロボット異常。同上★ |
| ★6★ | ★{ clean_error: 5 }(★コード表に無い値★)★ |
false |
★未知の値は「未実施ではない」側に落ちる(落とし穴3)★ |
| ★★7★★ | ★★undefined(★履歴行なし★)★★ |
★★true★★ |
★★過去なのに結果行が無い=データ収集処理の障害。出発失敗として残す(設計3-C。fail-loud)★★ |
★completionRateFromHistory(history)★ — ★★同じく7ケース(clean_error 0 / 1 / 2 / 3 / 4 / 未知(5) / 履歴行なし)を必ず含めること★★
| # | 入力 | 期待 | 意味 |
|---|---|---|---|
| 1 | { clean_error: 0, clean_ratio: 92.4 } |
★92.4★ |
★丸めない生値(CLAUDE.md §4.3)★ |
| 1' | ★{ clean_error: 0, clean_ratio: 0 }★ |
★0★ |
★★「0%で実施した」と「未実施」は別物。null にしないこと★★ |
| ★2★ | ★{ clean_error: 1, clean_ratio: 0 }★ |
★null★ |
★未実施(落とし穴1 の本体)★ |
| ★2'★ | ★{ clean_error: 1, clean_ratio: 55 }★ |
★null★ |
★矛盾したデータでも clean_error を正とする★ |
| 3 | { clean_error: 2, clean_ratio: 30.5 } |
30.5 |
★未実施ではないので率を返す★ |
| 4 | { clean_error: 3, clean_ratio: 0 } |
0 |
同上 |
| 5 | { clean_error: 4, clean_ratio: 61.2 } |
61.2 |
同上 |
| ★6★ | ★{ clean_error: 5, clean_ratio: 44.4 }(★未知の値★)★ |
★44.4★ |
★未実施ではない側に落ちるので率を返す★ |
| ★★7★★ | ★★undefined(★履歴行なし★)★★ |
★★null★★ |
★結果が記録されていない★。★★これで不変条件 departed === false ⟺ completion_rate === null が |
履歴行なしの場合にも保たれる★★(notExecutedFromHistory(undefined) === true なので departed も false) |
★hasCleanResult(history)★
| 入力 | 期待 |
|---|---|
| 履歴の行 | true |
undefined |
false |
★map.get("存在しないキー")★ |
★false★(★Map.get の戻りをそのまま渡す使い方を固定する★) |
★★上の2表を cleanLogs() の出力に翻訳した早見表(★これも1本テストにしておくと事故に気づける★)★★
clean_error |
departed |
completion_rate |
不変条件 |
|---|---|---|---|
| 0 | true |
clean_ratio |
✅ |
| ★1★ | ★false★ |
★null★ |
✅ |
| 2 / 3 / 4 | true |
clean_ratio |
✅ |
| 未知(5) | true |
clean_ratio |
✅ |
| ★★履歴行なし★★ | ★★false★★ |
★★null★★ |
✅ ★★(★設計3-C。行を消さずに出発失敗として出す★)★★ |
★latestHistoryByTimetableId(histories)★
| 入力 | 期待 |
|---|---|
| 1件 | ★キーは clean_timetable_id.toString()★(例 map.get("1001") で引ける) |
★同一 timetable 2件(ended_at が異なる)★ |
★ended_at が新しい方★ |
★同一 timetable 2件(ended_at 同着)★ |
★入力順の先頭★(★現行の < 比較の挙動を維持する★) |
★同一 timetable 2件で ★clean_error が 0 と 1★ で ended_at が違う★ |
★★新しい方の clean_error が採られる★★(★どの行を見るかで答えが変わることを固定する★) |
| 別 timetable 2件 | 2エントリ |
| 空配列 | 空の Map |
lib/db/clean-log-mapping.ts に追加する★(★設計2 の #2 / #3 / #4★)。 ★#1(departedFromHistory → hasCleanResult の改称)は段階3 で行う★ (★呼び出し側と同時に変えないとビルドが通らないため★)
departedFromHistory() を呼ぶ★★(★中身は history !== undefined で 同じもの★)。★段階3 の改称でそのまま hasCleanResult() に置き換わる★。 ★★history === undefined をインラインで書かないこと★★(★改称のときに直し漏れる★)CleanResultLike インターフェースを定義し、docstring に出所(prisma/schema.prisma:479)を書く★0/1 を直書きせず CLEAN_ERROR を使う★★(禁止事項5)clean_error を読むのか(D-091 項1 の解決)★ / ★★undefined(履歴行なし)を true(出発失敗)にする理由★★(★設計3-C。★過去なのに結果行が無いのは データ収集処理の障害であり、黙って消すより出発失敗として見せる(fail-loud)★)/ ★未実施のとき clean_ratio を返さない理由(落とし穴1)★ / ★履歴を1件に絞る規則を1箇所にした理由(落とし穴2 / D-103)★npx tsc --noEmit エラー0 / npm run lint 0 / npm test 全パス(★テスト数が増えていること★)lib/db/index.ts に差分が無いこと★★(★まだ呼び出していない★)cleanLogs() 253件 / prechecks() 27件は不変★★★この段階だけは、シード(書き込み側)と lib/db/(読み出し側)を同じコミットにすること★★。 ★片方だけコミットすると「変えてはいけない数字」が壊れた状態が履歴に残る★。
scripts/seed-local-db.ts)★| # | やること |
|---|---|
| ★1★ | ★★未実施行(fixtures の completion_rate === null)にも clean_status_histories を作る★★。★:779 の continue を消し、履歴を作る側に回す★ |
| ★2★ | ★未実施行の clean_error は ★CLEAN_ERROR.notExecuted★(★直書きしない★)★ |
| ★3★ | ★未実施行の clean_status は ★0(清掃前)★★。★新しい定数(例 CLEAN_STATUS_BEFORE = 0)を :209-210 の隣に定義し、★「★定義書の履歴側の備考には 2 / -1 しか書かれていない(原文)ので、これは当方の暫定値★」とコメントすること★(→ 申し送り / TeamWorks 確認事項) |
| ★4★ | ★未実施行の task_area / clean_area / clean_ratio は ★すべて 0★★。★理由をコメントに書く★ — ★「取得元(PUDU Cloud の clean task log)が存在しないため既定値のまま」★。★★併せて「面積から率を再計算する処理は 0 / 0 になるので、先に clean_error を見ること」を書く★★(落とし穴5) |
| ★5★ | ★未実施行の started_at / ended_at は ★予定の時刻をそのまま★★(NOT NULL。落とし穴4)。★定義書の原文「清掃スケジュールの清掃開始日時を格納する」を根拠としてコメントに引く★ |
| ★6★ | ★★既存の完了行に clean_error = 2 / 3 / 4 を散らす★★。★★clean_ratio / clean_area / task_area / clean_status は変えないこと★★(★変えると 253行の grade・連続日数・ダッシュボードが動く★)。★規則は剰余など決定的なものにする(ランダム禁止・冪等)★。★2 / 3 / 4 がそれぞれ3件以上になるようにする★。★13行の未実施行には当てないこと★ |
| ★7★ | ★clean_statuses(当日分。:812-826)にも同じ clean_error を入れる★。★履歴と clean_statuses で値が食い違わないようにする★。★当日の未実施行にも行ができるので件数が1件増える(clean_statuses は本作業の読み出し対象ではないので画面は変わらない)★ |
| ★8★ | ★console.log の完了出力に ★clean_error の分布★ を足す★(例 clean_status_histories=253 (0=..., 1=13, 2=..., 3=..., 4=...))。★段階5 の検証が楽になる★ |
| ★9★ | ★★設計判断1(:157-165)のコメントを書き換える★★ — ★「行を作らないことで未実施を表す」→「clean_error = 1 で未実施を表す」★。★★出所(定義書 :213)とユーザー回答(2026-09-10)を書く★★。★古い記述を残さないこと(次に読む人が両方を信じてしまう)★ |
| ★10★ | ★冪等であること(npm run seed:local を2回実行して結果が同じ)★ |
lib/db/index.ts / lib/db/clean-log-mapping.ts)★| # | やること |
|---|---|
| ★1★ | ★departedFromHistory() を ★hasCleanResult()★ に改称し、★docstring を全面的に書き換える★★ — ★★「★行があるから出発した★」という推測の記述を消す★★(★これが本指示書で消したい推測★)。★新しい意味(結果が記録されているか)と、★D-103 の「2箇所に書かない」意図は残す★。★★なお「★行が無いなら出発失敗★」という側は推測ではなく決定として残る★★(設計3-C。★docstring では notExecutedFromHistory() を参照させる★) |
| ★2★ | ★cleanLogs(): 履歴 Map の生成を ★latestHistoryByTimetableId(histories)★ に置き換える★(★:221-229 のインライン実装を削除。★ended_at 最新を採る規則とコメントは関数側へ移す★) |
| ★3★ | ★★cleanLogs(): mainRows のフィルタは ★isFutureSchedule() の条件のみ★。1文字も変えない★★ — ★★履歴の有無でフィルタを足さないこと★★(設計3-C。★履歴の行が無い過去の本清掃も返す。返さないと出発失敗が消える★)。★isFutureSchedule() が必須である理由(未来の予定は履歴が無いので、外さないと全部出発失敗になる)をコメントに書く★(設計4) |
| ★4★ | ★cleanLogs(): departed: !notExecutedFromHistory(history)★ |
| ★5★ | ★cleanLogs(): completion_rate: completionRateFromHistory(history)★(★落とし穴1★) |
| ★6★ | ★prechecks(): 履歴 Map の生成を ★同じ latestHistoryByTimetableId()★ に置き換える★(★new Map(histories.map(...)) をやめる。落とし穴2★) |
| ★7★ | ★prechecks() の launch_failed ループの条件を ★★①未来ならスキップ(isFutureSchedule。★残す。必須★)→ ②notExecutedFromHistory() が真なら出す★★ の2段だけにする★。★★「履歴が無ければスキップ」は入れないこと★★(設計3-C。★履歴が無い行は notExecutedFromHistory() が true を返すので、そのまま launch_failed になる★) |
| ★8★ | ★★「本清掃行を指すエラー行を launch_failed に読み替えない」という現行の扱いを変えないこと★★(D-104。lib/db/index.ts:402-406) |
| ★9★ | ★既存テスト lib/db/__tests__/clean-log-mapping.test.ts:86-105(departedFromHistory の3件)と :111 / :162(isFutureSchedule のテストが同関数を使っている)を追従させる★。★★改称(hasCleanResult)に合わせるだけで、期待値は変わらない★★(undefined → false のまま。★意味が「未出発か」から「結果が記録されているか」に変わる点だけ、テスト名と説明文を直す★)。★★「履歴の行が無い=出発失敗」という結果も変わらない★★ — ★ただしその判定を担う関数が notExecutedFromHistory()(undefined → true)に移ることに注意★。★行番号は自分で取り直すこと★ |
npx tsc --noEmit エラー0 / npm run lint 0 / npm test 全パスnpm run seed:local を2回実行して結果が同じ(★出力の件数が一致★)★"/c/Program Files/MySQL/MySQL Server 8.4/bin/mysql.exe" -u root --default-character-set=utf8mb4 dwatch_local -e " SELECT clean_error, COUNT(*) FROM clean_status_histories GROUP BY clean_error ORDER BY clean_error; SELECT COUNT(*) AS histories FROM clean_status_histories;"
★期待★: ★histories = 253★(240 → 253)/ ★clean_error = 1 が 13件★ / ★2 / 3 / 4 がそれぞれ 3件以上★ / ★合計が 253★
grep -n "clean_error" scripts/seed-local-db.ts lib/db/*.ts で、★数値の直書きが無いこと★を確認したclean_error を API まで運ぶ(画面には出さない)★★実装より先に契約を直す★(CLAUDE.md §6-1)。
d-watch-api.yaml(★CleanLogItem は :1118-1153★。★行番号は自分で取り直すこと★)clean_error を追加★:
{ type: integer, nullable: true, example: 0 }★★ — ★★nullable を忘れないこと★★ (★設計3-C により、★履歴(clean_status_histories)の行が無い過去の本清掃も返す★ため。 ★その行には読むべき clean_error が存在しない★)description に定義書の原文のコード表を書く★(0:正常 / 1:異常(清掃未実施) / 2:異常(セキュリティ有効) / 3:異常(D-S制御盤異常) / 4:異常(清掃ロボット異常))1 または null(履歴の行が無い)のとき departed=false / completion_rate=null になる★★」を書くnull の意味も書く★: ★「★履歴の行が無い(=清掃結果が記録されていない)★。★出発失敗として扱う★」★facility_id も追加する★★ — ★lib/types/index.ts:59 にはあるのに YAML に無い(実測)★。 ★同じスキーマブロックを触るので、契約と実装の乖離をここで閉じる★(CLAUDE.md §6-1。 ★指示書S が PrecheckItem で同じ抜けを直したのと同じ★)completion_rate の description(:1138「未出発時はnull」)は ★★「未実施(clean_error=1)、または履歴の行が無い(clean_error=null)ときnull」に直す★★departed の description(:1132)にも ★根拠が clean_error であること★ を書くlib/types/index.tsCleanLogItem に clean_error: number | null; を追加★★(★★nullable にする★★ — ★設計3-C により、★履歴の行が無い過去の本清掃も返す★ため。★その行の clean_error は null★。 ★★null の行は departed: false / completion_rate: null(=出発失敗)である★★ことをコメントに書く)completion_rate / departed のコメントも clean_error 基準に直す★ (★「clean_error === 1 または clean_error === null のとき departed === false」★)lib/fixtures/clean-logs.ts★★最初に読むこと — fixtures の既存の値は1行も変更しません★★ ★この段階でやるのは ★
row()に導出フィールドを1つ足す型追従だけ★ です★。 ★★既存の行のdeparted/completion_rate/status_1h/status_30m/ 施設名 / 件数を 1つも書き換えないこと★★(禁止事項12)。
row()(:44)で ★clean_error を departed から導出する★★: ★departed ? CLEAN_ERROR.normal : CLEAN_ERROR.notExecuted★(★数値を直書きしない★)departed が入力で clean_error が導出、 DB 経由では clean_error が正で departed が導出(★向きが逆★)★★USE_FIXTURES の true / false で clean_error の分布が違う(想定内。段階6 で記録する)★★departed が入力なので、★clean_error が null になる行は fixtures には存在しない★ (null は DB 経由でだけ出る)。★型は number | null だが fixtures 側は常に number★status_1h: "セキュリティ有効" + departed: false は新仕様と矛盾する」は誤り★★★★lib/fixtures/clean-logs.ts:139 と :157 を「矛盾しているから直す」と考えないこと★★。 ★★矛盾していません。直す必要はありません★★。
| カラム | 何を表すか | 出所 |
|---|---|---|
★status_1h / status_30m★ |
★★事前チェック時点(60分前 / 30分前)のロボットの状態★★ | ★robot_error_reports.error_type 由来★ |
★clean_error★ |
★★その清掃の結果★★ | ★clean_status_histories 由来★ |
★★別のカラム・別の時点の話なので、 「60分前にセキュリティ有効だった → 結果その清掃は実施されなかった(clean_error = 1)」は 新仕様の下でも完全に成立する組み合わせ★★です。 ★むしろ因果としては自然な並び★(★セキュリティが有効なままだったので出発できなかった★)。
★★この誤解は起草時に実際に発生しました★★ — ★同じ勘違いで fixtures を書き換えないよう、 この注意書きを消さないこと★。
prismaSource.cleanLogs()clean_error: history?.clean_error ?? null★★(★生値をそのまま。★履歴の行が無ければ null★)★を draft に足す?? を || にしないこと★★ — ★clean_error は 0(正常)が正当な値なので、|| だと 0 が null に化ける★ (★tsc も lint も捕まえない。段階5 の「clean_error の分布が DB と完全一致」で初めて分かる★)| # | 箇所 | 内容 |
|---|---|---|
| 1 | ★lib/clean-logs/__tests__/query.test.ts:6★ |
row(overrides: Partial<CleanLogItem>): CleanLogItem の土台オブジェクト |
| 2 | ★lib/dashboard/__tests__/aggregate.test.ts:24★ |
CleanLogItem を組み立てるヘルパー |
| 3 | ★lib/api/__tests__/client.test.ts:35★ |
ListResponse<CleanLogItem> の本文 |
| ★4★ | ★app/api/clean-logs/export/route.ts(toCsv()。:25)★ |
★★列を足さない★★(禁止事項14)。★差分が無いことを確認するだけ★ |
| ★5★ | ★components/features/detail-log/CleanLogTable.tsx★ |
★★差分なし★★(禁止事項14) |
npx tsc --noEmit エラー0 / npm run lint 0 / npm test 全パスd-watch-api.yaml と lib/types/index.ts の内容が一致していること★(★型 / nullable / description★)components/ と app/api/clean-logs/export/route.ts に差分が無いこと★★cleanLogs() 253件 / prechecks() 27件が不変(★段階5 で実測する★)★"/c/Program Files/MySQL/MySQL Server 8.4/bin/mysql.exe" -u root --default-character-set=utf8mb4 dwatch_local -e " SELECT clean_error, COUNT(*) FROM clean_status_histories GROUP BY clean_error ORDER BY clean_error; SELECT COUNT(*) AS main_rows FROM clean_timetables WHERE precheck = 0; SELECT COUNT(*) AS histories FROM clean_status_histories; SELECT clean_error, COUNT(*) FROM clean_statuses GROUP BY clean_error ORDER BY clean_error;"
★確認する点★:
| # | 項目 |
|---|---|
| 1 | ★main_rows(253)と histories(253)が ★一致★ すること★ — ★★これは「段階3 のシードが未実施13行にも履歴を作れたか」の確認★★。★★一致しない場合、差分の行は cleanLogs() から落ちるのではなく、★「出発失敗」として画面に出る★★★(設計3-C。★だから departed === false の件数が 13 を超える★)。★★件数が減っていないことも併せて見ること★★ |
| 2 | ★clean_error = 1 が 13件★(★「変えてはいけない数字」#2 と同じ★) |
| 3 | ★2 / 3 / 4 がそれぞれ3件以上あること★ |
| 4 | ★clean_statuses 側の clean_error が履歴と食い違っていないこと★ |
USE_FIXTURES=false)★★★.env.local を書き換えず、コマンドラインで env を渡すこと★★(★戻し忘れを防ぐ★):
USE_FIXTURES=false DATABASE_URL="mysql://root@localhost:3306/dwatch_local" npx tsx -e "
import { db } from './lib/db';
const logs = await db.cleanLogs();
const pre = await db.prechecks();
const count = (rows, f) => rows.filter(f).length;
console.log('cleanLogs', logs.length);
console.log('departed=false', count(logs, r => !r.departed));
console.log('rate=null', count(logs, r => r.completion_rate === null));
console.log('不一致(departed=false と rate=null がズレた行)', count(logs, r => !r.departed !== (r.completion_rate === null)));
console.log('clean_error 分布', JSON.stringify(logs.reduce((a, r) => (a[String(r.clean_error)] = (a[String(r.clean_error)] ?? 0) + 1, a), {})));
console.log('clean_error=1 の departed', [...new Set(logs.filter(r => r.clean_error === 1).map(r => r.departed))]);
console.log('clean_error=null の件数', count(logs, r => r.clean_error === null));
console.log('clean_error=null の departed', [...new Set(logs.filter(r => r.clean_error === null).map(r => r.departed))]);
console.log('clean_error=2/3/4 の rate が非null', count(logs, r => [2,3,4].includes(r.clean_error) && r.completion_rate !== null));
console.log('prechecks', pre.length, 'launch_failed', count(pre, p => p.severity === 'launch_failed'));
"
★★期待値(★1つでも外れたら実装が違う★)★★:
| # | 項目 | 期待 | ||
|---|---|---|---|---|
| ★1★ | cleanLogs() の件数 |
★253★ | ||
| ★2★ | departed === false |
★13★ | ||
| ★3★ | completion_rate === null |
★13★ | ||
| ★4★ | ★departed === false と completion_rate === null のズレ★ |
★0★(★落とし穴1 を踏んでいないことの証明★) | ||
| ★5★ | clean_error の分布 |
★DB の分布と完全一致(変換していないことの証明)★。★★0 のバケツが null に化けていないこと(?? を ` |
` にすると化ける)★★ | |
| ★6★ | clean_error === 1 の departed |
★[false] だけ★ |
||
| ★★7★★ | ★★clean_error === null の件数★★ |
★★0件★★(★シードが253行すべてに履歴を作っているため★)。★★もし 0 でなければ、それは「履歴の行が無い過去の本清掃」であり、★出発失敗として出ている★(設計3-C。★消えていないことが正しい★)。その場合 departed は ★[false] だけ★ であることを確認する★★ |
||
| ★8★ | clean_error が 2/3/4 の行の completion_rate |
★全件 非null★(★未実施扱いになっていないこと★) | ||
| ★9★ | prechecks() |
★27件 / launch_failed 13件★ |
★npx tsx でエイリアス(@/)が解決できない場合★は ★dev サーバ+curl に切り替えてよい★ (/api/clean-logs?per_page=300 / /api/prechecks)。★★その場合も上の9項目を実測すること★★。
| 画面 | 期待 |
|---|---|
★/detail-log★ |
★200 / 253件★。★✗(未出発)が 13件★ |
★/dashboard★ |
★200 / 9 / 9★(D-099) |
★/alerts★ |
★200 / 27件(launch_failed 13)★。★「出発失敗」が出ていること★ |
/facilities / /schedules / /ds-logs / /ds-alerts |
★200。回帰していないこと★ |
/reports |
★500(第5歩・指示書T の範囲)★ |
[db] xxx のPrisma実装はまだありません。… であること★(lib/db/index.ts:164)preview_start)が使えなければ curl で代替し、★目視できなかった項目を必ず記録する★★USE_FIXTURES=true での確認★.env.local を触っていないなら、そのまま USE_FIXTURES=true で dev サーバを起動するだけ★/detail-log / /dashboard / /alerts が★従来どおり★動くこと★.env.local が USE_FIXTURES=true のままであることを確認した★★docs/decisions.md に追記★着手直前に grep -nE "^### D-(0|1)[0-9][0-9]" docs/decisions.md | tail -3 で最終番号を実測すること★ (★^### D-0 だけでは3桁台を拾えない★。★作成時点の最終は D-105。改訂時点で別担当が D-106 (本指示書の判定方針を確定した決定)を追記中なので、★★D-107 の見込み★★★)。
★記録すること★:
| # | 内容 |
|---|---|
| ★1★ | ★★clean_error のコード表を根拠に判定を切り替えたこと★★ — ★出所(定義書 :213)とユーザー回答(2026-09-10)の原文★。★★D-091 項1 / D-099 §1 の「実データでどのカラムが未実施を表すかは要確認」が定義書ベースで解決した(実データでの確認は未了)★★ |
| ★2★ | ★コード表を lib/config/ の1箇所に置いたこと★と、★ラベルを原文ママにした理由★ |
| ★3★ | ★★履歴の行が無い過去の本清掃を、これまでどおり「出発失敗」として残した判断(設計3 の C案)★★と、★不採用にした A案(行ごと除外)/ B案(departed: true)とその理由★。★★fail-silent より fail-loud を採ったこと★★(★行ごと消すと運用担当が「何かが足りない」ことにすら気づけない★)。★★併せて「不変条件 departed === false ⟺ completion_rate === null は A でも C でも守られるので、A案の根拠にはならない」ことも書く★★(★起草時にこれを A案の根拠にしていた。同じ誤りを繰り返させないため★) |
| ★4★ | ★★isFutureSchedule() が C案の下では必須であること★★(★未来の予定には履歴の行が無いので、外すと未来の清掃が全部「出発失敗」になる★)。★加えて、逆向き(未来に clean_error=0 の履歴が先に作られる)の事故も止める二重の役割★ |
| ★5★ | ★★履歴を1件に絞る規則を latestHistoryByTimetableId() に統一したこと★★ — ★cleanLogs() と prechecks() で規則が違っていた(実測)事実も書く★ |
| ★6★ | ★★completion_rate が null → 0 に化ける落とし穴(落とし穴1)と、それをどう防いだか★★ |
| ★7★ | ★シードに足したもの★(未実施13行の履歴 / 2/3/4 の分布 / clean_status = 0 / 面積を0にした理由) |
| ★8★ | ★★シードの clean_status = 0 が当方の暫定値であること★★(★定義書の履歴側の備考は 2 / -1 のみ★) |
| ★9★ | ★API に clean_error を足したこと★と、★画面・CSV には出していないこと(未決)★。★★nullable にした理由(設計3-C により履歴の行が無い過去の本清掃も返すため)★★ |
| ★10★ | ★USE_FIXTURES の true / false で clean_error の分布が違うこと(想定内)★。★fixtures には clean_error = null の行が存在しない(departed が入力なので)ことも書く★ |
| ★★10'★★ | ★★lib/fixtures/ の既存の値は1行も変更していないこと★★と、★★その理由★★ — ★status_1h / status_30m(事前チェック時点のロボットの状態。robot_error_reports.error_type 由来)と clean_error(清掃の結果。clean_status_histories 由来)は別のカラムなので、★status_1h: "セキュリティ有効" + departed: false(lib/fixtures/clean-logs.ts:139 / :157)は新仕様と矛盾しない★。★起草時に「矛盾する」と誤認した経緯ごと記録し、次に読む人が fixtures を書き換えないようにする★ |
| ★11★ | ★★TeamWorks への確認事項(下記「申し送り」の表をそのまま)★★。★第8回の確認書の候補として記録する★ |
| ★12★ | ★実測値★(npm test のファイル数・テスト数 / tsc / lint / cleanLogs() / prechecks() の件数。★着手前の基準値との差★) |
| ★13★ | ★段階5 で確認できなかった項目があれば、その理由と限界★ |
docs/handoff.md に追記docs/handoff.md:2434)★。次は ★追記31★ の見込み。★着手直前に自分で確認すること★docs/handoff.md:6-7)の「最新の追記」が 追記29 のまま(実測)★★ → ★★自分の追記に合わせて直すこと★★clean_error = 1★ または ★履歴の行が無い★ になった★★ (★「行があれば出発した」という推測は消えた。★「行が無ければ出発失敗」は決定として残した★★)isFutureSchedule() は必須。消さないこと★★(★消すと未来の清掃が全部「出発失敗」になる★)lib/fixtures/ の status_1h: "セキュリティ有効" + departed: false は新仕様と矛盾しない★★ (★別カラム・別時点。fixtures は書き換えていない★)departed を実データでどう判定するかの確認」(追記30 の4番)★ を ★★本作業の内容に更新する★★(★カラムは決まった。残るのは「未実施でも行が作られるか」の実データ確認★)git add は必ずファイルパスを明示。git add -A は禁止★(禁止事項1)docs/handoff/管理画面_設計検討_20260910.md をコミットに含めないこと★★(禁止事項2).env.local をコミットしないこと★(禁止事項16)git push origin develop(★main へは push しない★)docs/decisions.md に決定番号を追記した(★上記13項目すべて★)/ docs/handoff.md に追記し★冒頭の最新番号も直した★ / ★origin/develop へ push 済み★ / ★文書末尾の「実行状態」表を更新した★| # | 未決事項 | 状態 |
|---|---|---|
| ★1★ | ★★clean_error が 2 / 3 / 4 の行をどこに・どう出すか★★(/detail-log の列を増やす / /alerts に種別を足す / 別画面 など) |
★★未決★★。★本指示書は「値を API まで運ぶ」ところで止めている★。★★着手前確認にはしていない(実装を止めないため)★★。★出し方を決めるときは、lib/config/ のラベル表(段階1)をそのまま使えば原文が保たれる★ |
| ★2★ | ★2(セキュリティ有効)は D-091 項3 の「セキュリティ無効はアラートにしない」という内部調整と関係しうる★ |
★★D-091 項3 の結果待ち★★。★先に決めないこと★ |
| ★3★ | ★出発失敗の行に対応状況(status / comment / responded_at / resolved_at)の置き場が無い★ |
★D-104 項3 のまま(★変わっていない★)。★clean_error = 1 の行には robot_error_reports の行が無い★ |
| # | 確認したいこと | なぜ重要か |
|---|---|---|
| ★1★ | ★★清掃が実施されなかった場合も clean_status_histories の行は作成されますか★★(clean_error = 1 の行) |
★★本指示書の設計の土台★★。★★前提が外れても出発失敗が消えることはない(設計3-C)★★が、★代わりに「★結果がまだ書かれていないだけの行★」まで出発失敗として出る(★過剰検知★)★。★fail-loud に倒す判断をしているので、実データでどちらの挙動になるかを確認したい★。★★最優先★★ |
| ★2★ | ★未実施の行の clean_status には何が入りますか★ |
★定義書の clean_status_histories 側の備考は ★2 / -1 のみ★(:212。原文)。★0(清掃前)を使ってよいのか不明★。★当方のシードは暫定で 0 にしている★ |
| ★3★ | ★★未実施の行の task_area / clean_area は 0 ですか。それとも「予定していた清掃対象面積」が入りますか★★ |
★★CLAUDE.md §4.2 の清掃完了率(Σ清掃面積 ÷ Σ清掃対象面積)の分母が変わる★★。★サマリ集計は TeamWorks 実装なので、当方の再計算値と一致するかに直結する★ |
| ★4★ | ★clean_error が 2 / 3 / 4 のとき、clean_ratio / 面積には何が入りますか★(途中まで清掃した値か、0か) |
★/detail-log の完了率と判定(◎○△×)の意味が変わる★ |
| ★5★ | ★1回の清掃に複数の異常が同時に起きた場合、clean_error はどれになりますか★(優先順位はあるか) |
★1カラムに1値しか入らないため★ |
| ★6★ | ★clean_error に 5以上の値が入る可能性はありますか★ |
★unsigned tinyint(0〜255)★。★当方は「1 以外は未実施ではない」として扱っている★ |
| ★7★ | ★clean_statuses.clean_error(論理名「エラー」)と clean_status_histories.clean_error(論理名「結果」)の使い分けは何ですか★ |
★同じコード表だが論理名が違う(原文)★。★当方は履歴側だけを読んでいる★ |
| ★8★ | ★1つの clean_timetable_id に対し clean_status_histories は必ず1行ですか★ |
★複数あると ended_at 最新の1件を採る規則(本指示書で統一)が効く。★その規則が妥当かの裏取りが要る★★ |
| 対象 | いつ |
|---|---|
| ★タイムゾーン(DBがJSTかUTCか未確定)★ | ★★別件★★(禁止事項8) |
★サマリ系4本(reportKpis / facilityPerformance / errorBreakdown / continuousProblems)★ |
★指示書T(第5歩)★ |
★点検記録2本(inspections / inspectionDetail)★ |
★最後(D-072 項7)★ |
★dsAlerts の security_off 側★ |
★D-091 項3 の内部調整の結果待ち★ |
★PATCH /prechecks/{id}/status の API ルート実装★ |
★別の指示書★ |
★docs/decisions.md の既存記述の整理(D-099 §1 の「要確認」の書き換え等)★ |
★★本指示書は追記のみ★★。★既存の決定文を書き換えないこと★ |
npx tsc --noEmit エラー0 / npm run lint 0 / npm test 全パス(★813テストから増えていること★)clean_error のコード表とラベルが lib/config/ の1箇所にあり、0/1/2/3/4 が他のどこにも直書きされていないこと★★clean_error === CLEAN_ERROR.notExecuted★ になり、 cleanLogs() と prechecks() が同じ純粋関数(notExecutedFromHistory())を共有していること★★ (★2項の AND を呼び出し側に書いて規則を2箇所に散らしていないこと★) 6'. ★★cleanLogs() の mainRows フィルタに「履歴があること」の条件が足されていないこと★★ (★履歴の行が無い過去の本清掃も返る=出発失敗として出る。設計3-C★)latestHistoryByTimetableId())に統一されていること★★isFutureSchedule() が残っていること★★(★呼び出し2箇所ともそのまま★)cleanLogs() 253件 / departed === false 13件 / completion_rate === null 13件(ズレ0)/ prechecks() 27件(launch_failed 13)/ /dashboard 9 / 9★★clean_error = 1(13件)と 2 / 3 / 4(各3件以上)が入り、clean_status_histories が 253行になっていること★★/★2回実行して同じ結果になること★d-watch-api.yaml の CleanLogItem に clean_error(★nullable: true★)と facility_id が定義され、 lib/types/index.ts(★clean_error: number | null★)と一致していること★ 11'. ★★lib/fixtures/ の既存の値に差分が無いこと★★(★row() への導出フィールド追加だけ★。 ★git diff lib/fixtures/ で departed / completion_rate / status_1h / status_30m / 施設名 / 行数が 1つも変わっていないことを確認する★)components/ / app/api/clean-logs/export/route.ts / prisma/schema.prisma / lib/db/prisma.ts / local-guard.ts / use-fixtures.ts / ds-status.ts / precheck-mapping.ts / prismaSource の facilities / schedules / dsDevices / dsAlerts / サマリ系4本 に差分が無いこと★★docs/handoff/管理画面_設計検討_20260910.md が未追跡のまま残っていること(コミットに入っていない)★docs/decisions.md / docs/handoff.md(★冒頭の最新番号も★)の更新 / ★origin/develop へ push 済み★ / ★文書末尾の「実行状態」表を更新した★| 段階 | 状態 | コミット |
|---|---|---|
| ★着手前の確認★ | ★★なし★★ — ★判定に使う値(clean_error = 1)・コード表・ラベルはすべて確定済み★。★未決1件(2/3/4 の出し方)は「値を API まで運ぶ」ところで止めることで回避★ |
— |
| 段階0 前提の確認 | ★★完了★★ | コミットなし |
段階1 lib/config/ のコード表 |
★★完了★★ | d744285 |
| 段階2 期待値表 → テスト → 純粋関数 | ★★完了★★ | 703690b |
| 段階3 ★シードと読み出しを同時に切り替え★ | ★★完了★★(★同一コミット★) | 3bbd504 |
段階4 clean_error を API まで運ぶ |
★★完了★★ | 6dfd0b8 |
| 段階5 実測での検証 | ★★完了★★(★5-1 の4項目 / 5-2 の9項目とも期待どおり★) | コミットなし |
| 段階6 記録と push | ★★完了★★ | 本記録のコミット |
実行日: ★★2026-09-10★★ / 取得した決定番号: ★★D-107★★(実測で最終番号が D-106 であることを確認して採番) 実測値(着手前 / 完了後): ★43ファイル / 813テスト / tsc 0 / lint 0★ → ★★44ファイル / 862テスト / tsc 0 / lint 0★★ / ★cleanLogs() 253件(departed === false 13 / completion_rate === null 13 / ズレ0)/ prechecks() 27件(launch_failed 13)/ /dashboard 9 / 9 / clean_status_histories 253行(clean_error 0=225 / 1=13 / 2=5 / 3=5 / 4=5)★ ★決めたこと(実行前に確定済み)★: ★★departed = 履歴の行がある かつ その行の clean_error !== 1★★ / 出発失敗(未実施)の判定 = ★clean_error === 1★ または ★履歴の行が無い★ / 履歴の行が無い場合 = ★★これまでどおり「出発失敗」として残す(★行ごと除外しない★。現行の13件を維持。fail-loud)★★ / isFutureSchedule() = ★残す(★C 案の下では必須★)★ / コード表の置き場所 = ★lib/config/index.ts の1箇所★ / ラベル = ★定義書の原文ママ(未知の値は String(code)。「その他」「不明」は使わない)★ / clean_error = ★2/3/4 も含めて API まで運ぶ(★nullable★。画面・CSV には出さない)★ / lib/fixtures/ = ★★既存の値は1行も変更しない(型追従の導出フィールド追加のみ)★★ ★改訂で反転した点(起草版との差)★: ★起草版は「履歴の行が無い=除外」だったが、★★「出発失敗として残す」に反転★★★ / ★起草版が持っていた「fixtures が新仕様と矛盾する」という前提は★誤り★として撤回(fixtures は無変更)★ / ★決定番号を D-106 → D-107 にずらした★ 詳細: docs/decisions.md の該当番号を参照