Newer
Older
client_dwatch / docs / handoff / 指示書AS_完了率を先方の式に合わせる_20261002.md

指示書AS — 完了率を先方の式に合わせる・清掃結果を表示名にする・予定外タスクを完了率から外す

日付: 2026-10-02 / 担当: Cursor / 前提: D-155(本書の根拠)/ D-151 / 指示書AQ / 指示書AR / CLAUDE.md §4.2・§4.3・§4.7・§6・§7・§9.4

★渡す時期: 指示書AR・指示書AT(4カラム UPDATE)と一緒に渡す★(D-155 項6)。 ★当てる順番: AR を先に終えてから AS★(同じファイルのコメントを両方が直すため。§10 を参照)。

★§2 の未決は監督が決定済み(2026-10-02。D-156)★。


0. 何をするか

  1. ★清掃完了率の出し方を、TeamWorks のサマリと同じ式にする★(D-155 項1。ユーザー判断「一旦B」)。
    • 1回ごとの完了率 = min(clean_area ÷ task_area × 100, 100) を ★小数第1位まで切り捨て★
    • 複数回の完了率 = 1回ごとの完了率の ★単純平均★(丸めない。判定は生値、表示だけ整数に丸める)
    • ★clean_task_reports.clean_ratio 列は完了率に使わない★
  2. ★総清掃面積(F-08)の完了率と実施回数から、予定外のタスク(clean_timetable_id が NULL)を外す★。面積の合計には含める(D-155 項3)。
  3. ★清掃結果(clean_task_reports.status)を数字ではなく表示名で出す★(D-155 項2)。判定には使わない。
  4. 上の3つで古くなった説明(コメント・API 定義・fixtures の説明)を直す。

★暫定の仕様である★(「一旦」)。第11回で TeamWorks から訂正後の clean_ratio の式を受け取ったら見直す。 ★だから「1回ごとの完了率を決める関数」は ★1つだけ★ にし、あとで clean_ratio 列に戻すときに ★その1関数を直せば済む★ 形にする。


1. 背景 — なぜ変えるのか

いつ 何が起きたか
2026-09-30(D-151) 完了率を「clean_task_reports.clean_ratio 列の単純平均」にした。面積比(Σclean_area ÷ Σtask_area)は廃止
2026-10-02(第10回 N-3 の回答) TeamWorks: ★clean_ratio 列は「計算式が間違っている。訂正したら回答する」★。★サマリ(*_summaries.clean_ratio)は「清掃1回ごとに clean_area / task_area * 100(100より大きい値は100、小数点第2位以下切捨て)を計算し、合算して平均」★。★precheck = 0 の予定に紐づく行だけが対象で、clean_timetable_id が NULL の行は対象外★
2026-10-02(D-155) ★当期(アプリがその場で集計する分)もサマリと同じ式にする★。式が違うと、★前期比(サマリ)と当期(アプリ)の差が、別の式どうしの差になる★

★廃止したものと採用するものを取り違えないこと★(いちばん間違えやすい):

中身 状態
面積比(D-151 で廃止) ★期間の合計どうしを割る★ Σclean_area ÷ Σtask_area ★廃止のまま★。使わない
面積で重み付けした平均 task_area を重みにした平均 ★使わない★
★1回ごとの面積比の単純平均(本書で採用)★ ★1回ずつ割って、上限100・小数第1位まで切り捨て★、それを ★清掃1回=1票★ で平均 ★採用★

いまのコードには「面積から再計算しない」「面積比は clean_ratio と相関しない」という説明が多数ある。 ★1回ごとの面積比は、本書で正しい計算になる★ので、★それらの説明は直す必要がある★(段階6)。 ただし「Σ面積 ÷ Σ面積は使わない」「面積で重み付けしない」という説明は ★今後も正しい★ので残す。

承知している弱点(変えないこと)

clean_area は同じ場所を複数回清掃した面積の総和(第10回 N-4)。ユーザー提供CSV 189行では ★68.3% の行が clean_area > task_area★ で、★これらは全部 100% になる★。★◎が多めに出る★。ユーザーはこれを承知で採用した(D-155 項1)。★上限100を外したり、別の補正を入れたりしないこと★。


2. ★監督が決めたこと(2026-10-02。D-156)★

【未決1】task_area = 0 で、レポートの行はある清掃をどう扱うか

★本書の式では、task_area = 0 の清掃は「1回ごとの完了率 = null」になる★(D-155 項1。分子にも分母にも入れない)。 ところが ★いまのコードは「完了率が null ⟺ 出発していない(未実施)」を前提にしている★ (lib/db/clean-log-mapping.ts:186-190 の不変条件)。この前提で「未実施」を数えている箇所が3つある:

箇所 いまの数え方 task_area = 0 の清掃がどうなるか
lib/judge/rescue.ts:153(要レスキュー R-1「未実施3日連続」) completion_rate === null を未実施とする ★出発したのに「未実施」に数えられる★
lib/db/report-mapping.ts:647(launchFailedDaysInRun()。連続問題施設の「うち{m}日は出発失敗」) 同上 ★出発したのに「出発失敗の日」に数えられる★
lib/db/clean-log-mapping.ts:323-325(isProblemCompletion()。連続日数) null を「問題あり」とする 問題日に数えられる(判定は × なので、これは整合する)

詳細ログの1行は ★departed = true(✓)・清掃結果は表示名・完了率 -・判定 ×★ になる(judgeGrade(null) が × を返すため。lib/judge/grade.ts:30)。アドバイスは R1・R2・B1 のどれも出ない(lib/judge/advice.ts:61-83)。

選択肢:

案 内容 帰結
★A(推奨)★ ★「未実施」は departed で数える★。ConsecutiveInput(lib/db/clean-log-mapping.ts:328)と RescueRowInput(lib/judge/rescue.ts:68)に departed: boolean を足し、R-1 と launchFailedDaysInRun() は !departed を見る 意味どおりになる。★判定ロジックの入力が1つ増える★ので、表5 の期待値を先にテストで固定する
C(最小) いまの数え方のまま。不変条件が崩れることだけ docstring に書く 変更が小さい。★task_area = 0 の清掃が R-1・出発失敗日に誤って数えられる★(頻度は不明。第11回で task_area = 0 の扱いを確認中)

★本書の段階3-4 は案A で書いてある★。★監督が案C を選んだら、段階3-4 の「案Aのみ」と書いた作業と表5 を飛ばす★。

監督の決定: ★案A★(2026-10-02)。理由: CLAUDE.md §4.7「未実施 = 本清掃に対応するレポート行が無いこと」に合う。★レポート行がある清掃を出発失敗に数えると、現場に誤った連絡(R1・R-1)が飛ぶ★。段階3-4 と表5 はそのまま行うこと。

監督の決定(作成者が挙げた残りの論点)

# 論点 決定
2 F-08 で期間指定が無いとき、完了率用の行を保持期間(2年)ぶん取ってくる ★このまま採る★。ただし ★取った行数と所要時間をログに出す★(judge 系の既存イベントに rate_rows / elapsed_ms を足す)。実データで重ければ別途手当てする
3 事前チェックの予定・存在しない予定に紐づく行(表4 F3/F4) ★面積の合計には含め、完了率と実施回数からは外す(表4 のとおり)★
7 F-08 で「面積はあるが実施回数0」の施設が出る ★画面の注記を1行足す★: 「清掃面積には予定外の清掃を含みます。完了率と実施回数は予定された本清掃だけです」(EN: "Area includes unscheduled runs. Completion rate and run count cover scheduled main runs only."。英訳は暫定)
8 EN 表示で出発失敗のセルが "Launch failed" になる ★それでよい★(i18n を通すのが正しい)
6 面積が負の値 特別扱いしない(本書のとおり)
4 / 5 / 9 clean_timetable_histories の扱い / CLAUDE.md §4.2 の分母の書き方 / docs/code-map.md 監督が扱う(第11回・CLAUDE.md・別作業)。★Cursor は触らない★

