Files
lisglosips/docs/CALLER_REALTIME_ANALYTICS_DESIGN.md
T

222 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 主叫号码接通率与应答率实时分析设计方案
日期:2026-08-31
版本:V1.1(按用户修订后的业务口径)
状态:设计文档已完成;代码、数据库迁移、真实联调与上线均待实施。
实施更新(2026-08-31):独立分析链路、API、页面和测试脚本已编写;部署与真实验收结果以实施状态及发布报告为准,上述为方案编制时状态。
## 1. 目标与范围
新增“主叫号码分析”页面,按号码实时查看呼叫是否到达振铃/会话进展阶段,以及是否产生正通话时长。支持异常发现、线路对比、失败原因分析和话单追溯。
本方案中的“接通”是平台自定义的业务阶段指标,不等于 SIP 最终成功应答,也不等于真人接听。此前方案中将接通数定义为 INVITE 成功应答的口径由本文替代。所有新增页面、接口、告警和测试应使用同一个口径版本 `caller-analytics-v1.1`
第一期只做统计和站内异常提示,不自动停号、换号、切换线路或修改计费。外部通知渠道另行配置授权,不在本轮发送消息。
## 2. 当前源码基础与实施差距
本地源码核对依据,不代表生产状态已核实:
- `prisma/schema.prisma``RawCdr` 已有 caller、landingCaller、客户/网关/线路维度、startedAt、answeredAt、endedAt、durationSec、sipCode 等字段。
- `packages/redis/src/cdr-stream.ts` 已有 CDR 事件流契约及消费机制;`apps/worker-cdr/src/rating.ts` 负责话单落库及计费。
- `apps/api/src/modules/dashboard/` 现有指标按话单 SIP 2xx 计算,不能直接复用为本文的“接通数”或“应答数”。
- 现有 RawCdr 没有独立的 first180At、first183At 等过程字段,最终响应码不能证明之前是否发生过振铃/进展。
- 当前通话页面的 durationSec 存在从 startedAt 计算的逻辑,不能未经核验直接作为本文的通话时长。
需新增呼叫过程采集、分析状态及预聚合。既有计费规则不随本次业务术语改变;首页旧指标保留旧版本语义,不可只改标签冒充新口径。后续统一首页时,应单独替换数据来源并展示口径版本。
## 3. 业务口径
### 3.1 总呼叫数与统计视角
默认“落地主叫”视角,可切换“原始主叫”:
| 视角 | 号码与总呼叫数 T | 用途 |
| --- | --- | --- |
| 原始主叫 | 客户送入号码;每个已识别客户的独立业务呼叫计一次 | 分析客户号码整体效果,包括平台内业务拒绝 |
| 落地主叫 | 平台向线路发送的号码;每个实际发出的线路尝试计一次 | 分析具体出局号码及线路表现 |
未认证扫描、OPTIONS、REGISTER 等不计入。客户一次呼叫的鉴权续发、SIP 重传不新增业务呼叫;线路重试用 attemptId 区分。原始主叫视角任一尝试接通则该业务呼叫接通,任一有效通话产生正时长则该业务呼叫应答,均最多计一次。不同线路尝试可分别计数,但不得相加后冒充客户呼叫数。
落地主叫不等于已经验证的被叫终端显示号码。保存号码原值和标准化值,不得无规则删除前缀;同号不同客户不能混淆归属。
### 3.2 接通数 C:达到 180 / 183 或更后阶段
接通数指初始 INVITE 已进入有效呼叫处理,并首次达到以下任一条件的去重呼叫数:
1. 收到与该初始 INVITE/线路尝试匹配的 `180 Ringing`
2. 收到匹配的 `183 Session Progress`,不要求必须带 SDP
3. 没有观测到 180/183,但直接收到初始 INVITE 的有效成功 2xx(通常为 `200 OK`):作为已进入后续阶段计入接通,原因记录为 `DIRECT_FINAL_SUCCESS`
4. 最终权威话单证明该呼叫产生正通话时长,但过程事件缺失:补计接通与应答,标注 `CDR_POSITIVE_DURATION`,不能伪造 180/183 时间。
“INVITE、Trying 均成功”按业务意图理解为 INVITE 已受理并继续推进,不硬性要求抓到 100 Trying。100 是临时处理响应;180 表示尝试提醒用户;183 表示会话进展,不证明实际振铃。状态代理不会向上游转发收到的 100,因此不能以缺少 100 排除后续有效进展。上述协议含义参考 [RFC 3261 §21.1](https://www.rfc-editor.org/rfc/rfc3261.html#section-21.1)。本文选择 180/183 作为业务接通门槛,是产品口径。
仅 INVITE 发出、100、181 或 182 不构成接通。匹配必须核实请求方法、事务与呼叫标识,不能使用 BYE、CANCEL、PRACK、OPTIONS 的 200,也不能把 re-INVITE 当新呼叫。响应与方法相关的依据见 [RFC 3261 §21.2.1](https://www.rfc-editor.org/rfc/rfc3261.html#section-21.2.1)。
线路视角只采信该出局尝试的实际下游响应,不能用本机合成振铃冒充线路进展。接通一旦成立,后续 486、487、超时等不会撤销;只能在证据校正或误关联修复时重算。
### 3.3 未接通数 N:未达到接通门槛
业务定义:未达到上述振铃/会话进展或后续阶段的呼叫数。实时展示必须区分“尚未达到”和“已经结束且未达到”:
- F:已结束且从未接通的呼叫,包括明确拒绝、取消或确认的呼叫超时。
- P:仍在处理中、尚未接通的呼叫;页面显示“待接通”,不作为最终失败。
- U:证据不足而无法判定是否接通的呼叫;页面显示“状态未知”,不强行归类。
已确定的实时未接通数 `N = F + P`,并标注“含待接通 P 次”。在无未知数据时 `T = C + N`;有未知时 `T = C + N + U`,未接通率只能展示已确认范围或暂不展示完整率,不能把 U 隐式算作失败。全部呼叫结束且完成对账后 P=U=0,此时 `N=F=T-C`
接通后无人接听、接通后取消、接通后最终失败,均归入“已接通、未应答”,不归入未接通数。另设“最终结果码分布”,避免把协议最终失败数等同未接通数。
### 3.4 应答数 A:通话时长大于 0
应答数指通话时长严格大于 0 的去重呼叫数。不以单独收到 200、存在 answeredAt 或计费时长大于 0 代替。
- 通话时长采用业务通话段的实际时长,不含呼叫建立、振铃、183 早期媒体时长,不用计费向上取整时长。
- 新采集建议保留 `talkDurationMs` 毫秒值,以 `talkDurationMs > 0` 判断,再格式化显示秒数。正的亚秒通话也应计入,不能因显示向下取整为 0 秒而漏计。
- 原始时长只有整数秒的历史话单,`durationSec > 0` 可计入应答;`durationSec = 0` 不得凭空推定存在亚秒通话,展示历史精度限制。
- 进行中的呼叫由权威会话状态及通话计时起点计算已发生的正时长,实时标注暂定。不能由前端收到 200 后自行运行计时器,也不能用 INVITE 发起时间作通话起点。
- 正时长证据不要求有 RTP 音频分析或真人检测;ACK/会话状态用于核实计时来源,不额外改变“实际通话时长 > 0”的最终业务条件。
- 最终以核验过来源和计时口径的 CDR/会话时长校准,允许撤销错误的暂定应答,并在同一事务调整统计。字段矛盾、负时长或缺少可靠计时证据进入数据质量核对。
应答数是接通数的子集,满足 `0 ≤ A ≤ C ≤ T`。其中 C-A 包含仍在等待应答、已结束但没有正时长,以及应答时长待核实的呼叫,不能全部称为“应答失败”。
### 3.5 指标公式
所有分子分母必须使用相同客户权限、号码视角、线路维度、发起时间窗口和快照,不混用全量总数与筛选后数量。
| 中文指标 | 接口建议字段 | 公式 |
| --- | --- | --- |
| 总呼叫数 | totalCalls | T |
| 接通数 | connectedCalls | C |
| 未接通数(含待接通) | notConnectedCalls | N=F+PU 单列 |
| 应答数 | answeredCalls | A |
| 实时接通率 | connectionRate | C / T × 100% |
| 实时总体应答率 | overallAnswerRate | A / T × 100% |
| 实时已接通应答率 | connectedAnswerRate | A / C × 100% |
分母为 0 时接口返回 null、页面显示“—”。比率由整数计数计算,不平均各时间桶的百分比。数据不完整时,计数与比率应标注“已观测/暂定”,未知或采集缺口不可展示为完整结果。
旧方案的“已判定接通率”不作为主指标,本版以以上三个率为准;不使用未经解释的 ASR 简称混淆业务口径。
示例:T=100C=60(其中 A=30),F=25P=15U=0。未接通数 N=40,其中待接通15;实时接通率60%,实时总体应答率30%,实时已接通应答率50%。C 中其余30次可能仍在振铃,也可能已经结束但没有正时长。
## 4. 时间窗口与状态更新
- 最近5/15/30/60分钟、今日、自定义;以发起时间归桶,后续事件回写原桶。原始视角用业务呼叫发起时间,线路视角用尝试发起时间。
- 聚合粒度默认1分钟。分钟对齐窗口必须在界面展示实际起止;自定义非整分钟边界从状态/事实数据补算边缘,禁止悄悄扩大范围。
- 数据库使用UTC,今日按Asia/Shanghai自然日计算。分子、分母与对比窗口使用统一服务端asOf。
- 记录独立事实标志:是否接通、是否正时长、是否结束、是否数据完整,而非只依赖最终 SIP 码。
- 正常过程:初始计T与P;达到180/183计C并减P;正通话时长计A;结束只更新状态/时长,不重复增加C或A。
- 未接通直接结束:P减1、F加1,N不变。已接通后失败:C不变、A按时长判定,F不增加。
- 业务呼叫未完成全部重试前,不能因一个尝试失败就判定客户呼叫最终未接通。
- 乱序、重复、跨分钟、跨日和迟到事件按稳定标识重算贡献差值;接通数不因183转180重复增加。
- 采集失联不能直接判业务超时;超时失败必须有事务/会话结束依据,否则进入U和数据异常提示。
## 5. 页面与交互
新增“主叫号码分析”,默认落地主叫,支持原始主叫切换。筛选包含时间、号码精确搜索、客户、客户网关、供应商、落地网关、被叫运营商/地区、最小样本、三个率的区间和仅看异常。
列表核心列:主叫号码、客户、总呼叫数、接通数、未接通数、应答数、实时接通率、实时总体应答率、实时已接通应答率、异常状态。待接通、未知、已结束未接通及前窗百分点变化可作为补充列或展开内容;宽表沿用横向滚动与固定标识列,不把业务数字藏到提示框。
号码详情展示分钟趋势(T/C/A及三个率)、线路对比、被叫运营商/地区拆分、180/183/直接成功分布、未接通原因、接通未应答的最终结果、主叫改写映射和关联话单。页面文案解释183不保证实际振铃,接通不等于应答。
每5秒刷新,保留筛选、排序、分页和展开详情。展示数据截至时间、采集延迟、统计口径版本和完整性。刷新失败保留旧数据并提示,不能清空成零。号码访问与导出按客户范围/权限控制,查询接口自行校验权限,不能只依赖前端隐藏。
## 6. 事件链路与数据设计
采用“OpenSIPS呼叫事件 → 独立Redis Stream → 分析Worker → MySQL状态/分钟汇总 → API及缓存 → 页面”。复用现有技术栈,不改变计费Worker消费组或计费规则。
事件类型建议为 CALL_STARTED、ATTEMPT_STARTED、TRYING、RINGING、SESSION_PROGRESS、INVITE_ACCEPTED、TALK_DURATION_OBSERVED、CALL_ENDED、CDR_RECONCILED。均需schemaVersion、eventId、callUid、attemptId(适用时)、nodeId/实例启动标识、occurredAt、receivedAt、初始事务标识、客户/线路/号码快照及证据来源。保留SIP Call-ID用于追溯,但不单独用它承担业务全局唯一性。
过程字段至少包括 first180At、first183At、inviteAcceptedAt、talkStartedAt、endedAt、talkDurationMs、connectedEvidence、finalSipCode、dataQuality。直接成功和CDR补计不得伪造first180At/first183At。通话计时起点须通过当前OpenSIPS实际事件与CDR生成代码核验;字段名不作为真实性证明。
| 建议数据对象 | 关键内容 |
| --- | --- |
| caller_analysis_events | eventId唯一约束、呼叫/尝试、事件时间、类型、来源、重放与去重证据 |
| caller_analysis_states | 以视角粒度保存T/C/N/A贡献、F/P/U状态、号码/维度快照、版本和时长来源 |
| caller_analysis_minute_buckets | 分钟、客户、号码类型/标准化号码、网关/线路、被叫分类、口径版本及各项计数/时长 |
| caller_analysis_alerts | 规则、触发快照、样本量、持续与恢复状态、冷却时间 |
原始呼叫总览和线路尝试汇总分开维护,或通过显式grain字段区分,避免多线路重复累计。分钟聚合的唯一键应使用稳定非空维度键,缺失维度用受控unknown键,不能依赖MySQL可空复合唯一键实现幂等。索引与维度组合在获知号码基数、呼叫量、保留期后验证,不宣称当前机器已具备容量。
分析Worker的去重记录、状态更新和统计差值必须同一数据库事务提交;提交后ACK。Redis缓存不是唯一事实来源;数据库提交后缓存更新失败可失效重建。消费者独立,使用独立去重命名空间,不复用计费锁以免互相吞事件。消费组重领/重放场景须验证;机制参考 [Redis Streams](https://redis.io/docs/latest/develop/data-types/streams/)。
采集失败使用有界补送/持久化方案,设计重试期限、积压容量和告警;不得让统计故障长时间阻塞SIP热路径。超过留存或补送能力产生的缺口必须可见,不能承诺无条件零丢失。最终CDR通过同一callUid/attemptId校准状态,不作为新呼叫重复累计。旧CDR关联不足时先核对,不能仅凭Call-ID猜测匹配。
建议APIGET /api/v2/caller-analytics/summary、/numbers、/trends、/breakdown、/calls;共享视角、时间与维度筛选契约。响应包含counts、rates、asOf、dataThrough、lagMs、metricVersion、qualityStatus、unknownCalls及durationPrecision。完整性无法判定时lagMs可为null,不用“最后一个业务事件的年龄”冒充采集延迟。
## 7. 历史回算与兼容
1. 有可信正时长的历史CDR可回算A并证明对应C;仅有2xx而无时长需核验CDR字段语义才能证明C,不能证明A。
2. 最终486/487/超时的话单可能先有180/183,仅凭最终码无法判定未接通。
3. 若历史信令留存完整且能准确关联,可以补回过程;否则C是已知下界,N和A/C不能输出为完整历史指标。展示“历史过程数据不足”,完整接通率/已接通应答率返回null,允许另列清楚标注的已观测计数。
4. 历史时长精度与新采集不同,应带durationPrecision,不把历史整数秒与新毫秒精度差异解释成业务变化。
5. 新实时数据生效时间、口径版本、回算批次明确记录。新旧版本不能无提示混画同一趋势或用于同比告警。
## 8. 异常规则
分别支持“低接通率”“低总体应答率”“低已接通应答率”“较前窗下降”和“连续已结束未接通”。接通率低侧重排查建立链路;接通率正常但已接通应答率低,侧重排查接通后的结果。仅作排查方向,不凭比例或单个SIP码认定封号。
建议初始模板:最近15分钟、总样本≥50;按已接通应答率触发时还需C≥30;与前一等长窗口比较时两窗均满足样本门槛;下降超过15个百分点且持续多个评估周期再提示。三个率的绝对低值阈值分别配置,原方案20%的示例不能无校准套用到新接通口径。
设置未结束占比上限、数据完整性门槛、冷却、合并和恢复迟滞;仍大量振铃、持续通话缺少可信时长、采集滞后或未知数据过多时暂缓业务告警,转为数据质量提示。连续未接通按发起顺序和已确定结果评估,不能按乱序到达的事件简单累加。
## 9. 实施步骤、风险与回滚
1. 核验当前生产OpenSIPS/CDR事件、通话计时精度、线路重试、号码基数、峰值CPS、保留期与机器资源;只读核验与受控呼叫分开安排。
2. 实施话单分析基础版:视角、列表、正时长应答、历史完整性提示、话单钻取。缺少过程数据时不宣称已支持完整接通统计。
3. 实施过程事件、独立Worker、预聚合、真实后端接口、进行中正时长更新、最终对账与站内异常,完成实时版。
4. 在受控真实呼叫与故障恢复验收后灰度启用;先观察数据质量和统计/计费隔离,再扩大覆盖。
新增影响包括OpenSIPS事件采集开销、Redis队列容量、MySQL写放大、查询和缓存负载。统计新表/新服务独立部署并使用功能开关;回滚可关闭采集钩子和分析Worker/入口,保留证据及新表,不删除既有话单、不调整计费余额和路由。生产修改及受控外呼须按届时任务授权执行。
## 10. 验收用例(设计,尚未执行)
以下单呼叫终态用例默认T=1;应答时长均指可信实际通话时长。未说明缺口的用例假设完整采集。
| ID | 场景 | 预期 |
| --- | --- | --- |
| CRA-001 | INVITE→100→180→200,正通话时长 | C=1N=0A=1;三个率均100% |
| CRA-002 | INVITE→100→183→487,无正时长 | C=1N=0A=0;接通率100%,两个应答率0% |
| CRA-003 | INVITE→100→486,从未180/183/2xx | C=0N=F=1A=0;总体应答率0%,已接通应答率为null |
| CRA-004 | 未见100,直接180,随后取消 | C=1A=0,不因缺100漏计 |
| CRA-005 | 直接初始INVITE 200,正时长 | C=A=1DIRECT_FINAL_SUCCESS,无伪造振铃时间 |
| CRA-006 | 180或直接200,最终实际时长严格为0 | C=1,A=0,不能仅凭200计应答 |
| CRA-007 | 183不带SDP,或带早期媒体但未建立通话 | C=1,A=0,早期媒体不计通话时长 |
| CRA-008 | 仅100且仍在处理 | C=A=0,P=N=1,显示待接通,不标最终失败 |
| CRA-009 | 仅181/182后失败 | C=A=0F=N=1 |
| CRA-010 | 重复INVITE、180、183、200、CDR及消费重放 | T/C/A不重复,N不为负,不重复扣费 |
| CRA-011 | BYE/PRACK/CANCEL的200、re-INVITE | 不新增T/C/A |
| CRA-012 | 先180后486或确认超时 | C=1,A=0;最终失败分布增加,未接通数不增加 |
| CRA-013 | 可信通话时长500ms,页面秒值取整为0 | 新精度A=1;旧整数秒0保持历史精度限制 |
| CRA-014 | 通话进行中已产生正时长,最终CDR校正 | 实时A可见且标暂定;结束不重复,错误暂定值可事务回退 |
| CRA-015 | 一次客户呼叫经两条线路尝试,第二条应答 | 原始视角T=1;线路视角共2次尝试,归属正确 |
| CRA-016 | 同号不同客户、主叫改写及客户越权查询 | 数据不串号/不越权,两个视角的分母保持一致 |
| CRA-017 | 乱序、跨窗口/跨日应答、迟到与重启重领 | 回写发起桶,重放前后计数一致,计费链路不受影响 |
| CRA-018 | 零呼叫或零接通 | 分母0返回null;不显示NaN或无依据的0% |
| CRA-019 | 历史487,缺少过程事件 | 不断言N=1;标未知,完整接通率及A/C不可用 |
| CRA-020 | Redis/MySQL/Worker故障、补送溢出 | 显示滞后/缺口,停止相关业务误告警,恢复重放可对账 |
| CRA-021 | T=100/C=60/A=30/F=25/P=15 | N=40;三个率依次60%/30%/50% |
| CRA-022 | 本机合成180,下游直接失败且无进展 | 线路视角不计C,不能用合成回复美化线路结果 |
| CRA-023 | 只有可信最终正时长CDR,过程丢失 | 补C/A并标CDR证据,不伪造180/183,不重复T |
| CRA-024 | 采集失联导致一个呼叫阶段无法判定 | U单列,T=C+N+U,不自动判失败,率标暂定/不完整 |
功能验收须形成真实SIP信令、API返回、数据库状态/聚合与最终话单的对应证据;mock、静态页面或localStorage不构成功能验收。单元测试用于公式/状态转换,真实联调用于端到端与故障恢复。正常约定负载下事件或可信正时长可用至页面可见的P95目标≤5秒;这是待测目标,不是当前容量承诺。需分别记录采集、消费、API和页面延迟及原有呼叫/计费回归结果。
## 11. 本次交付边界
本次仅完成方案、业务口径、文档索引和验收用例编写与一致性检查。没有修改应用代码、执行数据库迁移、提交/推送Git、连接生产实施或发起真实呼叫。后续开发应以本文V1.1为准,不沿用“2xx=接通/应答”的旧统计实现。
## 12. 实施补充(2026-08-31
- 第11节为方案编制轮次的交付边界。后续用户已授权实施、提交推送与部署测试环境。
- B现场核验发现旧CDR在收到200后直接填入固定6秒,新链路禁止用该字段校准。新链路使用同一OpenSIPS实例的成功应答和结束观察时间差,毫秒精度;进行中以MI确认会话存在后标记暂定正时长。原计费规则与旧CDR生成保持不变,既有固定时长问题需独立整改。
- 采集先由OpenSIPS写结构化本机日志,经rsyslog队列保留,再写独立Redis Stream;本机日志游标在XADD成功后落盘,重放由数据库事务去重。轮转/截断及异常记录进入缺口提示,不承诺无限留存或无条件零丢失。
- 新权限为caller_analytics.view及caller_analytics.view_all;迁移只默认授予超级管理员。其他账号需配置caller_analysis_access客户范围,否则拒绝访问;普通view不隐含全客户权限。
- 卡片和趋势按时间及业务筛选展示整体,最小样本/比例区间/仅告警只筛选号码列表,界面明确提示该范围。单次最多10000个聚合号码,超出要求缩小时间或筛选客户;详细分布最多500个组合,查询窗口最多7天。
- 实际当前OpenSIPS路由只选择一次落地,没有切换重试流程;采集标记单attempt,状态层支持多attempt且有测试,未来新增真实重试必须同步采集独立attemptId,不能直接复用单attempt钩子。
- 历史无过程证据的话单不自动导入完整统计。页面显示新采集生效说明,旧数据不可用不等于呼叫数为0;历史补录仅接受已核验来源的RECONCILE,不接受旧固定时长CDR。
- 告警通过独立进程环境变量配置三个低率阈值和前窗下降百分点;采用样本/未结束占比门槛、连续三次评估及三次恢复,站内展示,不执行停号换路。后续应按实际业务校准默认阈值。