> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# Dashboard 重新设计 — 时间与内容矩阵视图

**Path**: `apps/web/src/pages/dashboard-v3/` · **Created**: 2026-05-24

## 这份文档是什么

这份文档是 **Dashboard 单店深挖页 reshape 的 source of truth**, 取代现有 mvp-v3 mockup ([docs.retaintive.ai/ui-design/ui-prototype/mvp-v3](https://docs.retaintive.ai/ui-design/ui-prototype/mvp-v3)) 里 Dashboard section 的 4 time tab 设计。

实施时按这份文档走, mvp-v3 HTML 不需要改 — 它是过渡 mockup, 这份文档是 final spec。生产实现位于 `dashboard-v3/` (新版终态), `dashboard/` 是 legacy, v3 完成后被替代。

**vs mvp-v3 的变化**:

| 维度           | mvp-v3 (旧)                                                     | 本文档 (新)                                                           |
| ------------ | -------------------------------------------------------------- | ----------------------------------------------------------------- |
| 时间选择         | 4 个 time tab (Yesterday / Last Week / This Month / Last Month) | **统一 time range picker** (跟 My Stores 同款, 5 预设 + Custom max 93 天) |
| Layout 一致性   | 每个 time tab 不同 layout (Yesterday 3 块 / Weekly+ 6 块)            | **同一套 7 块 layout 跨所有 time range**, 切 time 只重算数字                   |
| Yesterday 缺块 | 没 Staff / Cancel Reasons / Complaint / Impacted Rev            | 7 块全部展示 (数据小就小, "—" 表示无对比)                                        |
| Weekly+ 缺块   | 没 Task 三联表                                                     | 7 块全部展示                                                           |

## Product Positioning

Dashboard 是 **single-store deep dive 页**, 是 My Stores fleet view 点店后的下一层。**Manager-primary** (每天工作面板), Owner 通过 Email Digest cliffhanger 偶尔点入复用同一页 — 没有单独的 Owner Dashboard。

不重发明信息, 不删 mvp-v3 已 design 好的块, 只 unify time × content 让用户切 time range 时认知不断裂。

***

## Page Shell

页面 7 层结构 (从上到下):

1. **Deep-link banner**(可选) — 仅当从 My Stores cell click 跳来时显示;详见末尾 "Deep-link from My Stores" 章节
2. **Header** — "Dashboard" 标题 + Store switcher dropdown + Snapshot / Export 按钮
3. **Filter row** — Time range picker + Voicemail toggle 同行
   - Time range picker:复用 My Stores 同款 component。5 个预设 (Yesterday / Last Week / This Month / Last Month / Custom max 93 天)
   - **Voicemail toggle**:`[✓] Include voicemail` 默认 ON,跟 My Stores 同款。Owner 可手动切换 / Deep-link 设置自动 apply
4. **AI Quick Scan** — 顶部 1 段 ≤ 80 字 AI summary (按 time scale 重写)
5. **KPI 卡条**(② block) — 9 个 KPI 卡跨 time / tab 一致
6. **Tab 切换** — 3 个 tab:
   - **Call deep-dive**(④ Cancellation Reasons + ⑤ Complaint Reasons)
   - **Task deep-dive**(⑥ Impacted Revenue + ⑦ Task by Reason — Yesterday only)
   - **Staff Performance**(③ Staff 一行表 — 独立 tab,加 task 列以反映 staff 跨 call/task 双维度工作)
7. **当前 tab 的内容**(主体)

切 time range 不重置 store / sort / VM toggle / tab。默认时间 = Yesterday (Manager 早 huddle 心智)。Time picker + VM toggle 跨 3 tab 共享。

**为什么 Staff 独立 tab(不在 Call deep-dive 下)?** Staff 是"人维度"切片,跟 Cancellation Reasons / Complaint Reasons("原因维度"切片)是不同的 lens。Staff 同时做 call + task,放 Call tab 下语义错配。独立 tab 后可加 task 相关列(每个 staff close 了多少 task、close rate、task productivity),反映 staff 真实工作量。

***

## ① AI Quick Scan

> 1 段 ≤ 80 字 plain-text summary, 按当前 time range 重写。Owner 30 秒读完 + Manager 决定要不要深挖。

**Sample (illustrative)**:

| Time range | 示例 narrative                                                                                                      |
| ---------- | ----------------------------------------------------------------------------------------------------------------- |
| Yesterday  | "Low call volume (7 vs daily avg 18) — 2 staff out sick. 3 tasks overdue, Lead Outreach + Payment Recovery 优先处理." |
| Last Week  | "本周 cancel ↑40% (Schedule conflict 主因, save rate 20%). Billing 投诉 ↑20%. Impacted Revenue $3,362 ↑11%."            |
| This Month | "MTD intro booking ↑8%, membership sold flat. Staff Holly 持续领跑, Nadia coaching flags ↑."                          |
| Last Month | "上月 churn rate 4.2%, save rate 36% — 跟前月持平. Top reason still 'schedule conflict', 建议 script update."              |

**视觉**: 顶部 1 段 plain text, 无 emoji 无 sentiment 标签, 文字本身已传达情绪 (urgent / stable / strong)。

### Calculation (① AI Quick Scan)

非 SQL 算的。`callytics-infrastructure` cross-call AI Lambda 每天凌晨扫这家店当前 time range 内所有 call analysis, 综合写一句 ≤ 80 字 plain-text summary 存到 store-level summary table。Dashboard 直接读 string 字段 `dashboardSummary[period]`, inline 显示。

V1 不带视觉状态标签。V2 视产品需要再加 sentiment classification。准确性问题 → issue 报到 `callytics-infrastructure` cross-call AI pipeline。

***

## ② KPI 卡条 (8 卡)

> 8 个数字卡 + trend % vs 上期。所有 time range 都展示同 8 卡 (单日 Impacted Revenue 数字小也显示, 数据真相)。

**Cancel Save + Complaint Resolve 两类对称 KPI 卡** — 既然下方 ④/⑤ 有 Cancel Reasons + Complaint Reasons 两 detail block,顶部 KPI 也对应展示两组 close/total 数字,visual + 语义上下对称。**Cancel Save % 严格只算 cancellation\_risk 类 call**(主动 cancel);**Complaint Resolve % 算 retention 类 call**(投诉/billing/服务争议)— 两类 schema 上 type\_category 不同,KPI 上各自独立分母。

**Sample (Last Week, illustrative)**:

| Card                   | Value                 | Trend |
| ---------------------- | --------------------- | ----- |
| Total Calls            | 218 (130 in · 88 out) | ↑12%  |
| Intro Booking Calls    | 9                     | ↓25%  |
| Memberships Sold Calls | 2                     | ↓33%  |
| Cancellation Calls     | 14                    | ↑40%  |
| Cancel Save %          | 5 / 14 (36%)          | ↓17%  |
| Complaint Calls        | 6                     | ↑20%  |
| Complaint Resolve %    | 4 / 6 (67%)           | ↑14%  |
| Impacted Revenue       | $3,362                | ↑11%  |

**视觉**: trend `↑N%` 绿色 (`#16A34A`) = 对店有利方向, `↓N%` 红色 (`#dc2626`) = 不利方向, `—` 灰色 (`#CBCED4`) = 无对比数据。Membership Cancellation Calls 列 `↓` 是好事 (cancel 减少) → 绿色, 颜色按"对店家有利"判定方向而非数字大小。沿用 mvp-v3 + My Stores 同款 component。

### Calculation Recipes (② KPI 卡条)

源表 `calls` + `tasks` (Neon) · Schema `@retaintive/common/db/schema/{calls,tasks}.ts` · Enum 常量 `apps/api/src/constants/call-analysis.ts`

所有公式默认前缀 `WHERE store_id = ? AND start_time IN period`, 下表只列额外条件。Trend % = `(current - previous) / previous × 100`, previous 自动按 time range 算同长度上期。

| 卡                                 | 公式 (在 base WHERE 上追加)                                                                                                        | 含义 / 备注                                                                    |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| **Total Calls**                   | `COUNT(*)` from calls                                                                                                        | 所有 call\_state 都算, 含 voicemail / no\_answer                                |
| · Inbound subtext                 | `AND direction = 'Inbound'`                                                                                                  | RingCentral enum 首字母大写                                                     |
| · Outbound subtext                | `AND direction = 'Outbound'`                                                                                                 | Inbound + Outbound = Total                                                 |
| **Intro Booking Calls**           | `AND primary_subcategory = 'intro_booking'`                                                                                  | 新客 first-time, free / $12 区间                                               |
| **Memberships Sold Calls**        | `AND primary_subcategory IN ('membership_purchase_related', 'reactivation_purchase') AND primary_outcome_result = 'success'` | 2 值口径同 My Stores Tab 1 (Pending Discussion Decision 1 待决)                  |
| **Membership Cancellation Calls** | `AND primary_subcategory = 'membership_cancel'`                                                                              | 完全终止, `membership_freeze` 不算。**↓ 为好事 (绿色)**                                |
| **Cancel Save %**                 | saved / Cancellation Calls;saved = `AND primary_subcategory = 'membership_cancel' AND primary_outcome_result = 'retained'`   | `retained` 是 cancel 专用 outcome。显示 `s / t (%)` 格式                           |
| **Complaint Calls**               | `AND primary_subcategory IN ('member_support', 'billing_issue', 'cancellation_fee_dispute', 'complaint_feedback')`           | 投诉(精确 subcategory list 待 Decision 2 — 见 my-stores spec Pending Discussion) |
| **Complaint Resolve %**           | resolved / Complaint Calls;resolved = Complaint Calls + `AND primary_outcome_result = 'resolved'`                            | 投诉处理成功保留客户。显示 `r / t (%)` 格式                                               |
| **Impacted Revenue**              | `SUM(coefficient(close_result))` from `tasks WHERE status='closed' AND closed_at IN period`                                  | 系数表 SoT 同 My Stores Tab 2 · Impacted Revenue V1 系数表                        |

**Sanity checks**:

- `Inbound + Outbound = Total Calls`
- `Cancel Save saved ≤ Cancellation Calls`(saved 数 ≤ total)
- `Complaint Resolve resolved ≤ Complaint Calls`
- Cancellation Calls 与 Complaint Calls 来自不同 subcategory(`membership_cancel` vs `member_support`/`billing_issue`/etc.),**互不重叠**
- 单日 Impacted Revenue 可能 = $0, 不是 bug

**Backend 改动 — voicemail toggle 替代 filter 删除** (与 My Stores spec 一致):
`apps/api/src/routes/v3/dashboard-multi-store.ts` line 181 + 206 现有 `call_state IS DISTINCT FROM 'voicemail'` filter **不删,改成参数化**:

- API 接 `?includeVoicemail=true|false` (默认 `true`)
- `true` → 移除 filter (含 VM)
- `false` → 保留 filter (排除 VM)

Dashboard UI 顶部 voicemail toggle 跟 My Stores 同款,默认 ON。Owner 切换时重查。Deep-link 从 My Stores 跳来时,VM 设置经 query param 带过来(详见末尾 "Deep-link from My Stores")。

***

## ③ Staff Performance 表 (独立 tab)

> 按 SA 一行表格,反映 staff 跨 call + task 双维度工作。**独立 tab**(不在 Call deep-dive 下) — staff 是"人维度"切片,跟 Cancellation/Complaint Reasons("原因维度"切片)分开。

**Sample (Last Week, illustrative)**:

| Staff                 | Calls | In / Out | Intro Booking | Sold | Cancel Save | Complaint Resolve | Tasks Closed | Task Productivity | Coaching Flags | Call Productivity % |
| --------------------- | ----- | -------- | ------------- | ---- | ----------- | ----------------- | ------------ | ----------------- | -------------- | ------------------- |
| Nadia K. (Front Desk) | 58    | 21 / 37  | 14            | 6    | 3 / 5       | 2 / 3             | 31           | 80%               | 18%            | 40% (vs avg +3)     |
| Ryan W. (Front Desk)  | 42    | 18 / 24  | 9             | 4    | 1 / 2       | 1 / 2             | 19           | 65%               | 28%            | 33% (vs avg −4)     |
| Kyle L. (Front Desk)  | 19    | 6 / 13   | 4             | 2    | 1 / 1       | 1 / 1             | 10           | 70%               | 8%             | 37% (vs avg ±0)     |
| —                     | —     | —        | —             | —    | —           | —                 | —            | —                 | —              | **Team Avg 37%**    |

**视觉**: Productivity vs avg `+N` 绿色, `−N` 红色, `±0` 灰色。Coaching Flags % > 25% 黄色背景警示 (阈值由 product confirm)。

### Calculation Recipes (③ Staff Performance)

源表 `calls` (聚合 by `staff_name`) + `tasks` (`assigned_to` field) · 跟 ② 同 base WHERE 加 `AND staff_name = ?`

| 列                          | 公式                                                                                                                                     | 含义                                                                                                                                                      |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Staff**                  | DISTINCT staff\_name + role                                                                                                            | 从 calls 表 staff\_name 字段去重                                                                                                                              |
| **Calls**                  | `COUNT(*) FROM calls WHERE staff_name = ?`                                                                                             | 该 SA 接的所有 call                                                                                                                                          |
| **In / Out**               | split by `direction`                                                                                                                   | 该 SA 接听 / 主动外呼                                                                                                                                          |
| **Intro Booking Calls**    | `AND primary_subcategory = 'intro_booking'`                                                                                            | 该 SA 接的 intro call                                                                                                                                      |
| **Memberships Sold Calls** | `AND primary_subcategory IN ('membership_purchase_related', 'reactivation_purchase') AND primary_outcome_result = 'success'`           | 该 SA 推成的                                                                                                                                                |
| **Cancel Save**            | `(saved/total)` from `primary_subcategory = 'membership_cancel'`                                                                       | 该 SA cancel 救回 / 接到的 cancel total。严格不混 complaint                                                                                                        |
| **Complaint Resolve**      | `(resolved/total)` from `primary_subcategory IN ('member_support', 'billing_issue', 'cancellation_fee_dispute', 'complaint_feedback')` | 该 SA 处理投诉 (Decision 2 待定 subcategory 范围)                                                                                                                |
| **Tasks Closed**           | `COUNT(*) FROM tasks WHERE closed_by_staff_name = ? AND status = 'closed' AND closed_at IN period`                                     | 该 SA 本期关闭的 task 数                                                                                                                                       |
| **Task Productivity**      | `(positive close_results) / (tasks_closed)` per staff                                                                                  | positive = converted / booked / cancel\_saved / win\_back / renewed / upgraded / issue\_resolved / referral\_obtained。同 my-stores Tab 2 productivity 公式 |
| **Coaching Flags**         | `flagged_calls / total_calls × 100`                                                                                                    | flagged = AI 标记 coaching-worthy (script: skip voicemail / \<60s / wrong number)                                                                         |
| **Call Productivity %**    | `(Intro Booking Calls + Memberships Sold Calls + Cancel Save saved + Complaint Resolve resolved) / Total Calls × 100`                  | Owner 唯一会盯的 staff call 指标(同 my-stores Tab 1 Team Productivity 公式)                                                                                       |

**Team Avg Productivity** = `SUM(numerator) / SUM(Total Calls) × 100` (不是 staff 平均的平均)

**Sanity checks**:

- `In + Out = Calls` (per staff)
- `Productivity % ≤ 100%`
- `vs avg` 偏差 = `staff_productivity - team_avg`

***

## ④ Cancellation Call Reasons

> Cancel call 按 reason 拆分 + 各 reason 的 Save Rate。顶部 1 行 AI summary 指明 "哪个 reason 在 bleeding"。

**Sample (Last Week, illustrative)**:

顶部 banner: "Where we're bleeding: Schedule conflict is the #1 cancel reason (5 calls) and we save only 20% — fix the scheduling-objection script."

| Cancellation Reason            | Cancel Calls | Saved | Save Rate        |
| ------------------------------ | ------------ | ----- | ---------------- |
| ● Schedule conflict            | 5            | 1     | 20%              |
| ● Price                        | 3            | 2     | 67%              |
| ● Moved away                   | 2            | 0     | — uncontrollable |
| ● Unhappy with staff / service | 2            | 1     | 50%              |
| ● Injury / health              | 1            | 1     | 100%             |
| ● Unclear / multi-reason       | 1            | 0     | —                |
| **Total**                      | **14**       | **5** | **36%**          |

**视觉**: Save Rate \< 30% 红色 (`#DC2626`), 30-60% 黄色 (`#CA8A04`), > 60% 绿色 (`#16A34A`)。"Moved away" 标 `— uncontrollable` 灰色 (不计入 save rate 评估)。

### Calculation Recipes (④ Cancellation Call Reasons)

源表 `calls WHERE primary_subcategory = 'membership_cancel'` · Reason 来自 AI prompt 的 `cancellation_reason` 字段 (callytics-infrastructure standard-v1.0.0.txt)

| 列                       | 公式                                                                               | 含义                                                                                   |
| ----------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| **Cancellation Reason** | DISTINCT `cancellation_reason`                                                   | AI 分类 6 reason (Schedule conflict / Price / Moved away / Unhappy / Injury / Unclear) |
| **Cancel Calls**        | `COUNT(*) WHERE cancellation_reason = ?`                                         | 该 reason 的 cancel call 数                                                             |
| **Saved**               | `COUNT(*) WHERE cancellation_reason = ? AND primary_outcome_result = 'retained'` | 救回的                                                                                  |
| **Save Rate**           | `Saved / Cancel Calls × 100`                                                     | "Moved away" Save Rate 显示 `—` 因 uncontrollable                                       |
| **AI banner**           | LLM 生成 1 句                                                                       | 指出 #1 reason + save rate, 给 actionable advice                                        |

**Sanity check**: 各 reason `Cancel Calls SUM = ② KPI 卡 Membership Cancellation Calls`

***

## ⑤ Complaint Call Reasons

> 跟 ④ 同结构, complaint 按 reason 拆 + Resolve Rate。

**Sample (Last Week, illustrative)**:

顶部 banner: "Where trust is slipping: Billing disputes are the #1 complaint (2 calls) and we resolve only 50% — tighten the billing-explanation script."

| Complaint Reason                 | Complaint Calls | Resolved | Resolve Rate |
| -------------------------------- | --------------- | -------- | ------------ |
| ● Billing / overcharge dispute   | 2               | 1        | 50%          |
| ● Equipment broken / unavailable | 1               | 1        | 100%         |
| ● Staff behavior / rudeness      | 1               | 1        | 100%         |
| ● Class / booking availability   | 1               | 1        | 100%         |
| ● Unclear / multi-reason         | 1               | 0        | 0%           |
| **Total**                        | **6**           | **4**    | **67%**      |

**视觉**: 同 ④ 颜色规则 (Resolve Rate \< 50% 红, 50-80% 黄, > 80% 绿)。

### Calculation Recipes (⑤ Complaint Call Reasons)

源表 `calls WHERE primary_subcategory IN ('service_complaint', 'billing_complaint')` · `complaint_reason` 字段同 AI prompt 分类。

公式结构同 ④, 替换:

- `cancellation_reason` → `complaint_reason`
- `primary_outcome_result = 'retained'` → `primary_outcome_result = 'resolved'`
- `Saved` → `Resolved`
- `Save Rate` → `Resolve Rate`

**Sanity check**: 各 reason `Complaint Calls SUM = ② KPI 卡 Complaint Calls`

***

## ⑥ Impacted Revenue Breakdown

> 9 类 MRR + 1 类 One-time, 每类 revenue + task count + trend %。Owner 验证 Retaintive ROI 核心视图。

**Sample (Last Week, illustrative)**:

**Recurring Revenue (MRR)**

| Category                  | Revenue     | Tasks | Description                                                  |
| ------------------------- | ----------- | ----- | ------------------------------------------------------------ |
| ● Conversion MRR          | $1,268 ↑14% | 11    | Revenue from new member conversions                          |
| ● Upgrade MRR             | $400 ↓6%    | 4     | Revenue from membership upgrades                             |
| ● Complaint Retention MRR | $379 ↑9%    | 5     | Protected revenue (complaints resolved, 90d retained × 0.60) |
| ● Payment Recovery MRR    | $318 ↑22%   | 3     | Recovered failed payments (× 0.75)                           |
| ● Cancel Save MRR         | $423 ↑5%    | 7     | Revenue saved from prevented churn                           |
| ● Freeze Recovery MRR     | $207 ↓12%   | 2     | Revenue from reactivated frozen members (× 0.70)             |
| ● Win-back MRR            | $67 ↑3%     | 2     | Revenue from re-enrolled former members                      |
| ● Promotion MRR           | $120 ↑18%   | 1     | Revenue from promotion memberships                           |

**One-Time Revenue**

| Category        | Revenue  | Tasks | Description                         |
| --------------- | -------- | ----- | ----------------------------------- |
| ● Event Revenue | $180 ↑7% | 6     | Challenge & event registration fees |

\| **Total** | **$3,362 ↑11%** | **41** | — |

### Calculation Recipes (⑥ Impacted Revenue Breakdown)

源表 `tasks WHERE status = 'closed' AND closed_at IN period` · 按 `close_result` 分类 + 应用 V1 系数表

| Category                | 来源 close\_result           | 系数公式                   | 含义                                    |
| ----------------------- | -------------------------- | ---------------------- | ------------------------------------- |
| Conversion MRR          | `converted`                | `count × $450`         | 新签 paying member                      |
| Upgrade MRR             | `upgraded`                 | `count × $200`         | 升级 tier                               |
| Complaint Retention MRR | `issue_resolved` (V2 算系数)  | `count × $X` (V1 = $0) | V1 暂不入账, V2 重评                        |
| Payment Recovery MRR    | (待 schema 补 close\_result) | `count × $? × 0.75`    | 失败付款回收                                |
| Cancel Save MRR         | `cancel_saved`             | `count × $450`         | Cancel 救回                             |
| Freeze Recovery MRR     | (待 schema 补 close\_result) | `count × $? × 0.70`    | 冻结复活                                  |
| Win-back MRR            | `win_back`                 | `count × $300`         | Former member 回归                      |
| Promotion MRR           | (V2)                       | —                      | Promotion membership 系数待定             |
| Event Revenue           | (V2)                       | —                      | Challenge/event 报名费, 非 task 来源, V2 接入 |

**V1 现实**: 只有 Conversion / Upgrade / Cancel Save / Win-back 有现成 close\_result enum 算得出。Complaint Retention / Payment Recovery / Freeze Recovery / Promotion / Event 是 mvp-v3 mockup 已 design 但 schema 未支持的, 见 Pending Team Discussion · Decision 2。

**Sanity check**: `SUM(all categories) = Total = ② KPI 卡 Impacted Revenue`

***

## ⑦ Task by Reason — Created / Closed / Overdue

> 3 列并排表格, 每列按 11 reason 拆 task 数。Manager huddle 看"今天打谁电话"+ Owner 看"团队 follow-through 怎样"。所有 time range 都展示。

**Sample (Yesterday, illustrative)**:

| Reason               | Tasks Created (6) | Tasks Closed (4) | Tasks Overdue (3) |
| -------------------- | ----------------- | ---------------- | ----------------- |
| Lead Outreach        | 2                 | 1                | 1                 |
| Lead Follow-up       | 1                 | 1                | 1                 |
| Booked Not Converted | 1                 | 1                | 0                 |
| Upgrade              | 0                 | 0                | 0                 |
| Complaint Retention  | 1                 | 0                | 0                 |
| Payment Recovery     | 0                 | 0                | 1                 |
| Cancellation Risk    | 1                 | 1                | 0                 |
| Freeze Recovery      | 0                 | 0                | 0                 |
| Win-back             | 0                 | 0                | 0                 |
| Event Promotion      | 0                 | 0                | 0                 |
| Special Promotion    | 0                 | 0                | 0                 |

**视觉**: 数字 = 0 的 reason 标灰色 (`#9CA3AF`)。bar 长度按各列 max 缩放。Created 蓝 / Closed 绿 / Overdue 红 — 颜色 = 状态情绪。

### Calculation Recipes (⑦ Task by Reason)

源表 `tasks` · 按 `type_category` 分组 (11 reason mapping 见下)

| 列                 | 公式 (base `WHERE store_id = ?`)                                                      | 含义                          |
| ----------------- | ----------------------------------------------------------------------------------- | --------------------------- |
| **Tasks Created** | `created_at IN period` GROUP BY `type_category`                                     | 本期新建 task                   |
| **Tasks Closed**  | `status = 'closed' AND closed_at IN period` GROUP BY `type_category`                | 本期关闭                        |
| **Tasks Overdue** | `status = 'pending' AND due_at < NOW()` GROUP BY `type_category` (不限 period, 看当前积压) | 当前逾期 (snapshot, 非 period 内) |

**Reason mapping** (mvp-v3 mockup 11 reason vs `@retaintive/common` schema 9 enum):

| Mockup label         | Schema `type_category` | 状态                       |
| -------------------- | ---------------------- | ------------------------ |
| Lead Outreach        | `lead_outreach`        | ✅ 对应                     |
| Lead Follow-up       | `lead_follow_up`       | ✅ 对应                     |
| Booked Not Converted | `booked_not_converted` | ✅ 对应                     |
| Upgrade              | `upgrade`              | ✅ 对应                     |
| Cancellation Risk    | `cancellation_risk`    | ✅ 对应                     |
| Win-back             | `win_back`             | ✅ 对应                     |
| Complaint Retention  | `retention`            | ✅ 对应 (改 label)           |
| Payment Recovery     | —                      | ❌ schema 缺, 见 Decision 2 |
| Freeze Recovery      | —                      | ❌ schema 缺               |
| Event Promotion      | —                      | ❌ schema 缺               |
| Special Promotion    | —                      | ❌ schema 缺               |

**Sanity checks**:

- `Tasks Created SUM = 顶部 Created total` (例 = 6)
- `Tasks Closed SUM = 顶部 Closed total` (例 = 4)
- Overdue 是 snapshot, 不受 period 影响

***

## Interaction Rules

| 行为                               | 跳转 / 结果                                                      |
| -------------------------------- | ------------------------------------------------------------ |
| 切 time range                     | 当前 tab 重算, layout 不变, 不重置 store / sort / VM / tab            |
| 切 store (顶部 switcher)            | 全 tab 重算, time / VM / tab 保留                                 |
| 切 VM toggle                      | 当前 tab 重算 (Call Volume 受影响最直接)                               |
| 切 tab (Call / Task / Staff)      | tab 内容重渲染, time / VM 保留                                      |
| 点 staff name (③)                 | `/staff-timeline/:staffName?period=...`                      |
| 点 cancel/complaint reason 行 (④⑤) | `/calls?subcategory=...&reason=...&period=...&voicemail=...` |
| 点 revenue category 行 (⑥)         | `/task?type_category=...&status=closed`                      |
| 点 task reason 行 (⑦)              | `/task?type_category=...&status=created/closed/overdue`      |
| Hover ⓘ                          | 显示 formula tooltip                                           |
| AI Quick Scan (①)                | inline 显示, 不 expand                                          |
| Grab Snap                        | 截图当前 tab (跟 mvp-v3 现有按钮一致)                                   |

***

## Deep-link from My Stores

Dashboard reshape 作为 **My Stores cell click 的跳转目标**,需要接受 my-stores 传过来的 4 个 query param。

**支持的 query param**:

| Param       | Values                                                                                                            | 含义                          |
| ----------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------- |
| `store`     | store name 或 store\_id                                                                                            | 自动 set 顶部 store switcher    |
| `metric`    | `calls` \| `intro` \| `membership` \| `cancel` \| `complaint` \| `productivity` \| `tasks` \| `revenue`           | Owner 在 my-stores 点的具体 cell |
| `period`    | `today` \| `7d` \| `14d` \| `28d` \| `this-week` \| `last-week` \| `this-month` \| `last-month` \| ISO date range | 与 my-stores time picker 一致  |
| `voicemail` | `include` (default) \| `exclude`                                                                                  | 与 my-stores VM toggle 一致    |

**Frontend 行为**:

1. 接到 deep-link 时,4 个 param 自动 apply 到对应 UI 控件
2. `metric` 决定 default tab + 是否高亮:
   - `metric=calls / intro / membership / productivity` → default Call tab,顶部 KPI 卡条对应那张高亮(紫色边框 + ⤴ pill)
   - `metric=cancel` → default Call tab,scroll to ④ Cancellation Reasons block + KPI Cancel Save % 卡高亮
   - `metric=complaint` → default Call tab,scroll to ⑤ Complaint Reasons block + KPI Complaint Resolve % 卡高亮
   - `metric=tasks / revenue` → default Task tab,对应 block + KPI 卡高亮
3. 顶部紫色 banner 显示 "⤴ From › My Stores › `<Store>` › `<Metric>`" + 一键 `‹ Back to My Stores` 按钮
4. `✕ Clear focus` 按钮 — 清掉 banner + 高亮,但保留 time / VM / tab 选择

**V1 visual scope**:V1 只对 3 个 metric 高亮 detail block(`cancel` → ④;`complaint` → ⑤;`revenue` → ⑥)。其他 5 个 metric 跳进来只显示顶部 banner + 对应 KPI 卡高亮,不 scroll(因为 dashboard reshape V1 没有对应的 detail block)。V2 补全 detail block 后再扩展。

**Backend 行为**:Dashboard 已经支持 store + period + voicemail param,无需新加 backend 字段。`metric` 是纯 frontend 路由信号,不影响 SQL。

***

## Backend Implications

复用 `apps/api/src/routes/v3/dashboard-multi-store.ts` 改 single-store route (或新建 `dashboard-single-store.ts`), 扩展:

- **Period enum** 加 `yesterday / last-week / this-month / last-month` + `startDate` / `endDate` ISO query params (custom 日历)
- **Response shape**:
  - `aiSummary: string` (① 来自 cross-call AI Lambda)
  - `kpis: { totalCalls, inbound, outbound, introBooking, membershipsSold, cancellation, cancelSaved, complaintCalls, complaintRetention, impactedRevenue, ...trends }` (②)
  - `staffPerformance: Array<{ staffName, calls, in, out, introBooking, sold, cancelSaved, tasks, coachingFlags, productivity, vsAvg }>` (③) + `teamAvgProductivity`
  - `cancellationReasons: Array<{ reason, count, saved, saveRate }>` (④)
  - `complaintReasons: Array<{ reason, count, resolved, resolveRate }>` (⑤)
  - `impactedRevenueBreakdown: { recurring: [...], oneTime: [...], total }` (⑥)
  - `taskByReason: Array<{ reason, created, closed, overdue }>` (⑦)
- 删 voicemail filter (见 ② Backend 改动)
- Previous-period 同长度自动算 (用于 trend %)

Store 列表沿用现有 `useStores` composable + `getAuthorizedStore` 权限检查。多租户隔离: `store_id`。

***

## Files to Change

| 文件                                                                                 | 改动                                                                                                                                                   |
| ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apps/web/src/pages/dashboard-v3/index.vue`                                        | 7 块 layout + 统一 time picker                                                                                                                          |
| `apps/web/src/pages/dashboard-v3/components/`                                      | 7 个新 component (AIQuickScan / KPICardRow / StaffPerformanceTable / CancellationReasons / ComplaintReasons / ImpactedRevenueBreakdown / TaskByReason) |
| `apps/web/src/composables/queries/useDashboardSingleStore.ts`                      | 新建 — TanStack Query hook 调单店 dashboard route                                                                                                         |
| `apps/web/src/components/date-range/DateRangeDropdown.vue`                         | 复用 (跟 My Stores 同款)                                                                                                                                  |
| `apps/api/src/routes/v3/dashboard-single-store.ts`                                 | 新建或扩展现有 multi-store route — 7 块数据聚合 + period enum + voicemail filter 删                                                                               |
| `apps/api/docs/api-technical-reference.md` + `apps/api/docs/frontend-api-guide.md` | 文档同步 (pre-commit hook 强制)                                                                                                                            |
| `CHANGELOG.md` (root + apps/web + apps/api)                                        | 各加 entry                                                                                                                                             |

***

## Pending Team Discussion

V1 按下面"当前选择"实施, 不 block 开发。Team review 后如改方向, 同步更新对应 section。

### Decision 1 — KPI 卡 ② 用哪 8 个

| 选项                        | 8 卡组合                                                                                             | Lens                                         | 利弊                                       |
| ------------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------- | ---------------------------------------- |
| **mvp-v3 Weekly+** (当前选择) | Total / Intro / Sold / Cancel / Cancel Saved / Complaint / Complaint Retention / Impacted Revenue | "完整 funnel 看完"                               | mvp-v3 已 design, manager 心智已建立           |
| **Owner Will 5 问**        | Intro / Sold / Cancel / Cancel Saved / Outbound Call / Impacted Revenue / Total + ?               | Will 锁定 "Owner 每天 5 问" (role-deep-dive §1)   | Owner-first, 但 Manager 复用时缺 Complaint 维度 |
| **Manager + Owner 并集**    | mvp-v3 8 卡 + Outbound Calls (从 subtext 提到卡级) = 9 卡                                                | "Owner 看 Outbound 主动出击, Manager 看 Complaint" | 9 卡视觉拥挤, 1 行排不下                          |

**背景** — mvp-v3 mockup 选 8 卡, role-deep-dive Owner 5 问偏向不同集合, Manager M1 还要 Complaint Retention。这是产品 lens 决策, 不是技术决策。

**决策人**: _(待填)_  ·  **决议日期**: _(待填)_

### Decision 2 — Impacted Revenue ⑥ 9 类系数缺 5 个

mvp-v3 mockup 展示 9 类 MRR (Conversion / Upgrade / Complaint Retention / Payment Recovery / Cancel Save / Freeze Recovery / Win-back / Promotion / Event), `@retaintive/common` v0.54 `close_result` 18 值只能算出 4 类 (Conversion / Upgrade / Cancel Save / Win-back)。

| 选项                                | 做法                                                                                        | 利弊                             |
| --------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------ |
| **A. V1 只展示 4 类** (当前选择)          | 缺的 5 类不渲染, 占位 "—"                                                                         | 数据真实, mvp-v3 mockup 跟生产差距明示    |
| **B. mockup 全展示, 缺的标 $0**         | 9 类全显, V1 系数表 5 类 = $0                                                                    | 视觉跟 mockup 一致, 但 owner 误以为系统在算 |
| **C. 扩 schema 加缺的 close\_result** | v1.x 加 `payment_recovered` / `freeze_recovered` / `promotion_signed` / `event_registered` | 长期对, 但 block dashboard 上线      |

**决策人**: _(待填)_  ·  **决议日期**: _(待填)_

### Decision 3 — Task by Reason ⑦ 11 vs 9 schema mismatch

mvp-v3 mockup 11 reason (含 Payment Recovery / Freeze Recovery / Event Promotion / Special Promotion), schema 只 9 enum。

| 选项                              | 做法                        | 利弊                       |
| ------------------------------- | ------------------------- | ------------------------ |
| **A. V1 只展示 schema 9 个** (当前选择) | mockup 多的 4 个不渲染          | 跟 ⑥ Decision 2A 一致, 数据真实 |
| **B. mockup 11 个全显, 缺的标 0**     | UI 跟 mockup pixel-perfect | 误导 owner "系统在追踪"         |

**决策人**: _(待填)_  ·  **决议日期**: _(待填)_

***

## Schema Source-of-Truth Chain

| 层                      | 位置                                                                                                                     |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| L1 Drizzle schema      | `@retaintive/common/src/db/schema/{calls,tasks}.ts` (web `^0.54.0`, API `^1.0.0` — 2026-05-24 bump)                    |
| L2 Enum constants      | 见下方 sub-list                                                                                                           |
| L3 Design SoT          | `docs/product-design/future-plans/role-deep-dive-v2.html` (Manager M1-M7 已锁定 dashboard 设计)                             |
| L4 AI prompt SoT       | `callytics-infrastructure/lambda/ai-analysis-processor/prompts/standard-v1.0.0.txt` (cancellation/complaint reason 分类) |
| L5 Neon test live data | `restless-boat-70724564` (org `org-long-math-59935021`), `npx neonctl connection-string` 拿 conn                        |
| L6 Existing route      | `apps/api/src/routes/v3/dashboard-multi-store.ts` (Tab 1 SQL 已写, line 165-208)                                         |

**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)
