# T527 — job-radar-sweep-template-daily-schedule

> Status: `done`  
> Milestone: `M2`  
> Kind: `feat`  
> Est. change: `~190 lines`

## Goal

完成後系統應：每日 **UTC 22:00** 為每個 active watch 建立一筆 `radar_sweep` Job，多台 worker 只有一台建立與執行。

## Depends on

- T512、T526

## Inputs

- Spec：`../spec.md` §4.2.3、§5.4
- 既有：`apps/backend/internal/module/job/domain/job.go`（template 常數）、`internal/module/job/usecase/service.go`、`cmd/worker/main.go`（maintenance tick＋`redislock`，lock value = `workerID`）

## Outputs

### 程式變更（預期路徑）

| 路徑 | 動作 | 說明 |
|------|------|------|
| `apps/backend/internal/module/job/domain/job.go` | edit | 新增 `TemplateRadarSweep = "radar_sweep"` |
| `apps/backend/internal/module/job/usecase/service.go` | edit | `ScheduleRadarSweep(owner, watchID, runAt)`；guarded 狀態更新沿用 |
| `apps/backend/internal/module/radar/usecase/sweep_schedule.go` | add | 每日排程 tick：判斷是否已過當日 UTC 22:00 且未建立過，逐一 active watch 建 Job |
| `apps/backend/cmd/worker/main.go` | edit | `case jobDomain.TemplateRadarSweep` 分派；maintenance 內呼叫每日排程 tick |

### 行為變更

- 同一 UTC 日、同一 watch 只建立一筆排程 Job（以當日標記或 Job 唯一性防重）。
- 排程與執行皆在既有 Redis lock 保護下，lock value = `workerID`；未取得鎖者跳過不報錯。
- 手動觸發（T533）走**同一條執行路徑**，不另寫一份。

## Out of scope

- 抓取實作（T528）、判定（T529／T530）
- 個人化時區（P2，不做）

## Acceptance

- [x] SW-01：2 個 active watch → 排程後各一筆 `radar_sweep` Job
- [x] 兩個 worker 實例同時跑，Job 不重複建立、不重複執行（RefID=`watchID:YYYY-MM-DD` 去重＋maintenance Redis lock；並行 schedule 測試）
- [x] 指令：

```bash
cd apps/backend && go test ./internal/module/radar/... ./internal/module/job/... -count=1 && make build
```

## Notes

排程時間為固定 UTC 22:00（台北 06:00），使用者早晨打開就有名單。

### 實作落點

- `job/domain`：`TemplateRadarSweep`
- `job/usecase`：`ScheduleRadarSweep`＋`RadarSweepPayload`（同 watch+日去重）
- `radar/usecase/sweep_schedule.go`：`ScheduleDailySweeps`（UTC 22:00 閘）、`BeginSweepRecord`
- `radar` repo：`ListAllActiveWatches`
- `cmd/worker`：template 分派＋maintenance tick 呼叫排程；執行體暫明確 fail「fetch not wired (T528)」並寫 Sweep 紀錄
