非エンジニア向け。 D-Watch のソースコードのどのファイル・どの関数・どの数値が何を司っているかを、 専門用語をできるだけ使わずに書いたものです。
develop の a1d7e43) 初版 2026-09-10 / 第2版 2026-09-14 / 第3版 2026-10-01 / 第3版改訂 2026-10-05(D-156)f5dce7b まで)★ — 指示書AU で古くなった記述だけを直しました (当期 KPI の式・task_area = 0 は 0%・予定は2表から読む・見出し語「屋号」→「顧客」・新しいファイル2本)。 ★それ以外の行番号・行数は第4版(a1d7e43)のまま★です。PDF は作り直していません137a94b まで)★ — 指示書AV で古くなった記述だけを直しました (ダッシュボードの数値カードと表を「各清掃エリアの直近の清掃(7日以内)」に・ダッシュボードが /api/clean-logs を呼ばなくなった・ 清掃詳細ログの既定を全期間に・新しいファイル2本と cleanLogRunMeta())。★直した箇所の行番号は 137a94b で確かめました★。それ以外は上のとおりですf02eb1d まで)★ — 指示書AW で増えたもの・古くなった記述だけを直しました (新しい画面 ★エラー一覧(/errors)★・API 2本(GET /api/errors・PATCH /api/errors/{error_id}/status)・DataSource の3メソッド・ lib/error-reports/・lib/db/error-report-mapping.ts・fixtures 2本・lib/prechecks/status-cycle.ts(★改訂4 で削除。D-161★)・メインDBへの書き込みが2本の道になったこと)。 ★新しく書いた箇所の行番号は f02eb1d で確かめました★。それ以外は上のとおりです(★lib/db/index.ts の既存メソッドの行番号は AV・AW で後ろにずれています。例: Prisma 版 prechecks() は :1216 → :1399(f02eb1d)★)04f974a まで。コードは 8a657a2 から変わっていない)★ — 10/06〜10/07 の手直しで古くなった記述だけを直しました (★アラート・エラー一覧の対応状況は「編集モード」でだけ変えられ、「保存(N件)」でまとめて送る★(D-161 のプルダウン → D-165)・lib/prechecks/status-cycle.ts の削除・ ★レポートの CSV が動くようになった★(GET /api/reports/facility-performance/export。D-163)・CSV の共通部品 lib/csv.ts と値が無い率の空欄(D-163・D-164)・ components/ui/date-picker.ts・分類プルダウンの固定幅(D-162)・表の列幅の配り方と見出しの寄せ(D-166〜D-168。★新設 3.15★)・ ★事前チェックは成功・失敗の判定に使わない★(D-170。5.9)・表示する事前チェックは 60/30 のまま(D-169)・ ★事前チェックの予定が無い清掃の 1h前ST / 30分前ST は null(画面「-」・CSV 空)、本清掃が成功した事前チェック異常はアラートで控えめに★(D-171。lib/format/precheck-status.ts・main_run_succeeded)・ ★レポートがまだ無い清掃は、予定の終了から2時間(RESULT_WAIT_MINUTES)まで「結果待ち」とし、失敗に数えない★(D-173。isResultPending()・CleanLogItem.result_pending・ダッシュボードの「結果待ち」カード・settleMinutes の廃止。★新設 5.9b★))。 ★直した箇所・新しく書いた箇所の行番号は a961829〜04f974a(HEAD。a961829 以降はテスト1本と docs だけの変更)で git show HEAD:<path> から確かめました★(D-171 までの節で f2b91fc と書いてある行番号は、D-173 で動かなかったファイルのもの)。それ以外は上のとおりです(★lib/config/index.ts は AU 以降で後ろにずれています。例: MAX_CSV_ROWS は :469 → :494(a961829)★。D-161〜D-173 で触っていない行は直していません)。 期待値表(D-171 の N1〜N5・M1〜M6、D-173 の表1・表2)の中身は docs/decisions.md とテストを見てください。★D-172(第12回の候補への回答)のうち、レポートの書き込みの遅れ(30分を超えうる)は D-173 で解決済み★。★D-174(アドバイス B2 の根拠をエラーコードが決まったら本清掃のエラー内容に切り替える)は 5.8 に書きました★f5dce7b)は 106ファイル / 2,540件(全件成功。2026-10-06 npx vitest run で実測)★。★改訂2 時点(137a94b)は 111ファイル / 2,601件(全件成功。監督が確認)★。★改訂3 時点(f02eb1d)は 119ファイル / 2,790件(全件成功。監督が確認)★。★改訂4 時点(04f974a)は 125ファイル / 2,946件(全件成功。監督が確認。f02eb1d から +7本・−1本。11.2)★★章5-B「画面に出る数字の出どころ」を新設しました。★ 画面に出る数字を1つずつ、どのテーブルのどの列から、どういう式で、なぜその根拠なのかを表にしています。 「この数字は何を数えているのか」「なぜこの計算なのか」と思ったら、まずそこを見てください。
あわせて章8(データの出どころ)と章6(設定値)にも、取得元の列と根拠の決定番号を添えました。
この1か月で、判定の基準・完了率の出し方・接続先の呼び名が何度か変わりました。 いま正しいのは右端の列です。
| 項目 | 9/14 版 | 第3版(10/01) | ★現在(第4版)★ | 根拠 |
|---|---|---|---|---|
| ◎○の境界 | 90%超(90.0 は ○) | 90%以上(90.0 は ◎) | ★90%以上(90.0 は ◎)★ | D-139 |
| ○△の境界 | 70%超(70.0 は △) | 70%以上(70.0 は ○) | ★70%以上(70.0 は ○)★ | D-139 |
| ×の境界 | 10%未満 | 10%未満 | 10%未満(10.0 は △) | D-139 |
| ★清掃完了率の出し方★ | 期間の面積の合計どうしの比 | clean_ratio 列の単純平均 |
★1回ごとの面積比(上限100・小数第1位切り捨て)の単純平均。★clean_ratio 列は使わない★(★確定。第11回 P-2=A★)★ |
D-151 → D-155 → D-158 |
★task_area = 0 の清掃★ |
— | — | ★1回ごとの完了率を 0%(判定 ×)★。旧(10/02〜10/05): null(分子・分母の外) |
D-155 → D-158(第11回 P-5=B) |
| ★当期の稼働率・出発成功率・エラー発生率★ | — | — | ★正常終了(status 4)÷ 予定 / 開始できた回数 ÷ 予定 / エラー ÷ 予定(サマリと同じ式。エラー発生率は上限なし)★。旧(指示書AU の前): レポート行がある回数 ÷ 予定 / 同じ / エラー ÷ 実施回数 | D-158(第11回 P-6) |
| ★清掃の予定の取得元★ | — | clean_timetables |
★clean_timetables と clean_timetable_histories の両方(同じ id は本表を採る)★ |
D-158 項4(第11回 P-7) |
★customers の見出し語★ |
— | 屋号 | ★顧客(EN "Customer" / "All Customers")★ | D-158(第11回 P-15=B) |
| ★ダッシュボードの数値カード・表の母集団★ | 当日 | 当日 | ★各清掃エリアの直近の清掃1件(7日以内・★予定の終了 ≤ 現在★の予定のみ・出発失敗と結果待ちを含む)★。★旧(10/06): 終了+30分(settleMinutes)たった予定のみ。D-173 で廃止★。?date= 指定時は従来どおりその日。★スケジュール進捗・要レスキューは当日のまま★ |
D-159(指示書AV)→ D-173 |
| ★清掃詳細ログの既定の期間★ | 今日1日 | 今日1日 | ★期間を指定しない(全期間)★ | D-159 追記(ユーザー指示「C」) |
予定外のタスク(clean_timetable_id が NULL) |
— | (扱いなし) | ★完了率・実施回数には入れない。総清掃面積には入れる★ | D-155 項3 |
| 清掃実績の取得元 | clean_status_histories |
clean_task_reports |
clean_task_reports |
D-151 |
清掃結果(clean_task_reports.status) |
— | 数字のまま | ★表示名(0 清掃開始前〜5 清掃異常終了)★。判定には使わない | D-155 項2 |
| ★接続先DBの呼び名★ | (外部DBは未接続。ローカルの MySQL のみ) | 「本番DBそのもの」 | ★ステージング(本番DBは別にある)★ | D-148 → D-152 |
| 接続先DBの中身 | (ローカルに当方のシード) | 実データ(予定0行でほぼ空) | 実データ(ドン・キホーテの実店舗10件+本社) | 2026-10-01 |
| ★DBへの書き込み★ | しない | しない | ★/alerts から4カラムを更新できる★(ステージングに限り可)。★10/06 から /errors(エラー一覧)からも、robot_error_reports の全行に書ける★ |
D-152 / D-156 / ★D-160★ |
| ★エラーの一覧★ | — | — | ★/errors(エラー一覧)が robot_error_reports の全行を出す(既定は未解消・対応中。種類は生値のまま)★。アラート画面は事前チェック+出発失敗のまま |
D-160(指示書AW) |
★対応状況・備考の変え方(/alerts・/errors)★ |
— | —(10/02〜10/06 は押すたびに巡回するボタン+備考はフォーカスが外れたら保存。指示書AT) | ★編集モード★(通常は表示だけ。「編集」で入り、プルダウンと入力欄で下書き → 「保存(N件)」で★変えた行だけを1行ずつ順に★ PATCH。全部成功で通常に戻る・失敗行は下書きを残す・編集中は絞り込みとページ送り不可)★ | D-161(プルダウンで即保存。10/06)→ D-165 |
| ★レポートの「CSVエクスポート」★ | 押しても何も起きない | 押しても何も起きない | ★施設別パフォーマンスの表を CSV で出す(表と同じ条件・limit は無視して全施設)★ |
D-163 |
| ★CSV の値が無い率★ | - |
-(Excel では '- と見えた) |
★空のセル★(清掃詳細ログ・レポートの両方。数式対策 D-125 は維持) | D-164 |
| ★事前チェックの結果・有無と成功/失敗★ | — | — | ★判定に使わない★(本清掃のレポート行の有無と完了率だけで決める。すでにそうなっていたことを確かめ、歯止めのテストを足した) | D-170(D-048 と同じ考え方) |
| ★事前チェックの予定が無い清掃の 1h前ST / 30分前ST★ | Standby. |
Standby.(異常なしと区別できない) |
★null★(画面「-」・CSV 空のセル・アドバイス B2/B3 に当たらない)。予定があって異常なしは Standby. のまま |
D-171 |
★本清掃が成功した事前チェック異常(/alerts)★ |
— | — | ★カードを控えめに(不透明度 0.6)+「本清掃は成功」★。API に main_run_succeeded。★対応状況・件数は変えない★ |
D-171 |
| ★レポートがまだ無い清掃★ | — | 予定の開始を過ぎたら出発失敗 | ★結果待ち★(予定の開始 ≤ 現在 < 予定の終了+RESULT_WAIT_MINUTES(120分)。清掃中も含む)。★失敗・未実施に数えない★(詳細ログは出発「…」・清掃結果「結果待ち」・判定なし/ダッシュボードは「結果待ち」カード/アラートの出発失敗・連続日数・要レスキュー・当期の率から外す)。過ぎたら今までどおり出発失敗。★旧(10/06〜10/07 朝): ダッシュボードだけ終了+30分待ち、他は開始を過ぎた瞬間から出発失敗★ |
D-173(D-091 項1 をこの件に限り上書き) |
| DBの時刻 | — | 一部の列がUTC | ★全列 JST の壁時計の値★(先方が統一。当方の再実測は未了) | D-155 項4 |
| 年次の集計 | 読まない | 解禁 | 解禁 | D-151 |
| 画面 | 12 | 13 | 13(★改訂3 時点 14★) | — |
| APIルート | 19本 | 22本 | 23本(★改訂3 時点 25本・改訂4 時点 26本★) | — |
DataSource のメソッド |
15本 | 20本 | 22本(★改訂2 時点 23本・改訂3 時点 26本★。改訂4 で増減なし) | — |
| テスト | 1,668件 | 2,297件 | 2,469件(★改訂時点 2,540件・改訂2 時点 2,601件・改訂3 時点 2,790件★。改訂4 時点 125ファイル・2,946件) | — |
★清掃完了率の式は確定しました★(第11回 P-2=A「今の式のまま」。P-1: 訂正後の clean_ratio 列もこの式と同じ値になる。D-158)。 ★まだ「暫定」の数字もあります★(施設ステータスの閾値など)。どれが暫定かは章5-Bの一覧にまとめています。
| 章 | 内容 | こんなときに |
|---|---|---|
| 0 | このドキュメントの読み方 | 最初に |
| 1 | D-Watch は何をするアプリか | 全体像をつかむ |
| 2 | まず1本だけ追う | ★これだけ読めば他の画面も分かる |
| 3 | 画面カタログ(13画面。★改訂3 から14画面★) | 画面から探す |
| 4 | APIの入口(23本。★改訂4 時点 26本★) | データの窓口を探す |
| 5 | 判定と計算(◎○△×・L1〜L3・KPI) | ★数字の出し方を知る |
| 5-B | ★画面に出る数字の出どころ(テーブル・列・式・根拠)★ | ★この数字は何?なぜこの計算?★ |
| 6 | 変えると挙動が変わる数値 | ★数値を変えたい |
| 6-B | クエリ層・型・設定の保存 | 絞り込みや並べ替えを変えたい |
| 7 | 見せ方(整形・部品・色・言葉) | 表示や文言を変えたい |
| 8 | データの出どころ | どのテーブルから来るのか知りたい |
| 9 | 裏方(認証・IP制限・ログ) | アクセスできない・ログを見たい |
| 10 | 人が書いていないファイル | ファイル数に圧倒されたとき |
| 11 | テストは何を守っているか | 変更後の確認 |
| 12 | よくある変更と、触る場所 | ★やりたいことが決まっている |
| 13 | 用語対応表 | 画面の言葉からコードを探す |
| 14 | 設定ファイルと保存ファイル | ビルド設定・環境変数・保存先を知る |
| 付録A | 全ファイル一覧 | ファイル名から探す |
| 付録B | 画面から引く逆引き索引 | 画面名から探す |
| 付録C | 設定値・定数の早見表 | 数値から探す |
急いでいる人へ: 章2 →章5-B →章12 →付録B の順に読めば、たいていのことはできます。
コードを書き換えることもあるが、プログラミングの専門家ではない人のための、D-Watch のソースコードの地図です。 「この数字はどこで決まっているのか」「ここを直したら何が変わるのか」を引けるようにしてあります。
| この文書 | 他の文書 | |
|---|---|---|
| ファイル・関数・定数が何を司っているか | ✅ ここ | — |
| どこを直すと何が変わるか | ✅ ここ | — |
| 機能の仕様(この画面は何を表示すべきか) | ❌ | docs/spec.md |
| なぜこの仕様なのか(決定の経緯) | ❌ | docs/decisions.md |
| 守るべきルール・禁止事項(これが「正」) | ❌ | CLAUDE.md |
| 依存の向き・触ってはいけないもの(これが「正」) | ❌ | docs/architecture.md |
| いま何が未実装か・進行中の作業 | ❌ | docs/handoff.md |
| 開発環境のセットアップ | ❌ | docs/getting-started.md |
この文書は「正」ではありません。 ルールの正は CLAUDE.md、構成と依存の向きの正は docs/architecture.md です。 この文書はその言い換えと索引であり、食い違ったら向こうが正しいと考えてください(そしてこの文書を直してください)。
夜のあいだに自動で走った清掃ロボットの結果を、翌朝 DFA の運用担当が確認して、問題のある店舗を見つける — これが D-Watch のすべてです。業務の流れは1本しかありません。
flowchart LR A["清掃ロボット<br/>(店舗に設置)"] --> B["Pudu Cloud<br/>(メーカーのクラウド)"] B --> C["メインDB<br/>(MariaDB)"] C --> D["D-Watch<br/>(このアプリ)"] D --> E["運用担当<br/>(翌朝ブラウザで確認)"] style C fill:#3d2a06,stroke:#f59e0b,color:#e2e8f0 style D fill:#1e3a5f,stroke:#3b82f6,color:#e2e8f0
このリポジトリが担当しているのは、青枠(D-Watch)だけです。 ロボットからデータを集めてデータベースに書き込むところまでは外部の会社(TeamWorks)の担当で、 D-Watch はできあがったデータベースを読んで画面に出すのが仕事です。
★サーバーの用意・構築・保守も TeamWorks の担当です★(D-143。2026-09-16 確認)。 DFA はサーバーの費用を払いますが、作るのは画面(このリポジトリ)だけです。 以前の文書に「DFA がアプリ用のサーバーを構築する」と書かれていた箇所がありますが、それは誤りです。
そのため D-Watch は原則としてデータベースに書き込みません。★例外は「対応状況を記録する」4項目だけ★です — アラート画面(/alerts)で事前チェック異常の行の対応状況(未解消 / 対応中 / 解消)と備考を変えると、 robot_error_reports の status / comment / responded_at / resolved_at の4列を更新します(指示書AT。2026-10-02)。 ★10/06 からはエラー一覧(/errors)からも、robot_error_reports の全行の同じ4列を書けます(D-160)★。 ★どちらの画面も「編集」で編集モードに入り、「保存(N件)」を押したときだけ送ります(D-165)★。 ★メインDBへ書き込むコードは lib/db/robot-error-write.ts の1ファイルだけ★で、4列以外は書けない作りです(章8・章9)。 以前は「本番DBへは一切書き込まない」(D-148)方針でしたが、接続先がステージングだと分かり(D-152)、 ステージングへの4列の書き込みはユーザーの指示(2026-10-01「書き込んでよい」)で解禁されました。
flowchart LR
U["利用者の<br/>ブラウザ"] -->|"① 画面を開く"| P["画面部品<br/>components/features/"]
P -->|"② データをください"| API["APIの窓口<br/>app/api/"]
API -->|"③ 取り出して"| DB["データ層<br/>lib/db/"]
DB -->|"④a 練習用"| F["サンプルデータ<br/>lib/fixtures/"]
DB -->|"④b 本番用"| M[("メインDB<br/>MariaDB")]
API -->|"⑤ 判定・計算"| J["判定ロジック<br/>lib/judge/"]
style J fill:#0a3d1f,stroke:#22c55e,color:#e2e8f0
style F fill:#3d2a06,stroke:#f59e0b,color:#e2e8f0
④a と ④b の切り替えは USE_FIXTURES という1つのスイッチだけで決まります(章8)。 画面の見た目はどちらでも1ピクセルも変わりません。何も指定しなければ ④a(練習用の作り物データ)です。
★清掃の実績は clean_task_reports というテーブル1つから読みます★(D-151。2026-09-30)。 以前は clean_status_histories から読んでいましたが、一本化されました。
① 接続先のDB(ステージング)には実データが入りましたが、まだ「ほぼ空」です
出どころは2通りあります。
| スイッチ | どこから読むか | 中身 |
|---|---|---|
USE_FIXTURES が false 以外(既定) |
lib/fixtures/ |
練習用の作り物データ |
USE_FIXTURES=false(現在の設定) |
★ステージングDB(TeamWorks のサーバー。★本番DBは別にある★。D-152)★ | ★2026-09-30 夜〜10-01 未明に実データが入った★ |
ステージングの中身は 9/30 の夜に入れ替わり、当方のダミーは消えました。いま入っているのは ★ドン・キホーテの実店舗10件+本社★です(CLAUDE.md §3。2026-10-01 実測・先方投入)。 ★データは今後も入れ替わることがあります★(第10回 N-6。時期未定)。
★ただし清掃の予定が0行なので、清掃詳細ログ・ダッシュボード・アラートは空です。★(2026-10-01 時点) ★2026-10-06 には予定が入り始め、ダッシュボードは直近モード(D-159)で11エリアを出しました★(先方投入。追記61。★予定と実績はまだつながっていない★。D-158 末尾) 清掃面積の画面だけは実績を直接数えるので、★面積の合計には clean_task_reports の2行ぶんが出ます★。 ただしその2行はどちらも clean_timetable_id が NULL(★予定外のタスク★)なので、 ★実施回数と完了率には入りません★(D-155 項3。実施回数0回・完了率は「-」の見込み。★画面での確認は未了★)。 画面どうしで数字が食い違って見えるのはこのためで、不具合ではありません。 清掃詳細ログを「予定」起点にするか「実績」起点にするかは、データが揃ってから検討することになっています(D-151 項7)。
★DBに書き込むのは、アラート画面・エラー一覧の対応状況・備考の4列だけです★(上記。D-152 / 指示書AT / D-160)。それ以外はすべて読むだけです。
サンプルデータにはわざと異常なケース(完了率0%、未実施、エラー多発)が仕込んであります。 不具合ではなく、判定が正しく働くか確かめるための仕込みです。
② 1つの画面は、役割の違う4つのファイルでできている
画面のファイルを開いたら4行しかなかった、というのは正常です。
| 役割 | 例 | 何をするか |
|---|---|---|
| 住所札 | app/(dashboard)/detail-log/page.tsx |
URL /detail-log を作るだけ。数行しかない |
| 画面の中身 | components/features/detail-log/DetailLogScreen.tsx |
あなたのブラウザの中で動く |
| データの窓口 | app/api/clean-logs/route.ts |
サーバーの中だけで動く |
| 取り出し方 | lib/clean-logs/query.ts |
絞り込み・並べ替えの規則 |
なぜ分けるのか。ブラウザの中で動くコードは利用者に中身が見えてしまうからです。 データベースの場所やパスワードをそこに書くわけにいかないので、画面はデータベースを直接読まず、 必ずサーバー側の窓口に「これをください」と頼みます。詳しくは章2。
③ ファイルの大半は、人が書いていない
lib/generated/prisma/ は自動生成物で、行数だけならアプリ全体の8割を占めます。 人が書いたコードは 27,977 行(TypeScript のみ)です。ファイル数に驚く必要はありません(章10)。
この章だけは飛ばさずに読んでください。D-Watch のソースコードは170を超えるファイルがありますが、どの画面も同じ型紙でできています。その型紙を1本だけ、ブラウザからデータベース(DB)の列まで通して見ます。例に使うのは「清掃詳細ログ」(画面ID F-02、URL は /detail-log)です。
この章で出てくる言葉
- API(エーピーアイ)… 画面がサーバーへ「データをください」と頼むための窓口。D-Watch では
app/api/の下のファイルがそれです- テーブル / 列 … DB の中の表と、その縦の項目。
clean_task_reports.clean_areaは「clean_task_reportsという表のclean_areaという列」- Prisma(プリズマ)… TypeScript から DB を読むための部品。SQL を手で書かずに済みます
- ステージング … TeamWorks が用意した検証用の DB(
133.125.238.144/clean_watch)。★本番DBは別にあり、まだ接続していません★(D-152)
ブラウザで /detail-log を開くと、下の5つが順番に動きます。6番目と7番目は5番目が中で呼ぶ部品です。
| # | ファイル | 行 | 何をする |
|---|---|---|---|
| 1 | app/(dashboard)/detail-log/page.tsx |
全5行 | URL /detail-log と中身を結びつけるだけの札 |
| 2 | components/features/detail-log/DetailLogScreen.tsx |
532行 | 画面そのもの。入力欄・表・ページ送り。表の部品は CleanLogTable.tsx(292行) |
| 3 | lib/api/client.ts の apiFetch() |
:102 |
画面からサーバーへの唯一の通り道 |
| 4 | app/api/clean-logs/route.ts の GET() |
:47 |
サーバー側の入口(112行) |
| 5 | lib/db/index.ts の db.cleanLogs() |
選ぶのは :2591、中身は :465(サンプル)か :754(実DB) |
データの取り出し口 |
| 6 | lib/db/clean-log-mapping.ts |
489行 | DB の行を画面の1行に組み立てる変換(出発したか・完了率・最新の1行の選び方) |
| 7 | lib/db/consecutive-source.ts の loadConsecutiveInputs() |
:343 |
連続日数を数えるための材料を読む(他の画面と共有) |
sequenceDiagram
participant B as ブラウザ
participant S as DetailLogScreen.tsx
participant C as apiFetch()<br/>lib/api/client.ts
participant R as GET()<br/>app/api/clean-logs/route.ts
participant D as db.cleanLogs()<br/>lib/db/index.ts
participant DB as ステージングDB<br/>(MariaDB 10.11)
B->>S: /detail-log を開く
S->>S: 入力値を URL の文字列にまとめる(:120 queryString)
S->>C: fetchLogs()(:164)が呼ぶ(:185)
C->>R: GET /api/clean-logs?date_from=…(:114 fetch)
R->>R: 1. パラメータ検査(:50)/2. 施設名→施設ID(:77-81)
R->>D: 3. 取得(:82)
D->>DB: ① clean_timetables + clean_timetable_histories(本清掃 precheck=0・期間・施設。同じ id は本表):765
D->>DB: ② その子の事前チェック行(parent_id):831
D->>DB: ③ clean_task_reports(clean_timetable_id = 本清掃の id):850
D->>DB: ④ robot_error_reports(clean_timetable_id = 事前チェック行の id):857
D->>DB: ⑤ 連続日数の材料(consecutive-source.ts:356 / :383)
D-->>R: CleanLogItem の配列(判定まで済み)
R->>R: 4. 絞り込み(:98)/5. ページ切り出し(:99-104)/6. 整形(:106-110)
R-->>C: { items, meta } の JSON
C-->>S: 型のついたオブジェクト
S-->>B: 表として描画(CleanLogTable.tsx)
右端の「ステージングDB」は TeamWorks の持ち物で、D-Watch は読むだけです(CLAUDE.md §2。例外はアラート画面の4列の更新だけ。章8)。
① app/(dashboard)/detail-log/page.tsx(5行)
import { DetailLogScreen } from "@/components/features/detail-log/DetailLogScreen";
export default function DetailLogPage() {
return <DetailLogScreen />;
}
本当にこれだけです。Next.js は「フォルダの形がそのまま URL になる」という決まりなので、detail-log/page.tsx が置いてあること自体が「/detail-log という URL を作る」という意味になります。
ここで丸括弧が出てきます。(dashboard) の括弧は URL に出ません。/dashboard/detail-log ではなく /detail-log です。括弧は「URL のためではなく、同じ見た目の枠(サイドバー)を共有する仲間をまとめるための印」です。共有部分の実体は app/(dashboard)/layout.tsx(9行)にあり、app/(dashboard)/ の下にある11画面すべてがこれを着ます。
角括弧も同じ仲間です。app/api/clean-logs/[id]/advice/route.ts の [id] は「ここには数字が入る」という穴です。/api/clean-logs/37/advice と呼ぶと 37 が id として渡ってきます。
変えると何が起きるか: フォルダ名 detail-log を変えると URL が変わり、画面内のリンク(モックから移植した遷移)が全部切れます。括弧を外して dashboard/ にすると、11画面すべての URL に /dashboard が割り込みます。
② components/features/detail-log/DetailLogScreen.tsx(532行)
1行目が "use client" です(:1)。これは「このファイルはブラウザの中で動く」という宣言です。入力欄に文字を打つ・ページ送りを押す・行を開く、といった人の操作に反応する画面にはこの宣言が要ります。宣言が無いファイルはサーバー側だけで動き、ボタンを押しても何も起きません。
この画面がやっていることは3つです。
:45-95 あたり)。★日付の初期値は空=期間を指定しない(全期間)です★(:74-75。D-159 追記。旧: 今日1日)。空の側は URL に付けません(lib/clean-logs/detail-log-date-range.ts):120 の queryString)apiFetch() を呼び、返ってきた配列を表にする(:164 の fetchLogs()、:185-189)文字入力(施設名・機種・シリアル)は1文字打つたびに通信すると重いので、0.3秒待ってからまとめて投げます(debounce。:98-118)。期間が MAX_DATE_RANGE_DAYS(731日)を超えると、通信する前に画面で止めます(:165-172)。
変えると何が起きるか: :120 の queryString の依存リストから page を落とすと、ページ送りボタンを押しても取り直しません(コメント :134-135 が警告しています)。例外もエラーも出ず、ただ「ボタンが効かない」だけになります。
③ lib/api/client.ts の apiFetch()(:102)
画面からサーバーを呼ぶときは、必ずこの関数を通します。fetch(通信命令)を画面に直接書いてはいけません。認証のやり方が変わったときに手を入れる場所を1箇所にしておくためです(ファイル冒頭 :1-16)。
この関数は3つのことを保証します。
:106-110)。D-Watch は自分のサーバーしか呼びません:125-142)status: 0 にする(:115-122)変えると何が起きるか: ここを通さずに画面に fetch を直書きすると、エラーのときに画面へ <!DOCTYPE html>... のような生の文字列が出ます(:22-27 がその事故を説明しています)。
④ app/api/clean-logs/route.ts の GET()(:47)
サーバー側の入口です。ファイル冒頭のコメント(:9-16)が、この中の処理を6段階と宣言しています。
{ items, meta } の形で返す実装でも :50(1)→ :77-81(2)→ :82-89(3)→ :98(4)→ :99-104(5)→ :106-110(6)と、宣言どおり並んでいます。
3でSQLに絞らせたのに4でもう一度絞っているのは意図的です(:18-22)。絞り込みの「正しい規則」を filterCleanLogs()(lib/clean-logs/query.ts:152)という1つの関数に置き、SQL 側はその写しにしておく設計です。ただしコメントは同時に警告しています — SQL が絞りすぎた場合、4では取り戻せません。
変えると何が起きるか: :77-81 の施設ID解決は、null(絞らない)と [](0件ヒット)を厳密に区別しています(:69-76)。?? で書くと、施設名と顧客を同時に指定したときに片方の条件が静かに消えます。例外もテスト失敗も出ません。
⑤ lib/db/index.ts の db.cleanLogs()
db という名前の実体は、ファイル最終行 :2591 の1行で決まります。
export const db: DataSource = USE_FIXTURES ? fixtureSource : prismaSource;
環境変数 USE_FIXTURES が "false" でなければサンプルデータ(:459 の fixtureSource)、"false" なら実DB(:706 の prismaSource)です。どちらを選んでも、④のルートから見える形はまったく同じです(章8)。
実DB側の cleanLogs()(:754)は、DB に4回問い合わせてから、メモリの上で行を組み立てます(次の 2.4 で列ごとに追います)。テーブル同士をつなぐ印(外部キー)が clean_timetable_id に張られていないため、Prisma の include(関連する表を一緒に読む機能)が使えず、別々に読んでから手で突き合わせています(:710-718。TeamWorks に追加を依頼中。D-094)。
変えると何が起きるか: :765 の問い合わせから precheck: CLEAN_TIMETABLE_PRECHECK.main(本清掃だけ)を外すと、事前チェックの行まで一覧に並びます。例外にはなりません(:912-913 のコメント)。
清掃詳細ログの1行は「清掃の予定1件」です。予定は clean_timetables(★と clean_timetable_histories。下の注★)、結果(実績)は clean_task_reports、事前チェックで見つかった異常は robot_error_reports にあり、それを clean_timetable_id という番号で突き合わせて1行にします。
| 段 | 読むテーブル | 条件 | 場所 | 根拠 |
|---|---|---|---|---|
| ① 骨格 | clean_timetables |
precheck = 0(本清掃)・start_at が期間内・施設ID |
lib/db/index.ts:765-807 |
1回の清掃は clean_timetables の3行(本清掃+60分前+30分前の事前チェック)で表される(D-092 項1 / D-112)。precheck は「何分前か」の数字そのもので、0 だけが本清掃(TeamWorks 第7回 A5) |
| ② 事前チェック行 | clean_timetables |
parent_id = ①の id |
:831-837 |
事前チェック行は parent_id に本清掃の id を持つ(D-092 項1) |
| ③ 結果 | clean_task_reports |
clean_timetable_id = ①の id |
:850-854 |
清掃実績の取得元は clean_task_reports に一本化(D-151。CLAUDE.md §4.7) |
| ④ 事前チェックの異常 | robot_error_reports |
clean_timetable_id = ②の id |
:857-861 |
事前チェック行に紐づくエラーが「1h前ST / 30分前ST」になる(D-065 / D-098) |
★注(2026-10-05 改訂。指示書AU 段階3b / D-158 項4)★: ①②の予定は ★clean_timetables と clean_timetable_histories(古い予定が移る履歴の表)の両方★から読みます。 読み口は ★lib/db/timetable-source.ts の findTimetables() / findTimetablesByChunks() / findTimetableById() の1か所だけ★で、 ★同じ where を2表に渡し、同じ id は clean_timetables 側を採ります★(履歴の id は本表の id のコピー。ユーザー回答 2026-10-05)。 同じ id で facility_id / section_id / start_at が2表で違えば ★warn ログ db.timetable_conflict★ を出します(採るのは本表のまま)。 ★③の実績は clean_task_reports だけのまま★です(D-151)。④の robot_error_reports は ★lib/db/robot-error-select.ts の select で読む列を絞り、robot_id / robot を読みません★(先方の DDL で robot_id が NULL 可になったため。第11回 P-8 b)。
③で1つの予定に結果の行が2行以上あった場合は、ended_at(終了時刻。空なら started_at)が最も新しい1行を採ります。この規則を知っているのは latestReportByTimetableId()(lib/db/clean-log-mapping.ts:241)だけで、アラート画面とも共有しています(D-107 / 指示書AQ §1-4)。
ロボット(機種・シリアル)は、③の clean_task_reports.robot_id からではなく、①の予定の清掃エリア → そのエリアのロボットで引いています(:793-799。section.robots[0])。P1 の前提が「1清掃エリア=1ロボット」だからです(CLAUDE.md §8)。ロボットが0台なら静かに空欄にせず例外を投げます(:923-928)。
画面の列(CleanLogTable.tsx) |
API の項目(CleanLogItem) |
どのテーブルのどの列か | どう決まるか | 根拠 |
|---|---|---|---|---|
| 施設名 | facility_name |
facilities.facility_name |
①の facility_id で facilities を一緒に読む(include: { facility: true }。:791-792) |
— |
| 機種 | model_name |
devices.maker_id + devices.model_name |
①の section_id → sections → robots(deleted_at が空のもの)の1台目 → devices。整形は formatModelName()(clean-log-mapping.ts:27)。メーカー番号→名前は MAKER_NAMES |
MAKER_NAMES の番号割り当ては DFA の暫定決定(D-085。makers テーブルは提供されない=D-057 A-1) |
| シリアル | serial_number |
robots.serial_number |
同上 | 1清掃エリア=1ロボット(CLAUDE.md §8) |
| 日付・予定 | scheduled_at |
clean_timetables.start_at |
jstIsoFromDb() で ISO 形式にする(★9時間足さない★) |
DB の時刻は列の型によらず JST の壁時計の値(CLAUDE.md §3 / D-155 項4) |
| 1h前ST / 30分前ST | status_1h / status_30m |
robot_error_reports.error_type |
★②の事前チェック行(precheck の生値)を分数の大きい順に並べ、1つ目が status_1h(1回目)・2つ目が status_30m(2回目)★(D-178。2026-10-16。分数は status_1h_minutes / status_30m_minutes で返す。1つだけなら1回目に入り2回目は null。3つ以上なら開始に近い2つを残し、捨てた分数は data.anomaly)。★旧: precheck = 60 が「1h前」・= 30 が「30分前」(PRECHECK_MINUTES_BEFORE と一致する行だけ)★。複数あれば errored_at が最新の1件。予定行があってエラーが無ければ Standby.。★予定行そのものが無ければ null(画面は「-」、CSV は空のセル)★(statusColumnsFromPrecheckRows() lib/db/precheck-mapping.ts:147。f2b91fc)。★D-171(2026-10-07)から★。旧: 予定行が無くても Standby. で、異常なしと見分けられなかった |
値は変換せずそのまま出す(D-077 / CLAUDE.md §4.8)。Standby. は TeamWorks の robots.status コード表の原文(D-098)。★robots.status を実際に読んでいるのではなく、エラーが無いことの読み替え★。★判定には使わない★(D-170) |
| 出発 | departed |
clean_task_reports に③の行があるか |
行があれば出発、無ければ「出発失敗」(notExecutedFromReport() clean-log-mapping.ts:165) |
★未実施 = 本清掃に対応する clean_task_reports の行が無いこと★。「出発失敗」と「未実施」は同じ扱い(CLAUDE.md §4.7。ユーザー確認 2026-09-30。D-107 設計3-C) |
| 清掃結果 | task_status |
clean_task_reports.status |
0 清掃開始前 / 1 清掃中 / 2 清掃中断 / 3 清掃キャンセル / 4 清掃正常終了 / 5 清掃異常終了 の表示名にする(formatCleanTaskStatus() lib/format/clean-result.ts:61。a961829)。表に無い値(6 など)は数字のまま。行が無ければ「出発失敗」。★結果待ち(result_pending)の行は「結果待ち」(D-173)★ |
TeamWorks 第10回 N-2 の回答(D-155 項2)。★判定には使わない★ |
| 完了率 | completion_rate |
clean_task_reports.clean_area と clean_task_reports.task_area |
★min(clean_area ÷ task_area × 100, 100) を小数第1位まで切り捨て★(runCompletionRate() lib/judge/kpi.ts:144)。★task_area = 0 は 0%(判定 ×。第11回 P-5=B。旧: 「-」)★。画面では小数第1位を四捨五入して整数で出す(displayRate() lib/judge/grade.ts:43) |
TeamWorks のサマリと同じ式に合わせた(第10回 N-3 / D-155 項1。★第11回 P-2=A で確定。D-158★)。★clean_task_reports.clean_ratio 列は使わない★(先方が「計算式が間違っている」と回答) |
| 判定 | grade |
(列ではない。上の完了率から計算) | judgeGrade()(lib/judge/grade.ts)。★丸める前の値で判定★ |
◎90%以上 / ○70%以上 / △10%以上 / ×10%未満または未実施(CLAUDE.md §4.1 / D-139) |
| (行を開いたときのアドバイス欄)連続日数・L1〜L3 | consecutive_problem_days / escalation_level |
clean_timetables(本清掃)+ clean_task_reports |
loadConsecutiveInputs()(lib/db/consecutive-source.ts:343)が表示期間より前まで遡って読み、computeConsecutiveProblemDays() が数える。表示上限(既定30日)は API の出口でかける(capCleanLogs()) |
連続は「スケジュール存在日」で数える(CLAUDE.md §4.5 / D-026)。4経路で同じ材料を使う(D-109) |
計算の式とその根拠の詳しい説明は章5-Bにあります。
| 出ないもの | 理由 | 根拠 |
|---|---|---|
★予定外のタスク★(clean_task_reports.clean_timetable_id が NULL の行) |
①の予定から引くので、予定を持たない清掃は③の in の条件に一致しません。欠損ではなく実在する清掃ですが、この画面は予定が起点です |
ユーザー回答 2026-10-01(D-152 3-3)。予定起点・実績起点・両方のどれにするかは、データが入ってから検討(D-151 項7) |
| まだ予定時刻が来ていない清掃 | isFutureSchedule()(clean-log-mapping.ts:308)で外します。外さないと、まだレポートが無い未来の清掃が全部「出発失敗」になります |
D-107 設計3-C(lib/db/index.ts:899-916 のコメント) |
★空です。★ 2026-10-01 の時点で clean_timetables(予定)が0行、clean_task_reports(実績)が2行で、その2行はどちらも clean_timetable_id が NULL(予定外)でした(2026-10-01 実測・先方投入。CLAUDE.md §3)。予定が0行なので①が何も返さず、画面は「該当なし」になります。★不具合ではありません★。 それ以降は再実測できていません(2026-10-02 は認証エラー、2026-10-05 時点もログインできない件を別途問い合わせ中。D-155 / D-157)。
変えると何が起きるか: 完了率を clean_task_reports.clean_ratio 列に戻すときは、runCompletionRate() の中身だけを直します。呼び出し側は行をそのまま渡しているので、1関数で全画面が切り替わります(指示書AS D6)。呼び出し側で割り算を書くと、画面ごとに式が分かれます。
「画面から直接データベースを読めば、③と④を省けるのでは」と思えます。省けません。理由は4つです。
lib/judge/ の関数が決めます(CLAUDE.md §9.4)。画面がDBを直接読むと、画面ごとに計算が生まれ、同じ施設の同じ日なのに画面によって判定が違うという事故が起きます。これは実際に起きています(D-109 / D-103)USE_FIXTURES の分岐が成立するのは、データの取り出し口が lib/db/ の1箇所だからですD-Watch の画面はすべてこの形です。新しい画面を読むときも、この4枚を探せば足ります。
| 役割 | 置き場所 | 例(清掃詳細ログ) | 書いてよいこと | 書いてはいけないこと |
|---|---|---|---|---|
| 札(URL を作る) | app/(dashboard)/<画面>/page.tsx |
detail-log/page.tsx |
画面部品を1つ呼ぶだけ | ロジック・通信 |
| 画面 | components/features/<画面>/ |
DetailLogScreen.tsx |
入力の保持、表示の整形、apiFetch() 呼び出し |
判定の再計算(◎○△×、連続日数、並び順はAPIから受け取る) |
| 入口 | app/api/<資源>/route.ts |
clean-logs/route.ts |
検査・絞り込み・ページング・エラー整形 | SQL、Prisma の直接呼び出し |
| 取り出し口 | lib/db/index.ts + lib/<資源>/query.ts |
db.cleanLogs() / lib/clean-logs/query.ts |
DBの読み取り、絞り込み規則の関数 | 画面の都合(表示上限など)の混入 |
変えると何が起きるか: 画面(2段目)で ◎○△× を計算し直すと、API が返した判定と画面に出る判定が食い違います。CLAUDE.md §9.6 が明示的に禁止している項目です。逆に表示上限(連続日数の頭打ち)を lib/db/ の中に入れると、USE_FIXTURES の true と false で画面の見え方が変わります(lib/clean-logs/query.ts:203-204 のコメント)。
D-Watch には13画面あります。app/(dashboard)/ の下に11、それに /login と /(入口)を足した数です。 ★改訂3(2026-10-06)でエラー一覧(/errors。3.5b)が増え、14画面((dashboard) の下に12)になりました★。
ここでは「どのファイルを開けばその画面に届くか」と「その画面の数字がどのテーブルから来るか」を書きます。各画面が何を表示するかの機能仕様は docs/spec.md が正です。 数字の式と根拠の詳細は章5-Bにあります。
各画面は同じ6項目で書いています — 1 業務の役割 / 2 URL と札のファイル / 3 画面のファイル / 4 呼ぶAPI / 5 この画面の数字はどこから / 6 固有の注意点。
app/(dashboard)/layout.tsx11画面に共通のサイドバー・ロゴ・テーマ切替・JP/EN切替を着せているのが、この9行のファイルです。
import { AppShell } from "@/components/layout/AppShell";
export default function DashboardGroupLayout({ children }: { children: React.ReactNode }) {
return <AppShell>{children}</AppShell>;
}
(app/(dashboard)/layout.tsx:1-9)
やっていることは1つだけ — 各画面の中身を AppShell で包むことです。AppShell(components/layout/AppShell.tsx、165行、本体は :55)が実際のサイドバーを描きます。
サイドバーのメニューは3つの定数に直接書かれています。
| 定数 | 行 | 中身 |
|---|---|---|
MENU_MONITORING |
:8-26(f02eb1d) |
清掃詳細ログ / アラート / ★エラー一覧(10/06)★ / レポート / 清掃面積(★点検記録は :25 でコメントアウト★) |
MENU_DS |
:25-28 |
D-Secログ / D-Secアラート |
MENU_SETTINGS |
:30-34 |
施設ロボットマスタ / スケジュール / システム管理 |
ダッシュボードだけは区分に属さず、単独で先頭に置かれています(:127-131)。
★契約フラグ(facilities.contract)によるメニューの出し分けは、現在も未実装です★(2026-10-05 に AppShell.tsx を確認)。CLAUDE.md §4.6 は「契約のないセクションはメニューごと非表示」と定めていますが、メニューは as const の固定配列で、AppShell.tsx は contract も monitoring_enabled も d_security_enabled も参照していません。契約フラグを使っているのは施設ロボットマスタ画面の表示列だけです(FacilitiesScreen.tsx:168 :175)。つまり現在は D-Security 未契約の施設しか無い顧客でも D-Sec メニューが見えます。 ★P2(顧客に使わせる前)で作ると決めました★(ユーザー判断 2026-10-06。D-160 追記4)。★エラー一覧のメニューも常に出しています★(指示書AW D15)。 なお P1 の利用者は DFA 社内だけで(CLAUDE.md §1)、ログインは顧客ごとに分かれていません。誰の契約でメニューを出し分けるのかも未決です(未確認)。
変えると何が起きるか: (dashboard) の括弧を外すと11画面すべての URL が変わります。layout.tsx を消すとサイドバーが全画面から消えます。契約フラグの分岐を足すときは、桁位置のハードコードを避け lib/format/contract.ts(桁位置は CONTRACT_DIGITS)を通してください(CLAUDE.md §4.6 / D-080)。★contract を数値として比較すると、例外もテスト失敗も出ないまま全施設が「契約なし」になりメニューが全部消えます★。
/)/ / app/page.tsx(全5行)redirect("/dashboard") を呼ぶだけ。:4)変えると何が起きるか: 行き先を変えると、ブックマークやメール中のリンクの着地点が変わります。
/login)/login / app/login/page.tsx(142行)LoginForm(:19)。★AppShell は使いません★ — 未認証の画面にメニューを出さないためです(:11)POST /api/auth/login(:31)。★この画面だけ apiFetch() ではなく素の fetch を使っています★:4-5)。許可されないIPからは 403 になります(ただし許可リストは空のままで、空なら全部通す設定です。リリース直前の対象として CLAUDE.md §3 に列挙済み。D-154):7。console.log も no-console で lint が落ちます):44)。★それ以上の情報を出しません★:38)safeNextPath() を通します(:39)。★絶対URLを自前で組まない★(:8-9。LB が HTTPS を終端するので http:// になります)変えると何が起きるか: エラー文言を「トークンが短すぎます」のように具体化すると、総当たりの手がかりを与えます。
/dashboard)/dashboard / app/(dashboard)/dashboard/page.tsxcomponents/features/dashboard/DashboardScreen.tsx(★370行。a961829★)。子部品3つ: RescueRequiredCard.tsx(165行)/ ScheduleProgressCard.tsx(32行)/ SecurityBanner.tsx(27行)。★清掃ログの表とエリアモーダルは清掃詳細ログから借りています★(AreaModal / CleanLogTable を import。:6-7)GET /api/dashboard(:85)の1本で数値カードとコンパクト版の表の両方★、行を開いたとき GET /api/clean-logs/{id}/advice(:120)、GET /api/clean-logs/{id}/areas(:175)。★初期表示で GET /api/clean-logs は呼びません★(D-159 / 指示書AV D7。旧: 表のために別に呼んでいた)pickLatestRunPerSection() lib/dashboard/latest-run.ts:67。a961829)、判定で数えたもの(computeSummary() lib/dashboard/aggregate.ts:41)。★出発失敗も選ぶ(未実施に数える)★。★結果待ち(result_pending)も選び、「結果待ち」カードに数える★(D-173)。?date= を指定したときは従来どおりその日の清掃(D-159)
DashboardScreen.tsx:265-273。色は中立の tone="mt"(StatCard に D-173 で足した。新しい色は足さない)。副題「レポートの到着待ち」。★モックに無いカード★)。★成功率の分母に入れません★(computeSummary() の pending)。★「成功 / N件」の N には入れる★(カード5枚の合計が N。:196-209)ので、成功 ÷ N と成功率は一致しないことがありますDASHBOARD_LATEST_RUN.settleMinutes)≤ 現在」の予定だけを候補にしていた★。D-173 で settleMinutes を削除し、終わってからレポートが届くまでは行の旗 result_pending で「結果待ち」と出すようにしました(5.9b)problem_logs)の★先頭5件★(COMPACT_LOG_ROWS DashboardScreen.tsx:50)。★API は件数で切りません★。★結果待ちの行は出しません★(grade は × のままなので旗で外す。app/api/dashboard/route.ts:203。D-173)clean_schedules.weeks(7桁)のうち今日の曜日が 1 で、inactive = 0 の件数。実施 … 当日の清掃のうち clean_task_reports の行があるもの(computeScheduleProgress() :101。★結果待ちは「実施」に数えない(出発していない)★)。★予定と実施が別のテーブルから来る★ので、件数が合わないときはログに data.anomaly を出します(app/api/dashboard/route.ts:134-146。D-049)clean_timetables + clean_task_reports を遡って読んだもの(loadConsecutiveInputs())と施設名(facilities)control_error_histories。★ただし実DB版は種別をいつも「通信断(disconnected)」として返し、バナーは「セキュリティ解除のまま(security_off)」だけを拾うため、実DBではバナーは必ず空です★(lib/db/index.ts:1614-1625 のコメント / lib/dashboard/aggregate.ts:130-140。解除状態を表す列が security_statuses に無い。D-090 項3-9)SUCCESS_RATE_TARGET(lib/config/index.ts:575。表示だけ)RESCUE_INITIAL_VISIBLE)。「もっと表示」で全件開きます。★API は件数で切りません★(D-137)logId は「エリアを特定するキー」ではなく、施設と日付を特定するための入口★です(:31-33)clean_timetables / clean_schedules)が0行なので、ほぼ全部が0か空です★(2026-10-01 実測・先方投入。CLAUDE.md §3)。 ★2026-10-06 15:0x JST は直近モードで11エリア・成功9・失敗1・未実施1(先方投入。追記61)★。?date=当日 は0件(実データの清掃は前日の日付のため)departed と grade だけです(5.9)CleanLogTable の compact)は施設名 240px を持ち、余りの幅を5列に幅の比で配ります★(最小幅 1000px。D-167 追記。旧: 施設名だけが余りを受け持ち 1920px で ≒833px)。3.15変えると何が起きるか: CleanLogTable.tsx を直すと、清掃詳細ログとダッシュボードの両方の表が変わります。★列幅は CleanLogTable.module.css の .table(通常)と .compact(縮小)で別々に持っています★。
/detail-log)/detail-log / app/(dashboard)/detail-log/page.tsxcomponents/features/detail-log/DetailLogScreen.tsx(★568行。f2b91fc★)。子部品: CleanLogTable.tsx(★338行。a961829★。表)/ AreaModal.tsx(165行。エリア別完了率のモーダル)GET /api/clean-logs(:193)、GET /api/clean-logs/export(CSV。:499 のリンク)、GET /api/clean-logs/{id}/advice(:231)、GET /api/clean-logs/{id}/areas(:286)(★行番号は f2b91fc★)clean_timetables(★と clean_timetable_histories。2026-10-05 から★)の本清掃1件。結果は clean_task_reports(clean_timetable_id で突き合わせ)、事前チェックの状態は robot_error_reports.error_type。★完了率は clean_task_reports.clean_area ÷ task_area(上限100・小数第1位切り捨て)★で、clean_ratio 列は使いません(D-155。★第11回 P-2=A で確定。D-158★)。列ごとの対応表は章2の 2.4。エリア別完了率のモーダルも同じ clean_task_reports の面積と sections.section_name から作ります(lib/db/index.ts:1095)MAX_DATE_RANGE_DAYS)。超えると画面側で止めます(dateRangeError() :163。★両方入っているときだけ★)。★初期表示は期間を指定しない(全期間)★(D-159 追記。旧: 今日1日)。★片方だけ入れたら入っている側だけを送ります★(5b 以前と同じ挙動。lib/clean-logs/detail-log-date-range.ts)。★期間なしの取得は Prisma 版で保持期間の予定を全部読むので、リリース前に見直す★MAX_CSV_ROWS)。判定は一覧で取った meta.total を流用し、CSV のために問い合わせを増やしません(:165-179)。★画面の判定とサーバーの判定は同じ向き(> であって >= ではない)に揃えること★ — 片方だけ >= にすると、押せるのに 422 が返ります<a href>)で、apiFetch() を通しません(:499)。ファイルのダウンロードだからですcsvRate() lib/csv.ts:69。D-164。旧: - を出し、数式対策で Excel に '- と見えた)。★画面の表示は「-」のまま★precheckStatusCell()(lib/format/precheck-status.ts:22)の1か所で、画面は PrecheckStatus(CleanLogTable.tsx:72)がそれを使います。★statusColor() に null を渡さないため★。予定があって異常なしは今までどおり Standby. の Pill。★表示だけで、判定には使いません★(D-170):55-60)。文字入力だけ 0.3 秒待ってからまとめて投げますopenDatePicker()。D-162)。★ホールディングス・顧客のプルダウンは 180px 固定★(.masterSelect。D-162)。★表は全列に px の幅を付け、余りを全列に幅の比で配ります★(施設名 126px・最小幅 1150px。D-167)。3つとも 3.15clean_task_reports.status の表示名です★(D-155 項2)。0 清掃開始前 / 1 清掃中 / 2 清掃中断 / 3 清掃キャンセル / 4 清掃正常終了 / 5 清掃異常終了。★表に無い値(6 など)は数字のまま★、レポート行が無ければ「出発失敗」(★ただし結果待ちの行は「結果待ち」。D-173★)。組み立ては formatCleanTaskStatus()(lib/format/clean-result.ts:61)を cleanLogRowView()(:98)経由で使い、画面では t() に通して英語にも切り替わります(CleanLogTable.tsx:267。a961829)。★判定には使いません★(中断・異常終了でも完了率で ◎○△× を出す)RESULT_WAIT_MINUTES(120分)以内のもの★(清掃中も含む)は、出発「…」・清掃結果「結果待ち」・完了率「-」・判定なしで出し、★行を開けません★(result_pending。見え方は cleanLogRowView() lib/format/clean-result.ts:98 の1か所。CleanLogTable.tsx:209-273)。★「問題あり(△×)」「×のみ」の絞り込みからは外れ、「全件」にだけ出ます★(lib/clean-logs/query.ts:182)。CSV も出発・清掃結果が「結果待ち」、完了率・判定は空のセルです。120分を過ぎたら今までどおり「出発失敗」。詳しくは 5.9btask_area が 0 の清掃は完了率 0%(判定 ×)です★(runCompletionRate()。第11回 P-5=B。D-158)。旧(10/02〜10/05): 出発していても「-」clean_timetable_id が NULL)はこの画面に出ません★(予定が起点のため。D-151 項7 で起点を検討中)/alerts)/alerts / app/(dashboard)/alerts/page.tsxcomponents/features/alerts/AlertsScreen.tsx(★684行。f2b91fc★)。共通部品 FilterButton / Pill / StatCard / Pagination。★下書きの扱いは lib/prechecks/edit-drafts.ts(エラー一覧と共有。D-165)★GET /api/prechecks(:225)、★PATCH /api/prechecks/{id}/status★(sendPrecheckPatch() :132。2026-10-02 指示書AT 段階6。★10/06 からは「保存」を押したときだけ、変えた行の数だけ呼ぶ★)robot_error_reports のうち、clean_timetable_id が事前チェック行(clean_timetables.precheck が 0 以外)を指すもの。種別は error_type をそのまま(D-077)。対応状況は robot_error_reports.status(0 未解消 / 1 対応中 / 2 解消。lib/db/precheck-mapping.ts:41。f2b91fc)、備考は comment、日時は responded_at / resolved_at、検知日時は errored_at(空なら created_at。D-105)clean_timetables の本清掃のうち、clean_task_reports に対応する行が無いもの(lib/db/index.ts:1564-1643 あたり。a961829。★予定の終了から120分以内でレポートが無い清掃は結果待ちとして出さない(:1588。D-173)★。清掃詳細ログの「出発失敗」と同じ判定 notExecutedFromReport())battery_level: null。robots.battery は読んでいない)clean_task_reports の行があり、完了率の判定が ◎○ なら main_run_succeeded: true(mainRunSucceededFromReport() lib/db/precheck-mapping.ts:283。成功の等級は SUCCESS_GRADES lib/dashboard/aggregate.ts:26 をダッシュボードと共有)。出発失敗の行は常に falselaunch_failed(出発失敗)だけ赤、事前チェック異常は警告色★(:43-49)main_run_succeeded が true のカードは控えめに出します★(バッジ・施設名・予定・検知・状態の表示を不透明度 0.6 にし、「本清掃は成功」の Pill を足す。itemCardClass() :109-121 / :570、CSS は .itemCardSubdued)。★対応状況(未解消など)も上の件数も変えません★(本清掃が成功しても未解消は未解消。D-171)。★編集モードのプルダウン・備考は薄くしません★(カード全体に opacity をかけない)clean_task_reports に該当行が無いことが根拠で、区別する列がありません(CLAUDE.md §4.7)robot_error_reports の4列。章8)。モックにこの操作は無いので、D-Secログの備考・モックの D-Sec アラートの作法を借りています(D-156 項4)
isEditable() :123-126)。出発失敗の行はバッジのままで変えられません(記録先が無い。D-105)。サーバー側も出発失敗の id には 404 を返します:191-203 のコメントが正です
:485 以降。編集できる行が0件なら押せない)で入ると、対応状況が ★プルダウン(未解消 / 対応中 / 解消。D-161)★、備考が入力欄(上限 PRECHECK_NOTE_MAX_LENGTH = 400字。:587)になりますsetDraftStatus() / setDraftNote())。変えた行は背景の色(.itemCardChanged)で分かりますsaveEdits() :299 → saveDrafts()。並列にしない)。送る本文は ★対応状況は必ず・備考は変えたときだけ★(patchRequestFromDraft())applyResolutionChange() で差分だけ直します★(一覧を取り直さない)。★「未解消のみ」で絞っていても、次に取り直すまで行は消えません★(押し間違いをその場で戻せるように)<fieldset disabled> :375 / :674。取り直すと下書きの行が一覧から消えるため)。★下書きがあるあいだはページを離れる前にブラウザが確認します★(beforeunload :257-267)。「キャンセル」は下書きがあれば確認してから捨てます(cancelEditMode() :284)saving。重ねて送らないため)lib/prechecks/status-cycle.ts(巡回順)は D-161 で削除済み)title)★(.noteText :601。D-162)。★枠は文字の長さによらず行の残りの幅いっぱいの固定の大きさ★(D-164)、編集欄も resize: none(D-162).statusTime。旧 11px。D-162)lib/prechecks/status-update.ts:50)/errors)— ★改訂3で追加★robot_error_reports の全行★(ロボット・制御盤・セキュリティ・清掃開始失敗。事前チェックかどうかを問わない)のうち、★未解消・対応中★のものを一覧し、★その場で対応状況と備考を記録する★画面です(D-160)。「解消済みも表示」で解消も出ます。★アラート画面(3.5)は事前チェック+出発失敗だけのまま★です/errors / app/(dashboard)/errors/page.tsx(★モックに無い画面★。メニューは「🔔 アラート」の直後の「🚨 エラー一覧」。AppShell.tsx:13)components/features/errors/ErrorsScreen.tsx(★670行。f2b91fc★)+ ErrorsScreen.module.css。共通部品 Pagination。★下書きの扱いは lib/prechecks/edit-drafts.ts(アラートと共有。D-165)★GET /api/errors(:238)、★PATCH /api/errors/{error_id}/status★(sendErrorUpdate() :105。★「保存」を押したときだけ、変えた行の数だけ呼ぶ★)robot_error_reports の1行。★種類(error_type)・レベル(error_level)・シリアル(serial_number)は生値のまま★(t() にも通さない。D-077)。シリアル「0」もそのままerrored_at、空なら created_at(/alerts と同じ detectedAtFromError())clean_timetable_id を2表(本表・履歴)から引いて ★本清掃 / 事前チェック(N分前)/ 予定なし / 予定不明★(errorScheduleLink() lib/db/error-report-mapping.ts:107、文言は errorScheduleLabel() lib/error-reports/label.ts:27)meta.total)error_id の昇順(文字列の辞書順)★errored_at ?? created_at の順を Prisma のクエリビルダで書けないため★(nulls last で近似すると errored_at が NULL の行が末尾に回る。D-160 追記2)。★「解消済みも表示」は保持期間ぶんのキーを読むので、リリース前に性能を見直す★/alerts(3.5)と同じ★)。通常は表示だけ → ツールバーの「編集」(読み込み中・0件・エラー表示中は押せない。canStartEdit :285)で入る → 対応状況はプルダウン(D-161。解消のときは下に解消日時)、備考は入力欄 → ★「保存(N件)」で変えた行だけを1行ずつ順に PATCH★(saveEdit() :311 → saveDrafts())→ ★全部成功で通常モードに戻る・失敗した行は下書きを残して行の下に理由★。★編集中は絞り込みとページ送りを使えません★(filtersDisabled :288)。★下書きがあればページを離れる前に確認★(beforeunload :176-183)、「キャンセル」も確認してから捨てます(cancelEdit() :296)。★日時の規則・書ける4列は /alerts と同じ★(章8 8.12)。★保存に成功した行は返ってきた1行で差し替え、解消にした行も取り直すまで消えません★title(マウスを乗せる)★(:634。D-162)。★枠は列幅いっぱいの固定の大きさ★(D-164。旧: 入力した文字の長さの幅しかなかった)。編集欄も resize: none。★解消日時は 12px★(.statusTime。D-162)robot_error_reports に行があるものすべて★(事前チェック以外・robot_id が NULL の行も)。サーバー側は行が無い id・大小文字だけ違う id に404table-layout: fixed+<colgroup>(:544-551)で JP/EN の切り替えで列幅が動きません★。★列幅は D-166★: 検知時刻 120 / シリアル 160 / レベル 90 / 紐づく予定 150 / 対応状況 150 / 備考 160px を固定し、★施設名と種類の2列だけ幅を付けず、表の残りの幅を2列で等分★(どちらも最小 150px)。★表の最小幅 1130px★(旧 1156px。狭い画面では表だけ横スクロール)。値は ErrorsScreen.module.css の .table(:246-261)。3.15。ただし★ツールバーは JP で「解消済みも表示」が2行目に回ります★(EN は1行)f2b91fc 時点でもそのまま★)/reports)/reports / app/(dashboard)/reports/page.tsxcomponents/features/reports/ReportsScreen.tsx(804行。全画面で最大。f2b91fc)。子部品: KpiCard.tsx(100行)/ FacilityPerformanceTable.tsx(★261行★。「変化」とステータスの表示名は lib/reports/facility-performance-labels.ts へ移した。D-163)。共通部品 ProgressBarGET /api/reports/kpi、/facility-performance、/continuous-problems、/error-breakdown の4本(:214-230)。★右上の「CSVエクスポート」は GET /api/reports/facility-performance/export(:354-360 のリンク。D-163)★customer_summaries、施設別は facility_summaries の★率4列(operation_ratio / launch_ratio / clean_ratio / error_ratio)をそのまま★、エラー内訳は facility_error_summaries.error_type ごとの error_count の合計(CLAUDE.md §4.7。指示書AQ)clean_timetables(本清掃)+ clean_task_reports + robot_error_reports からその場で集計します(CLAUDE.md §4.7)。完了率は1回ごとの完了率の単純平均(D-155)clean_timetables + clean_task_reports(loadConsecutiveInputs())limit を 10(DEFAULT_LIMIT :46)から 200(SHOW_ALL_LIMIT :55)に広げますlimit を付けません★ — 表示中の10件ではなく、★絞り込み後の全施設★を出します(画面の「全施設表示」の200件上限にも縛られない)。並びも表と同じ稼働率ワースト順facilityFilterQuery → 表は limit を足す、CSV の exportQuery はそのまま。:176-198)。★サーバー側も表と CSV で同じ1本 loadFacilityPerformance() を呼びます★(章4 の 26)<a download>)で、apiFetch() を通しません。★0件でも押せます★(見出し行だけの CSV)。列と形式は章13 13.2Promise.all ではなく Promise.allSettled で呼びます★(:208-230)。1本が失敗しても残り3本は表示するためです。★特に KPI は「対象顧客が1社に定まらない」と 422 になります★(サンプルデータは顧客が4件あるため、開発中は毎回これが起きます)/error-breakdown だけ meta を返しません★(集計結果で全件返すため)confirmedClassification(:121)という別の状態で保持し、選んだ瞬間ではなく確定時に反映されます。清掃詳細ログ(1操作=1確定)とは違いますweekOfMonth()(lib/format/period.ts)だけが知っています★。月をまたぐ週は月境界で分割され、週の長さは1〜7日で変わります(CLAUDE.md §4.7 / D-071)/clean-area)2026-09-16 のユーザー指示(指示書AN。D-141)で新設された画面です。
/clean-area / app/(dashboard)/clean-area/page.tsxcomponents/features/clean-area/CleanAreaScreen.tsx(★471行。f2b91fc★)。使う関数は lib/clean-area/ の4本 — aggregate.ts(集計)/ query.ts(パラメータ検証)/ visibility.ts(何件見せるか)/ anomaly.ts(異常値の見張り)GET /api/clean-area(:129)clean_task_reports.clean_area と task_area の合計(groupBy。lib/db/clean-area-source.ts:368-372)。★予定外のタスク(clean_timetable_id が NULL)も含めます★ — 実際に清掃した広さだからです(ユーザー判断 2026-10-02)clean_task_reports のうち clean_timetable_id が空でなく、その予定が本清掃(clean_timetables.precheck = 0)の行だけ(:374-406)。完了率は1回ごとの完了率(runCompletionRate())の単純平均。TeamWorks のサマリと同じ母集団です(第10回 N-3)clean_task_reports.started_at(lib/db/index.ts:1912)。★予定のある清掃では、この列に予定の clean_timetables.start_at と同じ値が入る★(ユーザー回答 2026-10-05「A」)ので、★清掃詳細ログ・レポートと同じ日に数えられます★。違うのは予定外のタスクだけで、その場合は実際に清掃を始めた時刻ですfacilities + customers + groups(deleted_at で絞らない。clean-area-source.ts:470)*_summaries.task_area / clean_area)は読みません★ — 実データで task_area が全行 1000 固定で、期間内の合計になっていないため(clean-area-source.ts:35-38。第9回 Q2-1 で照会中)AREA_UNIT。★DFA の判断であって TeamWorks の確認ではありません★(第9回5章で照会中):25-40)。CLAUDE.md §5 は「モックが画面レイアウト・文言・遷移の正」と定めていますが、★本画面はその例外★です。★この注記を消さないこと★ — 次にモックと突き合わせた人が「モックに無い=余計なもの」と判断して消してしまいますlib/clean-area/query.ts:14-18)。清掃詳細ログの 731日上限は持ち込みません。面積の合計は1本の集計で、返る行数は粒度ごとの件数(最大でも施設数)だからです
clean_task_reports の行(予定に紐づくもの)を取り寄せています★(1回ごとの完了率は集計クエリで書けず、生SQLも使わないため。指示書AS 段階4)。★期間を指定しないと全期間を運びます★。重さを見るため、取り寄せた行数と所要時間を judge.clean_area ログに出します(app/api/clean-area/route.ts:100-108。D-156 項2)CleanAreaScreen.tsx:356-361)date_from / date_to とも省略可(null = 上限・下限なし)。?date_from= のような空文字も null に正規化します(query.ts:43-47)AREA_UNIT から出し、"㎡" を直書きしません★(CleanAreaScreen.tsx:47 :318-319)。API は数値だけを返しますCLEAN_AREA_INITIAL_VISIBLE)。「もっと表示」で全件。★ページ送りはありません★facility)★(query.ts:39).table .thNum / .table .thName CleanAreaScreen.module.css:256 / :260。D-168。旧: .thNum が .table th の text-align: left に負け、見出しだけ左に寄っていた)AppShell.tsx:12-15)。期間で絞れる画面なので、名前に「総」「累計」を入れると定義を固定してしまいます変えると何が起きるか: AREA_UNIT を使わず "㎡" を直書きすると、単位を変えたいときに全画面を探すことになります。query.ts に日付上限を足すと、「2年ぶんの累計面積」という用途が使えなくなります。面積の groupBy に clean_timetable_id: { not: null } を足すと、予定外のタスクの面積が黙って消えます。
/inspections)— 出すと決まったが、画面はまだ「準備中」/inspections / app/(dashboard)/inspections/page.tsx(全5行)PlaceholderScreen(components/layout/PlaceholderScreen.tsx、全15行)★。見出しと「準備中」の2行を出すだけです。components/features/inspections/ は存在しませんapp/api/inspections/ のルートもありません(d-watch-api.yaml:1610 :1699(f2b91fc)には GET / POST / GET {id} / DELETE {id} が定義済み)| 項目 | 状態 |
|---|---|
| サービスとして出すか | ★確定★(D-150、2026-09-17) |
| サイドバーのメニュー | ★コメントアウトされたまま★(AppShell.tsx:22)。理由は :17-21(写真ストアが未着手で画面が成立しない。D-079 項3 / D-072 項7) |
| 画面 | PlaceholderScreen(準備中) |
| データ層 | ★Prisma版は未実装★(lib/db/index.ts:1800 :1803 が例外を投げる)。サンプル版は実装済み(:566 :569) |
| 型・サンプルデータ | ★作成済み★(lib/types/index.ts / lib/fixtures/inspections.ts 154行) |
| API契約 | ★d-watch-api.yaml にかなり書き込み済み★(写真は Before/After × 7項目 = 最大14枚、1枚10MB、jpeg・png、署名付き一時URLで配信) |
★着手をブロックしている未決が4件あります★(D-150 項2)。オブジェクトストレージの実体(B2)、誰が用意し費用を払い保守するか(B3)、★点検記録に「点検結果」を持たせるか(B4)★、誰がどこから入力するか(B5)。B4 が設計上いちばん重い — 「点検記録」という名前なのに点検の合否がどこにも残らない構造で、後から足すと既存レコードが全部「結果なし」になります。
変えると何が起きるか: AppShell.tsx:22 のコメントを外すと、メニューは出るが中身は「準備中」のままになります。
/ds-logs)/ds-logs / app/(dashboard)/ds-logs/page.tsxcomponents/features/ds-logs/DsLogScreen.tsx(★495行。f2b91fc★)GET /api/ds-devices(:217)、PATCH /api/ds-devices/{serial_number}/note(:271)controls.status(0 正常 / 1 異常。lib/db/ds-status.ts。D-065)control_reports.reported_at の制御盤ごとの最大値(groupBy。lib/db/index.ts:1535-1539)。★control_reports は制御盤1台につき1行(最新の報告だけ)★と TeamWorks が回答しています(第10回 Q8-3。D-155 項5)data/settings/ds-device-notes.json(章8 8.9):261-266。「クリックして外しただけ」で保存失敗の赤字が出るのを避けるため):280-284)。サーバー側が前後の空白を落とし「空文字 → 削除」の変換を行うからです:286-288):30-34。D-065。「通信断」は異常そのものであり3つ目ではありません)DEVICE_NOTE_MAX_LENGTH(200字。:107).th DsLogScreen.module.css:134)は text-align: left★で中身と揃えます(:145。D-168。旧: 指定が無く th の既定の中央寄せだった)。★備考の文字の頭も見出しと揃えています★(表示・編集欄とも左右に 8px はみ出させる。:206-210 / :232-234。D-171 であわせて直した)。★EN では備考の見出しが "NOTE"★(D-160 の副作用。3.5b)control_reports.reported_at は以前 UTC で書かれていました(9時間早く見えた)。TeamWorks は 2026-10-02 に DB の既定タイムゾーンを JST にしてデータを再登録したと回答しています(第10回 N-14)。★当方の再実測は未了★(D-155 項4)/ds-alerts)/ds-alerts / app/(dashboard)/ds-alerts/page.tsxcomponents/features/ds-alerts/DsAlertsScreen.tsx(★326行。f2b91fc★)GET /api/ds-alerts(:103)control_error_histories(発生日時 occurred_at / 対応状況 status 0 未対応・1 対応中・2 対応済 / 備考 comment / responded_at / resolved_at)+ 制御盤のシリアル(controls.serial_number)・施設名・清掃エリア名(lib/db/index.ts:1627-1656)。★実DB版は種別をいつも「通信断」として返します★(「セキュリティ解除のまま」を表す列が無い。D-090 項3-9)control_error_histories の4カラムの UPDATE も許していますが、★この画面ではまだ作っていません★(d-watch-api.yaml:1478(f2b91fc) に PATCH /ds-alerts/{id}/status の契約だけがあります)robot_error_reports に集約する話(第10回 N-5)は、TeamWorks の DDL 変更待ちです(第11回 P-8〜P-10。D-157).masterSelect DsAlertsScreen.module.css:47。D-162)。★この画面の表はカードの一覧で、D-167 の列幅の手直しの対象外★/facilities)/facilities / app/(dashboard)/facilities/page.tsxcomponents/features/facilities/FacilitiesScreen.tsx(221行)GET /api/facilities(:78)facilities(facility_name / prefecture_name / addr1・addr2 / phone / manager_name / charger_name / email / contract)+ robots(serial_number)+ devices(maker_id / model_name)+ customers / groups(所属)。★論理削除(deleted_at)された施設・ロボットは出しません★(lib/db/index.ts:1712-1724)。★連絡先は facility_contacts ではなく facilities の列から読んでいます★:16-21)。登録・更新は D-Security 側の画面で行います(D-053)。★グレーアウトして置くのも同じ理由で不可★:169 :176 の monitoring_enabled / d_security_enabled。facilities.contract の1桁目=D-Security、2桁目=D-Watch。D-080)。ただし表示列としてだけで、メニューの出し分けには使っていません(3.0)"-" を出します(orDash() :28-31)FacilitiesScreen.module.css の .colAddress。D-167 追記。旧: 住所だけが余りを受け持っていた)。★列が10あるので、画面幅 1440px では従来どおり横スクロール★。3.15/schedules)/schedules / app/(dashboard)/schedules/page.tsxcomponents/features/schedules/SchedulesScreen.tsx(188行。全画面で最小)GET /api/schedules(:80)clean_schedules(曜日 weeks / 開始 start_time / 終了 end_time(HHMM。時が24〜29を取りうる=翌日。TeamWorks 回答 B-5)/ 有効 inactive = 0)+ 施設名 + 清掃エリアのロボットのシリアル(lib/db/index.ts:1759-1797)。★clean_schedules.contract 列は読みません★(章8 8.3)。★ステージングでは0行です★(2026-10-01 実測・先方投入):19-23)。ユーザーの明示の承認(2026-09-11「ページ送りは作成してください」)に基づいて入れました:13-18):25-26)。モックがそうなっているので揃えませんweekdays(7桁の数字)を画面で解釈しないこと★(:28-31)。曜日列にはサーバーが作った weekdays_label(「毎日」「月水金」等)をそのまま出しますSchedulesScreen.module.css の .colFacility。D-167 追記。旧: 施設名だけが余りを受け持っていた)。3.15/system)/system / app/(dashboard)/system/page.tsxcomponents/features/system/SystemScreen.tsx(480行)GET /api/settings/advice(:116)、GET /api/settings/display-limits(:154)、PATCH /api/settings/display-limits(:193-195)。★PATCH /api/settings/advice はこの画面から呼んでいません★(API はあるが画面に編集欄が無い)lib/config/index.ts(GRADE_THRESHOLDS / ESCALATION_THRESHOLDS / PRECHECK_MINUTES_BEFORE 等。章6)、表示上限とアドバイス文言は data/settings/*.json(保存が無ければコードの既定値)。★DB は読みません★:4)。他の設定(事前チェック設定 / 完了率の判定閾値 / アドバイス エスカレーション閾値)は★表示だけ★で、見出しに 編集不可 バッジが付きます:11-15): 表示上限は★判定に影響しません★(L1〜L3 と R1〜R4 は上限をかける前の生値で判定する)。他は判定に使う閾値なので、変えると過去の判定結果と食い違います(★昨日 ◎ だった清掃が今日は ○ になる★)。D-130data/settings/display-limits.json):200-201)。サーバーが弾いて既定値に倒れた場合も「実際に有効になった値」が返ります:182-183)Number() は送る直前に1回だけ★(:143)| ページ送りあり(★7画面★) | ページ送りなし(7画面) |
|---|---|
| 清掃詳細ログ / アラート / ★エラー一覧★ / D-Secログ / D-Secアラート / 施設ロボットマスタ / スケジュール | 入口 / ログイン / ダッシュボード / レポート / 清掃面積 / 点検記録 / システム管理 |
ページ送りが無い画面のうち、ダッシュボード・レポート・清掃面積は「もっと表示」で件数を広げる方式です(RESCUE_INITIAL_VISIBLE = 5、CLEAN_AREA_INITIAL_VISIBLE = 10、レポートは limit を 10 → 200)。
変えると何が起きるか: ページ送りを外すと、上限(PAGINATION.maxPerPage = 200件)を超えた行が画面から見えなくなります。スケジュール画面にページ送りを入れたのは、まさにその問題(201件目以降が見えない)を直すためでした(D-120)。
★改訂4で追加★(D-162・D-166〜D-168。2026-10-06)。どれもユーザーが画面を見て指摘したもので、★判定や数字には関係しません★。行番号は f2b91fc。
選んだ名前の長さで幅が変わり、隣の部品が動いていたのを止めました。長い名前は「…」で切り、全文はマウスを乗せると出ます(<select> の title に選んだ名前を入れる)。 ★7画面とも、各画面の .module.css の .masterSelect に同じ指定(width: 180px + text-overflow: ellipsis)を書いています★(共通部品にはしていない)。
| 画面 | 場所 |
|---|---|
| 清掃詳細ログ | components/features/detail-log/DetailLogScreen.module.css:94 |
| アラート | components/features/alerts/AlertsScreen.module.css:151 |
| エラー一覧 | components/features/errors/ErrorsScreen.module.css:152 |
| レポート | components/features/reports/ReportsScreen.module.css:81 |
| 清掃面積 | components/features/clean-area/CleanAreaScreen.module.css:61 |
| D-Secログ | components/features/ds-logs/DsLogScreen.module.css:49 |
| D-Secアラート | components/features/ds-alerts/DsAlertsScreen.module.css:47 |
変えると何が起きるか: 幅を変えるときは7か所を揃えます。1か所だけ変えると、その画面だけ並びがずれます(エラーもテスト失敗も出ません)。
components/ui/date-picker.ts の openDatePicker()(:11)を <input type="date"> の onClick に渡しています。中身は showPicker() を呼ぶだけで、★showPicker() が無いブラウザでは何もせず、例外(iframe の中など)は握りつぶします★(開けなくても、カレンダーのマークを押せば開く・数字を打てば入る)。 対象は期間の入力がある4画面 — 清掃詳細ログ(DetailLogScreen.tsx:325 :337)・アラート(AlertsScreen.tsx:381 :393)・エラー一覧(ErrorsScreen.tsx:420 :434)・清掃面積(CleanAreaScreen.tsx:282 :291)です。
どの表も table-layout: fixed+<colgroup> で、★JP/EN を切り替えても列幅が動かない★作りです(2026-10-01 のユーザー指摘以来)。そのうえで「画面が広いときに余った幅をどの列が受け持つか」を決め直しました。 ★以前はどの表も「施設名(または住所)の1列だけ幅を指定せず、余りを全部受け持つ」形で、広い画面ではその列だけが極端に広くなっていました★(例: 清掃詳細ログの施設名が 1920px で ≒600px)。
| 画面(表) | CSS(.table の位置) |
余りの配り方 | 幅を持たせた列 | 表の最小幅 | 決定 |
|---|---|---|---|---|---|
| ★エラー一覧★ | components/features/errors/ErrorsScreen.module.css:246 |
★施設名と種類の2列で等分★(この2列だけ幅を指定しない。どちらも最小 150px) | 検知時刻 120 / シリアル 160 / レベル 90 / 紐づく予定 150 / 対応状況 150 / 備考 160 | ★1130px★(旧 1156 → 1170 → 1130) | D-166 + 追記 |
| 清掃詳細ログ(通常) | components/features/detail-log/CleanLogTable.module.css:33 |
★全列に幅の比で配る★ | 施設名 126(ほか10列も px 指定) | 1150px | D-167 |
| ダッシュボード(同じ表の縮小表示) | 同上 .compact(:60) |
全列に幅の比で | 施設名 240 / 予定 200 / 出発 160 / 完了率 200 / 判定 200 | 1000px | D-167 追記 |
| レポートの施設別パフォーマンス | components/features/reports/FacilityPerformanceTable.module.css:30 |
全列に幅の比で | 施設名 200(.colFacility :39) |
1040px(旧 990) | D-167 追記 |
| 清掃面積 | components/features/clean-area/CleanAreaScreen.module.css:215 |
全列に幅の比で | 名前の列 240(.colName :224) |
1048px(旧 988) | D-167 追記 |
| D-Secログ | components/features/ds-logs/DsLogScreen.module.css:85 |
全列に幅の比で | 施設名 200(.colFacility :107) |
800px(据え置き) | D-167 追記 |
| 施設ロボットマスタ | components/features/facilities/FacilitiesScreen.module.css:45 |
全列に幅の比で | 住所 170(.colAddress :79) |
1300px(据え置き。1440px の画面では横スクロール) | D-167 追記 |
| スケジュール | components/features/schedules/SchedulesScreen.module.css:43 |
全列に幅の比で | 施設名 190(.colFacility :66) |
700px(据え置き) | D-167 追記 |
table-layout: fixed の既定の動き★です。★全列に px の幅が付いていると、余りは各列の幅に比例して足される★。1列でも幅を付けない列があると、余りはその列が全部受け持ちますmin-width の calc か固定値)。これより狭い画面では、表を包む .tableWrap(overflow-x: auto)が横スクロールになります全画面の表(11画面)で、列ごとに見出しと1行目の中身の寄せが一致することを測って確かめています(fixtures・画面幅 1920px。不一致 0)。直したのは2画面です。
| 画面 | 何がずれていたか | 直し方 |
|---|---|---|
| 清掃面積 | 数値の列の見出しに .thNum(右寄せ)を付けていたが、.table th(詳細度 0,1,1)の text-align: left に負けて★見出しだけ左寄せ★ |
.table .thNum / .table .thName(CleanAreaScreen.module.css:256 / :260)にして勝たせる |
| D-Secログ | 見出し .th に寄せの指定が無く★th の既定の中央寄せ★(中身は左寄せ) |
.th に text-align: left(DsLogScreen.module.css:145) |
変えると何が起きるか: 列を足す・幅を広げるときは、★<colgroup> の <col> の順と、.table の min-width も同じだけ直します★(ずれると別の列に幅が付く・横スクロールの境目がずれる)。各 CSS の冒頭コメントに、見出しの EN の幅から決めた値の根拠が書いてあります。★寄せのクラスを足すときは詳細度に注意★してください(.table th のような指定に負けると、効いていないのにエラーは出ません)。
app/api/ の23本「API ルート」とは、ブラウザの画面がサーバーへ問い合わせるための窓口です。D-Watch では app/api/ の下に route.ts という名前で置かれているファイルがそれで、フォルダの形がそのまま URL になります(app/api/clean-logs/route.ts → /api/clean-logs)。
CLAUDE.md §2 の言う「APIサーバー」とは、外部の誰かが提供するものではなく、この app/api/ のことです。TeamWorks が API を提供するわけではありません。ルートの中で Prisma が DB を直接読みます(D-046)。
現在 app/api/**/route.ts は 23本です(2026-10-05、a1d7e43 で数えた)。1つのファイルが複数の HTTP メソッドを持つことがあるため、窓口の数は25です。 ★改訂3(2026-10-06、f02eb1d)で2本増え 25本・窓口27 です★(下の表の 24・25)。★改訂4(2026-10-07、f2b91fc)で1本増え 26本・窓口28 です★(下の表の 26。レポートの CSV。D-163)。
「HTTPメソッド」は操作の種類です。GET = 読む / POST = 作る・送る / PATCH = 一部を直す。実際に export async function GET(...) のように書かれている名前から判定しています。
★app/api/prechecks/[id]/status/route.ts(PATCH)★です(指示書AT 段階5。2026-10-02 のコミット 8ddd9a8)。アラート画面から事前チェック異常の対応状況と備考を保存する窓口で、★D-Watch がメインDBに書き込む唯一の窓口でした★(★改訂3 で PATCH /api/errors/{error_id}/status が加わり2本。どちらも同じ4列を同じ関数で書く★。書くのは robot_error_reports の4列だけ。章8 8.10)。消えたルートはありません。
| # | パス | メソッド | 何をするか | 使うクエリ層・部品 | 主に使う画面(呼び出し箇所) |
|---|---|---|---|---|---|
| 1 | /api/auth/login |
POST | 共有トークンを検証し dwatch_session cookie を発行 |
lib/auth/token lib/auth/cookie |
ログイン(app/login/page.tsx:31) |
| 2 | /api/auth/logout |
POST | dwatch_session cookie を削除(204) |
lib/auth/cookie |
★画面からの呼び出しは0件★(components/ app/ に該当なし。ログアウトのボタンが無い) |
| 3 | /api/clean-area |
GET | 清掃面積の集計(総面積・粒度別の内訳・完了率・実施回数・異常値の見張り) | lib/clean-area/query aggregate anomaly / lib/db/clean-area-source |
清掃面積(CleanAreaScreen.tsx:129) |
| 4 | /api/clean-logs |
GET | 清掃詳細ログ一覧(★他の一覧APIの見本★。★各行に result_pending(結果待ち)。結果待ちは result=problem / critical に出ない。D-173★) |
lib/clean-logs/query / lib/settings/display-limits |
清掃詳細ログ(DetailLogScreen.tsx:193)。★ダッシュボードはもう呼ばない(D-159)★ |
| 5 | /api/clean-logs/export |
GET | 同じ条件で CSV を出す(page / per_page は無視。20,000行超は 422) |
lib/clean-logs/query lib/clean-logs/csv ★lib/csv★ |
清掃詳細ログ(DetailLogScreen.tsx:499 のリンク) |
| 6 | /api/clean-logs/[id]/advice |
GET | 行を開いたときの推定原因+確認アクション(R1〜R4 / B1〜B3) | lib/judge / lib/settings/advice / lib/settings/display-limits |
清掃詳細ログ(:231)/ ダッシュボード(:120) |
| 7 | /api/clean-logs/[id]/areas |
GET | エリア別完了率(施設×日付) | lib/clean-logs/areas |
清掃詳細ログ(:286)/ ダッシュボード(:175) |
| 8 | /api/customers |
GET | 顧客一覧(分類マスタ。読み取り専用) | lib/masters/query |
分類の絞り込みがある全画面(lib/masters/options.ts:100) |
| 9 | /api/dashboard |
GET | 清掃結果サマリ(★summary.pending=結果待ち。D-173★) / ★コンパクト版の表(problem_logs)★ / スケジュール進捗 / 要レスキュー / セキュリティバナー。★date 省略時は直近モード(D-159)★ |
lib/dashboard/aggregate / ★lib/dashboard/latest-run★ / lib/clean-logs/query |
ダッシュボード(DashboardScreen.tsx:85) |
| 10 | /api/ds-alerts |
GET | D-Secアラート一覧+サマリ | lib/ds-alerts/query |
D-Secアラート(DsAlertsScreen.tsx:103) |
| 11 | /api/ds-devices |
GET | D-S制御盤の稼働状況一覧+サマリ(備考を合成) | lib/ds-devices/query lib/settings/ds-device-notes |
D-Secログ(DsLogScreen.tsx:217) |
| 12 | /api/ds-devices/[serial_number]/note |
PATCH | D-S制御盤の備考を保存(★メインDBではなくファイルへ★) | lib/settings/ds-device-notes |
D-Secログ(DsLogScreen.tsx:271) |
| 13 | /api/facilities |
GET | 施設ロボットマスタ(読み取り専用) | lib/facilities/query |
施設ロボットマスタ(FacilitiesScreen.tsx:78) |
| 14 | /api/groups |
GET | ホールディングス一覧(分類マスタ。読み取り専用) | lib/masters/query |
分類の絞り込みがある全画面(lib/masters/options.ts:99) |
| 15 | /api/prechecks |
GET | アラート(事前チェック異常+出発失敗)一覧+サマリ(★結果待ちの清掃は出発失敗に出さない。D-173★) | lib/prechecks/query |
アラート(AlertsScreen.tsx:225) |
| 16 | ★/api/prechecks/[id]/status★ ★新(10/02)★ |
★PATCH★ | ★事前チェック異常の対応状況・備考を更新★(★robot_error_reports の4列へ書く窓口。10/06 から 25 も★) |
lib/prechecks/status-update / lib/db/robot-error-write / lib/logger/db-write |
アラート(AlertsScreen.tsx:136。★「保存(N件)」で変えた行だけ1行ずつ★。D-165) |
| 17 | /api/reports/continuous-problems |
GET | 2日以上連続で△以下 or 出発失敗の施設 | lib/reports/query / lib/db/report-mapping |
レポート(ReportsScreen.tsx:220) |
| 18 | /api/reports/error-breakdown |
GET | エラー種別の内訳(件数の降順・★生値のまま★) | lib/reports/query |
レポート(ReportsScreen.tsx:226) |
| 19 | /api/reports/facility-performance |
GET | 施設別パフォーマンス(稼働率ワースト順) | lib/reports/query ★lib/reports/facility-performance-load(CSV と共有。D-163)★ |
レポート(ReportsScreen.tsx:217) |
| 20 | /api/reports/kpi |
GET | KPI4指標+前期比(対象顧客が1社に定まらないと 422) | lib/reports/query lib/reports/target-customer |
レポート(ReportsScreen.tsx:216) |
| 21 | /api/schedules |
GET | スケジュール一覧(読み取り専用) | lib/schedules/query |
スケジュール(SchedulesScreen.tsx:80) |
| 22 | /api/settings/advice |
GET / PATCH | アドバイス文言の一覧 / 1ルール分の保存(data/settings/advice.json) |
lib/settings/advice lib/settings/advice-validation |
システム管理(GET のみ SystemScreen.tsx:116)。★PATCH を呼ぶ画面は0件★ |
| 23 | /api/settings/display-limits |
GET / PATCH | 表示上限の一覧 / 1件更新・既定値へ戻す(data/settings/display-limits.json) |
lib/settings/display-limits |
システム管理(SystemScreen.tsx:154 / :193) |
| 24 | ★/api/errors★ ★新(10/06)★ |
GET | ★エラー一覧★(robot_error_reports の全行。既定は未解消・対応中。include_resolved=true で解消も。★小文字の true / false 以外は422★) |
lib/error-reports/query / lib/db/error-report-mapping / lib/facilities/resolve-by-name resolve-by-master intersect-ids |
エラー一覧(ErrorsScreen.tsx:238) |
| 25 | ★/api/errors/[error_id]/status★ ★新(10/06)★ |
★PATCH★ | ★エラーの対応状況・備考を更新★(★robot_error_reports に行があれば書ける。事前チェックかどうかを問わない★。規則・部品は 16 と同じ) |
lib/prechecks/status-update / lib/db/robot-error-write / lib/logger/db-write |
エラー一覧(ErrorsScreen.tsx:109。★「保存(N件)」で変えた行だけ1行ずつ★。D-165) |
| 26 | ★/api/reports/facility-performance/export★ ★新(10/06)★ |
GET | ★施設別パフォーマンスの CSV★(/reports/facility-performance と同じパラメータ。★limit は無視して絞り込み後の全施設★。並びも表と同じ稼働率ワースト順。UTF-8 BOM・CRLF・日本語固定。★MAX_CSV_ROWS(20,000)を超えたら 422★(details: { total, max }。清掃詳細ログの CSV と同じ形・同じ向き >)。ファイル名は facility-performance_{period}_{期間}.csv(例 _monthly_2026-09 / _weekly_2026-10-w1 / _yearly_2025)。app/api/reports/facility-performance/export/route.ts(83行)。D-163) |
★lib/reports/facility-performance-load(一覧と同じ1本)★ / lib/reports/facility-performance-csv / lib/reports/facility-performance-labels / lib/csv |
レポート(ReportsScreen.tsx:356 のリンク) |
ルートは DB を直接読まず、db.〇〇()(lib/db/index.ts)を呼びます。どのメソッドがどのテーブルを読むかは章8の 8.2 にまとめました。ここでは窓口ごとの要約だけを書きます。
| ルート | 呼ぶ db のメソッド |
主なテーブル |
|---|---|---|
/clean-logs /clean-logs/export /clean-logs/{id}/advice |
cleanLogs() |
clean_timetables clean_task_reports robot_error_reports |
/clean-logs/{id}/areas |
cleanLogs() + areaCompletions() |
上と同じ + sections |
/dashboard |
cleanLogs() ★cleanLogRunMeta()(直近モードだけ)★ schedules() dsAlerts() rescueRequired() |
上と同じ + clean_schedules control_error_histories |
/prechecks |
prechecks() |
clean_timetables clean_task_reports robot_error_reports |
/prechecks/{id}/status |
precheckItemById() → updatePrecheckErrorColumns() → precheckItemById() |
★robot_error_reports(読んで・4列を書いて・読み直す)★ |
★/errors★ |
errorReports()(★2段階で読む★)+ facilityNameIndex() facilityGroupIndex() |
robot_error_reports clean_timetables clean_timetable_histories facilities |
★/errors/{error_id}/status★ |
errorReportById() → updateErrorReportColumns() → errorReportById() |
★robot_error_reports(読んで・4列を書いて・読み直す)★ |
/reports/kpi |
customers() reportKpis() |
customer_summaries(確定期間)/ 詳細3テーブル(当期) |
/reports/facility-performance ★/reports/facility-performance/export★ |
facilityPerformance()(★両方とも loadFacilityPerformance() 経由★) |
facility_summaries(確定期間)/ 詳細3テーブル(当期) |
/reports/continuous-problems |
continuousProblems() |
clean_timetables clean_task_reports |
/reports/error-breakdown |
errorBreakdown() |
facility_error_summaries(確定期間)/ robot_error_reports(当期) |
/clean-area |
cleanArea() |
clean_task_reports clean_timetables facilities customers groups |
/ds-devices /ds-devices/{serial}/note |
dsDevices() dsDeviceExists() |
controls control_reports + data/settings/ds-device-notes.json |
/ds-alerts |
dsAlerts() |
control_error_histories |
/facilities |
facilities() |
facilities robots devices customers groups |
/schedules |
schedules() |
clean_schedules |
/groups /customers |
groups() customers() |
groups customers |
/settings/* /auth/* |
(DB を読まない) | data/settings/*.json / cookie |
d-watch-api.yaml)にあるが、ルートが無いもの★d-watch-api.yaml には26のパスがあり、ルートがあるのは23です(★改訂3 時点 28 / 25。/errors と /errors/{error_id}/status を契約とルートの両方に足した★。★改訂4 時点 29 / 26。/reports/facility-performance/export を足した(d-watch-api.yaml:981)★)。残り3つは契約だけ先に書かれていて、まだ作っていません。
| パス | メソッド | 状態 |
|---|---|---|
/inspections |
GET / POST | 点検記録。画面は「準備中」(章3 3.8。D-150 の未決4件待ち) |
/inspections/{id} |
GET / DELETE | 同上 |
/ds-alerts/{id}/status |
PATCH | D-Secアラートの対応状況の更新。CLAUDE.md §3 の権限上は control_error_histories の4列を書けるが、未着手 |
(d-watch-api.yaml:1610 / :1699 / :1478。f2b91fc)
変えると何が起きるか: API の形(パス・パラメータ名・返す JSON のキー)を変えたくなったら、先に d-watch-api.yaml を直してからコードに反映します(CLAUDE.md §6-1)。実装だけ変えると、YAML を正としている型定義・テスト・TeamWorks への説明と静かに食い違います。
23本すべてが同じ骨格でできています。見本は app/api/clean-logs/route.ts です(ファイル冒頭 :4 が「これが動く見本です」と宣言しています)。
① 必ず withRequestId で包む
export async function GET(request: Request) {
return withRequestId(request, async () => {
(app/api/clean-logs/route.ts:47-48)
withRequestId()(lib/logger/context.ts:70)は、1回の問い合わせに通し番号を振る関数です。この中で出たログにはすべて同じ番号が付くので、「このエラーはどの操作で起きたのか」を後から追えます。
23本すべてに入っていることを確認済みです(withRequestId を含まない route.ts は0件。2026-10-05)。
変えると何が起きるか: 包み忘れたルートは、ログの request_id が - になります。エラーは出ません。障害が起きた日に「追えない1本」としてだけ現れます。
② catch で logApiError() を呼ぶ
} catch (e) {
const body: ApiError = {
error: { code: "internal_error", message: "データの取得に失敗しました", details: null },
};
logApiError({ route: ROUTE, method: "GET", body, status: 500, err: e });
return errorResponse(body, 500);
}
(app/api/clean-logs/route.ts:90-96)
logApiError()(lib/logger/api.ts:48)の要点は、クライアントへ返すのと同じオブジェクトをログにも渡すことです(:3-7)。ログ側で文言を組み立て直すと、画面に出た文言とログの文言がずれます。生の例外メッセージはログの error_message に入りません(:7)。
各ルートの先頭には const ROUTE = "/api/..." が置かれ、これをログに使います(23本中22本。/api/auth/logout だけは ROUTE 定数を持ちません)。コメントが「実際のパスを書く。/api/v1/... のような架空のパスを書かない」と念を押しています(lib/logger/api.ts:15)。
③ 書き込みのルートだけ、もう1種類ログを出す
PATCH /api/prechecks/{id}/status は、メインDBに書いたときに★監査ログ db.write を成功・失敗の両方で1件ずつ★出します(app/api/prechecks/[id]/status/route.ts:150 :155 :159。lib/logger/db-write.ts)。★備考の中身と DB のエラーメッセージは載せません★(章8 8.10)。
④ 返す形は2通りだけ
成功: { items, meta }(一覧)または素のオブジェクト(単体) 失敗: { error: { code, message, details } }
CLAUDE.md §9.3 が全API共通と定めている形です。
{ "error": { "code": "validation_failed", "message": "…", "details": { "week": ["1以上を指定してください"] } } }
code に入ってよい値は6種類に限定されています(lib/types/index.ts:31-45 の ErrorCode)。
| code | 意味 | 誰が返すか |
|---|---|---|
validation_failed |
パラメータが不正(422) | 各ルート |
not_found |
対象が無い(404) | [id] [serial_number] を取るルート |
internal_error |
想定外の失敗(500) | 各ルートの catch |
unauthorized |
未認証(401) | proxy.ts:84 |
forbidden |
IP制限で拒否(403) | proxy.ts:69 |
invalid_token |
ログイン時のトークン不一致 | /api/auth/login |
"not_implemented"(501)は外されました(lib/types/index.ts:35-39)。501 を返している実装が0件で、d-watch-api.yaml にも出てこないためです。
PATCH /api/prechecks/{id}/status の 404 は3通りあります — ①その id の行が無い/事前チェック異常の行ではない(出発失敗の id を含む)(route.ts:126)、②確認と書き込みの間に行が消えた(書いた件数が0。:153-156)、③書いた後に読み直せない(:169)。どれも同じ文言で返します。
変えると何が起きるか: ここに値を足すと、d-watch-api.yaml と型定義が乖離します。足すなら先に YAML です。
lib/api/query-result.ts の役割「クエリパラメータの検査に失敗した」ことを、null ではなく形のある値で返すための共通の型です(lib/api/query-result.ts:23-25)。
export type QueryParseResult<T> =
| { ok: true; query: T }
| QueryParseFailure;
失敗側は { ok: false, error, status } を持ちます(:17-21)。fail()(:32)がそれを組み立てます。
なぜ null ではいけないか。null だと「どのパラメータがなぜ不正か」を呼び出し側へ渡せず、ルートは details: null の素っ気ない422しか返せません(:4-7)。画面は「何を直せばいいか」を利用者に言えなくなります。
置き場所が lib/clean-logs/ ではなく lib/api/ なのは、エラー形式が全API共通だからです(:9-12)。
使う側は route.ts の冒頭でこう書きます。
const parsed = parseCleanLogQuery(searchParams);
if (!parsed.ok) {
return errorResponse(parsed.error, parsed.status);
}
(app/api/clean-logs/route.ts:50-53)
URL の中の {id} などは別の部品 lib/api/path-params.ts が検査します — parsePathId()(上限 PATH_ID_MAX)、parsePathSerialNumber()(100文字)、★parsePathErrorId()(50文字。10/02 に追加)★。上限の数値は lib/config/index.ts にあります(章6)。
変えると何が起きるか: 検査を通さず searchParams.get("date_from") を直接データ層へ渡すと、検証されていない値が SQL の条件に届きます(lib/clean-logs/query.ts:52-54 が明示的に禁止)。
metaCLAUDE.md §9.3 は「一覧APIは必ず meta: { total, page, per_page } を返す」と定めています。
const body: ListResponse<CleanLogItem> = {
items,
meta: { total, page, per_page: perPage },
};
(app/api/clean-logs/route.ts:106-109)
total は絞り込んだ後・ページで切り出す前の件数です(:100)。画面はこれでページ数を計算します(DetailLogScreen.tsx:193)。
意図的な例外:
/api/reports/error-breakdown は meta を返しません(app/api/reports/error-breakdown/route.ts:31-41)。エラー種別の内訳は集計結果で全件を返すため、ページングの概念がないからです。コメントは「型を合わせるために meta を捏造しない」と書いています/api/reports/kpi と /api/dashboard は一覧ではなく単体の集計なので、items / meta で包まず素のオブジェクトを返します(app/api/reports/kpi/route.ts:6)/api/clean-area は meta を返しますが、ページ送りが無いので常に page: 1・per_page = 件数です(app/api/clean-area/route.ts:123)PATCH の3本(備考・表示上限・アラートの状態)は、更新後の1件を素のオブジェクトで返します。画面はその値で表示を書き換えます変えると何が起きるか: total を「ページ切り出し後の件数」にすると、ページ送りが常に1ページで止まります。表示上限(連続日数の頭打ち)を total に反映させると、CSV出力の可否判定(DetailLogScreen.tsx:179 の csvBlocked。f2b91fc)がずれます。
D-Watch は全ページ・全APIに認証がかかります。担当するのは proxy.ts(リポジトリ直下、115行)です。
★このファイルは middleware.ts ではありません★。Next.js 16 で middleware は非推奨になり proxy に改名されました(proxy.ts:4-7)。middleware.ts を置くと dev サーバが警告を出します。
proxy.ts はこの順で動きます(:13-17。逆にしてはいけません)。
:39)… 弾いたリクエストのログにも番号が付くよう必ず先:63-73)/login へリダイレクト(:92)、APIは 401(:81-85)認証をかけないパスは2つだけです。
const PUBLIC_PATHS = ["/login", "/api/auth/login"] as const;
(lib/proxy/authenticate.ts:25)
ここを保護すると誰もログインできなくなるため、という理由がコメントに書かれています。
重要なのは、この2つを matcher(proxy を走らせる対象の絞り込み)では除外していないことです(proxy.ts:106-107)。matcher から削ると「保護漏れの発見が難しくなる」ため、あえて関数の中で分岐して通しています。matcher が除外するのは静的ファイル(_next/static / _next/image / favicon.ico / .svg)だけです(:112-113)。
IP制限は X-Forwarded-For の末尾を見ます(proxy.ts:63 の clientIp(forwardedFor, TRUSTED_PROXY_HOPS))。先頭はクライアントが自由に書けるので使いません(CLAUDE.md §3 / D-077 項5)。 なお、許可リスト ALLOWED_IPS は空のままで、ALLOW_ALL_WHEN_EMPTY = true なので、★いまは IP 制限が事実上かかっていません★。これは検証段階の意図した状態で、★リリース直前のセキュリティ強化の対象として CLAUDE.md §3 に列挙済み★です(D-154。章6 6-6)。
変えると何が起きるか: PUBLIC_PATHS に /api を足すと、全APIが無認証で読めるようになります。順序を入れ替えて認証をIP制限より前に置くと、IP制限で弾いたログに相関IDが付きません。proxy.ts に runtime を指定するとエラーになります(:19-20。proxy は Node.js ランタイム固定)。
このアプリの「◎○△×」「L1〜L3」「KPI の数字」「対応アドバイス」「要レスキュー」は、すべて lib/judge/ の6ファイルの中で決まります。画面も API も、ここが出した答えを並べているだけです。
| ファイル | 行数 | 何を決めるか |
|---|---|---|
lib/judge/grade.ts |
76 | ◎○△× の判定(judgeGrade())と表示用の丸め(displayRate()) |
lib/judge/kpi.ts |
203 | ★1回ごとの完了率(runCompletionRate())★・完了率の平均(meanCleanRatio())・稼働率など3指標・前期比・施設ステータス |
lib/judge/escalation.ts |
68 | 連続問題日数と L1〜L3 |
lib/judge/advice.ts |
122(f2b91fc) |
対応アドバイス R1〜R4 / B1〜B3 の発火条件 |
lib/judge/rescue.ts |
296 | 要レスキュー店舗の3条件 |
lib/judge/index.ts |
22 | 上の5つをまとめて外に出す入口 |
判定そのものは上の6ファイルですが、★判定に渡す材料を作る係★が lib/db/ などに8本あります(章5-10)。「どの行を数えるか」はそちらで決まるので、数字の意味を追うときは両方を見てください。画面ごとの「どのテーブルのどの列から来た数字か」は、次の章5-Bにまとめてあります。
★完了率の出し方は、この1か月で2回変わりました★。古い版を覚えている人は、この表を先に読んでください。
| 時期 | 清掃完了率の出し方 | 決定 |
|---|---|---|
| 〜2026-09-29 | Σ清掃面積 ÷ Σ清掃対象面積(期間の合計どうしの面積比) | 旧 CLAUDE.md §4.2 |
| 2026-09-30〜10-01 | clean_task_reports.clean_ratio 列の単純平均 |
D-151(指示書AQ 決定4) |
| ★2026-10-02〜(現在)★ | ★1回ごとの完了率(min(clean_area ÷ task_area × 100, 100) を小数第1位まで切り捨て)の単純平均。予定外のタスクは除く★。★2026-10-05 から task_area = 0 は 0%★ |
★D-155(ユーザー判断「一旦B」2026-10-02)/ 指示書AS → ★D-158 で確定(第11回 P-2=A・P-5=B)/ 指示書AU★ |
あわせて ◎○△× の境界も 2026-09-16 に変わっています(「超」→「以上」。D-139。章5-2)。
純粋関数とは「同じ数字を入れれば必ず同じ答えが返り、データベースも時計も見ない関数」のことです。lib/judge/ の関数はすべてこれで、judgeGrade(90.1) は、いつ・誰が・どのパソコンで呼んでも必ず ◎ を返します。
こうしておくとテストで答えを固定できるのが最大の利点です。「完了率 90.1 なら ◎」という表を1枚作っておけば、機械が毎回自動で確かめてくれます。
lib/judge/ はサンプルデータと実DBの切り替え(USE_FIXTURES)の外側にあります(lib/judge/index.ts:4-6)。サンプルデータで動かしている間も本番とまったく同じ判定が走ります(D-044)lib/judge/rescue.ts は冒頭で「DBアクセスも new Date() も混ぜない」と明記していますlib/judge/ は lib/db/ を import しません。そのため runCompletionRate() の引数も「clean_area と task_area を持つもの」という形だけで受け取っています(lib/judge/kpi.ts:141-143 の docstring / :144-147)変えると何が起きるか: この中で DB や現在時刻を読むと、テストが日によって成功したり失敗したりするようになり、判定の正しさを誰も保証できなくなります。
judgeGrade()実体は lib/judge/grade.ts:29-35 です。引数は清掃完了率(%)で、未実施は null(値が無いこと)。閾値は直書きせず、lib/config/index.ts:18-25 の GRADE_THRESHOLDS(excellent: 90 / good: 70 / poor: 10)から読みます。
flowchart TD
A["完了率 rate(丸める前の生値)"] --> B{"rate は null か<br/>(未実施)"}
B -- はい --> X["× を返す"]
B -- いいえ --> C{"rate >= 90<br/>(excellent)"}
C -- はい --> D["◎ を返す"]
C -- いいえ --> E{"rate >= 70<br/>(good)"}
E -- はい --> F["○ を返す"]
E -- いいえ --> G{"rate < 10<br/>(poor)"}
G -- はい --> H["× を返す"]
G -- いいえ --> I["△ を返す"]
lib/judge/grade.ts:29-35 / 表示 :43-46)★比較は >=(以上)です★。★ちょうど 90.0 は ◎、70.0 は ○★。以前は >(超)で 90.0 は ○ でしたが、ユーザー指示「◯、×は90%以上のように境はふくめてください」(2026-09-16)で変わりました(D-139。旧 D-020b は破棄)。★× の境界だけは「10%未満」のまま★(同日「10%未満を×としてください」で確定)なので、10.0 は △ です。
| 完了率(生値) | 判定 | 画面表示 displayRate() |
なぜそうなるか | 旧(D-139 前) |
|---|---|---|---|---|
| 90.1 | ◎ | 90% |
90.1 >= 90(grade.ts:31) |
◎(変化なし) |
| ★90.0★ | ★◎★ | 90% |
90.0 >= 90 が成立 |
★○ だった★ |
| 89.9 | ○ | 90% |
>= 90 不成立、>= 70 成立 |
○ |
| ★89.5★ | ★○★ | ★90%★ |
★表示は 90% だが判定は ○★(章5-3) | ○ |
| 70.1 | ○ | 70% |
70.1 >= 70(grade.ts:32) |
○ |
| ★70.0★ | ★○★ | 70% |
70.0 >= 70 が成立 |
★△ だった★ |
| 69.9 | △ | 70% |
>= 70 不成立・< 10 不成立 |
△ |
| 10.1 | △ | 10% |
同上 | △ |
| 10.0 | △ | 10% |
10.0 < 10 は不成立なので △ |
△(変化なし) |
| 9.9 | × | 10% |
9.9 < 10 が成立(grade.ts:33) |
× |
| 0 | × | 0% |
同上 | × |
| null(未実施) | × | - |
最初の分岐で即 ×(grade.ts:30) |
× |
この表は lib/judge/__tests__/grade.test.ts:24-37 の期待値表(12ケース)と同じです。表を変えたいときは、まずこのテストを直します。
GRADE_THRESHOLDS を読むのは judgeGrade() だけではありません。★isProblemCompletion()(lib/db/clean-log-mapping.ts:332-334)は rate === null || rate < GRADE_THRESHOLDS.good という別の書き方の同じ規則★で「問題あり(△×)」を判定しています(連続日数の材料)。結果は judgeGrade() と常に一致しますが、★片方の比較演算子だけを動かすと食い違います★(grade.ts:54-63 / clean-log-mapping.ts:313-331)lib/judge/advice.ts:66)も GRADE_THRESHOLDS.poor を直接読みます変えると何が起きるか: GRADE_THRESHOLDS を1つ動かすと、全画面のバッジ・ダッシュボードの成功件数・アドバイス R2 / B1 の発火・連続問題日数(L1〜L3)・要レスキューの L3 判定が同時に動きます。70.0 の扱いが変わった影響は実際に出ており、lib/judge/__tests__/advice.test.ts:150-155 が「70.0% では B1 が出なくなった」をテストとして記録しています(lib/judge/advice.ts は1行も変えずに挙動が変わった例)。
displayRate()(lib/judge/grade.ts:43-46)は Math.round() で整数にして % を付けるだけの表示専用の関数です。null は - を返します。判定には絶対に使いません(CLAUDE.md §4.3)。
90%、判定は ○(grade.test.ts:28 / :52)90%、判定は ◎(grade.test.ts:49)つまり画面に同じ「90%」と並ぶ2行が、片方は ◎ で片方は ○ ということが正常に起こります。
★事故の向きが D-139 で逆になりました★(lib/judge/grade.ts:7-11 / grade.test.ts:44-57)。旧規則では「90.4 を丸めると 90 になり ◎ が消える」側でしたが、新規則では 90 も ◎ なので消えません。いまは ★無いはずの ◎ が丸めで生まれる★ 側が事故です — Math.round(89.5) は 90 で、それを判定に渡すと ○ が ◎ に化けます(grade.test.ts:53)。69.5 → 70 で △ が ○ に化けるのも同じ形です(:56)。どちらにせよ「丸めた値で判定しない」は変わりません。
★丸めの種類は2つあり、混同しないこと★:
| 丸め | どこで | 何に | 規則 |
|---|---|---|---|
| ★切り捨て★ | runCompletionRate()(lib/judge/kpi.ts:144-153) |
★1回ごとの完了率だけ★ | 小数第1位まで残して切り捨て(判定の材料。TeamWorks のサマリと同じ式) |
| ★四捨五入★ | displayRate()(lib/judge/grade.ts:43-46) |
画面に出すときだけ | 整数に四捨五入(判定には使わない) |
平均した完了率は★丸めずに★判定し、★表示のときだけ★整数に四捨五入します。例: 1回ごとの完了率 89.9 と 90.0 の平均 89.95 は、画面に「90%」と出ますが判定は ○ です(lib/judge/__tests__/run-completion-rate.test.ts:34 の M2)。
変えると何が起きるか: 丸めてから判定に渡しても、例外も出ずテストも(他のテストは)通ります。丸めた値も「正しそうな数字」だからです。誰も気づかないまま評価が1段階ずれ続けます。
★清掃完了率は「1回ごとの完了率」の単純平均(清掃1回=1票)です★(★D-155。ユーザー判断「一旦B」2026-10-02★ / 指示書AS。★第11回 P-2=A で確定。D-158★)。
1回ごとの完了率 = min(clean_area ÷ task_area × 100, 100) を小数第1位まで切り捨て 清掃完了率 = Σ 1回ごとの完了率 ÷ 1回ごとの完了率が null でない清掃の回数
clean_area … clean_task_reports.clean_area(清掃した面積。prisma/schema.prisma:534)task_area … clean_task_reports.task_area(清掃対象の面積。prisma/schema.prisma:533)double(12,6)(小数6桁まで持てる数値)です(D-094)画面ごとに「どの行を数えるか」の違いは章5-Bを見てください。ここでは式と、その根拠を説明します。
| 決めている場所 | 何を決めているか |
|---|---|
★runCompletionRate()(lib/judge/kpi.ts:144-153)★ |
★1回ごとの完了率を決める唯一の関数★(指示書AS D1)。★task_area が 0 なら 0(第11回 P-5=B。旧: null)★。★戻り値は number(null を返さない。指示書AU)★ |
RUN_COMPLETION_RATE(lib/config/index.ts:37-42) |
式の部品 — 上限 cap: 100 / 残す小数の桁 decimals: 1(指示書AS D2) |
FLOAT_ERROR_DIGITS = 5(lib/judge/kpi.ts:110) |
浮動小数の誤差の吸収幅(下の「誤差の吸収」)。★仕様の閾値ではなく計算の都合なので lib/config には置いていない★(kpi.ts:106-109) |
★meanCleanRatio()(lib/judge/kpi.ts:101-104)★ |
★複数回を畳む(単純平均する)唯一の関数★。件数0なら null |
completionRateFromReport()(lib/db/clean-log-mapping.ts:203-210) |
清掃1回ぶん(清掃詳細ログの1行)の完了率。★レポート行が無ければ null、あれば runCompletionRate() を呼ぶだけ★ |
★完了率を出している画面はすべて、この2関数を通ります★ — 清掃詳細ログの1行・エリア別完了率(行と全体)・レポートの当期KPI・施設別パフォーマンスの当期・総清掃面積・要レスキュー R-3(kpi.ts:70-75 の docstring が列挙)。2箇所で別々に畳むと、同じ施設がレポート画面と総清掃面積画面で違う完了率になります。
clean_task_reports.clean_ratio 列を使わないのか / なぜ暫定なのか★clean_task_reports には clean_ratio(清掃率)という列があり、D-151(2026-09-30)ではこの列の平均を完了率にしていました。しかし TeamWorks に「この列はどう作られているか」と照会したところ、★「計算式が間違っているので、訂正したら回答する」★という回答でした(第10回 N-3。docs/handoff/TeamWorks回答_第10回_20261002.md:67-69)。式が間違っていると先方が認めている列を、判定の材料にはできません。
同じ回答の中で、TeamWorks は★サマリ(確定期間の *_summaries.clean_ratio)の作り方★を「清掃1回毎に clean_area / task_area * 100(100より大きい値は100、小数点第2位以下切捨て)で計算し、それを合算して平均」と説明しました(同 :71-76)。★当期(アプリがその場で集計する分)をこの式にそろえれば、前期比(サマリ)と当期(アプリ)が同じ式どうしの比較になります★。そこでユーザーは「一旦B」(この式にそろえる案)を選びました(D-155 項1)。却下した案A(clean_ratio 列の平均を続け、サマリ側を直してもらう)は、確定期間と当期で式が違い、前期比が「別の式どうしの差」になるためです。
★2026-10-05 に確定しました★(第11回 P-2=A「今の式のまま」。P-1: 訂正後の clean_ratio 列もこの式と同じ値になる。D-158)。以下は暫定だったときの作りで、いまも残しています — 式を変えるときに直すのは ★runCompletionRate() の中身だけ★で済むよう、呼び出し側は clean_task_reports の行(clean_ratio も持っている)を分解せずにそのまま渡す約束です(kpi.ts:120-123 / 指示書AS D6)。clean_ratio 列は完了率には使っていませんが、型・select・サンプルデータからは消していません。
lib/judge/__tests__/run-completion-rate.test.ts:13-27)| # | 清掃した面積 | 対象面積 | 1回ごとの完了率 | 確かめていること |
|---|---|---|---|---|
| R1 | 50 | 100 | 50.0 | 基本 |
| R3 | 150 | 100 | ★100.0★ | 上限 100 で切る |
| R5 | 89.95 | 100 | ★89.9★ | 四捨五入ではなく切り捨て |
| R6 | 0.57 | 1 | ★57.0★ | 浮動小数の誤差(56.99999…)で 56.9 にしない |
| R8 | 0 | 100 | 0.0 | 「0%で実施」(未実施ではない) |
| R9 | 10 | 0 | ★0.0★ | 対象面積 0 は 0%(★第11回 P-5=B。D-158 / 指示書AU 表1。旧: null★) |
| R12 | 0 | 0 | ★0.0★ | 両方 0 も 0%(指示書AU 表1) |
| R11 | 898.892509 | 1377.39502 | 65.2 | 65.26… を切り捨て(実データの桁) |
0.57 ÷ 1 × 100 は 57 にならないコンピュータの小数は2進数で持つため、0.57 / 1 * 100 は 56.99999999999999 になります。これをそのまま「小数第1位まで切り捨て」ると 56.9 になってしまいます。runCompletionRate() は切り捨ての前に、★小数第6位で一度四捨五入してから★切り捨てます(lib/judge/kpi.ts:151-152)。
const absorbed = Math.round(raw * 10 ** (decimals + FLOAT_ERROR_DIGITS)) / 10 ** FLOAT_ERROR_DIGITS; return Math.floor(absorbed) / 10 ** decimals;
つまり ★1e-6 ポイント(0.000001%)未満の差は誤差とみなします★(指示書AS D3)。帰結として、真の値が 89.9999995 のような「境目のごく近く」の値は 90.0 に繰り上がりますが、double(12,6) の面積からその桁の差が意味を持つことはない、という判断です。★Math.floor(raw * 10) / 10 と書かないこと★(R6 が 56.9 になる)。★toFixed() は四捨五入なので使わないこと★(kpi.ts:128-130)。
clean_area は★同じ場所を何回も清掃した面積の総和★です(第10回 N-4「cleanarea は清掃した面積の総和」。`TeamWorks回答第10回_20261002.md:79-81)。そのため面積比は「どれだけ網羅したか」ではありません。 ユーザー提供CSV(clean_task_reports.csv189行。2026-09-30 受領。★先方投入の実データ★。第10回 N-10 で「Pudu Cloud から取得した実データ」と回答)では ★clean_area > task_areaが 129行(68.3%)★あり、★それらは全部 100% になります★(◎ が多めに出る)。 ユーザーはこれを承知で採用しました(D-155 項1)。★上限 100 を外したり、別の補正を入れたりしないこと★(kpi.ts:136-139`)。
| 条件 | 扱い | 根拠 |
|---|---|---|
★本清掃(clean_timetables.precheck = 0)に対応する行だけ★ |
事前チェック(precheck が 60 / 30 など)の予定に紐づく行は数えない |
TeamWorks 第10回 N-3「事前確認の結果は含めない。precheck が0のデータのみ集計対象」(TeamWorks回答_第10回_20261002.md:75)。CLEAN_TIMETABLE_PRECHECK.main = 0(lib/config/index.ts:243-246) |
★clean_timetable_id が NULL の行(予定外のタスク)は完了率にも実施回数にも入れない★ |
ただし★総清掃面積の面積の合計には入れる★ | D-155 項3。サマリが NULL を除く(N-3「当然 clean_timetable_id が NULL のデータも集計対象外」)のにそろえた。面積は実際に清掃した広さなので含める(ユーザー判断 2026-10-02) |
★task_area = 0 の行は1回ごとの完了率を 0%★ |
分子に 0 を足し、分母(件数)にも入れる。「実施」にも数える | ★第11回 P-5=B(D-158)/ 指示書AU 段階2★。旧: null(分子にも分母にも入れない。D-155 項1 / 指示書AS D4) |
★予定外を外している仕組みは画面によって違います★:
| 画面 | 予定外を外している仕組み |
|---|---|
| 清掃詳細ログ・ダッシュボード・アラート・エリア別完了率・レポート(当期)・連続日数・要レスキュー | ★もともと入らない★ — どれも本清掃の予定(clean_timetables の precheck = 0)の id で clean_task_reports を引くので、NULL の行は in に一致しない。latestReportByTimetableId()(lib/db/clean-log-mapping.ts:241-255)も NULL の行を Map に入れない(:247) |
★総清掃面積(/clean-area)★ |
★面積は全行の groupBy、完了率・実施回数は別に取り寄せた行のうち本清掃に紐づくものだけ★(cleanAreaStatsFromRows() lib/db/clean-area-source.ts:271-313。:292-293 の2行が判定) |
★予定は clean_timetables と clean_timetable_histories の両方から読みます★(TeamWorks 第10回 N-3「clean_timetables もしくは clean_timetable_histories の precheck が 0」/ 第11回 P-7=C。D-158 項4。指示書AU 段階3b。2026-10-05 から)。同じ id は clean_timetables 側を採ります(読み口は lib/db/timetable-source.ts の1か所。章2 の 2.4 の注)。
task_area = 0」は別物★(D-156 項1)| 清掃 | 出発したか(departed) |
1回ごとの完了率 | 実施回数 | 完了率の件数 | 未実施(出発失敗)に数えるか |
|---|---|---|---|---|---|
| レポート行が無い | false |
null |
数えない | 数えない | ★数える★ |
行があり task_area = 0 |
★true★ |
★0.0★(旧: null) |
★数える★ | ★数える★(旧: 数えない) | ★数えない★ |
行があり task_area > 0 |
true |
0.0〜100.0 | 数える | 数える | 数えない |
★未実施は completion_rate ではなく departed(レポート行の有無)で数えます★(要レスキュー R-1・連続問題施設の出発失敗日・ダッシュボードの「未実施」と「進捗」)。 completion_rate === null で数えると、レポート行がある清掃まで「出発失敗」になり、現場に誤った連絡が出るためです(D-156 項1 で却下した案C)。 不変条件は「departed === false なら completion_rate === null」です。★2026-10-05(指示書AU。P-5=B)から逆も成り立ちます★(completion_rate === null ⟺ レポート行が無い)。それでも★未実施は departed で数え続けます★(指示書AU D3)。
★ただし判定(◎○△×)と連続日数は完了率で見ます★。task_area = 0 の清掃は完了率 0% = 判定 × になり、ダッシュボードでは「失敗」、連続日数では「問題日」に入ります(isProblemCompletion() clean-log-mapping.ts:332-334)。
★未実施(結果の行が無い清掃)は完了率の分子にも分母にも入れません★(0% として混ぜない)。理由は ★稼働率で既に数えている失敗を、完了率でも二重に数えないため★です(lib/db/report-mapping.ts:436-441)。レポートの当期集計では aggregateCleanStatsByFacility()(lib/db/report-mapping.ts:460-505)が「実施と数えた行 かつ 結果行がある」ときだけ runCompletionRate() を呼び(:492)、null でなければ Σ と件数に足します(:500-501)。件数が 0 なら null(「0%で清掃した」ではなく「率を出せる清掃が1回も無い」)です。
2026-09-29 までは ★Σ清掃面積 ÷ Σ清掃対象面積★(期間の合計どうしの面積比)でした。D-151 でこれを廃止しました。ユーザー提供CSV(189行。先方投入)で、合計どうしの面積比 90.2% に対し clean_ratio 列の平均は 78.5%、個別行でも「面積比 13.8% ↔ clean_ratio 100.0%」「面積比 162.8% ↔ clean_ratio 61.0%」と相関しなかったためです(D-151 項3)。
D-155 の式は ★1回ずつ割って上限 100 で切ってから平均する★ので、合計どうしで割る旧方式とは別物です。★Σclean_area ÷ Σtask_area を「完了率」と呼ばないこと★(kpi.ts:93-96)。面積の合計そのものは総清掃面積画面の表示用に残っています。
重みに使う task_area が実清掃面積を下回る行が約7割(上の 68.3%)あるため、★加重すると面積が別経路で完了率に効いてしまう★からです(kpi.ts:77-83)。ユーザーには具体例(1,000㎡×90% と 100㎡×10% → 単純平均 50.0% △ / 面積加重 82.7% ○)を示し、「単純平均でよいです」(2026-10-01)で確定しています(D-151 項4-3)。lib/judge/rescue.ts は、要レスキュー判定の入力型 RescueRowInput(:61-86)から面積の項目をわざと外しています — ★「面積で重み付けし直す」実装を型のレベルで書けなくするため★です。
★率は加重平均できない★からです(kpi.ts:49-51)。施設ごとの平均を足して割ると「平均の平均」になり、清掃回数の違いが無視されます。そこで CleanRatioTally(kpi.ts:56-64)は cleanRatioSum(1回ごとの完了率の合計)と cleanRatioCount(件数)の2つだけを持ち、★施設・顧客・ホールディングスをまたいで合算しても平均が壊れない★形にしてあります。合算は sumCleanStatsBuckets()(lib/db/report-mapping.ts:333-339)や aggregateCleanArea()(lib/clean-area/aggregate.ts:165-248)が、割り算は meanCleanRatio() だけが行います。
名前は cleanRatioSum / cleanRatioCount(clean_ratio_sum / clean_ratio_count)のままですが、★中身は clean_ratio 列ではありません★(kpi.ts:45-47。指示書AS D7 — 名前を変えると16ファイルの機械的な差分が中身の変更に混ざり、レビューで読めなくなるため。命名した当時は暫定の式だった。★式は第11回 P-2=A で確定★)。
× 100 する共通関数 rate() を通してはいけません(kpi.ts:90-91。180/2 は 90 であって 900 ではない。lib/judge/__tests__/kpi.test.ts:94-96)Math.floor(raw * 10) / 10 と書かないこと★(上の「誤差の吸収」)clean_area = 0(task_area > 0)を null にしないこと★。「0%で実施した」が「未実施」に化けますが、判定はどちらも × なので画面もテストも赤くなりません(clean-log-mapping.ts:187-190)変えると何が起きるか: 2箇所で別々に畳むと、同じ施設がレポート画面と総清掃面積画面で違う完了率になります(kpi.ts:70-75)。★完了率を複数行から出すときは、画面を問わず runCompletionRate() → meanCleanRatio() を通してください★。
lib/judge/kpi.ts3指標はいずれも内部の共通関数 rate(分子, 分母)(kpi.ts:27-30。分母 0 なら null、それ以外は 分子 ÷ 分母 × 100)を呼ぶだけの薄い関数です。
| 指標 | 関数 | 場所 | 計算式 | 当期(アプリが集計)の分子・分母 |
|---|---|---|---|---|
| 稼働率 | operationRate(completed, planned) |
kpi.ts:36-38 |
★正常終了(status = 4)の回数 ÷ 清掃予定回数 × 100★ |
分子=最新のレポート行の status が 4 の本清掃の数(isCompletedTask()。面積は見ない)/ 分母=過去の本清掃の予定の数 |
| 出発成功率 | launchRate(started, planned) |
kpi.ts:44-46 |
★開始できた回数(レポート行がある回数)÷ 清掃予定回数 × 100。時刻の条件なし★ | 分子=レポート行がある本清掃の数 / 分母=稼働率と同じ |
| エラー発生率 | errorRate(errors, planned) |
kpi.ts:182-184 |
★エラー発生回数 ÷ 清掃予定回数 × 100。上限で切らない(100 を超えうる)★ | 分子=本清掃の予定を指す robot_error_reports の行数 / 分母=稼働率と同じ予定の数 |
★3つの式は TeamWorks のサマリ(operation_ratio / launch_ratio / error_ratio)と同じです★(第11回 P-6=A/A/B。ユーザー指示 2026-10-05「合わせてください」。D-158。指示書AU 段階3)。旧(〜2026-10-05): 稼働率 = 実施 ÷ 予定・出発成功率 = 同じ値・エラー発生率 = ÷ 実施回数。 ★帰結: 当期の稼働率は以前より低めに出ます★(例: 予定10・開始9・正常終了7 → 90% から 70%)。施設ステータスの閾値(FACILITY_STATUS_THRESHOLDS good 90 / warning 75)は★据え置き★です(ユーザー判断 2026-10-05。D-158 項8)。
当期の分子・分母を数えているのは aggregateCleanStatsByFacility()(lib/db/report-mapping.ts:460-505)、4指標に変換するのは kpiFromCleanStats()(同 :516-535)です。確定期間(先週・先月・前年)はサマリテーブルの率4列をそのまま使い、★詳細テーブルから再計算しません★(kpiFromSummaryRatios() 同 :231-241。D-102)。
★分母が 0 のときは null(0% ではない)★(kpi.ts:28)。これは意図的で、「その期間に清掃予定が1件もなかった」と「予定はあったが全部失敗して 0% だった」を区別するためです(kpi.test.ts:30-33)。0% と表示すると、全店定休日の週が「大失敗した週」に見えます。完了率(meanCleanRatio())も同じ規則で、件数 0 は null です。画面では - と出ます。
補助関数が2つあります。
| 関数 | 場所 | 役割 |
|---|---|---|
toKpiValue(value, prev) |
kpi.ts:170-176 |
前期比を組み立てる。どちらかが null なら diff も null(0 にしない)。画面は ▲▼ の行ごと出さない |
judgeFacilityStatus(稼働率) |
kpi.ts:196-203 |
良好 / 注意 / 要改善 / 判定なし。FACILITY_STATUS_THRESHOLDS(lib/config/index.ts:551-556。good 90 以上 / warning 75 以上 / それ未満は要改善)。★null は「要改善」ではなく「判定なし」★(決定1。2026-09-11 ユーザー確定)。★これは DFA 側の画面上の判断で、TeamWorks との合意事項ではない★(kpi.ts:178-187) |
★旧版の「当期は稼働率と出発成功率が同じ数字になる」は、指示書AU(2026-10-05)で解消しました★。稼働率の分子は★正常終了(status = 4)の回数★、出発成功率の分子は★開始できた回数(レポート行がある回数)★になり、★正常終了は開始の一部なので、稼働率 ≤ 出発成功率★が必ず成り立ちます(第11回 P-6=A。D-158)。 ★サンプルデータ(作り値)もこの関係を守ります★ — 施設6 の出発成功率を 65.0 / 前期 68.0 から ★75.0 / 71.0★ に直し(lib/fixtures/reports.ts)、全行で 稼働率 ≤ 出発成功率 を確かめるテストを足しました(lib/fixtures/__tests__/reports.test.ts。指示書AU §11 Q5)。
customer_summaries / facility_summaries には task_count(予定回数)・launch_count・error_count はありますが、★実施回数の列がありません★(prisma/schema.prisma:694-696 / :728-730。構造=実DDL)。率4列(operation_ratio / launch_ratio / clean_ratio / error_ratio)は実在します(:707-710 / :741-744)。
帰結は2つです(CLAUDE.md §4.7)。
operation_ratio)は、分子(正常終了の回数)が DB に無いので検算できません★lib/reports/target-customer.ts:1-37。D-133)| 指標 | サマリの分子 | サマリの分母 | 合算できるか |
|---|---|---|---|
| 清掃完了率 | ★1回ごとの完了率の合計が無い★ | ★件数が無い★ | できない |
| 出発成功率 | launch_count |
task_count(★第11回 P-6=A で確定★) |
材料はある |
| 稼働率 | ★列が無い(正常終了の回数)★ | task_count |
できない |
| エラー発生率 | error_count |
★task_count(第11回 P-6=B)★ |
材料はある |
(lib/reports/target-customer.ts:10-15 の表を写したもの。★4指標を揃えて出せない(稼働率・清掃完了率の材料が無い)ので、合算しない方針は変えません★。指示書AU §11 Q3。D-158)
分子は★本清掃の予定(precheck = 0)を指す robot_error_reports の行★だけです(errorCountByMainTimetable() lib/db/report-mapping.ts:380-392)。事前チェックの予定を指すもの・clean_timetable_id が NULL のもの・指す先の予定が無いものは数えません。根拠は TeamWorks 第8回 A7「事前チェックは本清掃を円滑に実施できるためのものなので、サマリには含めない」(D-114 項2)と、第10回の Q4-1(事前チェックのエラーは除く=A)・Q4-2(clean_timetable_id が NULL のエラーは数えない=B)です。
★制御盤・セキュリティのエラーの扱いは次フェーズです★(D-158 当方の対応5)。第11回の回答で、制御盤・セキュリティのエラーと「清掃を開始できなかった」エラーも robot_error_reports に入り(robot_id は NULL。P-8)、clean_timetable_id も入る(P-9=A)と分かりました。区分の見分け方と D-Security アラートの取得元の切り替え(P-10)は次フェーズで扱います。 ★いまの備え★: 先方の DDL では robot_id が NULL 可になった(P-8 b)が prisma/schema.prisma は NOT NULL のままなので、★robot_error_reports の読み取りはすべて select で列を絞り、robot_id / robot を読みません★(lib/db/robot-error-select.ts。指示書AU 段階0-4)。全列を読むと、NULL の行が1件入っただけで読み取りが落ちます。
変えると何が起きるか: rate() が null の代わりに 0 を返すようにすると、予定のない期間が全部「稼働率 0%・要改善」になり、実害のある施設が埋もれます。
lib/judge/escalation.ts| 関数 | 場所 | 役割 |
|---|---|---|
countConsecutiveProblemDays() |
escalation.ts:32-39 |
配列の末尾(最新日)から遡って問題が何日続いたか数える。最新日が問題なしなら 0 |
judgeEscalationLevel() |
escalation.ts:52-59 |
0日→null / 1日→L1 / 2日→L2 / 3日以上→L3。閾値は ESCALATION_THRESHOLDS(lib/config/index.ts:44-49。level2Days: 2 / level3Days: 3) |
judgeEscalation() |
escalation.ts:62-68 |
上の2つをまとめて呼ぶ入口 |
「問題あり」とは ★△以下 または 出発失敗★です(escalation.ts:22)。実際の判定は isProblemCompletion()(lib/db/clean-log-mapping.ts:332-334)で、★完了率が null(未実施)または 70 未満★を問題日にします(task_area = 0 は 0% なので 70 未満に入る)。
清掃予定が入っていない日は、連続を途切れさせません(escalation.ts:7-15。D-026)。
具体例。月・水・金だけ清掃する施設で、8/3(月)・8/5(水)・8/7(金) の3回すべてが失敗したとします。カレンダー上は5日間ですが、間の火・木はそもそも清掃予定がないので数に入れません。★スケジュール存在日の連続は3日なので L3★ です(lib/judge/__tests__/escalation.test.ts:54-70)。もし火・木を「問題なし」として配列に入れると、そこで連続が途切れて L1 に落ちます(同 :72-83 が、この誤りをアンチパターンとしてテストに固定しています)。
★「どの日が清掃予定日か」を判断するのはこの関数ではありません★(escalation.ts:13-15)。「予定日だけに絞り込み済みの配列を、古い順で」渡すのはデータ取得側の責任です。実際には次の3段で材料が作られます。
loadConsecutiveInputs()(lib/db/consecutive-source.ts:343-405)— 本清掃の予定と結果を、★表示期間より広く★読む(章5-10)foldProblemDaysByDate()(lib/db/clean-log-mapping.ts:404-416)— ★同じ施設・同じ日の複数行を1日に畳む★。1台でも問題なら、その日は問題日(OR。ユーザー確定 2026-09-11 / D-109)computeConsecutiveProblemDays()(同 :463-489)— 施設ごとに、各行の日付までの連続を数える。★分割キーは施設(facility_id)★(CLAUDE.md §4.5「同一施設で」)★表示上限★: 画面に出す連続日数は capConsecutiveDays()(lib/format/consecutive-days.ts:32-38)で上限(既定 30。システム管理画面で変更可。CONSECUTIVE_DAYS_DISPLAY lib/config/index.ts:151-158)をかけ、超えたら「{n}日以上」と出します。★L1〜L3 と R1〜R4 は上限をかける前の生値で判定します★(CLAUDE.md §4.3 と同じ考え方)。
変えると何が起きるか: 渡す配列に予定のない日を混ぜると、L3 になるべき施設が静かに L1 に見えます。エラーは出ません。
lib/judge/rescue.ts毎朝いちばんに見るべき施設を選ぶ判定で、★3条件のどれかに当たれば要レスキュー★、★当たった理由は全部返す★(1つに丸めない)作りです(judgeRescue() rescue.ts:268-296)。
| # | 条件 | 実装 | 閾値 |
|---|---|---|---|
| R-1 | 未実施が3日以上連続(=出発失敗と同義) | countNotExecutedStreak()(rescue.ts:202-209)。★未実施は departed === false で数える★(D-156 項1) |
RESCUE_THRESHOLDS.notExecutedDays = 3(lib/config/index.ts:87-90) |
| R-2 | 連続問題レベルが L3 | isEscalationL3()(rescue.ts:218-220)。★>= 3 と書き直さず judgeEscalationLevel() を呼ぶ★ |
ESCALATION_THRESHOLDS.level3Days |
| R-3 | 完了率が前回比 20% 以上の急落 | judgeCompletionDrop()(rescue.ts:246-255) |
COMPLETION_DROP.dropRatio = 0.2(lib/config/index.ts:65-68) |
lib/config/index.ts:78-82)(前回 − 当日) ÷ 前回 ≧ 0.2。前日 30% → 当日 18% は「12ポイント低下」ですが「40% 低下」で、ユーザーは後者を拾う側を選びました。★「前回」はカレンダー前日ではなく前回のスケジュール存在日★です。前回が null・当日が null・前回が 0 のときは算出しません(rescue.ts:250-252。未実施を 0% とみなさない)meanCleanRatio() を通します★(facilityDayRate() rescue.ts:135-143)。その日の★1回ごとの完了率の単純平均★で、レポート画面・総清掃面積と同じ定義です(D-155)。★以前は面積加重でした★。判定が変わるケースは lib/judge/__tests__/rescue.test.ts:548 以降の「表6」に、★R-1 を departed で数えるケース★は :219-296 の「表5(指示書AS)」に記録されていますfoldRescueDays()(rescue.ts:160-189)が1日に畳みます。★未実施フラグは OR、完了率は completion_rate が null でない行だけの合計と件数★ — 片方の台だけ未実施の日は「未実施の日」であり、かつ完了率も算出できます組み立てと並べ替えは buildRescueRequired()(lib/db/rescue-mapping.ts:105-198)です。評価日は「その施設の、指定日以前で最も新しいスケジュール存在日」、並び順は「理由の数の降順 → 連続問題日数の降順 → 施設 ID の昇順」です。★件数では切りません★(画面が最初の5件だけを見せ、「もっと表示」で全件。RESCUE_INITIAL_VISIBLE = 5 lib/config/index.ts:120)。
変えると何が起きるか: ここで連続日数を数え直すと、清掃詳細ログと食い違います(rescue.ts:38-40。D-109 の再発防止で、連続日数は引数で受け取るだけ)。
lib/judge/advice.ts| 何を直したいか | 触るファイル | 画面から編集できるか |
|---|---|---|
| どんなときに出すか(発火条件) | lib/judge/advice.ts:53-97(f2b91fc) |
できない。コード変更+テスト修正が必要 |
| 何と書くか(見出し・推定原因・推奨アクション) | lib/config/advice-rules.ts(既定値)/data/settings/advice.json(上書き) |
★できる★。システム管理の「運用設定」画面から編集 |
文言を変えてもテストは1本も壊れません(lib/judge/advice.ts:12-15)。
matchAdviceRules() は該当したものをすべて返す)| # | 発火条件(実装) | 場所 | レベル |
|---|---|---|---|
| R1 | 出発失敗 かつ 連続問題日数が 1 以下(初回のみ。2日目以降は R3/R4 に任せる) | advice.ts:64-66 |
1 |
| R2 | 完了率が null でなく、GRADE_THRESHOLDS.poor(10)未満。未出発は null なので対象外 |
advice.ts:69-71 |
1 |
| R3 | 連続問題日数 2 以上(ESCALATION_THRESHOLDS.level2Days) |
advice.ts:74-76 |
2 |
| R4 | 連続問題日数 3 以上(level3Days)。R3 と同時に出るのは意図的 |
advice.ts:79-81 |
3 |
| B1 | 完了率が null でなく、judgeGrade() が △ |
advice.ts:84-86 |
1 |
| B2 | 事前チェック1回目 or 2回目のステータスが B2_HANDLE_UP_STATUSES に一致(D-178 で文言を改めた。旧「1時間前または30分前」)。★null(その回の事前チェックの予定が無い。D-171)はどのルールにも当たらない★ |
advice.ts:89-91 |
1 |
| B3 | 同じく B3_OFFLINE_STATUSES に一致(null は当たらない) |
advice.ts:92-94 |
1 |
task_area = 0 の清掃★は出発しているので R1 は出ません。★完了率 0%(×)なので R2 が出ます★(2026-10-05 から。旧: null で R2 も B1 も出なかった)。連続が 2 日以上なら R3 / R4 も出ますlib/db/clean-log-mapping.ts:435-438。意図した結果)matchesStatus() advice.ts:105-116。null / undefined は一致しない)。レベル(1=注意 / 2=警告 / 3=重大)は lib/config/advice-rules.ts の各ルールが持ち、maxAdviceLevel()(advice.ts:118-122)が最大値を取って、清掃詳細ログの展開部分の見出し(「レベル3 — DFA介入を検討」など。components/features/detail-log/CleanLogTable.tsx:41-61)を決めます★B3 は 2026-10-05 時点でも空配列のままです★(lib/config/robot-status-match.ts:46。確認済み)。★どんな値を渡しても絶対に発火しません★ — オフラインを表す値が実データで未判明のため、推測で埋めない方針です(robot-status-match.ts:37-45)。B2 の ["Handrail Lifted"](:35)も★暫定値★で、受領済みの error_type 24値のリストには含まれていません(:28-34)。
★B2 の根拠は、エラーコードが決まったら本清掃のエラー内容に切り替えます★(D-174。2026-10-07 ユーザー了承「その進め方でいいです」。きっかけは D-171 項3)。本清掃時にハンドルアップしていれば清掃開始失敗として robot_error_reports のエラー内容に載る、というユーザーの見立てによります。いまは事前チェックの一時的なハンドルアップでも出ます。★それまでは今のまま★(事前チェックの状態を見る。予定が無い回は null で当たらない。△× の行を開いたときだけ出て、判定には影響しない)。エラーコード(error_type の値)はユーザーが情報収集中です。
変えると何が起きるか: B3_OFFLINE_STATUSES に値を1つ足せば B3 が復活します。同時に lib/judge/__tests__/advice.test.ts:211-221 のテストが落ちますが、★「落ちたら値を足した合図であり、バグではない」★ とテスト内に明記されています。
lib/dashboard/aggregate.ts(123行)日次のダッシュボード専用の集計で、こちらも純粋関数のみです。★lib/judge/kpi.ts は使いません★(aggregate.ts:6-8)。kpi.ts は週次/月次/年次の集計期間の KPI、こちらは日次の件数カウントで、混ぜると分母の意味が変わるためです。
| 関数 | 場所 | 役割 |
|---|---|---|
computeSummary() |
aggregate.ts:41-76(a961829。★ファイルは D-173 で140行★) |
成功 / 失敗 / 未実施 / ★結果待ち(pending)★ / 成功率。★結果待ちの行は departed より先に見て pending に数え、他の3つにも成功率の分母にも入れない★(D-173。:49-52)。★未出発(departed === false)の行は判定を見ずに未実施に入れて打ち切る★(二重計上を防ぐため)。成功率の分母は3つの合計で、0 なら null |
computeScheduleProgress() |
aggregate.ts:101-120 |
スケジュール進捗。planned は★清掃ログではなくスケジュールマスタ(clean_schedules)由来★(:107-111)。ログの行数を使うと「本当に定休日」と「収集処理の障害」が区別できない。completed は「完了」ではなく出発した件数(:112-113。★結果待ちの行は出発していないので数えない。D-173 で変えていない★) |
computeSecurityBanner() |
aggregate.ts:130-140 |
セキュリティ復帰漏れバナー。施設名・エリア名・検知時刻の3つだけ返す(自由記述は載せない) |
成功/失敗の分類は SUCCESS_GRADES = ["◎","○"](aggregate.ts:26)と FAILURE_GRADES = ["△","×"](:28)という定数で決まります。★SUCCESS_GRADES は D-171 で export になり、アラートの main_run_succeeded(mainRunSucceededFromReport() lib/db/precheck-mapping.ts:283)も同じ定数で「本清掃は成功」を決めます★(別の場所に同じ配列を書かない)。★出発した task_area = 0 の清掃は判定 × なので「失敗」に入ります★。
変えると何が起きるか: SUCCESS_GRADES に △ を足すとダッシュボードの成功率が跳ね上がりますが、清掃詳細ログのバッジは △ のままなので、同じ日のデータが画面ごとに食い違います。★D-171 以降はアラートの「本清掃は成功」(main_run_succeeded)も一緒に変わります★。★status_1h / status_30m を computeSummary() の条件に足すと D-170 に反し、歯止めのテストが赤くなります★。
ユーザー指示「事前チェックを行っていない清掃や事前チェックが失敗した清掃でも本清掃が成功していれば成功にしてください」。★調べた結果、すでにそうなっていました★(コードは変えず、歯止めのテストだけ足した)。
| 決めるもの | 見ているもの | 場所 |
|---|---|---|
| 成功 / 失敗 / 未実施(ダッシュボード) | ★departed(本清掃のレポート行の有無)と grade(完了率の判定)だけ★ |
computeSummary()(aggregate.ts:41-76)。★結果待ち(D-173)は旗 result_pending で別に数える(5.9b)★ |
| 出発したか(未実施=出発失敗) | 本清掃(precheck = 0)に対応する clean_task_reports の行があるか |
notExecutedFromReport()(lib/db/clean-log-mapping.ts:165) |
| 連続日数・要レスキュー・レポートの KPI | 同じく departed と完了率 |
章5-4〜5-7 |
status_1h / status_30m(1h前ST / 30分前ST)は表示だけの列★です。★事前チェックで異常が出ていても、予定が無くても、本清掃が ◎○ なら「成功」★。D-048(status_1h で失敗を分類しない)と同じ考え方ですlib/dashboard/__tests__/aggregate.test.ts:141-159(test.each の4件。事前チェックで異常 EmergencyStop / Offline・1h前だけ異常・予定はあり異常なし Standby.・★予定が無い null★ でも ◎○ は success)。★4件目(null)はコミット 8a657a2 で足しました★(それまでは「予定が無い(実DBでは Standby. になる)」という D-171 前の名前の1件だった)。結果待ち(D-173)のテストは同じファイルの :161-199★何分前の事前チェックを画面に出すか★(D-169。2026-10-07): ★D-178(2026-10-16)で解消★: 清掃詳細ログの事前チェック2列は、設定 PRECHECK_MINUTES_BEFORE との一致ではなく★分数の大きい順(早い方が1回目)★で割り当てるので、★ステージングの本社の 30分前・10分前も 1回目 30・2回目 10 として出ます★(何分前かは status_1h_minutes / status_30m_minutes)。★旧(D-169 時点): 60/30 と一致しない回は出ず「-」★。★アラート画面・エラー一覧は設定に無い分数(10分前など)も生の分数で出します★(D-105 項1・指示書AW D4)。表示する回を設定値で選べるようにするのは後で(ユーザー判断「このままで問題ない」)。
★改訂4で追加★(D-173。2026-10-07。実装 08ba9e9、CLAUDE.md §4.4 の注記 a961829)。行番号は a961829。
きっかけ: レポート(clean_task_reports の行)は清掃の後 30 分より遅れて届くことがあります(ユーザー回答 2026-10-07。D-172)。それまでは、ダッシュボードの直近の清掃(終了+30分で確定。D-159)以外のすべての経路で、★予定の開始時刻を過ぎた瞬間から★レポートの無い清掃を出発失敗として数えていました(清掃中も含む)。 ユーザー判断は「B」 — 一定時間はレポートが無くても「結果待ち」と出し、失敗に数えない。★待つ時間は一旦2時間・設定で変えられるように・全画面で揃える・ダッシュボードに「結果待ち」カードを足す★。
結果待ち = 本清掃のレポートが無く、予定の開始 ≤ 現在 < 予定の終了(無ければ開始)+ RESULT_WAIT_MINUTES(120分)(清掃中も含む。境界ちょうどは確定=結果待ちではない)。過ぎたら今までどおり出発失敗です。
| 何を | どこで |
|---|---|
| 判定 | ★isResultPending()(lib/clean-logs/result-pending.ts:22)★ — 純粋関数。期待値表は lib/clean-logs/__tests__/result-pending.test.ts(表1)。★Date は真の瞬間で渡す★(DB の値は fromDbJst() を通してから) |
| 待つ時間 | RESULT_WAIT_MINUTES = 120(lib/config/index.ts:205)。★LAUNCH_GRACE_MINUTES(30。未使用)とは別物で、流用しない★ |
| API の旗 | CleanLogItem.result_pending: boolean(lib/types/index.ts:288。契約は d-watch-api.yaml:2006)。★departed / task_status / completion_rate / grade の値は変えない★(結果待ちでも departed: false・grade: "×" のまま)— ★旗を見ない古い処理は安全側=出発失敗のまま★になる |
| 旗を詰める | Prisma 版 cleanLogs()(lib/db/index.ts:1141。clean_timetables.end_at を読む)。★サンプルデータは旗を直接書く★(時刻で決めない。施設11 川下店の今日 03:30 の1行だけ。lib/fixtures/clean-logs.ts:289-300。スケジュール id 34 を足した lib/fixtures/schedules.ts) |
★D-091 項1(当方で「予定+猶予」を計算しない)は、この件に限りユーザー判断で上書き★しました。D-091 の懸念(設定を変えると過去のログの判定まで変わる)は、待つ時間を過ぎた清掃には効かないので、影響は直近2時間に限られます。★第12回(★R-2★。04f974a で送付版)で、レポートが清掃の終了から最大でどれくらい遅れるかを聞きます★(RESULT_WAIT_MINUTES の見直し)。
| 画面・集計 | 結果待ちの扱い | 場所 |
|---|---|---|
| 清掃詳細ログの1行 | 出発「…」(--tx-m。✓✗ の色を使わない)・清掃結果「結果待ち」・完了率「-」・判定なし・★行を開けない★ |
★見え方は cleanLogRowView()(lib/format/clean-result.ts:98)の1か所★。表は CleanLogTable.tsx:209-273、文言は CLEAN_RESULT_PENDING_LABEL(clean-result.ts:73。EN "Pending") |
| 清掃詳細ログの絞り込み | ★「問題あり(△×)」「×のみ」から外す★(「全件」には出る) | filterCleanLogs()(lib/clean-logs/query.ts:182) |
| 清掃詳細ログの CSV | 出発・清掃結果が「結果待ち」、★完了率・判定は空のセル★ | buildCleanLogCsv()(lib/clean-logs/csv.ts:57-85)。★画面と同じ cleanLogRowView() を使う★ |
| ダッシュボードの数値カード | ★「結果待ち」カード★に数える(成功・失敗・未実施に入れない・★成功率の分母に入れない★) | computeSummary()(lib/dashboard/aggregate.ts:41-76)、カードは DashboardScreen.tsx:265-273(StatCard の中立色 mt) |
| ダッシュボードの直近の清掃 | ★候補は「予定の終了 ≤ 現在」★(★settleMinutes(終了+30分)は廃止★)。結果待ちも選ぶ |
pickLatestRunPerSection()(lib/dashboard/latest-run.ts:67。条件は :97) |
| ダッシュボードのコンパクト版の表 | ★出さない★ | app/api/dashboard/route.ts:203 |
| アラートの「出発失敗」 | ★出さない★(確定した出発失敗だけ出す) | Prisma 版 prechecks()(lib/db/index.ts:1588) |
| 連続問題日数(詳細ログ・要レスキュー・連続問題施設) | ★未来の予定と同じく入力から外す★(問題日にも非問題日にも数えない) | consecutiveInputsFromRows()(lib/db/consecutive-source.ts:268。end_at を読むようになった。約1.3MB 増)。サンプルデータ側は lib/db/rescue-mapping.ts:61 |
| レポートの当期の率 | ★未来の予定と同じく数えない★(予定回数にも入れない。施設のバケットは作る) | aggregateCleanStatsByFacility()(lib/db/report-mapping.ts:531。本清掃の型は CleanStatsTimetableLike :376) |
| ★変えないもの★ | スケジュール進捗(結果待ちは出発していないので「完了」に数えない)・main_run_succeeded(レポート行が無ければ false)・清掃面積(実績だけを数える) |
— |
変えると何が起きるか: RESULT_WAIT_MINUTES を延ばすと、確定した出発失敗が画面に出るまでが遅れます(アラートの出発失敗・ダッシュボードの未実施も同じだけ遅れる)。縮めると、レポートが遅れた清掃が一時的に出発失敗に見えます。★旗を見ずに departed や grade だけで「失敗」を数える処理を新しく書くと、結果待ちが失敗に混ざります★(例外もテスト失敗も出ない)。新しい集計を作るときは isResultPending() か result_pending を必ず見てください。
lib/db/ ほか判定の本体は lib/judge/ ですが、★「どの行を数えるか」「何日ぶん読むか」は次のファイルで決まります★。どれも「DB から読んだ行を、判定が使える形に整える」係です。数字が合わないときは、判定よりもこちらを先に疑ってください。
| ファイル | 行数 | 役割 | 主な関数 |
|---|---|---|---|
lib/db/clean-log-mapping.ts |
489 | 清掃1回ぶんの★未実施・完了率・連続日数★の規則 | notExecutedFromReport() :165-169 / completionRateFromReport() :203-210 / latestReportByTimetableId() :241-255 / isFutureSchedule() :308-310 / isProblemCompletion() :332-334 / computeConsecutiveProblemDays() :463-489 |
lib/db/report-mapping.ts |
971 | レポート(KPI・施設別・連続問題・エラー内訳)の組み立て | 下の表 |
lib/db/consecutive-source.ts |
405 | ★連続日数の入力を作る唯一の場所★ | consecutiveLookbackCalendarDays() :175-180 / consecutiveInputsFromRows() :235-265 / loadConsecutiveInputs() :343-405 |
lib/db/rescue-mapping.ts |
198 | 要レスキューの組み立て・並べ替え | buildRescueRequired() :105-198 |
lib/db/clean-area-source.ts |
498 | 総清掃面積の材料(面積・実施回数・完了率の Σ と件数) | cleanAreaStatsFromRows() :271-313 / loadCleanAreaFacilityStatsWithRowCount() :363-412 / countDuplicatedTimetables() :441-454 |
lib/clean-area/aggregate.ts |
264 | 総清掃面積のロールアップ(店舗 / 顧客 / ホールディングス) | aggregateCleanArea() :165-248 |
lib/clean-logs/areas.ts |
171 | エリア別完了率(行の並べ替え・全体率) | overallCompletionTally() :101-112 / buildAreaCompletions() :145-171 |
lib/dashboard/aggregate.ts |
123 | ダッシュボードの件数(章5-9) | — |
★lib/dashboard/latest-run.ts★(新・10/06) |
110 | ★ダッシュボードの数値カード・表に「どの清掃を出すか」★(清掃エリアごとの直近1件。D-159) | latestRunWindow() :35(DB から読む日付範囲。今日を含む8日)/ pickLatestRunPerSection() :64(★行とメタの突き合わせに失敗したら例外★) |
lib/db/report-mapping.ts — レポートの組み立て| 関数 | 場所 | 役割 |
|---|---|---|
periodOfSummaryRow() |
:100-104 |
サマリ行が週次か月次か★年次★かを決める |
customerSummariesByPeriodKey() |
:185-214 |
サマリ行を期間キーの表にする。★異常を見つけたら止まる★(下記) |
kpiFromSummaryRatios() |
:231-241 |
★確定期間★の KPI。DB の率4列をそのまま使い、★詳細テーブルから再計算しない★ |
aggregateCleanStatsByFacility() |
:460-505 |
★当期★(進行中の週・月・年)を詳細テーブルからその場集計する |
kpiFromCleanStats() |
:516-535 |
当期のバケットを4指標に変換。完了率だけ meanCleanRatio() を通す |
errorCountByMainTimetable() |
:380-392 |
エラー発生率の分子(本清掃の予定を指すエラーだけ) |
continuousProblemsInPeriod() |
:769-802 |
連続問題施設の1期間ぶん |
errorBreakdownItemsFromCounts() / errorBreakdownRowsFromSummary() / countMainErrorTypesInRange() |
:885-895 / :906-931 / :955-971 |
エラー種別内訳(確定期間はサマリの合算、当期は詳細テーブルの数え上げ) |
★periodOfSummaryRow() は month も見ること★(:100-104)。week === 0 だけで判定すると ★年次行(month=0 かつ week=0)が「月次」に化けて混入★し、重複検知で 500 になります。エラー内訳側は★年次の件数を月次に足し込んだまま黙って通る★ので、気づけるとは限りません。
★顧客が混ざっていたら例外で止まります★(:185-214)。「レポートの KPI は運用担当が判断に使う数字。別会社の数字で判断されるより、画面が出ないほうがまし」というユーザー判断(D-133)です。同じ期間キーが2行来た場合も止まります(サマリに一意制約が無く、後勝ちで不定になるため)。
lib/db/consecutive-source.ts — ★連続日数の入力を作る唯一の場所★連続日数は「その施設を過去に遡って数える」ので、★表示したい期間だけを読むと連続が途切れます★(9/8× 9/9× 9/10× を「9/10〜9/10」で引くと1日=L1 に化ける)。そこで★ここだけ、表示期間より広く読みます★。
(表示上限 + 1) × 7 カレンダー日(consecutiveLookbackCalendarDays() :175-180 / CONSECUTIVE_LOOKBACK lib/config/index.ts:185-190)。★1スケジュール存在日=最大7カレンダー日★として安全側に換算しています。★月1回しか清掃しない施設が出ると遡りが足りず、連続日数が実際より小さく出ます★(例外は出ない)consecutiveInputsFromRows() :235-265)★知っておくべき挙動(直すものではない)★: clean_task_reports.clean_timetable_id が NULL の行(予定外のタスク)は、予定の id の in に一致しないので、ここには返って来ません(:211-226 / :373-377)。予定外なので、どの予定の結果でもなく、完了率・実施回数に入らないのが正しい扱いです(D-155 項3)。ただし、予定された清掃の結果が何かの理由で NULL のまま届くと、その予定は「レポート行が無い」=★未実施に化けます★。例外もテスト失敗も出ないまま稼働率だけが静かに下がるので、未実施が多すぎるときは NULL の行数を疑ってください。
変えると何が起きるか: isFutureSchedule() の除外を外すと、まだ来ていない1週間ぶんの予定(clean_timetables には1週間分がまとめて登録される。D-106)が全部「出発失敗」になり、★全施設が常時 L3★ になります(D-106 / D-107 / D-110)。
動作には影響しませんが、読んだ人が古い前提を信じる恐れがあるものです。★直すのは本書の範囲外★なので、場所だけ記録します。
| 場所 | 古い記述 | いまの実態 |
|---|---|---|
lib/db/clean-area-source.ts:425 |
「clean_timetable_id は★TeamWorks がまだ埋めていない★(今後埋める前提)」 |
NULL は「予定外のタスク」で、埋める想定ではない(D-152 項3-3 / 第10回 N-1) |
lib/fixtures/clean-task-reports.ts:24 |
同上「今後 TeamWorks が埋める前提」 | 同上 |
lib/db/report-mapping.ts:249 |
「clean_timetables.started_at は常に予定時刻と同値でシード」 |
clean_timetables に started_at 列は無い(start_at)。当期の稼働率と出発成功率が同じ分子になる理由(定刻より遅れた出発を区別する列が無い)そのものは正しい |
lib/db/report-mapping.ts:278-280 |
「clean_error が 2 / 3 の清掃は robot_error_reports の行が作られない」 |
clean_error は旧取得元(clean_status_histories)の列で、いまは読んでいない。制御盤・セキュリティのエラーの入れ方は第10回 N-5 で方針が出て DDL 変更待ち |
lib/dashboard/__tests__/aggregate.test.ts:1438a657a2)★ |
|
★コミット 8a657a2(2026-10-07)で「予定はあり異常なし」(Standby.)と「予定が無い(null)」(null)の2件に分けた★(D-171 後の挙動に合わせた。5.9) |
古いコメントではないもの: 清掃詳細ログの凡例「(閾値はシステム設定で変更可能)」(
CleanLogTable.tsx:172)は、いまのシステム管理画面では閾値が「編集不可」なので食い違って見えますが、モックの文言どおり(d-watch-mock.jsx:312)で、閾値の編集は F-10 で P1 に実装する予定です(docs/spec.md:114-119、lib/config/index.ts:8)。連続日数の上限で作法を確定させてから広げる、という段階的な進め方の途中です(2026-09-13 ユーザー判断 A案 / 指示書AF §H-1)。
docs/architecture.md の「7. 触ってはいけないもの」に、lib/judge/** とそのテスト、および lib/dashboard/aggregate.ts とそのテストが挙げられています(docs/architecture.md:175-176)。理由は単純で、★ここは間違えても壊れたように見えない場所★だからです。画面の色がおかしければ誰でも気づきますが、> を >= に1文字変えただけの間違いは、エラーも出さず動作も止めず、ただ全施設の評価を静かに1段階ずらします。清掃が実際にどうだったかを知る手段は画面しかないので、間違ったまま数か月運用されても誰も気づけません。
だから直す順番が決まっています ── ★まず期待値の表(テスト)を直し、それから実装を直す★(CLAUDE.md §0-2 / §6-2 / §9.4、lib/judge/index.ts:7-10)。テストを後回しにすると、テストは「実装が正しいこと」ではなく「実装が今そうなっていること」を確認するだけの飾りになります。★期待値表の確定は分担しません★(CLAUDE.md §0-2)。
D-139(境界が「以上」になった)も D-151(完了率が単純平均になった)も D-155(完了率が1回ごとの完了率の平均になった)も、★この順番で進めたので挙動の変化がテストに記録されています★。
| 変化 | 記録しているテスト |
|---|---|
| 90.0 が ◎、70.0 が ○ に(D-139) | lib/judge/__tests__/grade.test.ts:26 / :30 |
| 70.0 で B1 が出なくなった(D-139) | lib/judge/__tests__/advice.test.ts:150-155 |
| 面積加重と単純平均で R-3 の結果が変わる(D-151) | lib/judge/__tests__/rescue.test.ts:548 以降(表6) |
★1回ごとの完了率の式・切り捨て・誤差の吸収・task_area = 0(D-155 / D-158)★ |
★lib/judge/__tests__/run-completion-rate.test.ts(表1・表2。指示書AS 段階1 で実装より先に「落ちるテスト」として入れた。★R9・R12(task_area = 0 は 0.0)は指示書AU 段階1★)★ |
★R-1 の未実施を departed で数える(D-156 項1)★ |
★lib/judge/__tests__/rescue.test.ts:219-296(表5(指示書AS))★ |
閾値そのもの(90 / 70 / 10、猶予30分、L2=2日 / L3=3日)は TeamWorks との合意事項です。変えたくなったら docs/decisions.md を確認し、必要なら合意を取り直してください(CLAUDE.md §4 冒頭 / §7)。
この章は、画面に出る数字を1つずつ取り上げて、次の5つを表にしたものです。
prisma/schema.prisma で確認したもの)ファイル:行)判定の考え方そのもの(◎○△× の境界・L1〜L3 など)は章5に書いてあります。この章は「画面のこの数字はどこから来たのか」を引くための辞書です。
読み方の約束
clean_task_reports.clean_area =「清掃実績」表の「清掃した面積」列)。列の意味が分からないときは章13の用語対応表を見てくださいjstIsoFromDb()(lib/format/date.ts:96-98)で「DB の生の値 = 画面の時刻」になるように読みます。★生の値に9時間を足しません★。DB_DATETIME_OFFSET_MINUTES = 9 * 60(lib/config/index.ts:211)は、読み取り(fromDbJst() date.ts:67-69)と書き込み(toDbJst() :82-84)で同じ値を引いて足すための設定で、★表示の時刻は DB の生の値のまま★になります(date.ts:89-95)displayRate() lib/judge/grade.ts:43-46)。値が無い(null)ときは - です。★判定は丸める前の生値で行います★(章5-3)formatNumber() lib/format/number.ts:46-52。341635.4 → 341,635)。★面積の単位は AREA_UNIT = "㎡"★(lib/config/index.ts:804)。★単位は DFA の判断で、TeamWorks の確認は取れていません★(第9回 5章で照会中。config/index.ts:792-803)clean_timetables.precheck = 0 の予定(CLEAN_TIMETABLE_PRECHECK.main lib/config/index.ts:243-246)。precheck が 60 / 30 などの行は「何分前の事前チェック」の予定で、清掃そのものではありません(TeamWorks 第7回 A5-2。D-112)clean_task_reports の行が無いこと★(notExecutedFromReport() lib/db/clean-log-mapping.ts:165-169。ユーザー確認 2026-09-30。指示書AQ §1-1)。★まだ予定時刻が来ていない予定は、どの画面でも数えません★(isFutureSchedule() clean-log-mapping.ts:308-310。clean_timetables には1週間分の予定がまとめて登録されるため。D-106)USE_FIXTURES が "false" 以外)のとき★は、DB ではなく lib/fixtures/ の値が出ます。判定(lib/judge/)は同じものが走りますが、★材料の値の出どころは変わります★。画面ごとの違いは各表の下に書きました清掃完了率は、清掃詳細ログ・エリア別完了率・レポート(当期)・施設別パフォーマンス(当期)・総清掃面積・要レスキューの★全画面で同じ式★です(D-155)。ただし★どの行を集めるか★は画面によって少しずつ違います(5B-12)。
flowchart TD
S["本清掃の予定<br/>clean_timetables + clean_timetable_histories<br/>(precheck = 0。同じ id は本表)"] --> T{"予定時刻を<br/>過ぎているか"}
T -- "いいえ" --> T0["数えない<br/>(まだ来ていない予定)"]
T -- "はい" --> U{"この予定を指す<br/>clean_task_reports の行があるか"}
U -- "無い" --> U0["未実施(=出発失敗)<br/>完了率 null・判定 ×<br/>完了率の分子にも分母にも入れない"]
U -- "ある" --> M{"2行以上あるか"}
M -- "はい" --> M1["ended_at(無ければ started_at)が<br/>最新の1行だけを採る<br/>※総清掃面積だけは全行を数える"]
M -- "いいえ" --> E
M1 --> E{"task_area = 0 か"}
E -- "はい" --> E0["1回ごとの完了率 = 0.0<br/>実施回数にも完了率の件数にも数える<br/>(第11回 P-5=B。旧: null)"]
E0 --> I
E -- "いいえ" --> F["raw = min(clean_area ÷ task_area × 100, 100)"]
F --> G["小数第6位で一度四捨五入<br/>(浮動小数の誤差を吸収)"]
G --> H["小数第1位まで切り捨て<br/>= 1回ごとの完了率<br/>runCompletionRate()"]
H --> I["Σ(合計)と件数に足す"]
I --> J["単純平均 = Σ ÷ 件数<br/>meanCleanRatio()<br/>件数 0 なら null"]
J --> K["◎○△× の判定<br/>judgeGrade()<br/>★丸めない生値で★"]
J --> L["表示<br/>displayRate()<br/>★整数に四捨五入するのはここだけ★"]
R["clean_task_reports の行<br/>(実績側から見た場合)"] --> N{"clean_timetable_id は NULL か"}
N -- "はい(予定外のタスク)" --> N0["完了率・実施回数に入れない<br/>★総清掃面積の面積の合計にだけ入れる★"]
N -- "いいえ" --> P{"指す予定は<br/>precheck = 0 か"}
P -- "いいえ(事前チェックの予定・<br/>予定が見つからない)" --> P0["完了率・実施回数に入れない<br/>(総清掃面積の面積の合計には入る)"]
P -- "はい" --> M
| 段 | 何をしているか | 関数 ファイル:行 |
根拠 |
|---|---|---|---|
| 本清掃だけ | precheck = 0 の予定だけを集める |
各取得関数の where: { precheck: CLEAN_TIMETABLE_PRECHECK.main }(lib/db/index.ts:768 他) |
TeamWorks 第10回 N-3「事前確認の結果は含めない。precheck が0のデータのみ集計対象」 |
| 予定外を外す | clean_timetable_id が NULL の行は予定の id に一致しないので自然に落ちる。latestReportByTimetableId() も NULL を捨てる |
lib/db/clean-log-mapping.ts:247 |
D-155 項3(ユーザー判断 2026-10-02)。サマリが NULL を除く(N-3「当然 clean_timetable_id が NULL のデータも集計対象外」)のにそろえた |
| 1行に絞る | 同じ予定に複数行あれば ended_at(無ければ started_at)が最新の1行 |
latestReportByTimetableId() clean-log-mapping.ts:241-255 / reportOrderKey() :266-268 |
指示書AQ §1-4。★1予定に何行できるかは第10回 N-8 で「A(想定どおり)」の回答★ |
| 1回ごとの完了率 | min(clean_area ÷ task_area × 100, 100) を小数第1位まで切り捨て |
runCompletionRate() lib/judge/kpi.ts:144-153 / RUN_COMPLETION_RATE lib/config/index.ts:37-42 |
★D-155 項1(ユーザー判断「一旦B」2026-10-02)★。TeamWorks 第10回 N-3 のサマリの式「100より大きい値は100、小数点第2位以下切捨て」と同じにした。★第11回 P-2=A で確定・task_area = 0 は 0%(P-5=B)。D-158★ |
| 平均 | Σ ÷ 件数(清掃1回=1票) | meanCleanRatio() kpi.ts:101-104 |
D-151 項4-3(ユーザー回答 2026-10-01「単純平均でよいです」) |
| 判定 | ◎ 90 以上 / ○ 70 以上 / △ 10 以上 / × 10 未満・未実施 | judgeGrade() lib/judge/grade.ts:29-35 |
D-139(ユーザー指示 2026-09-16) |
| 表示 | 整数に四捨五入 | displayRate() grade.ts:43-46 |
CLAUDE.md §4.3 |
アプリが実際に読んでいるテーブルだけを並べました(lib/db/ 内の Prisma の問い合わせを全件検索して確認。2026-10-05)。
| テーブル(日本語の意味) | 取っている列 | 何の数字になるか | 読んでいる画面 |
|---|---|---|---|
clean_timetables(清掃の予定表) |
id / facility_id / section_id / parent_id / start_at / ★end_at(結果待ちの判定。D-173)★ / precheck |
予定回数・予定時刻・日付・「何分前のチェックか」。未実施を見つける起点 | 清掃詳細ログ / ダッシュボード / アラート / エリア別完了率 / レポート(当期・連続問題施設) / 要レスキュー / 総清掃面積(本清掃かの確認だけ) |
clean_task_reports(清掃の実績) |
clean_timetable_id / clean_area / task_area / status / started_at / ended_at / facility_id |
★1回ごとの完了率・面積・清掃結果・実施したか★ | 上と同じ全画面 / ★総清掃面積★ |
robot_error_reports(ロボットのエラー) |
error_id / clean_timetable_id / error_type / errored_at / created_at / status / comment / responded_at / resolved_at / facility_id / serial_number |
事前チェックの状態・アラート・エラー発生率の分子・エラー内訳(当期)。★4列だけ書き込む★ | 清掃詳細ログ(1h前ST / 30分前ST)/ アラート / レポート(当期) |
customer_summaries(顧客ごとのサマリ) |
operation_ratio / launch_ratio / clean_ratio / error_ratio / year / month / week / customer_id |
★確定期間の KPI 4種と、当期の前期比★ | レポート(KPI) |
facility_summaries(施設ごとのサマリ) |
同じ率4列 / year / month / week / facility_id |
★確定期間の施設別パフォーマンスと、当期の前期比★ | レポート(施設別パフォーマンス) |
facility_error_summaries(施設ごとのエラー件数サマリ) |
error_type / error_count / year / month / week / facility_id |
★確定期間のエラー種別内訳★ | レポート(エラー種別の内訳) |
clean_schedules(清掃スケジュールのマスタ) |
weeks / start_time / end_time / inactive / deleted_at |
★ダッシュボードの「予定」件数★・スケジュール一覧 | ダッシュボード(進捗)/ スケジュール |
facilities(施設マスタ) |
facility_name / contract / prefecture_name / addr1 / addr2 / phone / manager_name / charger_name / email / customer_id / deleted_at |
施設名・契約(監視 / D-Sec)・所属 | 全画面 |
sections(清掃エリア) |
section_name |
エリア名 | エリア別完了率 / D-Secアラート |
robots / devices(ロボット・機種) |
robots.serial_number / devices.maker_id / devices.model_name |
シリアル・機種名 | 清掃詳細ログ / アラート / 施設ロボットマスタ / スケジュール / 施設別パフォーマンス |
customers / groups(顧客・ホールディングス) |
customer_name / group_id / group_name |
絞り込みの選択肢・総清掃面積のまとめ方 | 全画面の絞り込み / 総清掃面積 |
controls(D-S制御盤) |
serial_number / status / facility_id / deleted_at |
接続状態・台数 | D-Secログ |
control_reports(制御盤の生死報告) |
control_id / reported_at |
最終通信の時刻(★制御盤ごとの最大値★) | D-Secログ |
control_error_histories(制御盤の異常の履歴) |
occurred_at / status / comment / responded_at / resolved_at |
D-Secアラートの件数・検知時刻 | D-Secアラート / ダッシュボード(バナー) |
★読んでいないテーブル★: clean_status_histories(D-151 で取得元を clean_task_reports に一本化)、customer_error_summaries(エラー内訳は施設版で確定。D-102 項4)、clean_statuses / security_statuses / security_status_histories など。★clean_timetable_histories は 2026-10-05 から読みます★(予定の履歴。D-158 項4)。★customers.contract(削除依頼済み)と clean_schedules.contract(用途が違う。第10回 N-13)も読みません★(CLAUDE.md §4.6)。
★サマリテーブルの面積(*_summaries.task_area / clean_area)は読みません★。総清掃面積の画面も常に clean_task_reports から集計します(lib/db/clean-area-source.ts:35-38)。コードのコメントには「実データで task_area が全行 1000 固定で、期間内の合計になっていない」とあります(同所・lib/config/index.ts:797)が、★いつ・どのデータで見たかはコードに書かれていません★(第9回 Q2-1 で照会中)。
| 暫定のもの | 影響する画面・数字 | いまの扱い | 決まったら何が変わるか | 照会先 |
|---|---|---|---|---|
|
|
完了率を出す全画面・◎○△× の判定・ダッシュボードの成功 / 失敗・連続日数・要レスキュー R-3 | 1回ごとの面積比(上限100・小数第1位切り捨て)の単純平均(D-155) | ★第11回 P-2=A「今の式のまま」で確定(D-158)★。P-1: 訂正後の clean_ratio 列も同じ式。訂正の時期は無回答 |
第11回 P-1・P-2(回答済み) |
task_area = 0 の行 |
完了率・判定(× になる) | ★1回ごとの完了率を 0%★(指示書AU 段階2。旧: null) |
★第11回 P-5=B で確定(D-158)★ | 第11回 P-5(回答済み) |
清掃結果 status の表に無い値(CSV実測の 6) |
清掃詳細ログの「清掃結果」列・CSV | 数字のまま表示(cleanTaskStatusLabel() lib/config/index.ts:384-386) |
★6 は Pudu の値で、先方も意味が分からない★(第11回 P-4=A。D-158)。分かれば先方がデータを入れる。そのとき表示名を足す | 第11回 P-4(回答済み・意味は未確定) |
|
|
完了率 0.0 → 判定 × | そのまま 0%(clean_area = 0 は「0%で実施」) |
★第11回 P-3=A「清掃せずに終わった。0% でよい」(D-158)★。稼働率には正常終了として入る(指示書AU 表2 K8) | 第11回 P-3(回答済み) |
★中断・キャンセル・異常終了(status 2 / 3 / 5)★ |
判定 | ★判定には使わない★。完了率で ◎○△× を出す(D-155 項2) | CLAUDE.md §4.4「中断で返る=失敗」を実データで見直す | ユーザー判断待ち(実データ待ち) |
|
|
レポート(KPI・施設別)の当期 | ★サマリと同じ式★(正常終了 ÷ 予定 / 開始 ÷ 予定 / エラー ÷ 予定。指示書AU 段階3) | ★第11回 P-6=A/A/B で確定(D-158)★ | 第11回 P-6(回答済み) |
| エラー発生率・エラー内訳の分子 | レポート | 本清掃の予定を指す robot_error_reports の行だけ |
制御盤・セキュリティ・清掃開始失敗のエラーも robot_error_reports に入る(第11回 P-8〜P-10)。★区分の見分けと数え方は次フェーズ★(D-158 当方の対応5) |
第11回 P-8〜P-10(回答済み・対応は次フェーズ) |
面積の単位 ㎡ |
総清掃面積 | DFA の判断(ユーザー指示 2026-09-16「単位は㎡でよいです」) | TeamWorks の回答が違えば AREA_UNIT の1行を直す |
第9回 5章 |
clean_timetable_histories からも引くか |
予定を起点にする全画面の母集団 | ★clean_timetables と clean_timetable_histories の両方を読む。同じ id は本表を採る★(lib/db/timetable-source.ts。指示書AU 段階3b) |
★第11回 P-7=C・D-158 項4★。予定がいつ履歴に移るかは未確認(次回の確認事項の候補。D-158 項7) | 第11回 P-7(回答済み) |
| 「本番」の時刻 | 全画面の時刻 | DB の生の値 = 画面の時刻(ステージングで JST に統一済みと先方が申告。当方の再実測は未了) | ★第11回 P-11=A「本番DBも JST」・P-12=A「既存行も JST に直した」(D-158)★。当方の再実測は未了(ステージングに接続できない) | 第11回 P-11・P-12(回答済み・再実測待ち) |
| 施設ステータスの閾値(良好 90 / 注意 75) | 施設別パフォーマンスのステータス・セル色 | FACILITY_STATUS_THRESHOLDS(lib/config/index.ts:551-556。コメントに「暫定値」) |
DFA が別の基準を決めたら | DFA 内部(TeamWorks 照会なし) |
未実施の猶予30分(LAUNCH_GRACE_MINUTES) |
★いまはどの数字にも使っていない★ | 「予定+猶予」をアプリで計算しない(D-091 項1)。★D-173 の結果待ちはこの件に限り D-091 項1 を上書きしたが、この定数ではなく RESULT_WAIT_MINUTES を使う★ |
— | — |
★結果待ちの長さ 120分(RESULT_WAIT_MINUTES)★(D-173) |
清掃詳細ログ・CSV・ダッシュボード・アラートの出発失敗・連続日数・要レスキュー・当期の率(5.9b) | 予定の終了から120分は、レポートが無くても「結果待ち」とし失敗に数えない(ユーザー判断「一旦2時間」) | ★第12回(★R-2★。04f974a で送付版)で TeamWorks にレポートの最大の遅れを聞く★。答えに合わせて値を変える(直近の清掃の見え方だけが変わる) |
/dashboard)★取得は GET /api/dashboard(app/api/dashboard/route.ts)の1本です★(components/features/dashboard/DashboardScreen.tsx:85。2026-10-06 から。D-159 / 指示書AV)。旧: コンパクト版ログ用に GET /api/clean-logs?result=problem&date_from=当日&date_to=当日&per_page=5 も呼んでいた。 ★date を省略すると「直近モード」★: 数値カードと表は ★清掃エリアごとに、本清掃のうち「予定の開始 ≥ 現在 − 7日」かつ「予定の終了 ≤ 現在」(境界はどちらも含む)を満たす予定の中で開始が一番新しい1件★から作ります(pickLatestRunPerSection() lib/dashboard/latest-run.ts:67。7 は DASHBOARD_LATEST_RUN.lookbackDays lib/config/index.ts:213。a961829)。★旧(10/06〜10/07 朝): 「予定の終了 + 30分(settleMinutes)≤ 現在」。D-173 で settleMinutes を削除★し、終わってからレポートが届くまでの清掃は ★結果待ち(result_pending)★として選んだうえで「結果待ち」カードに数えます(5.9b)。 ★実データの清掃は 10:00 や 19:00〜22:00(前日の日付)なので、当日の日付で数えると翌朝に空になるため★(D-159)。★スケジュール進捗・要レスキュー・セキュリティバナーは当日のまま★(「当日」は toJstIso(now) の日付。route.ts:70-71)。
| 画面上の表示名 | 例 | テーブル.列 | 計算式(関数名 ファイル:行) |
対象にする行の条件 | 根拠 | 確定 / 暫定 |
|---|---|---|---|---|---|---|
| 成功 | 8 |
clean_timetables.id / start_at、clean_task_reports.clean_area / task_area |
出発した(レポート行がある)行のうち判定が ◎○ の件数。computeSummary() lib/dashboard/aggregate.ts:41-76(SUCCESS_GRADES :26。a961829)。★結果待ちの行は数えない★ |
★直近モード(上の段落)で選んだ行★(route.ts:131-164 / :183)。?date= 指定時はその日の予定で、予定時刻を過ぎたもの。予定外のタスクは入らない |
D-049(ダッシュボードの値の定義)・★D-159(母集団)★。成功=◎○ とするのは判定基準(D-139)に従うため | 確定(完了率の式も D-158 で確定) |
/ N件(成功の補足) |
/ 12件 |
同上 | 成功+失敗+未実施+★結果待ち★(DashboardScreen.tsx:196-209。D-173)。★直近モードでは、7日以内に終わった予定がある清掃エリアの数★。★成功率の分母とは違う(結果待ちを含む)★ |
同上。★1行=1ロボット×1回の予定★で、施設数ではない | D-049 | 確定 |
| 失敗 | 3 |
同上 | 出発した行のうち判定が △× の件数(FAILURE_GRADES aggregate.ts:28)。★task_area = 0 の清掃は完了率 0%・判定 × なのでここに入る★ |
同上 | D-049。未出発の行を失敗にも数えると二重計上になるので、出発した行に限る(aggregate.ts:33-34) |
確定 |
| 未実施(予定時刻経過・実績なし) | 1 |
clean_timetables.id、clean_task_reports.clean_timetable_id(行の有無) |
departed === false の件数(★結果待ちを除く★。aggregate.ts:54-57)。departed は !notExecutedFromReport()(lib/db/index.ts:969) |
同上 | 指示書AQ §1-1 / ユーザー確認 2026-09-30「出発失敗と未実施は同じ扱い」。★予定の終了から RESULT_WAIT_MINUTES(120分)以内でレポートが無い清掃は、未実施ではなく結果待ち(D-173)★。clean_task_reports には未実施を表す列が無い |
確定 |
| 成功率 | 67% |
上の3つ | 成功 ÷(成功+失敗+未実施)× 100。★結果待ちは分母に入れない★(D-173)。3つの合計が 0 なら -(aggregate.ts:67-75) |
同上 | D-049。0 件の日を 0% と出さないのは「予定が無い日」と「全部失敗した日」を区別するため | 確定 |
| ★結果待ち★(D-173) | 1 |
clean_timetables.start_at / end_at、clean_task_reports の行の有無 |
result_pending === true の件数(computeSummary() の pending。aggregate.ts:49-52)。旗は isResultPending()(lib/clean-logs/result-pending.ts:22)で、★予定の開始 ≤ 現在 < 予定の終了+120分 かつレポートが無い★。カードは中立色(StatCard の mt。DashboardScreen.tsx:265-273。副題「レポートの到着待ち」) |
同上(直近モードでは予定の終了 ≤ 現在の行なので、清掃中の行は入らない) | ★ユーザー判断 2026-10-07(「B」・カードを足す)。D-173★。★モックに無いカード★ | 確定(120分は暫定。第12回(★R-2★。04f974a で送付版)でレポートの最大の遅れを聞く) |
| 目標: 95% | 目標: 95% |
(DB ではない) | SUCCESS_RATE_TARGET = 95(lib/config/index.ts:575) |
— | DFA の設定値(コメントに出所の記載なし) | DFA 設定 |
スケジュール進捗 N / M 完了 と棒グラフ |
11 / 12 完了 |
★M(予定)= clean_schedules.weeks / inactive / deleted_at★、★N(完了)= clean_timetables × clean_task_reports の行の有無★ |
M=その曜日の桁が "1" で有効(inactive = 0)なスケジュールの数、N=当日の出発した件数、棒=N ÷ M × 100(computeScheduleProgress() aggregate.ts:101-120)。M が 0 なら「本日の清掃予定はありません」(ScheduleProgressCard.tsx:21-22) |
M: clean_schedules の deleted_at が NULL の行(lib/db/index.ts:1762-1764)。weeks は「日月火水木金土」の7桁(lib/format/schedule.ts:4)。N: 上の「成功」と同じ母集団 |
D-049「予定はスケジュール由来にする」— ログの行数を使うと、収集処理が止まって当日の行が0件のとき「本当に定休日」と「収集処理の障害」が区別できない。★「完了」と書いてあるが中身は「出発」★(aggregate.ts:112。★結果待ちも出発していないので数えない★。D-173 で変えていない) |
確定 |
D-Sec N件(右上の札)とセキュリティ無効バナー |
D-Sec 1件 / ⚠ セキュリティ無効: ○○店 1F(03:10以降) |
control_error_histories |
種別が security_off で対応済みでないものの件数と一覧(computeSecurityBanner() aggregate.ts:130-140)。時刻は detected_at |
★実DBでは常に0件★ — prismaSource.dsAlerts() は disconnected(通信断)しか返さない(lib/db/index.ts:1615-1626)。security_off に対応する列がどのテーブルにも無いため(D-090 項3-9) |
D-049 / D-090 項3-9。★意図した挙動で実装漏れではない★ | セキュリティエラーの入れ方は第10回 N-5 で方針が出て DDL 変更待ち |
要レスキュー店舗 N件 |
3件 |
clean_timetables × clean_task_reports(loadConsecutiveInputs() 経由)、施設名は facilities.facility_name |
3条件のどれかに当たった施設の数(buildRescueRequired() lib/db/rescue-mapping.ts:105-198 → judgeRescue() lib/judge/rescue.ts:268-296)。★件数では切らない★(画面は最初の5件だけ見せる。RESCUE_INITIAL_VISIBLE lib/config/index.ts:120) |
★当日に予定が無い施設も含む★(数値カードとは母集団が違う。DashboardScreen.tsx:271-272)。評価日は「当日以前で最も新しいスケジュール存在日」。施設名は deleted_at で絞らない |
ユーザー指示 2026-09-15 / 指示書AI・AJ | 確定 |
| 要レスキューの理由「{n}日連続で未実施」 | 3日連続で未実施 |
同上(clean_task_reports の行の有無) |
最新日から遡って departed === false の日が続いた数(countNotExecutedStreak() rescue.ts:202-209)。3 以上で該当(RESCUE_THRESHOLDS.notExecutedDays lib/config/index.ts:87-90) |
同じ日に複数台あれば、1台でも未実施ならその日は未実施(OR。foldRescueDays() rescue.ts:160-189)。★task_area = 0 の清掃は未実施に数えない★ |
ユーザー指示 2026-09-15(R-1)/ D-156 項1(未実施は departed で数える) |
確定 |
| 「連続問題レベル3({n}日連続)」 | 連続問題レベル3(4日連続) |
同上 | 評価日時点の連続問題日数(computeConsecutiveProblemDays() lib/db/clean-log-mapping.ts:463-489)が L3(3日以上) |
施設単位・スケジュール存在日で数える(章5-6) | CLAUDE.md §4.5 / D-026 | 確定 |
| 「完了率が前回比{n}%低下」 | 完了率が前回比40%低下 |
同上(clean_area / task_area) |
(前回 − 当日) ÷ 前回。0.2 以上で該当(judgeCompletionDrop() rescue.ts:246-255)。表示は × 100 を四捨五入(RescueRequiredCard.tsx:63-71) |
前回=前回のスケジュール存在日。前回・当日が null、前回が 0 なら算出しない |
ユーザー確定 2026-09-15「比で」(差ではない) | 確定 |
| 判定日 | 2026-10-04 |
clean_timetables.start_at |
その施設の、当日以前で最も新しいスケジュール存在日(rescue-mapping.ts) |
— | 指示書AI | 確定 |
| 完了率(前回 N%) | 完了率 18%(前回 30%) |
clean_task_reports.clean_area / task_area |
その日の1回ごとの完了率の単純平均(facilityDayRate() rescue.ts:135-143) |
その施設のその日の本清掃(複数台なら全台)。null の行は除く |
D-155 / D-151(面積加重をやめて単純平均に)/ D-158(確定) | 確定 |
| 清掃詳細ログ(コンパクト版) | — | 清掃詳細ログと同じ | 5B-5 と同じ。★「予定」のセルは M/D HH:mm(例 10/6 03:30)★ |
★数値カードと同じ母集団(直近モード)の △×(★結果待ちを除く★。problem_logs。route.ts:203)の、データソースの順の先頭5件★。旧: 当日・△×のみ・先頭5件 |
D-159 / 指示書AV D7・D11 | 確定 |
サンプルデータのとき: 数値カード・進捗・要レスキューは lib/fixtures/clean-logs.ts の清掃ログと lib/fixtures/schedules.ts から、★同じ関数で★計算します。★清掃ログの完了率は lib/fixtures/clean-logs.ts に直接書かれた値★(90.1 / 90.0 / 89.5 などの境界値)で、面積から計算したものではありません。★セキュリティ無効バナーはサンプルデータでは出ます★(lib/fixtures/ds-alerts.ts に security_off の行がある)が、実DBでは出ません。
/detail-log)1行=★本清掃の予定1回★(clean_timetables の precheck = 0 の1行)です。取得は prismaSource.cleanLogs()(lib/db/index.ts:754-1023)。★予定外のタスク(clean_timetable_id が NULL の実績)は行として出ません★(D-151 項7 で「予定起点のまま」か「実績起点」かは★保留★)。
| 画面上の表示名 | 例 | テーブル.列 | 計算式(関数名 ファイル:行) |
対象にする行の条件 | 根拠 | 確定 / 暫定 |
|---|---|---|---|---|---|---|
| 日付 / 予定 | 10/4 / 02:00 |
clean_timetables.start_at(prisma/schema.prisma:597。DATETIME) |
jstIsoFromDb()(lib/db/index.ts:962)→ formatMd() / formatHm()(lib/format/date.ts:8-17) |
本清掃の予定。予定時刻を過ぎたもの(index.ts:915-917) |
★予定の時刻であって、実際に清掃を始めた時刻(clean_task_reports.started_at)ではない★。日付の絞り込みもこの列(index.ts:784) |
確定 |
| 1h前ST / 30分前ST | Standby. / EmergencyStop / ★-★ |
robot_error_reports.error_type / errored_at、clean_timetables.precheck / parent_id |
その本清掃の子の事前チェック予定を★分数の大きい順に1回目・2回目★(D-178)とし、それぞれを指すエラーのうち errored_at が最新の error_type。予定行があってエラーが無ければ Standby.。★予定行そのものが無ければ null → 画面は「-」・CSV は空のセル★(robotStatusFromErrors() lib/db/clean-log-mapping.ts:54-60 / statusColumnsFromPrecheckRows() lib/db/precheck-mapping.ts:147- / 画面の分岐 precheckStatusCell() lib/format/precheck-status.ts:22。a961829) |
本清掃の子の事前チェック行すべて(★分数によらない★。3つ以上なら開始に近い2つ)。★D-178 から★。旧: PRECHECK_MINUTES_BEFORE = [60, 30] に一致する行だけで、10分前など設定に無い回は出なかった(D-169) |
D-065 Q2 / D-098。★error_type は変換しない★(D-077。CLAUDE.md §4.8)。Standby. は TeamWorks の robots.status コード表の原文(第2回回答)。★null は D-171(ユーザー判断 2026-10-07「事前チェックがないものは-にしてください」)★ |
★Standby. は「エラーが無い」の読み替えで、robots.status を実際に読んだ値ではない★。★判定には使わない★(D-170) |
出発 ✓ / ✗ |
✓ |
clean_task_reports の行の有無(clean_timetable_id で突き合わせ) |
departed = !notExecutedFromReport(report)(lib/db/index.ts:969) |
同上 | 指示書AQ §1-1。「予定+猶予30分」をアプリで計算しない(D-091 項1) | 確定 |
| ★結果待ち★(出発「…」・清掃結果「結果待ち」・完了率「-」・判定なし) | … / 結果待ち |
clean_timetables.start_at / end_at、clean_task_reports の行の有無 |
result_pending = isResultPending({ startAt, endAt, now, hasReport })(lib/db/index.ts:1141 → lib/clean-logs/result-pending.ts:22)。見え方は cleanLogRowView()(lib/format/clean-result.ts:98)。★departed / grade は出発失敗と同じ値(false / ×)のまま★ |
予定の開始 ≤ 現在 < 予定の終了+RESULT_WAIT_MINUTES(120分)で、レポート行が無い本清掃。★行を開けない・△×/×のみの絞り込みに出ない★ |
★ユーザー判断 2026-10-07「B」・待つ時間は一旦2時間(D-173)★。D-091 項1 をこの件に限り上書き | 確定(120分は暫定。第12回 R-2 で見直す) |
| 清掃結果 | 清掃正常終了 / 6 / 出発失敗 |
clean_task_reports.status(prisma/schema.prisma:529) |
formatCleanTaskStatus()(lib/format/clean-result.ts:61-67。a961829)→ cleanTaskStatusLabel()(lib/config/index.ts:384-386)。0 清掃開始前 / 1 清掃中 / 2 清掃中断 / 3 清掃キャンセル / 4 清掃正常終了 / 5 清掃異常終了。表に無い値は数字のまま。行が無ければ「出発失敗」 |
同上。行が複数あれば ended_at(無ければ started_at)が最新の1行 |
★TeamWorks 第10回 N-2(表示名の原文)/ D-155 項2(ユーザー判断 2026-10-02「推奨」)★。★判定には使わない★ | 表示名は確定。6 の意味は未受領(第11回 P-4) |
| 完了率 | 90% / - |
clean_task_reports.clean_area / task_area(prisma/schema.prisma:533-534) |
completionRateFromReport()(lib/db/clean-log-mapping.ts:203-210)→ runCompletionRate()(lib/judge/kpi.ts:144-153)→ displayRate() |
同上。★レポート行が無いときだけ -★(task_area = 0 は 0%。第11回 P-5=B。旧: -) |
★D-155 項1(ユーザー判断「一旦B」2026-10-02)★。TeamWorks のサマリと同じ式にそろえるため(第10回 N-3)。clean_ratio 列は先方が式の誤りを認めたため使わない。★第11回 P-2=A で確定(D-158)★ |
確定 |
| 判定 | ◎ |
同上 | judgeGrade(completion_rate)(lib/judge/grade.ts:29-35。生値で判定) |
同上 | D-139(境界は上のランク)/ CLAUDE.md §4.1 | 確定 |
| 判定基準の凡例(「判定 ?」) | ◎ 90%以上 |
(DB ではない) | GRADE_THRESHOLDS(lib/config/index.ts:18-25)を表示(components/features/detail-log/CleanLogTable.tsx:155-180) |
— | D-139。凡例の「(閾値はシステム設定で変更可能)」はモックの文言どおり(d-watch-mock.jsx:312)。閾値の編集は F-10 で P1 に実装する予定(docs/spec.md:114-119)だが、★いまは段階的に実装している途中で、編集できるのは連続日数の表示上限だけ★(2026-09-13 ユーザー判断 A案)。判定に使う閾値を編集可能にする前に、「いつから有効か」「過去の表示をどうするか」をユーザーに確認する決まり(指示書AF §H-1) |
確定 |
| 行を開いたときの見出し(レベル1〜3) | 🔶 レベル2 — DFA運用担当がアクション |
上の値すべて+連続問題日数 | 該当アドバイスの最大レベル(maxAdviceLevel() lib/judge/advice.ts:113-117)。アドバイスは matchAdviceRules()(:50-94) |
△× の行だけ開ける(CleanLogTable.tsx:192) |
CLAUDE.md §4.5(R1〜R4)。B1〜B3 は Phase B | 確定(B3 は値が未判明で空。章5-8) |
| 連続問題日数・L1〜L3 | (★画面に数字では出ない★) | clean_timetables × clean_task_reports |
computeConsecutiveProblemDays()(lib/db/clean-log-mapping.ts:463-489)→ judgeEscalationLevel()(lib/judge/escalation.ts:52-59) |
★表示期間より前も遡って読む★(loadConsecutiveInputs() lib/db/consecutive-source.ts:343-405)。施設単位・スケジュール存在日 |
D-026 / D-109 / CLAUDE.md §4.5 | 確定。アドバイス R3/R4 と見出しのレベルに効く |
| 件数「全{total}件中 {from}〜{to}件」 | 全213件中 1〜50件 |
上の行 | 絞り込み後の行数(meta.total。app/api/clean-logs/route.ts:100)。1ページ 50件(PAGINATION.defaultPerPage lib/config/index.ts:407-408) |
日付・施設・機種・シリアル・判定(△×のみ / ×のみ / 全件。lib/clean-logs/query.ts:180-185。★結果待ちは「全件」にだけ出る(:182。D-173)★)で絞ったあと |
指示書AA | 確定 |
| CSV の「完了率」「出発」「清掃結果」「1h前ST / 30分前ST」 | 90% / 出発 / 清掃正常終了 |
同上 | 画面と同じ関数(buildCleanLogCsv() lib/clean-logs/csv.ts:57-85。a961829)。出発は「出発 / 未出発」、清掃結果は日本語の表示名。★完了率は csvRate()(lib/csv.ts:69)で、値が無ければ空のセル★(D-164)。★1h前ST / 30分前ST も null は空のセル★(D-171)。★結果待ちの行は出発・清掃結果が「結果待ち」、完了率・判定は空のセル★(D-173。cleanLogRowView() を画面と共有)。旧: どちらも - を出し、数式対策(D-125)で '- になって Excel に見えていた |
絞り込み条件に一致する全件。★20,000行を超えると出せない★(MAX_CSV_ROWS lib/config/index.ts:494。a961829) |
指示書AO / 指示書AS D11(CSV は日本語固定)/ D-163(共通部品 lib/csv.ts)/ D-164 / D-171 |
確定(行数の上限は暫定値) |
サンプルデータのとき: 行・完了率・連続問題日数・1h前ST などは lib/fixtures/clean-logs.ts に書かれた値です(完了率は面積から計算しない。判定だけ judgeGrade() を通す。clean-logs.ts:89-90)。清掃結果は lib/fixtures/task-status-rule.ts の巡回列(CSV実測の分布 4:161 / 6:22 / 5:4 / 3:2)で決まります。★1h前ST / 30分前ST は既定が「待機中」で、施設11(バリューマート川下店)の履歴だけ null(事前チェックの無い施設。D-171)★ — ★既定値を ?? で入れないこと★(null まで「待機中」に化ける)。
取得は GET /api/clean-logs/{id}/areas(app/api/clean-logs/[id]/areas/route.ts)。{id} の行から ★施設と日付(JST)★を取り出し、★同じ施設・同じ日の本清掃の予定すべて★を並べます(prismaSource.areaCompletions() lib/db/index.ts:1095-1168)。★1エリア=1ロボット★で、1回の清掃の中の売り場別の内訳ではありません(lib/clean-logs/areas.ts:11-12)。
| 画面上の表示名 | 例 | テーブル.列 | 計算式(関数名 ファイル:行) |
対象にする行の条件 | 根拠 | 確定 / 暫定 |
|---|---|---|---|---|---|---|
全体 N% と判定 |
全体 78% ○ |
clean_task_reports.clean_area / task_area |
各エリアの完了率(null を除く)の単純平均(overallCompletionTally() lib/clean-logs/areas.ts:101-112 → meanCleanRatio())。判定は judgeGrade() |
★「問題エリアのみ」に切り替える前の全エリア★(areas.ts:134-135)。全エリアが null なら - / × |
D-155 / 指示書AQ §5-5(以前は全体だけ面積の合計どうしの比だった。指示書Z 決定7 を変更)/ D-158(確定) | 確定 |
| 各エリアの完了率と判定 | 売場A 45% △ |
sections.section_name、clean_task_reports.clean_area / task_area |
completionRateFromReport()(清掃詳細ログの1行と★同じ関数★。lib/db/index.ts:1158)。並びは完了率の昇順・null が先頭(areas.ts:75-87) |
その施設のその日の本清掃。予定時刻を過ぎたもの | 指示書Z / D-155 / D-158(確定) | 確定 |
| 「問題エリア(n / m エリア)」/「全 n エリア」 | 問題エリア(2 / 5エリア) |
同上 | n=判定が △× のエリア数、m=全エリア数(buildAreaCompletions() areas.ts:145-171) |
同上 | 指示書Z 決定8・決定12 | 確定 |
サンプルデータのとき: lib/fixtures/areas.ts の行です。面積は清掃ログの完了率から逆算して作り(clean_area = task_area × completion_rate ÷ 100。areas.ts:69)、★完了率そのものは清掃ログと同じ値★です。
/alerts)1枚のカードは次の★2種類★のどちらかです(prismaSource.prechecks() lib/db/index.ts:1414-1646。a961829)。
precheck が 60 / 30 など)を指す robot_error_reports の1行| 画面上の表示名 | 例 | テーブル.列 | 計算式(関数名 ファイル:行) |
対象にする行の条件 | 根拠 | 確定 / 暫定 |
|---|---|---|---|---|---|---|
| 未解消(要対応) | 5 |
robot_error_reports.status(prisma/schema.prisma:332)、出発失敗は clean_timetables |
全件 − 解消済み(summarizePrechecks() lib/prechecks/query.ts:177-183)。★「対応中」と★出発失敗のすべて★を含む★(出発失敗は書き込み先が無いので常に未解消。lib/db/index.ts:1629-1631) |
★日付範囲・分類で絞ったあと、対応状況で絞る前★(app/api/prechecks/route.ts:72-75)。日付は親の本清掃の予定日 |
D-054(サマリは対応状況の絞り込みに追従させない)/ D-105 | 確定 |
| 解消済み(対応完了) | 2 |
robot_error_reports.status = 2 |
resolutionStatusFromRobotErrorStatus()(lib/db/precheck-mapping.ts:41-54。0 未解消 / 1 対応中 / 2 解消) |
同上 | D-065 Q1-3 と同じ値域(TeamWorks 第2回回答) | 確定 |
| 合計(全アラート) | 7 |
上の2つ | 件数(query.ts:181) |
同上 | D-054 | 確定 |
| 種別「{n}分前異常」/「出発失敗」 | 30分前異常 |
clean_timetables.precheck(prisma/schema.prisma:604) |
precheck の値そのもの(分)を precheckMinutesFromCode()(precheck-mapping.ts:105-108)で読み、precheckSeverityLabel()(lib/prechecks/label.ts)で文言に |
エラー行が指す事前チェック予定 | ★TeamWorks 第7回 A5-2(precheck は「本清掃開始の何分前か」の数値そのもの)/ D-112★。20 なら「20分前異常」 |
確定 |
| ロボット状態(札) | EmergencyStop / Standby. |
robot_error_reports.error_type |
変換しない(precheckItemFromErrorRow() precheck-mapping.ts:223-262)。出発失敗の行は Standby.(robotStatusFromErrors([])。lib/db/index.ts:1622) |
— | D-077(CLAUDE.md §4.8) | 確定 |
予定 HH:MM |
予定: 02:00 |
clean_timetables.start_at(★親の本清掃の予定★) |
jstIsoFromDb() → formatHm() |
— | 指示書S 3-2 項5(事前チェック予定の時刻ではなく本清掃の時刻を出す) | 確定 |
検知 M/D HH:MM |
検知: 10/4 01:31 |
★robot_error_reports.errored_at、無ければ created_at★。出発失敗は clean_timetables.start_at |
detectedAtFromError()(precheck-mapping.ts:190-192)→ jstIsoFromDb() |
— | 指示書S 3-4 / D-105。★created_at は TIMESTAMP で、2026-10-02 以前のステージングでは UTC で書かれていた★(第10回 N-14b)。errored_at が空の行だけ影響する |
errored_at が空の行の時刻は再実測待ち |
残量 N% |
(★出ない★) | (robots.battery は読まない) |
常に null(precheck-mapping.ts:258) |
— | D-090 項8 — robots.battery は現在値だけで履歴が無く、過去のアラートに今の値を貼ることになるため |
確定 |
| ★「本清掃は成功」の Pill とカードの薄さ★ | 本清掃は成功 |
親の本清掃の clean_task_reports(clean_area / task_area。最新の1行) |
★main_run_succeeded = レポート行があり、完了率の判定が ◎○★(mainRunSucceededFromReport() precheck-mapping.ts:283-287 — notExecutedFromReport() → completionRateFromReport() → judgeGrade()(生値)→ SUCCESS_GRADES)。true ならカードの表示を不透明度 0.6 にし Pill を足す(AlertsScreen.tsx:109-121 / :570) |
事前チェック異常の行。★出発失敗の行は常に false★(lib/db/index.ts:1641)。親のレポートは本清掃の id でまとめて引いた Map から取る(1行ずつクエリしない) |
★D-171(ユーザー判断 2026-10-07。案2)★。成功の定義はダッシュボードと同じ(SUCCESS_GRADES) |
確定。★表示だけ — 対応状況・件数は変えない★ |
| 対応状況・備考(★編集モード★) | 未解消 / 対応中 / 解消 |
robot_error_reports.status / responded_at / resolved_at / comment(★書き込む4列★) |
★通常は表示だけ。「編集」→ プルダウンと入力欄で下書き → 「保存(N件)」で変えた行だけを1行ずつ PATCH★(下書きは lib/prechecks/edit-drafts.ts、日時の規則は lib/prechecks/status-update.ts。書き込みは lib/db/robot-error-write.ts) |
★事前チェック異常の行だけ★。出発失敗は札のまま | D-152(ステージングへの書き込み解禁)/ D-156 項3〜4 / 指示書AT / ★D-161(プルダウン)→ D-165(編集モード)★ | 確定 |
| 件数「全{total}件中…」 | 全7件中 1〜7件 |
— | 対応状況で絞ったあとの件数(meta.total) |
— | D-054(summary.total と別物) |
確定 |
サンプルデータのとき: lib/fixtures/prechecks.ts の行に、画面からの更新をメモリ上に重ねたものです(lib/db/fixture-precheck-overrides.ts。再起動すると消える)。★main_run_succeeded は各行に明示してあり、true は id 6(「30分前までに充電完了、出発成功」)だけ★(id 7 は完了率62%=△なので false。D-171)。
/errors)1行は robot_error_reports の1行です(errorReportItemFromRow() lib/db/error-report-mapping.ts:136)。★事前チェック・本清掃・予定なし・robot_id が NULL(制御盤・セキュリティ・清掃開始失敗)を問わず★出します(D-160)。
| 画面上の表示名 | 例 | テーブル.列 | 計算式(関数名 ファイル:行) |
対象にする行の条件 | 根拠 | 確定 / 暫定 |
|---|---|---|---|---|---|---|
| 検知時刻 | 10/5 19:05 |
★robot_error_reports.errored_at、無ければ created_at★ |
detectedAtFromError() → jstIsoFromDb()(/alerts と同じ) |
— | D-105 / 指示書AW D3 | 確定 |
| 施設名 | — | facilities.facility_name |
そのまま | — | — | 確定 |
| シリアル / 種類 / レベル | 0 / SECURITY_ON / CRITICAL |
robot_error_reports.serial_number / error_type / error_level |
★変換しない・t() にも通さない★ |
— | D-077 / D-160 項3(区分を作るのは次フェーズ) | 確定 |
| 紐づく予定 | 事前チェック(30分前) |
robot_error_reports.clean_timetable_id → clean_timetables / clean_timetable_histories の precheck |
errorScheduleLink()(error-report-mapping.ts:107): NULL → 予定なし / 2表に無い id → 予定不明 / precheck が本清掃 → 本清掃 / それ以外 → 事前チェック(★生の分数★)。文言は errorScheduleLabel()(lib/error-reports/label.ts:27) |
予定は2表から引く(同じ id は本表) | 指示書AW D4・D5 | 確定 |
| 対応状況・備考(★編集モード★) | 未解消 / 対応中 / 解消 |
robot_error_reports.status / responded_at / resolved_at / comment(★書き込む4列★) |
/alerts と同じ(★「保存(N件)」で変えた行だけ1行ずつ★ → planPrecheckStatusUpdate() → writeRobotErrorColumns()。下書きは lib/prechecks/edit-drafts.ts) |
★行があれば全部★ | D-160 項2 / 指示書AT / ★D-161 → D-165★ | 確定 |
| 件数「全{n}件」 | 全10件 |
— | 絞り込み後・ページ分け前の件数(meta.total) |
★既定は status が 未解消(0)・対応中(1)★(errorReportStatusCodes() lib/error-reports/query.ts:123。0/1/2 を直書きしない)。3以上の行は出さず例外にもしない |
D-160 項1 / 指示書AW 表2 F12 | 確定 |
★並び★は検知時刻の降順、同時刻は error_id の昇順(compareErrorReportKeys() query.ts:134)。★絞り込みの正は filterSortErrorReportKeys()(query.ts:151)で、SQL の where(errorReportListWhere() error-report-mapping.ts:177)はその写し★です(種類の部分一致は SQL に降ろさない)。
サンプルデータのとき: lib/fixtures/error-reports.ts の11行(DB の行の形)に、画面からの更新をメモリ上に重ねたものです(lib/db/fixture-error-report-overrides.ts。再起動すると消える。/alerts の上書き表とは別)。既定10行・「解消済みも表示」で11行(docs/fixtures.md §13)。
/reports)期間は週次 / 月次 / 年次から選び、‹ › で前後に動かせます。★週は日曜〜土曜で、月をまたぐ週は1日で切ります★(週の長さは1〜7日。periodRange() lib/format/period.ts:165-207。D-071)。★期間が「確定期間」か「当期(進行中)」かで、数字の取得元が変わります★。
| 確定期間(先週・先月・前年など) | 当期(今週・今月・今年) | |
|---|---|---|
| 判定 | isCurrentPeriod()(period.ts:214-226)が偽 |
今日(JST)が期間内 |
| KPI | ★customer_summaries の率4列をそのまま★ |
★clean_timetables × clean_task_reports × robot_error_reports からその場で集計★ |
| 施設別パフォーマンス | ★facility_summaries の率4列をそのまま★ |
同上(施設ごと) |
| エラー種別の内訳 | ★facility_error_summaries.error_count の合計★ |
robot_error_reports の数え上げ |
| 連続問題施設 | ★どちらも詳細テーブルから算出★(対応するサマリが無い。D-102 項4) | 同左 |
| 前期比の「前期」 | 1つ前の期間のサマリ行 | ★1つ前の期間(=確定期間)のサマリ行★ |
| 根拠 | ★TeamWorks のバッチが作ったサマリを正とし、履歴から再計算しない★(D-102 / CLAUDE.md §7)。年次は D-151 で解禁 | ★進行中の期間はサマリに存在しない★(CLAUDE.md §4.7。バッチは週次が毎週日曜6:00、月次が毎月1日6:00、年次が毎年1/1 6:00。第10回 N-12) |
サマリ行がどの粒度かは month / week が 0 かどうかで決めます(week ≥ 1 週次 / week = 0 月次 / month = 0 かつ week = 0 年次。periodOfSummaryRow() lib/db/report-mapping.ts:100-104。第5回 X-2 で TeamWorks 承認。D-074 項3)。★当期にもサマリ行があった場合(バッチが先に走った場合)は、サマリ行が優先されます★(lib/db/index.ts:2137-2139)。
「何日の清掃か」の決め方: 当期の集計は予定の日付(clean_timetables.start_at)で期間に振り分けます(aggregateCleanStatsByFacility() lib/db/report-mapping.ts:473-474)。TeamWorks のサマリ(確定期間)は clean_task_reports.started_at の日付で決めます(第10回 N-9 = A)。★2つは一致します★ — ★予定のある清掃では started_at に予定の start_at と同じ値が入るため★です(ユーザー回答 2026-10-05「A」。CLAUDE.md §4.7)。例: 9/30 23:50 の予定が 10/1 00:10 に始まっても、started_at は 9/30 23:50 なので、どちらも 9/30 の清掃として数えます。
★その代わり、予定のある清掃の実際の開始時刻はこの列からは分かりません★(定刻出発の判定には使えない)。
★KPI は1社(顧客)に定まるときだけ出します★。候補が2社以上なら「顧客を選んでください」と出ます(lib/reports/target-customer.ts:1-37。サマリに稼働率・清掃完了率の分子・分母が無く、複数社を合算できないため。D-133。第11回 P-6 で出発成功率・エラー発生率の材料はそろったが、方針は据え置き。D-158)。
| 画面上の表示名 | 例 | テーブル.列 | 計算式(関数名 ファイル:行) |
対象にする行の条件 | 根拠 | 確定 / 暫定 |
|---|---|---|---|---|---|---|
| 稼働率(確定期間) | 92% |
customer_summaries.operation_ratio(prisma/schema.prisma:707) |
そのまま(kpiFromSummaryRatios() lib/db/report-mapping.ts:231-241) |
customer_id が対象の1社、year / month / week が一致する行(lib/db/index.ts:1974-1987) |
D-102。★式は正常終了(status 4)の回数 ÷ 予定回数★(第11回 P-6=A。D-158)。分子の列はサマリに無い | 値は TeamWorks のバッチが正 |
| 稼働率(当期) | 88% |
clean_timetables + clean_timetable_histories の id / start_at / facility_id、clean_task_reports.status |
★正常終了(status = 4)の回数 ÷ 予定回数 × 100★(operationRate() lib/judge/kpi.ts:36-38。正常終了かは isCompletedTask()。集計は aggregateCleanStatsByFacility() report-mapping.ts:460-505 を全施設ぶん合算 sumCleanStatsBuckets() :333-339) |
対象の1社の施設の本清掃で、期間内・予定時刻を過ぎたもの。予定外は入らない | CLAUDE.md §4.2 / ★第11回 P-6=A(D-158。指示書AU 段階3)★ | 確定 |
| 出発成功率(確定期間) | 95% |
customer_summaries.launch_ratio(:708) |
そのまま | 同上 | D-102。TeamWorks「launch_count は清掃タスクを開始できた回数」(第10回 Q6-2 / Q11-3) |
値は TeamWorks が正 |
| 出発成功率(当期) | 90% |
稼働率(当期)と同じ表の、clean_task_reports の行の有無 |
★開始できた回数(レポート行がある回数)÷ 予定回数 × 100。時刻の条件なし★(launchRate() kpi.ts:44-46) |
同上 | ★第11回 P-6=A(D-158)★。サマリの launch_ratio と同じ式 |
確定 |
| 清掃完了率(確定期間) | 81% |
customer_summaries.clean_ratio(:709) |
そのまま | 同上 | ★TeamWorks のサマリの式=1回ごとの面積比(上限100・小数第2位以下切り捨て)の平均、本清掃のみ・clean_timetable_id が NULL の行は除く★(第10回 N-3) |
値は TeamWorks が正 |
| 清掃完了率(当期) | 79% |
clean_task_reports.clean_area / task_area |
1回ごとの完了率の単純平均(kpiFromCleanStats() report-mapping.ts:516-535 → meanCleanRatio()。1回ごとの値は runCompletionRate()。:492) |
上と同じ本清掃のうち、実施した(レポート行がある)もの。★task_area = 0 は 0% として含める(P-5=B)★ |
★D-155 項1★ — 確定期間(サマリ)と同じ式にそろえ、前期比が同じ式どうしの比較になるようにした。★第11回 P-2=A で確定(D-158)★ | 確定 |
| エラー発生率(確定期間) | 4% |
customer_summaries.error_ratio(:710) |
そのまま | 同上 | D-102 | 値は TeamWorks が正 |
| エラー発生率(当期) | 5% |
robot_error_reports.clean_timetable_id、分母は上の予定回数 |
★エラー件数 ÷ 予定回数 × 100。上限なし★(errorRate() kpi.ts:182-184。第11回 P-6=B)。件数は★本清掃の予定を指すエラー行★の数(errorCountByMainTimetable() report-mapping.ts:380-392) |
事前チェックの予定を指すもの・clean_timetable_id が NULL のもの・指す先が無いものは数えない |
TeamWorks 第8回 A7(事前チェックはサマリに含めない。D-114 項2)/ 第10回 Q4-1=A・Q4-2=B | 確定。★制御盤・セキュリティのエラーの扱いは次フェーズ★(第11回 P-8〜P-10。D-158 当方の対応5) |
| 前期比「▲N% 前週比」 | ▲3% 前週比 |
今期と前期の値 | ★今期 − 前期(パーセントの差=ポイント)★(toKpiValue() kpi.ts:170-176)。表示は絶対値を四捨五入(KpiCard.tsx:89-90)。色は生値の符号で、エラー発生率だけ「下がると良い」(KpiCard.tsx:43-46) |
どちらかが null なら行ごと出さない |
指示書F 5-4。★「3% 上がった」ではなく「3ポイント上がった」の意味★ | 確定 |
| 「前週: N%」(前期の値) | 前週: 89% |
★前期のサマリ行★(customer_summaries の率) |
そのまま | 1つ前の期間(shiftPeriod() lib/format/period.ts:267-) |
D-102 | 確定 |
当期の KPI と確定期間の KPI は、★4つとも同じ式★(完了率は D-155、3つの率は D-158 以降)ですが★別の人が計算した数字★です。確定期間は TeamWorks のバッチ、当期はアプリです。
| 画面上の表示名 | 例 | テーブル.列 | 計算式(関数名 ファイル:行) |
対象にする行の条件 | 根拠 | 確定 / 暫定 |
|---|---|---|---|---|---|---|
| Lv と「{n}日」 | Lv3 / 4日 |
clean_timetables × clean_task_reports(loadConsecutiveInputs()) |
★期間の最終スケジュール日時点★の連続問題日数(consecutiveProblemDaysAtPeriodEnd() report-mapping.ts:594-603)と L1〜L3(judgeEscalationLevel())。上限を超えたら「{n}日以上」(capContinuousProblems() :826-849) |
★2日以上★の施設だけ(filterContinuousProblems() lib/reports/query.ts:424-430。ESCALATION_THRESHOLDS.level2Days)。facilities.deleted_at が NULL の施設(★ロボット0台でも出す★)。連続日数の降順。最初の10件+「他{n}件を表示」 |
★対応するサマリテーブルが無い★(D-102 項4)ので、確定期間も当期も詳細テーブルから / CLAUDE.md §4.5 | 確定 |
| 説明文「{n}日連続で△以下(うち{m}日は出発失敗)」 | 4日連続で△以下(うち1日は出発失敗) |
同上 | m=連続のうち departed === false の行がある日の数(launchFailedDaysInRun() report-mapping.ts:648-665)。文言の選び方は continuousProblemSummary()(:683-690) |
連続は期間をまたいで遡る | 指示書W(混在のときの文言は C案。ユーザー確定 2026-09-11)/ D-156 項1(出発失敗は departed で数える) |
確定 |
| 画面上の表示名 | 例 | テーブル.列 | 計算式(関数名 ファイル:行) |
対象にする行の条件 | 根拠 | 確定 / 暫定 |
|---|---|---|---|---|---|---|
| 種別名 | EmergencyStop |
確定: facility_error_summaries.error_type / 当期: robot_error_reports.error_type |
変換しない(空のときだけ代替文言。formatRawLabel()) |
— | D-077 | 確定 |
| 件数(確定期間) | 12 |
facility_error_summaries.error_count(prisma/schema.prisma:785) |
同じ期間・同じ error_type を全施設ぶん足す(errorBreakdownRowsFromSummary() report-mapping.ts:906-931) |
year / month / week が一致し、絞り込んだ施設のもの(lib/db/index.ts:2516-2523) |
★施設版のサマリで確定(customer_error_summaries は読まない。D-102 項4)★ |
値は TeamWorks が正 |
| 件数(当期) | 3 |
robot_error_reports.clean_timetable_id / error_type、clean_timetables.start_at |
★本清掃の予定を指すエラー行★を error_type ごとに数える(countMainErrorTypesInRange() report-mapping.ts:955-971) |
期間の判定は★エラーが指す本清掃の予定日★(errored_at ではない) |
第8回 A7 / 決定7(エラー発生率の分母が清掃の回数なので、分子も清掃を基準にする) | 確定(制御盤分は未定) |
割合 N% と棒 |
40% |
上の件数 | ★その種別の件数 ÷ 全種別の件数の合計 × 100★(errorBreakdownItemsFromCounts() report-mapping.ts:885-895)。表示は四捨五入(ReportsScreen.tsx:720-722) |
同上 | ★「エラー発生率」ではなく「内訳の構成比」★ | 確定 |
| 見出しの「今週 / 今月 / 今年のエラー発生件数」 | 今週のエラー発生件数(種別ごと) |
— | 週次 / 月次 / 年次の切り替えだけを見て文言を決める(ReportsScreen.tsx:681-689) |
— | ★‹ で先週に戻っても「今週」のまま表示される★(観察。直っていない) | — |
| 画面上の表示名 | 例 | テーブル.列 | 計算式(関数名 ファイル:行) |
対象にする行の条件 | 根拠 | 確定 / 暫定 |
|---|---|---|---|---|---|---|
| 稼働率 / 出発成功率 / 完了率 / エラー率(確定期間) | 88% |
facility_summaries.operation_ratio / launch_ratio / clean_ratio / error_ratio(prisma/schema.prisma:741-744) |
そのまま(kpiFromSummaryRatios()) |
その期間のサマリ行がある施設。★facilities.deleted_at が NULL で、ロボットが1台以上★(lib/db/index.ts:2172-2189) |
D-102 | 値は TeamWorks が正 |
| 同上(当期) | 88% |
clean_timetables × clean_task_reports × robot_error_reports |
KPI(当期)と同じ式を施設ごとに(aggregateCleanStatsByFacility() → kpiFromCleanStats()) |
期間内に本清掃の予定がある施設(未来の予定しか無い施設も「判定なし」で出す。report-mapping.ts:427-431) |
KPI と同じ | 確定(D-158) |
| 前週 / 前月(稼働率・完了率の横) | 90% |
前期の facility_summaries |
そのまま | 同じ施設の1つ前の期間 | D-102 | 確定 |
変化 ▲N / ▼N / → |
▲2 |
上の差 | ★(稼働率の差 + 完了率の差)÷ 2 を四捨五入★(overallDiff() lib/reports/facility-performance-labels.ts:22-27。★D-163 で表から移し、画面と CSV で共有★)。どちらかが無ければ → |
— | モック :795-797 / 指示書F 5-7 | 確定 |
| ステータス | 注意 |
稼働率 | 稼働率が 90 以上 良好 / 75 以上 注意 / それ未満 要改善 / 値なし 判定なし(judgeFacilityStatus() lib/judge/kpi.ts:196-203)。表示名は FACILITY_STATUS_LABEL(lib/reports/facility-performance-labels.ts:42。画面と CSV で共有。D-163) |
— | ★DFA の画面上の判断(TeamWorks 照会なし)★。FACILITY_STATUS_THRESHOLDS は暫定値。「判定なし」はユーザー確定(決定1。2026-09-11) |
★暫定★ |
| セルの色 | 赤字 | 稼働率・出発成功率・エラー率 | 稼働率・出発成功率は上と同じ 90 / 75(rateTone() FacilityPerformanceTable.tsx:78-84)。エラー率は 8 を超えると赤(FACILITY_TABLE_THRESHOLDS.errorRateWarning lib/config/index.ts:569-572) |
— | モックの 80 / 90 を設定値に寄せた(意図的な逸脱。FacilityPerformanceTable.tsx:58-77) |
表示上の目安 |
| 「N施設」 | 10施設 |
— | 状態・施設名・シリアルで絞ったあとの件数(meta.total)。最初の10件+「全N施設を表示」 |
並びは稼働率の昇順・値なしは末尾(sortFacilityPerformance() lib/reports/query.ts:373-388) |
指示書F | 確定 |
サンプルデータのとき: KPI・施設別パフォーマンス・連続問題施設・エラー内訳は★lib/fixtures/reports.ts に直接書かれた作り値★です(計算していない)。KPI は★顧客1の数字としてだけ★持っていて、他の顧客を選ぶと空になります(reports.ts:203-214)。★エラー内訳はサンプルデータでは施設で絞り込めず、全社ぶんが出ます★(lib/db/index.ts:658-687)。
★施設別パフォーマンスの表を CSV で出します★(D-163。2026-10-06。★それまでは押しても何も起きない仮の実装★だった — API 契約に出力の口が無く、指示書F の「やらないこと」だったため。D-069 項8)。ユーザー判断は「A = 施設別パフォーマンスの表」(却下: KPI・両方)。
| 項目 | 中身 | 場所 |
|---|---|---|
| 口 | GET /api/reports/facility-performance/export(★表と同じパラメータ。limit は無視★) |
app/api/reports/facility-performance/export/route.ts:25。契約は d-watch-api.yaml:981 |
| どの施設を、どの順で | ★表と同じ1本 loadFacilityPerformance() が「取得 → 絞り込み → 並べ替え → 連続日数の上限処理」まで済ませた全件★(limit で切らない)。稼働率ワースト順 |
lib/reports/facility-performance-load.ts:38。一覧のルートは戻り値を slice(0, limit) するだけ(app/api/reports/facility-performance/route.ts:33 / :50) |
| 列 | ★画面の9列と同じ順+施設名の直後にシリアル★: 施設名 / シリアル / 稼働率 / 稼働率(前週・前月・前年)/ 出発成功率 / 完了率 / 完了率(前◯)/ エラー率 / 変化 / ステータス | facilityPerformanceCsvHeader()(lib/reports/facility-performance-csv.ts:29) |
| 値 | 率は csvRate()(四捨五入の整数+%。★値が無ければ空のセル★。D-164)。「変化」は overallDiff() / diffText()、ステータスは FACILITY_STATUS_LABEL(★画面と同じ関数・同じ表★。lib/reports/facility-performance-labels.ts)。★ここで判定し直さない★ |
buildFacilityPerformanceCsv()(facility-performance-csv.ts:56) |
| 形式 | 清掃詳細ログの CSV と同じ(UTF-8 BOM・CRLF・★全セルに数式インジェクション対策 D-125★・画面の言語によらず日本語) | csvDocument()(lib/csv.ts:88) |
| ファイル名 | facility-performance_monthly_2026-09.csv の形(週は _2026-10-w1、年は _2025)。★parse 済みの数値から組み立てる★(生のクエリ文字列を入れない) |
facilityPerformanceCsvFilename()(facility-performance-csv.ts:87) |
| 行数の上限 | 清掃詳細ログと同じ MAX_CSV_ROWS(20,000。★> で超えたら 422★、details: { total, max })。施設数なので実際には起きない(歯止めを揃えるだけ) |
route.ts:55-68 |
★表と CSV が食い違わない仕掛けは2段★です。画面側はクエリ文字列を1か所(facilityFilterQuery)から作り、表だけに limit を足す(ReportsScreen.tsx:174-198)。サーバー側は一覧も CSV も loadFacilityPerformance() を呼ぶ。★片方だけ絞り込みを足すと、例外もテスト失敗も出ずに表と CSV の施設がずれます★。
却下: 画面に出ていない列(連続問題日数・前期の出発成功率/エラー率など)を足す(ユーザー未確認。要望があれば足す)。
/clean-area)取得は GET /api/clean-area(app/api/clean-area/route.ts)→ prismaSource.cleanArea()(lib/db/index.ts:1899-1944)→ aggregateCleanArea()(lib/clean-area/aggregate.ts:165-248)。★この画面だけ、完了率の材料と面積の材料で読む行が違います★(D-155 項3 / 指示書AS D9)。
| 画面上の表示名 | 例 | テーブル.列 | 計算式(関数名 ファイル:行) |
対象にする行の条件 | 根拠 | 確定 / 暫定 |
|---|---|---|---|---|---|---|
| 清掃面積(カード・表) | 341,635㎡ |
clean_task_reports.clean_area(prisma/schema.prisma:534) |
施設ごとの合計(groupBy + _sum。loadCleanAreaFacilityStatsWithRowCount() lib/db/clean-area-source.ts:363-412)を粒度ごとに足す。表示は整数+㎡ |
★全行(予定外のタスク・事前チェックの予定に紐づく行も含む)★。期間は★clean_task_reports.started_at(実際に清掃を始めた時刻)★で絞る(index.ts:1906-1922)。★期間未指定なら全期間★(データ保持2年ぶん)。施設マスタに無い facility_id は合計からも外してログに出す |
ユーザー指示 2026-09-16(総清掃面積・指定期間の絞り込み)/ 指示書AN。★予定外を含めるのは「実際に清掃した広さ」だから★(D-155 項3。ユーザー判断 2026-10-02) | 確定(単位は DFA 判断) |
| 清掃対象面積 | 350,000㎡ |
clean_task_reports.task_area(:533) |
同上 | 同上 | 同上 | 確定 |
| 完了率 | 78% |
clean_task_reports.clean_area / task_area / clean_timetable_id、clean_timetables.precheck |
1回ごとの完了率の単純平均(cleanAreaStatsFromRows() clean-area-source.ts:271-313 で Σ と件数 → meanCleanRatio()。aggregate.ts:226-229 / :239-242) |
★clean_timetable_id が NULL でなく、その予定が本清掃(precheck = 0)の行だけ★(clean-area-source.ts:292-293)。task_area = 0 は除く。期間は started_at |
★D-155 項1・項3★ — 完了率の定義は全画面で1つ(D-151 項4-4)。★面積の合計どうし(Σclean_area ÷ Σtask_area)では割らない★ / D-158(確定) |
確定 |
| 清掃実施回数 | 1,024 |
同上 | 完了率と同じ母集団の行数(clean-area-source.ts:304)。★task_area = 0 の行も1回に数える(完了率は 0%)★ |
同上 | D-155 項3(予定外は数えない)。★2026-10-02 に意味が変わった★(旧: 予定外を含む全行数) | 確定 |
| 内訳の行(店舗 / 顧客 / ホールディングス)と「{n}件」 | 店舗 12件 |
施設マスタ facilities / customers / groups |
粒度ごとに Σ と件数を足してから1回だけ割る(aggregateCleanArea())。並びは清掃面積の降順(aggregate.ts:258-264)。最初の10件(CLEAN_AREA_INITIAL_VISIBLE lib/config/index.ts:812) |
施設マスタは deleted_at で絞らない(clean-area-source.ts:41-61。過去の実績が合計から消えないように)。所属なしの顧客は「グループなし」の1行 |
指示書AN / D-011 | 確定 |
| 注記「清掃面積には予定外の清掃を含みます…」 | — | — | 固定文言(CleanAreaScreen.tsx:356-361。f2b91fc) |
— | D-156 論点7(面積はあるのに実施回数0の施設を見た人が不具合と思わないように) | 確定 |
★他の画面と違う点が3つあります★(5B-12 で理由つきで並べます): ①予定外のタスクの面積を含む ②期間の基準が started_at(★予定のある清掃では予定の時刻と同じ値なので、他の画面と同じ日に数える★。違うのは予定外のタスクだけ) ③同じ予定にレポートが2行あると、★2行とも数える★(他の画面は最新の1行だけ)。③は「番人」が件数だけ数えてログ(clean_task_report_rows_per_timetable)に出します(countDuplicatedTimetables() clean-area-source.ts:441-454)。
サンプルデータのとき: lib/fixtures/areas.ts の面積行から作ります(cleanAreaStatsFromFixtureAreas() lib/db/fixture-clean-area.ts)。★サンプルデータには予定外のタスクがありません★。未実施の行(完了率 null)は数えません。番人は常に 0 です。
/ds-logs)| 画面上の表示名 | 例 | テーブル.列 | 計算式(関数名 ファイル:行) |
対象にする行の条件 | 根拠 | 確定 / 暫定 |
|---|---|---|---|---|---|---|
| 合計 / 正常 / 異常 | 12 / 11 / 1 |
controls.status(prisma/schema.prisma:211) |
台数を数える(summarizeDsDevices() lib/ds-devices/query.ts:147-152)。0 → 正常、1 → 異常(connectionStatusFromControlStatus() lib/db/ds-status.ts)。0 / 1 以外は例外 |
controls.deleted_at が NULL(inactive では絞らない。lib/db/index.ts:1528-1531)。分類で絞ったあと、検索語・状態で絞る前(app/api/ds-devices/route.ts:86-96) |
TeamWorks 第2回回答(D-065 Q1-2)/ D-054 | 確定 |
| 接続状態(札) | 正常 |
同上 | 同上 | 同上 | 同上 | 確定 |
| 最終通信 | 10/5 09:12 |
control_reports.reported_at(:373。TIMESTAMP) |
制御盤ごとの★最大値★(groupBy + _max。lib/db/index.ts:1535-1539)→ jstIsoFromDb() |
制御盤ごと。★報告が1件も無い制御盤があると例外で画面ごと止まる★(:1543-1550) |
★MAX(id) ではなく MAX(reported_at)★ — id の順と時刻の順が一致する保証が無いため(index.ts:1511-1520)。TeamWorks 第10回 Q8-3「control_reports は制御盤1台1行(最新の報告だけ)」 |
★2026-10-02 以前のステージングでは UTC で書かれていた★(第10回 N-14a)。JST に統一済みと先方申告、当方の再実測は未了 |
| 備考 | 電源工事中 |
★DB ではない★(data/settings/ds-device-notes.json) |
— | — | DB に対応する列が無い(D-065 Q1-1) | 確定 |
| 件数 | 全12件中 1〜12件 |
— | meta.total |
絞り込み後 | — | 確定 |
サンプルデータのとき: lib/fixtures/ds-devices.ts の値です。
/ds-alerts)| 画面上の表示名 | 例 | テーブル.列 | 計算式(関数名 ファイル:行) |
対象にする行の条件 | 根拠 | 確定 / 暫定 |
|---|---|---|---|---|---|---|
| 未対応 / 対応中 / 対応済み | 2 / 1 / 5 |
control_error_histories.status(prisma/schema.prisma:391) |
0 未対応 / 1 対応中 / 2 対応済み の件数(summarizeDsAlerts() lib/ds-alerts/query.ts:136-145 / resolutionStatusFromControlErrorStatus() lib/db/ds-status.ts) |
結合先の controls.deleted_at が NULL(lib/db/index.ts:1632-1639)。分類で絞ったあと、種別・状態で絞る前(app/api/ds-alerts/route.ts:58-69) |
TeamWorks 第2回回答(D-065 Q1-3)/ D-054 | 確定 |
| 種別(札) | 通信断 |
(固定) | ★実DBでは常に「通信断」★(index.ts:1643) |
— | control_error_histories は制御盤の生死報告なししか持たない(D-067 項1)。セキュリティ無効は対応する列が無い(D-090 項3-9) |
セキュリティエラーの入れ方は DDL 変更待ち(第10回 N-5) |
| 検知 | 10/4 23:40 |
control_error_histories.occurred_at(:393。DATETIME) |
jstIsoFromDb() → 月日・時刻 |
— | — | 確定 |
D-Secアラートの対応状況は、権限上は書き込めますが★まだ作っていません★(CLAUDE.md §3)。
サンプルデータのとき: lib/fixtures/ds-alerts.ts の値です。★サンプルデータにはセキュリティ無効の行があります★が、実DBでは出ません。
/facilities)・スケジュール(/schedules)・システム管理(/system)この3画面は計算をほとんどしません。数字に見えるものの出どころだけ書きます。
| 画面 | 画面上の表示名 | 例 | テーブル.列 | 計算式(関数名 ファイル:行) |
対象にする行の条件 | 根拠 | 確定 / 暫定 |
|---|---|---|---|---|---|---|---|
| 施設ロボットマスタ | 監視 / D-Sec(●) | ● |
facilities.contract(prisma/schema.prisma:141。10桁の文字列) |
2桁目が "1" なら監視、1桁目が "1" なら D-Sec(parseContract() lib/format/contract.ts:26-31 / CONTRACT_DIGITS lib/config/index.ts:740-743) |
facilities.deleted_at が NULL(inactive では絞らない。lib/db/index.ts:1712-1727) |
D-080。★数値として比較しない★(CLAUDE.md §4.6) | 確定 |
| 施設ロボットマスタ | ロボット(メーカー / 機種 / シリアル) | PUDU / CC1 / 8111R… |
devices.maker_id / model_name、robots.serial_number |
maker_id は MAKER_NAMES(1=PUDU / 2=Keenon。lib/config/index.ts:704-707)、表に無い番号は数字のまま |
robots.deleted_at が NULL |
D-085(番号の割り当ては DFA の暫定決定。makers テーブルが提供されない) |
★maker_id の割り当ては暫定★ |
| 施設ロボットマスタ | 件数 | 全12件中… |
— | meta.total |
— | — | 確定 |
| スケジュール | 曜日 | 月水金 / 毎日 |
clean_schedules.weeks(:564。「日月火水木金土」の7桁) |
weekdaysLabel()(lib/format/schedule.ts:17-) |
clean_schedules.deleted_at が NULL |
TeamWorks 回答 B-5 | 確定 |
| スケジュール | 時間 | 02:00 - 翌04:30 |
clean_schedules.start_time / end_time(:566 / :569。4桁の文字列) |
end_time の時が 24〜29 なら「翌」を付けて 24 を引く(formatEndTime() schedule.ts:51-61) |
同上 | TeamWorks 回答 B-5(日跨ぎを 24〜29 時で表す)/ D-060 | 確定 |
| スケジュール | 有効(● / ○) | ● |
clean_schedules.inactive(:572) |
inactive = 0 なら有効(lib/db/index.ts:1795。0 が有効なので論理が逆) |
同上 | — | 確定 |
| システム管理 | 事前チェック「第1回 60分前 / 第2回 30分前」 | 60分前 |
★DB ではない★ | PRECHECK_MINUTES_BEFORE = [60, 30](lib/config/index.ts:232) |
— | ★DFA 側の設定値★。実際に何分前のチェックだったかは DB の clean_timetables.precheck が正(D-105 項13-2)。1清掃につき2回で固定(ユーザー回答 2026-09-11) |
確定(編集不可) |
| システム管理 | 完了率の判定閾値 | 90% 以上 |
★DB ではない★ | GRADE_THRESHOLDS(config/index.ts:18-25) |
— | D-139 | 確定(編集不可) |
| システム管理 | エスカレーション閾値 | Lv2 2日以上連続 |
★DB ではない★ | ESCALATION_THRESHOLDS(config/index.ts:44-49) |
— | CLAUDE.md §4.5 | 確定(編集不可) |
| システム管理 | 連続日数の表示上限 | 30 |
★DB ではない★(data/settings/display-limits.json) |
保存が無い・壊れているときは既定 30(loadConsecutiveDaysMax() lib/settings/display-limits.ts:223-)。3〜365 |
— | ユーザー指示 2026-09-13(指示書AF)。★判定には効かない★(表示の上限と、遡って読む日数だけ) | ★この画面で唯一編集できる★ |
次の食い違いは★仕様どおり★です。不具合を疑う前に、ここに当てはまらないか確かめてください。
| 見え方 | 理由 | 根拠 |
|---|---|---|
| ★清掃面積の画面だけ、清掃の回数や面積が他の画面より多く出る★ | 清掃面積の「面積」は★予定外のタスク(clean_timetable_id が NULL)と事前チェックの予定に紐づく行も含めて★全行を足す。他の画面は本清掃の予定から引くので予定外は出ない |
D-155 項3(ユーザー判断 2026-10-02「実際に清掃した広さなので含める」) |
| ★清掃面積の画面で「面積はあるのに実施回数 0」の施設がある★ | 面積は予定外を含み、実施回数・完了率は含まないため。予定外の清掃しかしていない施設はこうなる | D-155 項3 / D-156 論点7。画面に注記あり |
| ★同じ期間を指定しても、清掃面積と清掃詳細ログで対象の清掃がずれる★ | 清掃面積は★実績の clean_task_reports.started_at★、清掃詳細ログ・レポートの当期は★予定の clean_timetables.start_at★で期間を絞る。深夜0時をまたぐ清掃や、予定より遅れて始まった清掃は別の日に入りうる |
lib/db/index.ts:1906-1922(清掃面積)/ :784(詳細ログ)/ report-mapping.ts:473-474(レポート当期) |
|
|
サマリは clean_task_reports.started_at、アプリの当期は予定の start_at で振り分けるが、★予定のある清掃では started_at に予定の start_at と同じ値が入る★ので一致する |
ユーザー回答 2026-10-05「A」(CLAUDE.md §4.7)。★2026-10-05 にこの文書の第4版が「ずれうる」と書いたが、確定した事実により取り下げ★ |
| ★同じ予定にレポートが2行あると、清掃面積だけ2回数える★ | 他の画面は最新の1行(ended_at ?? started_at)だけを採る。清掃面積は groupBy の合計なので全行が入る。番人がログに件数を出す |
latestReportByTimetableId() / countDuplicatedTimetables()。TeamWorks 第10回 N-8 は「1予定1行の想定(A)」 |
| ★2026-10-01 時点のステージングでは、清掃面積だけ2回分の数字が出て、詳細ログ・ダッシュボード・アラートは空★ | 予定(clean_timetables)が0行で、実績2行はどちらも clean_timetable_id が NULL だった(2026-10-01 実測・先方投入)。予定起点の画面には1行も出ない |
CLAUDE.md §3 / D-151 項7(詳細ログの起点は保留)。★その後のデータ投入(第10回 N-6「10/5 中に入る」)は当方未確認★ |
| ★ダッシュボードの「予定」と、清掃ログの行数(成功+失敗+未実施)が合わない★ | 「予定」は★スケジュールマスタ(clean_schedules)の曜日★から数え、ログは★予定表(clean_timetables)の行★から数える。食い違うとサーバーのログに clean_log_rows_vs_scheduled が出る |
D-049(収集処理の障害を見分けるため、わざと別の出どころにしている)/ app/api/dashboard/route.ts:138-147 |
| ★ダッシュボードの数値カードと要レスキューの件数が合わない★ | 要レスキューは★当日に清掃予定が無い施設も含む★(前日以前のスケジュール存在日で判定する)。数値カードは★各清掃エリアの直近の清掃(7日以内)★(D-159。旧: 当日の予定だけ) | 指示書AI §1-5 / D-159 |
| ★ダッシュボードの数値カードとスケジュール進捗の件数が合わない★(2026-10-06 から) | 数値カードは★直近モード★(各清掃エリアの直近1件・前日の清掃も入る)、進捗は★当日★の予定と出発 | D-159 / 指示書AV 4-1。★意図した違い★ |
| ★レポートの当期で、稼働率が以前より低く出る★(2026-10-05 から) | 稼働率の分子が「レポート行がある回数」から★正常終了(status = 4)の回数★に変わった。中断・キャンセル・異常終了・status 6 の清掃は稼働率に入らない(出発成功率には入る)。★施設ステータスの閾値は据え置き★ |
第11回 P-6=A。D-158 項1・項8。指示書AU 段階3 |
| ★レポートで、今週と先週を比べると、同じ施設でも完了率の出し方が「人」で違う★ | 今週(当期)はアプリ、先週(確定期間)は TeamWorks のバッチが計算する。式は同じ(D-155)にし、task_area = 0 の扱い(P-5=B)・clean_timetable_histories を読むこと(P-7=C)も第11回の回答に合わせた(D-158)。★ただし先方のバッチの実挙動を当方は確かめていない★ので、完全に一致する保証は無い |
D-155 項1 / D-158 |
| ★施設別パフォーマンスに出ない施設がある★ | ★ロボットが0台の施設は出さない★。確定期間は、その期間のサマリ行がある施設だけ | lib/db/index.ts:2172-2189 |
| ★連続問題施設には、施設別パフォーマンスに無い施設が出ることがある★ | 連続問題施設は★ロボット0台の施設も出す★(決定5)。施設別パフォーマンスは出さない | report-mapping.ts:758-760 / index.ts:2172-2189 |
★清掃詳細ログで ✓(出発した)なのに完了率が 0% で判定が × の行がある★ |
task_area = 0 の清掃(または清掃面積 0 の清掃)。★2026-10-05 から 0% と出る★(旧: -)。ダッシュボードでは「失敗」、アラートの出発失敗・要レスキューの未実施には★入らない★ |
第11回 P-3=A・P-5=B(D-158)/ D-156 項1 |
| ★「清掃正常終了」なのに判定が △ や × の行がある / 「清掃中断」なのに ◎ の行がある★ | ★清掃結果(status)は判定に使わない★。判定は完了率だけで決める |
D-155 項2(ユーザー判断 2026-10-02)。CLAUDE.md §4.4 の「中断で返る=失敗」は実データを見て見直す |
| ★◎ が多めに出る★ | clean_area は同じ場所を何回も清掃した面積の総和なので、1回ごとの面積比が 100 を超えやすく、上限 100 で切られる(ユーザー提供CSV 189行のうち 129行=68.3%、先方投入の実データ) |
D-155 項1(承知のうえで採用)/ 第10回 N-4 |
| ★画面の「90%」が ◎ の行と ○ の行に分かれる★ | 表示は四捨五入、判定は生値。89.5 は表示 90% で ○ | CLAUDE.md §4.3(章5-3) |
| ★エラー種別の内訳の「%」を足すと 100 になり、KPI のエラー発生率と一致しない★ | 内訳の % は★種別ごとの構成比★(全エラー件数に対する割合)。エラー発生率は★エラー件数 ÷ 予定回数★(第11回 P-6=B) | report-mapping.ts:885-895 / kpi.ts:182-184 |
| ★アラートの「未解消」が、対応しても減らない行がある★ | ★出発失敗の行は書き込み先が無いので、ずっと「未解消」★に数えられる | D-105 / lib/db/index.ts:1408-1418 |
| ★ダッシュボードのセキュリティ無効バナーが、サンプルデータでは出るのに実DBでは出ない★ | 実DBの読み取りは通信断しか返さない(セキュリティ無効に対応する列がどのテーブルにも無い) | D-090 項3-9 |
| ★サンプルデータに切り替えると、レポートの数字が「計算されていない」★ | サンプルデータのレポートは lib/fixtures/reports.ts の作り値。顧客1以外を選ぶと KPI は空、エラー内訳は施設で絞れない |
lib/db/index.ts:642-687 |
lib/config/業務ルールの数値は、コードのあちこちに散らばっていません。 lib/config/ の5ファイル(index.ts 812行 / advice-rules.ts 133行 / env-check.ts 192行 / allowed-ips.ts 63行 / robot-status-match.ts 46行)に集約されています。CLAUDE.md §7 が「閾値(90/70/10、猶予30分、L2=2日/L3=3日)のハードコード禁止」と定めているためで、判定ロジックも画面も、数値を直接書かずここを読みに行きます。
見分け方: export const で大文字スネークケース(GRADE_THRESHOLDS のような名前)のものが設定値です。
この章では各定数に「根拠」の列を付けました。誰が・いつ・何に基づいて決めた値かです。読み方は次のとおりです。
| 根拠の書き方 | 意味 | 変えるときに要るもの |
|---|---|---|
| TeamWorks 合意 / TeamWorks 回答(第n回) | TeamWorks(DBとサマリ集計の担当)と合わせた値。サマリ側も同じ値で計算している | ★TeamWorks との合意の取り直し★。片側だけ変えると確定期間(サマリ)と当期(アプリ)で数字の定義が割れる |
| ユーザー指示(日付) | daichi(D-Watch の責任者)が決めた値 | ユーザーの確認 |
| DFA の判断 | 当方(開発側)が理由を付けて決めた値。多くは暫定 | コメントの理由が今も成り立つかの確認 |
| DB の列の型 | DB の列の長さ・型から機械的に決まる値 | DB の型が変わったら追随 |
★判定に効く数値(6-1)を変えると、過去の清掃の判定まで変わります★(昨日 ◎ だった清掃が今日は ○ になる)。画面に保存された判定は無く、毎回計算し直しているからです。
2026-09-14(8f2f29e) |
2026-10-01(第3版 d23e203) |
★2026-10-05(第4版 a1d7e43)★ |
10/01 からの増減 | |
|---|---|---|---|---|
lib/config/index.ts の export const |
29本 | 36本 | ★41本★ | ★+5★ |
allowed-ips.ts |
4本 | 4本 | 4本 | ±0 |
robot-status-match.ts |
2本 | 2本 | 2本 | ±0 |
advice-rules.ts |
1本 | 1本 | 1本 | ±0 |
env-check.ts |
0本(関数3本のみ) | 0本(関数3本のみ) | 0本(★関数4本★) | ±0 |
| 合計 | 36本 | 43本 | ★48本★ | ★+5★ |
本章は ★export const だけを数えています★(index.ts の関数 cleanErrorLabel() / cleanTaskStatusLabel() は数えない)。数え方は grep -c "^export const" lib/config/index.ts(=41)です。
★10/01 → 10/05 で増えた5本★(すべて index.ts。指示書AS・AT。git で d23e203 と比較):
| 増えた定数 | 何のため | 根拠 |
|---|---|---|
RUN_COMPLETION_RATE |
1回ごとの完了率の式の部品(上限100・小数第1位まで) | TeamWorks 回答 第10回 N-3 / D-155 項1 |
CLEAN_TASK_STATUS |
清掃結果(clean_task_reports.status)のコード値 |
TeamWorks 回答 第10回 N-2 / D-155 項2 |
CLEAN_TASK_STATUS_LABELS |
同・表示名 | 同上 |
PRECHECK_NOTE_MAX_LENGTH |
アラートの備考の上限 | 指示書AT §3-6 / D-156 |
PATH_ERROR_ID_MAX_LENGTH |
PATCH /prechecks/{id}/status の {id} の上限 |
DB の列の型(robot_error_reports.error_id が varchar(50)) |
関数 cleanTaskStatusLabel() も増えました。env-check.ts には関数 databaseWriteCredentials() が増えました。★消えた定数・値が変わった定数はありません★(git diff d23e203 HEAD -- lib/config/index.ts で削除行はコメントだけ)。
lib/config/index.ts| 定数 | 行 | 現在の値 | 何を決めるか | 変えると何が起きるか | 根拠 |
|---|---|---|---|---|---|
GRADE_THRESHOLDS |
:18-25 |
excellent 90 / good 70 / poor 10 | ◎○△×の境目。★90.0 は ◎、70.0 は ○、10.0 は △★ | 清掃詳細ログ・アラート・レポート・アドバイス R2/B1・要レスキューが全部同時に変わる。唯一の判定者は judgeGrade()(lib/judge/grade.ts) |
値は TeamWorks 合意(CLAUDE.md §4.1)。★境界を「以上」にしたのはユーザー指示 2026-09-16(D-139。D-020b を破棄)★。× だけは「10%未満」のまま |
RUN_COMPLETION_RATE ★新(10/02)★ |
:37-42 |
cap 100 / decimals 1 | 1回ごとの完了率 = min(clean_area ÷ task_area × 100, 100) を小数第1位まで切り捨て。消費者は runCompletionRate()(lib/judge/kpi.ts:144)と fixtures だけ |
全画面の完了率と ◎○△× が同時に変わる。★TeamWorks のサマリと同じ式なので、片側だけ変えると前期比(サマリ)と当期(アプリ)が別の式になる★ | TeamWorks 回答 第10回 N-3(「100より大きい値は100、小数点第2位以下切捨て」)。ユーザー判断「一旦B」(D-155 項1)。★確定★(第11回 P-2=A「今の式のまま」。D-158) |
ESCALATION_THRESHOLDS |
:44-49 |
level2Days 2 / level3Days 3 | 何日連続(スケジュール存在日)で L2 / L3 に上げるか | 警告の出やすさ。アドバイス R3/R4、要レスキュー R-2、連続問題施設の抽出、CONSECUTIVE_DAYS_DISPLAY.minMax も連動 |
TeamWorks 合意(CLAUDE.md §4.5。Phase A の R1〜R4) |
COMPLETION_DROP |
:65-68 |
dropRatio 0.2 | 完了率が「前回」比で何割下がったら「急落」か(要レスキュー R-3)。★差(ポイント)ではなく比★。「前回」は前回のスケジュール存在日 | 要レスキュー R-3 の件数 | ユーザー指示 2026-09-15(「比で」)。指示書AI / D-135 |
RESCUE_THRESHOLDS |
:87-90 |
notExecutedDays 3 | 未実施(出発失敗)が何日連続で要レスキューか(R-1) | 要レスキュー R-1 の件数。★ESCALATION_THRESHOLDS.level3Days とは別物(たまたま両方3)★ |
ユーザー指示 2026-09-15(「出発失敗3日」「未実施3日連続」)。D-135 |
CONSECUTIVE_DAYS_DISPLAY |
:151-158 |
defaultMax 30 / minMax = level3Days(3)/ maxMax 365 |
連続日数の表示上限(超えたら「30日以上」) | ★表示だけ。判定(L1〜L3・R3/R4)は上限前の生値★。既定値は画面から変更でき、保存先は data/settings/display-limits.json |
ユーザー指示 2026-09-13(指示書AF / D-130)。30・365 の理由はコメント :133-150(DFA の判断) |
CONSECUTIVE_LOOKBACK |
:185-190 |
calendarDaysPerScheduleDay 7 / plusDays 1 | 連続日数のために表示期間より前を何日さかのぼって読むか(=表示上限 × 7 + 1 日) | ★plusDays を0にすると「30日以上」が二度と出なくなる★。週1回より疎な施設が出ると係数7では足りず、★連続日数が静かに小さく出る★ |
DFA の判断(「どの施設も7カレンダー日に1回は清掃予定がある」という前提。指示書AC 段階6 / D-131) |
FACILITY_STATUS_THRESHOLDS |
:551-556 |
good 90 / warning 75 | 稼働率の 良好 / 注意 / 要改善(施設別パフォーマンスのステータス列と色) | ステータス列と絞り込み。★エラー率用ではない★ | DFA の判断(暫定値。D-117 で「DFA 側の UI 判断」と確認。モックは 80/90 だった。D-069 項3) |
FACILITY_TABLE_THRESHOLDS |
:569-572 |
errorRateWarning 8 | エラー率のセルを赤くする目安(8.0 は薄字のまま) | ★表の色だけ。判定には一切影響しない★ | モック(d-watch-mock.jsx:807 の > 8)。指示書F |
LAUNCH_GRACE_MINUTES |
:196 |
30 | (名目上)未実施と判定するまでの猶予 | ⚠️ ★何も起きません★(下記) | D-025(暫定)。★D-091 項1 で「予定+猶予30分を当方で計算しない」と決まり、使われなくなった★。★D-173 の結果待ちも使わない(RESULT_WAIT_MINUTES は別の定数)★。f2b91fc 以降は :195 |
★RESULT_WAIT_MINUTES★(★新・10/07★) |
:205(a961829) |
120 | ★結果待ちの長さ(分)★。予定の終了(無ければ開始)からこの分数のあいだは、レポートが無くても「結果待ち」とし、出発失敗に数えない(清掃中も含む。5.9b) | ★延ばすと確定した出発失敗が出るまでが遅れる・縮めるとレポートが遅れた清掃が一時的に出発失敗に見える★。詳細ログ・CSV・ダッシュボード・アラートの出発失敗・連続日数・要レスキュー・当期の率が一斉に変わる(消費者は isResultPending() だけ)。★影響は直近の清掃に限られる★(待つ時間を過ぎた清掃には効かない)。★LAUNCH_GRACE_MINUTES を流用しないこと★ |
★ユーザー判断 2026-10-07(「B」・待つ時間は一旦2時間・設定で変えられるように)。D-173★。★暫定★ — 第12回(★R-2★。04f974a で送付版)でレポートの最大の遅れを聞いて見直す。D-091 項1 をこの件に限り上書き |
★DASHBOARD_LATEST_RUN★(新・10/06) |
:213-219(a961829) |
lookbackDays 7 | ダッシュボードの数値カード・表に出す「直近の清掃」を、何日前の予定まで遡るか(★境界を含む★)。★候補は「予定の終了 ≤ 現在」★ | ★どの予定を見せるかだけに効く★。1行の判定(出発・◎○△×・L1〜L3)には効かない。★LAUNCH_GRACE_MINUTES を流用しないこと★(意味が違う) |
7日はユーザー指示 2026-10-06(D-159 項3 / 指示書AV D6)。★旧 settleMinutes(30。予定の終了+30分たつまで候補にしない)は D-173 で削除★ — レポートは30分より遅れうる(ユーザー回答 2026-10-07。D-172)ため、終わってからレポートが届くまでは行の旗 result_pending(RESULT_WAIT_MINUTES)で「結果待ち」と出す |
LAUNCH_GRACE_MINUTES は★今も死に定数です★(2026-10-05 確認)lib/config/index.ts:196 に 30 と書かれていますが、★この値を import しているコードは1つもありません★。参照はすべて「ここから読まないこと」という趣旨のコメントだけです(lib/db/clean-log-mapping.ts:157 / :292、lib/db/index.ts:948 / :1368 / :1405)。出発したかどうかは「clean_task_reports の行があるか」で決め、未来の予定は start_at だけで外します(D-091 項1 / D-107)。★この値を変えても挙動は一切変わりません。★ CLAUDE.md §4.4 は猶予30分を「設定値として外出しする」と書いていますが、判定には使っていない、というのが実態です。★D-173(2026-10-07)で「予定の終了+120分」は計算するようになりましたが、それは別の定数 RESULT_WAIT_MINUTES(lib/config/index.ts:205)です★(5.9b。CLAUDE.md §4.4 にも a961829 で注記が入った)。
GRADE_THRESHOLDS(判定)と FACILITY_TABLE_THRESHOLDS(表の色)は別物★(lib/config/index.ts:558-568 のコメント)。混ぜると「判定基準を変えたつもりが表の色だけ変わった」が起きます。エラーは出ません。RESCUE_THRESHOLDS.notExecutedDays(3)と ESCALATION_THRESHOLDS.level3Days(3)は別物★(:79-82)。片方を変えたときにもう片方まで動くと、変えた人が意図しない条件まで変わります。lib/config/index.ts| 定数 | 行 | 現在の値 | 何を決めるか | 変えると何が起きるか | 根拠 |
|---|---|---|---|---|---|
RESCUE_INITIAL_VISIBLE |
:120 |
5 | 要レスキューを最初に何件見せるか | ★APIが返す件数の上限ではない★(「もっと表示」で全件)。★APIで切ると、切られたことに運用担当が気づけない★(実データで7件中2件が黙って消えた) | ユーザー指示 2026-09-15(「表示は5件、もっと表示…で全件」)。D-137 |
CLEAN_AREA_INITIAL_VISIBLE |
:812 |
10 | 清掃面積の内訳を最初に何件見せるか | 同上の作法 | DFA の判断(指示書AN。D-137 と同じ作法) |
MAX_DATE_RANGE_DAYS |
:442 |
731 | 清掃詳細ログで指定できる期間の上限(日)。両端を含む日数。★上限であって既定ではない(初期表示は期間を指定しない=全期間。D-159 追記)★ | 広げるほど1回の問い合わせが重い | ユーザー指示 2026-09-15(「詳細ログは2年分見れるように」。指示書AI 段階7 / D-136)。730 ではなく 731 なのはうるう日のため |
MAX_CSV_ROWS |
:469(★f2b91fc では :483★) |
20,000 | CSVエクスポートの行数上限。★ちょうど 20,000 行は出せる★。★清掃詳細ログとレポート(施設別パフォーマンス。D-163)の2つの CSV で共有★ | ★日数ではなく行数で決めている★(同じ2年でも店舗数で桁が違う) | ユーザー指示 2026-09-15(「出力できない期間を分かるように」。指示書AI 段階8 / D-136)。★値そのものは暫定★ — 本番の清掃回数が分からないので据え置き(D-144 訂正。コメント :439 :451 の「約25万行」は施設200件前提の古い試算で、D-144 でロボット30台未満に置き直した) |
PAGINATION |
:407-423 |
既定50 / 上限200 / ページボタン7 | 一覧APIの1ページ件数 | maxVisiblePages は★奇数かつ7以上★であること(テストで固定) |
API契約(d-watch-api.yaml:1498 の default: 50 maximum: 200)。ページ送りの部品は指示書AA(D-120) |
REPORT_YEAR |
:505-510 |
min 2000 / max 2100 | レポートで受け付ける西暦の範囲 | 外すと year=-1 や year=1000000 が 500 を出し、★攻撃者が選んだ値がサーバログに載る★ |
DFA の判断(2026-09-14 の実測で 500 を確認。理由はコメント :471-503) |
DB_IN_CLAUSE_MAX_ITEMS |
:548 |
1,000 | in 句に一度に渡す件数(超えたら分割) |
小さいとクエリ回数が増え、大きいとSQL1本が長くなる。★本番で実測して調整してよい★ | DFA の判断(指示書AC §E-5。Prisma の自動分割 65,535 を十分下回る値) |
SUCCESS_RATE_TARGET |
:575 |
95 | ダッシュボードの「目標: 95%」の表示 | ★表示文言だけ。計算には使われない★ | モック(d-watch-mock.jsx:494 の「目標: 95%」) |
DEVICE_NOTE_MAX_LENGTH |
:588 |
200 | D-S制御盤「備考」の最大文字数 | 入力欄(DsLogScreen.tsx:107)とAPI検証が同時に変わる |
DFA の判断(暫定。保存先がファイルなので肥大を防ぐ) |
PRECHECK_NOTE_MAX_LENGTH ★新(10/02)★ |
:599 |
400 | アラートの備考(robot_error_reports.comment に保存)の最大文字数。★トリム前で数える★ |
★d-watch-api.yaml:742 の maxLength: 400(★/errors 側は :866★。f2b91fc) と一致させること★(片方だけ変えない)。入力欄(AlertsScreen.tsx:120)と lib/prechecks/status-update.ts の検証が同時に変わる |
指示書AT §3-6(D-156)。comment 列は text で長さの制約は無いが、画面は1行メモとして表示するため |
ADVICE_TITLE_MAX_LENGTH |
:627 |
100 | アドバイス見出しの文字数 | 既定値の実測は最長14文字 | DFA の判断(DEFAULT_ADVICE_RULES の実物を数えて余裕を掛けた。2026-09-13。D-124) |
ADVICE_TEXT_MAX_LENGTH |
:629 |
400 | 推定原因の文字数 | 既定値の実測は最長62文字 | 同上 |
ADVICE_ACTION_MAX_LENGTH |
:631 |
100 | 推奨アクション1件の文字数 | 既定値の実測は最長19文字 | 同上 |
ADVICE_ACTIONS_MAX_ITEMS |
:633 |
10 | 推奨アクションの件数 | 既定値の実測は3件 | 同上 |
PATH_ID_MAX |
:663 |
Number.MAX_SAFE_INTEGER |
URL の {id} の上限 |
★上限の根拠はDBの列の幅ではなくJavaScript側の桁落ち★ | DFA の判断(JavaScript の数値の限界) |
PATH_SERIAL_NUMBER_MAX_LENGTH |
:680 |
100 | {serial_number} の文字数 |
★文字種は絞らない★(形式は TeamWorks の収集処理が決める) | DB の列の型(controls.serial_number が varchar(100)) |
PATH_ERROR_ID_MAX_LENGTH ★新(10/02)★ |
:692 |
50 | PATCH /prechecks/{id}/status の {id} の文字数。超えたら 422 |
★文字種は絞らない★ | DB の列の型(robot_error_reports.error_id が varchar(50))。指示書AT 段階1 |
lib/config/index.ts)| 定数 | 行 | 値 | 何を決めるか | 押さえどころ | 根拠 |
|---|---|---|---|---|---|
DB_DATETIME_OFFSET_MINUTES |
:211 |
9 * 60(540) |
DB の時刻を「JST の壁時計の値」として読むための時差。消費者は fromDbJst() / toDbJst()(lib/format/date.ts:67 / :82)だけ |
— | TeamWorks 回答 第8回 A2(「JSTです」)。D-115。★DB の時刻は列の型によらず JST の壁時計の値★(CLAUDE.md §3)。DB のドライバが文字列を UTC とみなして返すので、この値を引いて真の時刻に戻している(lib/format/date.ts:48-58)。★変えると全画面の時刻が9時間ずれ、深夜の清掃が別の日に動く★。★コメント(:200-210)の「@@system_time_zone = JST」は第8回の回答で、ステージングの実測は UTC だった★(2026-10-02 に先方が JST に変更。当方の再実測は未了。D-155 項4)。★アプリの値は変えていない★ |
PRECHECK_MINUTES_BEFORE |
:232(f2b91fc では :244) |
[60, 30] |
事前チェックを何分前にやるか(システム管理画面の表示・シード・fixtures の既定値)。★清掃詳細ログの列の割り当てには使わない★(D-178。列は DB の precheck の分数の大きい順) |
★2回で固定と確定済み★。この配列を3要素にしても詳細ログの列は増えない(DB に3つ以上あれば開始に近い2つを残し data.anomaly。D-178)。★旧: ここに無い分数(10分前など)は詳細ログの列に出なかった(D-169)★。アラート・エラー一覧は生の分数で出す。★予定行が無い回は null(画面「-」)★(D-171) |
ユーザー回答 2026-09-11(「2回だけ」)。precheck が分数そのものになったのは TeamWorks 回答 第7回 A5-2(D-112)。★表示する回を設定値で選べるようにするのは後で★(D-169。2026-10-07 ユーザー判断「このままで問題ない」) |
CLEAN_TIMETABLE_PRECHECK |
:243-246 |
main 0 | clean_timetables.precheck の 0 が本清掃。それ以外は「何分前か」の数字そのもの |
本清掃の絞り込みに全箇所で使う(0 を直書きしない) |
TeamWorks 回答 第7回 A5-2 / A5-5(D-112) |
CLEAN_ERROR / CLEAN_ERROR_LABELS |
:294-305 / :317-323 |
0 正常〜4 清掃ロボット異常 | clean_status_histories.clean_error のコード表。★2026-09-30 以降、未実施判定にも清掃結果表示にも使っていない★(取得元が clean_task_reports に変わり、そちらに clean_error 列が無い)。★アプリのコードで読んでいるのはローカル用シード(scripts/seed-local-db.ts)だけ★ |
clean_statuses 側で今後使う可能性があるため削除していない(指示書AQ §1-2) |
定義書の原文(docs/handoff/analysis/table-definitions-02-clean-error.md:213)。D-146 |
cleanErrorLabel()(関数) |
:333-335 |
— | 表に無いコードは String(code)。★現在どの画面も通していない★ |
★「その他」「不明」に潰さない★ | D-089 項3 / D-104 項2 |
CLEAN_TASK_STATUS / CLEAN_TASK_STATUS_LABELS ★新(10/02)★ |
:348-361 / :371-378 |
0 清掃開始前 / 1 清掃中 / 2 清掃中断 / 3 清掃キャンセル / 4 清掃正常終了 / 5 清掃異常終了 | ★clean_task_reports.status(清掃結果)の表示名★。清掃詳細ログの「清掃結果」列と CSV に出る。★CLEAN_ERROR とは別のテーブルの別のコード表★なので互いに読み替えない。★判定には使わない★ |
★原文から1文字も変えないこと★。英語は lib/i18n.ts で訳す |
TeamWorks 回答 第10回 N-2(docs/handoff/TeamWorks回答_第10回_20261002.md:63)。D-155 項2 |
cleanTaskStatusLabel()(関数)★新★ |
:384-386 |
— | 表に無い値(CSV 実測の 6 など。意味は未受領)は String(status) を返す |
★「その他」「不明」に潰さない★ | D-155 項2。6 の意味は第11回で照会中(D-157) |
ROBOT_STATUS_STANDBY |
:404 |
"Standby." |
事前チェックの予定行があり、エラーが記録されていないときに「1h前ST / 30分前ST」へ入れる値。★末尾のピリオド込みが原文★。★予定行そのものが無いときは入れず null(D-171)★ |
★robots.status を読んで得た値ではない★(エラーが無いことの読み替え) |
TeamWorks 回答 第2回(robots.status コード表の '0' => 'Standby.')。D-098 / D-065 Q2 |
MAKER_NAMES |
:704-707 |
{1: "PUDU", 2: "Keenon"} |
devices.maker_id → メーカー名。未知の番号は数字のまま出す |
番号を変えると全画面の機種名が変わる | ★DFA の暫定決定★(makers テーブルは提供されない=D-057 A-1。D-085)。実データの maker_id を受領したら差し替える |
ROBOT_MODELS |
:727-732 |
CC1 のみ supported: true(MT1 / C30 / C40 は false) |
対応機種。★未対応の機種も画面から消さず「未対応」バッジで出す★ | ★対応機種の追加は false → true の1文字だけ★ |
ユーザー指示 2026-09-09。D-087 |
CONTRACT_DIGITS |
:740-743 |
dSecurity 0 / dWatch 1 | facilities.contract(10桁の文字列)の桁位置。唯一の消費者は lib/format/contract.ts |
★数値として比較しないこと★(全施設が「契約なし」になる) | D-080(CLAUDE.md §4.6) |
GROUP_ID_NONE |
:758 |
0 | customers.group_id の「グループなし」。★APIには0を漏らさず null に変換する★(lib/format/group-id.ts) |
=== 0 を他所に散らすと「グループ0」という存在しないホールディングスが出る |
TeamWorks の設計(NOT NULL でセンチネル値方式)。D-011 |
ROBOT_STATUS_TONES |
:783-790 |
日本語6値の色分け | ⚠️ 下記 | — | fixtures 由来の暫定値(D-077 項3) |
AREA_UNIT |
:804 |
"㎡" |
面積の表示単位。★画面やAPIに直書きしないこと★ | 回答が違っていたら直すのはここ1行 | ★ユーザー指示 2026-09-16(「単位は㎡でよいです」)。DFA 側の判断であって TeamWorks の確認ではない★(第9回5章で照会中。D-141) |
ROBOT_STATUS_TONES は現状ほとんど機能しませんこの表は日本語6文字列の完全一致でしか色を付けず、載っていない値はすべてグレーになります(lib/theme-status.ts)。CLAUDE.md §4.8 で「エラー種別を変換せずDBの生値をそのまま表示する」と確定しているため、実データは EmergencyStop のような英語識別子で来ます。★実データ接続後は色がほぼ全部グレーになりますが、それが期待どおりの挙動です★。Standby. もあえて表に入れていません(:770-782)。
lib/config/advice-rules.tsR1〜R4・B1〜B3 の7ルールの文言の既定値 DEFAULT_ADVICE_RULES がここにあります(:34-127)。★発火条件はここではなく lib/judge/advice.ts にあります★(章5)。このファイルの condition は画面に説明として出す文字列であって、動作には影響しません。編集できるのは title / text / actions の3つだけで(:130-133)、保存先は data/settings/advice.json です。 ★ただし編集の窓口 PATCH /api/settings/advice を呼ぶ画面は、いまありません★(システム管理画面は表示だけ。章4)。
根拠: R1〜R4 は TeamWorks 合意(CLAUDE.md §4.5 の Phase A)、B1〜B3 はモックにあったエラー別のアドバイス(:12)。
lib/config/robot-status-match.ts| 定数 | 行 | 現在の値 | 意味 | 根拠 |
|---|---|---|---|---|
B2_HANDLE_UP_STATUSES |
:35 |
["Handrail Lifted"] |
B2(ハンドルアップ検知)を出す error_type の値。⚠️ ★暫定値★。受領済みの error_type 24値のリストに含まれておらず、実データでの存在は未確認 |
ユーザー指示 2026-09-08(「暫定で変更のおそれはあります」。D-078 項2) |
B3_OFFLINE_STATUSES |
:46 |
★[](空)★ |
⚠️ ★空なので B3(オフライン検知)は絶対に出ません★。オフラインを表す値が未判明のため意図的に空 | D-078 項2 / D-077 項3。★推測で値を入れない★(CLAUDE.md §7) |
比較は前後の空白を落とし、大小文字を無視します(lib/judge/advice.ts)。ここに1行足すだけでアドバイスが出るようになります(同時に lib/judge/__tests__/advice.test.ts の該当テストが落ちますが、それは「値を足した合図」です)。
lib/config/allowed-ips.ts★このファイルの値は環境変数ではなくコードです。.env.local を直しても変わらず、変更にはデプロイが必要です★(ALLOW_DIRECT_ACCESS だけは例外で環境変数由来)。index.ts に入れていないのは、proxy.ts に判定ロジックの部品を引きずり込まないためです(:4-8)。
| 定数 | 行 | 現在の値(2026-10-05 確認) | 押さえどころ | 根拠 |
|---|---|---|---|---|
ALLOWED_IPS |
:24-26 |
★空(例がコメントで1行あるだけ)★ | ★完全一致のみ★。CIDR・レンジ・ワイルドカードは効かず、192.0.2.0/24 と書くと★常に拒否★になる。★プライベートIPを書かないこと★ |
D-072 / D-077 項5 / D-079 項1 |
ALLOW_ALL_WHEN_EMPTY |
:35 |
true |
許可リストが空のとき「制限をかけない」。★いまは IP 制限が事実上かかっていない★ | 既定 true は開発を止めないため(D-101)。★false にするか許可リストを入れるかは、リリース直前のセキュリティ強化の対象として CLAUDE.md §3 に列挙済み★(ユーザー判断 2026-10-01。D-154) |
ALLOW_DIRECT_ACCESS |
:52 |
環境変数由来(未設定=false) |
nginx を経由しない直接アクセスを通すか。★=== "true" なので、タイポ・1・TRUE はすべて拒否(安全側)★。USE_FIXTURES(!== "false")と向きが逆なのは意図的 |
D-101 |
TRUSTED_PROXY_HOPS |
:63 |
1 | X-Forwarded-For の★末尾から★何番目を接続元とみなすか。2段構成になったら 2 にするだけで直る |
ユーザー回答 2026-09-08(LB と nginx は同一=1段。D-079 項1) |
lib/config/env-check.ts★このファイルに export const はありません★(関数4本: checkEnv() :43 / databaseWriteCredentials() :162 / hasError() :184 / formatFinding() :189)。scripts/check-env.ts(npm run check:env)から呼ばれ、AUTH_TOKEN が changeme のままでないか、ALLOW_DIRECT_ACCESS=true が残っていないか、書き込み用アカウント(DATABASE_WRITE_USER / DATABASE_WRITE_PASSWORD)が片方だけになっていないか等を調べます。
★databaseWriteCredentials()(10/02 に追加。指示書AT §3-2)は lib/db/prisma.ts の getWritePrisma() も使います★ — 起動前の検査と実際の接続で、判定の基準を1本にそろえてあります。2つとも未設定なら参照用の接続で書き、片方だけなら例外です(章8 8.10)。★エラーメッセージに値そのものを出さない★(DATABASE_URL にはパスワードが入り、出力はCIのログに残るため)のが設計上の要点です。
画面から「施設名で絞って、日付の新しい順で、50件ずつ」と注文が来ます。その注文を受け取って検品し、並べて、必要なぶんだけ切り出すのがクエリ層(lib/<画面>/query.ts)です。DBそのものには触りません(それは lib/db/)。
★2026-09-14(D-134 時点)からの増減★
| 2026-09-14 | 2026-10-01 | 2026-10-05 | 増えたもの | |
|---|---|---|---|---|
lib/*/query.ts |
7本 | ★9本★ | 9本 | ★lib/clean-area/query.ts(清掃面積。指示書AN)★ / ★lib/masters/query.ts(ホールディングス・顧客。指示書AK)★ |
lib/api/ |
3本 | ★4本★ | 4本 | ★master-id.ts★ |
lib/types/index.ts |
896行 | 1,361行 | ★1,398行★ | 10/05: ★PrecheckStatusUpdateRequest / PrecheckStatusPatch★(指示書AT) |
lib/settings/ |
5本 | 5本 | 5本 | —(旧版が「3ファイル」と書いていたのは誤り) |
★lib/prechecks/status-update.ts★ |
— | — | ★新★ | ★アラートの対応状況・備考の更新規則★(純粋関数。下の差分表と章8-10) |
★lib/prechecks/edit-drafts.ts★(改訂4) |
— | — | ★新(10/06)★ | ★編集モードの下書きと一括保存★(純粋関数。D-165)。★lib/prechecks/status-cycle.ts は D-161 で削除★ |
★lib/reports/facility-performance-load.ts★(改訂4) |
— | — | ★新(10/06)★ | ★施設別パフォーマンスの取得〜並べ替え(表と CSV で共有。D-163)★ |
まず全部に共通する形を1回だけ説明します。各ファイルの説明は、そのあとの差分表だけです。
lib/api/query-result.ts)9本すべての parseXxxQuery() は、同じ型 QueryParseResult<T>(lib/api/query-result.ts:23-25)を返します。中身は2つの状態のどちらかです。
| 状態 | 中身 | ルート側がすること |
|---|---|---|
{ ok: true, query } |
検品を通った値 | そのままデータ取得へ渡す |
{ ok: false, error, status } |
422 などのエラー本文一式 | そのまま HTTP 応答にする |
★失敗を null で返さないこと★(query-result.ts:5-7)。null だと「どのパラメータが、なぜ不正か」を呼び出し側に渡せず、ルートは details: null の 422 しか組み立てられません。失敗の組み立ては fail()(:32-43)1本に寄せてあり、details は { パラメータ名: [理由] } の形で入ります。
① parseXxxQuery() 注文を検品する(形式・範囲・enum)。不正なら 422 ② filterXxx() 条件に合う行だけ残す ③ sortXxx() 並べ替える(同値のときの安定化キーまで決める) ④ paginate / cap そのページぶんを切り出す/表示上限をかける
★①を飛ばして生の searchParams をデータ層に渡さないこと★(lib/clean-logs/query.ts:52-54)。検品を通った値だけがDBまで届く、という1段を必ず挟みます。
lib/api/master-id.ts)group_id(ホールディングス)と customer_id(顧客)は、9本のうち多くが受け取る共通パラメータです。検証は parseMasterIdFilters() / parseMasterIdParam()(lib/api/master-id.ts:45 以降)の1本だけが知っています。
★Number.parseInt() で書き直さないこと★(master-id.ts:36-38。D-123)。parseInt("1abc") は例外も出さずに 1 を返すので、★送った文字列とは違うIDで絞り込みが走ります★。判定は ^\d+$ の全体一致です。"0" も 422 にします(0 は「グループなし」を表す予約値であって、有効なIDではないため。D-011)。
変えると何が起きるか: この検証を各 query.ts に書き写すと、★どれか1本だけ緩い★状態ができても誰も気づけません(master-id.ts:5-10)。
| ファイル | 行数 | 対応する画面 | この1本だけの特徴 |
|---|---|---|---|
lib/clean-logs/query.ts |
220 | 清掃詳細ログ | ★日付の上限(731日)を持つ唯一の本★(:109-116)。parseDate() / toDbQueryDate() は他の本にも貸し出している(:41 / :59)。capCleanLogs()(:208)が連続日数に表示上限をかける |
lib/reports/query.ts |
534 | レポート | ★最大★。週/月/★年★の3粒度と year の範囲検証(:159)。select◯◯Rows() 4本(:489 :496 :503 :519)で確定期間のサマリ行を選ぶ |
lib/facilities/query.ts |
201 | 施設ロボットマスタ | 自前でページ切り出しを持つ(paginateFacilities() :190)。検索は filterFacilitiesByQ()(横断)と filterFacilitiesByFields()(項目別)の2系統 |
lib/schedules/query.ts |
197 | スケジュール | 同じく自前のページ切り出し(:190)。曜日の絞り込みを持つ |
lib/prechecks/query.ts |
207 | アラート(事前チェック) | summarizePrechecks()(:177)が画面上部の件数バッジを作る。★applyResolutionChange()(:193)は、1行の対応状況を変えたときに件数バッジを差分だけ直す★(一覧を取り直さないため。指示書AT) |
★(参考)lib/prechecks/status-update.ts★ |
130 | アラートの対応状況の更新 | ★クエリ層ではありませんが同じ置き場★。parsePrecheckStatusUpdateBody()(:100。本文の検品。status / note 以外のキーは捨てる)と planPrecheckStatusUpdate()(:50。★DBに書く列だけのパッチを作る唯一の関数★)。詳細は章8-10 |
★(参考)lib/prechecks/edit-drafts.ts★(★改訂4。D-165★) |
149 | アラート・エラー一覧の★編集モードの下書き★ | ★クエリ層ではありませんが同じ置き場★(純粋関数)。下書きは「行の値と違う項目」だけを持ち、同じ値に戻したら消える(setDraftStatus() :68 / setDraftNote() :80。★入力の Map を書き換えず新しい Map を返す★)。備考は行の null と "" を同じとみなす。送る本文は★対応状況は必ず・備考は変えたときだけ★(patchRequestFromDraft() :93)。★saveDrafts()(:121)は行の並び順に1行ずつ順に送り、1行の失敗で止めない★(成功・失敗・残った下書きを返す)。期待値表は lib/prechecks/__tests__/edit-drafts.test.ts(P1〜P8 / D1〜D5 / S1〜S3)。★旧 lib/prechecks/status-cycle.ts(巡回順)は D-161 で削除★ |
★(参考)lib/reports/facility-performance-load.ts★(★改訂4。D-163★) |
85 | レポートの施設別パフォーマンス(表と CSV) | ★DB を読むので純粋層ではありません★。loadFacilityPerformance()(:38)が「取得 → 絞り込み → 並べ替え → 連続日数の上限処理」まで済ませた★全件★を返し、★limit は見ない★。★表のルートと CSV のルートが同じ1本を呼ぶ★(片方だけ直すと表と CSV が食い違う) |
lib/ds-devices/query.ts |
152 | D-Secログ | summarizeDsDevices()(:147) |
lib/ds-alerts/query.ts |
145 | D-Secアラート | summarizeDsAlerts()(:136)。種別と対応状況の2軸で絞る |
★lib/clean-area/query.ts★ |
125 | 清掃面積(新) | ★日付の上限を持たない★(:14-18)。1本の集計クエリで返るのは粒度ごとの行数(最大でも施設数)なので、★期間を広げても応答は増えない★。group_by は group / customer / facility の3択(:32-36) |
★lib/masters/query.ts★ |
87 | セレクトリストの選択肢 | ★ページングしない唯一の本★(:8-14)。既定の50件で切ると★51件目以降の顧客がセレクトから黙って消える★。並びは必ず id 昇順(:60-66。日本語の名前順はDBとJSで一致せず、USE_FIXTURES の切替で順序が変わるため) |
変えると何が起きるか: summarizeXxx() の件数は「対応状況フィルタの前」に数えています。これを一覧の件数(meta.total)と取り違えると、ページ送りの総ページ数がずれます(components/ui/Pagination.tsx の total の注記。D-054)。
lib/types/index.ts(1,398行)型とは、データの「形の約束」です。 「清掃ログ1件には、施設名(文字)と完了率(数字か、未実施なら空)と判定(◎○△×のどれか)が入っている」という取り決めだけを書いた表で、★実際のデータは1件も入っていません★。約束に反したコードは、動かす前に機械が止めてくれます。たとえば判定に "◯"(別の記号)を入れようとすると、画面を開くまでもなくエラーになります。
★このファイルの正は d-watch-api.yaml(API契約)★です(lib/types/index.ts:4-5)。YAML を変えたら必ずここも合わせます。
| 型 | 行 | 何の形か | |||||
|---|---|---|---|---|---|---|---|
Grade / EscalationLevel |
:17 / :18 |
`"◎"\ | "○"\ | "△"\ | "×"と"L1"\ |
"L2"\ | "L3"` |
Meta |
:20-24 |
一覧APIが必ず返す { total, page, per_page } |
|||||
ApiError / ErrorCode |
:47 / :31-45 |
エラー応答の形と、使ってよいエラー名の一覧 | |||||
ListResponse<T> / SummaryListResponse<T,S> |
:84 / :89 |
一覧の外枠(件数バッジ付きの画面は後者) | |||||
CleanLogItem |
:202-274 |
清掃詳細ログ1行 | |||||
PrecheckItem |
:472-512 |
事前チェック1行 | |||||
★PrecheckStatusUpdateRequest / PrecheckStatusPatch★ |
:521 / :535 |
★アラートの対応状況の更新★ — 画面からの注文(status 必須・note 任意)と、★DBに書く4列だけのパッチ★(status / note→comment / responded_at / resolved_at)。★両者を分けてあるのが要点★(注文のキーがそのままDBに届く経路を作らない) |
|||||
ReportKpi / KpiValue |
:713 / :621 |
レポートのKPI4指標と、前期比つきの1指標 | |||||
FacilityPerformance / ContinuousProblem / ErrorBreakdownItem |
:643 / :668 / :697 |
レポートの3表 | |||||
Facility / Schedule / RobotSummary |
:981 / :1021 / :974 |
マスタ系 | |||||
DsDevice / DsAlert |
:899 / :916 |
D-Security 系 | |||||
RescueRequiredItem |
:1104 |
要レスキュー1行 | |||||
CleanAreaRow / CleanAreaTotal |
:1294 / :1348 |
清掃面積(新) | |||||
XxxDbQuery 各種 |
:117 ほか |
★画面からの注文ではなく、データ層へ渡す形★。両者を分けてあるのが要点 |
★RescueReason だけは lib/judge/rescue.ts から借りています★(:11-13)。理由の語彙を決めているのは判定層なので、★ここに文字列を書き写さない★(写すと片方だけ増えても誰も気づけません)。
変えると何が起きるか: 型を緩める(string にする等)と、機械の検査をすり抜けた値が画面まで届きます。CLAUDE.md §9.3 が any を禁止しているのはこのためです。
lib/api/(4ファイル)| ファイル | 行数 | 役割 |
|---|---|---|
client.ts |
153 | 画面から自アプリのAPIを叩く★唯一の入口★ |
query-result.ts |
43 | 検品結果の共通型(6B-1 参照) |
master-id.ts ★新★ |
107 | group_id / customer_id の検証(6B-1 参照) |
path-params.ts |
110 | URL の {id} / {serial_number} / ★{id}(文字列の error_id。parsePathErrorId() :89)★の検証 |
apiFetch()(lib/api/client.ts:102-153)★画面に fetch を直書きせず、必ずここを通します★(client.ts:4)。1箇所に寄せる理由は、認証方式が変わったときに直す場所を1つにするため、そしてエラー形式と meta の解釈を共通化するためです。外部URLは受け付けません(:106-110)。ビューアーは自アプリのAPIルート経由でしかデータを読まない決まり(D-046)を、コードで守らせています。
失敗したときは ApiFetchError(:28-63)を投げます。この例外は status(HTTPの番号。ネットワーク到達不能は 0)と serverMessage(サーバーが返した文言。読めなければ null)を別々に持っています。★混ぜない理由★は、HTMLのエラーページが返ったときに "<!DOCTYPE html>..." という文字列がそのまま画面に出てしまうからです(:22-26)。
変えると何が起きるか: details の型を「文字列の配列」に狭めると、GET /reports/kpi の 422 が返す ★数値の配列★({ customer_ids: [1, 2] })を扱うコードが★型検査を通ってしまいます★(:38-43)。
lib/settings/(5ファイル)アドバイスの文言と表示上限は画面から編集できます。保存先は★メインDBではなく、DFA独自のJSONファイル★です(DBはTeamWorks管理で読み取り専用のため。CLAUDE.md §7)。
既定値(コード) 編集結果(ファイル) 実際に使われる値 lib/config/advice-rules.ts + data/settings/advice.json = loadAdviceRules() lib/config/index.ts CONSECUTIVE_DAYS_DISPLAY + data/settings/display-limits.json = loadConsecutiveDaysMax()
★ファイルに入るのは「編集された項目だけ」★です(lib/settings/advice.ts:33-35、lib/settings/display-limits.ts:26-28)。未編集のルールはキーごと存在せず、コードの既定値がそのまま使われます。★これにより、既定値を後から改善したときに、編集していないルールへ自動で反映されます★。
| ファイル | 行数 | 役割 |
|---|---|---|
json-store.ts |
196 | ファイルの読み書きの共通部品。★アトミック差し替え★と★壊れたJSONの .bak 退避★ |
advice.ts |
166 | アドバイス文言。既定値に編集内容を重ねる合成だけを持つ |
advice-validation.ts |
112 | 文字数・件数の検証(上限は lib/config/index.ts:627-633) |
display-limits.ts |
385 | 連続日数の表示上限。loadConsecutiveDaysMax()(:223)/ validateConsecutiveDaysMax()(:147) |
ds-device-notes.ts |
155 | D-S制御盤の備考メモ |
★保存先 data/ は git の管理外です★(json-store.ts:8-9)。つまり★バックアップがありません★。だから壊れたファイルは消さずに .bak へ退避します。★ロックは入れていません★(施設20件・実質1人の運用のため。同時保存は後勝ち。:11-13)。利用者が増えたらここにロックを入れる想定で、呼び出し側は変えずに済む作りです。
★json-store.ts は lib/logger を import しません★(:15-18)。警告は onWarn で外へ渡すだけにしてあります。ログを呼ぶのは呼び出し側(APIルート)の仕事です。
変えると何が起きるか: loadConsecutiveDaysMax() を使わず lib/config の既定値を直接読むと、★管理画面で上限を上げても反映されず、しかもエラーもテスト失敗も出ません★(lib/db/consecutive-source.ts:383-386)。
判定が終わった生の数字を、人が読める形にして画面に並べるまでの層です。★ここで計算をしないこと★が全体の約束で、丸め・言い換え・単位付けだけを行います。
lib/format/(★12ファイル。改訂4 で13★)★2026-09-14(D-134 時点)は9ファイル、現在は12ファイル★(実測。git ls-tree 8f2f29e lib/format と現在の比較)。増えた3本は ★clean-result.ts★(清掃結果の生値表示)/ ★group-id.ts★(グループなしの変換)/ ★number.ts★(3桁区切り)です。consecutive-days.ts と search-text.ts は9/14時点で既に存在していました。
| ファイル | 行 | 入力 → 出力の実例 | 押さえどころ | ||
|---|---|---|---|---|---|
date.ts |
:8 :15 |
2026-08-27T02:00:00+09:00 → 8/27 / 02:00 |
★タイムゾーン変換をせず文字列から切り出す★(:2-3)。new Date() を挟むと実行環境のTZで1日ずれる |
||
date.ts(DB用) |
:37 :67 :96 |
Prismaの Date → 2026-08-27T02:00:00+09:00 |
DBの日時は★JSTの壁時計値★。時差は DB_DATETIME_OFFSET_MINUTES から読む |
||
date.ts(DBへ書く用) |
:82 |
真の瞬間の Date → DBに書く壁時計値の Date(toDbJst()) |
★メインDBに日時を書くときは必ずこれを通す★。消費者はシードと ★lib/db/robot-error-write.ts(アラートの対応日時2列)★(:77-80)。付け忘れはガードテストが検出する |
||
schedule.ts |
:17 |
"0111110" → 平日のみ / "1010100" → 月水金 / "0000000" → なし |
★曜日の起点は日曜★(7桁の1文字目が日曜)。空文字を返さず なし と書くのは、空欄だと不具合と区別できないため |
||
schedule.ts |
:35 :51 |
"0200" → 02:00 / ★"2830" → 翌04:30★ |
★終了時刻は24〜29時を取りうる★(日跨ぎ)。範囲外("3000")は★例外で止める★ |
||
number.ts ★新★ |
:46 :61 |
341635.4 → 341,635 / 341635 → 341,635㎡ |
★ロケールを en-US に固定★(環境で区切り文字が変わるのを防ぐ)。★NaN / Infinity は -★(0 と書かない) |
||
contract.ts |
:26 :36 |
"1100000000" → {d_security_enabled: true, monitoring_enabled: true} |
★下記 7-3★ | ||
group-id.ts ★新★ |
:32 |
0 → null / 3 → 3 |
★DBの 0 は「ホールディングスなし」の予約値★。APIに 0 を漏らすと★「グループ0」という存在しないホールディングスが画面に出る★ |
||
clean-result.ts ★新★ |
:61(★:98 cleanLogRowView() は D-173★) |
4 → "清掃正常終了" / 6 → "6" / ★null → "出発失敗"★ / ★結果待ちの行 → 出発 pending・"結果待ち"・完了率 null・判定なし・開けない★ |
★clean_task_reports.status の表示名を出す★(2026-10-02 から。第10回 N-2・D-155 項2。それまでは生値)。表は CLEAN_TASK_STATUS_LABELS、★表に無い値は数字のまま★。★日本語で返し、画面は t() に通す・CSV はそのまま★。★null の判定を `\ |
\ | で書くと0(清掃開始前)が「出発失敗」に化ける★(:62-63)。★cleanLogRowView()` は詳細ログの1行の見え方(出発・清掃結果・完了率・判定を出すか・開けるか)を決め、表と CSV が共有する★(片方だけ直すと同じ行が画面では結果待ち・CSV では出発失敗になる。D-173) |
raw-label.ts |
:21 |
null / "" / " " → (値なし) / " ABC " → ABC |
★トリム以外の変換をしない★。「その他」「不明」と書かない(破棄済み D-067 と紛らわしいため) | ||
robot-model.ts |
:26 |
("pudu", "cc1") → true / ("PUDU","CC10") → false |
前後空白と大小文字だけ揃えて★完全一致★。部分一致にすると CC10 が CC1 に当たる |
||
address.ts |
:7 |
("東京都○○区1-2-3", "△△ビル") → 東京都○○区1-2-3△△ビル |
★区切り文字を入れない★(日本語住所の慣習) | ||
consecutive-days.ts |
:32 |
(45, 30) → {days:30, capped:true} / (30, 30) → {days:30, capped:false} |
★表示上限であって判定の閾値ではない★。★上限ちょうどは capped:false★ |
||
search-text.ts |
:119 :133 :176 :190 |
foldPlain("PUDU-CC1") → pudu-cc1 / foldName("ドン・キホーテ") → ドンキホーテ |
★強さの違う2種類がある★(下記) | ||
period.ts |
:123 :133 :165 ほか |
weekOfMonth("2026-08-30") → {year:2026, month:8, week:6} |
★下記 7-2★ | ||
precheck-status.ts ★新(D-171)★ |
:22 |
null → {kind: "none", text: "-"} / "Standby." → {kind: "pill", text: "Standby."} |
★清掃詳細ログの「1h前ST / 30分前ST」のセル★。★- にするのは null(事前チェックの予定が無い)だけ★ — 空文字や値そのものが "-" は Pill のまま(DB の値を変換しない。D-077)。★statusColor() に null を届けないための分岐★。- は JP/EN 共通の記号なので t() に通さない。CSV は - ではなく空のセル(lib/clean-logs/csv.ts)。期待値表 lib/format/__tests__/precheck-status.test.ts(S1〜S5) |
search-text.ts)★| 強さ | 使うフィールド | すること |
|---|---|---|
foldName(強) |
施設名 / 都道府県 | 大小文字を揃え、さらに★記号(中点・長音・空白・ハイフン類)を除く★ |
foldPlain(弱) |
住所 / シリアル / メーカー / 機種 | ★大小文字だけ★揃える |
施設名だけ強いのは、店名の記号が表記ゆれであって桁位置の意味を持たないからです(ドン・キホーテ戸塚 を ドンキ で探せる)。★逆に住所とシリアルに強い方を使ってはいけません★(search-text.ts:22-40)。住所の 東京都○○区1-2-3 と 東京都○○区2-3-4 が両方 123 / 234 になり、23 で両方ヒットします。シリアルも PUDU-CC1-0001 → PUDUCC10001 となって★桁の境界が消えます★。
変えると何が起きるか: 照合規則を画面ごとに書くと、同じシリアルを同じ社内画面で探しても入力欄によって当たったり当たらなかったりします(実際に起きた。search-text.ts:5-8)。
weekOfMonth()★週は日曜〜土曜ですが、★月をまたぐ週は月境界で1日に切り、それぞれの月の別々の週として数えます★(D-071。TeamWorks のサマリ集計に合わせたもの)。その結果 ★週の長さは1〜7日で変わり、月の週番号は1〜6になります★。
gantt
title 2026年8〜9月 — 月境界で切った週(D-071。週の長さは1〜7日)
dateFormat YYYY-MM-DD
axisFormat %m/%d
section 2026年8月
第1週 1日(08-01 土のみ) :crit, 2026-08-01, 1d
第2週 7日 :2026-08-02, 7d
第3週 7日 :2026-08-09, 7d
第4週 7日 :2026-08-16, 7d
第5週 7日 :2026-08-23, 7d
第6週 2日(08-30〜08-31) :crit, 2026-08-30, 2d
section 2026年9月
第1週 5日(09-01〜09-05) :crit, 2026-09-01, 5d
第2週 7日 :2026-09-06, 7d
第3週 7日 :2026-09-13, 7d
第4週 7日 :2026-09-20, 7d
第5週 4日(09-27〜09-30) :crit, 2026-09-27, 4d
赤くした4本が★7日でない週★です。カレンダー上は 08-30(日)〜09-05(土) が1つの週ですが、★月末で切って「8月第6週(2日)」と「9月第1週(5日)」の2本になります★。各月の第1週は必ず1日から始まり、最終週は必ず末日で終わります。
| 押さえどころ | 場所 |
|---|---|
| ★帰属ルールを知っているのはこの関数だけ★ | lib/format/period.ts:133-146 |
periodRange() / shiftPeriod() も自前で計算せず、この関数を呼ぶ |
period.ts:27-29 |
| ★第6週は実在する★(2020〜2040 の252か月中55か月。2026年は5月と8月) | period.ts:31-35 |
| ★7日を前提にした算術を書かないこと★ | period.ts:17 |
年次(yearly)はこの関数に関与しない(暦年で締める) |
period.ts:37-42 |
変えると何が起きるか: 旧DFA案(D-067。1日を含む週をその月の第1週とし、同じ週を2つの月に分けない)に戻すと、TeamWorks の週次サマリと帰属がズレます。この関数以外の場所で週を計算すると、画面ごとに「第何週か」が食い違います。
contract.ts — いちばん静かに壊れる場所★facilities.contract は ★10桁の文字列★であって★数値ではありません★(D-080)。1桁目が D-Security、2桁目が D-Watch で、契約済みなら 1 です。
| 値 | 意味 |
|---|---|
"0000000000" |
未設定(既定値) |
"1000000000" |
D-Security のみ |
"0100000000" |
D-Watch のみ |
"1100000000" |
両方 |
★数値として比較してはいけません★(lib/format/contract.ts:4-6)。Number("0000000000") は★例外を出さずに★ 0 になり、contract === 3 のような比較は★絶対に真になりません★。結果は ★全施設が「契約なし」になってメニューが全部消える★ ですが、★例外もテスト失敗も出ません★。
判定は文字の完全一致(contract[i] === "1")だけ(:26-31)。桁位置は CONTRACT_DIGITS(lib/config/index.ts:740-743)から読み、ここに直書きしません。桁が足りない文字列・空文字でも例外を投げず、自然に「契約なし」扱いになります(:23-24)。
contract という列は2つあります★★CLAUDE.md §4.6 に 2026-09-17 の実測として追記された項目です。
| 列 | 場所 | 扱い |
|---|---|---|
facilities.contract |
prisma/schema.prisma:141 |
★これが正★ |
clean_schedules.contract |
prisma/schema.prisma:571 |
★読まないこと★ |
★型も既定値も完全に同一(varchar(10) / "0000000000")なので、取り違えても型エラーになりません★。用途は未確認で、TeamWorks の定義書にも説明がありません。★現在のアプリは clean_schedules.contract を読んでいません(正しい状態)★。
変えると何が起きるか: 読む列を取り違えると、メニューの表示制御が別のテーブルの値で決まります。型が同じなので、コンパイルもテストも通ります。
components/ui/(★6個。9/14から増減なし★。改訂4 で関数1本 date-picker.ts が増えた)| 部品 | 行数 | props | 使われ方 |
|---|---|---|---|
Pill |
17 | text color |
色付きの小さなラベル。背景は色コードに 22(=透明度)を足して薄くする(Pill.tsx:11) |
FilterButton |
30 | active color onClick children |
絞り込みボタン。押されているときだけ枠と文字に色が付く |
GradeBadge |
16 | grade |
◎○△× のバッジ。★色は grade から自分で決める★(◎○→ok / △→wn / ×→ng。GradeBadge.tsx:9-10) |
ProgressBar |
28 | percent tone? |
進捗バー。★NaN / Infinity が来ても壊れないよう 0〜100 に丸める★(:17-19) |
StatCard |
38(a961829) |
value label sub? tone(ok wn ng ac ★mt(中立。D-173)★) onClick? |
KPIカード。★value は文字列★(丸めは呼び出し側の責務)。★color ではなく tone にしてあるのは、生の色コードを直書きさせないため★(:4-6) |
Pagination |
130 | total page perPage ほか |
ページ送り。★何ページあるか・どの番号を描くかは自分で決めず lib/pagination/page-items.ts の純粋関数に任せる★(:15-19) |
★date-picker.ts(部品ではなく関数)★ ★新(10/06。D-162)★ |
19 | — | openDatePicker(e)(:11)。<input type="date"> の onClick に渡すと、★枠のどこを押してもカレンダーが開く★(showPicker() を呼ぶ。無いブラウザでは何もせず、例外は握りつぶす)。期間の入力がある4画面(清掃詳細ログ・アラート・エラー一覧・清掃面積)が使う。3.15 |
★Pagination だけが useTheme() を使います★(:24-30)。使うのは★文言の対訳のためだけ★で、色はCSS変数から取ります。★total に summary.total を渡さないこと★(:40-46。summary は対応状況フィルタの前に数えた件数なので、総ページ数がずれる。D-054)。
変えると何が起きるか: 判断(何ページ描くか等)を部品の中に書くと、テストが走りません(vitest.config.ts の対象は lib/** と app/** で、components/** は含まれていない)。
.module.css と .tsx の役割分担6個の部品はすべて Xxx.tsx と Xxx.module.css の2枚組です(★date-picker.ts は見た目を持たない関数なので1枚だけ★)。分け方の規則はこうです。
| どこに書くか | 何を書くか | 例 |
|---|---|---|
.module.css |
★状態によって変わらない見た目★ | 余白・角丸・文字サイズ・枠線の太さ |
.tsx の style={{...}} |
★状態によって変わる色★ | 押されているボタンの枠色、判定に応じた背景色 |
★インライン指定(.tsx 側)のほうが強く、CSSの指定を上書きします★。たとえば FilterButton.tsx:21-25 は borderColor / background / color の3つをインラインで与えるので、FilterButton.module.css に同じ項目を書いても効きません。
変えると何が起きるか: 「CSSを直したのに色が変わらない」の原因はほぼこれです。色を変えたいときは .tsx のインライン指定を見ます。
components/layout/AppShell.tsx(165行)左のサイドバーと右の本体という2分割の器です。メニューは3つの配列で定義されています。
| 定数 | 行 | 中身 |
|---|---|---|
MENU_MONITORING |
:8-26(f02eb1d) |
清掃詳細ログ / アラート / ★エラー一覧(10/06)★ / レポート / ★清掃面積★ |
MENU_DS |
:25-28 |
D-Secログ / D-Secアラート |
MENU_SETTINGS |
:30-34 |
施設ロボットマスタ / スケジュール / システム管理 |
ダッシュボードだけは配列に入れず、見出しの直下に単独で置いています(:127-131)。
★点検記録はコメントアウトで隠してあります★(:17-22)。写真ストアが未着手で画面が成立しないためで(D-079 項3)、★復活は1行のコメントを外すだけ★。画面ファイルと対訳はそのまま残してあります。★スコープから外したわけではありません★(D-031 は変えていない)。
サイドバー右上のボタン2つ(EN/JP と ☀/🌙)は専用の行に固定されています(:103-124)。言語を切り替えてもボタンの位置が動かないようにするためです。
| 場所 | 形 | 誰が使うか |
|---|---|---|
app/globals.css:1-40 |
CSS変数(--ok --ng など17個) |
.module.css と、.tsx の var(--ok) |
lib/theme.ts:4-43 |
JavaScriptのオブジェクト THEME_TOKENS(dark / light) |
.tsx が色コードを文字列として必要とするとき |
★この2つは同じ色を別々に書いてあり、同期は手作業です★。--ok: #22c55e(globals.css:9)と ok: "#22c55e"(theme.ts:11)のように、片方だけ直すと画面の中で色が食い違います。★どちらかを直したら必ずもう片方も直してください。★ 名前の対応は --sf-h ↔ sfH のようにケバブケースとキャメルケースの違いだけです。
lib/theme-status.ts(29行)— ★完全一致でしか色が付かない★ロボット状態の文字列から色を引く関数が1つだけあります(statusColor() :24-29)。対応表は ROBOT_STATUS_TONES(lib/config/index.ts:783-790)で、★日本語6文字列の完全一致★のみ。★表に無い値はすべてグレー★です。
CLAUDE.md §4.8 で「エラー種別を変換せず生値をそのまま表示する」と確定しているため、実データは EmergencyStop のような英語識別子で来ます。★実データ接続後は色がほぼ全部グレーになりますが、それが現在の期待挙動です★(theme-status.ts:10-12)。実際に出現した値を確認してから表に追記していく運用です。
★Object.hasOwn() で先に確かめてから引いています★(:26)。toString や __proto__ のような名前が状態として渡ったとき、添字アクセスが★undefined ではなくJavaScript内蔵の値を拾ってしまい、グレーにすらならない★ためです。
app/globals.css:54-58)★日本語フォントを明示的に列挙しています★。欧文フォント(Geist)は日本語の字を持たないため、指定しないと sans-serif に落ち、★閲覧環境に中国語フォントがあると漢字が中華字形で表示されます★。★この行から日本語フォントを外さないでください★(CLAUDE.md §0-3)。
lib/i18n.ts(395行。★a961829 では455行・表は :7-450★)には EN という表が1つだけあります(:7-390)。その形は次のとおりです。
キー(日本語の原文) → 値(英訳) "📋 清掃詳細ログ" → "Cleaning Logs" "完了率" → "Completion"
★つまり日本語はキー側であり、どこにも辞書がありません★。各 .tsx に日本語がそのまま書かれていて、t("完了率") は ★日本語のときは引数をそのまま返す★ だけです(translate() :392-395。a961829 では :452-)。
lib/i18n.ts を直しても日本語は1文字も変わりません★。ここは英語しか持っていません.tsx を探して直します(画面固有なら components/features/<画面>/、メニューなら components/layout/AppShell.tsx)lib/i18n.ts のキーも同じ文言に直します★。直さないと、★その項目だけ英語に切り替わらなくなります★(キーが一致しなくなり、translate() が引数をそのまま返すため)変えると何が起きるか: 手順3を忘れると、EN に切り替えたときその1行だけ日本語のまま残ります。lib/i18n.ts:49-54 は、まさにこれを避けるために「見出し語をここ1箇所に集めてある」と書いています(TeamWorks の回答が来たら4行差し替えるだけで済むように)。
★改訂4(10/06〜10/07)で足したキー★(どれも ★EN は暫定★): 編集モードの「編集」「キャンセル」「保存({n}件)」「変更を破棄しますか?」「{n}件を保存できませんでした」「編集中は絞り込みとページ送りを使えません」(D-165。:438-445 あたり)、アラートの「本清掃は成功」(D-171。:449)、「結果待ち」→ "Pending"・「レポートの到着待ち」→ "Waiting for report"(D-173。:228-229。詳細ログの清掃結果とダッシュボードのカードで共有)。 ★辞書は全画面で共有★なので、D-160 で足した「備考」→ "Note" により ★D-Secログの備考列の見出しも EN で "NOTE"★ になっています(直していない)。清掃詳細ログの「-」(事前チェックの予定が無い)は記号なので辞書に入れていません。
画面に出ている数字は、どこから来ているのか。D-Watch にはデータの置き場所が4種類あり、それぞれ持ち主が違います。持ち主が違うということは、勝手に直してよい場所とそうでない場所があるということです。
この章は「どのテーブルのどの列を、何のために、なぜ読むか」を書きます。数字1つずつの式と根拠は章5-Bを見てください。
flowchart LR
subgraph TW["TeamWorks の持ち物(DFA は読むだけ。例外は4列の更新)"]
STG[("ステージングDB<br/>MariaDB 10.11<br/>133.125.238.144 / clean_watch<br/>★実データあり★")]
PRD[("本番DB<br/>★別にある・未接続★")]
end
subgraph LOCAL["開発者のパソコン"]
LDB[("ローカルDB<br/>MySQL 8.4 / dwatch_local<br/>当方のシードで作る")]
end
subgraph DFA["DFA の持ち物(本リポジトリ)"]
FX["lib/fixtures/<br/>サンプルデータ<br/>15ファイル"]
ST["data/settings/*.json<br/>備考・アドバイス文言・表示上限<br/>★git に入らない★"]
end
subgraph GEN["自動生成(手で直さない)"]
SC["prisma/schema.prisma<br/>952行・39モデル"]
CL["lib/generated/prisma/<br/>Prisma クライアント"]
end
ENV{{"接続先は .env.local の<br/>DATABASE_* で決まる"}}
SW{"USE_FIXTURES<br/>lib/db/use-fixtures.ts:17"}
STG -->|"prisma db pull(構造だけ)"| SC
SC -->|"prisma generate"| CL
STG --- ENV
LDB --- ENV
PRD -.->|"まだつないでいない"| ENV
ENV --> PS["prismaSource(実DB版)<br/>lib/db/index.ts:706"]
CL --> PS
FX --> FS["fixtureSource(サンプル版)<br/>lib/db/index.ts:459"]
FX -->|"npm run seed:local<br/>★dwatch_local だけ★"| LDB
PS --> SW
FS --> SW
SW -->|"db(22メソッド)"| API["app/api/**/route.ts<br/>23本"]
ST <-->|"読み書き"| API
PS -->|"★robot_error_reports の4列だけ書く★"| STG
API --> UI["画面<br/>components/features/"]
classDef tw fill:#fde7e7,stroke:#c0392b,color:#000
classDef prd fill:#fff,stroke:#c0392b,stroke-dasharray: 5 5,color:#000
classDef local fill:#eef7ee,stroke:#2e7d32,color:#000
classDef dfa fill:#e7f0fd,stroke:#2471a3,color:#000
classDef gen fill:#eee,stroke:#777,color:#000
classDef sw fill:#fff4d6,stroke:#b9770e,color:#000
class STG tw
class PRD prd
class LDB local
class FX,ST,API,UI dfa
class SC,CL gen
class SW,ENV sw
| 置き場所 | 持ち主 | 中身 | 直してよいか |
|---|---|---|---|
| ステージングDB(赤) | TeamWorks | ドン・キホーテの実店舗10件+本社の実データ(2026-09-30 23:25〜10-01 00:09 JST に先方が投入) | ★読むだけ★。例外は robot_error_reports の4列の UPDATE(ユーザー指示 2026-10-01。D-152)。★スキーマ変更・データ投入は禁止★(CLAUDE.md §2・§7) |
| 本番DB(赤の点線) | TeamWorks | 未確認(★まだ接続していない★。構成・タイムゾーンも未確認。D-152 3-5) | — |
| ローカルDB(緑) | 開発者 | 当方のシード(scripts/seed-local-db.ts)が fixtures から作る dwatch_local。MySQL 8.4 なので、本番の MariaDB 10.11 と同一ではない(D-100) |
自由。★ただしシードはテーブルを消してから入れ直す★ |
サンプルデータ lib/fixtures/(青) |
DFA | 境界値・異常系を仕込んだ手書きのデータ | 自由(ただし docs/fixtures.md が正) |
設定の保存ファイル data/settings/(青) |
DFA | D-S制御盤の備考・アドバイス文言・表示上限 | 画面から保存される。★git に入らないのでバックアップが無い★ |
| 自動生成(グレー) | 機械 | DB の形を写したもの | ★手で直さない★(D-056) |
変えると何が起きるか: ★scripts/seed-local-db.ts をステージングに向けると、実データが消えます★(:848-852 で deleteMany してから入れ直すため)。lib/db/local-guard.ts が「ローカルホストの dwatch_local 以外」を拒否して止めます(:10-19。呼び出しは scripts/seed-local-db.ts:89)。★このガードを外さないこと★(D-152 3-2)。
★2026-09-30(D-151 / 指示書AQ)でデータの取得元が切り替わり、2026-10-02(D-155)で完了率の式が変わりました★。この表が今の正です。「読んでいる場所」は lib/db/ の中の findMany 等の位置です(2026-10-05、a1d7e43)。include(関連する表を一緒に読む機能)で読むテーブルは、呼び出し元の位置を書いています。
| テーブル | 読む列 | 何に使うか | 読んでいる場所 | 根拠 |
|---|---|---|---|---|
clean_timetables(清掃の予定) |
id / facility_id / section_id / parent_id / precheck / start_at / ★end_at(★結果待ちの判定 isResultPending() 用。詳細ログ・アラート・連続日数・当期の率で読む。D-173★)★ |
① ★本清掃(precheck = 0)1件が、清掃詳細ログの1行・稼働率の分母1件★ ② 事前チェック行(precheck = 60 / 30、parent_id = 本清掃の id)→「1h前ST / 30分前ST」とアラート ③ start_at が期間・日付の基準(清掃面積を除く) ④ start_at が未来の予定は外す |
★2026-10-05 から、すべて lib/db/timetable-source.ts の findTimetables() / findTimetablesByChunks() / findTimetableById() を通す★(呼び出し元: lib/db/index.ts / consecutive-source.ts / clean-area-source.ts) |
1回の清掃=3行(D-092 項1)。precheck は「何分前か」の数字そのもの、0 だけが本清掃(TeamWorks 第7回 A5。D-112)。★予定は clean_timetables と clean_timetable_histories の両方★(CLAUDE.md §4.7 の取得元の表。D-158 項4) |
clean_task_reports(清掃の結果) |
clean_timetable_id / status / clean_area / task_area / started_at / ended_at / facility_id(★clean_ratio は読み込むが計算に使わない★) |
① ★行があるかで出発/出発失敗(未実施)★ ② status → 清掃結果の表示名(判定には使わない) ③ ★clean_area ÷ task_area(上限100・小数第1位切り捨て)が1回ごとの完了率★ ④ clean_area / task_area の合計が清掃面積 ⑤ ended_at(空なら started_at)が最新の1行を選ぶ ⑥ started_at が清掃面積画面の期間の基準 |
lib/db/index.ts:850 :1128 :1296 :2085 :2352 / consecutive-source.ts:383 / clean-area-source.ts:368(面積の合計):374(完了率・実施回数用の行):445(重複の見張り) |
取得元を一本化(D-151。ユーザー指示 2026-09-30)。★未実施=行が無いこと★(CLAUDE.md §4.7)。完了率の式は TeamWorks のサマリと同じ(第10回 N-3。D-155 項1。★第11回 P-2=A で確定。D-158★)。status の意味は第10回 N-2(D-155 項2)。★status = 4 は稼働率の分子にも使う(第11回 P-6=A)★ |
robot_error_reports(ロボットのエラー) |
error_id / clean_timetable_id / error_type / errored_at / created_at / serial_number / facility_id / ★status / comment / responded_at / resolved_at(この4列は書く)★ |
① 事前チェック行を指すエラー → 「1h前ST / 30分前ST」とアラートの行(error_type を変換せずに出す) ② ★本清掃行を指すエラー → エラー発生率の分子・エラー内訳(当期)★ ③ 4列 → アラートの対応状況・備考(読み書き) ④ ★全行 → エラー一覧(/errors。10/06〜。4列の読み書きも)★ |
lib/db/index.ts:857 :1286 :1434 :2090 :2357 :2568 / ★読む列は lib/db/robot-error-select.ts の select で決める(robot_id / robot は読まない。指示書AU 段階0-4)★ / ★書き込みは lib/db/robot-error-write.ts:127(updateMany の1文)だけ★ |
値は変換しない(D-077 / CLAUDE.md §4.8)。エラー率の分子は「本清掃行を指すエラー」だけ(TeamWorks 第8回 A7。D-114 項2)。検知日時は errored_at、空なら created_at(D-105)。4列の書き込みは D-152 / D-156 |
| テーブル | 読む列 | 何に使うか | 読んでいる場所 | 根拠 |
|---|---|---|---|---|
customer_summaries |
customer_id / year / month / week / ★operation_ratio / launch_ratio / clean_ratio / error_ratio★ |
確定期間(先週・先月・昨年)の KPI 4つと、前期比の「前期」 | lib/db/index.ts:1974 |
確定期間はサマリを読む、詳細から再計算しない(CLAUDE.md §4.7 / D-102)。年次も読む(D-151) |
facility_summaries |
facility_id / year / month / week / 率4列 |
確定期間の施設別パフォーマンス | lib/db/index.ts:2197 |
同上 |
facility_error_summaries |
facility_id / year / month / week / error_type / error_count |
確定期間のエラー種別内訳(error_type ごとに error_count を合計) |
lib/db/index.ts:2516 |
同上。種別は変換しない(D-077 / D-088) |
★サマリの粒度は month と week で見分けます★ — week = 0 が月次、month = 0 かつ week = 0 が年次(TeamWorks 承認済み。D-074 項3)。判定は periodOfSummaryRow()(lib/db/report-mapping.ts:100)だけが知っています。★week === 0 だけで判定すると年次行が月次に化けて混入し、重複検知で 500 になります★。
★当期(今週・今月・今年)はサマリにまだ行が無い★ので、上の3テーブル(予定・結果・エラー)からその場で集計します(CLAUDE.md §4.7)。
| テーブル | 読む列 | 何に使うか | 読んでいる場所 | 根拠 |
|---|---|---|---|---|
facilities(施設) |
id / facility_name / customer_id / prefecture_name / addr1 / addr2 / phone / email / manager_name / charger_name / ★contract★ / deleted_at |
施設名(全画面)、施設マスタ画面の連絡先、施設名・分類 → 施設ID の解決、契約フラグの表示 | lib/db/index.ts:1712 :2172 :2438 / facility-name-source.ts:67 / facility-group-source.ts:91 / clean-area-source.ts:470 ほか各所の include |
★contract は10桁の文字列。1桁目=D-Security、2桁目=D-Watch★(D-080。CLAUDE.md §4.6)。★清掃ログ・清掃面積のための施設名は deleted_at で絞らない★(論理削除された施設の過去の実績を落とさないため。罠7) |
sections(清掃エリア) |
section_name(+ロボットへの橋渡し) |
エリア別完了率・D-Secアラートのエリア名。★予定 → 清掃エリア → ロボット★の橋渡し | include のみ(lib/db/index.ts:793 :1119 :1248 :1637 :1766) |
P1 は1清掃エリア=1ロボット(CLAUDE.md §8) |
robots(ロボット) |
serial_number / device_id / deleted_at |
シリアル番号(清掃詳細ログ・アラート・施設マスタ・スケジュール) | include のみ(lib/db/index.ts:795 :1248 :1715 :1768 :2177) |
★robots[0](1台目)を使う★。0台なら例外(静かに空欄にしない) |
devices(機種) |
maker_id / model_name |
機種名(MAKER_NAMES で番号→メーカー名) |
include のみ(lib/db/index.ts:797 :1717) |
makers テーブルは提供されない(D-057 A-1)。番号の割り当ては DFA の暫定(D-085) |
customers(顧客) |
customer_name / group_id / deleted_at |
分類の絞り込み、レポート KPI の対象顧客 | lib/db/index.ts:1694 ほか include |
group_id = 0 は「グループなし」(D-011)。★KPI は対象顧客が1社に定まらないと 422★(サマリの率は合算できないため。lib/reports/target-customer.ts) |
groups(ホールディングス) |
group_name / deleted_at |
分類の絞り込み | lib/db/index.ts:1667 |
— |
clean_schedules(清掃スケジュール) |
weeks / start_time / end_time / inactive / deleted_at / facility_id / section_id |
スケジュール画面、ダッシュボードのスケジュール進捗の「予定」件数 | lib/db/index.ts:1762 |
end_time は時が24〜29を取りうる(翌日。TeamWorks 回答 B-5)。★inactive は 0 が有効(論理が逆)★ |
| テーブル | 読む列 | 何に使うか | 読んでいる場所 | 根拠 |
|---|---|---|---|---|
controls(D-S制御盤) |
serial_number / status / facility_id / deleted_at |
D-Secログの一覧と通信状態(0 正常 / 1 異常)、備考を保存する前の実在確認 | lib/db/index.ts:1528 :1607 |
通信状態は2状態(D-065) |
control_reports(制御盤の報告) |
control_id / reported_at |
最終報告日時(制御盤ごとの最大値) | lib/db/index.ts:1535(groupBy) |
★制御盤1台につき1行(最新の報告だけ)★(TeamWorks 第10回 Q8-3。D-155 項5) |
control_error_histories(制御盤のエラー履歴) |
status / comment / occurred_at / responded_at / resolved_at + 制御盤・施設・エリア |
D-Secアラート、ダッシュボードのセキュリティバナー | lib/db/index.ts:1632 |
★書き込み(4列)は権限上は可だが未着手★。★種別はいつも「通信断」で返す★(「セキュリティ解除のまま」を表す列が無い。D-090 項3-9) |
変えると何が起きるか: 取得元を clean_status_histories に戻すと、完了率の定義ごと過去に戻ります。清掃面積の完了率用の取り寄せ(clean-area-source.ts:374-386)から clean_timetable_id: { not: null } と本清掃の絞り込みを外すと、予定外のタスクが完了率・実施回数に混ざり、★他の画面と完了率が食い違います★(例外は出ません)。periodOfSummaryRow() から month の判定を外すと、レポート画面が年次と月次を混ぜて 500 を返します。
clean_task_reports の行が無いこと★です。clean_task_reports には「未実施」を表す列がありません。したがって★「出発失敗」と「未実施」は同じ扱い★です(ユーザー確認 2026-09-30)。判定は notExecutedFromReport()(lib/db/clean-log-mapping.ts:165)の1箇所だけが知っています。★clean_task_reports.status(清掃結果)は判定に使いません★(表示名を出すだけ。D-155 項2。CLAUDE.md §4.4 の「中断で返る=失敗」は実データを見て見直す)clean_task_reports.clean_timetable_id が NULL の行は「予定外のタスク」★です(ユーザー回答 2026-10-01。D-152 3-3)。★欠損ではなく、実在する清掃★です。予定と突き合わせられないので in の条件に一致せず、予定起点の画面には出ません(lib/db/index.ts:843-845)。★完了率・実施回数には入れず、総清掃面積の面積の合計にだけ入れます★(D-155 項3。TeamWorks のサマリも NULL の行を除く=第10回 N-3)clean_area と task_area から runCompletionRate()(lib/judge/kpi.ts:144)が作り、★clean_task_reports.clean_ratio 列は使いません★。★task_area = 0 の行は1回ごとの完了率を 0% にします★(第11回 P-5=B。D-158。旧: null で分子にも分母にも入れなかった)groupBy(予定外を含む全行)、完了率・実施回数は★行を取り寄せて数えます★(loadCleanAreaFacilityStatsWithRowCount() lib/db/clean-area-source.ts:363)。1回ごとの完了率(上限100・切り捨て)は集計クエリで書けず、生SQLも使わないためです。★期間を指定しないと全期間を運ぶ★ので、行数と所要時間を judge.clean_area ログに出しています(D-156 項2)clean_timetables.start_at、清掃面積は clean_task_reports.started_at です(lib/db/index.ts:1912)。清掃面積が started_at を使うのは、予定外のタスクには予定の日時が無いからです。★予定のある清掃では started_at に予定の start_at と同じ値が入る★(ユーザー回答 2026-10-05「A」)ので、同じ清掃が画面によって別の日に入ることはありません。★旧: 「started_at は実際に清掃を始めた時刻」(2026-10-01)は、予定外のタスクについてだけ正しい★DB には読まない列がたくさんあります。「読み忘れ」ではなく、読まないと決めているものを書きます。
| テーブル.列 | 理由 | 根拠 |
|---|---|---|
★clean_task_reports.clean_ratio★ |
TeamWorks が「計算式が間違っている。訂正したら回答する」と回答。完了率は clean_area ÷ task_area から作る。★select には残している★(列に戻すとき runCompletionRate() の1関数で済むように。指示書AS D6) |
D-155 項1。★第11回 P-1: 訂正後の列も今の式と同じ値・P-2=A: 今の式のまま(D-158)★。列に戻す予定は無い |
★clean_schedules.contract★ |
★契約フラグは facilities.contract だけが正★。型も既定値も facilities.contract と同じ(varchar(10) / "0000000000")なので、★取り違えても型エラーにならない★。TeamWorks の回答では「事前確認の行を作るかの基準」 |
CLAUDE.md §4.6 / D-155 項5(第10回 N-13)。prisma/schema.prisma:571(正のほうは :141) |
★customers.contract★ |
削除依頼済み。★現在の prisma/schema.prisma にこの列はありません★(model Customer :96-121。構造) |
CLAUDE.md §4.6・§7 |
customer_summaries / facility_summaries の task_area / clean_area |
★実データで task_area が全行 1000 固定で、期間内の合計になっていない★。清掃面積は常に clean_task_reports から集計する |
lib/db/clean-area-source.ts:35-38。第9回 Q2-1 で照会中(指示書AN / D-141) |
customer_summaries / facility_summaries の task_count / launch_count / error_count |
率4列だけを使う。下の「実施回数の列が無い」件のとおり、件数だけでは率を組み立て直せない | 構造(prisma/schema.prisma:694-696 / :728-730) |
robots.status / robots.battery / robots.clean_ratio |
「1h前ST / 30分前ST」は robot_error_reports から作り、エラーが無ければ Standby. を入れる(robots.status を読んでいるのではない。★事前チェックの予定行が無ければ null。D-171★)。バッテリー残量はいつも空(battery_level: null) |
D-065 Q2 / D-098。robots.status の46値は動作フェーズだけで、ハンドルアップ・オフラインを表さない(D-077 項3) |
clean_timetables.inactive |
無効化された予定でも絞らない(現状の挙動) | lib/db/index.ts:724-725 のコメント(指示書R 禁止事項4/5) |
robot_error_reports.error_level / error_detail |
画面に出していない | — |
| テーブル | 理由 | 根拠 |
|---|---|---|
★clean_status_histories★ |
清掃実績は clean_task_reports に一本化した。★アプリのコードに問い合わせは0件★(2026-10-05 確認)。テーブル自体は TeamWorks の管理下なので残る。ステージングでは0行(2026-10-01 実測・先方投入) |
D-151(ユーザー指示 2026-09-30)/ CLAUDE.md §4.7 |
clean_statuses |
「今の状態」の表。画面に出していない | — |
customer_error_summaries |
エラー内訳は施設別(facility_error_summaries)を合計して出す |
— |
security_statuses / security_status_histories |
「セキュリティ解除のまま」を表す列が無く、セキュリティバナーを作れない | D-090 項3-9(lib/db/index.ts:1614-1625) |
facility_contacts / customer_contacts |
施設の連絡先は facilities の列から読む |
— |
section_robot_statuses / robot_task_versions / robot_instruct_histories / link_schedules |
画面に出していない | — |
clean_link_schedules |
不要テーブル(link_schedules と同じ構造でデータ0件) |
D-058 |
Laravel の基盤10テーブル(users / sessions / jobs ほか) |
TeamWorks 側のフレームワークの表 | — |
CLAUDE.md §4.7 は「サマリには丸め前の小数と分子・分母が格納される(検算可能にするため)」と定めていますが、★実際の DDL と食い違っています★(2026-09-30 実測・構造)。
| KPI | 式(CLAUDE.md §4.2) | サマリにある列 | 検算できるか |
|---|---|---|---|
| 稼働率 | ★正常終了(status 4)の回数 ÷ 清掃予定回数★(第11回 P-6=A) | operation_ratio(率)、予定回数 task_count |
★できない★ — ★正常終了の回数の列が無い★ |
| 出発成功率 | ★開始できた回数 ÷ 清掃予定回数★(第11回 P-6=A) | launch_ratio、launch_count |
★できる★ — launch_count ÷ task_count(第11回 P-6=A で分母が task_count と確定) |
| 清掃完了率 | 1回ごとの完了率の平均 | clean_ratio(率) |
★できない★ — 1回ごとの完了率の合計も件数も無い |
| エラー発生率 | ★エラー発生回数 ÷ 清掃予定回数★(第11回 P-6=B) | error_ratio、error_count |
★できる★ — error_count ÷ task_count |
(構造: prisma/schema.prisma:694-710 / :728-744)
★帰結★:
operation_ratio をそのまま信じるしかありません★。式は「正常終了(status 4)の回数 ÷ 予定回数」と回答がありました(第11回 P-6=A。D-158)が、分子の列がサマリに無いので検算はできませんlib/reports/target-customer.ts:1-15)。★率の平均を取ってはいけません★(施設数の違いが消える)★接続先 133.125.238.144 / clean_watch は★ステージング★です。本番DBは別にあります★(D-152。2026-10-01 ユーザー回答「これはステージングです、本番は他にあります」)。 2026-09-17 の D-148(「本番DBそのもの」)は★破棄★されました。過去の文書の「ステージング」「テスト環境」という呼称のほうが正しかったことになります。
| 項目 | 現状 | 根拠 |
|---|---|---|
| 中身 | ★実データ★。施設はドン・キホーテの実店舗10件+本社。★当方のダミーは消えた★ | 2026-10-01 実測・先方投入(CLAUDE.md §3) |
| 予定・実績の行数 | ★clean_timetables / clean_schedules / clean_status_histories は0行、clean_task_reports は2行(どちらも clean_timetable_id が NULL=予定外、status 4)★。★予定起点の画面(清掃詳細ログ・ダッシュボード・アラート)は空★で、清掃面積だけ2回と出る |
2026-10-01 実測・先方投入。TeamWorks は「0行のテーブルには 10/5 中に入る」と回答(第10回 N-6。D-155 項5)。★入ったかは未確認★ |
| データの入れ替え | ありうる(時期未定) | 第10回 N-6(D-155 項5) |
| ★書き込み★ | ★robot_error_reports の4列(status / comment / responded_at / resolved_at)だけ★。★実データ(ドン・キホーテのエラー行)の対応状況が書き換わることを承知のうえ★ |
ユーザー指示 2026-10-01「書き込んでよい」(D-152)。★4列以外は引き続き禁止★(CLAUDE.md §7。権限も無い) |
control_error_histories の4列 |
権限上は許されているが、★書くコードはまだ無い★ | CLAUDE.md §3 |
| 再実測 | ★2026-10-02 以降できていない★(10-02 は認証エラー、10-05 時点もログインできない件を別途問い合わせ中) | D-155 項4 / D-157 |
| セキュリティ(接続元の制限・TLS・パスワード変更) | ★リリース直前にまとめて行う★(検証段階のため保留)。対象は CLAUDE.md §3 に列挙済み | ユーザー判断 2026-10-01(D-154) |
| 構築・保守 | TeamWorks(サーバーの契約と費用は DFA) | D-143 |
接続情報(ホスト名・パスワード)は .env.local にあり、このドキュメントには書きません(★この文書を書くときも .env.local は開いていません★)。lib/db/prisma.ts:36-39 が「実在のDBユーザー名をコードに書かない。未設定なら黙って既定値で繋ぎにいかず、何が足りないかを言って落とす」と定めています。
★MariaDB と MySQL は同一ではありません★(D-100)。本番は MariaDB 10.11、ローカルは MySQL 8.4 なので、ローカルで通っても本番で通らない可能性があります。★アプリのコードに生SQL($queryRaw / $executeRaw)は1件もありません★(すべて Prisma のクエリビルダ経由。CLAUDE.md §3)。MariaDB 特有の書き方を使いたいときは TeamWorks に知らせる約束です。
USE_FIXTURES — サンプルと実DBの切り替え判定はたった1行です。
export const USE_FIXTURES = process.env.USE_FIXTURES !== "false";
(lib/db/use-fixtures.ts:17)
| 項目 | 現状 |
|---|---|
| 既定値 | ★true(サンプルデータ)★ — 「"false" 以外はすべて true」なので、未設定でもサンプルになります |
.env.example の記載 |
USE_FIXTURES=true(.env.example:2。新しい開発者はDB無しで始められる) |
.env.local の現在の設定 |
★未確認★(この文書を書くために .env.local は開いていません) |
false のときの接続先 |
.env.local の DATABASE_HOST / DATABASE_USER / DATABASE_PASSWORD / DATABASE_NAME(または DATABASE_URL)で決まる(lib/db/prisma.ts:41-71)。ステージングにもローカルの dwatch_local にも向けられる |
★切り替わらない書き方の罠★
USE_FIXTURES=FALSE(大文字)は効きません★。比較は !== "false" の完全一致なので、大文字・前後の空白・0・no はすべて「true(サンプル)」になります。例外もエラーも出ず、実DBに繋いだつもりでサンプルが出ますlib/db/index.ts から直接 USE_FIXTURES を import しないでください★。この定数がわざわざ独立ファイルに切り出されているのは、lib/logger がこの値を読むために @/lib/db を import していた結果、Edge Runtime のバンドルに Prisma クライアントまで引きずり込まれ、USE_FIXTURES=false で /api/reports/* が軒並み404になった実測事故があるためです(lib/db/use-fixtures.ts:4-15)db の分岐はモジュール読み込み時に1回だけ決まります★(lib/db/index.ts:2591)。.env.local を書き換えたら dev サーバを再起動してください設計上の決めごと: ★USE_FIXTURES の true / false で件数や数字が変わってはいけません★。これは指示書AC 禁止事項6 として、★サンプル版の絞り込み5本すべての冒頭★(lib/db/fixture-clean-logs.ts ほか)に書かれています。判定ロジック(lib/judge/)は切り替えの外側にあるので、サンプル開発中も本番と同じ判定が走ります(D-044 / CLAUDE.md §6-3)。 ★書き込みも同じです★ — アラートの対応状況の更新は USE_FIXTURES=true でも動き、サンプル版は★書き込みをメモリ上の上書き表に持ちます★(lib/db/fixture-precheck-overrides.ts。★プロセスを再起動すると消える★)。
lib/db/ の全ファイル(24本。★改訂3 時点 26本★)2026-09-14 時点は13本、2026-10-01(第3版)は20本、第4版(2026-10-05 a1d7e43)は22本でした。★現在は24本(ほかに __tests__/)。指示書AU(2026-10-05)で ★timetable-source.ts★ と ★robot-error-select.ts★ が増えました(行数は 2026-10-06 の wc -l)★。第3版から増えたのは ★robot-error-write.ts★ と ★fixture-precheck-overrides.ts★(どちらも指示書AT。2026-10-02)。行数は 2026-10-05 の wc -l です。 ★改訂3(指示書AW。2026-10-06)で ★error-report-mapping.ts★ と ★fixture-error-report-overrides.ts★ が増えて26本です★。
| ファイル | 行 | 役割 |
|---|---|---|
index.ts |
2,591 | ★中心★。DataSource の型(:192)、サンプル版(:459)、Prisma版(:706)、db の切り替え(:2591) |
use-fixtures.ts |
17 | USE_FIXTURES の判定だけ。Edge Runtime 対策で独立(8.5) |
prisma.ts |
137 | Prisma クライアントのシングルトン。参照用 getPrisma()(:75)と★書き込み用 getWritePrisma()(:100)★。★index.ts の先頭で静的に import しない★(未生成の環境でテストが全滅する。:4-8) |
| 読み取りの切り出し | ||
consecutive-source.ts |
405 | ★連続日数の材料★を作る唯一の読み取り loadConsecutiveInputs()(:343)。cleanLogs / facilityPerformance / continuousProblems / rescueRequired の4経路が共有(D-109 の再発防止)。表示期間の前を「表示上限 × 7 + 1 日」遡る |
clean-area-source.ts |
498 | 清掃面積の材料。★面積は groupBy、完了率・実施回数は行の取り寄せ★(loadCleanAreaFacilityStatsWithRowCount() :363、数える関数 cleanAreaStatsFromRows() :271)。重複の見張り countDuplicatedTimetables() も |
facility-name-source.ts |
75 | ★施設名 → 施設ID の解決★用。deleted_at で絞らない |
facility-group-source.ts |
100 | ★分類(ホールディングス/顧客)→ 施設ID の解決★用 |
★timetable-source.ts★ ★新(10/05)★ |
295 | ★予定を clean_timetables と clean_timetable_histories の両方から読む唯一の読み口★(findTimetables() / findTimetablesByChunks() / findTimetableById())。同じ where を2表に渡し、同じ id は本表を採る。中身が違えば warn db.timetable_conflict。★count / groupBy を2表にまたがって書かない★(同じ id の重複を引けない)。指示書AU 段階3b / D-158 項4 |
★robot-error-select.ts★ ★新(10/05)★ |
64 | ★robot_error_reports を読むときの select の定数★(ROBOT_ERROR_COUNT_SELECT ほか)。★robot_id / robot を読まない★(先方の DDL では NULL 可・Prisma では NOT NULL のため。第11回 P-8 b)。指示書AU 段階0-4。★改訂3 でエラー一覧の ★ROBOT_ERROR_LIST_KEY_SELECT(:70。1段目のキーだけ)★ と ★ROBOT_ERROR_LIST_ROW_SELECT(:85。2段目・PATCH の前後)★ を足した(99行)★ |
| ★書き込み(メインDB)★ | ||
★robot-error-write.ts★ ★新(10/02)★ |
137 | ★robot_error_reports の4列を UPDATE する唯一の関数★ writeRobotErrorColumns()(:121)。8.10 |
| 変換(DBに触らない関数) | ||
clean-log-mapping.ts |
489 | 清掃ログ1件を組み立てる変換。notExecutedFromReport()(:165)/ completionRateFromReport()(:203。★runCompletionRate() を呼ぶ★)/ latestReportByTimetableId()(:241)/ isFutureSchedule()(:308)/ 連続日数 computeConsecutiveProblemDays()(:463) |
report-mapping.ts |
971 | レポート4メソッドの変換。★periodOfSummaryRow()(:100)★、サマリの率 → KPI kpiFromSummaryRatios()(:231)、当期の集計 aggregateCleanStatsByFacility()(:460) |
precheck-mapping.ts |
238(★f2b91fc で287★) |
アラートの変換。★robot_error_reports.status の 0/1/2 と 未解消/対応中/解消 の対応★(:41 と逆向き :61。f2b91fc)、「1h前ST / 30分前ST」の割り当て statusColumnsFromPrecheckRows()(:147。★予定行が無い列は null。D-171★)、★親の本清掃が成功したか mainRunSucceededFromReport()(:283。D-171)★ |
★error-report-mapping.ts★ ★新(10/06)★ |
188 | ★エラー一覧の変換★。errorReportKeyFromRow()(:79。1段目のキー)/ errorScheduleLink()(:107。紐づく予定の4区分)/ errorReportItemFromRow()(:136。1行)/ errorReportListWhere()(:177。SQL の where。★正は lib/error-reports/query.ts の filterSortErrorReportKeys()★) |
rescue-mapping.ts |
198 | 要レスキュー店舗の組み立て |
ds-status.ts |
61 | D-Sec のコード値変換(controls.status 0/1、control_error_histories.status 0/1/2)。想定外の値は例外 |
date-range.ts |
68 | JST の YYYY-MM-DD を「DB の壁時計値」の範囲に変換 dbDateRange()(:50)。★ここ1箇所に閉じ込める★(9時間ずれの事故対策) |
chunked.ts |
33 | in: [...] に渡す配列の分割(DB_IN_CLAUSE_MAX_ITEMS ごと) |
| サンプル版の絞り込み(5本) | ||
fixture-clean-logs.ts |
46 | サンプル版 cleanLogs() |
fixture-prechecks.ts |
47 | サンプル版 prechecks() |
fixture-area-completions.ts |
33 | サンプル版 areaCompletions() |
fixture-clean-area.ts |
103 | サンプル版 cleanArea()(★完了率は runCompletionRate() を通す★) |
fixture-reports.ts |
117 | サンプル版レポート4メソッド |
| サンプル版の書き込み | ||
★fixture-precheck-overrides.ts★ ★新(10/02)★ |
69 | ★lib/fixtures/prechecks.ts は書き換えず、id → 4項目の上書き表をメモリに持つ★(:23)。Prisma 版と同じ歯止め(robotErrorColumnsFromPatch())を通す |
★fixture-error-report-overrides.ts★ ★新(10/06)★ |
60 | ★エラー一覧のサンプル版の上書き表★。robotErrorColumnsFromPatch() で DB の列に写してから重ねる(Prisma 版と同じ歯止め・同じ値)。★fixture-precheck-overrides.ts とは状態を共有しない★(id が重ならない) |
| その他 | ||
local-guard.ts |
31 | シードがローカルの dwatch_local 以外に書き込むのを止める(8.1) |
変えると何が起きるか: consecutive-source.ts を経由せずに連続日数の入力を自前で引くと、4経路の遡り幅が食い違い、「同じ施設・同じ日なのに清掃詳細ログとレポートで連続日数が違う」という不具合(D-109 / D-103)が復活します。★L3 が L1 に化けますが、例外もテスト失敗も出ません★。
★古いコメントが2箇所残っています★(2026-10-05 確認。どちらも動作には影響しません):
| 場所 | 古い記述 | 今の正 |
|---|---|---|
lib/db/clean-area-source.ts:425 |
「clean_timetable_id は TeamWorks がまだ埋めていない(今後埋める前提)」 |
★NULL は予定外のタスクで、後で埋まるものではない★(2026-10-01 ユーザー回答。D-152 3-3 / CLAUDE.md §4.7)。NULL を除く条件そのものは正しい |
lib/db/index.ts:722-723 |
「precheck = 1 … 1回目の事前チェック(60分前)/ precheck = 2 … 2回目(30分前)」 |
★precheck は「何分前か」の数字そのもの(60 / 30)★(TeamWorks 第7回 A5。D-112)。コードは生の分数として正しく読んでいる(詳細ログの列は D-178 から分数の大きい順に割り当て、PRECHECK_MINUTES_BEFORE は使わない) |
DataSource の22メソッド(★改訂2 時点 23・改訂3 時点 26★)★22本です★(2026-09-14 時点は15本、2026-10-01 時点は20本)。インターフェース(lib/db/index.ts:192-455)・サンプル版(:459-689)・Prisma版(:706-2589)の3箇所すべてで 22 本と数えました。★10/02 に増えた2本(precheckItemById / updatePrecheckErrorColumns)はアラートの対応状況の更新用★です。
| # | メソッド | 戻り値 | 主に読む(書く)テーブル | サンプル版 | Prisma版 |
|---|---|---|---|---|---|
| 1 | cleanLogs(query) |
清掃詳細ログ一覧 | clean_timetables clean_task_reports robot_error_reports |
:465 |
:754 |
| 2 | facilityNameIndex() |
施設名→IDの解決用 | facilities |
:474 |
:1032 |
| 3 | facilityGroupIndex() |
分類→IDの解決用 | facilities customers groups |
:491 |
:1046 |
| 4 | areaCompletions(query) |
エリア別完了率 | clean_timetables sections clean_task_reports |
:504 |
:1095 |
| 5 | prechecks(query) |
アラート一覧 | clean_timetables robot_error_reports clean_task_reports |
:511 |
:1216 |
| 6 | ★precheckItemById(id)★ ★新★ |
アラート1行(★書き込みの前後の確認用。事前チェック異常の行だけ。出発失敗の id は null★) |
robot_error_reports clean_timetables |
:519 |
:1429 |
| 7 | ★updatePrecheckErrorColumns(id, patch)★ ★新★ |
★robot_error_reports の4列を更新★({ matched }) |
★robot_error_reports(書く)★ |
:523 |
:1469(★書き込み用の接続★) |
| 8 | dsDevices() |
D-S制御盤一覧 | controls control_reports |
:526 |
:1522 |
| 9 | dsDeviceExists(serial) |
備考保存前の実在確認 | controls |
:535 |
:1604 |
| 10 | dsAlerts() |
D-Secアラート | control_error_histories |
:538 |
:1627 |
| 11 | groups() |
ホールディングス | groups |
:546 |
:1664 |
| 12 | customers() |
顧客 | customers |
:557 |
:1691 |
| 13 | facilities() |
施設マスタ | facilities robots devices customers groups |
:560 |
:1706 |
| 14 | schedules() |
スケジュール | clean_schedules |
:563 |
:1759 |
| 15 | inspections() |
点検記録一覧 | (メインDBには置かない) | :566 |
★:1799 未実装★ |
| 16 | inspectionDetail(id) |
点検記録の詳細 | (同上) | :569 |
★:1802 未実装★ |
| 17 | rescueRequired(query) |
要レスキュー店舗 | clean_timetables clean_task_reports facilities |
:590 |
:1827 |
| 18 | cleanArea(query) |
清掃面積の集計材料 | clean_task_reports clean_timetables facilities |
:625 |
:1899 |
| 19 | reportKpis(query) |
KPI4指標 | customer_summaries +(当期)詳細3テーブル |
:653 |
:1957 |
| 20 | facilityPerformance(query) |
施設別パフォーマンス | facility_summaries +(当期)詳細3テーブル |
:657 |
:2149 |
| 21 | continuousProblems(query) |
連続問題施設 | clean_timetables clean_task_reports facilities |
:661 |
:2417 |
| 22 | errorBreakdown(query) |
エラー種別内訳 | facility_error_summaries +(当期)robot_error_reports |
:684 |
:2496 |
| 23 | ★cleanLogRunMeta(query)★ ★新(10/06)★ |
★清掃ログの id → 清掃エリア・予定の終了★(ダッシュボードの直近モード専用。D-159)。★where は cleanLogs() と同じ非公開の mainTimetableWhere()(:750)を共有★。サンプル版は★終了=開始・清掃エリア=ロボット★ |
clean_timetables clean_timetable_histories(findTimetables() 経由) |
★:498(137a94b)★ |
★:1088(137a94b)★ |
| 24 | ★errorReports(query)★ ★新(10/06)★ |
★エラー一覧★({ items, total })。★2段階で読む★: キーだけを SQL で status IN・facility_id IN・期間に絞って全件読み → filterSortErrorReportKeys() で絞り込み・並べ替え → ページの error_id だけ列を足して読む。★0件なら2段目と予定のクエリを撃たない★。2段目で消えていた行は飛ばす(一覧全体を500にしない) |
robot_error_reports + 予定2表(findTimetablesByChunks()) |
★:642(f02eb1d)★ |
★:1677(f02eb1d)★ |
| 25 | ★errorReportById(id)★ ★新(10/06)★ |
エラー一覧の1行(★PATCH の前後の確認用。事前チェックかどうかを問わない★。大小文字は JS の === で決める) |
robot_error_reports + 予定2表(findTimetableById()) |
★:653★ |
★:1719★ |
| 26 | ★updateErrorReportColumns(id, patch)★ ★新(10/06)★ |
★robot_error_reports の4列を更新★({ matched })。★中身は updatePrecheckErrorColumns() と同じ3行を共通化せずに並べた★(/alerts 側の挙動を変えないため) |
★robot_error_reports(書く)★ |
★:663★ |
★:1742(★書き込み用の接続★)★ |
★改訂3 の3本(24〜26)★のページの切り出しと行の組み立ては、lib/db/index.ts の非公開2関数 errorReportPageIds()(:521)・errorReportItemsInPageOrder()(:535)を fixtures 版と Prisma 版が共有しています(規則を2箇所に書かない)。
★notImplemented() の呼び出しは2件です★(grep -n "notImplemented(" lib/db/index.ts は3行当たり、1行は :700 の関数の定義そのもの)。inspections()(lib/db/index.ts:1800)と inspectionDetail()(:1803)の Prisma 版が未実装で、呼ばれると例外を投げます。
async inspections() {
return notImplemented("inspections");
},
これは意図した状態です。点検記録はメインDBに保存しない(DFA 独自ストアに分離する)と決まっており(CLAUDE.md §7 / D-004)、画面も PlaceholderScreen(準備中の札)のままです。黙って空配列を返さないのは、「データが無い」のか「未実装」なのかを切り分けられるようにするためです(lib/db/index.ts:694-699)。
それ以外の20メソッドは、サンプル版・Prisma版の両方が実装済みです。
★サンプル版を notImplemented にしないこと★ — この警告が lib/db/index.ts の3箇所(:542 :577 :608)に書かれています。ダッシュボードも清掃面積の画面も USE_FIXTURES=true で開けなければならないからです。テストでも固定されています(lib/fixtures/__tests__/masters.test.ts:22 :91)。
lib/fixtures/ — サンプルデータ(15ファイル。★改訂3 時点 16★)DB 接続前の開発はすべてここで行いました(D-044)。DB 接続後も削除しません(テスト用・デモ用・障害切り分け用。D-045)。行数は 2026-10-05 の wc -l です。
| ファイル | 行 | 持っているデータ |
|---|---|---|
index.ts |
23 | まとめ役(14ファイルを再輸出) |
_helpers.ts |
35 | 日時ヘルパー。「昨夜の清掃」という運用イメージが崩れないよう日付を相対で作る |
clean-logs.ts |
269 | ★最重要★ 清掃詳細ログ。判定の境界値、L1〜L3、31日分、該当0件、長い施設名、1施設2台 |
clean-task-reports.ts |
366 | 清掃結果(clean_task_reports 相当)。★clean_area を清掃ログの完了率から逆算して作る★(完了率が面積から runCompletionRate() で決まるようになったため。面積を独立に作ると USE_FIXTURES=false 経由で境界値 90.0 / 70.0 / 10.0 が壊れる。指示書AS 段階6)。clean_ratio 列には清掃ログと同じ値を入れてあるが、★完了率には使っていない★ |
task-status-rule.ts |
76 | ★clean_task_reports.status の決め方の唯一の定義★。clean-logs.ts と clean-task-reports.ts が共有 |
areas.ts |
215 | エリア別完了率。★面積は完了率から作り、runCompletionRate() で元の完了率に戻ることをテストで確かめている★ |
prechecks.ts |
198 | アラート(事前チェック) |
★error-reports.ts★ ★新(10/06)★ |
220 | ★エラー一覧★(FX-ERR-01〜11。★DB の行の形★で持つ。表1 E1〜E9 と制御盤・清掃開始失敗)。既定10行・全11行。error_level 等はダミー(docs/fixtures.md §13) |
reports.ts |
559 | レポート(KPI・施設別・連続問題・エラー内訳)。★今年・前年の期間を含む★ |
facilities.ts |
285 | 施設マスタ。ロボット 0台 / 1台 / 3台 |
groups.ts |
26 | ホールディングス |
customers.ts |
33 | 顧客(★4件。だからレポートの KPI は開発中いつも 422 になる★) |
schedules.ts |
220 | スケジュール |
ds-devices.ts |
132 | D-S制御盤 |
ds-alerts.ts |
185 | D-Secアラート |
inspections.ts |
154 | 点検記録(画面は未着手) |
★サンプルデータにはわざと異常なケースが仕込んであります。★ 「きれいなデータ」だけでは、境界値や表示崩れのバグが見つからないからです。実例:
facilities.ts:262)— 「1清掃エリア=1ロボット」の前提が崩れたときに静かに空文字を返さず例外で気づけることを確認するためprechecks.ts:195-198)— 該当0件の見え方を確認するためreports.ts:392 :436)ds-devices.ts は「正常→異常→正常→異常」の順で定義してあり、★画面で「異常→正常」に並び替わること★を確認する用途です(:12-13)用意すべきケースの一覧は docs/fixtures.md が正です。 新しい境界値を足すときは、先にそちらへ書いてください。
もう1つの決めごと: ★サンプルデータであっても判定は本番と同じ関数を通します★(clean-logs.ts:17-20)。grade と escalation_level は lib/judge/ が埋めています。
★実在の店舗名・住所・電話番号・氏名は絶対に入れないこと★(lib/fixtures/index.ts:9。CLAUDE.md §7)。★ステージングには実店舗のデータが入ったので、なおさら混ぜないでください★。
変えると何が起きるか: サンプルから境界値のケースを消すと、lib/judge/__tests__/ は通ったまま画面だけが壊れます。全テストが通っていても、fixtures は画面で叩いて確認してください。
prisma/schema.prisma と lib/generated/prisma/| 項目 | 内容 |
|---|---|
prisma/schema.prisma |
952行。★39モデル★ — TeamWorks の定義書の28テーブル + 不要テーブル clean_link_schedules + Laravel の基盤10テーブル(:37-41)。最後の db pull は2026-09-11(DDL clean_watch_20260911.sql。:15-27) |
lib/generated/prisma/ |
schema.prisma から自動生成される読み書き部品(Prisma クライアント)。★.gitignore 済み(.gitignore:47)★ |
流れは一方通行です。
TeamWorks がDBを変える → prisma db pull → schema.prisma が書き換わる → prisma generate → lib/generated/prisma/ ができる
★どちらも手で直さないでください★(D-056 / CLAUDE.md §9.1)。schema.prisma は db pull の生成物なので、手で直しても次の db pull で消えます。消えた瞬間、「直したはずの型」が静かに元へ戻ります。
★例外が1つだけあります★(D-156 項3 / 指示書AT)。db pull の後に updated_at 列へ @updatedAt を手で付け直す運用(D-059)がありますが、★RobotErrorReport.updated_at にだけは付けません★(prisma/schema.prisma:338-344。ファイル冒頭 :33-34 にも注記)。アプリがこの表の4列を書くので、付けると Prisma が updated_at を勝手に書き込もうとするためです(8.10)。★付け忘れ・付け直しはガードテスト(lib/db/__tests__/robot-error-write-sql.test.ts)が見張っています★。
★今後の DDL 変更待ち★: robot_error_reports をエラー全般の表にし、robot_id / serial_number を NULL 可にする(制御盤・セキュリティのエラーも入れる)と TeamWorks が回答しています(第10回 N-5)。★DDL の変更後に db pull します★(D-155 項5。記号と本文の食い違いを第11回 P-8〜P-10 で照会中。D-157)。
lib/generated/prisma/ が git に入っていないということは、clone 直後の環境にはこのフォルダが存在しないということです。だから lib/db/prisma.ts は lib/db/index.ts の先頭で静的に読み込まれません(prisma.ts:4-8)。読み込むようにすると、Prisma クライアント未生成の環境(USE_FIXTURES=true の通常の開発者を含む)で npm test が全滅します。
メインDBへのマイグレーション作成・スキーマ変更は禁止です(CLAUDE.md §7)。
変えると何が起きるか: schema.prisma を手書きで直して型を合わせると、ローカルでは通って本番で落ちます。ローカル検証環境は MySQL 8.4、本番は MariaDB 10.11 で、同一ではありません(D-100)。
★DB に入っている時刻の値は、列の型(TIMESTAMP / DATETIME)によらず JST の壁時計の値です★(CLAUDE.md §3。ユーザー指摘 2026-10-01「データベース上の時間は全てJSTに変換済みです」)。★生の値に9時間を足さないこと★。
| 段 | 何をするか | 場所 |
|---|---|---|
| 読む | Prisma のアダプタは DB の文字列を「UTC」とみなして日時にする。そのままだと真の時刻より9時間先を指すので、fromDbJst() で9時間引いて真の時刻に戻す |
lib/format/date.ts:67(理由は :48-66) |
| 画面に出す | jstIsoFromDb() が "2026-09-11T02:00:00+09:00" の形にする。★DB の生値と画面の表示が一致すれば正しい★ |
lib/format/date.ts:96 |
| 期間で絞る | JST の YYYY-MM-DD を、DB の壁時計値の範囲に直す |
dbDateRange() lib/db/date-range.ts:50 |
| 書く | アラートの対応開始日時・解消日時は toDbJst() を1回だけ通して書く |
lib/format/date.ts:82 / lib/db/robot-error-write.ts:52 |
| 時差の値 | 540分(9時間) | DB_DATETIME_OFFSET_MINUTES(lib/config/index.ts:211) |
経緯(混乱しやすいので):
control_reports.reported_at は UTC で書かれていて9時間早く見え、robot_error_reports は errored_at が正しい JST で、ずれていたのは created_at(UTC)の側でした★(TeamWorks 第10回 N-14a / N-14b)fromDbJst() の前提どおりになるためです。★残る危険は、再登録されなかった古い TIMESTAMP 行だけ★(そこだけ +9時間に見える)。再実測で確かめます9c175a2)、取り消した(d540335)経緯があります★。★推測で9時間ずらさないこと★lib/config/index.ts:200-210 のコメントは「@@system_time_zone = JST」(TeamWorks 第8回 A2)と書いていますが、ステージングの実測は UTC でした(2026-10-02 に JST に変更。D-155 項4)。値(540分)はどちらの場合も正しく働きます変えると何が起きるか: fromDbJst() を通さずに toJstIso() へ直接渡すと、表示が9時間進むうえ、isFutureSchedule() の比較が狂い、★深夜2時の清掃が翌朝の確認時刻でも「まだ未来」と判定されて画面から消えます★(lib/format/date.ts:60-62)。
data/settings/ — メインDBに書けないものの置き場メインDBは読み取り専用で、書いてよいのは4カラムだけです(CLAUDE.md §3。その4カラムの書き込みは 8.12)。それ以外に画面で設定した値は、data/settings/ の JSON ファイルに行きます。
| ファイル | 中身 | 書き込む窓口 | 既定パスを決めている場所 |
|---|---|---|---|
ds-device-notes.json |
D-S制御盤の備考 | PATCH /api/ds-devices/{serial_number}/note |
lib/settings/ds-device-notes.ts:54 |
advice.json |
アドバイス文言の編集内容 | PATCH /api/settings/advice(★呼ぶ画面は0件★) |
lib/settings/advice.ts:45 |
display-limits.json |
表示上限(連続日数の頭打ち) | PATCH /api/settings/display-limits |
lib/settings/display-limits.ts:50 |
この作業環境では ds-device-notes.json だけが存在します(3バイト=実質空。2026-10-05 確認)。残り2つは初回保存時に作られます。保存されていない間は、コードに書かれた既定値が使われます。
読み書きの共通部品は lib/settings/json-store.ts(196行)です。押さえるべき点が3つあります(:5-17)。
/data は .gitignore 済み(.gitignore:50)★ = git のバックアップがありません。だから壊れたファイルは .bak に退避してから書き直しますlib/logger を import していません★ — すると @/lib/db 経由でサンプルデータ全体を引き込んでしまうためです変えると何が起きるか: これらをメインDBに保存しようとすると、権限エラーになります(更新用アカウントは4カラムしか UPDATE できません)。サーバーを入れ替えるときに data/ を引き継ぎ忘れると、備考・アドバイス文言・表示上限が全部既定値に戻ります。git には入っていないので、復元できません。★サーバーの構築・保守は TeamWorks です(D-143)★ので、移行手順にこのフォルダを明記しておく必要があります。
★D-Watch がメインDBに書き込むのは、ここで説明する1本の道だけです★(指示書AT。D-152 / D-156)。 ★改訂3(2026-10-06): エラー一覧の PATCH /api/errors/{error_id}/status(app/api/errors/[error_id]/status/route.ts)が加わり、道は2本です★(D-160 / 指示書AW)。 ★順序と部品は下の図と同じ★で、違うのは ③⑥ が db.errorReportById()(★robot_error_reports に行があれば書ける。事前チェックかどうかを問わない★)、⑤ が db.updateErrorReportColumns()(★同じ writeRobotErrorColumns()★)、404 の文言が「エラーが見つかりません」であることだけです。 ★/prechecks のルートとは共通化していません★(/alerts の挙動を変えないため。指示書AW D12)。★ステージングでは 2026-10-06 16:11 JST に /errors から1行を書いて戻し、4列が操作前と完全一致・権限エラーなし★(先方投入データ。docs/handoff.md 追記62)。 ★改訂4(2026-10-06。D-165): 画面から送るのは「編集モード」の「保存(N件)」を押したときだけです★。それまでの「プルダウンで選んだら即保存」(D-161)・「備考はフォーカスが外れたら保存」(指示書AT §3-8)は廃止しました。 画面側の規則は lib/prechecks/edit-drafts.ts(純粋関数。アラートとエラー一覧が共有)にあります — ★変えた行だけを、行の並び順に1行ずつ順に送る(並列にしない)★/★1行の失敗で止めない★(残りも送り、失敗した行は下書きを残す)/★送る本文は対応状況が必ず・備考は変えたときだけ★(note を省略すると備考は変えない。備考を消すときは note: "")。★サーバー側(下の図と表)は変えていません★ — API・書き込む列・日時の規則は指示書AT のままです。 書くのは robot_error_reports の4列 — ★status(対応状況)/ comment(備考)/ responded_at(対応開始日時)/ resolved_at(解消日時)★ — だけで、CLAUDE.md §3 が更新用アカウントに許している列と同じです。★それ以外の列・それ以外のテーブルに書くコードはありません★。★書き込んでよいのはステージングだけ★(D-152)で、本番DBにはまだ接続していません。
flowchart LR
S["アラート画面<br/>AlertsScreen.tsx<br/>(編集モードの「保存(N件)」で<br/>変えた行だけ1行ずつ)"] -->|"PATCH /api/prechecks/{id}/status<br/>{ status, note? }"| R["ルート<br/>app/api/prechecks/[id]/status/route.ts"]
R -->|"① 形式"| P1["parsePathErrorId()<br/>lib/api/path-params.ts"]
R -->|"② 本文"| P2["parsePrecheckStatusUpdateBody()<br/>lib/prechecks/status-update.ts"]
R -->|"③ 実在(参照用の接続)"| G["db.precheckItemById()"]
R -->|"④ 計画(変える列だけ)"| PL["planPrecheckStatusUpdate()"]
R -->|"⑤ 書き込み(書き込み用の接続)"| W["db.updatePrecheckErrorColumns()<br/>→ writeRobotErrorColumns()<br/>lib/db/robot-error-write.ts"]
W --> DB[("robot_error_reports<br/>4列だけ UPDATE")]
R -->|"⑥ 読み直し"| G
R -->|"監査ログ"| L["db.write<br/>lib/logger/db-write.ts"]
| 段 | 何をするか | 場所 |
|---|---|---|
| ① 形式 | URL の {id}(error_id。varchar(50))を検査。50文字を超えたら 422 |
parsePathErrorId()(lib/api/path-params.ts:89)。上限は PATH_ERROR_ID_MAX_LENGTH |
| ② 本文 | status(未解消 / 対応中 / 解消)は必須、note は任意。★それ以外のキーは捨てる★(updated_at など)。備考は400字まで(トリム前) |
parsePrecheckStatusUpdateBody()(lib/prechecks/status-update.ts:100) |
| ③ 実在 | ★参照用の接続★で1行読む。★書けるのは /alerts に「事前チェック異常」として出る行だけ★。出発失敗・予定に紐づかないエラー・大小文字だけ違う id は 404 |
db.precheckItemById()(Prisma 版 lib/db/index.ts:1429-1468。★a961829 では :1652-★。★D-171 から親の本清掃の clean_task_reports も読む(main_run_succeeded のため。参照用の接続)★) |
| ④ 計画 | 今の値と要求から、★変える列だけのパッチ★を作る。何も変わらなければ書かずに今の値を返す | ★planPrecheckStatusUpdate()(lib/prechecks/status-update.ts:50)★ — パッチを作る唯一の関数 |
| ⑤ 書き込み | ★書き込み用の接続★で updateMany の1文。★4列以外のキーが来たら例外★ |
writeRobotErrorColumns()(lib/db/robot-error-write.ts:121-133)。呼び出しは lib/db/index.ts:1469-1475 |
| ⑥ 読み直し | 書いた後の1行を返す。画面はこの値でその行だけ差し替える | db.precheckItemById() |
ルート本体は app/api/prechecks/[id]/status/route.ts:88-173(順序の宣言は :11-13)です。
planPrecheckStatusUpdate())lib/prechecks/status-update.ts:10-11):44-45)comment = NULL)/ 前後の空白は落とす / 同じなら書かない(:47-48)lib/prechecks/__tests__/status-update.test.ts(#1〜#12)根拠: D-156 項4(すべて推奨どおり。「戻すと消す・解消済みの再解消は上書きしない」)
| 仕掛け | 何を防ぐか | 場所 |
|---|---|---|
| ★書き込みはこの1ファイルだけ★ | 4列以外を書くコードが散らばること | lib/db/robot-error-write.ts:1-22。書いてよい列の一覧は ROBOT_ERROR_WRITABLE_COLUMNS(:29-34)。★ここに列を足さないこと★ |
| ★実行時の歯止め★ | 型をすり抜けたキーが DB に届くこと | robotErrorColumnsFromPatch()(:70-108)— 4つ以外のキー・4列以外の列・空のパッチは例外。★例外のメッセージに備考の中身を出さない★ |
★update ではなく updateMany★ |
update は SELECT → UPDATE → 全列 SELECT をまとめて走らせる。更新用アカウントに SELECT 権限があるか分からない(第11回で照会中。D-156 項5) |
:12-15 / :127-130 |
★RobotErrorReport.updated_at に @updatedAt を付けない★ |
付けると Prisma が updated_at = <アプリの時刻(UTC)> を勝手に SET に足す → ①5列目の書き込みで権限エラー ②JST の DB に UTC の値が入る。DB は ON UPDATE current_timestamp() で自分で更新する |
prisma/schema.prisma:338-344(D-156 項3) |
| ★ガードテスト★ | 誰かが db pull の後で @updatedAt を機械的に付け直すこと |
lib/db/__tests__/robot-error-write-sql.test.ts(★本物の生成クライアント+SQLを記録するだけの偽アダプタ★で、SET 句が渡した列だけ・updated_at を含まないことを確かめる。DBには接続しない) |
★日時は toDbJst() を1回だけ通す★ |
9時間ずれた日時を書くこと | dateOrNull()(:52-59)。lib/format/date.ts:82 |
| ★大小文字の照合をアプリで確かめる★ | DB の照合順序は大小文字を区別しないので、rer-1 で RER-1 の行を書き換えられること |
lib/db/index.ts:1442 |
| ★件数0なら 404★ | 確認と書き込みの間に行が消えた場合(ステージングのデータ入れ替え等) | route.ts:153-156 |
getWritePrisma()(lib/db/prisma.ts:100-137)が書き込み用の接続を返します。判定は databaseWriteCredentials()(lib/config/env-check.ts:162-170。npm run check:env と同じ1本)です。
DATABASE_WRITE_USER / DATABASE_WRITE_PASSWORD |
どうなるか |
|---|---|
| ★2つとも未設定★ | ★参照用の接続(getPrisma())で書く★(いまはこれで動く見込み) |
| ★片方だけ★ | ★例外★(黙って参照用に倒さない) |
| 2つとも設定 | 別の接続を作る。★接続先は参照用と同じDB★なので DATABASE_HOST / DATABASE_PORT / DATABASE_NAME も要る(★URL 形式は設けない★ — %40 が壊れる問題を持ち込まないため) |
db.write★成功・失敗の両方で1件ずつ★出します(lib/logger/db-write.ts:53。型は lib/logger/events.ts:241)。何も変わらない要求では書いていないので出しません。 載せるのは表名・error_id(★文字列のまま★)・SET した列・状態と日時の前後の値・comment_changed(★備考が変わったかどうかだけ。中身は載せない★)・成否です。 ★失敗時もエラーメッセージは載せません★(DB のユーザー名が入るため)。載せるのは dbErrorCode()(db-write.ts:22)が取り出したコードだけです。
USE_FIXTURES=true)のときlib/db/fixture-precheck-overrides.ts が ★id → 4項目の上書き表をメモリに持ちます★(lib/fixtures/prechecks.ts は書き換えない)。★dev サーバを再起動すると消えます★。Prisma 版と同じ歯止め(robotErrorColumnsFromPatch())を通し、★事前チェック異常の行だけ★書けるのも同じです(:40-69)。 ★エラー一覧は別の上書き表 lib/db/fixture-error-report-overrides.ts★(状態を共有しない。値は toDbJst() 済みの DB の列の形)。
変えると何が起きるか: ★robot-error-write.ts を通さずに Prisma の update を別の場所に書くと、4列の歯止めも @updatedAt の見張りも効かなくなります★。schema.prisma の RobotErrorReport.updated_at に @updatedAt を付け直すと、ステージングでは権限エラー(または UTC の値の混入)になりますが、npx prisma generate → npm test でガードテストが赤くなるので気づけます。★control_error_histories(D-Secアラート)の4列を書くときも、この形を真似て1ファイルに閉じ込めてください★(未着手)。
画面には出ないが、すべてのリクエストが必ず通る部分です。ここを触ると全画面・全APIに一斉に効きます。
proxy.ts — すべての入口(115行)proxy.ts(リポジトリ直下)は Next.js が全リクエストの最初に呼ぶ関数です。旧称 middleware.ts は Next.js 16 で非推奨になり proxy に改名されました(proxy.ts:4-7)。★1プロジェクトに1ファイルしか置けない★規約なので、ここには順序制御だけを書き、中身は lib/proxy/ に分けてあります(proxy.ts:9-11)。
flowchart LR
A["リクエスト到着"] --> B["① 相関IDの発行<br/>lib/proxy/request-id.ts"]
B --> C{"② IP制限<br/>lib/config/allowed-ips.ts"}
C -- "拒否" --> D["403 JSON<br/>code: forbidden<br/>ログ auth.ip_denied"]
C -- "通過" --> E{"③ 認証<br/>共有トークン"}
E -- "APIが未認証" --> F["401 JSON<br/>code: unauthorized"]
E -- "画面が未認証" --> G["/login?next=元のパス へ転送"]
E -- "通過・公開パス" --> H["本来の画面・APIへ"]
| # | 関門 | 実装 | 拒否時に返るもの |
|---|---|---|---|
| ① | 相関ID(リクエストごとの通し番号)の発行 | lib/proxy/request-id.ts:27-35(crypto.randomUUID()) |
拒否しない(必ず発行) |
| ② | IP制限 | lib/proxy/authenticate.ts:78-83 |
403 {error:{code:"forbidden",message:"このIPアドレスからは利用できません"}}(proxy.ts:66-74) |
| ③ | 認証 | lib/proxy/authenticate.ts:85-91 |
APIは 401 code:"unauthorized"(proxy.ts:82-86)/画面は /login?next=<元のパス> へ転送(proxy.ts:90-92) |
★①が先である理由★: 弾いたリクエストのログにもIDを付けるためです(proxy.ts:13-17、docs/logging-design.md §9)。逆にすると、★いちばん追跡したい403・401のログだけ request_id が "-" になります★。発行したIDは runWithRequestId() で包んでから使うので(proxy.ts:45)、この中で出るログにも必ず載ります。
★②が③より先である理由★: IPで弾くべき相手に、トークンの当たり外れを試させないためです(authenticate.ts:8-9)。
★クライアントが送ってきた x-request-id は信頼せず常に上書きします★(request-id.ts:19-21)。外から与えられたIDを通すと、別リクエストのログに同じIDを混ぜ込まれて相関が壊せます。
★認証失敗そのものはログを出しません★(proxy.ts:78-80)。未認証は「まだログインしていない」だけで、画面を開くたびに出るとノイズになるためです。追跡したいログイン試行は /api/auth/login 側が auth.login_success / auth.login_failed として記録します。
変えると何が起きるか: proxy.ts:112-114 の matcher を消すとCSS・画像にも関門がかかり画面が崩れます。matcher から /login を除外すると保護漏れが見つけにくくなるので、除外せず関数の中の isPublicPath()(authenticate.ts:35-39)で通します。runtime を書くとエラーになります(proxy は Node.js 固定)。
lib/auth/(4ファイル)P1は社内利用のみのため、利用者ごとのアカウントは無く、★共有トークン1本★の簡易認証です。
| ファイル | 行数 | 役割 | 主な定数・関数 | 変えると何が起きるか |
|---|---|---|---|---|
token.ts |
49 | トークン照合 | verifyToken()(:22)/ readAuthToken()(:47) |
★=== に書き換えると応答時間の差から1文字ずつ推測されます★(タイミング攻撃)。必ず timingSafeEqual を通す(:4-8)。★長さが違う場合も同じ時間を使ってから false を返す★(:34-38) |
cookie.ts |
33 | ログイン状態を保つcookie | SESSION_COOKIE_NAME = "dwatch_session"(:12)/ httpOnly secure sameSite:lax path:/(:28-33) |
名前を変えたら lib/logger/redact.ts:45 の伏せ字対象も同時に直す。直さないと★cookie値がログに出ます★ |
client-ip.ts |
78 | 接続元IPの取り出しと許可判定 | clientIp()(:36)/ isAllowedIp()(:66) |
★下記★ |
next-path.ts |
31 | ログイン後の戻り先を安全な値に丸める | safeNextPath()(:14)/ DEFAULT_NEXT_PATH="/dashboard"(:12) |
検査を外すと ?next=//evil.example で外部サイトへ飛ばされます(フィッシングの踏み台) |
★AUTH_TOKEN が未設定・空なら verifyToken() は常に false★(誰も入れない。token.ts:26)。★設定漏れが「認証を無効化して全部通す」にならない★安全側の設計です。トークンの実値はこの文書に書きません。
★cookie に有効期限を付けません★(cookie.ts:24-26)。CLAUDE.md §7 が「トークン期限/残日数の実装」を禁止しているためで、結果としてブラウザを閉じると消えるセッションcookieになります。
X-Forwarded-For は末尾を見る★X-Forwarded-For(XFF)は「どこから来た通信か」を中継サーバが書き足すヘッダです。★先頭はブラウザ側が勝手に書ける★ため、先頭を信じると X-Forwarded-For: <許可IP> と自分で書くだけでIP制限を素通りできます。手前の nginx は★自分が見た接続元を末尾に追記★するので、信頼できるのは末尾側だけです(client-ip.ts:12-16。D-077 項5)。
TRUSTED_PROXY_HOPS は「自分の前に中継サーバが何段いるか」で、clientIp() は★末尾からこの数だけ遡った要素★を採用します(client-ip.ts:36-49)。現在 ★1★(LBと nginx が同一なので1段。allowed-ips.ts:63)。要素数が足りなければ ★null(取れない扱い)★ とし、★足りない分を先頭で埋めません★(client-ip.ts:18-19)。
★表記の正規化もしません★(:21-25)。IPv6の短縮形・::ffff: 付き・ポート付きはそのまま扱うので、★ALLOWED_IPS には nginx が実際に付ける表記をログで確認してから書きます★。
★要素数で「直接アクセスかどうか」を判断してはいけません★(:27-29)。クライアントがXFFを送らない普通のリクエストも、nginx が1個足して要素1個になるためです。
変えると何が起きるか: 実際1段なのに 2 にすると全員 null 扱い=★全員403★(すぐ気づけます)。実際2段なのに 1 のままだと★末尾がLBの内部IPになります★(こちらは気づきにくい)。
| 定数 | 現在の値 | 意味 | 場所 |
|---|---|---|---|
ALLOWED_IPS |
★空の配列★(例のコメント1行のみ) | 許可IP一覧。★完全一致のみ。CIDR・レンジ・ワイルドカード非対応★ | lib/config/allowed-ips.ts:24-26 |
ALLOW_ALL_WHEN_EMPTY |
★true★ |
一覧が空のとき制限を「かけない」 | :35 |
ALLOW_DIRECT_ACCESS |
process.env.ALLOW_DIRECT_ACCESS === "true" |
nginx を通らない直接アクセスを通すか(★4つの中で唯一の環境変数★) | :52 |
TRUSTED_PROXY_HOPS |
1 |
信頼する前段プロキシの段数 | :63 |
★ALLOWED_IPS / ALLOW_ALL_WHEN_EMPTY / TRUSTED_PROXY_HOPS の3つは環境変数ではなくコードに直接書かれています。.env.local を直しても変わらず、変更にはコード修正とデプロイが必要です。★
現状の判定順(isAllowedIp() client-ip.ts:66-78):
ip === null(XFFから取れない=直接アクセス)なら ★ALLOW_DIRECT_ACCESS だけで決める★。ALLOW_ALL_WHEN_EMPTY は一切関与しないALLOW_ALL_WHEN_EMPTY を返す★この2つを1つの設定値にまとめない理由★(:54-57): まとめると、一覧が空の初期状態のまま本番へ出た瞬間に★直接アクセスが素通り★します。
★★結論: 一覧が空で ALLOW_ALL_WHEN_EMPTY が true の現在、nginx 経由で接続元IPが取れるリクエストはどのIPからでも全部通ります。IP制限は実質かかっていません。★★ 本番前に ALLOWED_IPS を埋め、ALLOW_ALL_WHEN_EMPTY を false にする必要があります(:31。D-101)。★これは検証段階のあいだは保留で、リリース直前にまとめて行う方針です★(D-154。CLAUDE.md §3 の対象一覧に入っています)。
変えると何が起きるか: ALLOWED_IPS に1件でも書くと ALLOW_ALL_WHEN_EMPTY は無視され、★書いたIP以外は全部403★。★プライベートIP(10.x / 172.16-31.x / 192.168.x)は書かないこと★(:20-22)。万一プロキシが2段だった場合に末尾がLBの内部IPになり、そこに一致すると★「全員素通り」という逆方向の事故★になります(1段確定なら全員403で即座に気づけます)。
lib/logger/(7ファイル)| ファイル | 行数 | 役割 | 変えると何が起きるか |
|---|---|---|---|
index.ts |
134 | ロガー本体。logEvent()(:113)が1行JSONを出す。★mixin()(:46-51)が全ログに request_id と data_source(fixtures / prisma)を自動付与★ |
mixin を外すと全ログから相関IDが消える |
events.ts |
429 | ★イベント名と項目の型定義★。ここに無いイベントはコンパイルが通らない | 新しいログを出すには、まずここに型を足す |
redact.ts |
234 | ★出してはいけない項目の検出・除去★ | 下記 |
context.ts |
73 | 相関IDの保管庫(AsyncLocalStorage)。REQUEST_ID_HEADER = "x-request-id"(:34) |
壊すとリクエストの串刺し追跡ができなくなる |
timestamp.ts |
30 | ISO 8601 の JST(+09:00)ミリ秒付き(:24-30) |
秒精度にすると同一秒内の順序が追えない |
api.ts |
50 | APIの catch から呼ぶヘルパー。★画面へ返すエラー本文をそのまま受け取る★のでログと表示がズレない(:3-7) |
文言を組み直すとログと応答が食い違う |
★db-write.ts★ ★新(10/05)★ |
60 | ★メインDB書き込みの監査ログ db.write★ を出す logDbWrite()(:53)と、例外から DB のエラーコードだけを取り出す dbErrorCode()(:22)。★成功・失敗の両方で1件ずつ★。★備考の中身もエラーメッセージも載せない★(§8.10) |
err やメッセージを載せると、DB のユーザー名(列の権限エラーの文言に入る)がログに残る |
イベント名の一覧(events.ts の行番号。★2026-10-05 時点★)
| イベント | 行 | いつ出るか |
|---|---|---|
app.start |
:44 |
起動時に1回 |
app.uncaught |
:59 |
catch で拾えなかったエラー |
api.error |
:77 |
APIが意図的に返したエラー |
judge.grade / judge.escalation / judge.advice / judge.areas |
:90 / :110 / :134 / :182 |
判定ロジックの記録。★judge.areas の分子・分母は overall_completion_rate_sum / overall_completion_rate_count★(:194-197。1回ごとの完了率の合計と件数。指示書AR で面積の合計から差し替え、AS で overall_clean_ratio_* から改名。★項目名に式の名前を入れない★ — 式を変えるたびに名前と中身がずれるため) |
★judge.clean_area★ ★新(10/05)★ |
:221 |
総清掃面積(F-08)の集計直後。★完了率用に取り寄せた行数 rate_rows と所要時間 elapsed_ms★、全体の実施回数と完了率(丸める前)。★期間を指定しないと全期間を運ぶので、重さを実データで見るため★(D-156 項2)。出すのは app/api/clean-area/route.ts:99-107 |
db.write |
:242 |
★許可された4カラムへの書き込み★。★2026-10-02 から実際に出るようになった★(最初の利用者は PATCH /prechecks/{id}/status。§8.10) |
inspection.read |
:272 |
点検記録の読み出し |
data.anomaly |
:283 |
データの異常を検知したとき |
settings.store_corrupt / settings.value_invalid |
:315 / :347 |
設定ファイルが壊れていた / 値が範囲外だった |
auth.login_success / auth.login_failed / auth.ip_denied |
:387(3つで1つの型) |
ログイン試行とIP拒否 |
log.forbidden_fields_stripped |
:408 |
★伏せ字の仕組みが実際に項目を取り除いたとき★ |
★client_ip を出すのは auth.ip_denied のときだけ★(events.ts:374-379)。拒否された接続元の生の表記が分からないと ALLOWED_IPS に登録できないためで、★ログイン成功・失敗には出しません★。
redact.ts — 伏せ字の仕組み★値の中身は予測できないので、★キー名で判定して危ないキーごと取り除きます★(redact.ts:1-7)。守っているのは2種類です。
:12-26): facility_name address phone email manager_name charger_name inspector_name customer_name notes comment memo note detail:36-46): cookie set-cookie authorization token session secret password api_key dwatch_session — ★漏れると「読まれる」だけでなく「なりすまされる」ので別枠にしてあります★大文字小文字は無視します(Authorization も拾う。:57-64)。検出したときの動きは環境で変わります(enforceForbiddenFieldsPolicy() :163-175)。
| 環境 | 動き | 理由 |
|---|---|---|
| 開発中 | ★例外を投げて止める★ | 書いた人がその場で気づけるように |
| 本番 | ★例外を投げず、キーを除去して warn を1行出す★ | ★ログの失敗でアプリを落とさない★ |
変えると何が起きるか: リストからキーを消すと、その項目が本番ログに素通りします。追加すると、そのキー名を使う既存ログが開発中に例外で落ちます。★dwatch_session は lib/auth/cookie.ts の cookie 名と同じ文字列でなければなりません★(cookie 名を変えるときは両方直す)。
相関IDと出力先: proxy.ts が発行したIDを runWithRequestId() が保管し、mixin() が全ログ行に request_id として載せるので、1リクエストのログを串刺しで拾えます。出力先は ★stdout のみ★(ファイル化・ローテーションはOS側。index.ts:2)。量は LOG_LEVEL で変え、未設定なら本番 info / それ以外 debug(index.ts:22-25)。詳細は docs/logging-design.md。
instrumentation.ts(80行)★起動時に1回だけ app.start を出し、実際に効いているログレベル / NODE_ENV / ランタイム種別を記録します★(:22-30)。これが無いと「ログが出ないのは LOG_LEVEL のせいか、そもそも起動していないのか」を切り分けられません。
もう1つ onRequestError()(:39-80)があり、catch で拾えなかったサーバーエラーを ★api.error ではなく app.uncaught★ として記録します(:34-37)。★意図的に処理したエラーと捕捉漏れが同じ名前で混ざると、障害調査で区別できなくなるため★です。URLからクエリ文字列を落とすのは、施設名などの値が載るためです(:51-53)。
変えると何が起きるか: logger をファイル冒頭で import するとビルドが壊れます。★必ず関数の中で動的 import すること★(:8-11。pino は Node.js のAPIに依存するため)。
package.json:5-14)| コマンド | 中身 | いつ使うか |
|---|---|---|
npm run dev |
next dev |
開発サーバー起動(日常) |
npm run build |
next build |
本番用に固める |
npm start |
next start |
固めたものを本番として動かす |
npm run lint |
eslint |
書き方チェック |
npm test |
vitest run |
テスト1回実行(CIもこれ) |
npm run test:watch |
vitest |
変更を監視して自動再実行 |
npm run seed:local |
tsx scripts/seed-local-db.ts |
ローカルDB dwatch_local にサンプルデータを入れ直す |
npm run check:env |
tsx scripts/check-env.ts |
★デプロイ前に必ず実行★ |
★スクリプトは8本で、9/14から増減なし★(実測)。
npm run check:env の検査項目.env.local → .env の順に読み(scripts/check-env.ts:16-18)、★エラー1件でも終了コード 1★、警告だけなら 0(:46-53)。検査の中身は lib/config/env-check.ts の純粋関数で、★7項目(判定は10通り)★です(2026-10-02 に7番目が増えた)。
| # | 対象 | 条件 | 重さ | 行 |
|---|---|---|---|---|
| 1 | AUTH_TOKEN |
未設定・空(誰もログインできない) | error | :52-58 |
| 1' | AUTH_TOKEN |
★changeme(.env.example の初期値)のまま★ |
error | :59-66 |
| 2 | ALLOW_DIRECT_ACCESS |
true になっている |
★本番 error / 開発 warn★ | :68-79 |
| 3 | USE_FIXTURES |
本番で false 以外(サンプルデータのまま動く) |
warn | :80-90 |
| 4 | DATABASE_URL |
未設定 | error | :91-99 |
| 5 | DATABASE_URL |
本番でホストが localhost / 127.0.0.1 |
warn | :100-109 |
| 6 | LOG_LEVEL |
pino のレベル名(trace/debug/info/warn/error/fatal)以外 |
warn | :110-119 |
| ★7★ | ★DATABASE_WRITE_USER / DATABASE_WRITE_PASSWORD★ |
2つとも未設定(★書き込みは参照用アカウントで行う★と知らせるだけ) | warn | :122-131 |
| ★7'★ | 同上 | ★片方だけ設定★(書き込みが失敗する) | ★error★ | :132-138 |
| ★7''★ | 同上 | 2つとも設定したが DATABASE_HOST / DATABASE_NAME が無い(書き込み用の接続は部品変数でしか組まない) |
★error★ | :139-147 |
★出力に値そのものを出しません★(DATABASE_URL にはパスワードが入り、出力はCIのログに残るため。env-check.ts:8-12 / :189-192)。★debug を弾かないこと★(:110-112。アプリの LogLevel は3つですが、開発時のしきい値として debug を返すため)。
変えると何が起きるか: この検査を飛ばして本番に出ると、AUTH_TOKEN が changeme のまま(=共有トークンが公開情報)でも起動してしまいます。
.env.example と .env.production.example に書かれている変数です。★実値はこの文書に書きません。.env.local も開いていません。★
| 変数名 | 何を切り替えるか | 既定値 | 設定しないとどうなるか |
|---|---|---|---|
USE_FIXTURES |
サンプルデータか実DBか | 未設定は ★true 扱い★(lib/db/use-fixtures.ts:17 が !== "false") |
サンプルデータのまま動く |
DATABASE_URL |
メインDBの接続先(1行で書く形式) | なし | USE_FIXTURES=false のとき起動時に落ちる(lib/db/prisma.ts:67-71) |
★DATABASE_HOST★ |
接続先を部品に分けて書く形式(★DATABASE_URL より優先される★) |
なし | 未設定なら DATABASE_URL が使われる |
★DATABASE_PASSWORD★ |
同上(★HOST と両方揃ったときだけ部品形式になる★) |
なし | 同上 |
★DATABASE_USER / DATABASE_NAME★ |
部品形式のとき★必須★ | なし | ★何が足りないかを言って起動時に落ちる★(prisma.ts:47-54) |
★DATABASE_PORT★ |
部品形式のポート | 3306(prisma.ts:59) |
既定で動く |
★DATABASE_WRITE_USER / DATABASE_WRITE_PASSWORD★ ★新(10/05)★ |
★書き込み用アカウント★(robot_error_reports の4列の UPDATE だけに使う。getWritePrisma() prisma.ts:100)。.env.example にだけ記載(.env.production.example には未記載) |
なし | ★2つとも未設定なら参照用アカウントで書く★。★片方だけだと書き込みが例外で失敗する★。2つとも設定するときは DATABASE_HOST / DATABASE_PORT / DATABASE_NAME も要る(§8.10) |
AUTH_TOKEN |
共有トークンの期待値 | なし | ★誰もログインできない★(lib/auth/token.ts:26) |
LOG_LEVEL |
ログの量 | 本番 info / それ以外 debug(lib/logger/index.ts:22-25) |
既定で動く |
ALLOW_DIRECT_ACCESS |
nginx を通らない直接アクセスを通すか | 未設定=false=拒否(allowed-ips.ts:52) |
★ローカル開発では全ページ403★。本番は設定しないのが正解 |
NODE_ENV |
本番判定(Node.js 標準。アプリでは設定しない) | 実行方法で決まる | LOG_LEVEL の既定と redact.ts の挙動が開発側に寄る |
NEXT_RUNTIME |
Next.js が自動で入れる。app.start ログに載せるだけ |
Next.js が設定 | ログに unknown と出る(instrumentation.ts:28) |
★部品変数(DATABASE_HOST ほか5本)が用意されている理由★(.env.example / lib/db/prisma.ts:32-33): ★パスワードに @ が含まれると、DATABASE_URL に書いた %40 が dotenv の展開で壊れることがある★ためです(p%40ss → p)。部品変数ならURLエンコードが要りません。★ユーザー名に既定値は置いていません★(実在のDBユーザー名をコードに書かない。CLAUDE.md §9.3)。
★ALLOW_DIRECT_ACCESS は === "true" で判定します。!== "false" にしないこと★(allowed-ips.ts:45-50)。未設定・空文字・タイポ・1・TRUE はすべて拒否側に倒れ、本番で設定し忘れても安全です。★USE_FIXTURES が逆向き(!== "false")なのは「開発しやすさ優先」で、意図的に違えてあります★。
★INSPECTION_DB_URL★ は .env.example に点検記録用SQLiteの接続先として書かれていますが、★lib/ app/ scripts/ prisma/ のどこからも読まれていません★(process.env. の全件検索で0件。2026-10-01 実測)。点検記録の画面自体がサイドバーから隠されている状態(章7-6)と対応しています。
★コード内で実際に読まれる環境変数は上表の14個です★(DATABASE_WRITE_USER / DATABASE_WRITE_PASSWORD を数えて。process.env. と env. の全件検索。ワークツリー配下と node_modules を除く。2026-10-05)。
リポジトリをエディタで開くと、ファイルは数万あるように見えます。そのほとんどは機械が作ったもので、人が読む必要はありません。 この章は「どこを開かなくてよいか」の一覧です。
数字はすべて 2026-10-05 に develop(ceeb736)で実測しました(出所: git ls-files と wc -l。生成物は find で数えたディスク上の実ファイル)。
| 場所 | 規模 | 正体 | git に入っているか |
|---|---|---|---|
lib/generated/prisma/ |
47ファイル / 73,780行 | prisma/schema.prisma から prisma generate が作るDB読み書き部品。2026-09-14・10-01 時点と同じ数字(@updatedAt を1つ外しても行数は変わらない) |
★入っていない★(.gitignore:47) |
node_modules/ |
直下に405項目 | npm install で取ってくる外部部品(Next.js・React・Prisma 等) |
入っていない(.gitignore:4) |
.next/ |
— | next dev / next build が作る作業場所 |
入っていない(.gitignore:17) |
tsconfig.tsbuildinfo |
約190KB | 型チェックを速くするためのキャッシュ | 入っていない(.gitignore:44) |
next-env.d.ts |
数行 | Next.js が自動で書く型の参照。.next/dev/types/ を指す |
入っていない(.gitignore:45) |
package-lock.json |
9,573行 | npm install が書く、外部部品のバージョン固定表 |
★入っている★(手で直さない) |
public/ |
svg 5ファイル | ★初期セットアップ(28f808d、2026-08-26)の雛形の残り★。app/ components/ lib/ のどこからも参照されていません |
入っている |
変えると何が起きるか: lib/generated/prisma/ を手で直しても、次の prisma generate で跡形もなく消えます(章8 §8.8)。package-lock.json を手で直すと、他の人の環境と本番で外部部品のバージョンがずれます。public/ の svg は消しても画面は変わらないはずですが、未確認です(ビルド時の参照までは調べていません)。
| 区分 | ファイル数 | 行数 | 備考 |
|---|---|---|---|
lib/(.ts、テスト除く) |
113 | 20,981 | 判定・整形・データ取得の本体 |
components/(.tsx) |
26 | 5,866 | 画面と部品 |
app/(.ts/.tsx、テスト除く) |
38 | 2,867 | URL の札(page.tsx)とAPIの入口(route.ts) |
| アプリ本体の小計 | 177 | 29,722 | 2026-09-14 時点は 140ファイル / 20,471行、2026-10-01 時点は 172ファイル / 27,977行 |
components/(.module.css) |
24 | 3,784 | 同名 .tsx 専用の見た目 |
app/globals.css |
1 | 94 | 全画面の色・フォント(★日本語フォントを外さない★。CLAUDE.md §0-3) |
ルート直下のコード(proxy.ts instrumentation.ts next.config.ts prisma7.config.ts vitest.config.ts eslint.config.mjs) |
6 | 313 | 設定と横断処理 |
scripts/ |
2 | 2,199 | ローカルDBのシード(2,145行)と環境変数チェック |
prisma/schema.prisma |
1 | 952 | ★prisma db pull の生成物★。人が書いたものではないが、人が読む |
テスト(lib/ 88本 + app/ 16本) |
104 | 29,236 | 章11 |
契約と仕様(d-watch-api.yaml / d-watch-mock.jsx / CLAUDE.md) |
3 | 4,939 | API契約の正 / 画面の正 / 作業ルール |
★アプリ本体(177ファイル / 29,722行)は、生成物 lib/generated/prisma/(73,780行)の約4割です。★ そしてテスト(29,236行)はアプリ本体とほぼ同じ量あります。
つまりこのリポジトリで「人が読むべきコード」は、エディタに見えている量のごく一部です。さらにその中でも、非エンジニアの方が最初に読むべきなのは 章2 で追った5ファイルだけです。身構える必要はありません。
変えると何が起きるか: この表の数字は、次に地図を直すときの基準線です。アプリ本体の行数は 9/14 → 10/01 の17日間で約7,500行(+37%)、10/01 → 10/05 の4日間で約1,700行(+6%)増えました。増え方が急になったら、どこかで同じ処理が二重に書かれ始めていないかを疑ってください。
テストとは、「この入力を入れたら、この答えが返るはずだ」をコードで書いて、機械に毎回確かめさせる仕組みです。
たとえば「完了率 90.0% は ◎」を一度テストに書いておけば、誰かが判定の式を書き換えて 90.0% が ○ になった瞬間に、テストが赤く失敗して知らせてくれます。人が画面を目で見て気づくのを待つ必要がありません。
D-Watch では CLAUDE.md §6-2 が「判定ロジック(◎○△×・L1〜L3・KPI計算・丸め)には必ずテストを書く。期待値表を先に作り、それをテストで固定する。目視確認だけで済ませない」と定めています。
命名の決まり: テストは、対象のファイルと同じフォルダの __tests__/ に、同じ名前+.test.ts で置きます。
lib/judge/grade.ts ← 本体 lib/judge/__tests__/grade.test.ts ← そのテスト
例外は2種類だけです。複数のルートをまたいで確かめるもの(app/api/__tests__/master-filter.test.ts など)と、同じルートをサンプルデータで通す版を分けたもの(route.fixtures.test.ts。app/api/clean-area/__tests__/route.fixtures.test.ts の冒頭に分けた理由が書かれています)です。
動かし方: npm test(中身は vitest run。package.json:10)。GitHub に push すると自動でも走ります(.github/workflows/ci.yml:45-46。Lint → テスト → ビルドの順)。
npx vitest run を 2026-10-05 に develop(ceeb736)で実行した結果です。
Test Files 104 passed (104)
Tests 2469 passed (2469)
Duration 8.95s
104ファイル / 2,469件、すべて成功。所要 約9秒です。(2026-10-01 は 98ファイル / 2,297件。★改訂4 時点(04f974a)は 125ファイル / 2,946件★。下の内訳表は 10/05 のまま)
| 場所 | ファイル | 件数 |
|---|---|---|
lib/ |
88 | 2,164 |
app/(すべて app/api/ の下) |
16 | 305 |
components/ |
★0★ | ★0★ |
実行中に "level":"error" のログが数行流れますが、失敗ではありません。「DBが落ちたら 500 を返すか」「同じ期間のサマリが2行あったら止まるか」といったわざと失敗させる経路を確かめているテストの出力です。
★テストは接続先のDBに接続しません。★ 実DB側の経路を確かめるテストは、Prisma クライアントを偽物に差し替えて動きます(例: app/api/clean-logs/__tests__/route.test.ts:183-185 の vi.mock("@/lib/db/use-fixtures", …) と vi.mock("@/lib/db/prisma", …))。★書き込みのガードテスト(lib/db/__tests__/robot-error-write-sql.test.ts)だけは本物の生成クライアントを使いますが、つなぐ先は「送られたSQLを記録するだけの偽アダプタ」で、やはりDBには接続しません★(§8.10)。
10/01 から増えた6ファイル(すべて指示書AS・AT)
| ファイル | 件数 | 守っているもの |
|---|---|---|
lib/judge/__tests__/run-completion-rate.test.ts |
22 | ★1回ごとの完了率の式と平均★(章5-4。指示書AS §4 の表1・表2) |
lib/prechecks/__tests__/status-update.test.ts |
33 | ★対応状況・備考の更新規則★(日時を埋める・消す。#1〜#12) |
lib/db/__tests__/robot-error-write-sql.test.ts |
10 | ★書き込みのSET句が4列以内で updated_at を含まないこと★(ガードテスト) |
lib/logger/__tests__/db-write.test.ts |
3 | 監査ログからDBのエラーコードだけを取り出すこと |
app/api/prechecks/[id]/status/__tests__/route.test.ts |
27 | 書き込みAPIの入口(422・404・500・監査ログ) |
app/api/prechecks/[id]/status/__tests__/route.fixtures.test.ts |
7 | 同じAPIをサンプルデータで通す版 |
★改訂4(f02eb1d → 04f974a)で増減したテストファイル★(git diff --name-status で数えた。★全体は 125ファイル / 2,946件・全件成功(04f974a。監督の実測)★。ファイルごとの件数は数えていません)
| ファイル | 増減 | 守っているもの |
|---|---|---|
lib/prechecks/__tests__/edit-drafts.test.ts |
★新★ | ★編集モードの下書きと一括保存★(P1〜P8 / D1〜D5 / S1〜S3。D-165) |
lib/__tests__/csv.test.ts |
★新★ | CSV の共通部品(数式インジェクション対策・csvRate() の空欄。D-163・D-164) |
lib/reports/__tests__/facility-performance-csv.test.ts |
★新★ | 施設別パフォーマンスの CSV の列・値・ファイル名(D-163・D-164) |
lib/reports/__tests__/facility-performance-labels.test.ts |
★新★ | 「変化」とステータスの表示名(画面と CSV で共有。D-163) |
app/api/reports/facility-performance/export/__tests__/route.test.ts |
★新★ | CSV の口(422・上限・ファイル名・一覧と同じ絞り込み。D-163) |
lib/format/__tests__/precheck-status.test.ts |
★新★ | 1h前ST / 30分前ST のセル(S1〜S5。null だけ「-」。D-171) |
lib/prechecks/__tests__/status-cycle.test.ts |
★削除★ | (巡回順の関数ごと削除。D-161) |
lib/dashboard/__tests__/aggregate.test.ts |
追加 | ★事前チェックの異常・有無で本清掃の成功を失敗にしない★(D-170。5.9) |
lib/db/__tests__/precheck-mapping.test.ts ほか |
追加 | ★予定行が無い列は null(N1〜N5)・main_run_succeeded(M1〜M6)★(D-171) |
lib/clean-logs/__tests__/result-pending.test.ts |
★新(D-173)★ | ★結果待ちの判定 isResultPending()★(表1。境界ちょうどは確定=false・未来の予定は対象外・終了が無ければ開始から) |
lib/format/__tests__/clean-result.test.ts・lib/clean-logs/__tests__/csv.test.ts・query.test.ts・lib/dashboard/__tests__/aggregate.test.ts・latest-run.test.ts・lib/db/__tests__/consecutive-source.test.ts・report-mapping.test.ts・rescue-mapping.test.ts・app/api/dashboard/__tests__/route.test.ts ほか |
追加 | ★結果待ちの見え方・絞り込み・数え方★(表2 C1〜C11。D-173)。lib/config/__tests__/dashboard-latest-run.test.ts は settleMinutes の削除に合わせて変更 |
lib/dashboard/__tests__/aggregate.test.ts |
変更(8a657a2) |
D-170 のテストを「予定はあり異常なし」(Standby.)と「予定が無い」(null)に分けた(D-171 後の挙動に合わせた) |
lib/ の内訳(多い順)
| フォルダ | ファイル | 件数 |
|---|---|---|
lib/db/ |
19 | 515 |
lib/format/ |
12 | 287 |
lib/judge/ |
6 | 181 |
lib/settings/ |
4 | 122 |
lib/clean-logs/ |
3 | 117 |
lib/reports/ |
3 | 114 |
lib/logger/ |
6 | 93 |
lib/fixtures/ |
6 | 91 |
lib/prechecks/ |
3 | 88 |
lib/facilities/ |
4 | 84 |
lib/auth/ |
4 | 62 |
lib/masters/ |
2 | 57 |
lib/pagination/ |
1 | 56 |
その他9か所(config api clean-area schedules ds-devices ds-alerts proxy dashboard と lib/__tests__/) |
15 | 297 |
components/ のテストは今も0件★これは設定上そうなっています。
include: ["lib/**/*.test.ts", "app/**/*.test.ts"],
(vitest.config.ts:14)
components/** が含まれていないので、仮に components/ にテストを置いても走りません。
その代わり、このリポジトリは「画面の判断を lib/ に外出しして、そこでテストする」設計を徹底しています。画面のコンポーネントは lib/ が返したものを描くだけにする、という分担です。実例が5つあり、どれも冒頭コメントに同じ理由を書いています。
| 判断 | 置き場所 | 描く側 |
|---|---|---|
| ページ送りで何番を描くか | lib/pagination/page-items.ts(:6-8) |
components/ui/Pagination.tsx |
| 要レスキューを何件見せるか | lib/dashboard/rescue-visibility.ts(:6) |
RescueRequiredCard.tsx |
| 清掃面積の内訳を何件見せるか | lib/clean-area/visibility.ts(:10) |
CleanAreaScreen.tsx |
| レポートの4ブロックを1本ずつ出す判定 | lib/reports/block-visibility.ts(:8) |
ReportsScreen.tsx |
| 清掃面積の集計 | lib/clean-area/aggregate.ts(:14) |
(API経由で画面へ) |
lib/pagination/page-items.ts:6-8 の言い方がいちばん直接的です — ★「判断をコンポーネントに置くとテストで固定できない」★。このファイルのテストは、総ページ数 1〜300 を総当たりして「描く番号が1つ残らず実在するページに入っている」ことを機械的に確かめています(:14-16)。ユーザーの要件「3ページ目を押したら有りません、みたいなのはだめです」(:11-13)をそのまま固定したものです。
変えると何が起きるか: 画面の判断を components/ の中に書くと、テストは1件も増えず、全部通ったまま画面だけが壊れる状態が生まれます。vitest.config.ts に components/** を足す場合は、React を描画するテスト環境(environment: "node" では足りない)の追加が要ります(未着手)。
lib/judge/__tests__/★判定ロジックのテストは6本・181件です(10/01 は5本・153件)。
| ファイル | 件数 | 守っているもの |
|---|---|---|
rescue.test.ts |
60 | 要レスキュー店舗の判定(★R-1 を departed で数える「表5(指示書AS)」が増えた★) |
grade.test.ts |
34 | ★◎○△× と表示の丸め★ |
advice.test.ts |
32 | 対応アドバイス(R1〜R4 / B1〜B3)の発火 |
★run-completion-rate.test.ts★ |
22 | ★1回ごとの完了率(runCompletionRate())と、その平均の判定・表示★(D-155) |
kpi.test.ts |
19 | KPI4指標と、平均の畳み方 meanCleanRatio()(★1回ごとの完了率の Σ と件数から平均を出す★) |
escalation.test.ts |
14 | L1〜L3 と連続日数の数え方 |
これらのテストは期待値表そのものが仕様書になっています。非エンジニアの方も、このファイルを開けば「何%なら何判定か」を表で読めます。
grade.test.ts の境界値表(lib/judge/__tests__/grade.test.ts:19-37)
★D-139(2026-09-16)で境界が変わりました。★ 「境界はすべて上のランクに入る」(90.0 は ◎、70.0 は ○)。ただし ★× の境界だけは「10%未満」のまま★なので、10.0 は △ です。ファイル冒頭(:12-15)にその経緯が書かれています。
const table = [
{ name: "◎の下限すぐ上", rate: 90.1, grade: "◎", display: "90%" },
{ name: "◎/○の境界(90%以上が◎なので90.0は◎。D-139)", rate: 90.0, grade: "◎", display: "90%" },
{ name: "◎の境界のすぐ下", rate: 89.9, grade: "○", display: "90%" },
{ name: "丸めると90だが○", rate: 89.5, grade: "○", display: "90%" },
{ name: "○の下限すぐ上", rate: 70.1, grade: "○", display: "70%" },
{ name: "○/△の境界(70%以上が○なので70.0は○。D-139)", rate: 70.0, grade: "○", display: "70%" },
{ name: "○の境界のすぐ下", rate: 69.9, grade: "△", display: "70%" },
{ name: "△の下限すぐ上", rate: 10.1, grade: "△", display: "10%" },
{ name: "△/×の境界(10%未満が×なので10.0は△。D-139でも変わらない)", rate: 10.0, grade: "△", display: "10%" },
{ name: "×の代表値", rate: 9.9, grade: "×", display: "10%" },
{ name: "ゼロ", rate: 0, grade: "×", display: "0%" },
{ name: "未実施", rate: null, grade: "×", display: "-" },
];
表を読むコツは、grade(判定)と display(画面の表示)が別の列になっていることです。89.5 は画面に「90%」と出ますが、判定は ○ です。判定は丸める前の生値で行うからです(CLAUDE.md §4.3)。
この表は judgeGrade() と displayRate() の両方に流し込まれます(:40 と :61)。表の1行が、判定と表示の2つを同時に守っています。また ★docs/fixtures.md §2.1 と lib/fixtures/clean-logs.ts の境界値ケースと同じ値★です(:8-10)。画面で見える判定とテストで通している判定が一致していることを、目でも確かめられます。
変えると何が起きるか: この表を書き換えると、判定の仕様そのものが変わります。表を変えたくなったら、先に CLAUDE.md §4.1 を確認してください(:17)。境界値は TeamWorks との合意事項で、CLAUDE.md §0-2 が「期待値表の確定は分担しない」と定めています。
このリポジトリには、わざと間違ったやり方をして、その間違った結果を「正しく」確かめるテストがあります。名前に「アンチパターンの記録」と付いていて、2本あります。
① 丸めてから判定すると判定が変わる(lib/judge/__tests__/grade.test.ts:44-57)
it("丸めてから判定すると判定が変わることを確認(アンチパターンの記録)", () => {
expect(judgeGrade(89.5)).toBe("○"); // ✅ 生値で判定
expect(judgeGrade(Math.round(89.5))).toBe("◎"); // ❌ 丸めると90になり ◎ に化ける
expect(judgeGrade(69.5)).toBe("△"); // ✅ 生値で判定
expect(judgeGrade(Math.round(69.5))).toBe("○"); // ❌ 丸めると70になり ○ に化ける
});
89.5% の清掃は本当は ○ です。でも先に「90」に丸めてから判定すると ◎ になってしまう。テストはその間違った結果(◎)が出ることを確認して通ります。
なぜこんなテストを書くのか。「やってはいけないこと」を文章で書いても読み飛ばされますが、実際に動くコードで「こうすると、こう壊れる」を見せると誰でも納得できるからです。後から来た人が「表示も判定も同じ数字でいいのでは」と丸めを1箇所にまとめたくなったとき、このテストが理由を示します。
★D-139 で事故の向きが逆になった★こともコメントに残っています(:45-48)。旧規則では 90.4 を丸めると ◎ が「消えた」のに対し、新規則では 90 も ◎ なので消えません。いまは ★「無い ◎ が丸めで生まれる」側が事故★です。それでも「表示用に丸めた値で判定しない」という原則は変わりません。
★注記★: CLAUDE.md §4.3 のコード例のコメントは、第3版(10/01)の時点では「90.4%が丸めで90%になり、◎判定が消える」(D-139 以前の規則での説明)のままでしたが、★指示書AR で「89.5%が丸めで90%になり、本当は○なのに◎になる」に直りました★(2026-10-05 確認)。
grade.test.ts:49-50はjudgeGrade(Math.round(90.4))を"◎"と固定しています。
② カレンダー日で数えると L3 が L1 に化ける(lib/judge/__tests__/escalation.test.ts:72-83)
月・水・金に清掃がある施設で、3回続けて失敗したとします。連続日数は「スケジュールがある日」で数えるので 3日=L3 です(:55-70)。ところが、清掃の予定が無い火・木を「問題なし」として混ぜてしまうと、連続が途切れて L1 になります。テストはその間違った結果(L1)を確かめて通ります。
const wrong = [
{ date: "2026-08-03", isProblem: true },
{ date: "2026-08-04", isProblem: false }, // ← 本来含めてはいけない
{ date: "2026-08-05", isProblem: true },
{ date: "2026-08-06", isProblem: false }, // ← 本来含めてはいけない
{ date: "2026-08-07", isProblem: true },
];
expect(judgeEscalation(wrong).level).toBe("L1");
L3(オンサイト訪問を検討)が L1(現場へ連絡)に化けるのは、運用上もっとも危ない間違いの1つです。しかも例外もエラーも出ません。だから「こうすると壊れる」を動く形で残してあります。
| # | ファイル | 件数 | 守っているもの |
|---|---|---|---|
| 1 | lib/db/__tests__/report-mapping.test.ts |
167 | レポートの組み立て。年次・月次・週次の判別、重複検知 |
| 2 | lib/db/__tests__/clean-log-mapping.test.ts |
120 | 清掃ログ1件の組み立て。未実施=レポート行なし、★1回ごとの完了率・task_area = 0★、連続日数 |
| 3 | lib/format/__tests__/period.test.ts |
108 | ★月跨ぎ週の帰属★(週の長さ1〜7日、第6週) |
| 4 | lib/reports/__tests__/query.test.ts |
86 | レポート4本のパラメータ検査 |
| 5 | app/api/reports/__tests__/route.test.ts |
73 | レポートAPIの入口(422・500・形) |
| 6 | lib/format/__tests__/search-text.test.ts |
65 | 施設名検索の記号ゆれ |
| 7 | lib/judge/__tests__/rescue.test.ts |
60 | 要レスキュー店舗の判定 |
| 8 | lib/pagination/__tests__/page-items.test.ts |
56 | ページ送りの番号(1〜300ページ総当たり) |
| 9 | lib/settings/__tests__/display-limits.test.ts |
56 | 表示上限の保存と既定値への倒れ方 |
| 10 | lib/facilities/__tests__/query.test.ts |
55 | 施設マスタのパラメータ検査 |
| 11 | lib/logger/__tests__/redact.test.ts |
53 | ★ログに出してはいけない項目の除去★ |
| 12 | lib/prechecks/__tests__/query.test.ts |
48 | アラートのパラメータ検査・並べ替え・件数バッジ(★対応状況を変えたときの差分反映 applyResolutionChange() を含む★) |
| 13 | lib/schedules/__tests__/query.test.ts |
42 | スケジュールのパラメータ検査 |
| 14 | app/api/__tests__/master-filter.test.ts |
40 | 分類(ホールディングス/顧客)の絞り込みを全ルート横断で確認 |
| 15 | lib/clean-logs/__tests__/areas.test.ts |
39 | エリア別完了率の絞り込みと全体率(★15位は39件で4本が並ぶ★ — ほかに lib/clean-logs/__tests__/csv.test.ts / lib/clean-logs/__tests__/query.test.ts / lib/settings/__tests__/advice.test.ts) |
| — | その他 89本 | 1,401 | — |
lib/judge/__tests__/grade.test.ts(34件)は件数では20位ですが、最初に読むべきテストです(§11.4)。
変えると何が起きるか: テストが通っているからといって、画面が正しいとは限りません。テストが守っているのは lib/ と app/api/ だけで、components/ は0件です(§11.3)。全テストが通っていても、サンプルデータ(USE_FIXTURES=true)で画面を開いて確かめてください。 境界値のケースは docs/fixtures.md が一覧にしています。
「この数字はどこから来ているか」を知りたいだけなら、変更の前に章5-Bを見てください。
「やりたいこと」から引く索引です。★印は事故が起きやすい変更なので、該当章を読んでから手を付けてください。
行番号は 2026-10-05 時点(
ceeb736)のものです。ズレていたら定数名で検索してください。
環境変数・保存ファイルの詳細は章14を参照。
| やりたいこと | やり方 |
|---|---|
| 連続日数の表示上限を変える | システム管理画面から編集。data/settings/display-limits.json に保存されます。★画面から編集できる設定はこれ1つだけです★ |
| D-S制御盤の備考を書く | D-Secログ画面から入力。200文字まで。data/settings/ds-device-notes.json に保存 |
| ★アラート・エラーの対応状況・備考を記録する★ | アラート画面(/alerts)またはエラー一覧(/errors)で「編集」を押し、対応状況をプルダウンで選ぶ/備考を入力して、「保存(N件)」を押す。★押すまでは送られません★(D-165)。備考は400文字まで。★保存先はメインDB(robot_error_reports の4列)★です(章3-5・3-5b・章8-12)。アラートの出発失敗の行には記録できません |
| 表示をサンプルデータ⇄DBで切り替える | .env.local の USE_FIXTURES(★"false" という綴りに完全一致しないとDB側に切り替わりません★。章8) |
| ログの量を増やす/減らす | .env.local の LOG_LEVEL(章9) |
| 日本語⇄英語を切り替える | 画面右上の切り替え。★保存されないのでリロードで日本語に戻ります★ |
| ダーク⇄ライトを切り替える | 同上。★これも保存されません★ |
アドバイスの文言は、9/14 版では「システム管理画面から編集」と書いていましたが、画面の編集機能はまだありません(API だけ存在します)。いまは表示のみです。
| やりたいこと | 触る場所 |
|---|---|
| ◎の基準を90%→95%にする | lib/config/index.ts:20 の excellent。★「以上」で比べるので、95.0 が ◎ になります★(D-139) |
| L2 を2日→3日連続にする | lib/config/index.ts:46 の level2Days |
| 清掃詳細ログの期間上限を変える | lib/config/index.ts:442 の MAX_DATE_RANGE_DAYS。★現在は 731日(約2年)★。以前は31日でした |
| 一覧の1ページ件数を増やす | lib/config/index.ts:408-409(既定50件・上限200件) |
| PUDU MT1 に対応する | lib/config/index.ts:729 の supported を false → true |
| 「点検記録」をサイドバーに戻す | components/layout/AppShell.tsx:22 のコメントアウトを外す。★ただし画面の中身はまだ「準備中」です★(章3。D-150 で出すことは決定済み、実装は未着手) |
| B3(オフライン検知)を出るようにする | lib/config/robot-status-match.ts:46 に該当する状態文字列を足す。★現在は空なので B3 は出ません★ |
| 空欄の表示を「(値なし)」から変える | lib/format/raw-label.ts:19 |
| ★結果待ちの長さ(予定の終了から何分は失敗に数えないか)を変える★ | lib/config/index.ts:205 の RESULT_WAIT_MINUTES(★現在 120★。D-173)。★詳細ログ・CSV・ダッシュボード・アラート・連続日数・要レスキュー・当期の率が一斉に変わる★(消費者は isResultPending() だけ)。★LAUNCH_GRACE_MINUTES を代わりに変えても何も起きない★ |
| やりたいこと | 触る場所 | 片方だけ直すとどうなるか |
|---|---|---|
| ★色を変える | lib/theme.ts:4-43 と app/globals.css:1-40 の両方 |
画面の一部だけ色が変わる。エラーは出ない(章7) |
| ★画面の日本語を変える | ①その画面の .tsx の文字列を直す ②lib/i18n.ts の辞書のキー側も同じ文字列に直す |
②を忘れると英語表示のときだけ日本語のまま出る(章7) |
| ★レポートの既定表示件数を変える | lib/reports/query.ts:85 と components/features/reports/ReportsScreen.tsx:46(どちらも DEFAULT_LIMIT = 10) |
サーバーと画面で件数がズレる |
| 事前チェックのタイミングを変える | lib/config/index.ts:255(a961829)の PRECHECK_MINUTES_BEFORE |
— (★清掃詳細ログの列には影響しない★。列は DB の分数の順で決まる。D-178) |
| ★アラートの備考の文字数上限を変える | lib/config/index.ts:599 の PRECHECK_NOTE_MAX_LENGTH と d-watch-api.yaml:742 の maxLength: 400(★/errors 側は :866★。f2b91fc) |
画面・サーバーと API 契約が食い違う |
| 清掃結果の表示名を変える | lib/config/index.ts:371-378 の CLEAN_TASK_STATUS_LABELS と lib/i18n.ts:218-223 のキー |
★表示名は TeamWorks の回答(第10回 N-2)の原文ママ★なので、原則として変えない。変えるなら英語に切り替わらなくなるのを防ぐため両方直す |
| ★完了率の式(上限・桁)を変える | lib/config/index.ts:37-42 の RUN_COMPLETION_RATE(式そのものは lib/judge/kpi.ts:144-153)と 期待値表 lib/judge/__tests__/run-completion-rate.test.ts |
★TeamWorks のサマリの式に合わせた値(D-155)★なので、アプリ側だけ変えると前期比が「別の式どうしの差」になる。★先に期待値表、それから実装★(章5-11) |
| ★CSV の数式対策・空欄の出し方を変える | lib/csv.ts(csvCell() / csvRate() / csvDocument())の1か所 |
★清掃詳細ログとレポートの2つの CSV が同じ関門を通る★(D-163)。CSV ごとに書き直すと片方だけ直して抜ける。★csvCell() を通さないセルを作らない★(D-125) |
| ★レポートの施設別パフォーマンスの絞り込み・並びを変える | lib/reports/facility-performance-load.ts の loadFacilityPerformance()(★表と CSV の両方が呼ぶ1本★)、画面のクエリは ReportsScreen.tsx の facilityFilterQuery |
★表と CSV で施設が食い違う★(例外もテスト失敗も出ない。D-163) |
| ★「変化」・ステータスの表示名を変える | lib/reports/facility-performance-labels.ts(画面と CSV で共有)と lib/i18n.ts のキー |
片方だけだと英語表示か CSV のどちらかがずれる |
| ★分類プルダウンの幅(180px)を変える | 7画面の .module.css の .masterSelect(3.15 ①) |
その画面だけ並びがずれる |
| ★表の列を足す・幅を変える | その表の <colgroup> の <col> と .module.css の列幅・.table の min-width |
別の列に幅が付く・横スクロールの境目がずれる(3.15 ③) |
| ★「本清掃は成功」の定義を変える | lib/dashboard/aggregate.ts の SUCCESS_GRADES(★ダッシュボードの「成功」とアラートの main_run_succeeded が共有★) |
ダッシュボードとアラートで「成功」の意味が食い違う(D-171) |
| API の入出力を変える | 先に d-watch-api.yaml を直してから実装(CLAUDE.md §6-1) |
契約と実装が乖離する |
★lib/i18n.ts を直しても日本語は変わりません。あの辞書は「日本語→英語」の対訳表で、 日本語表示のときは辞書を見ずに書かれた文字列をそのまま出すためです(章7)。
.env.local を直しても変わりません。コードを直してデプロイし直す必要があります。
| やりたいこと | 触る場所 |
|---|---|
| ★アクセスできるIPアドレスを限定する | lib/config/allowed-ips.ts:24 の ALLOWED_IPS に足す と :35 の ALLOW_ALL_WHEN_EMPTY を false に。★現在は両方とも未設定なので、IP制限は事実上効いていません★ |
| ★プロキシの段数が変わった | lib/config/allowed-ips.ts:63 の TRUSTED_PROXY_HOPS |
| 作りたいもの | 見本にするファイル |
|---|---|
| 新しい一覧API | app/api/clean-logs/route.ts(コード内に「これが動く見本です」と明記されています) |
| 新しい画面 | app/(dashboard)/detail-log/page.tsx + components/features/detail-log/DetailLogScreen.tsx |
| 新しいクエリ層 | lib/clean-logs/query.ts(クエリ層はすべて同じ形。章6-B) |
| 新しい共通UI部品 | components/ui/GradeBadge.tsx(.tsx + 同名 .module.css の2点セット) |
| 新しい判定ロジック | lib/judge/grade.ts + 先に lib/judge/__tests__/grade.test.ts の期待値表 |
CLAUDE.md §7 が正ですが、非エンジニアが踏みやすいものを抜き出します。
| ❌ やってはいけない | なぜ |
|---|---|
★4カラム以外をメインDBに書く/robot-error-write.ts を通さずに書く★ |
書いてよいのは robot_error_reports / control_error_histories の status / comment / responded_at / resolved_at だけです(CLAUDE.md §3・§7)。5列目は権限エラーになります。★書き込みは lib/db/robot-error-write.ts の1ファイルに閉じ込めてあります★(章8-10)。現在の接続先はステージングで(D-152)、★本番DBへの接続・書き込みはまだありません★ |
★RobotErrorReport.updated_at に @updatedAt を付け直す★ |
db pull の後に @updatedAt を付け直す運用(D-059)の★唯一の例外★です。付けると Prisma が updated_at を勝手に SET し、権限エラー(または UTC の値の混入)になります。ガードテストが赤くなります(章8-8・章8-10) |
★scripts/seed-local-db.ts をステージングに向ける★ |
テーブルを消してから入れ直すので、★実データが消えます★。lib/db/local-guard.ts が dwatch_local 以外を拒否します |
prisma/schema.prisma を手で書き換える |
TeamWorks が管理するDBの設計図。prisma db pull で取り込むだけ。手で直すと次の取り込みで消える |
lib/generated/prisma/ の中身を直す |
自動生成物。次の生成で消える |
| 閾値を画面や判定ロジックに直接書く | lib/config/ に外出しする決まり(章6) |
| ★表示用に丸めた数字で判定する★ | 89.5 は画面に 90% と出ますが判定は ○ です。★丸めた値で判定すると、存在しない ◎ が生まれます★(章5)。9/14 版は「◎が消える」と逆向きに書いていましたが、境界が「以上」に変わったため(D-139)事故の向きも逆になりました |
| ★完了率を期間の合計どうしの面積で割る/画面ごとに完了率を計算する★ | 清掃完了率は ★1回ごとの完了率(runCompletionRate())の単純平均★です(D-155。★D-158 で確定★)。★Σ清掃面積 ÷ Σ対象面積(期間の合計どうし)は D-151 で廃止したまま★です。★1回ごとの値は runCompletionRate()、平均は meanCleanRatio() だけが知っています★(lib/judge/kpi.ts)。clean_task_reports.clean_ratio 列を完了率に使うのも、いまは誤りです(章5-4) |
★未実施を completion_rate === null で数える★ |
2026-10-05(P-5=B)からは task_area = 0 も 0% になり、null は未実施だけになりました。それでも★未実施は departed(レポート行の有無)で数えます★(D-156 項1 / 指示書AU D3。章5-4)。完了率の扱いがまた変わったときに、出発失敗の数え方まで一緒に変わらないようにするためです |
| ★事前チェックの結果・有無で成功/失敗を変える★ | ★事前チェックは成功・失敗・未実施の判定に使いません★(D-170 / D-048)。判定は本清掃のレポート行の有無(departed)と完了率(grade)だけです。1h前ST / 30分前ST・アラートの事前チェック異常・main_run_succeeded は★表示だけ★(main_run_succeeded で対応状況や件数を変えない。D-171)。歯止めのテストは lib/dashboard/__tests__/aggregate.test.ts(章5-9) |
| ★結果待ちの旗を見ずに「失敗」「未実施」を数える★ | 結果待ちの行は departed: false・grade: "×" のままです(D-173)。★新しい集計・絞り込みで result_pending(または isResultPending())を見ないと、レポートがまだ届いていないだけの清掃が失敗に混ざります★(例外もテスト失敗も出ない。章5-9b) |
facilities.contract を数値として比較する |
10桁の文字列です。数値にすると全施設が「契約なし」になりますが、エラーもテスト失敗も出ません(章7) |
★clean_schedules.contract を読む★ |
★contract という列は2つあります★。正は facilities.contract だけ。型も既定値も同じなので、取り違えても型エラーになりません(章7) |
clean_status_histories を読む |
清掃実績は clean_task_reports に一本化されました(D-151。章8) |
git add -A を使う |
別作業の途中ファイルを巻き込む事故が過去に起きています。ファイルパスを明示してください |
npm test
判定ロジック(lib/judge/)を触った場合は特に必須です。 テストが落ちたら「壊した」か「仕様を変えた」かのどちらかで、後者なら期待値表も直します(章11)。
npm run check:env
環境変数を触った場合、およびデプロイ前(章9)。
完了率・稼働率・面積などの数字の用語は、計算式と根拠まで含めて章5-Bに詳しく書いています。この章は名前の対応だけです。
画面に出る日本語から、コードの名前・DBのカラム名を引くための辞書です。 「コードの中の名前」は API のキー名(snake_case)と型名(lib/types/index.ts)を挙げています。
CLAUDE.md:380(§8 用語)と getting-started.md(§7 用語のメモ)にあります。ここでは重複させず、名前の対応だけを載せます。ceeb736)に全件引き直しました★(第3版のこの章は、それ以前の版の行番号が残っていました)。| 画面に出る日本語 | コードの中の名前 | DBのカラム名 | 補足 | |
|---|---|---|---|---|
| 施設 / 施設名 | Facility / id / facility_name(lib/types/index.ts:981-983)、清掃ログでは facility_id / facility_name(:204-205)、i18n キー「施設名」(lib/i18n.ts:24) |
facilities.id / facilities.facility_name(prisma/schema.prisma:125,131) |
旧称「店舗」。EN は "Facility" | |
| 清掃エリア | section_name(lib/types/index.ts:298、:927) |
sections.section_name(prisma/schema.prisma:173) |
旧称「区画」。P1は1清掃エリア=1ロボット | |
| ロボット | RobotSummary / serial_number / model / maker(lib/types/index.ts:974-979) |
robots.serial_number(prisma/schema.prisma:233)、機種名は devices.model_name(:85) |
画面の「シリアル」「機種」がこれ | |
| 清掃予定(スケジュール) | Schedule / weekdays / start_time / end_time / active(lib/types/index.ts:1021-1032) |
clean_schedules.weeks / .start_time / .end_time(prisma/schema.prisma:564,566,569)、1回ごとの予定は clean_timetables.start_at / .end_at(:597-598) |
weeks は7桁文字列。end_time は24〜29時を取りうる |
|
| 完了率(清掃完了率) | completion_rate(清掃ログ lib/types/index.ts:256)。★1回ごとの値は runCompletionRate()(lib/judge/kpi.ts:144)、平均は meanCleanRatio()(:101)★ |
★clean_task_reports.clean_area / .task_area(prisma/schema.prisma:534,533)から計算★。確定期間の集計は facility_summaries.clean_ratio(:743) |
★1回ごとの完了率(min(clean_area ÷ task_area × 100, 100) を小数第1位まで切り捨て)の単純平均★(D-155。★第11回 P-2=A で確定。D-158★。task_area = 0 は 0%)。★clean_task_reports.clean_ratio(:539)は使っていない★(先方が式の訂正中。訂正後も同じ値になる=第11回 P-1)。予定外のタスクは入れない。APIは丸める前の生値 |
|
| 清掃結果 | task_status(lib/types/index.ts:247)、表示名は formatCleanTaskStatus()(lib/format/clean-result.ts:61。a961829。★結果待ちは CLEAN_RESULT_PENDING_LABEL「結果待ち」 :73★) |
clean_task_reports.status(prisma/schema.prisma:529) |
★0 清掃開始前〜5 清掃異常終了の表示名★(CLEAN_TASK_STATUS_LABELS。D-155 項2)。表に無い値は数字のまま、レポート行が無ければ「出発失敗」。★判定には使わない★ |
|
| ★結果待ち★(D-173) | result_pending(lib/types/index.ts:288。a961829)、ダッシュボードの件数は DashboardResponse.summary.pending(:1269) |
該当なし(clean_timetables.start_at / end_at とレポート行の有無から isResultPending() で計算) |
★予定の終了から120分以内でレポートがまだ無い清掃★。departed / grade は出発失敗と同じ値のまま(旗で区別する)。5.9b |
|
| 稼働率 | operation_rate(lib/types/index.ts:648、:714)、関数 operationRate()(lib/judge/kpi.ts:36) |
facility_summaries.operation_ratio(prisma/schema.prisma:741)。予定回数は task_count(:728)。★正常終了の回数の列は無い★ |
★正常終了(status 4)の回数 ÷ 予定回数★(第11回 P-6=A)。EN は "Uptime"(lib/i18n.ts:91) |
|
| 出発成功率 | launch_rate(lib/types/index.ts:649、:715)、関数 launchRate()(lib/judge/kpi.ts:44) |
facility_summaries.launch_ratio(prisma/schema.prisma:742)。分子は launch_count(:729) |
★開始できた回数 ÷ 清掃予定回数(時刻の条件なし)★ | |
| エラー発生率 | error_rate(lib/types/index.ts:651、:717)、関数 errorRate()(lib/judge/kpi.ts:182) |
facility_summaries.error_ratio(prisma/schema.prisma:744)。分子は error_count(:730) |
★分母は清掃予定回数★(第11回 P-6=B。旧: 実施回数)。上限なし | |
| 判定(◎○△×) | grade / 型 Grade(lib/types/index.ts:258、:17)、関数 judgeGrade()(lib/judge/grade.ts:29) |
該当なし(DBに保存されない。サーバが毎回計算する) | 閾値は lib/config/index.ts:18(GRADE_THRESHOLDS) |
|
| エスカレーションレベル(L1/L2/L3) | escalation_level / 型 EscalationLevel(lib/types/index.ts:273、:18)、consecutive_problem_days(:263) |
該当なし(DBに保存されない。計算値) | 閾値は lib/config/index.ts:44(ESCALATION_THRESHOLDS) |
|
| 事前チェック | PrecheckItem / precheck_minutes / 型 PrecheckSeverity(lib/types/index.ts:472、:491、:458) |
clean_timetables.precheck(prisma/schema.prisma:604) |
現行DDLは 0=本清掃 / 1=1回目 / 2=2回目 のコード値。分数への変換は lib/db/precheck-mapping.ts |
|
| 事前チェック時のロボット状態 | status_1h / status_30m(lib/types/index.ts:215-216)、robot_status(:496) |
★未確認★ — robots.status(prisma/schema.prisma:235)は動作フェーズのみで、ハンドルアップ等を表現していない(D-077 項3) |
現行の日本語6値は fixtures 由来の暫定値。★f2b91fc では status_1h は lib/types/index.ts:243、型は `string \ |
null(null` = その分数の事前チェックの予定が無い。D-171)★ |
| D-Sデバイス(D-S制御盤) | DsDevice / serial_number / connection_status(lib/types/index.ts:899-903) |
controls.serial_number / controls.status(prisma/schema.prisma:204,211) |
status は 0=正常 / 1=異常 の2値(D-065) |
|
| 契約フラグ | monitoring_enabled(D-Watch)/ d_security_enabled(D-Security)(lib/types/index.ts:1012,1017)、parseContract()(lib/format/contract.ts:26) |
facilities.contract(prisma/schema.prisma:141) |
★varchar(10) の文字列★。1桁目=D-Security / 2桁目=D-Watch(lib/config/index.ts:740)。★数値比較禁止★ |
|
| アドバイス | AdviceItem / advices / rule(R1〜R4・B1〜B3)(lib/types/index.ts:278-286、:395)、matchAdviceRules()(lib/judge/advice.ts:50) |
該当なし(メインDBには保存しない。文言は data/settings/advice.json。lib/settings/advice.ts:45) |
発火条件は仕様のため編集不可(lib/types/index.ts:411) |
|
| 対応状態(未解消/対応中/解消) | resolution_status / 型 PrecheckStatus(lib/types/index.ts:498、:459)。★更新の注文は PrecheckStatusUpdateRequest(:521)★ |
robot_error_reports.status(prisma/schema.prisma:332)、D-S側は control_error_histories.status(:391) |
★この status, comment, responded_at, resolved_at の4カラムだけが書き込み可(CLAUDE.md:134)★。★2026-10-02 からアラート画面で書き込める★(robot_error_reports のみ。章8-10)。0/1/2 との対応は lib/db/precheck-mapping.ts:41 / :61(f2b91fc) |
|
| 備考(アラート) | note(lib/types/index.ts:503) |
robot_error_reports.comment(prisma/schema.prisma:333) |
★メインDBに保存する★(アラート画面・エラー一覧の編集モードから書ける。400文字まで)。API の注文でも note(旧名 memo から改名。指示書AT) |
|
| ★本清掃は成功(アラート)★ | main_run_succeeded(lib/types/index.ts:561。a961829) |
該当なし(親の本清掃の clean_task_reports から計算) |
★表示だけ★。true のカードは控えめに出て「本清掃は成功」の Pill が付く。対応状況・件数は変えない(D-171) | |
| エラー種別 | type / label(lib/types/index.ts:702-703) |
robot_error_reports.error_type(prisma/schema.prisma:330) |
★生値をそのまま表示する。7分類への変換はしない★(D-077) | |
| 備考(D-Secログ) | note(lib/types/index.ts:905) |
該当なし(data/settings/ds-device-notes.json。lib/settings/ds-device-notes.ts:54) |
メインDBには保存しない | |
| バッテリー残量 | battery_level(lib/types/index.ts:511) |
robots.battery(prisma/schema.prisma:242) |
★実DB経由では常に null★(現在値のみで履歴が無い。D-090 項8) |
★CSV は2種類あります★(改訂4 で1つ増えた。D-163)。★どちらも共通部品 lib/csv.ts を通ります★ — csvDocument()(:88。UTF-8 BOM付き・改行 CRLF・最後の行も CRLF)が★全セルを csvCell()(:50)に通し★、= + - @(とタブ・CR)で始まる値の先頭に ' を付けて無害化します(表計算ソフトで数式として実行されるのを防ぐ。D-125)。★率の列は csvRate()(:69)で、値が無ければ空のセル★(D-164。旧: - を出し、無害化で '- になって Excel に見えていた)。★見出しも中身も日本語固定★です(CSV は画面の言語を受け取らないため。指示書AS D11)。行数の上限はどちらも MAX_CSV_ROWS(20,000。超えたら 422)。 ★csvCell() は以前 lib/clean-logs/csv.ts にありました★(D-163 で lib/csv.ts へ移動。lib/clean-logs/csv.ts:19 から再エクスポートしているので、古い import 先も動きます)。
GET /api/clean-logs/export組み立ては lib/clean-logs/csv.ts の buildCleanLogCsv()(:57-85)、ヘッダは CLEAN_LOG_CSV_HEADER(:22-40)です(a961829)。★1行の見え方(出発・清掃結果・完了率・判定)は画面と同じ cleanLogRowView() で決めます★(D-173)。ファイル名は clean-logs_{開始日}_{終了日}.csv(app/api/clean-logs/export/route.ts:110)。
| CSVの列名 | コードの中の名前(csv.ts の行) |
DBのカラム名 |
|---|---|---|
| 施設名 | facility_name(:65) |
facilities.facility_name(prisma/schema.prisma:131) |
| 機種 | model_name(:66) |
devices.model_name(prisma/schema.prisma:85) |
| シリアル | serial_number(:67) |
robots.serial_number(prisma/schema.prisma:233) |
| 日付 | scheduled_at を formatMd() で整形(:68) |
clean_timetables.start_at(prisma/schema.prisma:597) |
| 予定時刻 | scheduled_at を formatHm() で整形(:69) |
同上 |
| 1h前ST | status_1h(:72)。★null(事前チェックの予定が無い)は空のセル★(D-171。画面の「-」は入れない — 無害化で '- になるため) |
★未確認★(13.1 の「事前チェック時のロボット状態」参照) |
| 30分前ST | status_30m(:73。null は空のセル) |
★未確認★(同上) |
| 出発 | departed(真偽値 → 「出発」/「未出発」。★結果待ちは「結果待ち」(D-173)★。:74-78) |
該当なし(clean_task_reports の行の有無から判定) |
| ★清掃結果★ | task_status を formatCleanTaskStatus() で表示名に(★結果待ちは「結果待ち」★。:79) |
clean_task_reports.status(prisma/schema.prisma:529)。★例: 清掃正常終了。表に無い値は数字、行が無ければ 出発失敗★ |
| 完了率 | completion_rate を csvRate() で整形(:80。★未実施・結果待ちは空のセル★) |
★clean_task_reports.clean_area / .task_area から runCompletionRate() で計算★(prisma/schema.prisma:534,533)。clean_ratio 列ではない |
| 判定 | grade(:81。★結果待ちは空のセル★) |
該当なし(計算値) |
★注意★ CSV には escalation_level(エスカレーションレベル)と consecutive_problem_days(連続問題日数)は 含まれていません(csv.ts:22-40 にその列がない)。画面でしか見られません。
GET /api/reports/facility-performance/export(★改訂4。D-163★)組み立ては lib/reports/facility-performance-csv.ts の buildFacilityPerformanceCsv()(:56)、ヘッダは facilityPerformanceCsvHeader()(:29)、ファイル名は facilityPerformanceCsvFilename()(:87)です。★列は画面の表の9列と同じ順に、施設名の直後へシリアルを足したもの★。行は★表と同じ条件の全施設★(limit は無視)・★表と同じ稼働率ワースト順★です(5B-7)。
| CSVの列名 | 中身 | 出どころ |
|---|---|---|
| 施設名 | facility_name |
facilities.facility_name |
| シリアル | serial_number |
robots.serial_number |
| 稼働率 / 稼働率(前週・前月・前年) | operation_rate.value / .prev を csvRate() で |
確定期間は facility_summaries.operation_ratio、当期はその場で集計(5B-7) |
| 出発成功率 | launch_rate.value |
同 launch_ratio |
| 完了率 / 完了率(前◯) | completion_rate.value / .prev |
同 clean_ratio |
| エラー率 | error_rate.value |
同 error_ratio |
| 変化 | diffText(overallDiff(item))(▲2 / ▼1 / →。★画面と同じ関数★ lib/reports/facility-performance-labels.ts) |
稼働率と完了率の前期比の平均 |
| ステータス | FACILITY_STATUS_LABEL[status](良好 / 注意 / 要改善 / 判定なし。★画面と同じ表★) |
稼働率から(judgeFacilityStatus()。暫定の閾値) |
ファイル名は facility-performance_{weekly|monthly|yearly}_{期間}.csv(例 facility-performance_monthly_2026-09.csv / _weekly_2026-10-w1 / _yearly_2025)。★ASCII のみ・parse 済みの数値から組み立てる★(Content-Disposition に日本語や生のクエリ文字列を入れない)。
画面の日英切替は lib/i18n.ts の1つの辞書で行っています。辞書のキーが日本語の原文そのものです (lib/i18n.ts:3-6)。キーが無ければ原文をそのまま返すため(lib/i18n.ts:392-395)、 未登録でも画面は壊れませんが日本語のまま出ます。
| 日本語(=辞書のキー) | 英語 | 場所 |
|---|---|---|
| 施設名 / 機種 / シリアル / 日付 | Facility / Model / Serial / Date | lib/i18n.ts:24-27 |
| 出発 / 清掃結果 / 完了率 / 判定 | Dep. / Clean Result / Completion / Grade | lib/i18n.ts:29-32 |
| 1h前ST / 30分前ST | 1H Before ST / 30min Before ST | lib/i18n.ts:89-90(2026-10-01 追加) |
| 稼働率 / 出発成功率 / 清掃完了率 / エラー発生率 | Uptime / Dep. Rate / Completion / Error Rate | lib/i18n.ts:91-94 |
| 要改善 / 注意 / 良好 | Needs Fix / Caution / Good | lib/i18n.ts:98-100 |
| ★未解消 / 対応中 / 解消★ | Unresolved / In progress / Resolved | lib/i18n.ts:157-159(★2026-10-02 追加。指示書AT★) |
| 「{n}分前異常」/ 出発失敗 | Error {n} min before / Launch failed | lib/i18n.ts:214-215 |
| ★清掃開始前 / 清掃中 / 清掃中断 / 清掃キャンセル / 清掃正常終了 / 清掃異常終了★ | Not started / Cleaning / Interrupted / Cancelled / Completed / Ended abnormally | lib/i18n.ts:218-223(★2026-10-02 追加。英語は仮★) |
| 事前チェック異常 | Pre-check error | lib/i18n.ts:225 |
| 監視 / D-Sec | Monitor / D-Sec | lib/i18n.ts:172,175 |
★注意★ 「完了率」と「清掃完了率」はどちらも EN では "Completion" です(lib/i18n.ts:31,93)。 別々のキーとして登録されているので、片方だけ変えると画面によって表記が食い違います。
ここまでの章はアプリの中身の話でした。この章はその外側、つまり「どう組み立てるか」「どこに書き出すか」を決めているファイルの話です。
混ざりやすいので先に分けます。
| 種類 | 置き場所 | 例 | 変更に必要なこと |
|---|---|---|---|
| 業務のルール | lib/config/ |
◎の基準90%、L2は2日連続 | コード変更+デプロイ(章6) |
| アプリの動き方 | ルート直下の設定ファイル | どこをテスト対象にするか、何を禁止するか | コード変更+デプロイ(この章) |
| 環境ごとの値 | .env.local |
DBの接続先、認証トークン | ファイル書き換え+再起動(この章) |
| 実行中に増えるもの | data/ |
画面から保存した備考・設定 | 画面から操作するだけ(この章) |
「◎の基準を変えたい」は1番目、「本番のDBに繋ぎたい」は3番目、「備考を書きたい」は4番目です。
いずれもアプリの動作そのものではなく、作る作業のほうを決めるファイルです。
| ファイル | 何を決めているか | 押さえどころ |
|---|---|---|
package.json(36行) |
使う外部部品の一覧と、npm run … で打てるコマンドの定義 |
コマンドは8本(章9-6)。外部部品は本番用6個・開発用12個とかなり少ない。next / react / @prisma/client / pino が主役 |
tsconfig.json(34行) |
TypeScript の厳しさと、パスの書き方 | ★"@/*": ["./*"] が @/lib/db のような書き方を可能にしています★。"strict": true で型チェックが最も厳しい設定です |
next.config.ts(8行) |
Next.js の設定 | ★中身は1行だけ★。pino(ログ部品)を Next.js に取り込ませず Node.js 側で解決させる指定です。ログが別スレッドで動くため |
vitest.config.ts(21行) |
テストの対象範囲 | ★include が lib/** と app/** だけ★。`components/` は入っていません**(章11) |
prisma7.config.ts(20行) |
DBの接続先をどこから読むか | ★Prisma 7 は接続先を schema.prisma に書けません★。また Prisma は既定で .env しか読まないため、.env.local を明示的に読み込んでいます |
next-env.d.ts |
Next.js が自動生成する型定義 | ★編集禁止★とファイル自身に書いてあります。git 管理外 |
eslint.config.mjsこのファイルだけは、単なる設定ではなく安全装置として働いています。どちらも「レビューで気をつける」ではなく、違反したらその場でエラーになって先へ進めません。
① console.log の禁止
"no-console": "error"
動作ログは必ず lib/logger を通す決まりです(章9-4)。console.log を直接書くと、リクエストIDが付かず、個人情報の伏せ字処理も通りません。例外は lib/logger/** 自身と scripts/**(単発のコマンドで、画面に出すことが目的)だけです。
② 生SQLの禁止
$queryRaw $executeRaw Prisma.raw を書くとエラーになります。理由が2つ書かれています。
どうしても必要になったときは、このルールを消すのではなく、その1箇所だけ個別に許可して理由を docs/decisions.md に書くという手順まで指定されています。
.env の3兄弟| ファイル | 何か | git |
|---|---|---|
.env.example |
開発用のテンプレート。「ローカルで動くこと」優先 | 入っている |
.env.production.example |
本番用のテンプレート。「本番で安全なこと」優先 | 入っている |
.env.local |
★実際の値が入ったファイル★ | ★入っていない★ |
実値を書いてよいのは .env.local だけです。テンプレート2本には「★このファイルに実値を書かないこと★」と明記されています。
★開発用と本番用で、わざと値の向きが逆になっている項目が2つあります。★ これは間違いではなく意図的です。
| 変数 | .env.example(開発) |
.env.production.example(本番) |
逆になっている理由 |
|---|---|---|---|
USE_FIXTURES |
true(サンプルデータ) |
false(実DB) |
開発はDBなしで動くことを優先 |
ALLOW_DIRECT_ACCESS |
true |
★設定しない★ | 開発では無いと全ページ403になる。本番で設定すると誰でも入れてしまう |
USE_FIXTURES には注意書きが付いています。未設定だと true(サンプルデータ)になり、実DBに繋がらないまま「それらしい数字」が画面に出ます。例外もエラーも出ません。
だからデプロイ前に必ずこれを実行します(章9)。
npm run check:env
接続先は DATABASE_URL という1行で書くのが基本ですが、★5つに分けた「部品変数」でも書けるようになりました★。
| 書き方 | 変数 | 優先 |
|---|---|---|
| 1行で書く | DATABASE_URL="mysql://ユーザー:パスワード@ホスト:3306/DB名" |
— |
| 部品に分ける | DATABASE_HOST / DATABASE_PORT / DATABASE_USER / DATABASE_PASSWORD / DATABASE_NAME |
★HOST と PASSWORD の両方があればこちらが優先★ |
★部品変数が追加された理由★: パスワードに @ が含まれていると、1行の書き方では %40 と書き換える必要があり、 しかもその %40 が読み込みの途中で壊れて消えることが実際に起きました(p%40ss → p)。 部品変数なら書き換えが要りません。本番用テンプレートではこちらを推奨しています。
★DATABASE_HOST を使う場合、DATABASE_USER と DATABASE_NAME は必須です★。既定値は用意していません (実在のDBユーザー名をコードに書かないため)。足りないとどれが足りないかを言って起動時に止まります。 ★黙って別のDBにつながることはありません★。組み立ては lib/db/prisma.ts が担当しています。
アラート画面の対応状況・備考(robot_error_reports の4列)を書くときだけ使う接続です(章8-10)。.env.example にだけ書かれています(.env.production.example には未記載)。
| 変数 | 設定のしかた | どうなるか |
|---|---|---|
DATABASE_WRITE_USER / DATABASE_WRITE_PASSWORD |
★2つとも書かない★ | ★参照用の接続(上の DATABASE_*)で書く★(いまはこれで動く見込み) |
| 同上 | ★片方だけ書く★ | ★書き込みが例外で失敗する★。npm run check:env も error を出す |
| 同上 | 2つとも書く | 書き込み専用の接続を作る。★接続先は参照用と同じDB★なので DATABASE_HOST / DATABASE_NAME(と必要なら DATABASE_PORT)も書く。★1行の URL 形式は用意していません★(%40 が壊れる問題を持ち込まないため) |
★実値はコミットしないこと★(テンプレートにも空欄でしか書かれていません)。
.gitignorenode_modules や .next のような「作り直せるもの」に加えて、この2行が運用上重要です。
| 行 | 対象 | 意味 |
|---|---|---|
.env* / !.env.example / !.env.production.example |
実値の環境変数 | 認証情報をコミットしない。テンプレート2本だけ例外で通す |
/data |
★画面から保存したデータ★ | git にバックアップされません(14.7参照) |
__pycache__/ / *.pyc |
PDF生成スクリプトが作るキャッシュ | docs/handoff/pdf/ の Python スクリプト用(2026-09 追加) |
★!.env.production.example の行には「この行が無いと上の .env* に飲まれて Git 管理外になる」というコメントが付いています。★ 消すとテンプレートが配布されなくなります。
.github/workflows/ci.ymlPRを作ったときと main に入ったときに、GitHub が自動で4つを順に走らせます。
リポジトリ取得 → 依存インストール → Prisma クライアント生成 → Lint → テスト → ビルド
★「Prisma クライアント生成」は 2026-09-17 に追加されました★。生成先の lib/generated/prisma/ は git に入れていないため、 チェックアウト直後には存在しません。生成しないとビルドが「部品が見つからない」で落ちます(実際に PR #6 で発生)。 この手順はスキーマを読むだけでDBには接続しません。
押さえどころが2つあります。
USE_FIXTURES: "true" を渡しています★。つまり CI はデータベースに一切接続しません。「サンプルデータでビルドが通ること」だけを確認していますこのプロジェクトは Cursor と Claude Code の両方で開発しているため、AI に読ませる指示書自体がリポジトリに入っています。
| ファイル | 役割 |
|---|---|
CLAUDE.md(23KB) |
★規約の正★。確定仕様・禁止事項・実装規約。Cursor も Claude Code も自動で読む |
.cursorrules(6行) |
Cursor の旧形式。中身は「規約は CLAUDE.md にある。ここに書き戻すな」だけ。二重管理を防ぐための番人 |
.claude/commands/d-watch.md |
作業を引き継ぐ手順書。/d-watch と打つと読み込まれる |
.claude/launch.json |
開発サーバの起動設定。★2種類あります★: d-watch(ポート3000。.env.local の設定どおり=現在はDBに接続)と d-watch-fixtures(ポート3100。★USE_FIXTURES=true を強制してサンプルデータで起動★)。同時に立ち上げて、DB版とサンプル版を並べて見比べられます |
.claude/settings.local.json ほか |
個人環境の状態。git 管理外 |
eslint.config.mjs が .claude/** を検査対象から外しているのは、この下に作業用のコピー(worktree)ができることがあり、そこまで検査すると落ちるためです。
data/ に何が入るか★メインDBに書けないものは、すべてここに JSON で保存されます。★
DBは TeamWorks の管理で、DFA が書き込めるのは4カラムだけです(章8-1。★その4カラムにはアラートの対応状況・備考を書いています★。章8-10)。運用設定や D-S制御盤の手入力メモはそこに入れられないので、アプリが自分でファイルに書いています。
| ファイル | 中身 | いま存在するか |
|---|---|---|
data/settings/ds-device-notes.json |
D-S制御盤の備考(手入力メモ) | ✅ 存在。中身は {} =1件も保存されていない |
data/settings/display-limits.json |
連続日数の表示上限(システム管理画面で編集) | ❌ まだ無い |
data/settings/advice.json |
アドバイスの文言(編集機能は未実装) | ❌ まだ無い |
★「ファイルが無い=壊れている」ではありません。★ 3つとも保存操作を一度もしていないだけで、その場合はコードに書かれた既定値がそのまま使われます。初めて保存したときに作られます。
lib/settings/json-store.ts3つとも同じ部品を使っており、次の性質を持ちます。
| 性質 | 内容 |
|---|---|
| 書き換えは一瞬で入れ替え | 一時ファイルに書いてから名前を付け替えます。途中で電源が落ちても、中途半端なファイルが残りません |
| 鍵をかけない | ★意図的★。施設20件・実質1人の運用なので、同時書き込みは起きない前提です |
| 壊れたファイルは退避する | 読めない JSON を見つけたら、タイムスタンプ付きの .bak に逃がしてから空の状態で再開します。黙って消しません |
| 退避に失敗したら止まる | ★空の値を返さず例外を投げます★。「バックアップも取れないのに元ファイルを捨てる」を防ぐため |
__proto__ などを無害化 |
JSON に細工したキーが入っていてもプログラムを乗っ取れないようにしています |
| ログ部品を使わない | lib/logger を読み込むと lib/db まで芋づるで付いてくるため、通知は呼び出し側から受け取る作りです |
/data は .gitignore に入っているので、git にバックアップがありません。
サーバーのディスクが消えれば、画面から入力した備考も設定も一緒に消えます。 本番運用ではこのフォルダを別途バックアップの対象に入れる必要があります(現時点では未対応)。
人が書いたファイルを1行1ファイルで並べます。(新) は 2026-09-14 版の地図(最終更新 af11453)以降、第3版(d23e203)までに追加されたファイルです(git diff --diff-filter=A af11453..d23e203 で抽出。削除・改名は0件)。★(新・10/05) は第3版のあと 2026-10-05(ceeb736)までに追加されたファイルです★(git diff --diff-filter=A d23e203..ceeb736。削除・改名は0件)。
圧縮ルール(該当するファイルは表から省いています)
components/**/*.module.css は同名の .tsx 専用の見た目なので、.tsx の行に含めます__tests__/ の中身は 章11 で扱いますlib/generated/prisma/ は自動生成物なので1行にまとめますapp/(dashboard)/*/page.tsx(11本)は中身が同じ形なので1行にまとめます| パス | 一言でいうと何か |
|---|---|
CLAUDE.md |
開発の前提とルール。★実装前に全文を読む★ |
README.md |
リポジトリの入口 |
d-watch-api.yaml |
★API契約の正★。APIを変えるなら先にここ |
d-watch-mock.jsx |
★画面レイアウト・文言・遷移の正★。1文字も書き換えない原型 |
proxy.ts |
全リクエストの横断処理(相関ID → IP制限 → 認証)。Next.js 16 で middleware.ts から改名 |
instrumentation.ts |
起動時の初期化ログと、捕捉漏れエラーの記録 |
next.config.ts |
Next.js の設定。pino(ログ部品)をバンドルしない指定だけ |
prisma7.config.ts |
Prisma 7 の設定(Prisma が生成したもの) |
vitest.config.ts |
テストの設定。★lib/** と app/** だけを走らせる★ |
eslint.config.mjs |
コードの書き方チェックの設定 |
tsconfig.json |
TypeScript の設定 |
package.json |
依存部品と npm コマンドの定義 |
package-lock.json |
依存部品のバージョン固定表(★手で直さない★) |
.env.example |
環境変数の雛形(開発用)。USE_FIXTURES=true |
.env.production.example |
環境変数の雛形(本番用) |
.env.local |
★接続情報など本物の値★。git に入らない。★このドキュメントでは中身に触れません★ |
.gitignore |
git に入れないものの一覧 |
.cursorrules |
Cursor 向けの短い指示 |
.claude/commands/d-watch.md |
Claude Code の作業再開コマンド |
.claude/launch.json |
Claude Code のプレビュー起動設定 |
.github/workflows/ci.yml |
GitHub 上の自動チェック(Lint → テスト → ビルド) |
| パス | 一言でいうと何か |
|---|---|
app/layout.tsx |
全ページの一番外側の器(<html lang="ja">、フォント) |
app/globals.css |
全画面の色・フォントの定義 |
app/page.tsx |
/ に来た人を /dashboard へ送るだけ |
app/login/page.tsx |
ログイン画面(共有トークン入力) |
app/(dashboard)/layout.tsx |
11画面に AppShell(サイドバー)を着せる |
app/(dashboard)/*/page.tsx(11本。★改訂3 時点 12本★) |
各画面の札。components/features/ の画面を1つ呼ぶだけ。★clean-area/page.tsx が(新)★、★errors/page.tsx が(新・10/06)★、inspections/page.tsx は PlaceholderScreen |
| パス | 一言でいうと何か |
|---|---|
app/api/auth/login/route.ts |
共有トークンを検証して cookie を発行 |
app/api/auth/logout/route.ts |
cookie を削除(★画面からの呼び出しは0件★) |
app/api/clean-area/route.ts |
(新) 清掃面積の集計 |
app/api/clean-logs/route.ts |
清掃詳細ログ一覧(★他の一覧APIの見本★) |
app/api/clean-logs/export/route.ts |
清掃詳細ログのCSV出力 |
app/api/clean-logs/[id]/advice/route.ts |
行を開いたときの推定原因+確認アクション |
app/api/clean-logs/[id]/areas/route.ts |
エリア別完了率(施設×日付) |
app/api/customers/route.ts |
(新) 顧客一覧(分類マスタ) |
app/api/dashboard/route.ts |
ダッシュボードの集計 |
app/api/ds-alerts/route.ts |
D-Secアラート一覧+サマリ |
app/api/ds-devices/route.ts |
D-S制御盤の稼働状況一覧+サマリ |
app/api/ds-devices/[serial_number]/note/route.ts |
D-S制御盤の備考を保存 |
app/api/errors/route.ts |
★(新・10/06)★ エラー一覧(robot_error_reports の全行。2段階で読む) |
app/api/errors/[error_id]/status/route.ts |
★(新・10/06)★ エラーの対応状況・備考を更新(PATCH。行があれば書ける) |
app/api/facilities/route.ts |
施設ロボットマスタ(読み取り専用) |
app/api/groups/route.ts |
(新) ホールディングス一覧(分類マスタ) |
app/api/prechecks/route.ts |
アラート(事前チェック)一覧+サマリ |
app/api/prechecks/[id]/status/route.ts |
★(新・10/05)★ アラートの対応状況・備考を更新(PATCH)。★メインDBに書き込むルート★(章8-10。★10/06 から下の errors/[error_id]/status と2本★) |
app/api/reports/continuous-problems/route.ts |
連続問題施設(2日以上連続で△以下 or 出発失敗) |
app/api/reports/error-breakdown/route.ts |
エラー種別の内訳(生値のまま) |
app/api/reports/facility-performance/route.ts |
施設別パフォーマンス(稼働率ワースト順)。★取得〜並べ替えは CSV と同じ loadFacilityPerformance()★ |
app/api/reports/facility-performance/export/route.ts |
★(新・10/06)★ 施設別パフォーマンスの CSV 出力(limit は無視して全施設。D-163) |
app/api/reports/kpi/route.ts |
KPI4指標+前期比 |
app/api/schedules/route.ts |
スケジュール一覧(読み取り専用) |
app/api/settings/advice/route.ts |
アドバイス文言の一覧 / 保存 |
app/api/settings/display-limits/route.ts |
表示上限の一覧 / 更新 |
| パス | 一言でいうと何か |
|---|---|
components/layout/AppShell.tsx |
サイドバー・ロゴ・テーマ/言語切替。メニューは固定配列(★契約フラグでの出し分けは未実装★) |
components/layout/PlaceholderScreen.tsx |
「準備中」の札(点検記録が使用中) |
components/theme/ThemeProvider.tsx |
ダーク/ライトと JP/EN の状態を全画面へ配る |
components/ui/Pagination.tsx |
ページ送り。★判断は lib/pagination/ に任せ、描くだけ★ |
components/ui/StatCard.tsx |
数字カード。色は判定(◎○△×)ではなく KPI の意味合い(★D-173 で中立の mt を足した。ダッシュボードの「結果待ち」★) |
components/ui/ProgressBar.tsx |
0〜100 の横棒 |
components/ui/FilterButton.tsx |
絞り込みボタン |
components/ui/GradeBadge.tsx |
◎○△× のバッジ |
components/ui/Pill.tsx |
色付きの小さなラベル |
components/ui/date-picker.ts |
★(新・10/06)★ openDatePicker() — 期間の入力欄の枠のどこを押してもカレンダーを開く(D-162) |
components/features/dashboard/DashboardScreen.tsx |
ダッシュボード画面 |
components/features/dashboard/RescueRequiredCard.tsx |
(新) 要レスキュー店舗カード |
components/features/dashboard/ScheduleProgressCard.tsx |
スケジュール進捗カード |
components/features/dashboard/SecurityBanner.tsx |
セキュリティ復帰漏れの警告バナー |
components/features/detail-log/DetailLogScreen.tsx |
清掃詳細ログ画面(章2) |
components/features/detail-log/CleanLogTable.tsx |
清掃ログの表。ダッシュボードも5列表示で借りる |
components/features/detail-log/AreaModal.tsx |
エリア別完了率のモーダル。ダッシュボードも借りる |
components/features/alerts/AlertsScreen.tsx |
アラート画面(★対応状況・備考は編集モードで変える。D-165★。本清掃が成功したカードは控えめ。D-171) |
components/features/errors/ErrorsScreen.tsx |
★(新・10/06)★ エラー一覧画面(★モックに無い画面★。★編集モードで対応状況と備考を変え、「保存(N件)」で4列を書く。D-165★) |
components/features/reports/ReportsScreen.tsx |
レポート画面(★804行★。最大。★CSVエクスポートが動く。D-163★) |
components/features/reports/KpiCard.tsx |
KPI カード(モックの4枚に対応) |
components/features/reports/FacilityPerformanceTable.tsx |
施設別パフォーマンス表 |
components/features/clean-area/CleanAreaScreen.tsx |
(新) 清掃面積画面(★モックに無い例外画面★) |
components/features/ds-logs/DsLogScreen.tsx |
D-Secログ画面(備考の保存あり) |
components/features/ds-alerts/DsAlertsScreen.tsx |
D-Secアラート画面 |
components/features/facilities/FacilitiesScreen.tsx |
施設ロボットマスタ画面(読み取り専用) |
components/features/schedules/SchedulesScreen.tsx |
スケジュール画面(完全な参照専用) |
components/features/system/SystemScreen.tsx |
システム管理画面(編集できるのは表示上限だけ) |
「純粋関数」とは、同じ入力には必ず同じ答えを返し、外の世界(DB・ファイル・時計)に触らない関数です。テストで固定しやすいので、判断はできるだけここへ寄せる設計になっています。
| パス | 一言でいうと何か |
|---|---|
lib/judge/index.ts |
判定ロジックのまとめ役 |
lib/judge/grade.ts |
★◎○△× の判定★ |
lib/judge/escalation.ts |
L1〜L3 の判定 |
lib/judge/advice.ts |
対応アドバイスの発火判定(R1〜R4 + B1〜B3) |
lib/judge/kpi.ts |
KPI4指標の計算。★1回ごとの完了率 runCompletionRate()★ と、その単純平均 meanCleanRatio()(章5-4) |
lib/judge/rescue.ts |
(新) 要レスキュー店舗の判定 |
lib/format/date.ts |
日付・時刻の表示整形 |
lib/format/period.ts |
週次/月次/年次の期間。★月跨ぎ週の帰属は weekOfMonth() だけが知る★ |
lib/format/search-text.ts |
検索照合用の文字列正規化(記号ゆれに強くする) |
lib/format/contract.ts |
facilities.contract(10桁の文字列)↔ 契約フラグの変換 |
lib/format/number.ts |
(新) 数値の表示整形。面積の単位は AREA_UNIT から |
lib/format/clean-result.ts |
(新) clean_task_reports.status の表示整形(★10/02 から表示名★) |
lib/format/group-id.ts |
(新) customers.group_id(DBの0)↔ API の null の変換 |
lib/format/consecutive-days.ts |
連続日数の表示上限 |
lib/format/schedule.ts |
スケジュールの表示整形(曜日ラベル等をサーバーで生成) |
lib/format/robot-model.ts |
対応機種レジストリに載っているかの判定 |
lib/format/raw-label.ts |
生値ラベルの空値表示 |
lib/format/address.ts |
住所2列を1つに連結 |
lib/format/precheck-status.ts |
★(新・10/07)★ 1h前ST / 30分前ST のセル(null = 事前チェックの予定が無い → 「-」。D-171) |
lib/clean-logs/query.ts |
清掃詳細ログのパラメータ検査と絞り込み規則 |
lib/clean-logs/areas.ts |
エリア別完了率の絞り込みと全体率(全体率の分子・分母は overallCompletionTally()) |
lib/clean-logs/csv.ts |
清掃詳細ログの CSV の組み立て(★セルの無害化・率の空欄は lib/csv.ts★) |
lib/csv.ts |
★(新・10/06)★ ★CSV の共通部品★ csvCell()(数式インジェクション対策。D-125)・csvRate()(値が無い率は空のセル。D-164)・csvDocument()(BOM・CRLF)。清掃詳細ログとレポートの2つの CSV が使う(D-163) |
lib/clean-logs/detail-log-date-range.ts |
★(新・10/06)★ 清掃詳細ログの期間の検証と URL の組み立て(空の側は付けない。D-159 追記) |
lib/clean-logs/result-pending.ts |
★(新・10/07)★ ★結果待ちの判定 isResultPending() の1本★(予定の開始 ≤ 現在 < 予定の終了+RESULT_WAIT_MINUTES かつレポートが無い。D-173) |
lib/clean-area/aggregate.ts |
(新) 清掃面積の集計 |
lib/clean-area/query.ts |
(新) 清掃面積のパラメータ検査(★日付上限を持たない★) |
lib/clean-area/visibility.ts |
(新) 清掃面積の内訳を何件見せるか |
lib/dashboard/aggregate.ts |
ダッシュボードの集計 |
lib/dashboard/rescue-visibility.ts |
(新) 要レスキューを何件見せるか |
lib/dashboard/latest-run.ts |
★(新・10/06)★ ダッシュボードに出す「各清掃エリアの直近の清掃」の選び方(純粋関数。D-159)。★候補は「予定の終了 ≤ 現在」(D-173 で終了+30分の待ちを廃止)★ |
lib/reports/query.ts |
レポート4本のパラメータ検査 |
lib/reports/target-customer.ts |
(新) 確定期間KPIの対象顧客をどう決めるか |
lib/reports/block-visibility.ts |
(新) レポート画面の4ブロックを1本ずつ出す判定 |
lib/reports/facility-performance-csv.ts |
★(新・10/06)★ 施設別パフォーマンスの CSV の列・行・ファイル名(D-163) |
lib/reports/facility-performance-labels.ts |
★(新・10/06)★ 「変化」(overallDiff() / diffText())とステータスの表示名(FACILITY_STATUS_LABEL)。★画面の表と CSV で共有★(D-163) |
lib/prechecks/query.ts |
アラートのパラメータ検査(件数バッジの差分反映 applyResolutionChange() も) |
lib/prechecks/status-update.ts |
★(新・10/05)★ アラートの対応状況・備考の更新規則(本文の検品と、DBに書く列だけのパッチを作る planPrecheckStatusUpdate()) |
lib/prechecks/label.ts |
アラートの種別ラベル |
lib/prechecks/edit-drafts.ts |
★(新・10/06)★ ★編集モードの下書きと一括保存★(アラート・エラー一覧が共有。D-165)。★旧 status-cycle.ts(巡回順)は D-161 で削除★ |
lib/error-reports/query.ts |
★(新・10/06)★ エラー一覧のパラメータ検査と ★絞り込み・並べ替えの正★(filterSortErrorReportKeys()) |
lib/error-reports/label.ts |
★(新・10/06)★ エラー一覧の「紐づく予定」の文言 |
lib/ds-alerts/query.ts |
D-Secアラートのパラメータ検査 |
lib/ds-devices/query.ts |
D-Secログのパラメータ検査 |
lib/facilities/query.ts |
施設マスタのパラメータ検査 |
lib/facilities/resolve-by-name.ts |
施設名 → 施設ID の解決 |
lib/facilities/resolve-by-master.ts |
(新) 分類(ホールディングス/顧客)→ 施設ID の解決 |
lib/facilities/intersect-ids.ts |
(新) 施設IDの絞り込み結果どうしの積集合(★null と [] を区別★) |
lib/facilities/filter-by-ids.ts |
(新) 解決済みの施設IDで行を絞る |
lib/schedules/query.ts |
スケジュールのパラメータ検査 |
lib/masters/query.ts |
(新) 分類マスタのパラメータ検査 |
lib/pagination/page-items.ts |
ページ送りの番号を組み立てる(★components/ にテストが無いのでここに判断を寄せる★) |
lib/api/query-result.ts |
パラメータ検査の結果の共通型(失敗を null で返さない) |
lib/api/path-params.ts |
[id] などパスの穴の検査 |
lib/api/master-id.ts |
(新) group_id / customer_id の検査 |
lib/auth/client-ip.ts |
接続元IPの取り出し(X-Forwarded-For の末尾)と許可判定 |
lib/auth/next-path.ts |
ログイン後の戻り先を安全な値に丸める |
lib/settings/advice-validation.ts |
アドバイス文言の入力検証 |
lib/config/env-check.ts |
環境変数の検証 |
| パス | 一言でいうと何か |
|---|---|
lib/db/index.ts |
★データの取り出し口★。DataSource(20メソッド)と USE_FIXTURES の切り替え |
lib/db/use-fixtures.ts |
USE_FIXTURES の判定だけ(Edge 対策で独立) |
lib/db/prisma.ts |
Prisma クライアントのシングルトン(★静的 import しない★)。★書き込み用の接続 getWritePrisma() もここ★ |
lib/db/consecutive-source.ts |
連続日数の入力を作る唯一の読み取り(4経路が共有) |
lib/db/clean-area-source.ts |
(新) 清掃面積の集計材料の読み取り(面積は groupBy、完了率・実施回数は行の取り寄せ) |
lib/reports/facility-performance-load.ts |
★(新・10/06)★ 施設別パフォーマンスの取得 → 絞り込み → 並べ替え(db を呼ぶのでこちらの層)。★表と CSV の両方のルートが呼ぶ1本★(D-163) |
lib/db/robot-error-write.ts |
★(新・10/05)★ ★robot_error_reports の4列を UPDATE する唯一の関数★(updateMany。章8-10) |
lib/db/facility-name-source.ts |
施設名解決用の施設マスタ読み取り(deleted_at で絞らない) |
lib/db/facility-group-source.ts |
(新) 分類解決用の施設マスタ読み取り |
lib/db/timetable-source.ts |
★(新・10/05)★ 予定を clean_timetables と clean_timetable_histories の両方から読む唯一の読み口(同じ id は本表。指示書AU 段階3b) |
lib/db/robot-error-select.ts |
★(新・10/05)★ robot_error_reports を読むときの select(robot_id / robot を読まない。指示書AU 段階0-4) |
lib/db/clean-log-mapping.ts |
清掃ログ1件の組み立て(純粋関数) |
lib/db/report-mapping.ts |
レポートの組み立て(純粋関数)。★periodOfSummaryRow() がここ★ |
lib/db/precheck-mapping.ts |
アラートの組み立て(純粋関数) |
lib/db/error-report-mapping.ts |
★(新・10/06)★ エラー一覧の組み立て(純粋関数。SQL の where も) |
lib/db/rescue-mapping.ts |
(新) 要レスキューの組み立て(純粋関数) |
lib/db/ds-status.ts |
D-Sec のコード値変換(純粋関数) |
lib/db/date-range.ts |
JST の日付を「DBの壁時計値」に変換(純粋関数) |
lib/db/chunked.ts |
in に渡す配列の分割(純粋関数) |
lib/db/fixture-clean-logs.ts |
サンプル版 cleanLogs() の絞り込み |
lib/db/fixture-prechecks.ts |
サンプル版 prechecks() の絞り込み |
lib/db/fixture-area-completions.ts |
サンプル版 areaCompletions() の絞り込み |
lib/db/fixture-clean-area.ts |
(新) サンプル版 cleanArea() の集計材料 |
lib/db/fixture-reports.ts |
サンプル版レポート4メソッドの絞り込み |
lib/db/fixture-precheck-overrides.ts |
★(新・10/05)★ サンプル版の対応状況の更新(メモリ上の上書き表。再起動で消える) |
lib/db/fixture-error-report-overrides.ts |
★(新・10/06)★ サンプル版のエラー一覧の更新(メモリ上の上書き表。AT とは別) |
lib/db/local-guard.ts |
シードが dwatch_local 以外に書き込むのを防ぐ |
lib/fixtures/index.ts |
サンプルデータのまとめ役(★実在の店舗名・住所・氏名を入れない★) |
lib/fixtures/_helpers.ts |
日付を相対で作るヘルパー |
lib/fixtures/clean-logs.ts |
清掃詳細ログ(境界値10ケース等) |
lib/fixtures/clean-task-reports.ts |
(新) clean_task_reports のサンプル |
lib/fixtures/task-status-rule.ts |
(新) status の決め方の唯一の定義 |
lib/fixtures/areas.ts |
エリア別完了率 |
lib/fixtures/prechecks.ts |
アラート(事前チェック) |
lib/fixtures/error-reports.ts |
★(新・10/06)★ エラー一覧(11行。DB の行の形) |
lib/fixtures/reports.ts |
レポート |
lib/fixtures/facilities.ts |
施設マスタ(ロボット0台/1台/3台) |
lib/fixtures/groups.ts |
(新) ホールディングス |
lib/fixtures/customers.ts |
(新) 顧客 |
lib/fixtures/schedules.ts |
スケジュール |
lib/fixtures/ds-devices.ts |
D-S制御盤 |
lib/fixtures/ds-alerts.ts |
D-Secアラート |
lib/fixtures/inspections.ts |
点検記録(画面は未着手) |
lib/settings/json-store.ts |
data/settings/ の JSON を安全に読み書きする共通部品 |
lib/settings/advice.ts |
アドバイス文言の保存(advice.json) |
lib/settings/display-limits.ts |
表示上限の保存(display-limits.json) |
lib/settings/ds-device-notes.ts |
D-S制御盤の備考の保存(ds-device-notes.json) |
lib/logger/index.ts |
アプリ共通ロガー(出力は stdout のみ) |
lib/logger/api.ts |
APIルートの catch から呼ぶ logApiError() |
lib/logger/context.ts |
相関IDの格納と withRequestId() |
lib/logger/events.ts |
出してよいログイベントの一覧(allowlist) |
lib/logger/redact.ts |
出してはいけない項目名の検出と除去 |
lib/logger/timestamp.ts |
ログの時刻形式 |
lib/logger/db-write.ts |
★(新・10/05)★ メインDB書き込みの監査ログ db.write(成功・失敗とも。備考の中身・エラーメッセージは載せない) |
lib/clean-area/anomaly.ts |
(新) 清掃面積の異常値を見張ってログに出す |
lib/api/client.ts |
★画面からサーバーへの唯一の通り道★ apiFetch() |
lib/auth/token.ts |
共有トークンの検証(定数時間比較) |
lib/auth/cookie.ts |
dwatch_session cookie の属性 |
lib/proxy/authenticate.ts |
IP制限と認証の判定(PUBLIC_PATHS はここ) |
lib/proxy/request-id.ts |
相関IDの発行 |
| パス | 一言でいうと何か |
|---|---|
lib/config/index.ts |
★設定値(閾値の外出し)★。90/70/10・猶予30分・上限値など |
lib/config/advice-rules.ts |
対応アドバイスのルール定義(既定値) |
lib/config/allowed-ips.ts |
IP制限の許可リスト |
lib/config/robot-status-match.ts |
B2/B3 アドバイスを発火させるロボット状態の一覧(暫定値) |
lib/types/index.ts |
★型定義★(1,398行。YAML のスキーマ名と揃える) |
lib/hooks/use-classification-filter.ts |
(新) 分類の絞り込みを5画面で共有するフック |
lib/masters/options.ts |
(新) 分類の選択肢の組み立て(画面側ロジックの土台) |
lib/i18n.ts |
日本語/英語の対訳表 |
lib/theme.ts |
ダーク/ライトの色トークン |
lib/theme-status.ts |
ロボット状態の色分け(★日本語6文字列の完全一致なので、生値ではグレーに落ちる★) |
| パス | 一言でいうと何か |
|---|---|
scripts/seed-local-db.ts |
ローカルDB(dwatch_local)へサンプルを投入する(2,145行)。★ステージングに入っていたダミーもこれ由来だった★(9/30 夜に先方の実データで置き換わった)。★テーブルを消してから入れ直すので、ステージングには絶対に向けない★ |
scripts/check-env.ts |
環境変数の検証(npm run check:env) |
data/settings/*.json |
画面で保存した設定の置き場。★git に入らない★(章8 §8.9) |
| パス | 一言でいうと何か |
|---|---|
prisma/schema.prisma |
DBの28テーブルの形(952行)。★prisma db pull の生成物。手で直さない★(★例外: RobotErrorReport.updated_at に @updatedAt を付けない。章8-8★) |
lib/generated/prisma/(47ファイル) |
上から prisma generate で作る部品。★git に入らない・開かない★ |
| パス | 一言でいうと何か |
|---|---|
docs/spec.md |
★機能一覧と開発フェーズの正★ |
docs/decisions.md |
★決定事項とその理由の正★(D-001〜D-156) |
docs/fixtures.md |
★サンプルデータに用意するケースの正★ |
docs/code-map.md |
このソースコード地図 |
docs/architecture.md |
ファイル・フォルダ構成と役割 |
docs/logging-design.md |
ログ設計 |
docs/deploy.md |
本番デプロイ手順 |
docs/getting-started.md |
開発手順書 |
docs/handoff.md |
引き継ぎメモ |
docs/setup-checklist-daigo.md |
開発者に渡す前の準備チェックリスト |
docs/変更点まとめ_20260914.md |
(新) 9/14 時点の変更点のまとめ |
docs/UI差分まとめ_20260915.md |
(新) モックと実装の画面差分のまとめ |
docs/データの流れ_画面1-5_20260917.md / _画面6-10_20260917.md |
(新) 画面ごとのデータの流れ(2本) |
docs/handoff/指示書D〜指示書AT(42本。AD は欠番) |
実装担当(Cursor)への作業指示書。★AI〜AQ の9本が(新)、AR・AS・AT の3本が(新・10/05)★。最新は 指示書AT_アラートの対応状況を画面から更新する_20261002.md |
docs/handoff/TeamWorks確認事項_第2回〜第11回(第10回は原稿と送付版の2本、第11回は送付版)+ TeamWorks回答_第2回 / _第10回 + TeamWorks実DDL照合結果_第1回 |
TeamWorks への照会と回答。★第9回が(新)、第10回・第11回と TeamWorks回答_第10回_20261002.md が(新・10/05)★。★完了率の式(N-3)・清掃結果の値(N-2)・時刻(N-14)の回答は第10回★ |
docs/handoff/PRレビュー手順_20261001.md |
★(新・10/05)★ PR のレビュー手順(レビュー担当は daichi。D-154) |
docs/handoff/TeamWorks待ちで止まっていること_20260930.md |
(新) TeamWorks の回答待ちで止まっている作業の一覧 |
docs/handoff/セッション引き継ぎ_*(2本)/ セキュリティ申し送り_20260916.md / 第9回送付手順_20260916.md |
(新) 作業の引き継ぎと申し送り |
docs/handoff/UI差分第3章_調査_* / UI差分第4章_調査_* |
(新) 画面差分の調査(2本) |
docs/handoff/テストDBのデータ出所_20260917.md |
(新) ★「テストDB」の行が当方のダミーだった件の記録★(CLAUDE.md §6-6 の由来) |
docs/handoff/点検記録_決まっていないこと_20260917.md |
(新) 点検記録の未決事項(D-150 の前提) |
docs/handoff/規模前提の見直し_20260917.md / 管理画面_設計検討_20260910.md |
(新) 規模前提と管理画面の検討メモ |
docs/handoff/PR説明_develop_to_main_骨子.md |
develop → main の PR 説明の骨子 |
docs/handoff/analysis/(10本) |
実装解析・DDL差分・テーブル定義の分析資料。★error-type-mapping.md は参考資料で実装の下敷きではない(D-077)★ |
docs/handoff/pdf/(make_* / check_* の Python 20本 + README) |
TeamWorks 送付用 PDF の生成・検査スクリプト。★*qa9* の2本が(新)、*qa10* の2本が(新・10/05)★。PDF 本体は git に入らない |
「この画面のここを直したい」というときに開くファイルの対応表です。空欄(—)はそのファイルが存在しないことを意味します。★lib/db/index.ts の行番号は 2026-10-05(a1d7e43)時点★で、Prisma 版(実DB)のメソッドの位置です。
| サイドバー表示名 | URL | 見た目を直すファイル | 数字の計算を直すファイル | データの取り出しを直すファイル | 数字の出どころ(テーブル) | API |
|---|---|---|---|---|---|---|
| (入口・メニューに出ない) | / |
app/page.tsx |
— | — | — | — |
| (ログイン・メニューに出ない) | /login |
app/login/page.tsx |
— | lib/auth/token.ts lib/auth/cookie.ts |
— | POST /api/auth/login |
| 📊 ダッシュボード | /dashboard |
components/features/dashboard/DashboardScreen.tsxRescueRequiredCard.tsxScheduleProgressCard.tsxSecurityBanner.tsx |
lib/dashboard/aggregate.ts★ lib/dashboard/latest-run.ts★lib/dashboard/rescue-visibility.tslib/db/rescue-mapping.tslib/judge/ |
lib/db/index.ts の cleanLogs() :754 / ★cleanLogRunMeta()★ / schedules() :1759 / dsAlerts() :1627 / rescueRequired() :1827lib/db/consecutive-source.ts |
clean_timetables clean_task_reports robot_error_reports clean_schedules control_error_histories facilities |
GET /api/dashboardGET /api/clean-logs/{id}/adviceGET /api/clean-logs/{id}/areas(★GET /api/clean-logs は呼ばない。D-159★) |
| 📋 清掃詳細ログ | /detail-log |
components/features/detail-log/DetailLogScreen.tsxCleanLogTable.tsx(列幅は CleanLogTable.module.css)AreaModal.tsxcomponents/ui/date-picker.ts |
lib/clean-logs/query.tslib/clean-logs/areas.tslib/clean-logs/csv.ts★ lib/csv.ts★lib/db/clean-log-mapping.tslib/format/clean-result.ts★ lib/format/precheck-status.ts★lib/judge/ |
lib/db/index.ts の cleanLogs() :754 / areaCompletions() :1095lib/db/consecutive-source.tslib/db/fixture-clean-logs.ts |
clean_timetables clean_task_reports robot_error_reports facilities sections robots devices |
GET /api/clean-logsGET /api/clean-logs/exportGET /api/clean-logs/{id}/adviceGET /api/clean-logs/{id}/areas |
| 🔔 アラート | /alerts |
components/features/alerts/AlertsScreen.tsxcomponents/ui/date-picker.ts |
lib/prechecks/query.tslib/prechecks/status-update.ts★ lib/prechecks/edit-drafts.ts★lib/db/precheck-mapping.ts(★mainRunSucceededFromReport()★)lib/dashboard/aggregate.ts(SUCCESS_GRADES) |
lib/db/index.ts の prechecks() :1216 / precheckItemById() :1429 / updatePrecheckErrorColumns() :1469lib/db/robot-error-write.ts(★書き込み★)lib/db/fixture-prechecks.ts / lib/db/fixture-precheck-overrides.ts |
robot_error_reports(★4列を書く★)clean_timetables clean_task_reports facilities robots |
GET /api/prechecksPATCH /api/prechecks/{id}/status |
| 🚨 エラー一覧(★10/06★) | /errors |
components/features/errors/ErrorsScreen.tsxcomponents/ui/date-picker.ts |
lib/error-reports/query.tslib/error-reports/label.tslib/prechecks/status-update.ts★ lib/prechecks/edit-drafts.ts★lib/db/error-report-mapping.ts |
lib/db/index.ts の errorReports() :1677 / errorReportById() :1719 / updateErrorReportColumns() :1742(f02eb1d)lib/db/robot-error-select.ts(ROBOT_ERROR_LIST_*_SELECT)lib/db/robot-error-write.ts(★書き込み★)lib/db/fixture-error-report-overrides.ts |
robot_error_reports(★4列を書く★)clean_timetables clean_timetable_histories facilities |
GET /api/errorsPATCH /api/errors/{error_id}/status |
| 📈 レポート | /reports |
components/features/reports/ReportsScreen.tsxKpiCard.tsxFacilityPerformanceTable.tsx |
lib/reports/query.tslib/reports/target-customer.tslib/reports/block-visibility.ts★ lib/reports/facility-performance-csv.ts★★ lib/reports/facility-performance-labels.ts★★ lib/csv.ts★lib/db/report-mapping.tslib/format/period.tslib/judge/kpi.ts |
lib/db/index.ts の reportKpis() :1957 / facilityPerformance() :2149 / continuousProblems() :2417 / errorBreakdown() :2496★ lib/reports/facility-performance-load.ts(表と CSV で共有)★lib/db/fixture-reports.ts |
確定期間: customer_summaries facility_summaries facility_error_summaries当期: clean_timetables clean_task_reports robot_error_reports |
GET /api/reports/kpiGET /api/reports/facility-performance★ GET /api/reports/facility-performance/export(CSV)★GET /api/reports/continuous-problemsGET /api/reports/error-breakdown |
| 📐 清掃面積 | /clean-area |
components/features/clean-area/CleanAreaScreen.tsxcomponents/ui/date-picker.ts |
lib/judge/kpi.ts(runCompletionRate())lib/clean-area/aggregate.tslib/clean-area/query.tslib/clean-area/visibility.tslib/clean-area/anomaly.tslib/format/number.ts |
lib/db/index.ts の cleanArea() :1899lib/db/clean-area-source.tslib/db/fixture-clean-area.ts |
clean_task_reports(面積は全行・完了率は予定ありの行)clean_timetables facilities customers groups |
GET /api/clean-area |
| 🔧 点検記録(★メニュー非表示★) | /inspections |
app/(dashboard)/inspections/page.tsxcomponents/layout/PlaceholderScreen.tsx |
— | lib/db/index.ts の inspections() :1799 / inspectionDetail() :1802(★Prisma版は未実装★)lib/fixtures/inspections.ts |
★メインDBには置かない★(独自ストアは未着手) | ★なし★ |
| 🔒 D-Secログ | /ds-logs |
components/features/ds-logs/DsLogScreen.tsx |
lib/ds-devices/query.tslib/db/ds-status.ts |
lib/db/index.ts の dsDevices() :1522 / dsDeviceExists() :1604lib/settings/ds-device-notes.ts |
controls control_reports facilities備考: data/settings/ds-device-notes.json |
GET /api/ds-devicesPATCH /api/ds-devices/{serial_number}/note |
| ⚠ D-Secアラート | /ds-alerts |
components/features/ds-alerts/DsAlertsScreen.tsx |
lib/ds-alerts/query.tslib/db/ds-status.ts |
lib/db/index.ts の dsAlerts() :1627 |
control_error_histories controls facilities sections |
GET /api/ds-alerts |
| 🏢 施設ロボットマスタ | /facilities |
components/features/facilities/FacilitiesScreen.tsx |
lib/facilities/query.tslib/format/robot-model.tslib/format/contract.ts |
lib/db/index.ts の facilities() :1706 |
facilities robots devices customers groups |
GET /api/facilities |
| 📅 スケジュール | /schedules |
components/features/schedules/SchedulesScreen.tsx |
lib/schedules/query.ts |
lib/db/index.ts の schedules() :1759 |
clean_schedules facilities sections robots |
GET /api/schedules |
| ⚙ システム管理 | /system |
components/features/system/SystemScreen.tsx |
lib/settings/advice-validation.tslib/config/advice-rules.ts |
lib/settings/advice.tslib/settings/display-limits.tslib/settings/json-store.ts |
★DBは読まない★(lib/config/ と data/settings/) |
GET /api/settings/adviceGET/PATCH /api/settings/display-limits |
全画面に共通
| 直したいもの | ファイル |
|---|---|
| サイドバー・ロゴ・テーマ切替・JP/EN切替 | components/layout/AppShell.tsx / app/(dashboard)/layout.tsx |
| 画面を包む器(全体) | app/layout.tsx |
| 分類(ホールディングス/顧客)の絞り込み | lib/hooks/use-classification-filter.ts / lib/masters/options.ts(GET /api/groups :99 / GET /api/customers :100) |
| サーバーとの通信 | lib/api/client.ts |
| 認証・IP制限 | proxy.ts / lib/proxy/authenticate.ts / lib/config/allowed-ips.ts |
| 共通部品(Pill / StatCard / FilterButton / Pagination / ProgressBar / GradeBadge) | components/ui/ |
| 閾値・上限などの設定値 | lib/config/index.ts(章6) |
| 日本語/英語の文言 | lib/i18n.ts |
| ◎○△×・L1〜L3・KPIの計算 | lib/judge/ |
| 契約フラグの読み方 | lib/format/contract.ts(★メニューの出し分けには未使用★) |
| サンプルデータ | lib/fixtures/(docs/fixtures.md) |
「この数字を変えたい」から引く索引です。値の意味は章6、根拠の略号(合意 = TeamWorks 合意・回答 / 指示 = ユーザー指示 / DFA = DFA の判断 / 型 = DB の列の型)は章6の冒頭を見てください。★行番号は 2026-10-05 時点の develop(a1d7e43)★。
| 変えたい数字 | 現在の値 | ファイル:行 | 定数名 | 根拠 |
|---|---|---|---|---|
| ◎ の基準(★以上★) | 90 | lib/config/index.ts:20 |
GRADE_THRESHOLDS.excellent |
合意(§4.1)+ 境界は指示(D-139) |
| ○ の基準(★以上★) | 70 | lib/config/index.ts:22 |
GRADE_THRESHOLDS.good |
同上 |
| × の基準(未満) | 10 | lib/config/index.ts:24 |
GRADE_THRESHOLDS.poor |
同上 |
| 1回ごとの完了率の上限 | 100 | lib/config/index.ts:39 |
RUN_COMPLETION_RATE.cap |
合意(第10回 N-3)。★確定★(D-155 → D-158) |
| 1回ごとの完了率の残す小数の桁 | 1(小数第1位まで切り捨て) | lib/config/index.ts:41 |
RUN_COMPLETION_RATE.decimals |
同上 |
| L2 に上げる連続日数 | 2 | lib/config/index.ts:46 |
ESCALATION_THRESHOLDS.level2Days |
合意(§4.5) |
| L3 に上げる連続日数 | 3 | lib/config/index.ts:48 |
ESCALATION_THRESHOLDS.level3Days |
合意(§4.5) |
| 完了率「急落」の割合(要レスキュー R-3) | 0.2(20%) | lib/config/index.ts:67 |
COMPLETION_DROP.dropRatio |
指示 2026-09-15(D-135) |
| 未実施が何日連続で要レスキューか(R-1) | 3 | lib/config/index.ts:89 |
RESCUE_THRESHOLDS.notExecutedDays |
指示 2026-09-15(D-135) |
| 稼働率の 良好 / 注意 の境目 | 90 / 75 | lib/config/index.ts:551-556 |
FACILITY_STATUS_THRESHOLDS |
DFA(暫定。D-117) |
| エラー率セルを赤くする目安 | 8 | lib/config/index.ts:571 |
FACILITY_TABLE_THRESHOLDS.errorRateWarning |
モック :807 |
| 未実施と判定するまでの猶予 | 30 | lib/config/index.ts:196 |
LAUNCH_GRACE_MINUTES(⚠️ ★死に定数。変えても何も起きない★) |
D-025(暫定)→ D-091 項1 で不使用 |
| ★ダッシュボードの「直近の清掃」を何日前まで遡るか★ | 7 | lib/config/index.ts:213-219(a961829) |
DASHBOARD_LATEST_RUN |
D-159(7日はユーザー指示)。★旧 settleMinutes(30分)は D-173 で削除★ |
| ★結果待ちの長さ(予定の終了からの分)★ | 120 | lib/config/index.ts:205(a961829) |
RESULT_WAIT_MINUTES |
ユーザー判断 2026-10-07(D-173)。★暫定★(第12回(★R-2★。04f974a で送付版)で見直す) |
| 変えたい数字 | 現在の値 | ファイル:行 | 定数名 | 根拠 |
|---|---|---|---|---|
| 連続日数の表示上限(既定) | 30(3〜365) | lib/config/index.ts:151-158 |
CONSECUTIVE_DAYS_DISPLAY(★画面から編集可★) |
指示 2026-09-13(D-130) |
| 連続日数のために何日さかのぼるか | 上限 × 7 + 1 | lib/config/index.ts:185-190 |
CONSECUTIVE_LOOKBACK |
DFA(D-131) |
| 変えたい数字 | 現在の値 | ファイル:行 | 定数名 | 根拠 |
|---|---|---|---|---|
| 要レスキューの初期表示件数 | 5 | lib/config/index.ts:120 |
RESCUE_INITIAL_VISIBLE |
指示 2026-09-15(D-137) |
| 清掃面積の内訳の初期表示件数 | 10 | lib/config/index.ts:812 |
CLEAN_AREA_INITIAL_VISIBLE |
DFA(指示書AN) |
| 清掃詳細ログの期間上限 | 731日 | lib/config/index.ts:442 |
MAX_DATE_RANGE_DAYS |
指示 2026-09-15(D-136) |
| CSV出力の行数上限 | 20,000 | lib/config/index.ts:494(a961829) |
MAX_CSV_ROWS(★清掃詳細ログ・レポートの2つの CSV で共有。D-163★) |
指示 2026-09-15(D-136)。値は暫定(D-144) |
| 一覧の1ページ件数 / 上限 / ページボタン数 | 50 / 200 / 7 | lib/config/index.ts:407-423 |
PAGINATION |
API契約(d-watch-api.yaml:1498) |
| 受け付ける西暦の範囲 | 2000〜2100 | lib/config/index.ts:505-510 |
REPORT_YEAR |
DFA |
in 句の分割単位 |
1,000 | lib/config/index.ts:548 |
DB_IN_CLAUSE_MAX_ITEMS |
DFA(指示書AC) |
| D-Sec備考の文字数上限 | 200 | lib/config/index.ts:588 |
DEVICE_NOTE_MAX_LENGTH |
DFA(暫定) |
| アラートの備考の文字数上限 | 400 | lib/config/index.ts:599 |
PRECHECK_NOTE_MAX_LENGTH(★d-watch-api.yaml:742(/errors 側は :866)の maxLength と揃える★) |
DFA(指示書AT §3-6) |
| アドバイス見出し / 本文 / 行動 / 件数の上限 | 100 / 400 / 100 / 10 | lib/config/index.ts:627 / :629 / :631 / :633 |
ADVICE_TITLE_MAX_LENGTH ほか |
DFA(D-124) |
| URLのIDの上限 | 9007199254740991 | lib/config/index.ts:663 |
PATH_ID_MAX |
DFA(JavaScript の限界) |
| シリアル番号の文字数上限 | 100 | lib/config/index.ts:680 |
PATH_SERIAL_NUMBER_MAX_LENGTH |
型(controls.serial_number varchar(100)) |
アラートの id(error_id)の文字数上限 |
50 | lib/config/index.ts:692 |
PATH_ERROR_ID_MAX_LENGTH |
型(robot_error_reports.error_id varchar(50)) |
| 成功率カードの目標表示 | 95 | lib/config/index.ts:575 |
SUCCESS_RATE_TARGET |
モック :494 |
| 変えたい数字 | 現在の値 | ファイル:行 | 定数名 | 根拠 |
|---|---|---|---|---|
| DBの時刻をJSTの壁時計値として読む時差 | 540分 | lib/config/index.ts:211 |
DB_DATETIME_OFFSET_MINUTES |
合意(第8回 A2)。D-115 / D-155 項4 |
| 事前チェックのタイミング | 60分前・30分前 | lib/config/index.ts:232 |
PRECHECK_MINUTES_BEFORE |
指示 2026-09-11 / 合意(第7回。D-112) |
本清掃を表す precheck の値 |
0 | lib/config/index.ts:243-246 |
CLEAN_TIMETABLE_PRECHECK |
合意(第7回。D-112) |
| 旧・清掃結果コードのラベル | 0〜4 | lib/config/index.ts:317-323 / :333 |
CLEAN_ERROR_LABELS / cleanErrorLabel()(★現在どの画面も通していない★) |
定義書(D-146) |
清掃結果(clean_task_reports.status)の表示名 |
0〜5 | lib/config/index.ts:348-361 / :371-378 / :384 |
CLEAN_TASK_STATUS / CLEAN_TASK_STATUS_LABELS / cleanTaskStatusLabel() |
合意(第10回 N-2。D-155 項2)。★判定には使わない★ |
エラー記録が無いときの代替値(★予定行が無ければ null。D-171★) |
Standby. |
lib/config/index.ts:404 |
ROBOT_STATUS_STANDBY |
合意(第2回。D-098) |
| メーカー番号→名前 | 1 PUDU / 2 Keenon | lib/config/index.ts:704-707 |
MAKER_NAMES |
DFA(暫定。D-085) |
| 対応機種の追加 | CC1 のみ | lib/config/index.ts:727-732 |
ROBOT_MODELS[].supported |
指示 2026-09-09(D-087) |
| 契約フラグの桁位置 | D-Security 0 / D-Watch 1 | lib/config/index.ts:740-743 |
CONTRACT_DIGITS |
D-080 |
| 「グループなし」を表す値 | 0 | lib/config/index.ts:758 |
GROUP_ID_NONE |
合意(D-011) |
| ロボット状態の色 | 日本語6値 | lib/config/index.ts:783-790 |
ROBOT_STATUS_TONES(★実データではほぼ全部グレー★) |
fixtures 由来(D-077) |
| 面積の表示単位 | ㎡ | lib/config/index.ts:804 |
AREA_UNIT |
指示 2026-09-16(TeamWorks 未確認。D-141) |
| 変えたい数字 | 現在の値 | ファイル:行 | 定数名 | 根拠 |
|---|---|---|---|---|
| アドバイス文言の既定値 | R1〜R4・B1〜B3 | lib/config/advice-rules.ts:34-127 |
DEFAULT_ADVICE_RULES |
R は合意(§4.5)、B はモック |
| B2(ハンドルアップ)の発火値 | Handrail Lifted |
lib/config/robot-status-match.ts:35 |
B2_HANDLE_UP_STATUSES |
指示(暫定。D-078) |
| B3(オフライン)の発火値 | 空 | lib/config/robot-status-match.ts:46 |
B3_OFFLINE_STATUSES(★空=発火しない★) |
値が未判明(D-078) |
| 許可するIPアドレス | 空 | lib/config/allowed-ips.ts:24-26 |
ALLOWED_IPS |
D-079。★リリース直前の対象(D-154)★ |
| IPリストが空のときの扱い | true(制限しない) |
lib/config/allowed-ips.ts:35 |
ALLOW_ALL_WHEN_EMPTY |
D-101。★リリース直前の対象(D-154)★ |
| 直接アクセスを通すか | 未設定=拒否 | lib/config/allowed-ips.ts:52 |
ALLOW_DIRECT_ACCESS(★環境変数由来★) |
D-101 |
| 信頼するプロキシの段数 | 1 | lib/config/allowed-ips.ts:63 |
TRUSTED_PROXY_HOPS |
指示 2026-09-08(D-079) |
lib/config/ の外にある数値(★ここだけ例外★)| 変えたい数字 | ファイル:行 | 定数名・中身 |
|---|---|---|
| 連続日数の表示上限の保存値 | data/settings/display-limits.json |
ファイル(★git に入らない★)。読むのは loadConsecutiveDaysMax()(lib/settings/display-limits.ts:223) |
| アドバイス文言の保存値 | data/settings/advice.json |
ファイル(★git に入らない★)。読むのは loadAdviceRules()(lib/settings/advice.ts:110) |
| 浮動小数の誤差を吸収する桁 | lib/judge/kpi.ts:110 |
FLOAT_ERROR_DIGITS(5)。★仕様の閾値ではなく計算上の都合なので lib/config に置かない★(指示書AS D3)。0.57 / 1 * 100 が 56.99999… になるのを 57.0 に戻すため |
| レポートの「もっと表示」の件数 | components/features/reports/ReportsScreen.tsx:46 / :55 |
DEFAULT_LIMIT(10)/ SHOW_ALL_LIMIT(= PAGINATION.maxPerPage) |
| 空欄の表示文言 | lib/format/raw-label.ts:19 |
EMPTY_VALUE_LABEL("(値なし)") |