Pipeline 全景分析

Historical code audit / Non-normative direction(2026-07-15):本文是 2026-05-31 的 pipeline snapshot,保留问题发现与 shared mutation 方向;其中旧 Task Pipeline Deliverabletask_progress_events、global closeResultcreate_closed 和独立 lead_outreach Task 不再是 Target。Task 设计见 Task System Design V3;任何 Current 实现结论都必须重新以 live code 核查。

读者:产品/工程团队成员、AI Agent — 理解系统所有数据 pipeline 的完整流转、交叉依赖、code/prompt 职责边界,以及从 Task Pipeline 设计出发的统一架构方向。

数据来源:2026-05-31 从 callytics-infrastructurecallytics-commonlead-tracking 代码直接验证。以代码为准,文档描述如与代码不符已标注。

与其他文档的关系


一、Pipeline 全景图

系统当前有 5 条活跃数据 pipeline + 2 条辅助 pipeline。每条 pipeline 的触发、处理、写入、下游消费路径如下。


二、逐条 Pipeline 详解

Call Analysis(通话分析)

触发:RC Webhook → ringcentralSubscriptionService → SQS transcribe-queue(5 分钟延迟等录音就绪)

阶段链

阶段Lambda输入处理输出/写入
转录transcribe-processor (concurrency=2)SQS message(callSessionId + 租户信息)下载 RC 录音 → S3 → Deepgram 转录S3 录音文件、DDB call-events、SQS → ai-analysis-queue
AI 分析ai-analysis-processor (concurrency=20)SQS 转录结果3-stage prompt pipeline(见下)Neon calls/contacts/contact_timeline、DDB call-analysis、SQS → dailyBatchQueue

3-Stage AI Prompt Pipelineai-analysis-processor/src/core/stages/):

StagePrompt输入输出职责边界
Pre-Triage无(纯代码规则)原始 transcript 文本PreTriageResult(callState, staffName)或 null代码闸门 — 关键词匹配("mailbox is full"、"please leave your message" 等),节省 ~40% triage AI 调用。0 tokens、<1ms
TriageTRIAGE_SYSTEM_PROMPT通话前 500 字符 + 员工名单 + 通话时长call_state(human_conversation/voicemail/no_answer/system_error/busy_signal)、staff_nameworth_analyzingAI 闸门 — 决定这通电话要不要继续分析。不触发 task、不改 lifecycle
ClassificationCLASSIFY_SYSTEM_PROMPT完整 transcript + business contextcategory/subcategorycustomer_typeoutcomefollow_up.needed + reason 码单通分析 — 不创建/关闭 task;follow_up.needed 是下游 contact-analyzer 的触发信号之一
CoachingCOACHING_SYSTEM_PROMPT完整 transcript + classification 结果员工话术反馈(coaching notes)话术评估 — 不碰 task、不改 lifecycle、不改 lead status

写入表(代码验证)

写入方式关键字段
Neon callsUPSERT(transcribe-processor 写基础字段,ai-analysis-processor 补 33 个 AI 字段)store_idaccount_idcall_statecategoryoutcomefollow_up_needed
Neon contactsUPSERT(更新 lastActivityAt、AI 字段)store_idlifecycle_stagelead_status
Neon contact_timelineINSERT通话事件审计记录
DDB call-analysisPUT(legacy dual-write)完整 AI 分析结果

下游 fan-out:当 follow_up_needed=yes 时,向 dailyBatchQueue.fifo 发 SQS 消息,触发 Contact Analysis 对该联系人重新做跨通话分析。

架构 patternHexagonalhandler.ts 是 composition root,core/stages/ 含 pipeline use case,core/protocols.ts 定义 ports,infrastructure/ 含 AI/Neon/DDB/S3/SQS adapters。这是系统中最成熟的架构实现。


Contact Analysis(联系人跨通话分析 + Task 生命周期)

触发方式(3 种):

触发源事件路径SQS message body
每日 cronEventBridge 06:00 UTC → contacts-analyzer dispatch path → 分页扫描活跃联系人 → dailyBatchQueue{phone, storeId, source: 'cron'}
Call Analysis per-callai-analysis-processor follow_up_needed=yes → SQS dailyBatchQueue{phone, storeId, source: 'per_call_analysis', telephonySessionId, webhookReceivedAt}
员工 On-Demand Refreshstudio-api endpoint → SQS dailyBatchQueue{phone, storeId, source: 'on_demand'}