3. 決定(Cursor は従うこと)

# 決定 理由
D1 ★1回ごとの完了率は lib/judge/kpi.ts の新しい関数 runCompletionRate() だけが決める★。シグネチャ: `runCompletionRate(report: { clean_area: number; task_area: number }): number \ null` ★暫定の式なので、戻すときに1関数で済むようにする★(CLAUDE.md §4.2)。判定層(lib/judge/)は DB 層を import しないので、引数は構造的な型にする(CleanRatioTally と同じ作法。kpi.ts:46-47)
D2 ★上限 100 と小数の桁数 1 は lib/config/index.ts に定数として置く★(例: RUN_COMPLETION_RATE = { cap: 100, decimals: 1 } as const。出所 = 第10回 N-3 を docstring に書く) CLAUDE.md §7(数値の直書き禁止)の趣旨に合わせる。式の部品が1箇所に集まる
D3 ★切り捨ての前に浮動小数の誤差を吸収する★。方法は次の1つに決める(§5-1): Math.floor(Math.round(raw * 10 ** (decimals + 5)) / 10 ** 5) / 10 ** decimals(decimals = 1 なら Math.floor(Math.round(raw * 1e6) / 1e5) / 10) 0.57 / 1 * 100 は 56.99999999999999 になり、そのまま切り捨てると 56.9 になる(表1 R6)。★1e-6 ポイント未満の差は誤差とみなす★
D4 ★task_area === 0 なら null★(分子にも分母にも入れない) D-155 項1。★暫定・第11回で確認★。task_area は NOT NULL(prisma/schema.prisma:525)なので null の扱いは要らない
D5 ★平均の取り方は変えない★ — meanCleanRatio()(kpi.ts:88-91)をそのまま使う。★平均は丸めない★。★判定は生値、表示だけ displayRate()(Math.round)★ CLAUDE.md §4.3。切り捨ては「1回ごとの値」だけ
D6 ★clean_ratio 列は完了率に使わない。ただし型・select・fixtures からは消さない★(読まないだけ) ★戻すときに runCompletionRate() の1関数を直せば済むようにするため★。呼び出し側は ★行(clean_ratio も持っている)をそのまま渡し★、呼び出し側で clean_area / task_area だけに分解しないこと
D7 ★内部の名前(meanCleanRatio / CleanRatioTally / cleanRatioSum / cleanRatioCount / clean_ratio_sum / clean_ratio_count)は変えない★。docstring で「1回ごとの完了率の合計・件数」と意味を書き直す 名前を変えると16ファイル・約200行の機械的な差分が中身の変更に混ざり、レビュー(daichi。D-154)で読めなくなる。★暫定の式なので、戻すときにも名前を戻さずに済む★
D8 ★judge.areas ログの分子・分母の項目名は、最終的に overall_completion_rate_sum / overall_completion_rate_count にする★(§10) 中身は「items[].completion_rate の合計と件数」。式の名前(clean_ratio)を項目名に入れると、式を戻したり変えたりするたびに名前と中身がずれる
D9 ★総清掃面積(F-08)の完了率と実施回数は、「clean_timetable_id が NULL でなく、その予定が本清掃(precheck = 0)である行」だけで数える★。★面積の合計は全行(予定外を含む)★ D-155 項3。サマリと同じ(N-3)。他の画面は既に本清掃の id で引いているので変更不要(§5-4)
D10 ★清掃結果の表示名は lib/config/index.ts に表で置き、lib/i18n.ts で英訳する★。★表に無い値は生値の数字のまま★(「その他」に潰さない)。★API の task_status は数字のまま★ D-155 項2。CLEAN_ERROR_LABELS(lib/config/index.ts:298-316)と同じ形。英語は仮(ユーザーが後でまとめて見直す)
D11 ★CSV の清掃結果は日本語の表示名★ CSV は見出し(lib/clean-logs/csv.ts:63-79)も「出発/未出発」(:103)も日本語固定で、言語を受け取っていない。それに合わせる
D12 ★清掃結果(status)は判定に使わない★ D-155 項2。中断・異常終了でも完了率で ◎○△× を出す。CLAUDE.md §4.4 の「中断で返る=失敗」は実データを見て見直す(本書の範囲外)

4. ★期待値表(正)★

★表1・表2 は監督が確定した正である。Cursor は値を変えないこと★(CLAUDE.md §0-2・§6-2・§9.4)。 ★段階1 で、実装より先にこの表をテストにする★。表とテストが食い違ったら、テストを表に合わせる。実装の都合で表を直さない。

表1 — 1回ごとの完了率 runCompletionRate

# clean_area task_area 期待値 確かめること
R1 50 100 50.0 基本
R2 100 100 100.0 ちょうど100
R3 150 100 100.0 上限で100に切る
R4 89.99 100 89.9 四捨五入ではなく切り捨て
R5 89.95 100 89.9 切り捨て(四捨五入なら90.0)
R6 0.57 1 57.0 浮動小数の誤差(56.99999…)で56.9にしない
R7 0.7 1 70.0 同上
R8 0 100 0.0 清掃面積0
R9 10 0 null 対象面積0は分子・分母の外
R10 1377.39502 1377.39502 100.0 実データの桁
R11 898.892509 1377.39502 65.2 65.26…を切り捨て

表2 — 平均(判定は生値の平均で行う。表示は Math.round)

# 1回ごとの値 平均(生値) 表示 判定
M1 [100.0, 80.0] 90.0 90% ◎
M2 [89.9, 90.0] 89.95 90% ○(表示は90だが判定は○。§4.3)
M3 [100.0, 40.0] 70.0 70% ○
M4 [70.0, 69.9] 69.95 70% △
M5 [10.0] 10.0 10% △
M6 [9.9] 9.9 10% ×
M7 [null, 50.0] 50.0 50% △(nullは数えない)
M8 [null] / [] null — 未実施と同じ扱い(×・出発失敗の表示は既存どおり)
M9 [150/100→100.0, 150/100→100.0, 0/100→0.0] 66.666… 67% △

テストの書き方の注意:

  • 平均は ★meanCleanRatio() に Σ と件数を渡して出す★(テストの中で自前の割り算を書かない)。null は Σ にも件数にも入れない
  • 「表示」は displayRate()(lib/judge/grade.ts:43-46)の戻り。★M8 の「—」は displayRate(null) の "-"★
  • 「判定」は judgeGrade(平均の生値)。★M8 は judgeGrade(null) = ×★
  • M9 は ★面積から runCompletionRate() を通して1回ごとの値を作ってから★平均する(表1 と表2 をつなぐケース)
  • 生値の比較は toBe(M2 の 89.95、M4 の 69.95 は JS でそのまま出る。2026-10-02 に node で確認済み)。M9 だけ toBeCloseTo(200 / 3, 10)

表3 — 清掃結果の表示名(formatCleanTaskStatus。出所: 第10回 N-2 の原文。D-155 項2)

