Newer
Older
client_dwatch / docs / handoff / 指示書BD_エラー内容の選択リストのAPI_20261027.md

指示書BD — エラー一覧の「エラー内容」選択リストの API(2026-10-27)

担当: Claude Code(サブエージェントで実装し、監督がレビューする)。範囲は API まで(画面は次の指示書)。 決定の記録は D-184(案は本書 §9。docs/decisions.md には別セッションの未コミット差分があるため、その commit 後に追記する)。


0. 背景とユーザーの決定

  • ユーザー要望(2026-10-27): エラー情報を統計的に取るため、エラー一覧(/errors)でエラー内容をセレクトリストから選べるようにする。 選択肢はスプレッドシート「PUDUエラー一覧_20261016」の「PUDUエラー 決定稿」シート(131行。A列 = ID、C列 = エラー分類(main ja)、G列 = エラー詳細(日))。 「その他」を選んだときは備考欄に手入力。セレクトリストはエラー分類で絞れる。
  • テーブル定義書(清掃ロボット監視テーブル定義書、2026-10-27 時点)の変更:
    • 新テーブル error_lists(管理側UIなし): id bigint unsigned pk AI / error_category_main_en varchar(100) / error_category_main_ja varchar(200) / error_category_sub_en varchar(100) / error_category_sub_ja varchar(200) / error_detail_en text / error_detail_ja text / inactive tinyint unsigned 既定0 / created_at / updated_at / deleted_at(すべて timestamp)。★Pudu の error_type の列は無い★
    • robot_error_reports.error_list_id bigint unsigned(定義書は必須○・既定値なし・FK なし・索引なし)
  • ユーザー回答(2026-10-27):
    • ① error_lists は先方で作成済み。★こちら(DFA)が ID で選んで登録し、名前は ID で DB から引く★(先方が自動で入れる列ではない)
    • ② 更新用アカウントには既に更新権限があるはず → ★接続できるようになったら SHOW GRANTS で確認する★(未確認)
    • ③ 未選択は★空欄(NULL)★。こちらが選んで初めて入る
    • ④ ID は基本固定。追加・変更はありうる
    • ⑤ ステージング・本番に導入済み。★ただしステージングに接続できない(1045)ので実DDLは未確認★
    • 決定: ★ID で保存する★/★清掃開始失敗(NOT_STARTED)の行にも出す★(制御盤 D-S_CONTROL_FAILURE・セキュリティ SECURITY_ON の行には出さない — 監督の提案「ロボットのエラーの行だけ」に清掃開始失敗を足した決定と読む)/★その他は備考必須★
    • 「とりあえず API を実装して」

1. 共通ルール(G1〜G4。指示書AZ §2 と同じ)

  • G1: API を変えたら d-watch-api.yaml の★関係する全経路★(/errors・/errors/{error_id}/status・新設・スキーマ)を直す
  • G2: 触った箇所の古いコメント(「4列」「4カラム」等)を直す
  • G3: 素通りするテストを書かない(期待値を固定し、壊したら落ちることを確かめる)
  • G4: 完了時に npx tsc --noEmit・npm run lint・npx vitest run の件数を報告(基準: 139 files / 3284 tests)

2. 禁止・罠

  • ★prisma db push / migrate / db pull を実行しない★(.env.local はステージングを指している。スキーマ変更は TeamWorks の領域。CLAUDE.md §7)。npx prisma generate は DB に触らないので可
  • ★git add -A 禁止★。★docs/decisions.md・docs/handoff/指示書AY_*・docs/handoff/設計_制御盤ログの取り出し_* は別セッションの未コミット差分 — 触らない★
  • 認証情報を出力・コードに書かない
  • エラーの文字列(分類名・詳細)は t() に通さない・変換しない(D-077 と同じ。日英は DB の列を出し分ける)
  • /prechecks/{id}/status と D-Sec(同じ PATCH を使う lib/ds-alerts/edit.ts)から error_list_id を書ける経路を作らない

3. API 契約(d-watch-api.yaml を先に直す)

3-1. 新設 GET /error-lists(tags: masters)

  • 選択肢。★inactive = 0 かつ deleted_at IS NULL の行だけ★。並びは ★id の昇順★
  • ページングしない。meta は /groups と同じ書き方(total = 件数、page = 1、per_page = 件数)。クエリパラメータなし
  • 応答 { items: ErrorListItem[], meta }
  • ErrorListItem: id(integer) / error_category_main_en / error_category_main_ja / error_category_sub_en / error_category_sub_ja / error_detail_en / error_detail_ja(すべて string。DB の値のまま)/ note_required(boolean。この選択肢を選ぶと備考が必須。lib/config の ERROR_LIST_NOTE_REQUIRED_IDS)

