Newer
Older
client_dwatch / docs / fixtures.md

fixtures.md — サンプルデータ仕様

DB接続前の開発は、すべて lib/fixtures/ のサンプルデータで行います。 本書は「サンプルデータに必ず含めるべきケース」の一覧です。

正常系だけのきれいなデータで作ると、本番接続時に表示崩れ・判定ミスが一気に噴出します。 異常系・境界値を先に仕込んでおくことが目的です。


1. 方式

lib/fixtures/          サンプルデータ(TypeScript、型付き)
  clean-logs.ts
  clean-task-reports.ts  ★清掃実績の新しい取得元(指示書AQ 決定2)。§2.6★
  task-status-rule.ts    ★`status` の分布と巡回列(依存の無い葉モジュール)。§2.6★
  prechecks.ts
  reports.ts
  ds-devices.ts
  ds-alerts.ts
  facilities.ts
  schedules.ts
  inspections.ts
lib/db/
  index.ts             USE_FIXTURES で fixtures / prisma を切り替える
  • 環境変数 USE_FIXTURES=true の間は、APIルートが fixtures を返す
  • DB接続時は USE_FIXTURES=false にするだけ。画面側のコードは一切変更しない
  • 判定ロジック(lib/judge/)はこの切替の外側に置く。 fixture開発中も本番同等のロジックが走る
  • 元データはモック(d-watch-mock.jsx)の LOG / PRECHK / DSA_INIT / RANK / ROBOTS / SCHED / DSDEV / INSPECTIONS を移植し、本書のケースを追加する

ダミー化のルール ★必須★

モックには実在の店舗名・住所・電話番号が入っています。移植時に必ず置換してください。

実データ ダミー
ドン・キホーテ / MEGAドンキ バリューマート / メガバリューマート
ユニー / アピタ フレッシュタウン
PPIH ARH
住所 「東京都○○区1-2-3」形式の架空住所
電話番号 03-0000-0001 形式(連番)
メール store001@example.com
担当者名 架空の氏名

シリアル番号(8111R5628060011 等)は形式が分かることに意味があるので、桁数・書式を保ったまま連番で構いません。


2. 清掃詳細ログ(clean-logs)

モックの LOG を移植し、以下を追加します。

2.1 判定の境界値 ★最重要★

ケース 完了率 期待される判定 表示(整数丸め)
◎の下限すぐ上 90.1 ◎ 90%
◎/○の境界 90.0 ★◎(「90%以上」なので90.0は◎。D-139)★ 90%
丸めると90だが○ 89.5 ○ 90%
○の下限すぐ上 70.1 ○ 70%
○/△の境界 70.0 ★○(「70%以上」なので70.0は○。D-139)★ 70%
△の下限すぐ上 10.1 △ 10%
△/×の境界 10.0 △(「10%未満」が×なので10.0は△。★D-139 でも変わらない★) 10%
×の代表値 9.9 × 10%
ゼロ 0 × 0%
未実施 null × -

この10ケースは判定ロジックのテストと同じ値を使うこと。 画面表示とテストで挙動が一致していることを目視でも確認できます。

★境界はすべて「上のランクに入る」★(ユーザー指示 2026-09-16。D-139。旧 D-020b は破棄)。 ★× の境界だけは「10%未満」のまま★なので、10.0 は △。

2.2 エスカレーション

ケース 連続日数 期待レベル 適用ルール
初回の出発失敗 1 L1 R1
完了率×(初回) 1 L1 R2
2日連続△以下 2 L2 R3
3日連続 3 L3 R4
5日連続(長期化) 5 L3 R4
スケジュール非存在日を挟む連続 実日数3・カレンダー日5 L3 R4

最後のケースが重要です。「月水金」スケジュールの施設で月・水・金と3回連続で失敗した場合、カレンダー上は5日間ですがスケジュール存在日の連続は3日なのでL3になります。この判定を間違えやすいので必ずデータに含めてください。

2.3 ステータスの組み合わせ

事前チェック1回目 / 2回目(★旧見出し 1h前ST / 30分前ST★。D-178)は以下を網羅:

  • 待機中 → 待機中(正常)
  • 充電中 → 待機中(1h前異常だが解消)
  • ハンドルアップ → ハンドルアップ(未解消)
  • 緊急停止 → 待機中(復旧)
  • オフライン → オフライン
  • セキュリティ有効(D-Sec契約施設のみ)
  • ★事前チェックの予定が無い → null / null(画面は「-」、CSV は空のセル)★(D-171): ★施設11 バリューマート川下店の履歴25行(6〜30日前)★。lib/fixtures/prechecks.ts に出てこない施設を選んだ。完了率・判定・行数は変えていない
  • ★事前チェックの分数(★D-178★。列は分数ではなく順番で割り当てる。早い方が1回目)★:
    • ★既定は 60 / 30★(PRECHECK_MINUTES_BEFORE。row() の status_1h_minutes / status_30m_minutes を省略した行。status が null の回は分数も null)
    • ★60分前・10分前(設定に無い分数)★: 施設7 メガバリューマート緑野店・ロボット 8111R5628060100・昨日 01:30(待機中 / 待機中・60 / 10)
    • ★30分前の1回だけ★: 施設7・ロボット 8111R5628060177・昨日 02:00(1回目 待機中・30 / 2回目 null・null)
    • ★行は足していない★(既存の2行の分数だけを変えた。件数・id・完了率・判定は変わらない)。シードもこの分数で事前チェック行を作る

