thread-master/docs/product/brand-product-radar/spec.md

24 KiB
Raw Permalink Blame History

Spec: 品牌 × 產品商機雷達

Status: approved
Status note: 使用者 2026-08-10「批准」
Source: docs/product/brand-product-radar/requirements.mdapproved 2026-08-10
Parent spec: docs/product/demand-radar/spec.md
Last updated: 2026-08-10

1. 摘要

本規格把既有 RadarWatch、每日立即巡邏、Opportunity、回覆與 CRM 串上 BrandProduct。產品型雷達以一個品牌下的一個產品為上下文同一公開內容仍只建立一筆 Opportunity但可累積多個產品匹配並選出唯一主推產品。

P0 不另建第二套商機系統:保留既有商機五問與 0100 分數,新增可獨立解釋的產品適配分數、證據與風險;產品型結果預設按商機分數排序。舊通用雷達與舊商機保持可用,逐步補上產品關聯。

2. 名詞與實體

名詞 定義
通用雷達generic watch 舊式 RadarWatch沒有 BrandProduct 關聯,沿用 ServiceProfile 判定服務匹配
產品型雷達product watch 綁定一個 Brand 與該 Brand 下的一個 Product 的 RadarWatch
ProductContextSnapshot 判定當下使用的品牌/產品識別與版本快照,只保留解釋歷史所需資訊
ProductMatch 一筆 Opportunity 對一項 Product 的適配結果;同產品在同一商機中最多一筆
ProductFitReason 產品適配的一個維度分數、理由與公開貼文證據
PrimaryProduct 商機目前主推的唯一產品;必須存在於該商機的 ProductMatch 中
產品適配分數 product_fit_score0100與既有 intent_score 分開呈現
可跟進產品匹配 適配分數至少 45、有痛點或使用情境的正向證據且沒有產品硬排除

沿用且不重新定義Brand、Product、ServiceProfile、RadarWatch 狀態、RadarSweep、Opportunity 狀態、Contact、FollowUp、ReplyVariant、Job、Usage、Notification。

3. 核心行為契約

3.1 RadarWatch 上下文

generic --(使用者補選合法 Brand + Product)--> product
product --X--> generic
product(A) --X--> product(B)
  • context_modegeneric | product
  • genericbrand_idproduct_id 皆空;product 時兩者皆必填,且 Product 必須屬於該 Brand 與目前會員。
  • generic active watch 維持既有 ServiceProfile 必填;product active watch 不要求 ServiceProfileBrandProduct 才是產品適配的必要上下文。
  • product watch 有自己的 regions[] 時優先使用;留空且 ServiceProfile 存在時沿用其地區remote兩者都沒有時視為「未限制地區」不得把未知地區硬判為不符。
  • 新前端流程一律建立 product watch。既有 API 呼叫未帶兩個識別欄位時仍可建立 generic,維持相容性,但 UI 不主動提供此入口。
  • generic watch 可做一次性補綁;第一次成功巡邏後,產品關聯不可原地更換。要改產品時封存或暫停舊 watch再以相同搜尋詞建立新 watch避免歷史統計混義。
  • watch 保存品牌名稱、產品名稱與綁定時間快照。列表優先顯示目前名稱;來源已刪除時顯示快照。

3.2 產品適配評分

產品型判定除既有五問外,必須輸出以下四個維度;四維滿分合計 100

維度 滿分 判定來源
pain 35 貼文問題是否對應 Product pain_points
scenario 25 貼文使用/購買情境是否對應 product_contextmatch_tags
audience 20 公開文字是否支持 Brand target_audience;不確定給 0不得猜測
capability 20 Product provider_capability_terms 是否確實能處理該需求

每個維度都產生一筆 ProductFitReason

  • dimension:上述四值之一。
  • score0 到該維度滿分。
  • reason:人話說明。
  • candidate_excerpt正分時必填必須是候選公開文字中的短片段0 分可留空。
  • product_basis:對應的品牌受眾、產品痛點、情境、標籤或能力詞;不得生成來源資料中不存在的產品承諾。