入力(task_status) 日本語(画面・CSV) 英語(画面。★仮★)
null(レポートの行が無い) 出発失敗 Launch failed(既存キー lib/i18n.ts:208)
0 清掃開始前 Not started
1 清掃中 Cleaning
2 清掃中断 Interrupted
3 清掃キャンセル Cancelled
4 清掃正常終了 Completed
5 清掃異常終了 Ended abnormally
6(CSV実測にある・意味は未受領) 6 6
255 255 255

★日本語は N-2 の原文から1文字も変えないこと★(言い換え禁止。lib/config/index.ts:291-295 と同じ約束)。 ★「その他」「不明」に潰さないこと★(D-089 項3 / D-104 項2)。

表4 — 総清掃面積(F-08)の施設1件ぶんの集計(本書で定めた。表1・D9 から導いたもの)

純粋関数(§段階4 の cleanAreaStatsFromRows())に対するテスト。施設はすべて同じ1施設。

# その施設のレポート行 clean_area 合計 task_area 合計 executed_count completion_rate(aggregateCleanArea() の結果)
F1 予定外(clean_timetable_id NULL)100/100 の1行だけ 100 100 0 null
F2 本清掃 50/100 + 予定外 100/100 150 200 1 50.0
F3 事前チェック(precheck ≠ 0)の予定に紐づく 80/100 の1行だけ 80 100 0 null
F4 予定が見つからない id(dangling)80/100 の1行だけ 80 100 0 null
F5 本清掃 10/0 の1行だけ(task_area = 0) 10 0 1 null
F6 本清掃 150/100 + 本清掃 0/100 150 200 2 50.0((100.0 + 0.0) ÷ 2)
F7 本清掃の行の clean_ratio 列が 12.3、面積は 50/100 50 100 1 ★50.0★(★clean_ratio 列を読んでいないことの確認★)

表5 — 未実施の数え方(★【未決1】が案A のときだけ★)

# 清掃 departed completion_rate R-1 の未実施日 出発失敗の日(launchFailedDaysInRun) 連続日数の問題日(isProblemCompletion)
U1 レポートの行が無い false null 数える 数える 問題
U2 ★行がある・task_area = 0★ ★true★ ★null★ ★数えない★ ★数えない★ 問題(判定 × と同じ)
U3 行がある・50/100 true 50.0 数えない 数えない 問題(△)
U4 行がある・100/100 true 100.0 数えない 数えない 問題なし
U5 同じ施設・同じ日に U1 と U4 — — その日を1日数える(OR) その日を1日数える(OR) 問題

5. ★落とし穴(全段階で読むこと)★

# 罠 どう避けるか
5-1 ★浮動小数の誤差で切り捨てが1段下がる★(0.57/1*100 = 56.999… → 56.9) D3 の式を使う。★Math.floor(raw * 10) / 10 と書かない★。★toFixed() は四捨五入なので使わない★。表1 R6・R7 で固定する
5-2 ★平均した値を切り捨てる・丸める★ 切り捨ては1回ごとの値だけ(D5)。★meanCleanRatio() の戻りに手を加えない★。M2・M4 で固定する
5-3 ★periodOfSummaryRow()(lib/db/report-mapping.ts:99)に触る★ ★触らない★。年次行が月次に化けて500になる(CLAUDE.md §4.7)
5-4 ★サマリの clean_ratio(*_summaries)まで計算し直す★ ★サマリはそのまま読む★(kpiFromSummaryRatios()。report-mapping.ts:230-240)。★サマリの clean_ratio は既に本書と同じ式★(N-3)。SummaryRatios.clean_ratio(:221)は変えない
5-5 ★生SQL($queryRaw / $executeRaw)で F-08 を集計する★ ★禁止★(eslint が止める。CLAUDE.md §3)。上限・切り捨ては Prisma の groupBy で書けないので、★行を取り寄せて JS で計算する★(段階4)
5-6 ★閾値・式の数字を直書きする★ 90/70/10 は GRADE_THRESHOLDS、上限100と桁数は D2 の定数から読む
5-7 ★判定のルールを変える★ ★変えるのは「完了率の材料」だけ★。judgeGrade()・L1〜L3・アドバイスの条件は変えない(【未決1】案A の「未実施の数え方」だけは例外。表5)
5-8 ★clean_area / task_area を Decimal として扱う★ ★Prisma の型は Float(number)★(prisma/schema.prisma:525-526。D-094 で double(12,6) に変わった)。Number() 変換も Decimal の import も要らない
5-9 ★呼び出し側で report.clean_area / report.task_area を書く★ ★必ず runCompletionRate() を通す★。`grep -rn "clean_area /\ clean_area\s*\/" lib appで0件にする(runCompletionRate()` の中だけ)
5-10 ★clean_timetable_id が NULL の行を F-08 の番人と同じ扱いにし忘れる★ 番人(countDuplicatedTimetables()。lib/db/clean-area-source.ts:292-305)は ★変えない★。完了率用の取り寄せも ★同じ { not: null } で除く★(NULL は1グループにまとまる。同 :272-283)
5-11 ★include でレポート→予定を引く★ ★使わない★(clean_timetable_id に FK が無い。IN が膨らむ。clean-area-source.ts:56-62)。予定は ★id を集めて chunkForInClause()(lib/db/chunked.ts)で分けて★別クエリで引く
5-12 ★作業ツリーの CleanLogTable.tsx が「変更あり(M)」に見える★ 2026-10-02 時点で ★git diff の中身は0行★(改行コードだけの差と思われる)。★着手前に git diff components/features/detail-log/CleanLogTable.tsx を見て、自分の変更以外を混ぜないこと★
5-13 ★git add -A★ ★禁止★(CLAUDE.md §7)。ファイルパスを明示する

6. 段階ごとの作業

★各段階の終わりに npx tsc --noEmit -p . と npm test を通すこと★。 ★段階3 で fixtures を使うテストが落ちたら、段階6 の fixtures の作業を先にやってよい★(順番を入れ替えてよいのは fixtures だけ)。 行番号は 2026-10-02 の develop(ef34043)で確かめたもの。★AR を先に当てるとずれる★ので、記述(関数名・文)で探すこと。

段階1 — 期待値表をテストにする(実装より先)

  1. ★新しいテストファイル lib/judge/__tests__/run-completion-rate.test.ts★ を作る:
    • 表1 の11行を it.each で。期待値は toBe(R9 は toBeNull())
    • 表2 の9行(M8 は [null] と [] の2件に分ける)。§4 の「テストの書き方の注意」に従う
    • ★ファイル冒頭のコメントに表1・表2 をそのまま貼り、「本書 §4 が正。変えないこと」と書く★
  2. 表3 のテストを lib/format/__tests__/clean-result.test.ts(既存)に足す。英語は translate("en", formatCleanTaskStatus(x)) で確かめる
  3. 表4 のテストは段階4、表5 のテストは段階3-4 で書く(関数がまだ無いため)。★ただし段階1 の時点で it.todo で行だけ並べておく★(書き忘れ防止)
  4. ★この時点ではテストが落ちる(runCompletionRate が無い)のが正しい★。コミットはテストだけで1つ