2.3b 結果待ち(★D-173★)

  • ★施設11 バリューマート川下店・今日 03:30 の1行★が result_pending: true(lib/fixtures/clean-logs.ts の pendingCases)。departed:false・task_status:null・completion_rate:null・× のまま、事前チェックは無い(null。D-171)
  • 画面: 詳細ログは出発「…」・清掃結果「結果待ち」・判定なし。ダッシュボードは「結果待ち 1」(成功率の分母に入れない)。アラートの出発失敗・連続日数・要レスキューには数えない
  • これに合わせて ★スケジュール id 34(施設11・毎日 03:30〜04:30)を足した★(曜日ごとの有効なスケジュール数 = 今日の行数、の不変条件。D-050)。★今日の行は 10 → 11★、今日の schedule_progress は 11 / 9
  • ★施設12 今日 03:30 は確定した出発失敗のまま★(§2.1 の境界値「未実施(null)」)

2.4 データ量・表示崩れ

  • 31日分のデータ(日付フィルタの上限テスト用)
  • 11件以上の△×行(「もっと表示」の発火確認)
  • 該当0件(△×が1件もない日 = 全部◎の日)
  • 施設名が長いケース(30文字程度)
  • 1施設に複数ロボット(2台が別タスク = 2行になることの確認)

2.5 エリア別完了率(GET /clean-logs/{id}/areas)

lib/fixtures/areas.ts(areaCompletions)。★★1エリア = 1清掃ロボット★★(CLAUDE.md §8 / TeamWorks 第8回 A9-2)。★1回の清掃の中を売り場・通路・レジ前に区切った内訳ではありません★。

★★日付と clean_log_id は lib/fixtures/clean-logs.ts の既存の行から引いています★★ (手書きの日付を作らない。既存ファイルは1行も変更していない)。 行の completion_rate は対応する清掃ログの完了率と同じ値です(一覧とモーダルで数字を食い違わせない)。 ★★全体率(overall_completion_rate)は行の完了率(runCompletionRate())の単純平均です★★(清掃1回=1票。未実施の行は分母に入れない。 lib/judge/kpi.ts の meanCleanRatio())。★指示書AQ §5-5 で面積ベース(Σclean_area ÷ Σtask_area)から変わり、D-155 / 指示書AS で平均する材料が clean_ratio 列から runCompletionRate() に変わりました★。 ★行の完了率は面積から runCompletionRate() で決まります(D-155)★。面積は行の完了率から作ってあり(小数第6位)、 runCompletionRate() で元の完了率に戻ることを lib/fixtures/__tests__/areas.test.ts で確かめています。 ★期間の合計どうし(Σclean_area ÷ Σtask_area)は全体率の分子・分母ではありません★。

# ケース 施設 / 日 仕込んだ値 確認できること
1 1エリアだけの施設 施設1 / 当日 90.1%(1000㎡) ★1件でも正常(エラーにしない)★ / ◎の境界
2 ちょうど90.0 施設2 / 当日 90.0%(1000㎡) ★「90%以上」が◎なので ◎(D-139)★
3 ちょうど70.0 施設5 / 当日 70.0%(1000㎡) ★「70%以上」が○なので ○(D-139)★
4 ちょうど10.0 施設8 / 当日 10.0%(1000㎡) ★「10%未満」が×なので △★
5 0% 施設10 / 当日 0%(1000㎡ / 0㎡) ★「0%で実施した」は × だが未実施ではない★
6 ★全エリア未実施★ 施設12 / 当日 null(面積0) ★completion_rate は null(0% と混ぜない)★ / ★平均する行が0件なので全体率も null★
7 ★3エリアの施設 + 担当面積が違う★ 施設7 / 1日前 62%(1000㎡) / 94%(100㎡) / 88%(100㎡) ★全体率は単純平均の 81.33%(○)★。旧定義の面積ベースだと 66.83…%(△)で判定が割れるので、★面積ベースに戻っていないこと★を確認できる
8 ★★履歴の行が無いエリアと正常終了のエリアの混在★★ 施設5 / 1日前 履歴なし(面積0) + 97%(1000㎡) ★履歴なしは completion_rate が null で単純平均の分母に入らない★ → 全体 97.0%(◎)
9 該当0件(全部◎○) 施設1 / 1日前 92%(1000㎡) ★filter=problem で items が空配列(404 にしない)★
  • ★未実施・履歴なしの行は task_area / clean_area とも 0★(ローカルDBのシードと同じ)。 ★面積は全体率に効かない★ので、実DBで未実施行の task_area が非0でも全体率は変わらない(面積の表示にはDBの値をそのまま出す)
  • ★section_name はシードと同じ 清掃エリア(ロボット107) の形★。 モックの「1F 生鮮エリア」のような名前は作りません(実装はDBの生値をそのまま出すため)
  • ケース表の期待値は lib/fixtures/__tests__/areas.test.ts で固定しています