另輸出 risks[]。若候選文字明確命中 Product provider_exclude_terms,或判定為同業叫賣/品牌公告/雜訊,該 ProductMatch 設 excluded=true 並附 exclude_reason

product_fit_score = pain + scenario + audience + capability
product_fit_band  = strong   if score >= 75
                    possible if score 45..74
                    weak     if score < 45
eligible = score >= 45
           AND (pain > 0 OR scenario > 0)
           AND excluded = false
  • 單純命中搜尋詞或產品名,不算 painscenario 證據。
  • 產品型 Opportunity 的既有五問 fit 維度,取主推 ProductMatch 分數等比例換算為 010其他四問及 8050 分級門檻不變。
  • intent_score 仍是今日排序的主要分數;product_fit_score 獨立顯示,作為同分排序與解釋依據。

3.3 同貼文多產品合併與主推產品

  • Opportunity 的唯一語意仍是「同一會員+同一來源內容識別」;不因 watch 或 product 不同而複製商機。
  • ProductMatch 的唯一語意是「同一 Opportunity同一 Product」。同產品再次命中只合併 watch_ids[]matched_terms[],不新增重複項目。
  • 首個 eligible ProductMatch 成為 PrimaryProduct後續出現更高適配分數時自動改成較高者。
  • 分數相同時依 strong 優先、首次匹配時間較早、最後以 product_id 穩定排序。
  • 使用者手動選定主推產品後寫入 override之後新增 ProductMatch 不再自動改主推,直到使用者再次選擇。
  • PrimaryProduct 改變時,以該 ProductMatch 重新換算既有 fit 維度與 intent_score,其他四維理由不重跑;變更須留操作者與時間。
  • ProductMatch 使用判定當下的品牌/產品版本快照。產品日後修改不回寫舊 match同貼文重複命中也只合併來源不重算舊快照。只有先前尚無該 ProductMatch 時才執行一次適配判定。

3.4 時效與顯示

  • 既有時效分數不變24 小時內最高、2472 小時遞減、72 小時至 14 天低分。
  • 超過 14 天仍以 stale 原因保留為 rejected,但產品型判定仍保存 ProductMatch 與證據;不得只因過舊就完全不入庫。
  • 「今日商機」只顯示未逾 14 天、既有狀態允許顯示,且至少有一個 eligible ProductMatch 的產品型 Opportunity。
  • 「全部結果」可看 stale、weak、excluded 與通用商機;預設仍依 intent_score 高到低,其次主推產品適配分數高到低、posted_at 新到舊。
  • rejected、weak、excluded 預設不混入今日高/中/低統計,但篩選後可檢查其理由。

3.5 Watch 與來源生命週期

沿用 active ↔ paused → archived,新增 pause_reason

active --> paused(product_unavailable) : 綁定 Brand/Product 不存在或不可用
paused(product_unavailable) --X--> active : 不允許直接恢復
paused(product_unavailable) --> archived : 保留歷史
  • 使用者手動暫停使用 pause_reason=user
  • 刪除 Product 時,同一會員下所有綁定該 Product 的 active watch 先轉 paused歷史保留禁止自動改綁。
  • Brand 仍沿用「有產品不可刪」;若因資料異常找不到 Brand相關 watch 依同樣規則暫停。
  • product_unavailable watch 可複製搜尋設定建立新產品 watch不能原地換產品後恢復。
  • 排程發現無效產品上下文時,必須 guarded 暫停 watch、記錄原因並通知不建立成功空 Sweep。

4. 使用者流程

4.1 建立產品型每日雷達

  1. 使用者進入雷達訂閱頁,先選 Brand產品選單只列該 Brand 下、目前會員可用的 Product。
  2. 選 Product 後顯示上下文完整度:品牌受眾、產品情境、痛點至少一項、能力詞。缺品牌或產品本體直接阻擋;內容不足可繼續,但提示結果信心會降低。
  3. 使用者要求建議詞。系統只讀所選 BrandProduct回傳 includeexclude 建議、理由與依據種類。
  4. 使用者增刪建議,沿用 Threads 短詞規則驗證後啟用。
  5. 建立成功後沿用既有首巡與每日 UTC 22:00 排程;卡片顯示 Brand、Product、狀態與最近巡邏時間。