处理流程handler.ts 第 154-331 行):

1. 从 Neon 读联系人行(phone + storeId PK)
2. 并行聚合:getRecentCalls() + getRecentMessages() + getRecentLeads()
3. 从 Neon 读 pending + closed tasks(按 phone + storeId 隔离)
4. buildUserMessage() 组装 prompt 输入
5. AI 调用(OneRouter/Grok 4.1)→ 输出 ContactsAnalysisSchema
6. writeAnalysisWithTasks() 原子写入

AI Prompt 输出core/prompt-builder.ts):

输出区域内容下游消费
Contacts 字段lifecycleStageleadStatuscustomerSummaryriskSignals 等 18 个字段Neon contacts 表
taskDecisions[]每个 decision 含 action(CREATE/UPDATE/CLOSE/NO_CHANGE)+ typeCategory + priority + closeResult + suggestedActionsNeon tasks + contact_timeline 表
actionNeeded + suggestedActions是否需要人工跟进 + 具体建议Tasks UI 展示

Task 生命周期管理(代码路径:infrastructure/neon-repository.ts:writeAnalysisWithTasks()):

操作条件代码行为
CREATE有新的未解决目标 + 无同类 pending taskINSERT tasks + INSERT contact_timeline
UPDATE同一目标仍开放 + 新证据改变 priority/dueAtUPDATE tasks (priority, suggestedActions, dueAt)
CLOSE目标完成/失效/DNC/wrong-numberUPDATE tasks (status='closed', closeResult, closeType='auto_closed') + INSERT contact_timeline
NO CHANGE新事件无有用 task 信息不操作

写入表

写入内容
Neon contacts18 个 AI 分析字段 + lastContactAnalysisAt
Neon tasksCREATE/UPDATE/CLOSE 操作
Neon contact_timelinetask 生命周期事件(task.createdtask.closedtask.updated

架构 patternSemi-hexagonal — 有 infrastructure/protocols.ts 定义 ports(AIClient、ContactsReader、ContactsWriter),有 infrastructure/neon-repository.tsinfrastructure/ai-client.ts 作 adapters。但 core/ 只有 models.ts + prompt-builder.ts,没有显式的 pipeline/use-case 层。业务逻辑主要在 handler.ts:analyzeContact()neon-repository.ts:writeAnalysisWithTasks() 里。


SMS Processing(短信处理)

触发:RC Webhook → ringcentralSubscriptionService → SQS message-processing-queue

处理message-processor/src/core/message-processing.ts):

  1. 从 RC API fetch message 详情
  2. S3 保存 MMS 附件
  3. 原子 db.batch() 写入 Neon

写入表

写入内容
Neon messagesSMS/VM 消息记录(direction、content、attachments)
Neon contactsUPSERT(更新 lastActivityAt
Neon contact_timelinemessage.received / message.sent 事件

关键特征

  • 不调用 AI — 纯数据持久化管道
  • 不触发下游 pipeline — 不向 dailyBatchQueue 发消息
  • SMS 内容要等到 Contact Analysis 的每日 cron 或 per-call 触发时才会被 AI 分析

架构 patternHexagonal(轻量) — 有 core/ + infrastructure/ 分层,但没有 AI 相关 ports。


Lead Processing(线索处理)

触发lead-tracking Lambda(IMAP 轮询邮箱)→ EventBridge LeadCreated → SQS → lead-processor

处理lead-processor/src/core/persist-downstream.ts):

单个原子 db.batch() 写入 3 个实体:

await client.batch([
  // 1. contacts UPSERT — lifecycleStage='lead', leadStatus='new'
  client.insert(contacts).values({...}).onConflictDoUpdate({...}),
  // 2. tasks INSERT — lead_outreach, high priority, 5-min SLA
  client.insert(tasks).values({
    taskType: 'lead_outreach',
    typeCategory: 'lead_outreach',
    sourceType: 'lead',
    priority: 'high',
    dueAt: computeDueAt('high', now, { slaMinutes: 5 }),
    suggestedActions: [{ action: 'Call the lead back promptly', ... }],
  }).onConflictDoNothing(),
  // 3. contactTimeline INSERT — lead.created 事件
  client.insert(contactTimeline).values({...}).onConflictDoNothing(),
]);

