import { PRECHECK_MINUTES_BEFORE } from "@/lib/config";
import type { ErrorReportRowSource, ErrorTimetableRef } from "@/lib/db/error-report-mapping";
import { toDbJst } from "@/lib/format/date";
import { at } from "./_helpers";
import { dsDevices } from "./ds-devices";
import { facilities } from "./facilities";
/**
* エラー一覧(`/errors`。指示書AW 段階3-2)のサンプルデータ。docs/fixtures.md §13。
*
* ★DB の行の形で持つ★(指示書AW D13)— 日時は `toDbJst()` を通した壁時計値の `Date`、id は `bigint`。
* ★fixtures と Prisma は同じ純粋関数★(`errorReportKeyFromRow()` / `filterSortErrorReportKeys()` /
* `errorReportItemFromRow()`)を通すので、`USE_FIXTURES` で件数や並びが変わらない(`CLAUDE.md` §9.5)。
*
* ★既定(未解消・対応中)で10行(`FX-ERR-03` 以外)・「解消済みも表示」で11行★。
*
* ★★値はダミー★★:
* - 実在の店名・住所を入れない(`CLAUDE.md` §7)。★施設名は `facilities` から id で引く★(直書きしない)
* - ★`error_level` の値(`CRITICAL` 以外)と、`SECURITY_ON` / `D-S_CONTROL_FAILURE` の `error_level` は
* 実データ未受領のダミー★。`NOT_STARTED`・`CRITICAL`・`serial_number` `'0'` の組はステージングの実測の形
* (D-158 2026-10-06 実測・先方投入)
* - `error_type` は生値のまま画面に出る(区分を作らない。D-077 / D-160 項3)
*
* ★`robot_id` は読まない★(`select` で絞る。指示書AU 段階0-4)。fixtures でも値は使わない —
* ★制御盤・セキュリティ・清掃開始失敗の行は `robot_id` が NULL★ という実データの形を示すためだけに持つ。
*
* ★予定(`errorReportTimetables`)は本表・履歴を分けない★(fixtures には2表の区別が無い。
* 2表から引く規則は Prisma 版の `findTimetablesByChunks()` / `findTimetableById()`)。
*
* ★id は `"FX-ERR-01"` 〜★(`lib/fixtures/prechecks.ts` の id と重ならない。上書き表も別。指示書AW §11 Q8)。
*/
/** fixtures の行(★`robot_id` は読まない。実データの形を示すためだけ★) */
export interface FixtureErrorReportRow extends ErrorReportRowSource {
robot_id: bigint | null;
}
// ★分数を直書きしないための別名(禁止事項3)★ — 30分前は設定値から読む
const SECOND_ROUND_MINUTES = PRECHECK_MINUTES_BEFORE[1];
// ★10 は設定(60/30)に無い値であることが本題なので直書きする★(`lib/fixtures/prechecks.ts` の `20` と同じ扱い。
// D-105 項1: 設定に無い分数でも生の分数で「事前チェック(10分前)」と出す)
const UNCONFIGURED_MINUTES = 10;
/** 予定の id(`clean_timetables.id` 相当) */
const TT_MAIN_A = BigInt(9001);
const TT_PRECHECK_30 = BigInt(9002);
const TT_MAIN_B = BigInt(9003);
/** ★実DBでは `clean_timetable_histories` にだけある想定★(表1 E6。fixtures では2表を分けない) */
const TT_MAIN_HISTORY_ONLY = BigInt(9006);
const TT_PRECHECK_10 = BigInt(9009);
/** ★どの予定にも無い id★(表1 E5「予定不明」) */
const TT_MISSING = BigInt(9999);
/** 予定(本表・履歴をまとめたもの。`{ id, precheck }` だけ) */
export const errorReportTimetables: ErrorTimetableRef[] = [
{ id: TT_MAIN_A, precheck: 0 },
{ id: TT_PRECHECK_30, precheck: SECOND_ROUND_MINUTES },
{ id: TT_MAIN_B, precheck: 0 },
{ id: TT_MAIN_HISTORY_ONLY, precheck: 0 }, // 実DBでは履歴にだけある想定(E6)
{ id: TT_PRECHECK_10, precheck: UNCONFIGURED_MINUTES },
];
/** ★施設名は `facilities` から id で引く★(無ければ fixtures の誤りなので例外) */
function facilityOf(id: number): { facility_id: bigint; facility: { facility_name: string } } {
const found = facilities.find((f) => f.id === id);
if (!found) throw new Error(`[fixtures/error-reports] facilities に id=${id} がありません`);
return { facility_id: BigInt(id), facility: { facility_name: found.facility_name } };
}
/** その施設の先頭のロボットのシリアル(直書きしない) */
function robotSerialOf(facilityId: number): string {
const serial = facilities.find((f) => f.id === facilityId)?.robots[0]?.serial_number;
if (!serial) throw new Error(`[fixtures/error-reports] 施設 id=${facilityId} にロボットがありません`);
return serial;
}
/** その施設の D-S制御盤のシリアル(`lib/fixtures/ds-devices.ts` の実在の値) */
function controlPanelSerialOf(facilityId: number): string {
const serial = dsDevices.find((d) => d.facility_id === facilityId)?.serial_number;
if (!serial) throw new Error(`[fixtures/error-reports] 施設 id=${facilityId} に D-S制御盤がありません`);
return serial;
}
/** n日前の HH:MM(JST)→ DB の壁時計値の `Date`(Prisma が返すのと同じ形) */
function dbAt(n: number, time: string): Date {
return toDbJst(new Date(at(n, time)));
}
/** ロボットのエラー行の既定値(施設1・本清掃 A・未解消) */
function robotError(
errorId: string,
overrides: Partial<FixtureErrorReportRow> = {}
): FixtureErrorReportRow {
return {
error_id: errorId,
robot_id: BigInt(101),
clean_timetable_id: TT_MAIN_A,
serial_number: robotSerialOf(1),
...facilityOf(1),
error_type: "EmergencyStop",
error_level: "ERROR",
errored_at: dbAt(1, "23:10"),
created_at: dbAt(1, "23:11"),
status: 0,
responded_at: null,
resolved_at: null,
comment: null,
...overrides,
};
}
export const errorReportRows: FixtureErrorReportRow[] = [
// E1: ロボットのエラー・本清掃に紐づく(未解消)
robotError("FX-ERR-01"),
// E2: ロボットのエラー・事前チェック(30分前)に紐づく(対応中・responded_at あり)
robotError("FX-ERR-02", {
clean_timetable_id: TT_PRECHECK_30,
error_type: "LowBattery",
error_level: "WARNING",
errored_at: dbAt(1, "22:30"),
created_at: dbAt(1, "22:31"),
status: 1,
responded_at: dbAt(0, "08:30"),
}),
// E3: 解消済み・本清掃(responded_at / resolved_at / 備考あり)— ★既定では出ない★
robotError("FX-ERR-03", {
robot_id: BigInt(103),
serial_number: robotSerialOf(3),
...facilityOf(3),
clean_timetable_id: TT_MAIN_B,
error_type: "HandleUp",
errored_at: dbAt(2, "23:40"),
created_at: dbAt(2, "23:41"),
status: 2,
responded_at: dbAt(1, "08:20"),
resolved_at: dbAt(1, "09:05"),
comment: "現場でハンドルを戻してもらった",
}),
// E4: clean_timetable_id NULL(予定なし)
robotError("FX-ERR-04", {
robot_id: BigInt(102),
serial_number: robotSerialOf(2),
...facilityOf(2),
clean_timetable_id: null,
error_type: "LostLocation",
errored_at: dbAt(1, "15:20"),
created_at: dbAt(1, "15:21"),
}),
// E5: clean_timetable_id がどの予定にも無い id(予定不明)
robotError("FX-ERR-05", {
robot_id: BigInt(102),
serial_number: robotSerialOf(2),
...facilityOf(2),
clean_timetable_id: TT_MISSING,
error_type: "BrushError",
errored_at: dbAt(1, "23:50"),
created_at: dbAt(1, "23:51"),
}),
// E6: 予定が履歴にだけある想定・本清掃
robotError("FX-ERR-06", {
robot_id: BigInt(104),
serial_number: robotSerialOf(4),
...facilityOf(4),
clean_timetable_id: TT_MAIN_HISTORY_ONLY,
error_type: "Offline",
errored_at: dbAt(3, "23:05"),
created_at: dbAt(3, "23:06"),
}),
// E7: errored_at NULL(検知時刻は created_at)・本清掃
robotError("FX-ERR-07", {
robot_id: BigInt(104),
serial_number: robotSerialOf(4),
...facilityOf(4),
clean_timetable_id: TT_MAIN_B,
error_type: "Offline",
errored_at: null,
created_at: dbAt(0, "06:40"),
}),
// E8: ★robot_id NULL・serial_number '0'・error_type SECURITY_ON★(セキュリティのエラー。D-158 P-8)
robotError("FX-ERR-08", {
robot_id: null,
serial_number: "0",
...facilityOf(5),
clean_timetable_id: TT_MAIN_B,
error_type: "SECURITY_ON",
error_level: "ERROR", // ★ダミー(実データ未受領)★
errored_at: dbAt(1, "22:55"),
created_at: dbAt(1, "22:56"),
}),
// E9: 事前チェック(★10分前。設定に無い値★)
robotError("FX-ERR-09", {
clean_timetable_id: TT_PRECHECK_10,
error_type: "LowBattery",
error_level: "WARNING",
errored_at: dbAt(1, "22:50"),
created_at: dbAt(1, "22:51"),
}),
// P-8 a: ★robot_id NULL・serial_number = 制御盤の SN・error_type D-S_CONTROL_FAILURE★(制御盤のエラー)
robotError("FX-ERR-10", {
robot_id: null,
serial_number: controlPanelSerialOf(1),
clean_timetable_id: TT_MAIN_A,
error_type: "D-S_CONTROL_FAILURE",
error_level: "ERROR", // ★ダミー(実データ未受領)★
errored_at: dbAt(1, "22:45"),
created_at: dbAt(1, "22:46"),
}),
// ★robot_id NULL・serial_number '0'・error_type NOT_STARTED・error_level CRITICAL★
// (清掃を開始できなかった。ステージング実測の形 — D-158 2026-10-06 実測・先方投入)
robotError("FX-ERR-11", {
robot_id: null,
serial_number: "0",
...facilityOf(5),
clean_timetable_id: TT_MAIN_B,
error_type: "NOT_STARTED",
error_level: "CRITICAL",
errored_at: dbAt(1, "23:30"),
created_at: dbAt(1, "23:31"),
}),
];