段階2 — runCompletionRate() を作る(lib/judge/kpi.ts)

  1. lib/config/index.ts に D2 の定数を足す(GRADE_THRESHOLDS(:18)の近く)。docstring に出所(第10回 N-3 の原文「100より大きい値は100、小数点第2位以下切捨て」。docs/handoff/TeamWorks回答_第10回_20261002.md:73)と「暫定(D-155)」を書く
  2. lib/judge/kpi.ts に runCompletionRate() を足す。中身は D3・D4 の通り:
    • task_area === 0 → null
    • raw = clean_area / task_area * 100 → Math.min(raw, cap) → D3 の切り捨て
    • docstring に書くこと: ★「1回ごとの完了率を決めるのはこの関数だけ」★/★暫定(D-155)。clean_ratio 列に戻すときはこの関数の中身だけを直す★/D3 の誤差吸収の理由(R6)/承知している弱点(§1)/★Σ面積 ÷ Σ面積(廃止)とは別物★
  3. lib/judge/kpi.ts の既存の説明を直す:
    • 冒頭 :4-21 — :9 の式を「Σ 1回ごとの完了率 ÷ 件数(1回ごとの完了率 = runCompletionRate())」に。:12-14 は AR 追補2 で一部消える。残りを「完了率は D-155 で TeamWorks のサマリと同じ式にした(暫定)」に書き直す
    • CleanRatioTally(:39-54)— cleanRatioSum は「★1回ごとの完了率(runCompletionRate() の戻り)の合計★」、cleanRatioCount は「その件数。★未実施と task_area = 0 の清掃は数えない★」(D7。名前は変えない)
    • meanCleanRatio() の docstring(:56-87)— 「clean_task_reports.clean_ratio の単純平均」を「1回ごとの完了率の単純平均」に。:80-83 の「★その2つを割って『完了率』と呼ばないこと★(実測で clean_ratio と相関しない)」は ★いまの仕様と逆★ なので、「Σclean_area ÷ Σtask_area(期間の合計どうし)で割らないこと。1回ごとに割るのは runCompletionRate() の仕事」に直す。:65-72(単純平均にする理由)は残す
    • ★meanCleanRatio() の中身(:88-91)は変えない★
  4. 段階1 の表1・表2 のテストが通ることを確かめる

段階3 — 完了率を出しているすべての経路を runCompletionRate() に通す

★完了率を出している経路は次の表ですべて★(2026-10-02 に grep -rn "clean_ratio" app lib components scripts の全件を分類して確かめた。分類は §8)。

# 経路 変えるもの
3-1 清掃詳細ログの1行・アラート・エリア別完了率の行・連続日数・要レスキュー(すべて completionRateFromReport() 経由) lib/db/clean-log-mapping.ts
3-2 レポートKPI(当期) lib/db/report-mapping.ts
3-3 総清掃面積 F-08 段階4
3-4 要レスキュー R-1 と出発失敗の日(【未決1】案A のみ) lib/db/clean-log-mapping.ts / lib/db/consecutive-source.ts / lib/judge/rescue.ts / lib/db/rescue-mapping.ts / lib/db/report-mapping.ts

3-1. lib/db/clean-log-mapping.ts

  • CleanTaskReportLike(:91-107)に clean_area: number / task_area: number を足す。★clean_ratio は残す★(D6。docstring に「完了率には使っていない。runCompletionRate() を clean_ratio 列に戻すときのために残している」と書く)
    • docstring :101-102「★clean_area / task_area を足さないこと★」は ★いまの仕様と逆★ なので書き直す
    • :94 の prisma/schema.prisma:534 は ★古い★(いまは :531)。直す
  • completionRateFromReport()(:194-201)— 行があれば ★return runCompletionRate(report)★。行が無ければ null(いまと同じ)
    • docstring(:163-193)を書き直す。表を「行が無い → null/行があり task_area = 0 → null/それ以外 → runCompletionRate()」の3行にする。「面積から再計算しないこと」(:171-179)は ★逆★ なので消し、D-155 の経緯(D-151 の clean_ratio 列 → D-155 で1回ごとの面積比。暫定)を書く
    • ★不変条件(:186-190)を書き直す★: 「departed === false ⟹ completion_rate === null。★逆は成り立たない★(task_area = 0 の清掃は departed === true で completion_rate === null)」。【未決1】の決定(案A/案C)と、その帰結をここに書く
    • 「0%で実施」と「未実施」は別物(:181-184)は ★残す★(clean_area = 0、task_area > 0 なら 0.0。表1 R8)
  • テスト lib/db/__tests__/clean-log-mapping.test.ts: 行の形 { clean_ratio: … } だけで作っているもの(:173・:255-256・:282・:289 ほか)に面積を足す。★clean_ratio 列と面積比が食い違う行★(例: clean_ratio: 12.3, clean_area: 50, task_area: 100 → 50.0)を1件以上入れ、★列を読んでいないこと★を固定する。task_area = 0 → null も足す

3-2. lib/db/report-mapping.ts(レポートKPI の当期)

  • aggregateCleanStatsByFacility() の :482-484 / :492-493:
    const runRate = executed && report !== undefined ? runCompletionRate(report) : null;
    // …
    cleanRatioSum: bucket.cleanRatioSum + (runRate ?? 0),
    cleanRatioCount: bucket.cleanRatioCount + (runRate === null ? 0 : 1),
    (runCompletionRate は @/lib/judge/kpi から import。★lib/db/clean-log-mapping.ts は import しない★ — このファイルは未実施の判定を引数で受け取る作りで、依存を増やさない。:442-448)
  • ★executed と planned の数え方は変えない★(task_area = 0 の清掃も実施1回に数える。完了率の件数にだけ入らない)
  • docstring を直す: CleanStatsBucket.cleanRatioSum / cleanRatioCount(:282-295)、CleanStatsReportLike(:391-411。★clean_ratio は残す★。D6)、aggregateCleanStatsByFacility()(:428-437)、kpiFromCleanStats()(:504-505)
  • ★kpiFromSummaryRatios()(:225-240)と SummaryRatios(:217-223)は変えない★(5-4)
  • :265-280(errorCount の説明)は完了率と無関係。★触らない★
  • テスト lib/db/__tests__/report-mapping.test.ts: clean_ratio だけで行を作っているものに面積を足す。clean_ratio 列と面積比が食い違う行で「面積比のほうが使われる」ことを1件固定する。task_area = 0 の行は executed に入り完了率の件数に入らないことを1件固定する

3-3. 経路の確認だけ(コードの中身は変えない。コメントだけ直す)

completionRateFromReport() を通っているので、3-1 で自動的に新しい式になる。★行に面積の列が載っているかだけ確かめた★:

