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

# Contacts Specification

Contacts 是"一个号码 = 一个客户"的中心化档案表。本文档定义前端 Contacts 页面展示的所有数据及其来源。

完整版字段分析见 [Contacts 字段设计](/product-design/future-plans/contacts-field-design.md)。

***

## 数据来源分类

前端 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`、#13 `actionNeededReason`、#16 `leadStatusReason`、#20 `purchaseIntentReason`、#23 `lastContactAnalysisAt`、#24 `doNotContactUpdatedBy`、#27 `updatedAt`
> - **不展示的 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 / bad\_timing / not\_interested / lost\_contact） | 固定, Connected 及之后由 Task 系统管理，温度不再适用。详见 [Lead Temperature](/product-design/v1/lead-tracker-feature/lead-temperature-v1.md)。 | N/A     |

> 温度仅在 pre-contact 阶段适用。`neglected` 和 `unreachable` 是温度系统的终态（温度冷掉后自动判定），固定 Cold。

***
