# T530 — judge-persist-reject-reasons-dedupe

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

## Goal

完成後系統應：判定結果寫入 Opportunity，`rejected` 亦入庫可查，`reasons[]` 不齊拒絕入庫，同貼文跨 watch 只留一筆。

## Depends on

- T525、T529

## Inputs

- Spec：`../spec.md` §3.2、§4.3.4、§8（去重）、§9.2（OP-02、OP-06、SW-06）

## Outputs

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

| 路徑 | 動作 | 說明 |
|------|------|------|
| `apps/backend/internal/module/radar/usecase/judge_persist.go` | add | 逐筆：去重檢查 → 判定 → 組 Opportunity → upsert → 累加 Sweep 計數 |
| `apps/backend/internal/module/radar/usecase/sweep_run.go` | edit | 串接抓取結果 → 判定入庫 |

### 行為變更

- 命中既有 `external_id`：只併入觸發 term，**不重跑判定、不重複扣點**。
- `rejected` 入庫且帶 `reject_reason`；預設不出現在今日頁（由 T545 的 filter 決定）。
- `reasons[]` 不足五條：該筆不入庫、記入 Sweep 的失敗計數與原因，不讓半成品資料汙染名單。
- 每筆判定完成即把 `external_id` 記入 `judged_external_ids`，供中斷續跑。

## Out of scope

- 截斷與計費（T531）
- 通知與續跑觸發（T532）

## Acceptance

- [x] OP-02：`provider_offer` → `rejected` 入庫、今日頁查詢不含它
- [x] OP-06：缺理由不入庫
- [x] SW-06：同貼文兩個 watch 命中 → 1 筆、兩個 term
- [x] 指令：

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