経路 どこで行を読むか 面積の列
清掃詳細ログ lib/db/index.ts:802-809(findMany、select なし=全列) 載っている
エリア別完了率 lib/db/index.ts:1076-1082(同上) 載っている
アラート lib/db/index.ts:1243-1249(同上。完了率は出さない) —
連続日数・要レスキュー lib/db/consecutive-source.ts:370-392(select あり) ★clean_area / task_area は既に select にある★(:387-388)
  • lib/db/consecutive-source.ts: ★select は変えない★(clean_ratio も残す。D6。テストが select のキーを完全一致で見ている)。コメントを直す: :34-37 / :72-79 / :130-132 / :249-252 / :283-288 / :382-386。要点は「★面積は R-3 の重み付けではなく、runCompletionRate() で1回ごとの完了率を出すために読んでいる★(:265-277 の『列を増やしてよい条件』の条件3 を満たし直した)」
  • lib/db/clean-log-mapping.ts の ConsecutiveInput.clean_area / task_area(:343-362): docstring の「completion_rate を面積から再計算しないこと」(:351-354)は ★逆★。「completion_rate は runCompletionRate() で作ったもの。★ここで割り直さないこと★(規則が2箇所に散る)」に直す
  • lib/judge/rescue.ts: 中身は変えない(completion_rate を受け取るだけ)。コメントを直す: :20-23 / :50 / :72 / :78-84 / :113-118 / :139-142。★「面積加重にしないこと」は残す★(今も正しい)
  • lib/db/rescue-mapping.ts:39-50: 「R-3 は clean_ratio の単純平均」を「1回ごとの完了率の単純平均」に。★「completion_rate から面積を捏造しないこと」(:49)は残す★
  • lib/clean-logs/areas.ts:85-113 / :129-137: 中身は変えない(AR 段階4 で分子・分母の数え方が整理される)。表(:89-92)の「clean_task_reports.clean_ratio の生値」を「1回ごとの完了率(runCompletionRate())」に。:94-98 の経緯は残す(AR §2)
  • lib/db/index.ts: :541(R-3 の説明)/ :695(「clean_ratio から judgeGrade()」→「完了率から」)/ :931-933(「report ? report.clean_ratio : null の形に戻さないこと」は ★今も正しい★。不変条件の書き方だけ 3-1 に合わせる)/ :1020-1026(AR 段階1-5 で直る。AS では「行の完了率は runCompletionRate()」に合わせるだけ)/ :1107-1112
  • lib/types/index.ts の docstring: :109-110 / :247-252 / :296-305 / :312-319 / :344 / :1092-1096

3-4. 未実施を departed で数える(★【未決1】が案A のときだけ★)

  1. ★先に表5 をテストにする★(lib/judge/__tests__/rescue.test.ts に U1〜U5、lib/db/__tests__/report-mapping.test.ts に launchFailedDaysInRun() の U1〜U5、lib/db/__tests__/consecutive-source.test.ts に departed の詰め方)
  2. ConsecutiveInput(lib/db/clean-log-mapping.ts:328)に departed: boolean を足す(必須。optional にしない)
    • consecutiveInputsFromRows()(lib/db/consecutive-source.ts:242-256): departed: !notExecutedFromReport(report)
    • rescueInputsFromCleanLogs()(lib/db/rescue-mapping.ts:51-68): departed: log.departed
  3. RescueRowInput(lib/judge/rescue.ts:68-76)に departed: boolean を足す。foldRescueDays() の :153 を const notExecuted = !row.departed; に。★completion_rate の Σ と件数は null を数えないまま★(:154-155)
  4. launchFailedDaysInRun()(lib/db/report-mapping.ts:647)を const isFailed = !row.departed; に。docstring :629-630 の不変条件の記述を直す
  5. ★isProblemCompletion()(lib/db/clean-log-mapping.ts:323-325)は変えない★(表5 U2 のとおり、null は問題日のまま)
  6. ★lib/judge/rescue.ts:16 の表(「R-1 … completion_rate === null が3スケジュール日以上連続」)を「departed === false」に直す★

段階4 — 総清掃面積(F-08)を1回ごとの完了率にし、予定外を完了率・実施回数から外す

いまは groupBy + _sum で施設ごとに clean_ratio を合計している(lib/db/clean-area-source.ts:249-263)。 ★上限100・切り捨ては groupBy で書けず、生SQL は禁止(5-5)★なので、★完了率と実施回数の材料は行を取り寄せて JS で作る★。 ★面積の合計は今までどおり groupBy で作る★(予定外を含む全行。行を運ばない)。

4-1. lib/db/clean-area-source.ts

  1. CleanAreaFacilityGroupRow(:73-77)と groupBy の引数(:102-111 / :253-261)から ★clean_ratio の _sum と _count を外す★(使わなくなる)。面積の _sum は残す
  2. 完了率用の行の型を足す: { facility_id: bigint; clean_timetable_id: bigint | null; clean_area: number; task_area: number; clean_ratio: number }(★clean_ratio は読まないが select に入れる。D6★)
  3. CleanAreaStatsSourcePrisma(:102-111)に次の2つを足す(テストからスタブを渡せる最小の形。既存の作法どおり):
    • cleanTaskReport.findMany({ where: CleanAreaWhere & { clean_timetable_id: { not: null } }, select: { facility_id, clean_timetable_id, clean_area, task_area, clean_ratio } }) — ★where は集計・番人と同じものに { not: null } を足しただけ★(countDuplicatedTimetables() の :299 と同じ形)
    • cleanTimetable.findMany({ where: { id: { in: bigint[] }, precheck: number }, select: { id: true } }) — ★precheck は CLEAN_TIMETABLE_PRECHECK.main(lib/config/index.ts:226-229)。0 を直書きしない★
  4. ★純粋関数 cleanAreaStatsFromRows(groupRows, reports, mainTimetableIds: ReadonlySet<string>) を足す★(cleanAreaStatsFromGroupRows()(:219-232)を置き換える):
    • 面積の合計は groupRows から(_sum の null は 0。いまと同じ)
    • reports のうち ★clean_timetable_id が null でなく、mainTimetableIds に入っているものだけ★(★「本清掃の集合に入っている」という肯定条件で数える。errorCountByMainTimetable()(report-mapping.ts:377-389)と同じ考え方★。dangling・事前チェックの行はこれで自然に落ちる):
      • executed_count += 1
      • const rate = runCompletionRate(report)。null でなければ clean_ratio_sum += rate / clean_ratio_count += 1
    • ★groupRows に無い施設の行が reports に来たら例外を投げる★(同じ where を使っていれば起きない。起きたら where の食い違いなので、黙って足さない)
    • facility_id は Number() に通す(:197-199 の罠。いまと同じ)
    • 戻りの型 CleanAreaFacilityStats は変えない(clean_ratio_sum は number | null のままでよいが、常に数値を入れる)
  5. loadCleanAreaFacilityStats(prisma, where)(:249-263)— ★関数名とシグネチャは変えない★(:242)。中で:
    1. groupBy(面積)と findMany(完了率用の行)を Promise.all で
    2. 行の clean_timetable_id を重複なしで集め、chunkForInClause() で分けて cleanTimetable.findMany(★塊が0個ならクエリを撃たない★。lib/db/chunked.ts:12-15)
    3. 戻った id を toString() して Set にし、4. の純粋関数に渡す
  6. docstring を直す: ファイル冒頭 :17-26(「完了率に面積を使わない」→「1回ごとの完了率に面積を使う。Σ面積 ÷ Σ面積 は使わない」)、:194-218(executed_count の意味が「本清掃に紐づくレポートの行数」に変わった。★予定外を含まない★)、:234-247(★「groupBy を使う(行を運ばない)」は面積だけになった。完了率用の行は運ぶ★。理由: 上限・切り捨てが groupBy で書けず、生SQL は禁止)
  7. countDuplicatedTimetables()(:265-305)は ★変えない★。:285-287「除いた NULL 行の面積は集計側では足されている」は今も正しい

4-2. lib/db/index.ts の cleanArea()(:1782-1861)

  • 呼び出し(:1846-1854)は変えなくてよい(loadCleanAreaFacilityStats() の中で済む)
  • docstring :1786-1794 を直す: ★「行を運ばない」「行を取り寄せてJSで足すと期間を広げた瞬間に破綻します」は、完了率用の取り寄せには当てはまらなくなった★。★なぜ運ぶことにしたか(上限・切り捨て・生SQL 禁止)★と、★運ぶのは clean_timetable_id が NULL でない行の5列だけ★であることを書く。★期間を指定しないと全期間(保持2年)を運ぶ★ことも書く(性能は §11 で監督が判断する)