4.2 每日巡邏

  1. worker 取得 active watch 後,重新讀取同 owner 的 BrandProduct驗證歸屬。
  2. 找不到或關聯錯誤時按 §3.5 暫停並通知。
  3. 讀取最新 BrandProduct 組成 ProductContextSnapshot再以 watch 搜尋詞 fan-out 取得候選。
  4. 對全新公開內容執行既有商機五問與產品適配判定;對已存在 Opportunity、但尚無此 ProductMatch 的內容,只新增該產品適配判定,不重建商機。
  5. 對已存在且已有同 ProductMatch 的內容,只合併 watchterm不重複計 AI 判定用量。
  6. 新 ProductMatch 不占用「每日新商機筆數」;新 Opportunity 才占用。AI 適配判定仍受既有點數 gate。
  7. Sweep 另外記錄適配評估數、合併數與因產品適配不足未進今日頁的數量。

4.3 立即探索

  1. 使用者先選 BrandProduct再輸入或採用 16 組短詞。
  2. 立即探索使用與每日 watch 相同的上下文、去重、評分、配額與 ProductMatch 合併規則。
  3. 沒選 BrandProduct 時,舊 API 相容路徑可繼續做 generic explore新 UI 不提供 generic 預設。
  4. 結果回報搜尋命中、判定、新增商機、合併產品匹配、截斷與用量,不把「合併」誤算成新商機。

4.4 查看與篩選商機

  1. 今日頁預設跨所有產品、去重後按 §3.4 排序;提供 Brand、Product、適配程度與商機分級篩選。
  2. 選定 Product 篩選時,以「是否存在該 ProductMatch」決定是否入列卡片優先展示被篩選產品的證據與適配分數但不暗中更改儲存的 PrimaryProduct。
  3. 卡片顯示主推 BrandProduct、商機分數、產品適配分數、需求摘要、至少一個證據、風險、其他匹配產品數與原文。
  4. 展開「為什麼適合」顯示四維理由;展開其他產品可比較分數與證據。
  5. 使用者可把任一 eligible ProductMatch 設為主推產品weakexcluded 不可設為主推,除非先以既有人工覆寫機制確認並留下理由。
  6. 「全部結果」提供 staleweakexcluded未指定產品篩選結果多但不混淆可立即聯絡的今日商機。

4.5 回覆、CRM 與成交

  1. 生成回覆時使用 Opportunity 的 PrimaryProduct 快照、Brand 語境及 ServiceProfile 的營運限制。
  2. 沒有 PrimaryProduct 的舊商機沿用既有 ServiceProfile 回覆;畫面明確標示「未指定產品」。
  3. 加入名單後ContactTouch 保存該次主推 brand_idproduct_id 與 opportunity 關聯;同一聯絡人可在不同時間對應不同產品。
  4. 主推產品後續改變,不改寫已送出回覆或既有 ContactTouch新的回覆與接觸使用新主推產品。
  5. 成交回報保存成交當下主推產品;既有 OutcomeEvent 行為不變,額外攜帶產品歸屬供後續 P1 統計。

4.6 舊資料過渡

  1. context_mode 的既有 watch 讀作 generic;缺 ProductMatch 的既有 Opportunity 讀作「未指定產品」。不需一次性猜測回填。
  2. watch 列表提供「補上產品」;只允許為 generic watch 執行一次。
  3. 舊商機可繼續接受、回覆、進 CRM 與成交;不因未指定產品而變成不可用。
  4. matched_service 保留給 generic 與舊資料;產品型商機不再以它代表產品,欄位暫不移除。

5. 介面契約

