Contacts Specification¶
Contacts 是"一个号码 = 一个客户"的中心化档案表。本文档定义前端 Contacts 页面展示的所有数据及其来源。
完整版字段分析见 Contacts 字段设计。
数据来源分类¶
前端 Contacts 页面的数据来自 4 种来源:
| 来源 | 说明 |
|---|---|
| ① contacts 表 | 直接存储在 contacts 表中的字段,直读即可 |
| ② contact_timeline 表 | 从 timeline 事件聚合/查询 |
| ③ 跨表派生(calls / leads / messages) | 从其他表 SQL 查询派生 |
| ④ API 层计算 | 不存 DB,API 运行时计算 |
一、contacts 表(直读)¶
字段名和类型以 callytics-common/src/db/schema/contacts.ts 为准。
写入者说明¶
| 写入者 | 含义 |
|---|---|
| 代码 | 数据管道自动写入(通话管道、Lead 管道、SMS 管道) |
| ai | AI 分析写入(Pipeline 1 Per-Call / Pipeline 2 Contact Analysis) |
| ai+代码 | AI 分析 + 规则映射共同决定 |
| ai+员工 | AI 可写入,员工可覆盖 |
| 员工 | 仅员工通过 UI 手动操作 |
字段清单(27 个)¶
| # | 前端展示位置 | 前端展示名 | 字段 | 类型 | 写入者 | 说明 |
|---|---|---|---|---|---|---|
| 1 | Table + Profile | Phone | phone |
text (PK) | 代码 | E.164 格式,复合 PK 之一 |
| 2 | 不展示 | — | franchiseId |
text (PK) | 代码 | 所属品牌(franchise 级多租户隔离) |
| 3 | 不展示 | — | siteId |
text (PK) | 代码 | 所属门店(RingCentral providerAccountId) |
| 4 | Table + Profile | Contact | firstName |
text | 代码 | 名(与 lastName 拼接显示) |
| 5 | Table + Profile | Contact | lastName |
text | 代码 | 姓(与 firstName 拼接显示) |
| 6 | Table + Profile + Summary Bar + Filter | Stage | lifecycleStage |
text | ai+代码 | lead / member / churned / unknown |
| 7 | 不展示 | — | lifecycleState |
text | ai+代码 | active / frozen / terminal |
| 8 | Table + Profile | Notes | notes |
text | 员工 | 备注(Profile 中为可编辑 textarea) |
| 9 | Table + Filter | DNC | doNotContact |
boolean | ai+员工 | DNC 标记,全渠道停止触达 |
| 10 | Table + Profile | Card | hasCardOnFile |
boolean | 代码 | 客户是否有信用卡信息存档 |
| 11 | Table + Profile | Last Activity | lastActivityAt |
timestamp | 代码 | 最后活动时间(任何互动都更新) |
| 12 | Table + Filter + Profile | Follow-up / Action Needed | actionNeeded |
boolean | ai | 是否需要跟进(Table 列名 Follow-up,Profile 展示名 Action Needed) |
| 13 | 不展示 | — | actionNeededReason |
text | ai | 为什么需要行动(数据存储但前端不展示) |
| 14a | Profile | Suggested Action | suggestedActions[].action |
jsonb | ai | AI 建议操作,多条用分号分隔展示 |
| 14b | Profile | Priority | suggestedActions[].priority |
jsonb | ai | 取所有建议中最高优先级,单个 badge(High / Medium / Low) |
| 14c | 不展示 | — | suggestedActions[].reason |
jsonb | ai | 为什么建议这个操作(数据存储但前端不展示) |
| 14d | 不展示 | — | suggestedActions[].priorityReason |
jsonb | ai | 为什么是这个优先级(数据存储但前端不展示) |
| 15 | Table + Filter + Profile + Pipeline Bar | Lead Status | leadStatus |
text | ai | 前端展示 9 个:New / Attempted / Connected / Booked / Bad Timing / Not Interested / Unreachable / Lost Contact / Neglected。Showed / Trialed / Converted 后端存储但前端不展示。当 lifecycleStage='lead' 时,Profile 顶部显示 Lead Pipeline 进度条(New → Attempted → Connected → Booked),当前状态高亮 |
| 16 | 不展示 | — | leadStatusReason |
text | ai | 为什么是这个状态(数据存储但前端不展示) |
| 17 | Profile | Lead Objections | leadObjections |
jsonb | ai | 犹豫中的顾虑标签(amber 标签展示,仅 leadStatus = Connected / Booked 时显示) |
| 18 | Profile | Lead Rejection Reasons | leadRejectionReasons |
jsonb | ai | 条件性拒绝的原因标签(red 标签展示,仅 leadStatus = Bad Timing 时显示) |
| 19 | Profile | Purchase Intent | purchaseIntent |
text | ai | high / medium / low(仅 Lead) |
| 20 | 不展示 | — | purchaseIntentReason |
text | ai | 为什么是这个意向等级(数据存储但前端不展示) |
| 21a | Profile | Goal | goals[].goal |
jsonb | ai | 客户目标(仅 Lead) |
| 21b | 不展示 | — | goals[].reason |
jsonb | ai | 为什么判断是这个目标(数据存储但前端不展示) |
| 22 | Profile | Customer Summary | customerSummary |
text | ai | 综合所有通话和 SMS 的整体画像摘要 |
| 23 | 不展示 | — | lastContactAnalysisAt |
timestamp | 代码 | Pipeline 2 最后分析时间(cooldown gate 内部使用) |
| 24 | 不展示 | — | doNotContactUpdatedBy |
text | 代码 | DNC 来源:staff / ai / system(TCPA 合规审计) |
| 25 | Table + Filter + Profile | Open Complaint | hasOpenComplaint |
boolean | ai+代码 | 当前是否有未解决投诉 |
| 26 | Table + Profile | Acquired | createdAt |
timestamp | 代码 | 首次创建时间 = 客户进入系统时间 |
| 27 | 不展示 | — | updatedAt |
timestamp | 代码 | 最后更新时间(Drizzle 自动刷新) |
不展示说明:
所有 Reason 类数据后端存储但前端不展示,供 AI 内部追溯使用,未来可考虑以 tooltip 或展开方式呈现:
- 不展示的独立字段(7 个):#7
lifecycleState、#13actionNeededReason、#16leadStatusReason、#20purchaseIntentReason、#23lastContactAnalysisAt、#24doNotContactUpdatedBy、#27updatedAt- 不展示的 jsonb 内部属性(3 个):
suggestedActions[].reason、suggestedActions[].priorityReason、goals[].reason- 不展示的 leadStatus 枚举值(3 个):
showed、trialed、converted— 当前系统无线下数据(门店签到、课程管理、POS),V1 暂不支持
二、contact_timeline 表(事件查询)¶
| 前端展示位置 | 前端展示名 | 查询方式 | event_type |
|---|---|---|---|
| Table + Profile | Last Complaint | MAX(occurred_at) |
contact.complaint_opened |
三、跨表派生(calls / leads / messages)¶
| 前端展示位置 | 前端展示名 | 来源表 | 查询方式 |
|---|---|---|---|
| Profile | First Attempted | calls | MIN(start_time) WHERE contact_phone=? AND direction='Outbound' |
| Profile | First Connected | calls | MIN(start_time) WHERE contact_phone=? AND call_state='human_conversation' |
| Table | Calls | calls | COUNT(*) WHERE contact_phone=? AND direction='Outbound' |
| Table | SMS | messages | COUNT(*) WHERE from/to_phone_number=? |
| Profile | Communication Log | calls + messages | 通话和 SMS 记录按时间倒序合并展示 |
四、前端计算(不存 DB)¶
Lead Temp 不存数据库,前端根据 contacts.createdAt + contacts.leadStatus + 固定阈值直接判断:
| leadStatus | 判断逻辑 | 结果 |
|---|---|---|
new / attempted |
now - createdAt ≤ 1 天 |
🔥 Hot |
new / attempted |
now - createdAt ≤ 5 天 |
☀️ Warm |
neglected / unreachable |
now - createdAt > 5 天 |
❄️ Cold |
| 其他(connected / booked / badtiming / notinterested / lost_contact) | 固定, Connected 及之后由 Task 系统管理,温度不再适用。详见 Lead Temperature。 | N/A |
温度仅在 pre-contact 阶段适用。
neglected和unreachable是温度系统的终态(温度冷掉后自动判定),固定 Cold。