4-3. fixtures 版(lib/db/fixture-clean-area.ts:49-88)

  • :71-74 / :83-84 の clean_ratio_sum に row.completion_rate を足している所を、★runCompletionRate(row) を通した値★にする(null なら Σ にも件数にも足さない)。executed_count は今のまま(completion_rate !== null の行=レポートがある行を数える)
  • ★fixtures(lib/fixtures/areas.ts)には予定外の行が無い★ことを docstring に書く(予定外を外す規則は 4-1 の純粋関数のテスト=表4 で確かめる)
  • ★USE_FIXTURES の true/false で数字が変わらないこと★は、段階6 で fixtures の面積を作り直した後に lib/db/__tests__/fixture-clean-area.test.ts で確かめる

4-4. 型・API 定義

  • lib/types/index.ts の CleanAreaFacilityStats(:1202-1242)・CleanAreaRow(:1287-1303)・CleanAreaTotal(:1316-1322)の docstring: executed_count は「本清掃に紐づくレポートの行数(予定外を含まない)」、clean_area / task_area は「予定外を含む全行の合計」、completion_rate は「1回ごとの完了率の単純平均」
  • d-watch-api.yaml: :876-886(GET /clean-area の説明)/ :2002-2004 / :2016-2023 / :2024-2027 / :2082-2085 / :2086-2089。★面積の合計と実施回数・完了率で母集団が違う★ことを明記する(面積は予定外を含み、回数・完了率は含まない)

4-5. テスト

  • lib/db/__tests__/clean-area-source.test.ts: 表4 の F1〜F7 を純粋関数 cleanAreaStatsFromRows() に対して書く。スタブで loadCleanAreaFacilityStats() を通し、★findMany の where に clean_timetable_id: { not: null } が付くこと★、★cleanTimetable.findMany の where に precheck: CLEAN_TIMETABLE_PRECHECK.main が付くこと★、★行が0件なら cleanTimetable.findMany を呼ばないこと★を固定する
  • lib/clean-area/__tests__/aggregate.test.ts / app/api/clean-area/__tests__/route.test.ts は形が変わらないので、落ちたものだけ直す

段階5 — 清掃結果を表示名にする

  1. lib/config/index.ts の cleanErrorLabel()(:314-316)の後ろに足す:
    export const CLEAN_TASK_STATUS = {
      notStarted: 0, cleaning: 1, interrupted: 2, cancelled: 3, completed: 4, endedAbnormally: 5,
    } as const;
    export const CLEAN_TASK_STATUS_LABELS: Readonly<Record<number, string>> = { /* 表3 の日本語 */ };
    export function cleanTaskStatusLabel(status: number): string {
      return CLEAN_TASK_STATUS_LABELS[status] ?? String(status);
    }
    docstring: 出所(第10回 N-2 原文。docs/handoff/TeamWorks回答_第10回_20261002.md:63)/★原文から1文字も変えない★/★表に無い値は生値(CSV実測の 6 など。意味は未受領・第11回で確認)★/★判定には使わない(D-155 項2)★/CLEAN_ERROR_LABELS とは ★別のテーブルの別のコード表★(clean_status_histories.clean_error)で、★互いに読み替えない★
    • CLEAN_ERROR の docstring(:231-274。該当は :239)の「status をこのコード表で読み替えないこと(値の意味が未受領)」は「status には別の表(CLEAN_TASK_STATUS_LABELS)がある」に直す
  2. lib/format/clean-result.ts:
    • formatCleanTaskStatus()(:58-64): null → CLEAN_RESULT_NOT_DEPARTED_LABEL(いまと同じ)、それ以外 → cleanTaskStatusLabel(status)
    • 冒頭の docstring(:1-45)を書き直す。「★status は生値をそのまま返す★」「画面には 4 のような数字がそのまま出る。それが意図した挙動」(:11-22)は ★いまの仕様と逆★。★関数名を改名した理由(:24-29)と null を「出発失敗」と出す根拠(:31-41)は残す★
    • :50-57 の @returns を直す
  3. lib/i18n.ts: 表3 の6語の英訳を足す(出発失敗(:208)の近く。★「英語は仮。ユーザーが後でまとめて見直す」とコメント★)。重複キーは lint が落とすので、既存キーと重ならないことを確かめる(2026-10-02 時点で6語とも未登録)
  4. components/features/detail-log/CleanLogTable.tsx:221-226: セルを t(formatCleanTaskStatus(r.task_status)) にする。コメント(:221-225)を「表示名を t() に通す。表に無い値は数字のまま(辞書に無いので t() も数字を返す)」に直す。★status_1h / status_30m の Pill は変えない★
    • 帰結: ★英語表示で「出発失敗」も "Launch failed" になる★(いまは日本語のまま出ている)
  5. lib/clean-logs/csv.ts: :104 は変えない(formatCleanTaskStatus() が日本語の表示名を返すようになるので、そのまま日本語が出る。D11)。見出しのコメント :72-75「中身は status の生値(例 4)で、日本語に変換しない」を直す
  6. d-watch-api.yaml の task_status(:1682-1700): ★型は integer のまま★。description を書き直す — 値の意味(表3)、表に無い値は画面・CSV で数字のまま、★判定には使わない★、null の意味(いまと同じ)。:1688-1692(「値の意味は未受領」「推測で変換しない」)は ★古い★
  7. テスト: 段階1 で書いた表3 のテスト。★既存の lib/format/__tests__/clean-result.test.ts:57(0 → "0")は逆の仕様なので "清掃開始前" に直す(:61 の 255 → "255" は今も正しい)。lib/clean-logs/__tests__/csv.test.ts:219 も同様に確かめる★。lib/clean-logs/__tests__/csv.test.ts で清掃結果の列が日本語の表示名になること(例: 4 → 清掃正常終了、6 → 6、null → 出発失敗)

段階6 — fixtures・API 定義・残りのコメントを直す

6-1. lib/fixtures/clean-task-reports.ts(★作り直し★)

いまは ★面積を clean_ratio から独立に作っている★(設計上の約束3。:46-51)。★本書の式では完了率が面積から決まる★ので、このままだと ★DB 経由(USE_FIXTURES=false。シードを入れたローカルDB)で docs/fixtures.md §2.1 の境界値(90.1 / 90.0 / 89.5 / 70.1 / 70.0 / 10.1 / 10.0 / 9.9 / 0)がすべて壊れる★。

  1. cleanTaskReportCleanArea()(:190-205)を「完了率から面積を作る」形に変える。引数に完了率を足す(例: cleanTaskReportCleanArea(taskArea, completionRate, cleanTimetableId)):
    • 完了率が100未満: clean_area = round6(task_area × 完了率 ÷ 100)
    • 完了率が100: clean_area = round6(task_area × 係数)、係数は pseudoRandom() で 1.00〜1.40(★task_area を超える行の再現。上限100の確認になる★)
    • ★この作り方で runCompletionRate({ clean_area, task_area }) が元の完了率に一致すること★を 2026-10-02 に node で確かめた(task_area が小数第6位まである200通り × 完了率 0.0〜100.0 の0.1刻み、20万件で不一致0件)。★それでもテストで全行を確かめること★(下の 4.)
  2. build() の :342 で完了率を渡す。clean_ratio(:343-344)は ★log.completion_rate のまま残す★(D6。戻すときに fixtures を作り直さずに済む)。コメントを「clean_ratio 列は完了率に使っていない。列に戻すときのために、清掃ログの完了率と同じ値を入れてある」に
  3. 冒頭の docstring(:26-27 / :41-51)と CLEAN_AREA_BELOW_TASK_AREA_SHARE(:177-188)を直す。★約束3(面積を clean_ratio から逆算しない)は廃止★し、理由(D-155 で完了率が面積から決まるようになった)を書く。:87-88 の clean_ratio の説明も
    • 帰結: ★fixtures の清掃ログに完了率100の行は無い★(最大 99.5。lib/fixtures/clean-logs.ts:230-241)ので、★clean_area > task_area の行は0件になる★。CSV の分布(68.3%)の再現はやめる
  4. テスト lib/fixtures/__tests__/clean-task-reports.test.ts:
    • ★追加★: 全行で runCompletionRate(row) === 対応する清掃ログの completion_rate
    • ★削除★: :147-154(過半数が task_area 超え)/ :156-164(clean_ratio が面積比と一致しない)/ :166-171(面積比100%超で clean_ratio が低い)。★いまの仕様と逆★
    • :50-61(clean_ratio が清掃ログの完了率と同じ)は残してよい(D6)
  5. scripts/seed-local-db.ts:657 の呼び出しに完了率(params.completionRate)を渡す。:645 のコメント(「決定4 でこれが clean_ratio になる(面積から逆算しない)」)と :675-676 を直す。:2127 のログ(clean_area > task_area の割合を CSV実測 68.3% と並べて出す)は、0% になることをコメントに書けば残してよい
    • ★シードの接続先・ガード(lib/db/local-guard.ts)には触らない★。★シードを流すのは任意★。流すなら dwatch_local だけ(ガードが他を拒否する)。★ステージングには絶対に向けない★(CLAUDE.md §3)