关键特征

  • 不调用 AI — Task 创建是硬编码的确定性规则(5 分钟 SLA、high priority)
  • 直接写 tasks 表 — 不经过任何 Task Orchestrator 或 contacts-analyzer
  • suggestedActions 是模板 — 不是 AI 生成的,是代码里写死的默认建议
  • onConflictDoNothing — 通过 uq_tasks_source_lead 唯一索引防重复

架构 patternThin handler + core functionhandler.ts 做 SQS 解析和 Neon client 管理,core/persist-downstream.ts 是纯业务逻辑(no AWS SDK import)。


Analytics / Periodic(定时报告和监控)

Lambda触发功能技术栈
analytics-generatorEventBridge daily 7:00 AM ET汇总 call-analysis 数据 → PDF/HTML 报告 → 邮件Python 3.13
force-refresh手动触发手动重算分析数据Python 3.12
storeid-coverage-monitorEventBridge hourly(仅 test)UNION ALL 查 6 张 Neon 表 NULL storeId 覆盖率TypeScript
reconciliation-orchestratorEventBridge every 3h(当前 DISABLED)扫描 DDB 找遗漏/失败的通话 → reconciliation-queueTypeScript
reconciliation-workerSQS reconciliation-queue逐条修复 → 重新注入 transcribe-queueTypeScript

三、跨 Pipeline 依赖矩阵

3.1 共享 Neon 表 — 多 Writer 冲突风险

Writer 1Writer 2Writer 3冲突风险
contactstranscribe-processor(RC metadata + name trust=60)ai-analysis-processor(AI 字段 + name trust=40/80)message-processor(lastActivityAt)、contacts-analyzer(18 个 AI 字段)、lead-processor(UPSERT, trust=80)、studio-api(staff 手动, trust=100) — 6 个 writer 各写不同字段集。name trust scoring(WEBHOOK=20 < AI_TRANSCRIPT=40 < RC_API=60 < LEAD=80 < STAFF=100)分散在 5 个 Lambda 代码库,没有共享 enforcement
contact_timelineai-analysis-processormessage-processorcontacts-analyzer、lead-processor — INSERT-only(append-only audit log),onConflictDoNothing 用 idempotencyKey 去重
taskslead-processor(lead_outreach CREATE)contacts-analyzer(follow_up CREATE/UPDATE/CLOSE)studio-api(manual CREATE/CLOSE/REOPEN) — 三个独立入口,各自用不同的去重策略(uq_tasks_source_lead / effectivePendingCategories code check / 无去重)。没有统一的 mutation 入口
callstranscribe-processor(基础字段)ai-analysis-processor(33 个 AI 字段) — 两步 UPSERT 是设计好的顺序(先转录再 AI),不会并发

3.2 SQS 队列 — 生产/消费关系

队列生产者消费者类型
transcribe-queueringcentralSubscriptionService、reconciliation-workertranscribe-processorStandard
ai-analysis-queuetranscribe-processor(via S3 Object Created → EventBridge)ai-analysis-processorStandard
message-processing-queueringcentralSubscriptionServicemessage-processorStandard
dailyBatchQueue.fifoai-analysis-processor(per-call fan-out)、contacts-analyzer dispatch path、studio-api(on-demand)contacts-analyzer workerFIFO
lead-processor-queuelead-tracking(via EventBridge)lead-processorStandard
reconciliation-queuereconciliation-orchestratorreconciliation-workerStandard

3.2.1 Onboarding historical backfill 的正确位置

新店 onboarding 需要“把过去 14 天数据导入系统”时,不应该新写一条绕过现有 pipeline 的 ETL。

Calls:
operator / control-plane
  -> reconciliation-queue
  -> reconciliation-worker
  -> RingCentral call-log
  -> synthetic webhook
  -> transcribe-queue
  -> transcribe-processor
  -> ai-analysis-processor
  -> contacts-analyzer

Messages:
operator / control-plane
  -> message historical backfill job(待补)
  -> RingCentral message-store
  -> existing message-processor write path

