# Plan: 話題海巡品質、分數排序與批次分頁

> Status: `approved`
> Source: `docs/product/scout-topic-quality/spec.md`
> Last updated: `2026-08-10`

## 1. 交付策略

先以自動化案例鎖住現有的話題產詞、網址正規化與其他 Scout 模式，避免新工作覆蓋目前工作樹中的有效修正。接著遵守 Phase B → Phase C 順序，先讓前端與 mock 具備獨立 run、雙層分頁及貼文時間語意，再由 goctl 產生後端契約，逐層完成 run persistence、搜尋管線與 worker 狀態。

後端不在原始候選階段截斷，而是每輪完成 canonical 去重、歷史去重與關聯性判定後才計算 target。完成結果以 run 為不可混同的邊界，由後端固定 score 排序並分頁；前端不再載入全量 posts 自行組批次。最後接上 live repository，使用同一組 mock/live 契約案例、品質題組及全專案測試完成驗收。

實作期間保留目前未提交的 Scout 改動，不回復、不覆寫與本功能無關的使用者變更。所有後端 HTTP 變更一律先改 `generate/api/*.api` 再執行 `make gen-api`；生成的 handler／routes 僅由 goctl 維護，業務實作只落在 `internal/logic/**` 與 `internal/module/**`。

## 2. 里程碑

### M0 — 基線與可重現品質題組

- **目標：**把目前已存在的產詞、關聯性與 shortcode 去重行為固定成回歸基線，並建立本次規格所需的代表題組與分頁 fixtures。
- **完成定義：**
  - 現有「鬼滅之刃」、「外包／後端工程師」與網址 alias 案例持續通過。
  - 加入至少 30 組人工標註題組的資料格式與首批固定 fixtures；題組能標出必要概念群、直接相關與離題候選。
  - 建立 score／同分時間排序 fixtures，涵蓋已知時間、未知時間、相同時間與相同貼文網址別名。
  - 記錄開始實作前的 backend Scout 測試、web 單元測試與 build 結果；若既有失敗，與本工作造成的回歸分開列示。
