Contacts Schema
Status boundary:本文的 Contacts inventory 是 2026-06 snapshot;实施前必须重新核查 live schema。原 §3/§4 的 “Final / Delta” 是历史 proposal,尤其
task_progress_events、globalcloseResult和旧 Task Decision contract 不再是 Target。Snapshot source:
callytics-common/src/db/schema/contacts.ts(verified 2026-06-01) Task V3 selected Target: Task V3 Product / Domain Contract Current rollout evidence boundary: Task V3 Rollout 状态快照 Historical Task V2+ audit: 工程审计与设计建议 表名:contacts| 复合主键:(phone, storeId)(State C 终态,callytics-infrastructure#599) 阅读顺序:2026-06 snapshot (§2) → historical proposed target (§3) → historical delta (§4)。Name trust scoring:
firstNameTrustScore决定多写入方 UPSERT 时谁赢,详见 name-trust.md。
1. 表说明
Contacts 是中心化客户档案表,聚合来自 calls / messages / Lead pipeline / AI 分析的数据。一个号码在不同门店下是独立客户记录(跨品牌隔离由 storeId UUID 自动保证)。
- State C 终态:复合主键
(phone, storeId),franchiseId/accountId降级为审计字段(superseded bystoreIdfor isolation) - 所有枚举字段都是
text列 + 注释里写约束,无 DB CHECK,无 const 数组 — 跟tasks不同(tasks 有 DB CHECK + UI 常量强制)。contacts 枚举值靠写入方(AI prompt + Lambda 代码)自觉遵守注释,数据库不拦非法值 - 写入者分类:
- code = 数据管道自动写入(通话管道 / Lead 管道 / 温度衰减规则)
- ai = AI 分析写入(Per-Call / Daily Batch / On-Demand)
- ai+code = AI 分析 + 规则映射共同决定
- ai+staff = AI 可写入,员工可通过 UI 覆盖
2. Current Schema(2026-06-01 live)
2.1 contacts 表全字段(30 字段)
按代码存储顺序排列(PK + 隔离键在前,身份 → 生命周期 → 运营 → 活动 → 行动 → Lead 状态 → 障碍 → 分析 → 画像 → Contact Analysis 追踪 → 风险 → 时间戳)。复杂枚举(firstNameTrustScore / leadStatus)在 §2.2 展开。删除线 + ⚠️ 标记的字段在 Final 退役,详见 §4.1。
2.2 枚举清单(Current)
2.2.1 firstNameTrustScore — 6 levels
UPSERT 时按 score 比较,高分覆盖低分;同分时新的非空值赢。详见 name-trust.md。
2.2.2 leadStatus — 12 values
Lead 漏斗状态(ai 写入)。前端只展示其中 9 个 — showed / trialed / converted 依赖线下数据,V1 不展示。完整映射见 lead-funnel-status.md。
2.3 Indexes(Current)
3. Historical proposed schema(原 Final)
3.1 contacts 表全字段(29 字段,退役 1 个派生字段)
设计原则:Code as Guardrail, AI as Judgment —— AI 只输出不可派生的语义判断,可派生的值由代码派生(详见 §3.2)。从 Current 移除 1 个派生冗余字段:actionNeeded(#15)。lifecycleState 保留(churned re-engage 等需要 AI 语义判断,见 §3.2)。
3.2 退役字段的真值源(派生方式)
为什么
lifecycleState不能退役(2026-06-02 engineer feedback):prompt-builder.ts:154-157 真值表显示 churned stage 对应active/terminal两值,active 需要 AI 从 transcripts 判断 "customer-initiated re-engagement" 语义信号 —— 代码无法纯派生。f(lifecycleStage, leadStatus)公式 incomplete。此外,lead_declinedclose result 设计依赖该字段作为触发信号(见docs/product-design/v1/prompt-improvement/2026-05-prompt-open-issues.md:125)—— 它有 downstream consumer,不是单纯的派生 cache。为什么
actionNeeded退役:历史上 task 系统不完整(closeResult 混乱、无清晰 open/closed 语义),没法可靠用"有无 open task"代表"要不要跟进",所以 AI 单独输出 boolean 当缓存。task 4 层 model + Code as Guardrail 原则下,变成冗余。跨表关联:历史设计里的
tasks.actionNeeded是同类冗余 signal 的“另一半”;V3 Target 用简单 lifecycle 与 derived projection 分开表达,见 Task V3 Product / Domain Contract §5.1。历史演进原则见 unified-pipeline-final.md「两个 actionNeeded 都应退役」。仍真需要 AI 输出的字段(不退役,对照):
lifecycleStage(churned/unknown 带 leadStatus 表达不了的独立信息)、leadStatus、purchaseIntent、hasOpenComplaint(有状态机的跨通话聚合)、customerSummary、goals等 —— 这些是不可派生的语义判断。
4. Historical delta(2026-06 snapshot → 旧 proposed Final)
4.1 Removed(字段退役 + 索引一并移除)
lifecycleState不退役(2026-06-02 engineer feedback,撤销之前的退役决定):churned re-engage 等 case 不能纯派生,需要 AI 语义判断;lead_declinedclose result 设计依赖该字段作为触发信号。详见 §3.2。
contacts.actionNeeded 退役 Phase 拆解
EXISTS perf 担心:tasks 表
idx_tasks_contact_status现已 cover(contactPhone, franchiseId, accountId, status),Phase 1 改名后改为(contactPhone, storeId, status)(随tasks.store_id NOT NULLmigration 同步)。EXISTS 子查询能走 index,dashboard 高频查也 OK。无需 denormalized counter。
4.2 Modified
无字段名变或语义改的 modify 项(枚举值无变化,沿用 §2.2)。
4.3 Added
无新增字段或新表(Final 是从 Current 删 1 字段 actionNeeded,不加新东西)。
4.4 Unchanged(明确保留,reviewer 不用 verify)
phone / franchiseId / accountId / storeId / firstName / lastName / firstNameTrustScore / firstNameUpdatedAt / lifecycleStage / lifecycleState / notes / doNotContact / hasCardOnFile / lastActivityAt / actionNeededReason / suggestedActions / leadStatus / leadStatusReason / leadObjections / leadRejectionReasons / purchaseIntent / purchaseIntentReason / goals / customerSummary / lastContactAnalysisAt / doNotContactUpdatedBy / hasOpenComplaint / createdAt / updatedAt(共 29 字段)。
枚举(firstNameTrustScore 6 值 + leadStatus 12 值)、其余 7 个 indexes 均不变(沿用 §2.2 / §2.3)。
actionNeededReason/suggestedActions仍保留 — 它们是承载内容的字段(reason 文本被前端实际显示、suggestedActions 是 AI 给出的具体建议),不是冗余 boolean。但它们最终归属 contacts 还是并入 task 实体待定,见 §6 Open Questions。
5. Prompt Output ↔ Schema 字段对照(给 reviewer 用)
Reviewer 用本表逐一比对 prompt 输出 JSON 字段是否能写入 contacts 表。 Prompt 全文存在
../unified-pipeline/prompts/。
5.1 Current(Phase 1)— Contact Analyzer 一个大 prompt
5.2 Historical Target — 拆成 Contact Profile + Task Decision 两个 prompt
unified-pipeline-final.md §1 把 Contact Analyzer 拆成 Stage A + Stage B,两个 stage 在同一次 Lambda 调用里跑,共享内存:
关键判断:Contact Profile prompt 拆分是 prompt architecture refactor(让 contact 画像和 task decision 分别迭代),不是 schema migration。reviewer 验:① prompt JSON 字段落在 §3.1 Final 29 字段内 + 枚举值符合 §2/§3 注释;② 退役字段(actionNeeded)不在 prompt 输出中。
5.3 Reviewer checklist
6. Open Questions(待产品拍板)
来源 unified-pipeline-final.md §4 Open Questions。本表不计入 Final scope,产品决定后再 update §2。
7. Appendix A — Re-verification commands
2026-06-01 verified。文档 stale(>30 天)时重跑下方命令并比对。
8. Cross-References
- Name trust scoring(
firstNameTrustScore详细规则): name-trust.md - Contact 详情页 UI 展示: contacts-display.md
- Tasks 表(下游 Task Decision prompt 写入): ../tasks-feature/tasks-schema.md
- Calls 表(上游 per-call AI 写入): ../calls-feature/calls-schema.md
- Coaching 字段(物理在 calls 表内): ../coaching-feature/coaching-schema.md
- Lead funnel 状态映射: ../lead-tracker-feature/lead-funnel-status.md
- Store-level 隔离设计:
docs/system-design/store-level-isolation.md - Unified pipeline final design: ../unified-pipeline/unified-pipeline-final.md
- Task Pipeline Deliverable (Codex): ../tasks-feature/design/task-pipeline-deliverable-codex.md
- All 7 prompts: ../unified-pipeline/prompts/
- 2026-06 snapshot schema:
callytics-common/src/db/schema/contacts.ts - Task V3 selected Target: Task V3 Product / Domain Contract
- Task V3 rollout boundary: Task V3 Rollout 状态快照