32 KiB
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
active --> paused : 使用者暫停(不再排入每日巡)
paused --> active : 恢復
active|paused --> archived : 軟刪;歷史商機保留
archived的 watch 不再產生新商機,但既有商機與統計不刪除。- 超過方案 watch 上限時禁止新增
active(明確錯誤),既有超額者維持不變不強制降級。
3.2 Opportunity(判定與收單)
(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 意向分數與分級
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)。
stage: new_found --> engaged --> dm_sent --> replied --> quoted --> won | lost
- 允許任意跳轉(含回退、跳級);每次轉移寫一筆
ContactTouch(來源階段、目標階段、時間、操作者)。 won/lost為終態但可重新開啟(轉回任一階段,同樣留痕)。needs_follow_up是 boolean 標記,可與任何階段並存,非階段值。won必須由人確認;系統永不自動判定成交。
3.5 FollowUp
scheduled --> notified : 到期 → 站內通知
notified --> done : 使用者處理(回覆/推進階段/手動關閉)
notified --> snoozed : 延後 N 天 → 回到 scheduled
notified --> escalated : 已通知達上限次數 → 建議轉 lost(僅建議,需人確認)
- 預設:無回應 3 天觸發,最多通知 2 次,天數可由使用者調整。
escalated不自動改階段、不自動關閉。
4. 使用者流程(行為)
4.1 服務檔案(P0)
- 使用者填寫:
services[](項目+價格區間)、cases[]、forbidden[](不能說的內容)、faq[]、service_areas[](縣市代碼;可勾「可遠端」)、availability(可接案空檔文字)、tone_note(語氣備註)。 - 服務檔案每會員一份(非多份),可隨時修改;修改不回溯既有商機的判定結果。
- 未建立服務檔案時:禁止建立
active的 RadarWatch,並在雷達頁引導先建檔(避免判定缺乏依據而產生垃圾名單)。 forbidden[]為回覆生成的硬性過濾(§4.5)。
4.2 雷達訂閱與每日自動巡(P0)
- 使用者建立 RadarWatch:
terms[](關鍵字)、exclude_terms[]、regions[](可留空=沿用服務檔案)、enabled。 - 系統可依服務檔案建議關鍵字(
POST /api/v1/radar/watches/suggest),使用者可全採用或逐條增刪。這是 P0 上手體驗的一部分(requirements 開放問題 #1)。 - 每日排程: 每日 UTC 22:00(=台北隔日 06:00)為所有
activewatch 建立radar_sweepJob,讓使用者早晨打開即有名單。多台 worker 以既有 Redis lock 模式確保只跑一台。時區個人化為 P2。 - 巡的路徑沿用既有海巡雙路徑分流:會員
dev_mode_enabled=false走 API 路徑,true走瀏覽器工作階段路徑。不新增第三種抓取方式。 - 命中 → 逐筆五問判定(§4.3)→ 寫入 Opportunity。
- 配額截斷: 當日商機數達方案上限時,依
intent_score由高至低保留,其餘不建立,並在 RadarSweep 記truncated_count;今日頁明確提示「已達今日上限,N 筆較低意向未收錄」。不得靜默丟棄。 - 使用者可對單一 watch 手動觸發即時巡(
POST /api/v1/radar/watches/:id/sweep),受每日配額同一個池子約束。 - 降級: 抓取路徑不可用時,Sweep 記
failed與原因並發通知;禁止回成功空資料。
4.3 五問判定(P0)
- 輸入:貼文全文與中繼資料(作者、發布時間、permalink)、ServiceProfile、觸發的 watch 關鍵字。
- 沿用既有分類結果(
seeking_help/seeking_recommendation/provider_offer/discussion/asking/announcement/noise)作為第一問的主要依據,不重做分類器。 - 輸出:
intent_score、intent_band、reasons[](五維度各一條人話理由)、region_detected、region_match、freshness_hours、matched_service(對到哪個服務項目)。 - 硬否決者寫
rejected與reject_reason,仍入庫。 - 判定計入既有
ai_researchmeter,source標radar.judge(§5.5)。
4.4 今日商機頁(P0)
- 頂部統計:
今日找到 N 筆+高意向 A ・中意向 B ・低意向 C。 - 卡片欄位:原文摘要、
intent_score、intent_bandbadge、region_detected、發布距今時間(由posted_at前端換算,後端只給 ns 時間)、預設回覆(公開留言版)。 - 卡片動作:開啟原文(permalink 新分頁)、加入名單(→
accepted,綁定或建立 Contact)、產生其他回覆(→ 生成其他版本)、略過(→dismissed)、看判定理由(展開reasons[])、覆寫分級。 - 空狀態:當日 0 筆時不得顯示空白,須給明確原因(尚未到巡的時間/watch 全部暫停/關鍵字太窄的建議/抓取失敗)與下一步動作。
- 低意向預設收合,可展開。
4.5 AI 回覆五版本(P0)
- 版本枚舉:
public_comment|dm|no_sales|professional|humorous。 - 首次判定時只生成
public_comment(成本控制,requirements 決策 #6);其餘按需生成。 - 輸入:Opportunity 原文、ServiceProfile(服務項目、價格區間、案例、FAQ、語氣備註)、active Persona(若有)。
forbidden[]硬性過濾: 生成後檢查,命中則自動重生一次;仍命中則不輸出並回明確錯誤(非 102000),不得輸出違規內容。- 回覆生成計入既有
ai_copymeter,source標radar.reply。 - 送出沿用既有管道:公開留言版走既有海巡外展送出/Outbox;私訊版 P0 僅提供複製(Threads 無私訊 API),複製後由使用者手動送出並回頭標記。
- 送出前必經既有 AccountHealth 送出閘:
warn帶警示、throttle拒絕自動送出僅留手動路徑。本 run 不新增繞過 護欄的路徑。
4.6 加入名單與 CRM(P0)
- 「加入名單」時依(
source_platform+author_handle+owner_uid)尋找既有 Contact:命中則掛上,未命中則建立,初始stage=new_found。 - 同一人多筆商機收斂在同一 Contact 下,時間軸可看到全部接觸歷史。
- 跨平台不自動合併;提供手動
merge/unmerge,合併保留兩邊時間軸。 - CRM 列表以 Contact 為單位,可依階段、
needs_follow_up、意向分數、最後接觸時間篩選排序。 - 階段推進、備註、標記待追蹤皆寫
ContactTouch。 - 成交回報:填金額(選填)與備註 → 階段轉
won→ 寫入既有 OutcomeEvent(source_type=radar_opportunity,連opportunity_id、contact_id),不另建成交帳。可事後修改/刪除並留痕。
4.7 追蹤提醒(P0)
- 商機送出回覆或階段進入
engaged/dm_sent後,建立 FollowUp,due_at = 最後接觸 + N 天(預設 3)。 - 日排程
radar_followup_scan掃到期 → 站內通知(沿用既有notifications,ref_type=contact)。 - 通知落地頁為該 Contact 詳情;提供「產生 AI 追蹤訊息」(計入
ai_copy,source標radar.followup)。 - 階段推進到
replied以後或使用者手動關閉 → FollowUpdone。 - 通知達 2 次仍無動作 →
escalated,在 CRM 顯示「建議轉未成交」,需人確認。
4.8 轉換統計(P0)
- 維度:
- 關鍵字轉換率:以 watch 的
term為單位,won 數 / accepted 數,附 accepted、replied、won 絕對數。 - 回覆版本成功率:以
variant為單位,(replied + won) / used。 - 成交來源:radar/既有海巡/手動匯入 的成交筆數與金額。
- 關鍵字轉換率:以 watch 的
- 樣本不足不下結論: 任一維度樣本 < 5 時只顯示原始數字並標「樣本不足」,不顯示排名或百分比結論(沿用既有 benchmark 的謹慎原則)。
- 金額來源為手動回報(P1 起有金額維度),P0 先以筆數呈現。
4.9 方案配額(P0)
- 配額維度:
max_active_watches、max_daily_opportunities。AI 判定與回覆另計入既有點數 meter,不重複收費。 - 建議值(plan 階段可調,行為鎖定):
| 方案 | max_active_watches | max_daily_opportunities |
|---|---|---|
| Free | 1 | 5 |
| Starter | 5 | 30 |
| Pro | 20 | 100 |
- 超限行為:新增 watch 超限 → 明確錯誤並提示升級;當日商機超限 → §4.2 第 6 點截斷並提示,每日巡不因此停止。
- 點數耗盡時:判定與回覆生成走既有 platform gate 的既有行為(BYOK 者不受平台額度限制);每日巡本身不因點數耗盡而刪除 watch。
4.10 既有海巡命中升級為商機(P0)
- 既有海巡命中列表新增動作「升級為商機」。
- 決策:複製,不共用(requirements 開放問題 #5)。建立新的 Opportunity 並記
source_scout_post_id反向連結,兩邊狀態機互不影響,避免outreach_status與 Opportunity 狀態互相污染。 - 升級後跑同一套五問判定,進同一條 CRM。
- 既有海巡三模式(
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. 開放規格問題
巡的觸發時段→ 已定:每日 UTC 22:00(台北 06:00);個人化時區 P2(§4.2)。既有海巡命中升級是複製或共用→ 已定:複製+反向連結(§4.10)。是否新增 usage meter→ 已定:不新增,以source標籤區分(§5.5)。- 意向分數五維度權重數值 → plan/實作可調;band 門檻 80/50 與硬否決條件鎖定。
- 地區判定的縣市代碼表來源與比對規則細節 → plan(行為已鎖:三態、不猜測)。
- 側欄新增兩項後共 13 項是否需要再收斂資訊架構 → plan 決定;本 spec 已先以分組與手機底欄調整緩解。
- 關鍵字建議的產生方式(純 AI 或 AI+既有痛點關鍵字工具)→ plan。
11. 批准
- Spec 已批准(2026-07-31/使用者「好」)