thread-master/docs/product/demand-radar/tasks/T525-opportunity-model-repo...

62 lines
2.3 KiB
Markdown
Raw Normal View History

2026-08-03 05:52:02 +00:00
# T525 — opportunity-model-repository
> Status: `done`
> Milestone: `M2`
> Kind: `feat`
> Est. change: `~190 lines`
## Goal
完成後系統應Opportunity domain 與 repository 就位,狀態機依 spec §3.2,同 owner 同 `external_id` 去重且可記錄多個觸發 term。
## Depends on
- T501
## Inputs
- Spec`../spec.md` §3.2、§3.3、§8、§9.2OP-06、SW-06
- 既有 module 模式:`apps/backend/internal/module/scout/**`
## Outputs
### 程式變更(預期路徑)
| 路徑 | 動作 | 說明 |
|------|------|------|
| `apps/backend/internal/module/radar/domain/opportunity.go` | add | structstatus 轉移+`intent_band` 由 score 推導8050`reasons[]` 五維度驗證 |
| `apps/backend/internal/module/radar/repository/opportunity_mongo.go` | add | `UpsertByExternalID`、`ListByOwner`bandstatuswatch日期 filter、`CountToday`、`Get`、`UpdateStatus`、`SetOverride` |
| `apps/backend/internal/module/radar/repository/opportunity_memory.go` | add | 測試用 |
### 行為變更
- `reasons[]` 不足五維度時 repository **拒絕寫入**回明確錯誤——OP-06。
- `UpsertByExternalID`:命中既有則只把新的觸發 term 併入 `matched_terms[]`,不重複建立、不重跑判定。
- `region_match` 為三態 `match|mismatch|unknown``status` 含 `judging|qualified|rejected|accepted|dismissed`
- `override``{from_band, to_band, from_status, to_status, actor_uid, at}`
## Out of scope
- 判定演算法T529與寫入流程T530
- HTTP logicT545T546T547
## Acceptance
- [x] unit test缺一條 reason 被拒;同 `external_id` 兩次 upsert 只有一筆且 `matched_terms` 有兩個
- [x] score 79→`mid`、80→`high`、49→`low`
- [x] 指令:
```bash
cd apps/backend && go test ./internal/module/radar/... -count=1
```
## Notes
band 門檻 8050 為 spec 鎖定值,**不可**在此 task 調整。
### 實作落點
- `domain/opportunity.go`status 轉移、`BandFromScore`8050、`ValidateReasons` 五維度、`UTCDayBounds`
- `repository/opportunity_{memory,mongo}.go``UpsertByExternalID` 命中只併 `matched_terms``CountToday``ListOpportunities``SetOpportunityOverride`
- 測試:`domain/opportunity_test.go`、`repository/opportunity_memory_test.go`