453 lines
32 KiB
Markdown
453 lines
32 KiB
Markdown
# Spec: 商機雷達+成交 CRM
|
||
|
||
> Status: `approved`
|
||
> Status note: 使用者 2026-07-31「好」(spec 通過)
|
||
> Source: `docs/product/demand-radar/requirements.md`(**approved** 2026-07-31)
|
||
> Last updated: `2026-07-31`
|
||
> 底座:`docs/product/haixun-backend/spec.md`(海巡雙路徑、Outbox、用量 BYOK)、`docs/product/growth-loop/spec.md`(OutcomeEvent 歸因、AccountHealth 送出閘);本 run 為**加值層**
|
||
> 決策對齊:requirements §6 命名決策、§7 決策 #1–#12
|
||
|
||
## 1. 摘要
|
||
|
||
本規格定義「服務者每天收到一份『正在找你的人』名單,並追到成交」的**對外行為契約**。
|
||
|
||
**P0:** 服務檔案 → 雷達訂閱與每日自動巡 → 商機五問判定與意向分數 → 今日商機頁 → AI 回覆五版本 → 聯絡人與 CRM 七階段(+待追蹤標記)→ 追蹤提醒 → 轉換統計 → 方案配額掛載。
|
||
**P1:** 手動匯入商機、報價與成交金額、價格帶校準。
|
||
**P2(只鎖邊界):** LINE 官方帳號、Google 商家評論、網站表單、自有留言私訊收斂、空檔媒合。
|
||
**不做:** 重做海巡抓取與發文主流程、重做付費流程、重量級 CRM、自動判定成交。
|
||
|
||
## 2. 名詞與實體
|
||
|
||
| 名詞 | 定義 |
|
||
|------|------|
|
||
| **ServiceProfile** | 服務檔案:服務項目、價格區間、代表案例、**不能說的內容**、常見問題、服務地區、可接案空檔。判定與回覆生成的共同輸入 |
|
||
| **RadarWatch** | 雷達訂閱:常駐關鍵字監控設定(關鍵字、地區條件、排除詞、啟用狀態) |
|
||
| **RadarSweep** | 一次每日巡的執行紀錄:對應一個 Job,記錄掃描詞、命中數、判定數、成本與截斷情形 |
|
||
| **Opportunity** | 商機:一筆經五問判定、值得跟進的需求訊號,帶 `intent_score`、`intent_band`、判定理由 |
|
||
| **ReplyVariant** | 商機下的一個回覆版本(公開留言/私訊/不帶銷售感/專業/幽默) |
|
||
| **Contact** | 聯絡人:跨貼文聚合的人,鍵為(來源平台+作者帳號+擁有者) |
|
||
| **ContactTouch** | 聯絡人時間軸上的一則接觸紀錄(狀態變更、送出回覆、備註、成交回報) |
|
||
| **FollowUp** | 追蹤提醒:到期未回應時進站內通知,可產生 AI 追蹤訊息 |
|
||
| **五問** | 需求真實性、購買意圖、地區符合、需求時效、服務匹配度 |
|
||
|
||
**沿用底座(不重定義):** Member、ThreadsAccount、ScoutPost/`outreach_status`、Brand/Product、OutboxBundle/Step、Persona、Usage/Plan(Free/Starter/Pro)、Job、Notification、OutcomeEvent/AttributionLink、AccountHealth。
|
||
|
||
**命名紅線:** 全域禁用 `lead` 指稱銷售線索(既有語意為 ThreadPlay 主帳號)。
|
||
|
||
## 3. 狀態機
|
||
|
||
### 3.1 RadarWatch
|
||
|
||
```text
|
||
active --> paused : 使用者暫停(不再排入每日巡)
|
||
paused --> active : 恢復
|
||
active|paused --> archived : 軟刪;歷史商機保留
|
||
```
|
||
|
||
- `archived` 的 watch 不再產生新商機,但既有商機與統計**不刪除**。
|
||
- 超過方案 watch 上限時**禁止**新增 `active`(明確錯誤),既有超額者維持不變不強制降級。
|
||
|
||
### 3.2 Opportunity(判定與收單)
|
||
|
||
```text
|
||
(sweep 命中) --> judging : 進入五問判定
|
||
judging --> qualified : 通過硬否決,得 intent_score 與 band
|
||
judging --> rejected : 硬否決(非真實需求/同業叫賣/逾期/地區明確不符且不可遠端)
|
||
qualified --> accepted : 使用者「加入名單」→ 綁定或建立 Contact
|
||
qualified --> dismissed : 使用者略過
|
||
rejected --> qualified : 使用者手動覆寫(記錄覆寫)
|
||
accepted --> (CRM 由 Contact 接手)
|
||
```
|
||
|
||
- **硬否決條件(不計分直接 `rejected`):** 五問第一問「是不是真實需求」為否;既有分類為 `provider_offer`/`announcement`/`noise`;貼文發布距今 > 14 天。
|
||
- `rejected` **仍入庫可查**(避免誤殺無法回溯),但預設不出現在今日商機頁。
|
||
- 使用者覆寫 `intent_band` 或把 `rejected` 拉回 `qualified` 時,寫入 `override`(原值、新值、時間、操作者)——這是後續校準判定的素材。
|
||
|
||
### 3.3 意向分數與分級
|
||
|
||
```text
|
||
intent_score = 需求真實性 30 + 購買意圖 30 + 地區符合 15 + 需求時效 15 + 服務匹配度 10
|
||
band: high >= 80 | mid 50–79 | low < 50
|
||
```
|
||
|
||
- 每個維度必須輸出**分數與人話理由**;`reasons[]` 缺任一維度不得入庫。
|
||
- 地區三態:`match`/`mismatch`/`unknown`。`unknown` 得部分分(不等同 `mismatch`),且**禁止猜測縣市**。ServiceProfile 標記可遠端服務時,`mismatch` 不觸發硬否決且地區維度給滿分。
|
||
- 時效:距今 ≤ 24h 給滿分,24–72h 遞減,72h–14 天低分,> 14 天硬否決。
|
||
|
||
### 3.4 Contact CRM 階段
|
||
|
||
七個互斥階段 + 一個並存標記,畫面上呈現為八格(requirements 決策 #5)。
|
||
|
||
```text
|
||
stage: new_found --> engaged --> dm_sent --> replied --> quoted --> won | lost
|
||
```
|
||
|
||
- **允許任意跳轉**(含回退、跳級);每次轉移寫一筆 `ContactTouch`(來源階段、目標階段、時間、操作者)。
|
||
- `won`/`lost` 為終態但**可重新開啟**(轉回任一階段,同樣留痕)。
|
||
- `needs_follow_up` 是 **boolean 標記**,可與任何階段並存,非階段值。
|
||
- `won` 必須由人確認;**系統永不自動判定成交**。
|
||
|
||
### 3.5 FollowUp
|
||
|
||
```text
|
||
scheduled --> notified : 到期 → 站內通知
|
||
notified --> done : 使用者處理(回覆/推進階段/手動關閉)
|
||
notified --> snoozed : 延後 N 天 → 回到 scheduled
|
||
notified --> escalated : 已通知達上限次數 → 建議轉 lost(僅建議,需人確認)
|
||
```
|
||
|
||
- 預設:無回應 **3 天**觸發,最多通知 **2** 次,天數可由使用者調整。
|
||
- `escalated` **不自動改階段**、不自動關閉。
|
||
|
||
## 4. 使用者流程(行為)
|
||
|
||
### 4.1 服務檔案(P0)
|
||
|
||
1. 使用者填寫:`services[]`(項目+價格區間)、`cases[]`、`forbidden[]`(不能說的內容)、`faq[]`、`service_areas[]`(縣市代碼;可勾「可遠端」)、`availability`(可接案空檔文字)、`tone_note`(語氣備註)。
|
||
2. 服務檔案**每會員一份**(非多份),可隨時修改;修改不回溯既有商機的判定結果。
|
||
3. 未建立服務檔案時:**禁止**建立 `active` 的 RadarWatch,並在雷達頁引導先建檔(避免判定缺乏依據而產生垃圾名單)。
|
||
4. `forbidden[]` 為回覆生成的**硬性過濾**(§4.5)。
|
||
|
||
### 4.2 雷達訂閱與每日自動巡(P0)
|
||
|
||
1. 使用者建立 RadarWatch:`terms[]`(關鍵字)、`exclude_terms[]`、`regions[]`(可留空=沿用服務檔案)、`enabled`。
|
||
2. 系統可依服務檔案**建議關鍵字**(`POST /api/v1/radar/watches/suggest`),使用者可全採用或逐條增刪。這是 P0 上手體驗的一部分(requirements 開放問題 #1)。
|
||
3. **每日排程:** 每日 **UTC 22:00**(=台北隔日 06:00)為所有 `active` watch 建立 `radar_sweep` Job,讓使用者早晨打開即有名單。多台 worker 以既有 Redis lock 模式確保只跑一台。時區個人化為 P2。
|
||
4. 巡的路徑**沿用既有海巡雙路徑分流**:會員 `dev_mode_enabled=false` 走 API 路徑,`true` 走瀏覽器工作階段路徑。**不新增第三種抓取方式。**
|
||
5. 命中 → 逐筆五問判定(§4.3)→ 寫入 Opportunity。
|
||
6. **配額截斷:** 當日商機數達方案上限時,依 `intent_score` 由高至低保留,其餘不建立,並在 RadarSweep 記 `truncated_count`;今日頁明確提示「已達今日上限,N 筆較低意向未收錄」。**不得靜默丟棄。**
|
||
7. 使用者可對單一 watch 手動觸發即時巡(`POST /api/v1/radar/watches/:id/sweep`),受每日配額同一個池子約束。
|
||
8. **降級:** 抓取路徑不可用時,Sweep 記 `failed` 與原因並發通知;**禁止**回成功空資料。
|
||
|
||
### 4.3 五問判定(P0)
|
||
|
||
1. 輸入:貼文全文與中繼資料(作者、發布時間、permalink)、ServiceProfile、觸發的 watch 關鍵字。
|
||
2. 沿用既有分類結果(`seeking_help`/`seeking_recommendation`/`provider_offer`/`discussion`/`asking`/`announcement`/`noise`)作為第一問的主要依據,**不重做分類器**。
|
||
3. 輸出:`intent_score`、`intent_band`、`reasons[]`(五維度各一條人話理由)、`region_detected`、`region_match`、`freshness_hours`、`matched_service`(對到哪個服務項目)。
|
||
4. 硬否決者寫 `rejected` 與 `reject_reason`,仍入庫。
|
||
5. 判定計入既有 `ai_research` meter,`source` 標 `radar.judge`(§5.5)。
|
||
|
||
### 4.4 今日商機頁(P0)
|
||
|
||
1. 頂部統計:`今日找到 N 筆`+`高意向 A ・中意向 B ・低意向 C`。
|
||
2. 卡片欄位:原文摘要、`intent_score`、`intent_band` badge、`region_detected`、發布距今時間(由 `posted_at` 前端換算,後端只給 ns 時間)、**預設回覆(公開留言版)**。
|
||
3. 卡片動作:**開啟原文**(permalink 新分頁)、**加入名單**(→ `accepted`,綁定或建立 Contact)、**產生其他回覆**(→ 生成其他版本)、**略過**(→ `dismissed`)、**看判定理由**(展開 `reasons[]`)、**覆寫分級**。
|
||
4. 空狀態:當日 0 筆時**不得顯示空白**,須給明確原因(尚未到巡的時間/watch 全部暫停/關鍵字太窄的建議/抓取失敗)與下一步動作。
|
||
5. 低意向預設收合,可展開。
|
||
|
||
### 4.5 AI 回覆五版本(P0)
|
||
|
||
1. 版本枚舉:`public_comment`|`dm`|`no_sales`|`professional`|`humorous`。
|
||
2. **首次判定時只生成 `public_comment`**(成本控制,requirements 決策 #6);其餘按需生成。
|
||
3. 輸入:Opportunity 原文、ServiceProfile(服務項目、價格區間、案例、FAQ、語氣備註)、active Persona(若有)。
|
||
4. **`forbidden[]` 硬性過濾:** 生成後檢查,命中則自動重生一次;仍命中則**不輸出**並回明確錯誤(非 102000),不得輸出違規內容。
|
||
5. 回覆生成計入既有 `ai_copy` meter,`source` 標 `radar.reply`。
|
||
6. **送出沿用既有管道**:公開留言版走既有海巡外展送出/Outbox;私訊版 P0 **僅提供複製**(Threads 無私訊 API),複製後由使用者手動送出並回頭標記。
|
||
7. 送出前**必經既有 AccountHealth 送出閘**:`warn` 帶警示、`throttle` 拒絕自動送出僅留手動路徑。本 run **不新增繞過** 護欄的路徑。
|
||
|
||
### 4.6 加入名單與 CRM(P0)
|
||
|
||
1. 「加入名單」時依(`source_platform`+`author_handle`+`owner_uid`)尋找既有 Contact:命中則掛上,未命中則建立,初始 `stage=new_found`。
|
||
2. 同一人多筆商機**收斂在同一 Contact 下**,時間軸可看到全部接觸歷史。
|
||
3. **跨平台不自動合併**;提供手動 `merge`/`unmerge`,合併保留兩邊時間軸。
|
||
4. CRM 列表以 Contact 為單位,可依階段、`needs_follow_up`、意向分數、最後接觸時間篩選排序。
|
||
5. 階段推進、備註、標記待追蹤皆寫 `ContactTouch`。
|
||
6. 成交回報:填金額(選填)與備註 → 階段轉 `won` → **寫入既有 OutcomeEvent**(`source_type=radar_opportunity`,連 `opportunity_id`、`contact_id`),不另建成交帳。可事後修改/刪除並留痕。
|
||
|
||
### 4.7 追蹤提醒(P0)
|
||
|
||
1. 商機送出回覆或階段進入 `engaged`/`dm_sent` 後,建立 FollowUp,`due_at = 最後接觸 + N 天`(預設 3)。
|
||
2. 日排程 `radar_followup_scan` 掃到期 → 站內通知(沿用既有 `notifications`,`ref_type=contact`)。
|
||
3. 通知落地頁為該 Contact 詳情;提供「產生 AI 追蹤訊息」(計入 `ai_copy`,`source` 標 `radar.followup`)。
|
||
4. 階段推進到 `replied` 以後或使用者手動關閉 → FollowUp `done`。
|
||
5. 通知達 2 次仍無動作 → `escalated`,在 CRM 顯示「建議轉未成交」,**需人確認**。
|
||
|
||
### 4.8 轉換統計(P0)
|
||
|
||
1. 維度:
|
||
- **關鍵字轉換率**:以 watch 的 `term` 為單位,`won 數 / accepted 數`,附 accepted、replied、won 絕對數。
|
||
- **回覆版本成功率**:以 `variant` 為單位,`(replied + won) / used`。
|
||
- **成交來源**:radar/既有海巡/手動匯入 的成交筆數與金額。
|
||
2. **樣本不足不下結論:** 任一維度樣本 < 5 時只顯示原始數字並標「樣本不足」,不顯示排名或百分比結論(沿用既有 benchmark 的謹慎原則)。
|
||
3. 金額來源為手動回報(P1 起有金額維度),P0 先以筆數呈現。
|
||
|
||
### 4.9 方案配額(P0)
|
||
|
||
1. 配額維度:`max_active_watches`、`max_daily_opportunities`。AI 判定與回覆另計入既有點數 meter,不重複收費。
|
||
2. 建議值(plan 階段可調,行為鎖定):
|
||
|
||
| 方案 | max_active_watches | max_daily_opportunities |
|
||
|------|--------------------|-------------------------|
|
||
| Free | 1 | 5 |
|
||
| Starter | 5 | 30 |
|
||
| Pro | 20 | 100 |
|
||
|
||
3. 超限行為:新增 watch 超限 → 明確錯誤並提示升級;當日商機超限 → §4.2 第 6 點截斷並提示,**每日巡不因此停止**。
|
||
4. 點數耗盡時:判定與回覆生成走既有 platform gate 的既有行為(BYOK 者不受平台額度限制);**每日巡本身不因點數耗盡而刪除 watch**。
|
||
|
||
### 4.10 既有海巡命中升級為商機(P0)
|
||
|
||
1. 既有海巡命中列表新增動作「升級為商機」。
|
||
2. **決策:複製,不共用**(requirements 開放問題 #5)。建立新的 Opportunity 並記 `source_scout_post_id` 反向連結,兩邊狀態機互不影響,避免 `outreach_status` 與 Opportunity 狀態互相污染。
|
||
3. 升級後跑同一套五問判定,進同一條 CRM。
|
||
4. 既有海巡三模式(`product`/`theme`/`activity`)與品牌/產品模型**原樣保留**,不修改其行為。
|
||
|
||
### 4.11 P1/P2 邊界
|
||
|
||
| 能力 | 波次 | 行為邊界 |
|
||
|------|------|----------|
|
||
| 手動匯入 | P1 | 貼 Threads/Facebook 貼文網址或 CSV → 建 Opportunity → 跑同一套判定;來源標 `manual_import` |
|
||
| 報價與成交金額 | P1 | `quoted`/`won` 可填金額幣別預設 TWD;寫既有 OutcomeEvent |
|
||
| 價格帶校準 | P1 | 依 P0 每會員雷達成本數據決定是否調 Starter/Pro 價格;本 run 不改價 |
|
||
| LINE 官方帳號 | P2 | 私訊建立/更新 Contact,進同一 CRM |
|
||
| Google 評論/網站表單 | P2 | 只做自有與公開來源,不碰第三方私有資料 |
|
||
| 空檔媒合 | P2 | 依 ServiceProfile `availability` 對高意向商機提示可約時段 |
|
||
|
||
## 5. 介面契約
|
||
|
||
### 5.1 共通
|
||
|
||
- 沿用 `haixun-backend`:成功 `code=102000`;`page`/`pageSize` → `pagination`+`list`;時間 **unix nanoseconds UTC**;`uid` 數字。
|
||
- Auth:Bearer JWT;資源擁有者 = 登入 uid,全部 API 以 `owner_uid` 隔離。
|
||
- 後端一律 goctl:改 `generate/api/*.api` → `make gen-api` → 業務只寫 `internal/logic/**` 與 `internal/module/**`。
|
||
- **禁止未實作回成功空資料。**
|
||
|
||
### 5.2 API 能力分組
|
||
|
||
兩個新 group,前綴不與既有 `/api/v1/scout` 衝突。
|
||
|
||
| Group | Method | Path | 用途 |
|
||
|-------|--------|------|------|
|
||
| `radar` | GET / PUT | `/api/v1/radar/service-profile` | 讀/寫服務檔案(每會員一份) |
|
||
| `radar` | GET / POST | `/api/v1/radar/watches` | 列表/建立雷達訂閱 |
|
||
| `radar` | GET / PUT / DELETE | `/api/v1/radar/watches/:id` | 讀/改/封存 |
|
||
| `radar` | POST | `/api/v1/radar/watches/:id/pause`、`/resume` | 暫停/恢復 |
|
||
| `radar` | POST | `/api/v1/radar/watches/:id/sweep` | 手動觸發即時巡 → 回 Job |
|
||
| `radar` | POST | `/api/v1/radar/watches/suggest` | 依服務檔案建議關鍵字 |
|
||
| `radar` | GET | `/api/v1/radar/today` | 今日統計+分級筆數+卡片首頁 |
|
||
| `radar` | GET | `/api/v1/radar/opportunities` | 列表(filter:band/status/watch/日期) |
|
||
| `radar` | GET | `/api/v1/radar/opportunities/:id` | 詳情(含 `reasons[]`、變體、來源) |
|
||
| `radar` | POST | `/api/v1/radar/opportunities/:id/accept` | 加入名單 → 綁定/建立 Contact |
|
||
| `radar` | POST | `/api/v1/radar/opportunities/:id/dismiss` | 略過 |
|
||
| `radar` | POST | `/api/v1/radar/opportunities/:id/override` | 覆寫 band 或把 rejected 拉回 |
|
||
| `radar` | POST | `/api/v1/radar/opportunities/:id/replies` | 生成指定 variant 回覆 |
|
||
| `radar` | GET | `/api/v1/radar/sweeps` | 巡的執行紀錄(可觀測性) |
|
||
| `radar` | POST | `/api/v1/radar/import` | **P1** 手動匯入(網址/CSV) |
|
||
| `crm` | GET | `/api/v1/crm/contacts` | 名單列表(filter:stage/follow_up/band/排序) |
|
||
| `crm` | GET | `/api/v1/crm/contacts/:id` | 詳情+時間軸 |
|
||
| `crm` | POST | `/api/v1/crm/contacts/:id/stage` | 推進/回退階段 |
|
||
| `crm` | POST | `/api/v1/crm/contacts/:id/follow-up` | 設定/解除待追蹤標記與天數 |
|
||
| `crm` | POST | `/api/v1/crm/contacts/:id/notes` | 新增備註 |
|
||
| `crm` | POST | `/api/v1/crm/contacts/:id/merge` | 手動合併聯絡人 |
|
||
| `crm` | POST | `/api/v1/crm/contacts/:id/conversion` | 成交回報(寫既有 OutcomeEvent) |
|
||
| `crm` | PUT / DELETE | `/api/v1/crm/contacts/:id/conversion` | 改/刪成交(留痕) |
|
||
| `crm` | GET | `/api/v1/crm/followups` | 待追蹤清單 |
|
||
| `crm` | POST | `/api/v1/crm/followups/:id/done`、`/snooze` | 完成/延後 |
|
||
| `crm` | GET | `/api/v1/crm/stats` | 轉換統計(關鍵字/回覆版本/來源) |
|
||
| `scout`(既有擴充) | POST | `/api/v1/scout/posts/:id/promote` | 既有海巡命中升級為商機 |
|
||
|
||
### 5.3 前端頁面
|
||
|
||
| 路由 | 變更 |
|
||
|------|------|
|
||
| `/app/radar` | **新增** 今日商機:統計列+卡片+分級分組+空狀態原因 |
|
||
| `/app/radar/watches` | **新增** 雷達訂閱管理+關鍵字建議 |
|
||
| `/app/crm` | **新增** 名單:八格視圖(七階段+待追蹤)、篩選排序 |
|
||
| `/app/crm/:contactId` | **新增** 聯絡人詳情+時間軸+成交回報 |
|
||
| `/app/crm/stats` | **新增** 轉換統計(或內嵌 `/app/crm` 分頁) |
|
||
| `/app/brands` | +「服務檔案」分頁(避免側欄再膨脹) |
|
||
| `/app/scout` 命中列 | +「升級為商機」動作 |
|
||
| `/app/today` | +今日商機摘要卡(連向 `/app/radar`) |
|
||
| 側欄 `nav.ts` | +新分組「商機」含 `radar`、`crm` 兩項;手機底欄以 `radar` 取代 `scout` 進主要四格,`scout` 移入「更多」 |
|
||
|
||
- 沿用既有設計系統:`src/components/ui/`、`src/styles/tokens.css`,不引入新 UI 框架、不用 emoji 當主 icon。
|
||
- 新頁樣式**不再灌入已 6,171 行的 `global.css`**:新增 `src/styles/radar.css` 並於 `main.tsx` 匯入,須通過 `npm run check:tokens`。
|
||
- 新增 repository 介面於 `src/data/repos.ts`(`radar`、`crm`),live 實作分檔 `src/data/live/radarRepos.ts`。
|
||
|
||
### 5.4 Job / Worker
|
||
|
||
| Template | 觸發 | 成功結果 |
|
||
|----------|------|----------|
|
||
| `radar_sweep` | 每日 UTC 22:00 排程(每個 active watch 一筆)/手動觸發 | 抓取→判定→寫 Opportunity;寫 RadarSweep 統計(命中、判定、截斷、失敗原因) |
|
||
| `radar_followup_scan` | 日排程(維護 tick,Redis lock) | 到期 FollowUp → `notified` +站內通知 |
|
||
| (既有)`scout_scan` | 不變 | 不變;**不得**改動既有海巡行為 |
|
||
|
||
- 沿用既有 Job guarded 狀態更新與 Redis lock(lock value = `workerID`)。
|
||
- `radar_sweep` 失敗須寫可讀錯誤原因並發通知,不得靜默。
|
||
|
||
### 5.5 計費對照(不新增 meter)
|
||
|
||
既有 `soft_caps` 各分項加總必須等於 `monthly_credits`,**新增 meter 會迫使三個方案全部重算**。因此本 run **沿用既有四個 meter**,僅以 `source` 標籤區分來源。
|
||
|
||
| 行為 | Meter | `source` |
|
||
|------|-------|----------|
|
||
| 巡的抓取(API 路徑) | `web_search` | `radar.sweep` |
|
||
| 五問判定 | `ai_research` | `radar.judge` |
|
||
| 回覆生成(各 variant) | `ai_copy` | `radar.reply` |
|
||
| AI 追蹤訊息 | `ai_copy` | `radar.followup` |
|
||
| 關鍵字建議 | `ai_copy` | `radar.suggest` |
|
||
|
||
- BYOK 規則不變;平台 gate 行為不變。
|
||
- 用量頁可依 `source` 前綴 `radar.` 聚出「雷達花了多少」,供 P1 價格校準使用。
|
||
|
||
### 5.6 錯誤語意
|
||
|
||
| 情境 | 行為 |
|
||
|------|------|
|
||
| 未建服務檔案就建 active watch | 明確錯誤,非 102000;message 指向服務檔案 |
|
||
| watch 數超過方案上限 | 明確錯誤;message 含目前上限與升級提示 |
|
||
| 當日商機達上限 | **不是錯誤**:正常回應並帶 `truncated_count` 與提示文案 |
|
||
| 回覆生成兩次都命中 `forbidden[]` | 明確錯誤;不得輸出違規內容 |
|
||
| AccountHealth `throttle` 時嘗試自動送出 | 沿用既有錯誤語意;僅留手動路徑 |
|
||
| 抓取路徑不可用 | Sweep `failed` +通知;**禁止**空成功 |
|
||
| 合併聯絡人跨不同 owner | 明確錯誤(越權) |
|
||
| 成交回報金額為負或幣別非法 | 明確錯誤 |
|
||
|
||
## 6. 與現況對照(Retain / Replace / Remove)
|
||
|
||
| 能力 | Retain | Replace | Remove | 備註 |
|
||
|------|--------|---------|--------|------|
|
||
| 海巡三模式(product/theme/activity) | ✅ 原樣 | — | — | 不改行為 |
|
||
| 海巡 Brand/Product/pain_points | ✅ | — | — | 與 ServiceProfile 並存,非取代 |
|
||
| 海巡抓取雙路徑 | ✅ 復用 | — | — | 不新增第三種抓取 |
|
||
| 既有貼文分類(`seeking_help` 等) | ✅ 復用為第一問依據 | — | — | 不重做分類器 |
|
||
| ScoutPost `outreach_status` | ✅ | — | — | 與 Opportunity 狀態**不共用** |
|
||
| 海巡命中列表 | ✅ | **+**「升級為商機」 | — | 複製+反向連結 |
|
||
| OutcomeEvent 歸因 | ✅ | **+** `source_type=radar_opportunity` | 另建成交帳 | |
|
||
| AccountHealth 送出閘 | ✅ 強制經過 | — | 繞過護欄的新路徑 | |
|
||
| Outbox 真發送 | ✅ | — | — | 公開留言版沿用 |
|
||
| Usage meter 四分項 | ✅ | **+** `source` 標籤 | 新增第五個 meter | 避免重算 soft_caps |
|
||
| 付費流程(Stripe 訂閱/結帳/portal/webhook) | ✅ 原樣 | **+**配額欄位 | 重做付費流程 | 金流已存在 |
|
||
| 方案價格 Free/Starter/Pro | ✅ 不改價 | — | — | 校準列 P1 |
|
||
| 站內通知 | ✅ | **+** `ref_type=contact` | — | |
|
||
| 前端設計系統與 token 守門 | ✅ | **+** `radar.css` | 樣式灌 `global.css` | |
|
||
| `lead` 指稱銷售線索 | — | — | **禁止** | 既有語意為主帳號 |
|
||
| 模擬/空成功 API | — | — | **禁止** | AGENTS.md |
|
||
|
||
## 7. 非功能
|
||
|
||
- **安全/權限:** 全部新資源以 `owner_uid` 隔離;Contact 合併需同 owner;Admin 不預設讀全站商機(如需另開需求)。
|
||
- **隱私:** 只處理公開貼文與使用者主動匯入內容;Contact 僅存公開作者識別與使用者自填備註,**不做跨平台身分推測**。
|
||
- **合規:** 抓取僅走既有雙路徑;`dev_mode` 爬蟲路徑不擴大;所有自動化保留「複製 → 手動送出 → 標記完成」人工路徑。
|
||
- **可觀測性:** RadarSweep 記錄每次巡的命中/判定/截斷/失敗原因與耗用點數;FollowUp 掃描寫日誌;日誌不含 token 明文。
|
||
- **效能邊界:** 列表 `pageSize ≤ 50`;單次 sweep 判定筆數以方案每日上限為硬上限;判定為逐筆 AI 呼叫,須可中斷並記錄已完成進度(Job 失敗不重跑已判定者)。
|
||
- **成本:** 每日巡是常駐成本,必須有 §4.9 配額閘;`radar.` source 聚合是價格校準的資料來源。
|
||
- **語系:** 判定理由與回覆文案預設繁體中文台灣語感。
|
||
|
||
## 8. 資料保存(邏輯層)
|
||
|
||
| 實體 | 關鍵欄位 | 生命週期 |
|
||
|------|----------|----------|
|
||
| `radar_service_profiles` | `_id=owner_uid`, services[], cases[], forbidden[], faq[], service_areas[], remote_ok, availability, tone_note, updated_at | 每會員一份,可改 |
|
||
| `radar_watches` | id, owner_uid, terms[], exclude_terms[], regions[], status, last_swept_at, created_at | CRUD+封存 |
|
||
| `radar_sweeps` | id, owner_uid, watch_id, job_id, path(api/crawler), hit_count, judged_count, created_count, truncated_count, failed_reason?, credits_used, started_at, ended_at | 保留供觀測與成本分析 |
|
||
| `radar_opportunities` | id, owner_uid, watch_id?, source(threads/manual/scout_promote), source_scout_post_id?, external_id, permalink, author_handle, text, posted_at, status, intent_score, intent_band, reasons[], region_detected, region_match, freshness_hours, matched_service, reject_reason?, override?, contact_id?, created_at | 判定後入庫;rejected 亦留 |
|
||
| `radar_replies` | id, owner_uid, opportunity_id, variant, text, used_at?, sent_channel?, created_at | 按需生成 |
|
||
| `crm_contacts` | id, owner_uid, source_platform, author_handle, display_name?, stage, needs_follow_up, follow_up_days, last_touch_at, opportunity_ids[], merged_from[], created_at | 唯一鍵(owner_uid, source_platform, author_handle) |
|
||
| `crm_touches` | id, owner_uid, contact_id, type(stage/reply/note/conversion), from_stage?, to_stage?, body?, actor_uid, created_at | 只增不改;成交修改寫新一筆 |
|
||
| `crm_followups` | id, owner_uid, contact_id, due_at, status, notified_count, created_at | 到期→通知→終態 |
|
||
| 成交 | **不新增 collection**,寫既有 `growth_outcomes` | `source_type=radar_opportunity` |
|
||
|
||
- 索引:`owner_uid` 為所有查詢前綴;`radar_opportunities` 需 `(owner_uid, created_at)`、`(owner_uid, intent_band, status)`、`(owner_uid, external_id)` 去重;`crm_contacts` 需唯一鍵;`crm_followups` 需 `(status, due_at)`。
|
||
- 去重:同一 `owner_uid` 下同 `external_id` 不重複建立 Opportunity(跨 watch 命中同一貼文只留一筆,記錄多個觸發 term)。
|
||
|
||
## 9. 驗收場景(Given / When / Then)
|
||
|
||
**共通:** 成功 envelope `102000`;時間 ns UTC;未實作 ≠ 空成功。
|
||
|
||
### 9.1 服務檔案與雷達訂閱
|
||
|
||
| ID | Given | When | Then |
|
||
|----|-------|------|------|
|
||
| SP-01 | 新會員無服務檔案 | 建 active watch | 明確錯誤;指向服務檔案 |
|
||
| SP-02 | 已填服務檔案 | GET service-profile | 欄位完整回傳;`forbidden[]` 可讀 |
|
||
| RW-01 | Free 方案已有 1 個 active watch | 再建一個 | 明確錯誤含上限與升級提示 |
|
||
| RW-02 | active watch | pause | 不再排入每日巡;既有商機保留 |
|
||
| RW-03 | 有服務檔案 | watches/suggest | 回關鍵字建議清單,可逐條採用 |
|
||
| RW-04 | archived watch | 列表 | 不再產生新商機;歷史商機與統計不變 |
|
||
|
||
### 9.2 每日巡與判定
|
||
|
||
| ID | Given | When | Then |
|
||
|----|-------|------|------|
|
||
| SW-01 | 有 2 個 active watch | 排程到 UTC 22:00 | 各建一筆 `radar_sweep` Job;隔日台北早晨可見結果 |
|
||
| SW-02 | `dev_mode_enabled=false` | sweep | 走 API 路徑;`path=api` |
|
||
| SW-03 | `dev_mode_enabled=true` 且有工作階段 | sweep | 走爬蟲路徑;`path=crawler` |
|
||
| SW-04 | 抓取路徑不可用 | sweep | Sweep `failed` +原因+通知;**非**空成功 |
|
||
| SW-05 | 命中 40 筆、方案上限 30 | sweep | 依 score 保留 30;`truncated_count=10`;今日頁提示 |
|
||
| SW-06 | 同一貼文被兩個 watch 命中 | sweep | 只建 1 筆 Opportunity,記錄兩個觸發 term |
|
||
| OP-01 | 貼文為「求推薦台北婚攝」、服務地區台北 | 判定 | `qualified`;`region_match=match`;`reasons[]` 五條齊全 |
|
||
| OP-02 | 貼文為同業叫賣(`provider_offer`) | 判定 | `rejected`;不出現在今日頁;仍可查 |
|
||
| OP-03 | 貼文發布 20 天前 | 判定 | 硬否決 `rejected`(逾期) |
|
||
| OP-04 | 貼文未提地區 | 判定 | `region_match=unknown`;得部分分;**不猜測縣市** |
|
||
| OP-05 | 服務檔案標可遠端、貼文在高雄、服務地區台北 | 判定 | 不因地區硬否決;地區維度給滿分 |
|
||
| OP-06 | 任一維度缺理由 | 寫入 | **拒絕入庫**(契約要求 `reasons[]` 五條齊全) |
|
||
| OP-07 | 使用者覆寫 band | override | 生效並記錄原值/新值/時間/操作者 |
|
||
|
||
### 9.3 今日頁與回覆
|
||
|
||
| ID | Given | When | Then |
|
||
|----|-------|------|------|
|
||
| TD-01 | 當日有 18 筆 | GET today | 統計為 18 與高/中/低分項;卡片含原文、分數、地區、發布時間、預設回覆 |
|
||
| TD-02 | 當日 0 筆 | GET today | **非空白**:回明確原因與下一步建議 |
|
||
| RP-01 | 商機剛判定完 | 查 replies | 僅存在 `public_comment` 一個版本 |
|
||
| RP-02 | 使用者要私訊版 | 生成 `dm` | 產出並計入 `ai_copy`/`source=radar.reply` |
|
||
| RP-03 | 服務檔案 forbidden 含「保證錄取」 | 生成回覆 | 輸出不含該詞;連兩次命中則明確錯誤且不輸出 |
|
||
| RP-04 | 帳號 AccountHealth `throttle` | 自動送出公開留言 | **拒絕**;僅留複製+標記完成 |
|
||
| RP-05 | 私訊版 | 送出 | P0 僅提供複製;不宣稱已自動送出 |
|
||
|
||
### 9.4 CRM 與追蹤
|
||
|
||
| ID | Given | When | Then |
|
||
|----|-------|------|------|
|
||
| CR-01 | 商機 `qualified` | accept | 建立或綁定 Contact;`stage=new_found` |
|
||
| CR-02 | 同一作者第二筆商機 | accept | 掛在**同一** Contact 下;時間軸兩筆 |
|
||
| CR-03 | Contact 在 `dm_sent` | 直接跳 `won` | 允許;寫 ContactTouch 記錄跳轉 |
|
||
| CR-04 | Contact `won` | 重新開啟為 `replied` | 允許;留痕 |
|
||
| CR-05 | 任一階段 | 標記待追蹤 | `needs_follow_up=true` 與階段**並存**,非取代 |
|
||
| CR-06 | 兩個不同平台的 Contact | merge | 允許手動合併;時間軸保留兩邊 |
|
||
| CR-07 | 跨 owner 的 Contact | merge | 明確錯誤(越權) |
|
||
| CR-08 | 回報成交金額 12000 | conversion | 階段轉 `won`;寫入既有 `growth_outcomes`(`source_type=radar_opportunity`);成果頁可見 |
|
||
| CR-09 | 修改/刪除成交 | PUT/DELETE | 明細與成果彙總一致;留痕;非空成功 |
|
||
| FU-01 | 送出回覆後 3 天無動作 | followup scan | FollowUp `notified` +站內通知,落地 Contact 詳情 |
|
||
| FU-02 | 通知中 | 產生 AI 追蹤訊息 | 產出並計入 `ai_copy`/`source=radar.followup` |
|
||
| FU-03 | 已通知 2 次仍無動作 | scan | `escalated` +顯示「建議轉未成交」;**不自動改階段** |
|
||
| FU-04 | 使用者延後 5 天 | snooze | `due_at` 後移;回 `scheduled` |
|
||
|
||
### 9.5 統計與配額
|
||
|
||
| ID | Given | When | Then |
|
||
|----|-------|------|------|
|
||
| ST-01 | 某關鍵字 accepted 8、won 2 | GET stats | 顯示轉換率與絕對數 |
|
||
| ST-02 | 某關鍵字樣本 3 筆 | GET stats | 只顯示絕對數並標「樣本不足」,不顯示排名結論 |
|
||
| ST-03 | 有 radar/scout/manual 三種來源成交 | GET stats | 依來源分列 |
|
||
| QT-01 | 當日已達商機上限 | 再次 sweep | 不新增;回應帶 `truncated_count` 與提示;**非錯誤** |
|
||
| QT-02 | 平台點數耗盡(非 BYOK) | 判定 | 沿用既有 platform gate 行為;watch **不被刪除** |
|
||
| QT-03 | 用量頁 | 依 source 聚合 | 可看出 `radar.*` 的耗用,供價格校準 |
|
||
|
||
### 9.6 既有海巡橋接與回歸
|
||
|
||
| ID | Given | When | Then |
|
||
|----|-------|------|------|
|
||
| PR-01 | 既有海巡命中一筆 | promote | 建立**新** Opportunity 並記 `source_scout_post_id`;原 ScoutPost 狀態不變 |
|
||
| PR-02 | promote 後推進 CRM | 操作 | 不影響原 `outreach_status` |
|
||
| RG-01 | 既有海巡三模式 | 執行 brief/scan | 行為與現況完全一致 |
|
||
| RG-02 | 既有 Outbox 真發送 | 送出 | 仍僅平台成功後才 published |
|
||
| RG-03 | 既有 growth-loop 成果卡 | 開啟 | 既有歸因不受影響,radar 成交併入同一彙總 |
|
||
| RG-04 | 既有 Usage soft_caps | 檢查 | 四分項加總仍等於 `monthly_credits`(未新增 meter) |
|
||
| RG-05 | 全 repo | 檢索 | 無以 `lead` 指稱銷售線索的新程式碼或契約 |
|
||
|
||
## 10. 開放規格問題
|
||
|
||
1. ~~巡的觸發時段~~ → **已定:每日 UTC 22:00(台北 06:00)**;個人化時區 P2(§4.2)。
|
||
2. ~~既有海巡命中升級是複製或共用~~ → **已定:複製+反向連結**(§4.10)。
|
||
3. ~~是否新增 usage meter~~ → **已定:不新增**,以 `source` 標籤區分(§5.5)。
|
||
4. 意向分數五維度權重數值 → **plan/實作可調**;**band 門檻 80/50 與硬否決條件鎖定**。
|
||
5. 地區判定的縣市代碼表來源與比對規則細節 → **plan**(行為已鎖:三態、不猜測)。
|
||
6. 側欄新增兩項後共 13 項是否需要再收斂資訊架構 → **plan 決定**;本 spec 已先以分組與手機底欄調整緩解。
|
||
7. 關鍵字建議的產生方式(純 AI 或 AI+既有痛點關鍵字工具)→ **plan**。
|
||
|
||
## 11. 批准
|
||
|
||
- [x] Spec 已批准(2026-07-31/使用者「好」)
|