3-2. ErrorReportItem に3項目を追加(required に入れる)

  • error_list_id: integer, nullable。DB の値のまま。null = 未選択
  • error_list: ErrorListItem, nullable。error_list_id の名前解決。★無効化・削除済みの行でも解決する★(施設名と同じく名前解決は絞らない)。 error_list_id が null、または error_lists に無い id なら null(画面は前者を「未選択」、後者を「不明(id)」と出す)
  • error_list_selectable: boolean。この行でエラー内容を選べるか。error_type が ERROR_LIST_UNSELECTABLE_ERROR_TYPES(D-S_CONTROL_FAILURE・SECURITY_ON。完全一致)なら false、それ以外は true。★画面は error_type を見ずにこれだけを見る★(CLAUDE.md §9.6)

3-3. PATCH /errors/{error_id}/status の本文に error_list_id を追加

  • 省略 → 変えない。整数(1以上)→ その id を書く。★null・0・負数・小数・文字列は 422★(解除は今回作らない。実DDLの NULL 可否が未確認のため)
  • 次の順で検証する(422 の details は既存の書式。フィールドは error_list_id / note):
    1. 現在値と同じ id → 検証せず、その列は書かない(無効化済みの id が入っている行で状態だけ保存しても 422 にしない)
    2. 変わる場合: 行の error_list_selectable が false → 422
    3. 変わる場合: id が error_lists に無い、または inactive ≠ 0、または deleted_at が非 NULL → 422
    4. ★要求がエラー内容か備考を変えるときだけ★、★適用後★の error_list_id が ERROR_LIST_NOTE_REQUIRED_IDS に入り、★適用後★の備考が空(null または空白だけ)なら 422(その他を選んで備考を書かない/既にその他の行で備考を消す、の両方)。 ★状態だけを変える要求には掛けない★・★選べない行(error_list_selectable が false)には掛けない★ (レビュー指摘で監督が判断 2026-10-27: /prechecks は error_list_id を見ずに備考を消せるので、「その他・備考なし」の行はアプリ自身が作れる。 そこで状態だけの保存まで 422 にすると、触っていない備考が理由で止まり、アラート画面と食い違う)
  • 何も変わらない要求の判定(isEmptyPatch)に error_list_id を含める
  • 応答は従来どおり更新後の ErrorReportItem(新3項目を含む)
  • ★書き込む列は robot_error_reports の5列(status / comment / responded_at / resolved_at / error_list_id)★。error_list_id は★この経路だけ★。 control_error_histories は従来どおり(今回触らない)

4. 設定(lib/config/index.ts)

  • ERROR_LIST_NOTE_REQUIRED_IDS: readonly number[] = [131](決定稿の「OTHER ERROR / その他(該当なし)」。ID は基本固定・追加変更ありうる — ユーザー回答④)
  • ERROR_LIST_UNSELECTABLE_ERROR_TYPES: readonly string[] = ["D-S_CONTROL_FAILURE", "SECURITY_ON"](ユーザー判断 2026-10-27。NOT_STARTED は選べる)

5. データ層

5-1. prisma/schema.prisma(★db pull できないための手書き★)

  • ErrorList モデル(@@map("error_lists"))を定義書どおりに足す。id BigInt @id @default(autoincrement()) @db.UnsignedBigInt、 inactive Int @default(0) @db.UnsignedTinyInt、created_at DateTime @db.Timestamp(0)、updated_at / deleted_at は DateTime?。リレーションは張らない(FK が無い)
  • RobotErrorReport に error_list_id BigInt? @db.UnsignedBigInt(定義書は必須だがユーザー回答③「初期は空欄」で NULL 可にする)
  • 両方に「★定義書ベースの手書き(2026-10-27)。ステージングに接続できず db pull できないため。接続回復後に db pull で置き換え、差分を確認する(D-056 の例外。D-184)★」とコメント
  • npx prisma generate を実行
  • ★@updatedAt を付けない★(既存の注意どおり)

5-2. fixtures

  • lib/fixtures/error-lists.ts: 決定稿 131行(元データ: 監督が抽出した JSON。パスは依頼文に書く)。すべて inactive: 0・deleted_at: null。★error_type 列はテーブルに無いので入れない★(コメントに出典だけ書く)
  • lib/fixtures/error-reports.ts: 既存行のいくつかに error_list_id を入れる — 有効な id/その他(131)+備考あり/error_lists に無い id(例 9999)/null(大半)。 NOT_STARTED の行と D-S_CONTROL_FAILURE・SECURITY_ON の行が fixtures にあることを確かめる(無ければ足す)
  • docs/fixtures.md に追記

5-3. 読み取り

  • GET /error-lists 用: db.errorLists()(fixtures / Prisma)。Prisma は where: { inactive: 0, deleted_at: null }, orderBy: { id: "asc" }
  • robot-error-select.ts の選択列に error_list_id を足す
  • 名前解決: 一覧・1件とも、行の error_list_id(非 null)を集めて errorList.findMany({ where: { id: { in } } }) を★絞らずに★1回引き、写像で解決(N+1 にしない)
  • error-report-mapping.ts で3項目を組み立てる。error_list_selectable の判定は純粋関数1つ(lib/config の定数を読む)