Calls 已经有可复用的机制:reconciliation-worker 支持 reconciliation_window.startTime/endTime,检测 missing calls 后把 synthetic webhook 投进 transcribe-queue。Onboarding 只需要一个更清晰的 operator trigger。

Messages 目前还没有同级的 historical backfill job。studio-api 能按 storeId/dateFrom/dateTo 读取 RingCentral message-storemessage-processor 能持久化 message-store webhook,但中间缺“按日期拉历史消息并复用 message-processor 写入”的 job(跟踪:retaintive/callytics-infrastructure#1156)。

3.3 Pipeline 交叉点

核心交叉点

  1. P1 → P2 的 per-call fan-out — ai-analysis-processor 通过 follow_up_needed=yes 触发 contacts-analyzer 重新分析
  2. P2 读取所有其他 pipeline 的产出 — contacts-analyzer 聚合 calls(P1 产出)+ messages(P3 产出)+ leads(P4 产出)做跨维度 AI 分析
  3. contacts 表是 4 个 pipeline 的共享写入点 — 是系统中 writer 最多的表
  4. tasks 表有 2 个独立写入入口 — lead-processor 直接创建 lead_outreach,contacts-analyzer 管理 follow_up 生命周期

四、Code vs Prompt 职责边界(现状)

4.1 各 Pipeline 的职责分配

PipelineCode 负责AI/Prompt 负责边界清晰度
P1 Call Analysis转录调度、录音下载、S3 存储、DB 写入、SQS fan-out、重试/幂等Triage(闸门)、Classification(语义分类)、Coaching(话术评估)清晰 — Hexagonal 架构,core/ports/adapters 分层明确
P2 Contact Analysis联系人分页扫描、数据聚合(calls+messages+leads+tasks)、task mutation 执行(CREATE/UPDATE/CLOSE)、幂等/去重/并发控制客户画像分析、task 决策提案(taskDecisions[])、priority 判断、closeResult 判断中等 — AI 提案 + code 执行的分工存在,但 writeAnalysisWithTasks() 里的 task mutation 逻辑散落在 neon-repository 里,不是独立的 Orchestrator
P3 SMS全部(RC API fetch、MMS 存储、DB 原子写入)无 AI 介入N/A — 纯 code pipeline
P4 Lead全部(IMAP 解析、task 创建、contact UPSERT)无 AI 介入N/A — 纯 code pipeline,task 创建是硬编码规则

4.2 Task Pipeline Deliverable 提出的理想模式

Deliverable 定义了一个 8 步 responsibility chain(第 2 节):

Input event → Filter layer (code) → Task Orchestrator (code) → Read business context
→ AI semantic judgment → Code execution layer → DB transaction → Downstream consumption

核心原则:"AI proposes, code executes." AI 只输出 taskDecisions[] 提案,所有状态变更由 Orchestrator 代码验证并执行。

4.3 现状 vs 理想的差距

维度Deliverable 理想现状Gap
统一入口所有 task mutation 经过 Task Orchestratorlead-processor 直接写 tasks 表,contacts-analyzer 通过 writeAnalysisWithTasks()Task 写入有 2 个独立入口,没有统一 Orchestrator
Filter layer代码层在 AI 之前做 dedup、merge、trivial 过滤P1 有 Triage 闸门(但是 AI 做的不是代码做的);P3 SMS 没有 AI 过滤Filter 逻辑混在 AI prompt 里而不是代码层
progress vs outcomeno_answer/left_voicemail 是 progress event(新表 task_progress_events),不是 closeResult当前 closeResult 枚举 18 个值混合了 progress(no_answerleft_voicemailcallback_later)和 business outcome(convertedcancel_saved进展和结果没分离,closeResult 枚举过载
create_closed 路径支持 "当场解决" 的事(来电预约、来电投诉当场解决) → 创建后立即关闭closeType 只有 auto_closed + manual_closed,没有 create_closed无法记录 "来了就完成" 的工作

五、Schema 健康检查

5.1 Task 枚举对齐状态

枚举Schema 定义Prompt 实际使用V1 文档描述状态
TASK_TYPE_CATEGORY9 个值8 个(排除 lead_outreach,由 lead-tracking 创建)12 个场景标签(命名/粒度不一致)⚠️ V1 文档 stale
TASK_CLOSE_RESULT18 个值(代码)13 个(prompt 不知道 booked/cancelled/already_member/callback_later/left_voicemailV1 文档写"11 个"或"13 个"⚠️ Prompt vs Schema 不同步
TASK_CLOSE_TYPE2 个(auto_closed/manual_closedDeliverable 提议加 create_closed🔲 待实现
TASK_STATUS2 个(pending/closedDeliverable 建议语义映射 pendingopen设计决策

5.2 关键字段覆盖

字段定义位置写入方问题
tasks.executorTypeDeliverable 提议(human/ai_agent/system)尚未创建🔲 新字段
tasks.attemptCountDeliverable 提议(反规范化计数器)尚未创建🔲 新字段(需 task_progress_events 表)
task_progress_eventsDeliverable 提议(新表)尚未创建🔲 新表 — 承载从 closeResult 中剥离的 progress 事件
contacts.lastContactAnalysisAtSchema 已定义contacts-analyzer 写入✅ 正常
tasks.storePhoneSchema 已定义contacts-analyzer 写入(审计字段)✅ 正常
closeResult = booked / cancelledSchema 已加入(infra #164)❌ 前端 dropdown 缺失、prompt 不知道这 2 个值⚠️ Schema/UI/Prompt 三方不同步

六、Gap Analysis — 从 Task Pipeline 推广到全系统

Gap 0(P0):NeonRetryProcessor DLQ retry 不发 LeadCreated 事件 ✅ 已修复(2026-06-08,lead-tracking#201)

原现状:lead-tracking 的 Neon 写入失败时进 DLQ,NeonRetryProcessor 负责 retry。但 retry 成功后不发 EventBridge LeadCreated 事件

原后果:DLQ retry 成功的 leads 在 Neon leads 表里存在,但永远没有下游 contacts/tasks/timeline

修复(lead-tracking#201 PR):

  • publishLeadCreatedEvent 到共享 module src/lead-created-event.ts,poller 和 NeonRetryProcessor 共用同一份事件契约
  • NeonRetryProcessor persistLeadPipeline 成功后调用 publishLeadCreatedEvent(row, tenantId),使用 persistLeadPipeline 返回的 tenantId 避免下游再查 control-plane
  • 显式检查 PutEventsCommand 返回的 FailedEntryCount > 0(AWS SDK 在 200 OK + per-entry failure 时不抛,会静默丢失事件)
  • 给 NeonRetryProcessor Lambda 加 events:PutEvents IAM grant(之前只有 poller 有)
  • 区分 stage-tagged log:Neon 失败 vs EventBridge publish 失败,操作人员能清晰判断哪一步出问题
  • 详细写入流程见 lead-tracking 写入流程

Gap 1:Task 写入缺乏统一 Orchestrator

现状(3 个独立入口):

  • lead-processor — 直接 INSERT tasks(lead_outreach),hardcoded 5-min SLA、hardcoded suggestedActions,去重靠 uq_tasks_source_lead unique index
  • contacts-analyzerwriteAnalysisWithTasks() 处理 AI 产出的 CREATE/UPDATE/CLOSE,去重靠 effectivePendingCategories code check + DB unique constraint
  • studio-api — HTTP API 直接写(manual CREATE/CLOSE/REOPEN),无 AI 判断,无共享 state machine

行为不一致

行为lead-processorcontacts-analyzerstudio-api
去重uq_tasks_source_lead indexcode check + DB constraint
closeResult不 closeAI 输出, 代码校验UI dropdown(含 progress 值)
dueAt 计算computeDueAt(high, now, slaMinutes=5)computeDueAt(priority)员工直接设
timeline 审计lead.createdtask.created / task.status_changedtask.status_changed

建议方向:Deliverable 的 Task Orchestrator 应该成为 唯一的 task mutation 入口。lead-processor 调 Orchestrator 的 createTask(type='lead_outreach', source='lead', ...) 而不是直接 INSERT。lead-processor 的 deterministic create 可以作为 Orchestrator 的 bypass-AI 模式

Gap 2:SMS Pipeline 没有 AI 分析

现状:message-processor 只做数据持久化,不做任何语义分析。SMS 内容要等 Contact Analysis 的 cron(最多 24 小时后)才会被 AI 看到。

问题

  • 客户发 "STOP" → 应立即标 DNC + 关闭所有 pending tasks → 现在要等到次日凌晨
  • 客户 SMS 回复表达高意向 → 应立即触发 contacts-analyzer → 现在没有 per-message fan-out

建议方向:在 message-processor 末尾加一步 Code Filter(不是 AI):

  • 检测 "STOP"/"UNSUBSCRIBE" → 立即写 DNC + 触发 task close
  • 检测有意义回复(非自动回复、非单字) → 向 dailyBatchQueue 发 SQS 触发 Contact Analysis

Gap 3:contact_timeline 缺乏统一写入标准

现状:4 个 pipeline 各自写 contact_timeline,event_type 命名、actor_type 取值、newValue 结构都是各 pipeline 自行定义。

问题:没有中央的 "timeline event catalog",新增 event type 时容易命名不一致或遗漏字段。

建议方向:在 callytics-common 里定义 timeline event schema(event_type 枚举 + 每种 event 的 required fields),各 pipeline 的 timeline 写入通过 buildTimelineValues() helper 强制走 schema validation。

Gap 4:contacts 表 name trust scoring 分散,storeId null 策略不统一

Name Trust 现状:contacts 表的 firstName/lastName 由 6 个 writer 写入,各自带不同的信任分数:

WEBHOOK=20 < AI_TRANSCRIPT=40 < RC_API=60 < LEAD=80 < STAFF=100

这个 trust scoring 逻辑(SQL CASE WHEN + GREATEST分散在 5 个 Lambda 代码库中,没有共享的 enforcement layer。如果某个 pipeline 的 UPSERT 忘了带 trust guard,低信任来源可以覆盖高信任来源的姓名。

storeId null 现状:4 个 pipeline 对 null storeId 的行为不一致:

Pipelinenull storeId 行为
transcribe-processor跳过整个 persistCallBatch
message-processor跳过 contacts + timeline(只写 messages)
contacts-analyzer继续处理但 logger.warn
lead-processor从 StoresV2 DDB 查 fallback

建议:写一份 store-id-null-policy.md 明确每个 pipeline 的策略,并在 storeid-coverage-monitor 增加 per-pipeline breakdown 维度。

Gap 5:Prompt 输入不完整

现状:contacts-analyzer prompt 的 PENDING TASKS 输入只包含 taskId、typeCategory、priority、dueAt、first suggested action。不包含

  • 员工手动调整的 dueAt 变更历史(dueAtChangelog
  • task progress events(Deliverable 提议的新表,当前不存在)
  • 来自其他 pipeline 的最新 SMS 内容(如果距离 cron 运行时刚收到)

问题:AI 做 task 决策时缺少关键上下文,导致可能重复创建已经在跟进的 task,或覆盖员工手动调整的 dueAt。

Gap 6:Per-Call AI(P1)和 Contact-Level AI(P2)的职责边界需要重新审视

现状

  • P1 Classification 输出 follow_up.needed + reason 码 → 是 P2 的触发信号
  • P1 Classification 输出 outcome.result(booked/cancelled/pending_follow_up)→ 是 P2 做 task 决策的证据之一
  • P1 不直接操作 task → 这是设计意图("单通信息不足以判断 task")

问题:P1 的 follow_up.needed=yes 触发 P2 重新分析,但 P2 的 AI 还需要自己重新读一遍那通电话的内容来做判断。这意味着同一通电话被 AI 分析了 两次(P1 一次 + P2 一次),且 P2 的分析范围更大(全量 calls + messages + leads)。

这不一定是问题 — P1 和 P2 的分析粒度不同(单通 vs 跨通话),两次分析的目的不同。但 token 成本可以优化:P2 可以直接消费 P1 的结构化输出(category/outcome/follow_up)作为预处理过的事实,而不是从 transcript 重新推断。


七、架构一致性评估

Pipeline架构 PatternHexagonal 成熟度有无 Ports有无 Core有无 Adapters
P1 Call AnalysisHexagonal★★★★protocols.tscore/stages/✅ AI/Neon/DDB/S3/SQS
P2 Contact AnalysisSemi-hexagonal★★★protocols.ts⚠️ 只有 models + prompt-builder✅ AI/Neon
P3 SMSHexagonal(轻量)★★☆core/infrastructure/
P4 LeadThin handler + core★★core/persist-downstream.ts
P5 AnalyticsScript

建议:不需要所有 pipeline 都达到 P1 的 Hexagonal 深度。判断标准是"业务规则是否值得从 runtime/vendor 细节里剥离出来"(引自 backend-patterns.md §6)。P3 和 P4 业务规则简单,thin handler 够用。P2 的 task mutation 逻辑值得抽成独立的 Orchestrator core。


八、Prompt 设计对 Pipeline 架构的影响

当你为每个 pipeline 设计 prompt 时,需要清楚 prompt 能读到什么数据

Prompt需要的数据来源 pipeline来源表当前是否可用
P1 Triage通话开头 transcript + 员工名单当前通话staff + RC API
P1 Classification完整 transcript + business context当前通话RC transcript + DDB config
P1 Coachingtranscript + classification 结果P1 前一 stage内存传递
P2 Contact Analyzer全量 calls + messages + leads + pending tasks + closed tasks + contacts 快照P1 + P3 + P4 + P2 历史Neon calls/messages/leads/tasks/contacts✅ 但缺 task progress events + dueAtChangelog
Future: Task Orchestrator当前 task 状态 + progress events + 最近活动 + allowed enum set + store policyP2 AI 输出 + code policyNeon tasks + task_progress_events + store_config🔲 需要 task_progress_events

关键洞察:prompt 的输入质量直接取决于上游 pipeline 写入的数据质量。如果 SMS pipeline 不做 AI 分析,那 P2 prompt 里的 RECENT MESSAGES 就只有原始文本,没有预处理的 intent/sentiment。如果 lead-processor 不记录 progress events,P2 prompt 就不知道员工已经打了几次电话给这个 lead。


九、实际运行架构全景(2026-06 code-verified)

从代码直接验证的端到端架构 — 从外部源到 Neon 写入,含 shared mutation layer 和尚未接入的 Control Plane / studio-api。

┌─────────────────────────────── 外部源 ──────────────────────────────────────────┐
│                                                                                 │
│  📞 RingCentral (call ended)     💬 RingCentral (SMS)        📧 健身房网站 (Lead) │
│           │ webhook                       │ webhook               │ IMAP poll   │
│           ▼                               ▼                       ▼             │
│  ┌────────────────────────────────────────────┐       ┌───────────────────────┐ │
│  │ ringcentralSubscriptionService (独立 repo)  │       │ lead-tracking (独立)  │ │
│  │ • call → callLogQueue (300s delay)          │       │ • 写 leads + DDB     │ │
│  │ • SMS → message queue                       │       │ • 发 LeadCreated EB  │ │
│  └──┬──────────────────────┬───────────────────┘       └──────────┬────────────┘│
│     │ SQS                  │ SQS                                  │ EventBridge │
└─────┼──────────────────────┼──────────────────────────────────────┼─────────────┘
      ▼                      │                                      ▼
═══ Stage 1: source ingest ══│══════════════════════════════════════════════════════

┌────────────────────────┐   │                          ┌──────────────────────────┐
│ transcribe-processor   │   │                          │ lead-processor           │
│ • 下载录音 → Deepgram   │   │                          │ • applyTaskAction(       │
│ • 写 calls 表           │   │                          │    create_open,          │
│ • timeline:            │   │                          │    lead_outreach)        │
│   transcribe.completed │   │                          └──────────┬───────────────┘
└────────┬───────────────┘   │                                     │
         │ S3 → EB → SQS    │                                     │
         ▼                   │                                     │
┌────────────────────────┐   │                                     │
│ ai-analysis-processor  │   │                                     │
│ • AI: Triage/Classify/ │   │                                     │
│   Verify/Coaching      │   │                                     │
│   (DeepSeek V4 Flash)  │   │                                     │
│ • 写 calls (33 字段)    │   │                                     │
│ • 发 SQS 触发下游       │   │                                     │
└────────┬───────────────┘   │                                     │
         │ SQS               │                                     │
         ▼                   ▼                                     │
   dailyBatchQueue ◄── cron 06:00      ┌───────────────────────┐   │
   (FIFO)          ◄── on_demand       │ message-processor     │   │
         │                             │ • 入库 messages 表     │   │
         ▼                             │ • S3 存 MMS 附件       │   │
═══ Stage 2: contact-level ════════════│ • STOP → setDNC()     │   │
                                       └───────────┬───────────┘   │
┌──────────────────────────────┐                   │               │
│ contacts-analyzer            │                   │               │
│ • 聚合 calls+msgs+leads+tasks│                   │               │
│ • AI: 18 画像字段 +           │                   │               │
│   taskDecisions[] (6 action) │                   │               │
│ • applyTaskAction() ×N       │                   │               │
│ • inline UPDATE contacts     │                   │               │
└──────────┬───────────────────┘                   │               │
           │                                       │               │
           ▼                                       ▼               ▼
═══ Stage 3: shared mutation layer (callytics-common/src/domain/) ══════════════════

    ┌──────────────────────────────────────────────────────────────────┐
    │           Policy Guard (8 checks, pure functions)                │
    │  storeId / DNC / hallucination / state / typeCategory / etc.    │
    └──────┬──────────────────────┬──────────────────────┬────────────┘
           ▼                      ▼                      ▼
    ┌──────────────┐    ┌────────────────┐    ┌──────────────────┐
    │ Task         │    │ Contact        │    │ Timeline         │
    │ Orchestrator │    │ Writer         │    │ Writer           │
    │ 6 actions    │    │ upsertIdentity │    │ 16 eventType     │
    └──────┬───────┘    └────────┬───────┘    └──────┬───────────┘
           ▼                     ▼                   ▼
═══ Stage 4: Neon PostgreSQL (db.batch() 原子事务) ═════════════════════════════════

    tasks          contacts         contact_timeline    task_progress_events
    (open/closed)  (画像 SoT)       (审计 + feed)       (progress SoT)

═══ 独立平面(尚未接入) ═══════════════════════════════════════════════════════════

    control-plane repo: tenants / tenant_members / tenant_store_mappings
    ⚠️ 尚无 callytics-infra / studio-website-monorepo 代码消费

    studio-api tasks routes: close/reopen/postpone 仍走老逻辑
    ⚠️ 已接 @retaintive/common 但只用 schema 不用 domain modules

十、建议的统一架构方向

基于 Task Pipeline Deliverable 的 "Task Orchestrator" 模式,推广到整个系统的统一架构:

每条 Pipeline 遵循同一个 pattern:

Input Event → Code Filter → Context Reader → AI Judgment (optional) → Orchestrator → DB Transaction → Downstream

其中:
- Code Filter: dedup、merge、trivial 过滤(不是 AI 做的)
- Context Reader: 聚合该决策需要的所有数据
- AI Judgment: 只输出结构化提案,不执行
- Orchestrator: 验证提案 + 执行状态变更 + 幂等/并发控制
- DB Transaction: 原子写入
- Downstream: SQS fan-out / event publish

这不是要重写所有 pipeline — 是一个渐进式的统一方向,按优先级排序:

优先级项目理由
P0推进 issue #771 — studio-api 迁移到 Neon 读取 call-analysisDDB 双写期间无 compensating transaction,是数据一致性风险
P1Task Orchestrator — 统一 lead-processor / contacts-analyzer / studio-api 的 task 写入Deliverable 已设计完,3 个入口的去重/closeResult/dueAt/审计全不一致
P1SMS Filter Layer — message-processor 末尾加 code-level 过滤,触发 P2STOP → 立即 DNC;有意义回复 → 立即触发 contacts-analyzer;当前延迟 24h
P1统一 storeId null handling policy4 个 pipeline 行为不一致,需明确策略文档
P2Timeline Event Catalog — callytics-common 统一 event_type 定义6 个 writer 各自定义 event_type,无中央 catalog
P2CLAUDE.md Product Context 更新 AI 模型描述文档写 "Kimi K2/Claude",代码实际是 DeepSeek V4 Flash via OpenRouter
P2closeResult 前端 dropdown 补齐 booked / cancelledSchema 有 18 个值,UI 只有 16 个
P2Prompt Input Enrichment — task_progress_events 加入 P2 promptAI 缺少员工手动调整和进展历史
P3reconciliation-orchestrator 去除 orangeTheory hardcode单租户假设嵌入多租户架构
P3contacts.hasCardOnFile 清理决策零 writer,永远 NULL
P3AI 输出字段增加 DB CHECK constraintscalls 表 AI 字段无 DB-level 校验

相关文档