2.6 clean_task_reports(清掃実績の新しい取得元。★指示書AQ 決定2★)

lib/fixtures/clean-task-reports.ts(cleanTaskReports)。 期待値の固定は lib/fixtures/__tests__/clean-task-reports.test.ts(19ケース)。 ローカルDBへの投入は scripts/seed-local-db.ts(★同じ純粋関数を import している★)。

★なぜ必要か★: 指示書AQ 決定2 で ★清掃実績の取得元が clean_status_histories → clean_task_reports に変わる★。★このテーブルはローカルDBにも fixtures にも1行も無かった★ため、 ★切替後は全画面が0件になり、他担当が動作確認できない★(指示書AQ 落とし穴5)。 ★clean_status_histories の投入は消していません★ — 切替の前後比較に使うためです。

★これは API の形(CleanLogItem)ではなく DBテーブルの形です★。 そのため型は lib/types/ ではなく lib/fixtures/clean-task-reports.ts に置いています (CleanTaskReportFixture)。

実データの形(出所: ★ユーザー提供CSV clean_task_reports.csv 189行★。2026-09-30 受領)

カラム 実測
report_id ★19桁の数値文字列★(例 1295486100215435264)。varchar(20)
clean_timetable_id ★CSV では189行すべて NULL★。今後 TeamWorks が埋める前提(決定5)
status ★3:2件 / 4:161件 / 5:4件 / 6:22件★。値の意味は第10回 N-2 で受領(★6 は未受領★。D-155 項2)
task_area ★清掃エリアごとにほぼ一定★(例: あるエリアは19行すべて 878.524992)
clean_area ★毎回変動し、68.3%(129/189行)で task_area を超える★
clean_ratio ★面積比とは一致しない★(例: 面積比130.0% のとき clean_ratio は 76.0)
ended_at ★nullable★
clean_time 秒(★0 の行もある★)

仕込んだケース(境界値・異常系)