5-4. 書き込み

  • /errors 専用のパーサ・計画(lib/errors/status-update.ts を新設。parsePrecheckStatusUpdateBody / planPrecheckStatusUpdate を内部で使い、error_list_id を足す)。 ★lib/prechecks/status-update.ts の挙動は変えない★(/prechecks は error_list_id を捨てるまま)
  • 型 ErrorStatusPatch = PrecheckStatusPatch & { error_list_id?: number }、ErrorStatusUpdateRequest(lib/types)
  • robot-error-write.ts: 既存の robotErrorColumnsFromPatch(4列)は★そのまま★(事前チェックが使う)。/errors 用に robotErrorColumnsFromErrorPatch を足し、 error_list_id だけを追加で許す(BigInt に変換)。「4列以外を含めば例外」の歯止めは5列版にも入れる
  • lib/db/index.ts の updateErrorReportColumns(interface と fixtures 版・Prisma 版の3か所)を ErrorStatusPatch に
  • fixture-error-report-overrides.ts は error_list_id も写す(★写し忘れると fixtures で 200 なのに反映されない★)
  • 監査ログ(lib/logger/events.ts の DbWriteColumn・auditBase)に error_list_id の old/new
  • テスト: lib/db/__tests__/robot-error-write-sql.test.ts を5列に。★事前チェック・D-Sec の経路では error_list_id を書けない★ことを固定するテストを足す

6. 段階と担当(サブエージェント)

段階 内容 担当ファイル
1 契約 §3 の YAML、§4 の設定、lib/types の型、§5-1 の schema と generate、§5-2 の error-lists.ts d-watch-api.yaml / lib/config/index.ts / lib/types/index.ts / prisma/schema.prisma / lib/generated/ / lib/fixtures/error-lists.ts
2a 読み取り §5-2 の error-reports fixtures、§5-3、app/api/error-lists/route.ts+テスト、GET /errors のテスト更新 lib/fixtures/error-reports.ts / lib/db/robot-error-select.ts / lib/db/error-report-mapping.ts / 新 lib/db/error-list-source.ts / lib/db/index.ts(★読み取り関数だけ★)/ app/api/error-lists/ / app/api/errors/__tests__/ / docs/fixtures.md
2b 書き込み §5-4 新 lib/errors/ / app/api/errors/[error_id]/status/ / lib/db/robot-error-write.ts / lib/db/fixture-error-report-overrides.ts / lib/logger/events.ts / lib/db/index.ts(★updateErrorReportColumns の3か所だけ★)/ 関連テスト
3 レビュー 監督が差分を読み、別のエージェントに独立レビューさせる —
4 資料 docs/code-map.md、CLAUDE.md §3・§7(書ける列をテーブルごとに書き分ける)、本書の完了記録 監督

2a と 2b は並行。lib/db/index.ts は担当の関数以外を触らない(Edit が「変更された」で失敗したら読み直して再適用)。

7. 完了条件

  • tsc 0 / lint 0 / vitest 全件通過(件数を記録)/ npm run build 成功
  • fixtures ビルドで GET /error-lists(131件)・GET /errors(新3項目)・PATCH(有効 id/その他+備考なし→422/選べない行→422/無効 id→422/同じ id で状態だけ→200)を実際に叩いて確かめる

8. 接続回復後にやること(今回はできない)

  • SHOW GRANTS で error_list_id の UPDATE 権限を確認(ユーザー回答②)
  • information_schema で error_lists と error_list_id(型・NULL 可否)を確認 → db pull → 手書きとの差分確認
  • error_lists の行数・中身が決定稿 131行と一致するか(特に 131 = その他)
  • ★ローカルDB(dwatch_local)には error_lists も error_list_id も無かった★。実DBモードでは GET /errors・PATCH /errors・D-Sec の保存・GET /error-lists が 500 になり、 シード(select の無い robotErrorReport.create)も落ちる。→ ★2026-10-27 にローカルにだけ表と列を足す手順を scripts/local-db/ に用意した★(ユーザー指示「ローカル環境でテストするのでダミーのDB作成」)

9. D-184(案)

  • エラー一覧の各行に「エラー内容」(error_lists の id)を登録できるようにする。保存先は robot_error_reports.error_list_id(★アプリが書く5列目。この表のこの経路だけ★)
  • 選べる行は D-S_CONTROL_FAILURE・SECURITY_ON 以外(清掃開始失敗は選べる)。その他(id 131)は備考必須。未選択は NULL。解除は作らない
  • 名前解決は絞らない/選択肢は有効な行だけ
  • prisma/schema.prisma の ErrorList と error_list_id は定義書ベースの手書き(D-056 の例外。接続回復後に db pull で置き換え)
  • 却下案: 備考(comment)に G列の文字列を書く — 権限変更は要らないが、手入力の備考と混ざり統計が文字列一致頼みになる