# 智能宿位分配設計

> 狀態：已與使用者確認（2026-08-26）

## 目標

列出未分配宿舍的工人，依店鋪地址與宿舍地址，用 OpenStreetMap（Nominatim + OSRM）預先計算距離，建議最多 3 間合規宿舍（容量＋性別），可切換行車／步行排序，並可一鍵套用或選其中一間。

## 已確認規則

1. 未分配工人＝`workers.dormitory_id` 為空。
2. 建議依店鋪地址 vs 宿舍地址；同時顯示行車（OSRM driving）與步行（OSRM foot）。
3. 每位工人顯示前 3 間；頁面可切換「按行車／按步行」排序。
4. 可看建議，也可一鍵套用最佳或選某一間。
5. 合規：剩餘容量 > 0；性別為 `不限` 或與工人一致。
6. 缺店鋪／地址／快取時仍列出工人，標明原因，不給距離建議。
7. 不開頁即時打 API；預存座標與路線。更新時機：按「更新距離資料」，或新增／儲存店鋪、宿舍且地址變更時。

## 架構（方案 A）

### Schema

**`branches` / `dormitories` 新增**
- `lat` decimal nullable
- `lng` decimal nullable
- `geocoded_at` timestamp nullable

**`location_route_caches`**
- `id`
- `branch_id` FK
- `dormitory_id` FK
- `driving_distance_m` unsigned int nullable
- `driving_duration_s` unsigned int nullable
- `walking_distance_m` unsigned int nullable
- `walking_duration_s` unsigned int nullable
- `computed_at` timestamp nullable
- unique(`branch_id`, `dormitory_id`)

### Services

- `GeocodingService` — Nominatim（`countrycodes=hk`，合理 User-Agent）
- `RoutingService` — OSRM public API，`driving` + `foot`
- `LocationCacheService` — 地理編碼缺失點；重算全部或與某 branch/dorm 相關的快取
- `DormAssignmentService` — 合規宿舍、讀快取、排序、取前 3、套用分配（再驗容量／性別）

### Controllers / UI

- `SmartDormAssignmentController`
  - `index` — 列表＋建議
  - `refresh` — 全量更新距離資料
  - `assign` — 套用指定宿舍（或最佳）
- 側邊欄「智能宿位分配」
- 店鋪／宿舍 store／update：地址變更時觸發該實體相關快取更新（queue 或同步；首版可同步並注意 Nominatim 1 req/s）

### 外部 API（開源免費）

- 地理編碼（優先）：香港政府 ALS `https://www.als.gov.hk/lookup`（中文地址最準）
- 地理編碼（備援）：Photon / Nominatim（公開 Nominatim 常被 403）
- 路線：OSRM via `https://routing.openstreetmap.de/routed-{car|foot}/...`（公開 `project-osrm.org` 常被擋）

注意公開服務的使用政策與限速；全量更新應節流（sleep／間隔）。

## 非目標（YAGNI）

- 不做自動排程夜間更新（有手動＋CRUD 觸發即可）
- 不引入付費地圖（Google／Mapbox）
- 不做地圖視覺化（首版表格即可）
- 不強制工人必須有店鋪才出現在清單