- **建議 task 區間：** T001–T004
- **驗證：**

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

  cd ../web
  npm test -- src/lib/threadsTerm.test.ts
  npm run build
  ```

### M1 — Phase B：前端 repository 契約、mock 與操作行為

- **目標：**先在 mock 模式完成使用者能驗收的 run 清單、批次結果、時間顯示與獨立分頁，不依賴尚未存在的 live API。
- **完成定義：**
  - 前端 domain 定義 `ScoutRun`、不足原因與通用 page response；`ScoutPost` 增加 run ID。
  - Scout repository 增加 `listRuns`、`listRunPosts`、`removeRun`，mock 與 live 介面形狀一致；舊 `listPosts` 暫時保留。
  - mock 每次執行建立新 run；相同 theme 的兩次執行不合併、不搬動舊結果。
  - Scout 頁面先由新 repository 讀取批次與單批結果，不再使用全量 `listPosts`＋`runGroups`＋`pageSlice` 作主路徑。
  - 批次 pager 預設 10、結果 pager 預設 8；兩個頁碼獨立，切換批次只重設結果頁。
  - 結果列與明細顯示貼文時間／未知，掃入時間另行標示；score 主排序，pending 不影響顯示順序。
  - 新批次完成只顯示提示，不自動切換目前選取或頁碼；刪除後頁碼收斂到有效頁。
- **建議 task 區間：** T010–T018
- **驗證：**

  ```bash
  cd apps/web
  npm test -- src/pages/ScoutPage.test.tsx
  npm test -- src/data
  npm run lint
  npm run build
  ```

### M2 — Phase C 契約地基：goctl API 與 domain

- **目標：**依已批准 Spec 建立 run、分頁結果與 scan response 契約，並產生可編譯的 go-zero 程式骨架。
- **完成定義：**
  - 在 Scout API 定義 `ScoutRunPublic`、run list、run posts page、run delete、pagination 與 `run_id`。
  - scan response 同時含 job 與新建 run；舊 posts/theme 端點保留相容。
  - domain 加入 Run、狀態、計數、不足原因與 guarded transition 所需輸入；所有時間為 UTC unix nanoseconds。
  - API 經 `make gen-api` 生成；不手寫 handler 或 routes。
  - 生成後 gateway、worker 及既有 Scout logic 可編譯，新增 logic 尚未實作時不得回成功空資料。
- **建議 task 區間：** T020–T025
- **驗證：**

  ```bash
  cd apps/backend
  make gen-api
  go test ./internal/logic/scout/... ./internal/module/scout/... -count=1
  go build .
  go build ./cmd/worker
  ```

### M3 — Run persistence、穩定查詢與 legacy 相容

- **目標：**讓 memory 與 Mongo repository 都能保存獨立 run、穩定分頁結果及完整批次統計。
- **完成定義：**
  - repository 支援建立 run、guarded 更新狀態／統計、owner-scoped run 分頁、單 run posts 分頁及終態 run 刪除。
  - run 排序固定為 `created_at DESC, id DESC`。
  - run posts 依 `score DESC`；同分已知時間優先，再依 `posted_at DESC, created_at DESC, id DESC`。
  - owner＋canonical identity 維持唯一；新掃描遇到歷史貼文不覆寫其 run、時間或 outreach 狀態。
  - 批次 total、eligible 與 pending 從完整批次維護，不從目前頁推算。
  - legacy posts 以穩定 synthetic run 呈現、分頁及整組刪除；不虛構無法還原的多次執行。
  - Mongo 查詢具備符合 owner、run、時間與唯一性的索引；memory 與 Mongo contract tests 使用相同案例。
- **建議 task 區間：** T030–T037
- **驗證：**

  ```bash
  cd apps/backend
  go test ./internal/module/scout/repository/... ./internal/module/scout/usecase/... -count=1
  go test ./internal/logic/scout/... -count=1
  ```

### M4 — 合格數驅動的搜尋、關聯性與原子發佈

- **目標：**把「找得夠多」改成以合格結果計數，同時維持關聯性硬門檻與可診斷的不足原因。
- **完成定義：**
  - 候選管線拆成可測的 normalize → canonical dedupe → historical dedupe → relevance/classification → eligible 階段。
  - 話題簽章從原始 intent 與批准搜尋詞建立；泛用錨點不算主題證據，必要實體及多概念群不能被廣義詞繞過。
  - target 在每階段過濾後計算；不得在 raw hit 數達標時提前 cap。
  - 依 initial fan-out、同源加量、單一批准詞的有限 intent 擴展、既有次來源補量；達標立即停止，最多檢視 320 個原始候選。
  - 計算 searched、duplicate、irrelevant、eligible、shortfall 與原因；單一來源失敗可降級，全來源失敗則整批失敗。
  - 所有模式結果依 score 排序；posted_at 僅作同分 tie-breaker，軟時效只降分、不淘汰。
  - 結果先暫存為該 run 不可見集合，完整成功後才發佈；失敗 run 不暴露半套 results。
- **建議 task 區間：** T040–T049
- **驗證：**

  ```bash
  cd apps/backend
  go test ./internal/module/scout/usecase/... -count=1
  go test ./internal/module/scout/... -run 'Target|Relevance|Dedupe|PostedAt|Run' -count=1
  ```

### M4 補強 — 真實稽核後的召回與去重修正

首輪品質 gate 通過後，針對「實際結果仍偏少、單一核命中不夠貼題」再做一次資料流稽核：

- 候選只有通過完整關聯性與分類門檻後才佔用 local identity；低品質的先到摘要不會鎖死後續較完整摘要。
- 自然語句意圖先抽出真正主題核，不把產生器的所有變體（外包／接案／徵人等）錯誤合併成同時必要條件。
- activity 只有泛用錨點（例如「推薦」）時全部拒絕，不再把錨點當成主題證據。
- 多概念批准詞的 token 不會被誤當成另一個核心的 alias；只有單一批准詞才能明確授予短 alias。
- API provider 沒有 offset 時，單一批准詞在同源加量仍重複，會依原始 intent 追加最多四組短查詢；每筆仍經同一個 topic signature gate。
- 批次清單顯示 `created_at`，讓跨頁批次辨識不依賴狀態或結果數。

這些補強不降低關聯性門檻，只改善被錯誤去重與單一 query 第一頁限制造成的召回損失。

### M5 — Job／worker 生命週期與進度

- **目標：**讓 run 與 `scout_scan` job 一對一、狀態 guarded 且失敗可收斂。
- **完成定義：**
  - POST scan 先建立 run，再排程 job；job `RefID` 與 immutable payload 都帶同一 run ID。
  - 排程失敗時 run guarded 轉 failed；不得留下永遠 queued 的批次。
  - worker 驗證 RefID／payload／owner 一致後才執行，依序 guarded 更新 running、succeeded／failed／cancelled。
  - 進度摘要包含搜尋階段與目前 eligible／target，不記錄 token 或敏感 payload。
  - job 更新沿用既有 guarded 語意，Redis lock value 維持 workerID。
  - retry 或重複 claim 不會重複發佈結果、搬動舊貼文或讓終態倒退。
- **建議 task 區間：** T050–T055
- **驗證：**

  ```bash
  cd apps/backend
  go test ./internal/module/job/... ./internal/module/scout/... -count=1
  go test ./cmd/worker -count=1
  make test-integration
  ```

### M6 — Live repository 串接與前後端整合

- **目標：**讓 Phase B 已驗收的操作改用真 API，mock 與 live 保持同一契約。
- **完成定義：**
  - live repository 正確送出 `page`／`pageSize`、run ID 與 filters，映射 `pagination`、run counters、shortfall reasons、posted_at 與 created_at。
  - Scout 主頁只取得目前 run page 與目前 result page；不再因 job revision 下載全部 posts。
  - 其他仍依賴舊 `listPosts` 的頁面維持相容並通過測試。
  - 401／404／409／503 使用既有錯誤顯示，不將失敗轉成成功空列表。
  - live contract tests 驗證 URL、query、envelope mapping 與 unix ns 欄位；前端不以 JavaScript number 精度重新排序 ns 時間。
- **建議 task 區間：** T060–T066
- **驗證：**

  ```bash
  cd apps/web
  npm test -- src/data/live/m5Repos.test.ts src/pages/ScoutPage.test.tsx
  npm run lint
  npm run build
  ```

### M7 — 品質門檻、全域回歸與文件收尾

- **目標：**以需求成功標準證明「相關、足量、按時間、可分頁、不重複」均成立。
- **完成定義：**
  - 30 組人工標註題組的 precision@10 平均至少 0.8；有足量合格候選的題組至少 90% 達成 target。
  - 規格中的 17 個 Given／When／Then 場景都有自動化證據，或明確對應的人工驗收記錄。
  - backend 全測試、integration gate、web 全測試、lint 與 build 通過。
  - 檢查 goctl 生成差異，確認沒有手寫 handler／routes，也沒有改動 `old/`。
  - 更新相關產品文件中的現況描述，tasks INDEX 全部反映實際狀態；不把尚未達標項目標成 done。
- **建議 task 區間：** T070–T075
- **驗證：**

  ```bash
  cd apps/backend
  make test
  make test-integration
  make build

  cd ../web
  npm test
  npm run lint
  npm run build

  cd ../..
  git diff --check
  git status --short
  ```

## 3. 依賴

```text
M0 → M1 → M2 → M3 → M4 → M5 → M6 → M7
           └──────────────→ M6