6-2. lib/fixtures/areas.ts

  • :201 の round1(…) を ★round6(小数第6位)★ にする。round1 だと task_area = 120 のような値で完了率が1段ずれる(2026-10-02 に node で確認。いまの施設は 1000 / 100 なのでずれないが、面積を変えた人が踏む)
  • 冒頭 :18-24 と :63-69 の説明を直す(「面積は全体率の計算に使われない」→「行の完了率は面積から runCompletionRate() で決まる。全体率は行の完了率の単純平均」)
  • ★:115-116 の「旧定義の面積ベース 802/1200 = 66.83…%」は残す★(AR §2。Σ面積 ÷ Σ面積 に戻っていないことの確認で、今も意味がある)。:115 の「clean_ratio の単純平均」だけ「行の完了率の単純平均」に
  • テスト lib/fixtures/__tests__/areas.test.ts に ★全行で runCompletionRate(row) === row.completion_rate(completion_rate が null の行を除く)★ を足す

6-3. docs/fixtures.md

  • §2.5(:107-133): :116-118 の「全体率は各行の clean_ratio の単純平均」を「行の完了率(runCompletionRate())の単純平均」に
  • §2.6(:137-220): :160-161(CSV の形の説明)は実測なので残す。:170(ケース #2)を「clean_area を清掃ログの完了率から作る(clean_ratio 列にも同じ値)」に、:175(ケース #7「面積比と clean_ratio の乖離」)を ★削除★して「完了率100の行は task_area を超える面積にする(上限100の確認)。ただし fixtures の清掃ログには完了率100が無いので該当0件」に置き換える。:212-220 に D-155 の経緯を1行

6-4. d-watch-api.yaml(完了率の説明)

clean_task_reports.clean_ratio を完了率の出所として書いている所を、すべて「1回ごとの完了率(min(clean_area ÷ task_area × 100, 100) を小数第1位まで切り捨て。task_area = 0 は除く)の単純平均。D-155(暫定)」に直す: :338-343 / :348-350 / :563-566 / :595-602 / :1701-1710(★:1707「clean_ratio の生値(面積から再計算しない)」は逆★。:1708「行が無いとき null」に「task_area = 0 のときも null」を足す)/ :1757-1760。 段階4・段階5 で直した所と合わせて、★grep -n "clean_ratio" d-watch-api.yaml の残りが「サマリの列(*_summaries.clean_ratio)」の説明か、経緯の説明だけになること★。

6-5. その他の古い説明

ファイル:行 いま 直し方
lib/reports/target-customer.ts:12 清掃完了率の合算「★できる★」(分子 clean_area / 分母 task_area) ★「★できない★(1回ごとの完了率の合計と件数がサマリに無い。clean_area / task_area の合計で割ると Σ面積 ÷ Σ面積 になり、サマリの式と違う)」★
lib/clean-area/aggregate.ts:5 / :16-19 / :145 / :158 / :188 / :221-222 「clean_ratio の単純平均」「clean_ratio の Σ」 「1回ごとの完了率の単純平均」「1回ごとの完了率の Σ」。★中身は変えない★
lib/judge/__tests__/rescue.test.ts:42 / :215-231 / :271-275 「clean_ratio の生値」「面積比162.8%の行でも clean_ratio の 61 を使う」 テストの中身(completion_rate を直接渡している)は今も正しい。名前と説明を「1回ごとの完了率」に。3-8・3-9 は「判定は面積を読まず、渡された completion_rate だけを使う」ことの確認として残す
lib/db/__tests__/consecutive-source.test.ts:72 / :79 / :118-122 clean_ratio = 50 → completion_rate = 50 面積を足し(例 50/100)、期待値は表1 に従う
components/features/detail-log/AreaModal.tsx:39 AR 段階1-4 で直る AS では「行の完了率」の出所を runCompletionRate() に合わせるだけ

★最後に次の grep を流し、残ったものを §8 の分類に当てはめる★:

grep -rn "clean_ratio\|面積から再計算\|相関しない" app lib components scripts d-watch-api.yaml docs/fixtures.md --include=*.ts --include=*.tsx --include=*.yaml --include=*.md | grep -v "lib/generated"

段階7 — 全体の確認

  1. npx tsc --noEmit -p . — エラー0件
  2. npm run lint — 指摘0件($queryRaw を使っていないこと・any が無いこと)
  3. npm test — ★全件通過★
  4. npm run build — 成功
  5. ★画面で確かめる(USE_FIXTURES=true)★: .claude/launch.json の d-watch-fixtures(ポート 3100)で起動する
    • 清掃詳細ログ: 当日の境界値の行が §2.1 どおり(施設2 = 90% ◎ / 施設3 = 90% ○ / 施設5 = 70% ○ / 施設8 = 10% △ / 施設9 = 10% × / 施設10 = 0% ×)。★清掃結果の列が「清掃正常終了」などの表示名になり、6 は 6 のまま★、施設12 は「出発失敗」
    • JP/EN 切替で EN にし、清掃結果が英語("Completed" など)、6 は 6、出発失敗は "Launch failed"
    • 清掃結果の CSV を出し、清掃結果の列が日本語の表示名であること
    • エリア別詳細(施設7・1日前): 全体 81% ○(行は 62 / 94 / 88)
    • 総清掃面積(/clean-area): 画面が開き、完了率と実施回数が出る
    • レポート(当期)・ダッシュボードの要レスキュー: 開いてエラーが出ないこと
  6. ★judge.areas ログ★(エリア別詳細を開いたときに標準出力に出る)に overall_completion_rate_sum / overall_completion_rate_count が出て、raw_overall_completion_rate と割り算が合うこと

7. テスト一覧(本書で足す・直すもの)

ファイル 内容 段階
lib/judge/__tests__/run-completion-rate.test.ts(新設) 表1・表2 1
lib/format/__tests__/clean-result.test.ts(既存に追加) 表3 1 / 5
lib/db/__tests__/clean-log-mapping.test.ts 面積から完了率/clean_ratio 列を読まない/task_area = 0 3-1
lib/db/__tests__/report-mapping.test.ts 当期KPI の完了率が面積から/task_area = 0 は実施に入り件数に入らない/(案A)launchFailedDaysInRun 表5 3-2 / 3-4
lib/judge/__tests__/rescue.test.ts (案A)表5 の R-1 3-4
lib/db/__tests__/consecutive-source.test.ts 面積を足す/(案A)departed 3-3 / 3-4
lib/db/__tests__/clean-area-source.test.ts 表4 F1〜F7/where と precheck/0件でクエリを撃たない 4
lib/db/__tests__/fixture-clean-area.test.ts runCompletionRate() 経由/数字が変わらない 4
lib/clean-logs/__tests__/csv.test.ts 清掃結果が日本語の表示名 5
lib/fixtures/__tests__/clean-task-reports.test.ts 全行で面積から完了率が一致/逆の仕様のテスト3件を削除 6
lib/fixtures/__tests__/areas.test.ts 全行で面積から完了率が一致 6
app/api/clean-logs/[id]/__tests__/route.test.ts judge.areas の項目名(§10) 6

8. clean_ratio の使われ方の分類(2026-10-02。grep -rn "clean_ratio" app lib components scripts)

分類 何か 扱い
★完了率の材料(clean_task_reports.clean_ratio 列)★ lib/db/clean-log-mapping.ts:106 / :200、lib/db/report-mapping.ts:408 / :492、lib/db/clean-area-source.ts:75 / :107 / :229 / :258、lib/db/fixture-clean-area.ts:73 / :83 ★runCompletionRate() に置き換える★(段階3・4)。型・select からは消さない(D6)
★サマリの列(*_summaries.clean_ratio)★ lib/db/report-mapping.ts:221 / :237 / :521(prev?.clean_ratio) ★そのまま★(5-4。既に本書と同じ式)
内部の合計・件数の名前 cleanRatioSum / clean_ratio_sum ほか(16ファイル) ★名前は変えない★(D7)。docstring だけ直す
select の列 lib/db/consecutive-source.ts:116 / :377 ★残す★(D6)
fixtures・シードが列に入れる値 lib/fixtures/clean-task-reports.ts:88 / :344、scripts/seed-local-db.ts:676 ほか ★残す★(D6)。説明だけ直す
clean_status_histories 側のシード scripts/seed-local-db.ts:1280-1318 / :1476-1478 / :1706-1730、サマリのシード :1835-1885 ★触らない★(旧テーブル・サマリの仮値。本書と無関係)
説明(コメント・API 定義) 上の段階3〜6 で挙げたもの 直す
経緯として書かれた説明(「旧:」「以前は」「廃止」) 例 lib/clean-logs/areas.ts:94、lib/fixtures/areas.ts:116 ★残す★(AR §2 と同じ)
生成物 lib/generated/prisma/** ★触らない★

★完了率の画面表示は、どの画面も completion_rate を displayRate() に通すだけ★で、clean_ratio を直接表示している画面は無い(grep -rn "clean_ratio" components は AreaModal.tsx:39 のコメント1件だけ)。


9. 触ってはいけないもの

  • prisma/schema.prisma(★DB の定義は TeamWorks の領域★。CLAUDE.md §7)
  • lib/generated/prisma/**
  • scripts/seed-local-db.ts の ★接続先とガード★(lib/db/local-guard.ts を含む)。★変えてよいのは 6-1 の5. の呼び出しとコメントだけ★
  • CLAUDE.md / docs/decisions.md(監督が直す)
  • docs/code-map.md(監督が直す。§11)
  • ★4カラム UPDATE(status / comment / responded_at / resolved_at)★ — 指示書AT の範囲
  • periodOfSummaryRow()(lib/db/report-mapping.ts:99)と kpiFromSummaryRatios()(:230-240)
  • countDuplicatedTimetables()(lib/db/clean-area-source.ts:292-305)
  • judgeGrade() / displayRate() / isProblemGrade() / matchAdviceRules() / GRADE_THRESHOLDS(判定の規則は変えない。5-7)
  • CLEAN_ERROR / CLEAN_ERROR_LABELS の値(lib/config/index.ts:275-304。docstring の1文を直すだけ。段階5-1)
  • CLAUDE.md §4.4 の「中断で返る=失敗」に関わる判定(D12。実データを見てから)
  • 時刻の補正(D-155 項4。アプリのコードは変えない)

10. 指示書AR との関係(★AR を先に当てること★)

AR の段階 本書との関係
段階1-3 / 1-4 / 1-5(areas/route.ts:126 / AreaModal.tsx:39 / lib/db/index.ts:1023 のコメント) AR で「全体率は行の完了率の単純平均」に直す。★本書では「行の完了率 = runCompletionRate()」を足すだけ★
追補2(lib/judge/kpi.ts:12-14 の句を削る) AR で削ったあと、本書の段階2-3 で冒頭を書き直す
★段階4(judge.areas の項目)★ ★AR のとおり overall_clean_area / overall_task_area を消して overall_clean_ratio_sum / overall_clean_ratio_count を足す★。★そのあと本書で、この2つを overall_completion_rate_sum / overall_completion_rate_count に改名する★(D8)

AR と AS を当てたあとの JudgeAreasLogEvent(lib/logger/events.ts)の最終形:

項目 中身
raw_overall_completion_rate 全体率(丸める前)。いまと同じ
★overall_completion_rate_sum★ 全体率の分子 = ★completion_rate が null でない行の completion_rate(=1回ごとの完了率)の合計★
★overall_completion_rate_count★ 全体率の分母 = その行数
overall_clean_area / overall_task_area / overall_clean_ratio_sum / overall_clean_ratio_count ★出さない★
  • docstring(lib/logger/events.ts:158-171)に ★raw_overall_completion_rate === overall_completion_rate_sum / overall_completion_rate_count(分母0なら null)★ と、★項目名に式の名前を入れなかった理由(D8)★を書く
  • ★本番は未稼働で、このログを集計・監視している仕組みは無い★(AR 4-2)ので、改名で壊れるものは無い。★ただし本番稼働後は改名しない★(D-151 6b と同じ理由)
  • AR 段階4 のテスト(app/api/clean-logs/[id]/__tests__/route.test.ts)の項目名も合わせて直す。★AR と AS を同じ日に続けて当てるなら、AR 段階4 を最初から最終形の名前で入れてもよい★(最終形が上の表と同じなら可)

11. 範囲外(本書ではやらない。監督が扱う)

  • docs/code-map.md(:22 / :785 / :857-884 / :947 / :1603-1609 / :2159 / :2336 / :2377 / :2408 に D-151 の定義が書いてある)
  • 第11回の照会(task_area = 0 の扱い・status = 6 の意味・訂正後の clean_ratio・clean_timetable_histories の扱い)
  • 総清掃面積の取り寄せ量の実測(期間なしで全期間を運ぶ。段階4-2)
  • 清掃詳細ログを予定起点・実績起点のどちらにするか(D-151 項7。保留中)

12. コミット

段階ごとに分ける(例: 段階1 / 段階2 / 段階3 / 段階4 / 段階5 / 段階6)。★git add はファイルパスを明示する★(git add -A 禁止)。 メッセージには ★何を変えたか★ と ★なぜ(D-155。TeamWorks 第10回 N-2 / N-3)★ を書く。★段階1 のコミットは「テストだけ(この時点では落ちる)」と明記する★。