5.1 共通規則與 Auth matrix

  • 所有下列 /api/v1/radar/** 與既有 BrandProduct 操作只接受會員 Authorization: Bearer <member JWT>
  • AI provider tokenBYOK key 只能由既有 provider 設定與內部 AI 呼叫使用,不得當會員 JWT也不得回傳至前端。
  • 成功 envelopecode=102000messagedataerror;失敗不得回 102000 空資料。
  • 列表:pagepageSize,回 paginationlist
  • 所有時間為 UTC unix nanoseconds int64

5.2 HTTP / API

既有路徑原地擴充;欄位均以 generate/api/*.api 為契約真相,再由 goctl 產生。表內只列本 run 有變化的介面。

Method Path 用途 本 run 主要 requestresponse 變更
GET /api/v1/radar/watches 列 watch filter 加 context_modebrand_idproduct_iditem 加上下文、名稱快照、pause_reason
POST /api/v1/radar/watches 建 watch req 可帶成對 brand_idproduct_idresponse 加 context_mode 與上下文
PUT /api/v1/radar/watches/:id 改搜尋設定 只改 termsexcluderegions不得更換產品
POST /api/v1/radar/watches/:id/assign-product 舊 watch 一次性補綁 req brand_idproduct_id;只允許 generic 且尚未補綁
POST /api/v1/radar/watches/suggest 產品需求詞建議 req 加 brand_idproduct_iditem 加 basis_kindbasis_text
POST /api/v1/radar/watches/:id/sweep 手動巡 行為加產品上下文驗證;無效時明確失敗/暫停
GET /api/v1/radar/today 今日商機 query 可帶 brand_idproduct_idfit_bandOpportunity item 加產品欄位
GET /api/v1/radar/opportunities 全部結果 filter 加 brand_idproduct_idfit_bandmatch_statesort
GET /api/v1/radar/opportunities/:id 商機詳情 回完整 product_matches[]、PrimaryProduct 與 override
PUT /api/v1/radar/opportunities/:id/primary-product 改主推產品 req product_idreason;回更新後 Opportunity
POST /api/v1/radar/explore 立即探索 req 加成對 brand_idproduct_idresponse 加 matched_countmerged_count
POST /api/v1/radar/import 手動匯入 req 可帶成對 BrandProduct使用同一適配判定
GET /api/v1/radar/sweeps 巡邏紀錄 item 加 match_evaluated_countmatch_merged_countfit_rejected_count
DELETE /api/v1/scout/products/:id 刪 Product 刪除前同步暫停相關 active watch任一步失敗不得假裝刪除成功

主要輸出形狀:

RadarWatchPublic += {
  context_mode, brand_id?, product_id?,
  brand_name_snapshot?, product_label_snapshot?, context_bound_at?, pause_reason?
}

OpportunityPublic += {
  primary_brand_id?, primary_product_id?,
  primary_brand_name?, primary_product_label?,
  primary_product_fit_score?, primary_product_fit_band?,
  primary_product_overridden?, product_matches[]
}

ProductMatchPublic = {
  brand_id, product_id, brand_name_snapshot, product_label_snapshot,
  brand_updated_at, product_updated_at,
  product_fit_score, product_fit_band, eligible, excluded,
  exclude_reason?, reasons[4], risks[], watch_ids[], matched_terms[], matched_at
}

5.3 錯誤語意

情境 行為
只帶 Brand 或只帶 Product validation error不得降級成 generic
BrandProduct 不屬於會員或關係不符 對外使用 not foundforbidden 的既有安全語意,不洩漏他人資料
對 product watch 更換 BrandProduct validation error提示建立新 watch
重複替 generic watch 補綁 conflict回目前綁定不覆寫
product_unavailable watch 恢復 conflict提示封存複製到新產品
主推產品不在 ProductMatch validation error
主推 weakexcluded match 且無覆寫理由 validation error
ProductMatch 判定缺四維理由 判定失敗,不保存不完整 matchSweep 記失敗數
產品來源失效 watch 暫停+通知;不得成功空巡
全為 staleweakexcluded today 正常回 0empty_reason=no_eligible_product_match;全部結果仍可查

5.4 前端頁面與互動

路由 行為變更
/app/radar/watches Brand → Product 串接選擇、資料完整度、產品詞建議、generic 補綁、產品失效提示
/app/radar/today BrandProduct適配篩選、產品證據卡、多匹配展開、改主推產品
/app/radar/opportunities 新增全部結果視圖,包含 staleweakexcluded未指定產品與分數排序
/app/brands Product 刪除前顯示會暫停的 radar 數量與後果;不重做產品編輯器
/app/crm 聯絡人與接觸時間軸顯示來源 BrandProduct既有階段操作不變
  • mock 與 live 必須實作同一 Radar repository 介面與相同錯誤/空狀態。
  • 沿用 Harbor Desk、既有 UI 元件與 radar.css;不新增 UI framework不用 emoji 作主 icon。
  • BrandProduct 名稱只是導覽資訊;證據與風險才是商機卡的主要解釋。

5.5 Job / 背景任務

Job Retain變更 成功結果
radar_sweep 保留每日 UTC 22:00、手動首巡、guarded 更新與 Redis workerID lock加入產品上下文與 ProductMatch 合併 新 Opportunity 或合併 ProductMatchSweep 統計完整
radar_followup_scan Retain 行為不變;通知與 Contact 歷史可帶產品歸屬
scout_scan Retain 不改 Scout 掃描行為;只復用 BrandProduct 真相

6. 資料保存與不變量

實體 新增/保留欄位 生命週期與不變量
RadarWatch context mode、BrandProduct IDs、名稱快照、綁定時間、pause reason product 關聯不可原地更換;封存保留
Opportunity PrimaryProduct、override、product_matches[];保留 matched_service 同 ownerexternal identity 唯一;舊資料可無產品
ProductMatch 來源版本快照、四維理由、分數、band、eligible、exclude、watchterm 來源 同 OpportunityProduct 唯一;判定後不可因產品編輯回寫
ContactTouch brand_id、product_id、opportunity_id 寫入後不隨主推產品改變
OutcomeEvent product_id可空向後相容 以成交當下主推產品保存
RadarSweep match evaluatedmergedfit rejected counts 與既有 Sweep 一起保存、可追查

必須成立:

  1. ProductMatch 的 Brand 必須與 Product 所屬 Brand 相同,且兩者 owner 與 Opportunity owner 相同。
  2. PrimaryProduct 必須指向 eligible ProductMatch若人工覆寫 weakexcluded則必須同時存在理由與稽核紀錄。
  3. 同一產品 match 的 watch_idsmatched_terms 去重;同一 Opportunity 的 ProductMatch 不重複。
  4. ownerexternal identity 的既有唯一性不變;多 worker 競態時結果仍是一筆 Opportunity。
  5. API 列表預設排序穩定:intent_score desc、primary fit desc、posted_at descid asc

7. 與現況對照Retain / Replace / Remove

能力 Retain Replace Remove 備註
每日排程、首巡、立即探索 同一 pipeline 加產品上下文
RadarWatch 狀態與配額 新增 context 與 pause reason
Threads 短詞 fan-out 建議詞改由選定產品產生
ServiceProfile 地區remoteforbidden product watch 可選用generic watch 仍必填
active watch 一律要求 ServiceProfile 只對 generic 保留product 改以 BrandProduct 為門檻
ServiceProfile 作為產品 fit 來源 product watch 改用 BrandProductgeneric 保留舊行為
matched_service generic 相容,產品型不再用它冒充 Product
重複貼文只合併 matched terms 改為同產品合併來源、不同產品新增 ProductMatch
Opportunity ownerexternal identity 去重 核心去重不變
五問分數與高/中/低門檻 fit 10 分由 PrimaryProduct 換算
>14 天不進今日頁 仍保存產品適配供全部結果查看
今日頁只看即時商機 加 eligible product gate
全部結果頁 從只有 API 查詢補成可操作前端視圖
回覆、CRM、FollowUp、成交 附加產品歸屬,不重造狀態機
BrandProduct CRUD Product 刪除增加 radar 暫停副作用
既有欄位/舊資料 P0 無硬刪欄位、無猜測回填

本 run 不移除任何既有對外路徑或持久欄位。

8. 非功能契約

  • 權限: 每次 watch、opportunity、product match、primary product 變更都以 JWT owner 驗證;不可只信 request 中的 brand_idproduct_id。
  • 一致性: Product 刪除與 watch 暫停必須具備可重試的一致結果;部分失敗不得回刪除成功。
  • 併發: 同一貼文被多 watchworker 同時命中時,需以 guardedatomic 語意保證單 Opportunity、單 ProductMatch。
  • 效能: 列表解析 BrandProduct 名稱不得逐筆查詢;pageSize 沿用既有限制,產品篩選與排序在合理索引下完成。
  • 成本: 同一 (opportunity, product) 不重複計適配判定;新增 ProductMatch 可計一次既有 ai_researchsource=radar.product_fit,不新增 meter。Product 更新不觸發舊商機重算。
  • 可觀測性: Sweep 可區分搜尋命中、商機判定、新商機、適配評估、合併、適配不足、截斷與失敗。
  • 隱私: prompt 與 UI 只使用公開貼文、會員自己的 BrandProductServiceProfile禁止輸出推測的敏感屬性。
  • 降級: AI 適配不可用時可用明確的規則結果,但仍須四維理由與證據;無法產出完整契約時該 match 失敗,不以空理由成功。

9. 驗收場景Given / When / Then

  1. Given 會員有 Brand A 下的 Product A1 與 A2When 建立 A1 watchThen watch 回傳 product context且不能綁 A2 所屬以外的 Brand。
  2. Given Product 有痛點但沒有品牌名出現在貼文,When 貼文明確描述該痛點,Then 可形成含原文 excerpt 的 ProductMatch不要求命中品牌名。
  3. Given 貼文只提到產品同名詞但無需求/痛點,When 判定,Then 不得只憑詞命中成為 eligible match。
  4. Given 同一貼文先被 A1 watch 命中、後被 A2 watch 命中,When 兩次巡邏完成,Then 只有一筆 Opportunity、兩筆 ProductMatch且不重複 Contact。
  5. Given A2 分數高於 A1 且沒有人工 overrideWhen A2 match 合併,Then A2 成為 PrimaryProductintent fit 維度按 A2 換算。
  6. Given 使用者手動指定 A1When 後續加入更高分 A2 matchThen PrimaryProduct 仍是 A1並保留 override 紀錄。
  7. Given Product 更新名稱與痛點,When 查看舊商機,Then 顯示判定時快照與舊證據;下一次新商機才使用新版資料。
  8. Given 一筆 20 天前但產品高度適配的內容,When 巡邏完成,Then 今日頁不顯示、全部結果可見 stale 與 ProductMatch 理由。
  9. Given Product 被刪除,When 刪除完成,Then 相關 active watch 已暫停並通知,歷史 OpportunityContactOutcomeEvent 仍可看。
  10. Given 舊 generic watch 與舊 OpportunityWhen 升級部署,Then 原巡邏、回覆與 CRM 可繼續,且 UI 顯示未指定產品而非猜測關聯。
  11. Given 今日沒有 eligible match 但有 weakstale 結果,When 開今日頁,Then 顯示 no_eligible_product_match 與前往全部結果的動作,不顯示模糊空白頁。
  12. Given 兩個 worker 同時處理同一貼文與同一 ProductWhen 都完成,Then 只存在一筆 Opportunity 與一筆該 ProductMatchwatchterm 集合無重複。
  13. Given 另一會員的 BrandProduct IDWhen 嘗試建立 watch 或改主推產品,Then 請求失敗且不洩漏對方名稱與內容。
  14. Given 產品型商機生成回覆,When 檢查 prompt 與輸出,Then 使用 PrimaryProduct 快照與正確 Brand且不引用其他品牌資料。

10. 開放規格問題

P0 無阻擋問題。以下保留到 P1P2不影響本規格批准

  1. 產品回饋訊號要採明確正負樣本,或由 CRM 行為推導權重。
  2. 產品自己的理想使用者欄位何時取代/覆蓋 Brand target_audience
  3. 跨來源同一人的商機是否允許人工合併P0 仍不做身分推測。

11. 批准

  • Spec 已批准2026-08-10使用者「批准」