# ケース 仕込み方 確認できること
1 ★未実施★ ★行を作らない★(completion_rate === null の清掃) ★指示書AQ 1-1「未実施 = 対応する行が無い」★。clean_task_reports に clean_error 相当の列は無いので、★行の有無だけが根拠★
2 判定の境界値10ケース ★clean_area を清掃ログの完了率から作る★(clean_ratio 列にも同じ値)。§2.1 の値(90.1 / 90.0 / 89.5 / 70.1 / 70.0 / 10.1 / 10.0 / 9.9 / 0、未実施は #1) ★runCompletionRate()(面積から。D-155)でも §2.1 の判定が変わらないこと★。全行で元の完了率に戻ることをテストで固定
3 ★status 4種★ 3 / 4 / 5 / 6 を ★CSV実測の分布どおり★(189要素の巡回列)。★塊にせず散らしてある★ ★画面・CSV で表示名(3 清掃キャンセル / 4 清掃正常終了 / 5 清掃異常終了)になり、6 は数字のまま出ること(D-155 項2)★。★CLEAN_ERROR に対応づけない★
4 ★ended_at が NULL★ 清掃 id が29の倍数の行 ★順序キーが ended_at ?? started_at に倒れること(指示書AQ 1-4 / 落とし穴3)★。★非nullを型で要求している箇所は型ごと直す★
5 ★clean_timetable_id が NULL★ ★history ブロック(daysBack 6〜30)で id が37の倍数の行★ ★落とし穴1★。★行が消えるのではなく「未実施」として静かに集計される(fail-silent)★。稼働率・出発成功率が落ちる
6 clean_time が 0 ★ended_at が NULL の行★ 実測に 0 の行がある。0除算・「0秒だから異常」といった扱いをしていないこと
7 ★完了率100の行は task_area を超える面積★ 完了率100なら task_area × 1.00〜1.40(pseudoRandom()) ★上限100の確認★。★ただし fixtures の清掃ログには完了率100が無い(最大 99.5)ので該当0件★(関数を直接テストで確かめる)
8 清掃エリアごとに違う task_area エリア(section_id)ごとに一定、600〜1000 m² で散らす(小数第6位まで) ★既存シードの一律 1000 m² では出ない不具合★(総清掃面積の按分など)

★#5 を boundaryCases / escalationCases / statusCases に当てていないのは意図★です。 当てると §2.1 の境界値10ケースと §2.2 のエスカレーションが ★まるごと「未実施」になって 画面で確認できなくなる★ため、history ブロックだけを対象にしています (この制約は lib/fixtures/__tests__/clean-task-reports.test.ts で固定済み)。

★★#5 の行は、DB 経由と fixtures 経由で見え方が違います(既知の構造上の制約。意図した非対称)★★

経路 #5 の清掃の見え方 なぜ
DB 経由(USE_FIXTURES=false) ★出発失敗・task_status が null★ clean_timetable_id が NULL の行は IN に一致しないので「対応する行が無い」=未実施(指示書AQ 1-1)。★落とし穴1 そのもの★
fixtures 経由(cleanLogs) ★実施済み・task_status あり★ cleanLogs の departed / completion_rate / task_status は clean-logs.ts の入力から決まり、clean-task-reports.ts の結果を見ない

★fixtures 側を DB 経由に合わせないのは意図★です。#5 の対象(cleanTaskReportNullTimetableLogIds)は ★completion_rate !== null の行から作っている★ため、cleanLogs 側でその行を未実施にすると ★対象が0件になり、落とし穴1 を再現するケースそのものが消えます★。 この数行について2経路の画面表示が違うのは不具合ではありません(lib/fixtures/clean-task-reports.ts の cleanTaskReportNullTimetableLogIds の docstring にも同じことを書いています)。

★status の規則(CSV実測の分布と189要素の巡回列)は lib/fixtures/task-status-rule.ts の1箇所だけ★で定義し、 clean-task-reports.ts(status)と clean-logs.ts(task_status)の両方がそこから import します。 ★clean-logs.ts は clean-task-reports.ts を import できません★(あちらが読み込み時に cleanLogs / history を 読むため循環 import になる)。★2つの一致は lib/fixtures/__tests__/areas.test.ts で固定しています★。

件数

どこ 行数
lib/fixtures/ ★263行★(cleanLogs 280行のうち実施済み263行。未実施17行には行を作らない)
ローカルDB(本ループ + 施設7検証) ★246行★(投入対象253行のうち実施済み240行 + 施設7検証6行)
ローカルDB(画面確認用ボリューム) 実行時に決まる(★シードの完了ログに出力される★)

★clean_status_histories は未実施でも行を作る(clean_error = 1)ので、★2つのテーブルで 行数が違います。それが意図した状態★です(切替の前後比較の材料)。

★2026-10-02(D-155 / 指示書AS 段階6)★: 完了率が clean_ratio 列から ★1回ごとの面積比(runCompletionRate())★ に 変わったため、clean_area を完了率と独立に作る方式(旧ケース #7「面積比と clean_ratio の乖離」。約7割が task_area 超え)をやめ、 ★清掃ログの完了率から作る★ 方式に変えました(CSV の分布 68.3% の再現はやめた)。

値の作り方

★すべて清掃 id から決まる純粋関数★(Math.random() を使わない=冪等)。 ★scripts/seed-local-db.ts は lib/fixtures/clean-task-reports.ts の関数をそのまま import します★ので、 ★USE_FIXTURES の true/false で同じ値が出ます★(ズレたらどちらかのバグ)。 シードは投入後に ★status 4種 / ended_at NULL / clean_timetable_id NULL / clean_time=0 が 1件も無ければ例外で止まります★(fail-loud。ケースが欠けたまま他担当が動作確認に入らないため)。


3. アラート(prechecks)

ケース 内容
2種別すべて precheck(事前チェック異常)/ launch_failed(出発失敗)★D-105 で3値→2値★
分数の3通り 60分前 / 30分前 / ★20分前(設定値以外の任意の分数。D-093 項2)★
3ステータスすべて 未解消 / 対応中 / 解消
解消済み(時刻あり) responded_at と resolved_at の両方が入っている
対応中(解消前) responded_at のみ、resolved_at は null
未解消 両方 null
バッテリー低下 battery_level が低い値(Pudu APIで取得可能と確認済み)
該当0件 未解消が0件の日(サマリカードが0表示になる)
★本清掃は成功(main_run_succeeded)★ ★true は id 6(出発成功・親の本清掃が ◎○)だけ★。id 7(完了率62% = △)・出発失敗の行・ほかは false。★対応状況・件数はこの値で変わらない★(D-171。lib/fixtures/prechecks.ts の冒頭コメント)

4. レポート(reports)

モックの RANK を移植し、以下を追加。

ケース 内容
3ステータス 良好 / 注意 / 要改善 が最低1件ずつ
前期比+ 改善している施設(▲表示)
前期比− 悪化している施設(▼表示)
前期データなし prev が null(新規施設。差分表示なし)
11件以上 「全X施設を表示」の発火確認
エラー種別(生値。D-077で変換しない方針に確定) 実在する値が複数(EmergencyStop 等) / 未知の値 / 空文字
連続問題施設 L1 / L2 / L3 が最低1件ずつ
当期データ 進行中の週・月・年(サマリテーブルに存在しない前提のケース)
週次/月次/年次 3モードのデータ(粒度タブ切替の確認)。期間キーは実行日からの相対で6つ(今週 / 先週 / 今月 / 先月 / 今年 / 前年)。★年次は month=0, week=0★(§4.7。D-151 で今年・前年を追加)

★確定期間のKPI(kpiWeekly / kpiMonthly / kpiYearly)は作り値です★(実データ由来ではない)。 当期(今週・今月・今年)は3つとも kpiCurrentPeriod を使い回しています。


5. D-Secログ(ds-devices)

ケース 内容
3状態すべて 正常 / 通信断 / 異常
自動ソート確認 正常→異常→正常→通信断 の順で定義し、表示時に異常→通信断→正常に並ぶことを確認
備考あり/なし note が空文字のケースを含む
全部正常 異常0件のケース(サマリカードの表示確認)
11件以上 ページング確認用(12件)
該当0件 q に一致しない検索語(例: q=ZZZ-NOMATCH)を指定すると0件になる。専用のfixtureは用意していない

※ トークン期限(te)は移植しない。 仕様から除外済みです。


6. D-Secアラート(ds-alerts)

ケース 内容
2種別 セキュリティ無効 / 通信断
3ステータス 未対応 / 対応中 / 対応済み
メモあり/なし memo が空のケースを含む
11件以上 ページング確認用(12件)
該当0件 type=disconnected&status=done を指定すると0件になる。disconnected×doneの組み合わせは意図的に作っていない

※ モックの DSA_INIT には「トークン期限」種別が含まれています。移植時に削除してください。


7. 施設ロボットマスタ(facilities)

ケース 内容
契約フラグ4パターン ★varchar(10)の10桁文字列(D-080)★。0000000000:未設定 / 1000000000:D-Securityのみ / 0100000000:D-Watchのみ / 1100000000:両方
ロボット0台 施設は登録済みだがロボット未設置
ロボット複数台 2台、3台のケース(モックの立川店は3台)
メーカー・機種 PUDU CC1 のみ(P1 の対象機種。CLAUDE.md §1)。未対応機種の表示確認は lib/facilities/__tests__/query.test.ts の合成データで担保する(指示書N / D-087)
任意項目が空 電話・メール・担当者が null のケース
複数都道府県 東京都 / 神奈川県 など

契約フラグ4パターンはメニュー表示制御のテストに必須です(契約のないセクションはメニューごと非表示)。


8. スケジュール(schedules)

ケース 内容
曜日パターン 毎日 / 月水金 / 平日のみ / 土日のみ
有効・無効 active の true / false 両方
日跨ぎ 23:30開始〜00:30終了

無効スケジュールは「連続日数カウント」の対象外になるので、判定ロジックの確認にも使います。

★D-050: 曜日パターンを持つ有効スケジュールは「補完ペア」にしてある★

schedule_progress.planned(lib/dashboard/aggregate.ts)は施設を区別しない、 その日の曜日に該当する active なスケジュールの総数です。一方 clean-logs.ts の 当日分(daysBack=0)は曜日に関係なく毎日必ず11行になります(相対生成のため。★D-173 で結果待ちの1行(施設11)を足して 10 → 11★)。 この2つを常に一致させるため、平日のみ/土日のみ、月水金/日火木土 をそれぞれ 「全曜日を過不足なくカバーする補完ペア」にして、合計が常に11件になるようにしています (★D-173 で「毎日」のスケジュール id 34(施設11・03:30〜04:30)を足して 10 → 11★。§2.3b)。 詳細な内訳は lib/fixtures/schedules.ts 冒頭のコメントを参照してください。


9. 点検記録(inspections)

ケース 内容
申し送りあり/なし notes が空のケースと長文のケース
写真全部あり Before/After × 7項目 = 14枚
写真一部欠け 一部項目のみ撮影(グリッドの欠けた表示確認)
写真なし 記録のみ登録されたケース
同一施設の複数回 劣化傾向の比較表示用に、同じ施設で日付違いの記録

写真はダミー画像(単色+項目名を書いたPNG等)で構いません。


10. 共通

すべての一覧APIで以下を確認できるようにします。

  • 空配列(該当0件のときの表示)
  • 11件以上(ページング / もっと表示の発火)
  • エラーレスポンス(500、404 のときの画面表示)— fixture側にエラーを返すフラグを用意すると確認しやすい

11. DB接続への移行手順

  1. npx prisma db pull で実スキーマを取り込み、テーブル定義書(28テーブル)と突き合わせる(この確認だけは早めにやる。カラム名の食い違いが後から見つかると全画面に波及するため)
  2. カラム名・型の食い違いがあれば実装を止めて確認
  3. USE_FIXTURES=false に切り替え、1画面ずつ実データで確認
  4. 数値検証: サマリテーブルの値と画面表示の一致、判定結果がfixture時と同じロジックで出ているかを確認
  5. fixtures は削除せず残す(テスト用・デモ用・障害切り分け用に使う)

12. ダッシュボード(dashboard)

各値の定義は docs/decisions.md D-049 が正。集計は lib/dashboard/aggregate.ts(純粋関数)で、 期待値表は lib/dashboard/__tests__/aggregate.test.ts に固定してあります。

12.1 日付ごとに何が再現できるか(★D-050で確定★)

clean-logs.ts は実行日からの相対生成(daysBack。0 が当日)で、schedules.ts は 固定データです。GET /api/dashboard?date=YYYY-MM-DD の date を「今日から何日前か」で 指定することで、以下のケースを確認できます(date 省略時は当日=daysBack=0)。 ★schedule_progress は省略時も当日。summary と problem_logs は直近モード(D-159)★ — 下の表の 「当日(date 省略)」の行は ★スケジュール進捗(planned / completed / data.anomaly)だけ★ に当てはまる。 省略時の数値カードと清掃詳細ログ(コンパクト版)は §12.3。

date(当日からの相対) planned 当日ログ行数 completed data.anomaly 確認できること
当日(daysBack=0、date 省略) 11 11 9 出ない 既定が正常であること。曜日に関係なく常にこの値になる(schedules.ts の補完ペア設計。§8参照)。出発なしの2行は施設12 の出発失敗と施設11 の結果待ち(§2.3b)
1日前(daysBack=1) 11 11 7 ★出ない★ ★D-173 以前は「実績が予定を上回る乖離」(expected=10, actual=11)の再現用だった★。★planned が11になって一致し、★上回る側の乖離は fixtures で再現できなくなった★。★直さない(監督判断 2026-10-07)★: data.anomaly の判定は logsOfDay.length !== planned(app/api/dashboard/route.ts)で上回る・下回るが同じ分岐なので、下回る側(2〜5日前・6〜30日前)で同じ経路を確かめられる
4日前(daysBack=4) 11 1 1 出る(expected=11, actual=1) 乖離(実績が予定を大きく下回るケース。D-049が本来想定していた「収集処理が止まった」ケースに近い)
2〜3日前・5日前 11 3 / 3 / 2 2 / 2 / 2 出る 同様の乖離(不足側)の追加サンプル
6〜30日前 11 10 変動 ★出る(expected=11, actual=10)★ 履歴データ(history)は1日10行の生成。★D-173 以前は planned と偶然一致して出なかった★。乖離確認には使わない

★上の数字は lib/fixtures を import して db.cleanLogs()・db.schedules()・computeScheduleProgress() で数えたもの★ (当方の fixtures 由来。2026-10-07 11:3x JST。completed は出発した行の数なので相対生成の日によって変わりうる)。

planned はどの date を指定しても常に11(schedules.ts の設計上、曜日に関係なく 有効スケジュールの該当件数が11で一定のため。★D-173 で 10 → 11★)。乖離が起きるのは常に清掃ログ側の行数が 日によって変わるからで、これは意図的な相対生成データの性質です。

12.2 planned = 0 の日は fixtures では再現しない(★意図的★)

上記の通り、既定(当日・曜日を問わず)を常に「正常」にするという要件(D-050)を満たすには、 schedules.ts の有効スケジュールの曜日該当件数を全曜日で一定(11件。D-173 以前は10件)にする必要があり、 その結果どの date を指定しても planned は0にならない設計になっています (0になる曜日が1つでもあると、その曜日が「当日」に回ってきたときに既定が異常表示になってしまうため)。

そのため planned = 0(全スケジュールがその日該当なし → rate が null)のケースは、 fixtures 経由では再現できません。 このケースは computeScheduleProgress 単体の 性質(0除算をしないこと)であり、lib/dashboard/__tests__/aggregate.test.ts の 「planned = 0(全施設が定休日)なら rate は null」テストで合成データ(fixtures非依存) により直接検証済みです。画面から確認したい場合はこのテストを参照してください。

/dashboard の描画経路を直接確認したい場合(★手順・要注意★)

上の aggregate.test.ts は computeScheduleProgress という集計関数を直接呼んでいるだけで、 「planned = 0 のレスポンスを /dashboard 画面がどう描画するか」(例: 「本日の清掃予定はありません」といった表示)は検証していません。画面の描画経路まで 目視で確認したい場合は、以下の手順で一時的に再現してください。

  1. lib/fixtures/schedules.ts の全スケジュールの active を一時的にすべて false に変更する (曜日を問わず該当件数が0になるため planned = 0 になる)
  2. npm run dev で /dashboard を開き、表示を確認する
  3. 確認が終わったら必ず active を元の値に戻す。 戻し忘れても、lib/fixtures/__tests__/schedules.test.ts(修正1で追加)が 「有効スケジュールの該当件数が当日の清掃ログ行数と一致しない」として落ちるため、 npm test を実行すれば戻し忘れにすぐ気付けます

schedules.ts の有効スケジュールの曜日該当件数を全曜日で一定(11件)に保つという制約 (12.2冒頭・D-050)がある限り、planned = 0 を fixtures に常設することはできません。 常設すると、どこかの曜日で該当件数が0になってしまい、その曜日が「当日」に回ってきたときに 既定表示(date 省略・daysBack=0)が異常系になってしまうためです。そのため常設ではなく、 確認したいときだけ上記の手順で一時的に active を落とす運用にしています。

ケース 何を確認するか
planned = 0 の日(全施設が定休日) rate が null になる。0除算で NaN / Infinity を出さないこと。★fixturesでは再現不可。12.2参照。aggregate.test.ts で検証済み★
security_banner が 0件 バナーを出さない(空配列)
security_banner が 複数件 複数行の並び。security_off かつ未解消(done 以外)だけが出る
当日の問題行が 0件 コンパクト版清掃詳細ログが空のときの表示。★date 省略時は problem_logs(直近モード。§12.3)、date 指定時はその日の △×(D-159)★。旧: /clean-logs?result=problem を画面が別に呼んでいた
success_rate = 100.0 全件 ◎○。丸め前の生値が返る(表示の丸めは画面側)
success_rate = 0.0 全件 △× または未実施。null(分母0=ログ0件)と区別できること
planned と清掃ログ行数が乖離する日 data.anomaly が出る(収集処理の障害検知)。期待値と実測値だけを記録し、原因は断定しない。★再現手順は12.1参照★

section_name は暫定のダミー値(lib/fixtures/ds-alerts.ts)。 security_banner が要求するが実DB上の取得元が未確定なため、DB接続時に prismaSource 側で差し替えます。

failure_breakdown は fixtures を用意しません。 M2 では返さない(D-048)。 M3 でエラーテーブルを扱うときに、型・DataSource メソッド・fixtures をまとめて作ります。

12.3 date 省略時(直近モード)に何が出るか(★D-159 / 指示書AV★)

date を省略すると(?date= の空文字も同じ)、summary(数値カード)と problem_logs(コンパクト版の表)は ★各清掃エリアの直近の清掃1件★ から作られます(規則は lib/dashboard/latest-run.ts の pickLatestRunPerSection()、 日数・分数は lib/config の DASHBOARD_LATEST_RUN)。

  • 候補: 予定の開始 ≥ 現在 − 7日 ★かつ★ 予定の終了 ≤ 現在(★境界はどちらも含む★)。 ★D-173 で「終了+30分」(旧 settleMinutes)をやめた★ — 終わってからレポートが届くまでは、行の旗 result_pending で「結果待ち」と出す
  • 清掃エリアごとに、候補のうち開始が一番新しい1件(同じなら id の大きいほう)。★出発失敗も選ぶ★(未実施に数える)。 ★結果待ちの行も選ぶ★が、summary.pending に数え、★未実施にも成功率の分母にも入れない★。★problem_logs には出さない★(D-173)
  • ★schedule_progress・要レスキュー・セキュリティバナーは当日のまま★(§12.1 の「当日」の行: 予定11・実施9)

★fixtures には予定の終了も清掃エリアも無い★ので、fixtures 版の cleanLogRunMeta() は ★終了=予定の開始(scheduled_at)・清掃エリア=ロボット(serial_number)★ とみなします(D10。★fixtures にデータは足さない★)。 つまり fixtures では「★開始時刻を過ぎた予定★」が「終わった予定」になります。 当日の行は 01:30〜03:30(最後は施設12 と施設11 の 03:30)なので、★JST 3:30 が境目★です(D-173 以前は 4:00)。 ★fixtures の result_pending は時刻で決めず行に直書き★(施設11 の 03:30 だけが true。§2.3b)なので、時刻によって結果待ちが確定に変わることはありません。

★下の数字は lib/fixtures を import して db.cleanLogs()・db.cleanLogRunMeta()・pickLatestRunPerSection()・computeSummary() で数えたもの (当方の fixtures 由来。2026-10-07 11:3x JST。★D-173 の後★)★。

JST 3:30 以降(3:30 ちょうどを含む。その日のうちは同じ値):

項目 値
選ばれる行 ★15行(ロボット15台)★ — 当日11行・前日4行(施設5 の2台目・施設7 の3台)。★施設11 は当日の結果待ちの行が選ばれる★(D-173 以前は6日前の行)
summary ★成功8 / 失敗5 / 未実施1 / 結果待ち1 / 成功率 57.14…%(8 ÷ 14)★
△×(problem_logs) ★6行★ — 当日の5行(施設12 ×出発失敗・10 ×・9 ×・6 △・8 △)+ 前日の施設7(△。8111R5628060099)。★施設11 の結果待ちは出ない★
表に出る先頭5件 ★当日の5行: 施設 12・10・9・6・8★(データソースの順)
  • ★施設9 と施設6 は同じ 02:30★。fixtures の cleanLogs(lib/fixtures/clean-logs.ts)の並べ替えは scheduled_at しか見ないため、 同じ時刻の行は ★実際には id の降順★(施設9 = id 大 → 施設6)で出ます。Prisma 版は id の昇順なので、DB では 6・9 の順になります。 ★先頭5件の顔ぶれは変わりません★
  • 6行目(前日の施設7)は表に出ません(画面は先頭5件)

JST 3:30 より前は、当日の行の一部が「終わっていない」扱いになり、そのロボットは前日以前の行が出ます。 ★施設12(8111R5628060166)は7日以内に当日の行しか無いので消えます。それが正しい★。

現在(JST) 選ばれる行 summary △× 表に出る先頭5件
02:00 14行(当日5行) 成功9 / 失敗3 / 未実施2 / 結果待ち0 / 成功率 64.28…% 5行 施設 8・4・9・6・7
03:29 14行(当日9行) 成功9 / 失敗5 / 未実施0 / 結果待ち0 / 成功率 64.28…% 5行 施設 10・9・6・8・7

(02:00 は当日の 02:00 までの予定が終わった扱い。03:29 は 03:30 の施設12・施設11 だけが終わっていない扱い。 ★D-173 以前の表(終了+30分。02:00 は 01:30 だけ・03:59 が境目の直前)とは値が違う★)

13. エラー一覧(error-reports)(★D-160 / 指示書AW★)

GET /errors(/errors 画面)のサンプルデータは lib/fixtures/error-reports.ts の errorReportRows(11行)と errorReportTimetables(予定5件)です。

  • ★DB の行の形で持つ★(日時は toDbJst() を通した壁時計値の Date、id は bigint)。 ★fixtures と Prisma は同じ純粋関数を通す★(errorReportKeyFromRow() → filterSortErrorReportKeys() → errorReportItemFromRow())ので、 USE_FIXTURES で件数・並びが変わりません
  • ★既定(未解消・対応中)で10行(FX-ERR-03 以外)・「解消済みも表示」で11行★
  • ★robot_id は読みません★(select で絞る。指示書AU 段階0-4)。fixtures にも値はありますが使いません — ★制御盤・セキュリティ・清掃開始失敗の行は robot_id が NULL★ という実データの形を示すためだけに持っています
  • ★error_level の値(CRITICAL 以外)と、SECURITY_ON / D-S_CONTROL_FAILURE の error_level は実データ未受領のダミー★です。 NOT_STARTED・CRITICAL・シリアル '0' の組はステージングの実測の形(D-158 2026-10-06 実測・先方投入)
  • 施設名は facilities から、ロボットのシリアルは各施設の先頭のロボットから、制御盤のシリアルは ds-devices から id で引きます(★直書きしない・実在の店名を入れない★)
  • ★予定は本表・履歴を分けません★(fixtures には2表の区別が無い)。FX-ERR-06 の予定は「実DBでは履歴にだけある」想定です
  • ★対応状況の更新(PATCH /errors/{error_id}/status)は lib/db/fixture-error-report-overrides.ts の上書き表★に入ります (fixtures は書き換えない。プロセスを再起動すると消える。アラートの上書き表とは状態を共有しない)
  • 日付は実行日からの相対(at())です
id 内容 status 紐づく予定 表1(指示書AW §3)
FX-ERR-01 ロボットのエラー(EmergencyStop / ERROR)・施設1 0 未解消 本清掃 E1
FX-ERR-02 ロボットのエラー(LowBattery / WARNING)・施設1 1 対応中(responded_at あり) 事前チェック(30分前。PRECHECK_MINUTES_BEFORE[1]) E2
FX-ERR-03 解消済み(HandleUp)・施設3(responded_at / resolved_at / 備考あり) 2 解消 本清掃 E3(★既定では出ない★)
FX-ERR-04 clean_timetable_id が NULL・施設2 0 予定なし E4
FX-ERR-05 clean_timetable_id がどの予定にも無い id(9999)・施設2 0 予定不明 E5
FX-ERR-06 予定が履歴にだけある想定・施設4 0 本清掃 E6
FX-ERR-07 errored_at が NULL(検知時刻は created_at)・施設4 0 本清掃 E7
FX-ERR-08 ★robot_id NULL・シリアル '0'・SECURITY_ON★・施設5 0 本清掃 E8
FX-ERR-09 事前チェック(★10分前。設定に無い値★)・施設1 0 事前チェック(10分前) E9
FX-ERR-10 ★robot_id NULL・シリアル = 制御盤の SN(DS-001)・D-S_CONTROL_FAILURE★・施設1 0 本清掃 制御盤のエラー(D-158 P-8 a)
FX-ERR-11 ★robot_id NULL・シリアル '0'・NOT_STARTED・CRITICAL★・施設5 0 本清掃 清掃開始失敗(ステージング実測の形)

★既定の並び(検知時刻の降順)★: FX-ERR-07(当日 06:40)→ 05(前日 23:50)→ 11(23:30)→ 01(23:10)→ 08(22:55)→ 09(22:50)→ 10(22:45)→ 02(22:30)→ 04(15:20)→ 06(3日前 23:05)。 「解消済みも表示」では FX-ERR-03(2日前 23:40)が 04 と 06 の間に入ります (★当方の fixtures 由来。lib/db/__tests__/fixture-error-reports.test.ts が件数と降順を固定★)。