My Stores 仪表板 — 两个标签重设计
Path: apps/web/src/pages/overview/ · Created: 2026-05-24
这份文档是什么
这份文档是 My Stores 页面 V2 设计的 source of truth, 取代现有 mvp-v3 mockup (docs.retaintive.ai/ui-design/ui-prototype/mvp-v3) 里的 My Stores section。
实施时按这份文档走,mvp-v3 HTML 不需要改 — 它是过渡 mockup, 这份文档是 final spec。
vs mvp-v3 的变化:
Product Positioning
Studio 是 call-data observation tool,不是 CRM。看到什么展示什么 — 不重建客户真相,不替 owner 决定哪些信号该藏。voicemail / no_answer 算 call (客人确实拨过来了);funnel 各阶段分开计数,同客人同事件可能在 Tab 1 和 Tab 2 都出现 — owner 自己 connect。
Page Shell
页面 5 层结构 (从上到下):
- Header — "My Stores" 标题 + Snapshot / Export 按钮
- Store grid — 8 家店卡片 (复用 mvp-v3 Store row 组件,显示店名 + Open/Closed 状态)
- Filter row — Time range picker + Voicemail toggle 同行
- Time range picker:复用现有 component,8 个预设 (Today / 7d / 14d / 28d / This Week / Last Week / This Month / Last Month) + Custom (max 93 天)
- Voicemail toggle:
[✓] Include voicemail默认 ON。Owner 可手动切换。切换会重新查询并把 Call Volume 重算
- Tab 切换 — Call Activity / Outcomes
- 当前 tab 的 multi-store table (主体)
切 tab 不重置 time range / VM toggle。默认时间 = This Week。Time picker + VM toggle 跨 tab 共享,这是相比 mvp-v3 (per-time-tab table) 最大的标准化改进。
Tab 1 — Call Activity
Call activity — 数字格式
closed / total。一个客人为同一件事打多通电话被算多次。
Sample (illustrative numbers, not live data):
视觉: trend ↑N% 绿色 (#16A34A) 表示同比上升, ↓N% 红色 (#dc2626) 表示下降, — 灰色 (#CBCED4) 表示无对比数据。Cancel Save / Complaint Resolve 列 ↑ = 救回 / 解决率上升 = 好事 = 绿色。沿用现有 component。
Design philosophy — "看苦劳 → 看功劳":Call Volume 是 activity metric(折叠到 subtext),3 个 outcome 列(Intro / Membership / Cancel Save)+ Complaint Resolve(投诉处理)是 outcome metrics(占主体),Team Productivity 是 rate metric。Voicemail toggle 控制 Call Volume 是否含 voicemail。Cancel Save % 严格只算 cancellation_risk 类 call(客人主动要走的);Complaint Resolve % 单独一列算 retention 类 call(投诉/billing/服务争议)— 两类语义不同,各自独立分母不混。
Calculation Recipes (Tab 1)
源表 calls (Neon) · Schema @retaintive/common/db/schema/calls.ts · Enum 常量 apps/api/src/constants/call-analysis.ts (CallCategory / CallSubcategory / OutcomeResult / CallState / CallDirection)
所有公式默认前缀 WHERE store_id = ? AND start_time IN period,下表只列额外条件。
Sanity checks (实施时验证):
Outbound + Inbound = Call Volumeclosed ≤ total(每列)- 任何
closed > total= SQL bug - Cancel Save total 与 Complaint Resolve total 互不重叠(不同 subcategory,语义独立)
Backend 改动 — voicemail toggle 替代 filter 删除:
apps/api/src/routes/v3/dashboard-multi-store.ts line 181 + 206 现有 call_state IS DISTINCT FROM 'voicemail' filter — 不删,改为受 includeVoicemail query param 控制:
- API 接
?includeVoicemail=true|false(默认true) includeVoicemail=true→ 移除 filter (含 VM)includeVoicemail=false→ 保留 filter (排除 VM)
UI 顶部 toggle 默认 ON,Owner 切换时重查。这避免删 filter 后 Owner 第一次打开 dashboard 数字一夜 1.77x 暴涨的 baseline shift(verify 数据:Main 7d 含 VM 560 vs 不含 317,VM 占 43%)。
Tab 2 — Outcomes
Task outcomes — 数字格式
closed / total。Task = 一客一事件 (去重,一人多通电话只算 1 个 task)。Base =closed_at IN period(本期 close 了多少 task)— 对齐 Tab 1start_time IN period口径,与 HubSpot "Tasks Completed" pattern 一致。Carryover task(上期建本期 close)归入"本期 close 数",反映本期工作量,但 Volume 不再包含 pending backlog(backlog 警示放别处)。
Sample (illustrative numbers, not live data):
镜像 Tab 1 结构:同 6 列 outcome / productivity,unit 从 call 换 task。Cancel Save % 和 Complaint Resolve % 是两个独立分母列,不混(对应 schema 的 cancellation_risk 和 retention 两个独立 type_category)。AI Summary 是 LLM 生成的一句话,不是固定 emoji 状态。
Calculation Recipes (Tab 2)
源表 tasks (Neon) · Schema @retaintive/common/db/schema/tasks.ts (TASK_TYPE_CATEGORY 9 值,TASK_CLOSE_RESULT 18 值 — v0.54 起加 'booked' + 'cancelled') · UI label + category (positive 7 / neutral 6 / negative 3) @retaintive/common/db/schema/task-ui.ts CLOSE_RESULT_OPTIONS · Task 创建规则 callytics-infrastructure/lambda/contacts-analyzer/src/core/prompt-builder.ts
所有公式默认前缀 WHERE store_id = ? AND closed_at IN period AND status = 'closed',下表只列额外条件。Task 去重靠 schema 约束 (同 contact_phone + 同 type_category 同时只能 1 个 pending,见 prompt-builder.ts CONSTRAINTS)。
Base 决策(reviewer claim #5 verify 后):Tab 2 不再用
created_at IN period OR closed_at IN period这种 "OR base"。原口径会把 carryover task 进 base(Main 7d:188 created + 25 closed-old = 213),Owner 看到 Tab 2 数字虚高于 Tab 1。改成 closed-only 后,Tab 1 (本期 calls)和 Tab 2 (本期 closed tasks)口径平行可比,且贴 HubSpot/Salesforce "Tasks Completed" pattern。Pending backlog 信号放页面顶部 alert banner(待 V2 spec),不混进 Volume。
Sanity checks:
closed ≤ total(每列)- Intro / Membership / Cancel Save / Complaint Resolve 四类 task 互不重叠(不同
type_category) Impacted Revenue ≥ 0,与 Tab 2 closed/saved 总数 directionally 一致
两个 Productivity 联合诊断 (Tab 1 Team Prod + Tab 2 Task Prod 一起看):
Impacted Revenue V1 系数表
SoT @retaintive/common/db/schema/task-ui.ts CLOSE_RESULT_OPTIONS (v0.54 起 18 值)。
V1 系数硬编码,V2 下放 store-level config。
AI Summary
不是 SQL 算的。callytics-infrastructure cross-call AI Lambda 每天凌晨扫这家店当天所有 call analysis,综合写一句 ≤ 80 字 plain-text summary 存到 store-level summary table。Dashboard 直接读 string 字段,inline 显示。
示例 narrative:
- "Stable week, cancel saves on track"
- "Intro booking +20% vs last week, momentum strong"
- "Membership conversion below avg, review SA scripts"
- "Urgent: 8/9 cancel calls unsaved, immediate intervention needed"
V1 不带视觉状态标签 (无 emoji 无 color tag) — 文字本身已传达情绪 (urgent / stable / strong)。V2 视产品需要再加 sentiment classification。
准确性靠 cross-call AI prompt 质量,与 dashboard 本身无关。Owner 反馈 "对不上" → issue 报到 callytics-infrastructure cross-call AI pipeline。
Interaction Rules
Deep-link query param spec(发给 store-detail):
Backend Implications
复用 apps/api/src/routes/v3/dashboard-multi-store.ts,扩展:
- Period enum 加
today / 7d / 14d / 28d / this-week+startDate/endDateISO query params(custom 日历) includeVoicemailquery param 新增 — defaulttrue,控制 line 181 + 206 现有call_state IS DISTINCT FROM 'voicemail'filter 是否应用。不删 filter,改成参数化(避免 VM 含/不含数字一夜 1.77x 跳变,Owner 失去 baseline)- Tab 2 response fields:
totalTasks(closed in period),introBookingClosed/Total,membershipClosed/Total,cancelSavedSaved/Total,complaintResolvedResolved/Total,taskProductivity,impactedRevenue,aiSummary - Tab 1 response 重构 from flat fields 成 closed/total 对:
introBookingClosed/Total,membershipCallClosed/Total,cancelSaveSaved/Total,complaintResolveResolved/Total,teamProductivity - Tab 2 SQL base 改 closed-only:
AND closed_at IN period AND status = 'closed'(替代原created_at OR closed_at) - 现有
tasksCreated / tasksClosed / tasksOverdue字段 V1 保留但不展示
Store 列表沿用 rc_stores + store_members + store_config(activated_at IS NOT NULL)(line 139-150)。多租户隔离:store_id (见 apps/api/CLAUDE.md Store-Level 隔离)。
Files to Change
Pending Team Discussion
V1 按下面"当前选择"实施,不 block 开发。Team review 后如改方向,同步更新 Tab 1 Calculation Recipes 表格 + Defaults section。
Decision 2 — Complaint subcategory list 范围
Tab 1 Complaint Resolve total 公式当前列了 4 个 subcategory:
member_support + billing_issue + cancellation_fee_dispute + complaint_feedback
Scarsdale 30d 真实分布:member_support 39(最大)+ billing_issue 16 + cancellation_fee_dispute 4 + complaint_feedback 1 = 60。
但 member_support 这 39 通里可能很多只是"客人问问题"(not actual complaint)。如果含 member_support,Complaint Resolve total = 60;不含 = 21。两个数字差 3 倍。
待 product 拍板 哪些 subcategory 算"complaint"。
- 含
member_support= 范围大,可能噪声 - 不含
member_support= 范围窄,数据稀疏但语义干净
决策人: (待填) · 决议日期: (待填)
Decision 1 — Tab 1 Membership 用 2 值还是 4 值
背景 — Prompt + schema 把 4 个都列为 revenue_impacting,没规定列怎么 group。这是产品 lens 决策,不是技术决策。
决策人: (待填) · 决议日期: (待填)
Schema Source-of-Truth Chain
L2 详情:
apps/api/src/constants/call-analysis.ts— call enums (CallCategory/CallSubcategory/OutcomeResult/CallState/CallDirection)@retaintive/common/db/schema/tasks.ts—TASK_TYPE_CATEGORY(9 值) +TASK_CLOSE_RESULT(18 值,v0.54 起加'booked'+'cancelled')@retaintive/common/db/schema/task-ui.ts—CLOSE_RESULT_OPTIONS(Impacted Revenue 系数表 SoT)