```

- M0 先建立保護網，所有後續里程碑都依賴它。
- M1 先固定 Phase B 操作與 mock 契約；M2 的 API 形狀必須與其一致。
- M3 提供 M4 的歷史去重、暫存／發佈與分頁能力。
- M4 完成後，M5 才能正確把搜尋統計與終態寫回 run。
- M6 同時依賴 M2 的 API 契約與 M5 的完整 live 行為。
- M7 只能在所有前後端路徑接通後開始宣告成功標準。

## 4. 風險與緩解

| 風險 | 緩解 |
|------|------|
| 目前工作樹已有未提交的 Scout 修正，生成或重構時可能被覆蓋 | M0 先記錄基線；每個 task 只碰輸出範圍，執行 goctl 後逐檔檢查差異，不回復使用者既有變更。 |
| `theme_key` 曾同時扮演話題與批次，舊資料無法還原每次執行 | 新資料強制 run ID；舊資料只建立一個穩定 synthetic run，不虛構歷史。 |
| owner＋canonical identity 唯一規則可能與既有 permalink ID／upsert 行為衝突 | persistence contract tests 先覆蓋「新 run 不覆寫舊 post」；在搜尋階段查 seen identity，保存端再以唯一約束防競爭。 |
| 多文件結果寫入中途失敗會暴露半套批次 | 以 run 可見狀態隔離暫存結果；列表只讀成功發佈的 membership，失敗路徑清理或保留不可見資料供重試。 |
| offset pagination 遇到背景新增資料會漂移 | 完成 run 的 membership 與排序鍵固定；批次列表不自動刷新，提示使用者主動回到最新頁。 |
| unix nanoseconds 超過 JavaScript safe integer，前端重排可能不穩 | score／同分時間排序在後端完成，使用唯一 ID 決勝；前端只格式化與顯示，不再按 ns 數值重排分頁結果。 |
| 高關聯門檻使窄題目無法湊足 target | 三階段有限補量並呈現 shortfall；品質門檻優先，不降低核心概念要求。 |
| 人工題組過度貼合既有範例 | 30 組涵蓋不同領域並分離固定回歸 fixture 與驗收 fixture，避免只對兩個已知查詢最佳化。 |
| 新 run API 破壞 Today 或其他舊呼叫者 | 保留舊 posts endpoint 與 repository 方法直到相依頁面另有批准的遷移計畫；M6 明確跑全前端測試。 |
| goctl 生成大量無關差異 | 固定使用專案內 goctl home 與 make target；生成後只接受契約導致的差異，業務不寫 handler／routes。 |

## 5. 刻意不做（本 plan 週期內）

- 不改造 Radar 商機判定、CRM 狀態或 Radar 列表。
- 不新增第三個外部搜尋／爬取來源。
- 不降低主題關聯性門檻來湊 target，也不做無限補抓。
- 不自動發送 Threads 回覆。
- 不刪除所有歷史 Mongo 重複資料；只確保讀取與新寫入不再重複呈現。
- 不移除舊 `GET /scout/posts` 或強迫其他頁面同時遷移。
- 不重新設計 Harbor Desk 全站視覺或導覽。
- 不在本週期加入個人化學習、互動量趨勢或跨批次分析。

## 6. 全域驗證指令

```bash
cd apps/backend
make gen-api
make test
make test-integration
make build

cd ../web
npm test
npm run lint
npm run build

cd ../..
git diff --check
git status --short
```

需要 Mongo 行為的 repository contract 以可控 test database／既有測試容器執行；外部搜尋來源一律使用 fake provider，不以真實 provider token 或不穩定網路作為 CI 成功條件。

## 7. 批准

- [x] Plan 已批准：2026-08-09／使用者
