コンテンツにスキップ

0008. 配車コースモデル (courses / stops / assignments + 生成 RPC)

コンテキストと課題

処理業者の配車は「毎週・第 N 週の特定曜日に、決まった回収先群を、担当ドライバーが回る」という 繰り返しパターンが中心。これを毎日手で組むのは非効率なので、繰り返し定義から その日の 配車 (ドライバー列) を自動生成したい。一方で、当日に手で差し込む回収先 (繰り返し設定だけ持つ 回収先) も「未割当プール」として扱えるようにしたい。

決定

配車を コース (course)割当 (assignment) に分けてモデル化し、生成 RPC で日次の dispatch_stops を materialize する。回収先は collection_site_id を主キーとして扱う。

テーブル (migration 40)

テーブル 役割 主な列
dispatch_courses コース (回収先の順序付き束) name(tenant 内一意) / active
dispatch_course_stops コース内の停車地 course_id / collection_site_id / stop_order
dispatch_course_assignments 「曜日 × 週序数」へのコース割当 course_id / driver_id / weekday / week_ordinal
  • 曜日 (weekday): 0=日 .. 6=土 (extract(dow) / JS getDay() と一致、check (weekday between 0 and 6))。
  • 週序数 (week_ordinal): dispatch_course_assignments('every','1','2','3','4','last')第 5 週 '5' は持たない点に注意 (後述の回収先側とは異なる)。

生成 RPC (migration 45)

generate_dispatch_stops_from_courses(p_date date, p_force boolean default false) returns integer
  • security defineradmin / normal のみ実行可。当日の dow / 第 N 週 / 最終週を計算する。
  • 発火する割当を展開: weekday = dow かつ (week_ordinal = 'every' または = 第N週 または 'last' かつ最終週) を満たす割当の、active コースの停車地を集める。
  • (driver, collection_site) の重複は (コース名, コース内 stop_order) 最小の 1 件へ畳み、 ドライバー毎に 0 始まりの stop_order を採番して dispatch_stops に挿入する。
  • p_force の意味:
    • false (既定) … 冪等。当日の stops が既にあれば 0 を返し二重生成しない。
    • true … 当日 (tenant, date) の dispatch_stops先に削除してから現在のコース割当で 作り直す (手動 D&D の編集は破棄)。配車ボードの「再生成」が使う。

フロントは supabase.rpc('generate_dispatch_stops_from_courses', { p_date, p_force }) で呼ぶ (useGenerateDispatchStopsFromCourses)。

未割当プールは「回収先の繰り返し」駆動 (別経路)

  • 回収先 collection_sites (migration 36) と、その繰り返し collection_site_schedules を持つ。 collection_site_schedules.week_ordinal('every','1','2','3','4','5','last') で、 第 5 週 '5' を含む (コース割当側と非対称)。
  • 未割当プール = その日に collection_site_schedules で該当する active な回収先のうち、まだ ドライバー列に配置されていないもの (computePoolSites / isSiteDueOnDate)。 コース生成 (generate_dispatch_stops_from_courses) はドライバー列の事前生成のみを担い、 プールの源泉ではない。両者は独立した経路。

回収先への re-key (migration 37)

配車は当初 partner_id を持っていたが、migration 37 で collection_site_id に張り替えた (回収先ごとに既定の collection_site をバックフィルし、曜日繰り返しを collection_site_schedules へ移行)。dispatch_stopspartner_id を落とし、collection_site_id を not null + FK 化、 一意制約も (tenant, date, driver, collection_site) に更新。

影響 (Consequences)

良い点

  • 繰り返し定義 (コース割当) から日次配車を 冪等に自動生成 でき、p_force で「割当変更を後から 反映する再生成」もできる。
  • 「ドライバー列 = コース割当駆動」「プール = 回収先の繰り返し駆動」と源泉を分けたことで、 当日の手差し込み (プール) と定常配車 (コース) を別々に運用できる。
  • 重複畳み込みと per-driver 採番が RPC 内で完結し、フロントは結果を受け取るだけでよい。

トレードオフ / 注意点

  • 週序数の非対称性: コース割当は '5' を持たず、回収先繰り返しは '5' を持つ。型も COURSE_WEEK_ORDINALS (6 値) と WEEK_ORDINALS (7 値) に分かれており、取り違え注意。
  • p_force = true は手動編集を破棄する。配車ボードの D&D 編集後に再生成すると当日の手作業が 消えるため、UI 側で「未割当状態のリセット」を伴う。
  • ドライバー列とプールが別経路のため、「コース専用 (繰り返し非該当) の回収先」を外したときに プールへ戻す等、両経路を突き合わせる補正ロジックがフロントに必要。
  • 生成は当日単位の materialize なので、過去/未来日を見るには各日について生成/参照する設計。

根拠 (典拠)

  • supabase/migrations/00000000000040_dispatch_courses.sql:16-17,27-49,69-110,115-159 — 曜日の意味、3 テーブル定義、dca_weekday_range/dca_week_ordinal_check ('every','1','2','3','4','last')/dca_unique
  • supabase/migrations/00000000000045_dispatch_stops_from_courses_force.sql:1-27,33-110p_force 設計コメント、RPC 本体 (security definer、admin|normal、発火条件、重複畳み込み、per-driver 採番)。
  • supabase/migrations/00000000000038_generate_dispatch_stops_recurrence.sql / 00000000000026_generate_dispatch_stops.sql / 00000000000024_dispatch_route_templates.sql / 00000000000025_dispatch_stops.sql — 旧 generate_dispatch_stops(route templates + collection_site_schedules) と前身テーブル。
  • supabase/migrations/00000000000036_collection_sites.sql:2-12,51-62collection_sitescollection_site_schedules(week_ordinal'5' を含む)。
  • supabase/migrations/00000000000037_dispatch_rekey_collection_site.sql:1-8,56-64 — partner_id → collection_site_id への re-key と dispatch_stops の制約更新。
  • supabase/migrations/00000000000041_dispatch_write_admin_normal.sql / 00000000000042_dispatch_stops_from_courses_renumber.sql — 書込 admin|normal 化と per-driver 採番。
  • apps/web/src/lib/dispatch-pool.ts:1-42 / apps/web/src/pages/collector/dispatch/DispatchBoardPage.tsx:215-224,453-471 — プールは collection_site_schedules 駆動 (コース生成はプールの源泉ではない)、force:true の再生成。
  • packages/shared/src/schemas/dispatch-course.ts:10-16,77-86 / apps/web/src/lib/recurrence.ts:3-13,76-87COURSE_WEEK_ORDINALS(6 値) vs WEEK_ORDINALS(7 値) と曜日算出。