Newer
Older
client_dwatch / docs / handoff / 指示書AK_分類を復活させる.md

指示書AK — 分類(ホールディングス / 屋号 / 店舗)を復活させる

作成: 2026-09-15 / ブランチ: develop / 前提: 指示書AJ 完了(D-137)


★Cursor への指示文(ここから下をそのまま貼ってください)★

D-Watch の実装をお願いします。ブランチは develop です。

■ 最初に読むもの(この順で、全部)
  1. CLAUDE.md(全文。特に §2 責任分界 / §4.2 KPI / §4.6 契約フラグ / §5 モックが正 /
     §7 禁止事項 / §9 実装規約)
  2. docs/handoff/指示書AK_分類を復活させる.md(全文)
  3. docs/decisions.md の D-053 / D-011 / D-102 / D-133
     (grep -nE "^### D-(0|1)[0-9][0-9]" docs/decisions.md で位置を出してから読む。
       ★番号順に並んでいません★)
  4. lib/facilities/resolve-by-name.ts(全文。★今回の実装が真似る作法そのもの★)

■ やること
  指示書AK の 段階1 から 段階9 までを、★番号順に★実行してください。
  ★段階6(確定期間KPIの安全弁)だけは、段階2〜4 が終わるまで着手しないでください★ —
  顧客で絞る手段が無い状態で安全弁を外すと、静かに誤った数字が出ます。

■ 段階ごとに必ずやること
  1. npx tsc --noEmit    → 0 であること
  2. npm run lint        → 0 であること
  3. npm test            → 全パス
  4. コミットする(★段階ごとに1コミット★。まとめない)

■ コミットの決まり
  ・git add -A は使わないでください。必ずファイルパスを明示してください
  ・コミットメッセージは日本語。「なぜそうしたか」を1〜2行入れてください
  ・push はしないでください

■ 絶対に触らないファイル
  README.md / docs/code-map.md /
  docs/handoff/管理画面_設計検討_20260910.md / .claude/worktrees/** /
  d-watch-mock.jsx / lib/judge/**(判定ロジックは今回変えません)
  lib/db/prisma.ts

■ 絶対にやらないこと
  ・生SQL($queryRaw / $executeRaw / *Unsafe / Prisma.raw)— ESLint が落とします
  ・メインDBのスキーマ変更・マイグレーション作成
  ・customers.contract の参照(facilities.contract が正)
  ・contract を数値として比較すること(varchar(10) の桁位置です)
  ・新しい CSS に font-family を書くこと
  ・仕様が不明なところを推測で実装すること

■ 迷ったら
  推測で埋めず、手を止めて「ここが決まっていません」と報告してください。
  指示書AK の §12(実装者へ — 特に踏みやすい落とし穴)に、
  例外もテスト失敗も出ないまま壊れる箇所を挙げてあります。着手前に読んでください。

■ 動作確認
  ★テストDBには グループ1件・顧客1件・施設12件 しか入っていません★。
  そのままでは3階層の絞り込みを確かめられないので、
  段階8 で scripts/seed-local-db.ts を拡張してローカルDBで確かめます。
  ★テストDB(133.125.238.144)には書き込まないでください★。
  期待される結果は指示書AK の §10 に書いてあります。

■ 終わったら
  ・段階ごとのコミットハッシュ
  ・tsc / lint / test の結果(テスト本数も)
  ・★§10 の動作確認を実際に行った結果★
  ・指示書どおりにできなかったところと、その理由
  ・判断に迷って自分で決めたところ(あれば全部)

0. この指示書の位置づけ

ユーザーの新要件4件のうち、★3件目★です。

要件 状態
要レスキュー店舗 ★完了★(D-135 / D-137)
詳細ログの期間上限2年 + CSV上限 ★完了★(D-136)
★分類の復活★ ★★この指示書★★
総清掃面積の新画面 ★次★(★この指示書の絞り込みに乗るので、順番が後★)

0-1. ★TeamWorks 待ちの部分と、待たずに作れる部分★

TeamWorks確認事項_第9回_20260915.md で3件を照会中です。

照会中 この指示書への影響
Q7-1: customers は屋号か法人か ★画面の見出し語だけ★。構造は変わらない
Q7-5: 「屋号が無い顧客」はどの段が空くか ★表示の出し方だけ★
Q7-4: group_id = 0 が FK で入らない ★データ投入側の話★。読む側は 0 → null で扱えばよい

★★どれも構造を変えません。★★ ★DBの階層は groups → customers → facilities の3段しかなく、 4段目の受け皿が無いからです。★ ★したがって、仕組みは今すぐ作れます。★

★見出し語は lib/i18n.ts の1箇所に置く★ので、★回答が来たら差し替えるのは1行★です。


1. ★何を作るのか★

1-1. D-053 で凍結したものを解除する

★D-053(docs/decisions.md)の原文★:

  • 決定: グループ・会社の絞り込み <select> は、マスタが揃うまで画面から削除する
  • 理由: 固定の選択肢だけがあって何も起きないUIは、不具合と区別できないため
  • 運用: ★マスタが揃った時点で、グループ・会社の絞り込みは全画面まとめて実装する★

★★「全画面まとめて」と書かれています。★★ ★1画面ずつ足さないこと。★

削除したコミットは 441c8bb(2026-09-01)です。

1-2. 下地は既にあります

状態
DBの3階層 ★groups → customers.group_id → facilities.customer_id。実在★
Prisma のモデル ★Group / Customer / Facility。実在★
API契約の group_id / customer_id ★11エンドポイントに定義済み★
GET /groups / GET /customers ★契約にはある。★ルートが無い★
実装 ★受け取って無視している★(D-053 のコメントが13ファイル)

★★つまり契約の追加ではなく、実装の穴埋めです。★★

1-3. ★ユーザーが決めたこと(原文)★

・分類をホールディングス、各グループ(ブランド)、店舗 分類を復活

★真ん中は屋号(ブランド)★(2026-09-15「屋号でよいです、ただ顧客によってはない場合もあります」)。

★対応:★

ホールディングス  →  屋号(ブランド)  →  店舗
   groups              customers          facilities

2. ★監督が決めたこと(実装前に読むこと。覆すなら理由を残すこと)★

★ユーザーに個別の確認を取らず、既存の規則との整合から決めた事項です。★

2-1. ★★確定期間のKPIは、対象顧客が1社に定まるときだけ出す★★

★これが今回いちばん重要な決定です。★

背景: /reports/kpi の確定期間は customer_summaries を読みます(D-102)。 現在は「複数顧客の行が混ざったら例外(500)」という安全弁が入っています(D-133)。

合算できないことが実測で分かっています。

指標 サマリの分子 サマリの分母 合算できるか
清掃完了率 clean_area task_area ★できる★
出発成功率 launch_count task_count? ★式が未確認★
★稼働率★ ★列が無い★ task_count? ★★できない★★
★エラー発生率★ error_count ★列が無い★ ★★できない★★

★「清掃実施回数」に当たる列がサマリにありません。★ ★率の平均を取るのは誤り★です(店舗数の違いが消えます)。

★決定:★

customer_id が指定されている        → その顧客で絞る
group_id が指定されている           → 傘下の顧客が1社なら、その顧客で絞る
                                      2社以上なら 422
どちらも指定なし・顧客が1社だけ     → その顧客(★P1 はここ★)
どちらも指定なし・顧客が2社以上     → ★★422★★

★422 のメッセージ★:

「顧客またはグループを指定してください。複数の顧客のデータを合算した数値は出せません。」
details: { customer_ids: [...] }   ← ★候補を返す★

★★500 ではなく 422 にすること★★ — ★サーバの不具合ではなく、指定が足りないという話★です。 ★details に候補を返すこと★ — ★画面が「どれを選べばよいか」を示せます。★

★★「静かに1社を選ぶ」ことは絶対にしないこと★★(D-133 が明確に否定しています。 どの顧客かを決める根拠が無いまま選ぶと、選び間違いに誰も気づけません)。

★P1 の対象顧客は1社だけ★(CLAUDE.md §1)なので、★この422は当面発火しません★。 ★2社目が入った日に、画面が「選んでください」と言うようになります。★

★却下した案と理由(記録に残すこと)★:

案 なぜ却下したか
全顧客を合算する ★4指標のうち2つを正しく合算する材料がDBに無い★(上の表)
率の加重平均で近似する ★TeamWorks が計算した値ではなくなる★。検算可能性(D-022 の設計意図)を失う
500 のまま ★2社目が入った瞬間、レポート画面が常時500になる★。運用が止まる
顧客が1社のときだけ暗黙に選ぶ ★候補が1つなら選び間違いようが無いので採用★(上の決定に含めた)。★2社以上で暗黙に選ぶのは却下★

2-2. 絞り込みは「施設IDの配列」に解決してから渡す

★既存の作法に乗せます★(lib/facilities/resolve-by-name.ts、指示書AC / AG の帰結)。

group_id / customer_id  →  対象の施設IDの配列  →  where: { facility_id: { in: [...] } }

★★null と [] を区別すること★★:

値 意味
null ★条件なし(全件)★
[] ★0件ヒット★
[1,2,3] その施設だけ

★★必ず !== null で分岐すること★★ — ★in: undefined は条件が消えて全件になります★。 ★?.length で判定すると、「0件ヒット」が「全件表示」に化けます。例外もテスト失敗も出ません。★

★既存の正しい書き方が2箇所にあります★。★そのまま真似ること★:

  • lib/db/index.ts の cleanLogs()(facility_ids !== null ? { facility_id: { in: ... } } : {})
  • lib/db/index.ts の facilityPerformance()

★★施設名の検索と併用されたときは、両方の積集合を取ること★★(AND)。 ★どちらか片方を捨てないこと。★

2-3. 施設名の照合とは違い、DBで完結させてよい

★施設名検索がアプリ側照合になっているのは、「中点を無視する比較が MariaDB に無い」からです★ (実測済み。lib/facilities/resolve-by-name.ts の冒頭コメント)。

★group_id / customer_id は整数の完全一致★なので、★その制約はありません★。 ★全件をメモリに読んで照合する必要はありません。★

★真似るのは「null / [] の戻り値の契約」と「in に渡す形」だけ★です。

2-4. ★200件の壁は、今回は素通しする★

★eq_range_index_dive_limit = 200★(MariaDB 10.11 で実測)。 ★IN の要素数が200を超えると、索引ダイブをやめて統計推定に切り替わります。★ ★遅くなるだけで、エラーは出ません。★

★グループの絞り込みは「1グループ=傘下の全店」なので、施設名検索より200を超えやすい★です。

★決定: 今回は素通しする。★

★理由★: P1 の施設数は200件程度の想定で、★本番の施設数の見積もりが資料に1件も無い★ (第9回 8章で照会中)。★測れない相手に対策を入れても、効いたかどうかが分かりません。★

★★申し送りに書くこと★★: 施設が200件を超えたら実行計画を測り直す。 ★JOIN(リレーションフィルタ)に切り替える案があるが、このプロジェクトに前例が0件で、 Prisma 7 + MariaDB adapter がどんなSQLを出すか未実測★。

2-5. 見出し語

★ユーザーの言葉を使う★。★lib/i18n.ts の1箇所に置くこと★。

語 EN
ホールディングス Holdings
屋号 Brand
全ホールディングス All Holdings
全屋号 All Brands

★★既存の「全グループ」「全会社」を再定義しないこと★★(lib/i18n.ts に既にあります)。 ★古い語を消すかどうかは実装者判断★ですが、★消すなら他に使っていないことを確かめること★。

★★TeamWorks の回答(Q7-1)で customers が法人だと分かったら、この表だけ差し替えます。★★ ★だから1箇所に集めておくこと。★


3. ★触ってはいけないもの★

対象 理由
README.md / docs/code-map.md / docs/handoff/管理画面_設計検討_20260910.md / .claude/worktrees/** ★別作業★
d-watch-mock.jsx ★モックは原型として保存する★
★lib/judge/**★ ★判定ロジックは今回変えません★
lib/db/prisma.ts ★未コミットの変更が乗っている★
★テストDB(133.125.238.144)への書き込み★ ★★読み取り専用。SELECT 以外を投げないこと★★

★★禁止★★(CLAUDE.md §7):

  • ❌ ★git add -A★ — 必ずファイルパスを明示する
  • ❌ ★生SQL★(ESLint が error で落とす。D-122)
  • ❌ ★メインDBのスキーマ変更・マイグレーション作成★
  • ❌ ★customers.contract の参照★(★facilities.contract が正★。D-010 / D-080)
  • ❌ ★contract を数値として比較すること★ — ★varchar(10) の桁位置です★。 ★Number("0000000000") は例外を出さずに 0 になり、★★全施設が「契約なし」になってメニューが 全部消えるのに、例外もテスト失敗も出ません★★★(D-080)
  • ❌ ★新しいCSSへの font-family★
  • ❌ ★推測での実装★

4. 段階1 — マスタAPI 2本

4-1. YAML は既にあります

d-watch-api.yaml の /groups と /customers は★定義済み★です。★先に読むこと。★

★ただし、そのままでは足りない点が5つ★あります。★どう埋めるかを決めてから実装すること。★

# 足りない点 どうするか
1 ★meta が無い★(CLAUDE.md §9.3 は「一覧APIは必ず meta を返す」) ★セレクトリスト用なのでページングは要らない★。★meta を足すか、YAML に「セレクト用なので meta を返さない」と明記するか、どちらかに決めて理由を書くこと★
2 ★エラー応答の宣言が無い★ ★他のルートも全部同じ状態★(別課題として記録済み)。★今回だけ足すと書式が揃わない★。★足さないことを選んでよいが、選んだ理由を書くこと★
3 ★group_id に「未設定を明示する」値が無い★ ★group_id=0 を API に漏らさないこと★(D-011 は「API層で 0 → null に変換する」と定めている)。★「ホールディングスなしの顧客だけ」を引く手段は今回作らない★
4 ★inactive / deleted_at の扱いが未定義★ ★deleted_at IS NULL で絞る★(db.facilities() と揃える)。★inactive では絞らない★。★YAML の description に書くこと★
5 ★ソート順が未定義★ ★id の昇順★。★契約に明記すること★(D-052 / D-055 の前例)

4-2. 実装

  • app/api/groups/route.ts / app/api/customers/route.ts
  • ★手本は app/api/schedules/route.ts★(items + meta だけの一覧で、形が近い)
  • ★認証はルートに書かない★ — proxy.ts が横断して処理します
  • ★GET だけ★。★POST / PUT / DELETE を足さないこと★(マスタのCRUDは D-Security 側の画面。docs/spec.md)

4-3. DataSource への追加

lib/db/index.ts の interface DataSource に足し、 ★fixtureSource と prismaSource の両方に実装すること★。

★★fixtures 側を notImplemented にしないこと★★ — ★USE_FIXTURES=true で画面が落ちます。★

★lib/fixtures/ に groups.ts / customers.ts が存在しません。新設すること。★ ★施設12件をどの顧客に割り当てるかは実装者判断★ですが、 ★★1グループ・1顧客にしないこと★★ — ★それでは3階層のUIを fixtures で確かめられません。★

4-4. ★group_id = 0 の扱い★

DB: customers.group_id = 0   (NOT NULL。0 = グループなしのセンチネル。D-011)
API: group_id = null

★変換は1箇所に閉じ込めること★(lib/format/ か lib/config/ の純粋関数。D-080 の作法)。 ★画面やルートに === 0 を散らさないこと。★

★★「グループ0」という行を作らないこと★★ — ★グループ一覧に id=0 が現れてはいけません。★


5. 段階2 — クエリパーサに group_id / customer_id を足す

5-1. 対象

ファイル
lib/prechecks/query.ts
lib/ds-devices/query.ts
lib/ds-alerts/query.ts
lib/reports/query.ts
lib/facilities/query.ts
lib/clean-logs/query.ts
lib/schedules/query.ts

★「D-053。マスタ未整備のため無視する」というコメントを消し、実際に読むようにする。★

5-2. ★既存テスト5本が必ず落ちます★

lib/prechecks/__tests__/query.test.ts
lib/ds-devices/__tests__/query.test.ts
lib/ds-alerts/__tests__/query.test.ts
lib/reports/__tests__/query.test.ts
lib/facilities/__tests__/query.test.ts

すべて it("group_id / customer_id を渡してもパース結果に含まれない(D-053)") です。

★★it ごと消さないこと★★ — ★テストが1本も残らないと、次に誰かが「無視する」実装に戻しても 気づけません。★

★反転させて、さらに強くすること★:

# 入力 期待
2-1 group_id=1 ★1 が入る★(★toHaveProperty ではなく値まで見ること★)
2-2 customer_id=2 ★2 が入る★
2-3 ★未指定★ ★null★
2-4 ★group_id=0★ ★★422★★(★0 はセンチネルで有効なIDではない★)
2-5 ★group_id=1abc★ ★★422★★(★parseInt の前方一致で 1 にしないこと★。D-123 で踏んだ罠)
2-6 group_id=-1 ★422★
2-7 ★group_id=1.5★ ★422★
2-8 ★巨大な整数★(Number.MAX_SAFE_INTEGER 超) ★422★

★★2-5 は過去に実際に踏んだ罠です★★(D-123)。★必ずテストに入れること。★ ★既存の parsePathId()(lib/api/path-params.ts)が ^\d+$ + Number.isSafeInteger で やっているので、★同じ作法を使うこと★。

5-3. YAML の description を直す

d-watch-api.yaml の GroupId / CustomerId:

グループで絞り込み。★グループ・会社マスタ未整備のため現在は受け取るが無視する。実DB接続時に対応する★

★この記述を書き換えること。★ ★minimum: 1 を足すこと★(0 はセンチネルなので有効なIDではない)。

★★group_id と customer_id を同時に指定したときの扱いを決めて、契約に書くこと★★ — ★資料のどこにも定義がありません。★ ★AND(両方を満たす施設)が素直★ですが、 ★決めたら必ず YAML に書くこと★(次の人が推測しないように)。


6. 段階3 — 施設IDへの解決

6-1. 新しい純粋関数

lib/facilities/ に足す(resolve-by-name.ts の隣)。

groupId / customerId  →  対象の施設IDの配列 | null

★戻り値の契約は resolveFacilityIdsByName() と完全に同じにすること★:

状況 戻り値
どちらも未指定 ★null★(条件なし)
指定されたが該当0件 ★[]★
該当あり ID の配列

6-2. ★施設名との併用★

★施設名の絞り込みと同時に指定されたら、積集合を取ること★(AND)。

施設名 グループ 期待
null null ★null★
[1,2,3] null [1,2,3]
null [2,3,4] [2,3,4]
★[1,2,3]★ ★[2,3,4]★ ★★[2,3]★★
★[1]★ ★[2]★ ★★[]★★(★null にしないこと★)
[] [1,2] ★[]★

★★最後の2行が要です★★ — ★積集合が空になったとき [] を返すこと★。 ★null にすると全件表示に化けます。★

6-3. 読み取りの注意

  • ★施設マスタは12件程度なので、1回読んでメモリで解決してよい★
  • ★★施設ごとにループでクエリを撃たないこと★★
  • ★deleted_at で絞るかどうか★: ★facilityNameIndex() は絞っていません★ (★削除済み施設のログが見えなくなるのを避けるため。罠7★)。★同じ扱いにすること。★ ★違う扱いにするなら、理由をコメントに書くこと。★

7. 段階4 — 一覧系APIに絞り込みを効かせる

7-1. 対象

ルート 備考
/clean-logs / /clean-logs/export ★YAML は宣言済み。コードにコメントも無い★
/prechecks
/ds-devices
/ds-alerts
/facilities ★YAML に宣言が無い。足すこと★
/schedules ★同上★
/dashboard ★要レスキューと数値カードの両方に効く★
/reports/facility-performance ★施設単位なので普通に効く★
/reports/continuous-problems ★同上★
/reports/error-breakdown ★★当期は施設単位。確定期間は段階6 の話★★

7-2. ★/dashboard の注意★

★数値カード(summary)にも絞り込みが効きます。★ ★それが正しい★です (「この屋号の昨夜の成績」が見たいので)。

★★ただし、要レスキューと数値カードで母集団がずれないようにすること★★ — ★同じ facility_ids を両方に渡すこと。★

7-3. ★facility_error_summaries の絞り込み★

lib/db/index.ts の errorBreakdown() は facility_error_summaries を ★facility_id で絞らずに読んでいます★(D-133 の申し送り③)。

★今回、絞り込みが入るので解決できます。★ ★ReportDbQuery に facility_ids を足すこと★ (現在は FacilityPerformanceDbQuery にしかありません)。


8. 段階5 — Facility に所属を足す

8-1. 契約と型

d-watch-api.yaml の Facility と lib/types/index.ts の Facility に足す:

フィールド 型 備考
customer_id integer ★NOT NULL なので nullable にしない★
customer_name string ★一覧の「ブランド」列★。★InspectionListItem.customer_name に前例があり、文言を揃えること★
group_id integer, nullable ★nullable 必須★(0 → null。D-011)
group_name string, nullable 同上

8-2. 取得

lib/db/index.ts の facilities() は既に include: { robots: ... } しています。 ★include: { customer: { include: { group: true } } } を足すだけ★です。 ★施設12件なので N+1 にはなりません。★

8-3. fixtures

lib/fixtures/facilities.ts は Facility[] を直接持っています。 ★12件すべてに新しい4フィールドを足すこと。★


9. 段階6 — ★確定期間KPIの安全弁を置き換える★

★★段階2〜4 が終わるまで着手しないこと。★★ ★顧客で絞る手段が無い状態で安全弁を外すと、静かに誤った数字が出ます。★

9-1. 現状

lib/db/report-mapping.ts の customerSummariesByPeriodKey():

  • 全行の customer_id が1種類かを確認し、★2種類以上なら例外★
  • docstring に「★customer_id で where を絞って回避しないこと★ — ★どの顧客かを決める根拠が無い★」

★★分類が復活すると「どの顧客かを決める根拠」ができます。★★ ★この docstring は書き換えること★ — ★残すと、次の人が矛盾した指示を読みます。★

9-2. 実装

★§2-1 の決定のとおり★:

customer_id が決まる  → lib/db/index.ts の customerSummary.findMany の where に customer_id を足す
決まらない            → 422

★同じ期間キーの重複を検知する分岐は残すこと★ — ★一意制約が無いので、同一顧客でも重複しうるからです。★

9-3. 期待値表

# 状況 期待
6-1 customer_id=1 指定・顧客1社のデータ ★200。その顧客の数字★
6-2 customer_id=1 指定・DBに2社ぶんある ★★200。顧客1だけの数字★★(★絞れているか★)
6-3 ★指定なし・顧客が1社だけ★ ★★200★★
6-4 ★指定なし・顧客が2社★ ★★422★★(★500 ではない★)
6-5 6-4 の details ★候補の customer_ids が入る★
6-6 group_id 指定・傘下1社 ★200★
6-7 ★group_id 指定・傘下2社★ ★★422★★
6-8 同じ期間キーが重複 ★例外のまま★(★この検知は残す★)
6-9 ★customer_id が存在しないID★ ★200・空★(★422 にしない★。「該当なし」は正常)

★★6-2 が要です★★ — ★絞り込みが本当に効いているかを、2社ぶんのデータで確かめます。★ ★段階8 の seed 拡張が要ります。★


10. 段階7 — 画面に絞り込みを戻す

10-1. ★6画面まとめて★(D-053 の指定)

モックに <select>全グループ</select><select>全会社</select> があるのは6画面です。

モックの行 画面
:556 清掃詳細ログ
:610 アラート(事前チェック)
:669 レポート
:836 D-Secログ
:923 D-Secアラート
:1091 ★点検記録★ — ★★画面自体が未実装(D-083 で非表示)。今回は対象外★★

★★実質5画面です。★★ ★点検記録は画面が無いので、足しようがありません。★ ★対象外であることを記録に書くこと。★

★現在 D-053 のコメントが残っているのは3画面だけ★(清掃詳細ログ / レポート / 施設マスタ)で、 ★残りは移植時にそもそも作られていません★。★無い画面にも足すこと。★

10-2. 作法

  • ★選択肢は /groups と /customers から取る★。★固定の選択肢を書かないこと★ (★それをやったのが D-053 で消された元のUIです★)
  • ★ホールディングスを選んだら、屋号の選択肢がその傘下だけに絞られること★ (GET /customers?group_id=X)
  • ★draft(入力中)と確定(検索ボタン押下)を分けること★ — ★ReportsScreen.tsx に前例があります★。★1操作ごとに叩かないこと★
  • ★取得は apiFetch★(lib/api/client.ts)。★fetch を画面に直書きしない★
  • ★文言は t() を通す★。★色は CSS 変数★。★新しいCSSに font-family を書かない★

10-3. ★マスタの取得が失敗したとき★

★選択肢が取れなかったら、絞り込みUIを出さないこと。★ ★★空の <select> を出すと、D-053 で消した「押しても何も起きないUI」に逆戻りします。★★


11. 段階8 — seed を拡張する(★ローカルDBのみ★)

11-1. ★テストDBには書き込まないこと★

★133.125.238.144 は読み取り専用で扱います。★ ★拡張するのは scripts/seed-local-db.ts(ローカルの MySQL 8.4 dwatch_local)です。★

11-2. 現状

groups     1件("テストグループ001")
customers  1件("テスト顧客001")
facilities 12件(全部 customer_id=1)

★これでは3階層の絞り込みも、§9-3 の 6-2 / 6-4 も確かめられません。★

11-3. 足すもの

最低限
groups ★2件以上★
customers ★4件以上★ — ①グループA傘下 ②グループA傘下(★同一グループ2社目★)③グループB傘下 ④★グループなし★
facilities ★12件を複数の顧客に散らす★
customer_summaries ★顧客ごとに作る★ — ★★これが §9-3 の 6-4(2社で422)を再現する唯一の手段★★
facility_error_summaries ★複数顧客の施設に散らす★(申し送り③の再現)

11-4. ★group_id = 0 の投入について★

★ローカルは MySQL 8.4 で、FK の状況がテストDBと違う可能性があります。★ ★group_id = 0 が入らない場合は、無理に入れようとしないこと★ — ★★TeamWorks へ照会中の項目です(第9回 Q7-4)★★。

★入らなければ「グループなし」の検証は保留★とし、★そう記録すること★。 ★★FK制約を外すなどの回避をしないこと。★★

11-5. 完了ログ

scripts/seed-local-db.ts の末尾に groups=1 customers=1 ... と★数がハードコード★されています。 ★実際の件数を出すように直すこと★ — ★でないと拡張後に嘘のログが出ます。★


12. 段階9 — 記録

12-1. docs/decisions.md

★★採番の前に必ず実測すること★★:

grep -nE "^### D-(0|1)[0-9][0-9]" docs/decisions.md | tail -3

★番号順に並んでいません。★ 末尾を見て最大値を判断しないこと。

書くこと:

  • ★D-053 を解除したこと★ — ★★D-053 の見出しに [破棄] または [一部破棄] の印と行き先を書くこと★★ (D-024 / D-067 / D-135 と同じ作法。★これを忘れると、次の人が「絞り込みを出すな」と読みます★)
  • ★★確定期間KPIを「顧客が1社に定まるときだけ出す」とした決定と、却下した4案の理由★★(§2-1)
  • ★D-133 の安全弁を置き換えたこと★。★D-133 側にも印を付けること★
  • ★200件の壁を素通しした理由★(測れる見積もりが無い)
  • ★見出し語を lib/i18n.ts に集めた理由★(TeamWorks の回答で差し替えるため)
  • ★点検記録の画面が対象外であること★
  • ★group_id = 0 の投入が保留であること★

12-2. docs/handoff.md に追記

  • ★施設が200件を超えたら実行計画を測り直すこと★
  • ★TeamWorks の Q7-1 / Q7-4 / Q7-5 が未回答であること★と、★回答で何が変わるか(見出し語だけ)★
  • ★グループ単位のKPIは、傘下が2社以上になると422で止まること★ — ★group_summaries か「清掃実施回数」カラムが手に入れば合算できる(第9回 6章で照会中)★
  • ★点検記録の絞り込みは、画面が復活したときに足すこと★

13. ★実装者へ — 特に踏みやすい落とし穴★

# 落とし穴 どうなるか
★1★ ★facility_ids を ?.length で分岐する★ ★「0件ヒット」が「全件表示」に化ける。例外もテスト失敗も出ない★
★2★ ★施設名とグループの積集合が空のとき null を返す★ 同上
★3★ ★group_id = 0 を API に漏らす★ 「グループ0」という存在しないグループが画面に出る
★4★ ★contract を数値として比較する★ ★★全施設が「契約なし」になってメニューが全部消えるのに、例外もテスト失敗も出ない★★(D-080)
★5★ ★customers.contract を読む★ ★削除依頼済みの列★。facilities.contract が正
★6★ ★2社以上のとき静かに1社を選ぶ★ ★選び間違いに誰も気づけない★(D-133 が明確に否定)
★7★ ★parseInt の前方一致で 1abc を 1 にする★ ★過去に実際に踏んだ罠★(D-123)
★8★ ★既存テスト5本を消す★ 「無視する」実装に戻っても気づけない
★9★ ★fixtures 側を notImplemented にする★ ★USE_FIXTURES=true で画面が落ちる★
★10★ ★fixtures を1グループ1顧客のままにする★ 3階層のUIを fixtures で確かめられない
★11★ ★空の <select> を出す★ ★D-053 で消した「押しても何も起きないUI」に逆戻り★
★12★ ★D-053 / D-133 に印を付けない★ ★次の人が古い決定を読んで、実装を「壊れている」と判定する★
★13★ ★テストDBに書き込む★ ★★読み取り専用。絶対にしないこと★★
★14★ ★施設ごとにループでクエリを撃つ★ 施設数ぶんの往復が発生する

14. やらないこと(★この指示書の範囲外★)

項目 なぜ
★総清掃面積の新画面★ ★次の指示書★。この絞り込みに乗る
★グループ管理・顧客管理の画面★ ★docs/spec.md が「実装しない(D-Security側の画面を流用)」と定めている★
★点検記録の絞り込み★ ★画面自体が未実装★(D-083)
★複数顧客の合算★ ★4指標のうち2つを合算する材料がDBに無い★(第9回 6章で照会中)
★200件超の実行計画対策★ ★測れる見積もりが無い★(第9回 8章で照会中)
★group_summaries の新設★ ★TeamWorks の領域★。第9回 6章で照会中
★group_id = 0 を入れられるようにすること★ ★DDLの話。DFAは直せない★(第9回 Q7-4)
★スマートフォン幅のレイアウト★ 別課題