docs: record caller analytics test deployment and acceptance limits

This commit is contained in:
hectorzhao
2026-08-31 17:57:19 +08:00
parent 2a3dcb0c68
commit a116926867
2 changed files with 655 additions and 0 deletions
@@ -0,0 +1,56 @@
# 主叫号码分析测试环境交付记录
日期:2026-08-31。状态:代码已提交推送、测试 B 已部署、基础真实信令与后端对账通过;完整功能验收尚未完成。
## 版本与环境
- 应用版本:`2a3dcb0c68c52476d4ad38b9924607ce45acdcf5`,远端 `origin/main` 已推送成功。
- 测试入口:<https://100.90.90.91/>,菜单“主叫号码分析”。
- 当前发布目录:`/opt/lisglosips/releases/caller-analytics-20260831T095225Z`
- 本次发布备份:`/var/backups/lisglosips-caller-analytics/20260831T095225Z`;最初引入功能前的发布仍在 `/opt/lisglosips/releases/s57-ui-number-library-20260827T124730Z`
- 新增7张分析表与独立分析进程;原 CDR/计费进程未替换。本次没有部署 A/T,没有呼叫公网电话。
- 部署后 OpenSIPS、API、CDR Worker、分析 Worker 均为 active`/api/v2/health/ready` 的 MySQL/Redis 检查均为 ok。
## 本次实现
OpenSIPS 观察事件经本机 rsyslog 文件、独立 Redis Stream 进入事务状态计算,提供原始主叫和落地主叫两种统计视角、分钟趋势、号码筛选、详情证据、三个业务比例、客户范围控制及站内异常提示。沿用设计 V1.1:180/183及后续成功阶段计接通,可信正时长计应答,未接通拆分待接通与结束未接通,未知单列,分母0返回null。
实现与运行限制仍以[设计补充](CALLER_REALTIME_ANALYTICS_DESIGN.md#12-实施补充2026-08-31)及下方未验收项为准,不把基础功能部署视为整个设计全部验收通过。
## 实际验证
- TypeScript构建、ESLint、Vite生产构建、Prisma schema校验通过;全量 Vitest 29文件、116项通过。
- B 本机真实 UDP SIP UAC/UAS,通过平台原有路由,不使用 mock API 或 localStorage。
- 测试客户与网关 `cra_20260831`,主叫 `99100001`,仅 `127.0.0.1:50630`,费率0。验收后入口/出口已禁用;保留分析证据。
- 真实API验证401、无客户授权403、跨客户查询403、授权客户可见、客户筛选项不越权、非法查询400。
- 全部已采集测试呼叫的分钟桶与状态表逐字段相等:T=24、C=16、A=8、F=8、P/U/active均为0。重放一通呼叫的4条持久化事件,均返回duplicate,聚合不变。
- 末轮从 `2026-08-31T09:54:12.271Z` 开始的6通测试:T=6、C=4、A=2、N=2C/T=66.6667%、A/T=33.3333%、A/C=50%。短通话观察时长301ms。
| 实际信令场景 | C | A |
| --- | --- | --- |
| 180后486 | 1 | 0 |
| 183后487 | 1 | 0 |
| 直接486 | 0 | 0 |
| 无180/183,直接200ACK后约1.5秒BYE | 1 | 1 |
| 180、200ACK后约0.3秒BYE | 1 | 1 |
| 仅100后486 | 0 | 0 |
机器证据见 [SIP/API/DB验收JSON](../tests/reports/caller-analytics-real-20260831.json)。该证据只含本次合成测试号码,无登录凭据。
## 联调修复
修复了OpenSIPS编码转换名称、rsyslog完整程序路径匹配、MySQL趋势BigInt序列化、分钟桶JSON键、验收脚本的用户角色关联,以及发布所需Prisma引擎执行权限。原生效配置引用了未加载的旧Lua SHA,现按已安装Lua内容核对SHA并加载,不改Lua路由逻辑。
对已产生的测试分析状态,在暂停独立分析进程后事务重建了派生分钟桶;未删除原始事件、状态或CDR。新增维护脚本需明确停分析Worker后才能使用。
另补充显式最终零时长优先于较晚暂定时长、END不自动清除缺过程未知、缺口标记跨进程重启保留的逻辑和回归用例。未测得端到端延迟时返回null,不再把0当实测延迟。
## 未完成验收与启用限制
1. **页面验收未完成**:Chrome能列出测试标签页,但读取DOM、页面截图多次超时;没有取得本次新页面的有效截图、控制台记录或交互结果。不能以Vite构建/API成功代替UI验收。
2. **时长证据边界未完成**:末轮SIP脚本没有SDP/RTP;它证明响应、ACK/BYE及信令计时链路,不证明媒体协商或实际音频。当前以成功响应至结束观察的毫秒差计算结束时长;无ACK、协商失败、取消竞态、对话超时等仍需完善证据适配与真实验收。不能据此宣称全部应答边界已满足。
3. **采集完整性与可靠性未完成**:运行心跳不证明整个查询窗口完整。页面明确提示此边界,health标记OBSERVATIONS_ONLY。生产端连续水位、停采识别、共享Redis资源隔离、日志/Stream/数据库留存、毒事件处理、断电恢复与满盘保护仍需专项实现/验证。
4. **生命周期与对账未完成**:当前真实路由只有单次落地;多attempt纯状态单测不是完整重试链路验收。长期未结束呼叫的权威终态对账、可信最终时长外部来源接入、告警真实触发恢复和端到端P95≤5秒未完成验收。
5. **旧CDR问题保留**:现场既有成功CDR路径使用固定6秒。本功能不采信该值,也没有修改计费或修复既有CDR;本次无SDP测试不构成完整CDR/媒体计费回归。
因此当前交付适合继续受控测试,不作为上述缺口已关闭或具备生产完整性/容量保证的声明。并行任务的设计审查文档及IMPLEMENTATION_STATUS变更未混入本次提交。
@@ -0,0 +1,599 @@
{
"date": "2026-08-31T09:54:19.504Z",
"from": "2026-08-31T09:54:12.271Z",
"api": {
"status": 200,
"body": {
"metricVersion": "caller-analytics-v1.1",
"view": "landing",
"from": "2026-08-31T09:54:12.271Z",
"to": "2026-08-31T09:54:19.395Z",
"asOf": "2026-08-31T09:54:19.399Z",
"qualityStatus": "LIVE",
"dataThrough": "2026-08-31T09:54:17.859Z",
"lagMs": null,
"durationPrecision": "milliseconds",
"health": {
"gap": false,
"lagMs": null,
"reason": "",
"degraded": false,
"updatedAt": "2026-08-31T09:54:18.752Z",
"dataThrough": "2026-08-31T09:54:17.859Z",
"spoolOffset": 40402,
"captureSince": "2026-08-31T09:24:19Z",
"coverageStatus": "OBSERVATIONS_ONLY"
},
"summary": {
"totalCalls": 6,
"connectedCalls": 4,
"answeredCalls": 2,
"failedCalls": 2,
"pendingCalls": 0,
"unknownCalls": 0,
"activeCalls": 0,
"talkMs": 1801,
"notConnectedCalls": 2,
"connectionRate": 66.6667,
"overallAnswerRate": 33.3333,
"connectedAnswerRate": 50
},
"summaryScope": "时间及业务筛选;样本/比例/异常筛选仅影响号码列表",
"numbers": [
{
"customerId": "cra_20260831",
"caller": "99100001",
"totalCalls": 6,
"connectedCalls": 4,
"answeredCalls": 2,
"failedCalls": 2,
"pendingCalls": 0,
"unknownCalls": 0,
"activeCalls": 0,
"talkMs": 1801,
"notConnectedCalls": 2,
"connectionRate": 66.6667,
"overallAnswerRate": 33.3333,
"connectedAnswerRate": 50,
"alert": null
}
],
"total": 1,
"skip": 0,
"take": 25,
"trends": [
{
"time": 1788170040000,
"totalCalls": 6,
"connectedCalls": 4,
"answeredCalls": 2,
"failedCalls": 2,
"pendingCalls": 0,
"unknownCalls": 0,
"activeCalls": 0,
"talkMs": 1801,
"caller": "",
"customerId": "",
"notConnectedCalls": 2,
"connectionRate": 66.6667,
"overallAnswerRate": 33.3333,
"connectedAnswerRate": 50
}
],
"historyNotice": "仅统计新采集链路的数据;旧固定6秒话单不参与应答统计或校准。"
}
},
"detail": {
"rows": [
{
"id": "e1bad2678acf3d69b58d2992f2ef008bd0f54bd347d76f8011fc4e767d05c11c",
"callKey": "eef1447b3032d62a5b7f0dfadfe78ebad4794856540a36e18217379d49d76b93",
"startedAt": "2026-08-31T09:54:16.358Z",
"caller": "99100001",
"callee": "99106",
"vendorGatewayId": "cra_20260831",
"counts": {
"talkMs": 0,
"totalCalls": 1,
"activeCalls": 0,
"failedCalls": 1,
"pendingCalls": 0,
"unknownCalls": 0,
"answeredCalls": 0,
"connectedCalls": 0
},
"payload": {
"event": {
"at": 1788170056358,
"city": "UNKNOWN",
"code": 0,
"kind": "ATTEMPT",
"callId": "cra-99106-6d80829c329241ce817b249874bf2b66@loopback",
"callee": "99106",
"caller": "99100001",
"source": "opensips",
"talkMs": null,
"callUid": "a7d5c0d5eca6d45f9b67b3278e82d8d5eb350348dfccb4ca7c5f9f42750a88e8",
"carrier": "UNKNOWN",
"eventId": "2882824c4cbeb2e995755a42731897be4bd65ba375a0e490a250a38cedc63143",
"sequence": 0,
"vendorId": "cra_20260831",
"attemptId": "1",
"startedAt": 1788170056358,
"customerId": "cra_20260831",
"landingCaller": "99100001",
"vendorGatewayId": "cra_20260831",
"customerGatewayId": "cra_20260831"
},
"talkMs": 0,
"endedAt": 1788170057859,
"unknown": false,
"finalCode": 486,
"acceptedAt": null,
"durationAt": 0,
"first180At": null,
"first183At": null,
"durationFinal": false
}
},
{
"id": "19e656a4a8d3dc370140d28d5e394987339d7f7b827f5b920d8b34fa9385f205",
"callKey": "c51a9184939429c82bd8528dd39293465139d02bd535b4b1f06aaa637b4c7170",
"startedAt": "2026-08-31T09:54:15.605Z",
"caller": "99100001",
"callee": "99105",
"vendorGatewayId": "cra_20260831",
"counts": {
"talkMs": 301,
"totalCalls": 1,
"activeCalls": 0,
"failedCalls": 0,
"pendingCalls": 0,
"unknownCalls": 0,
"answeredCalls": 1,
"connectedCalls": 1
},
"payload": {
"event": {
"at": 1788170055605,
"city": "UNKNOWN",
"code": 0,
"kind": "ATTEMPT",
"callId": "cra-99105-3d214eca9545467598ba188705ce4e7b@loopback",
"callee": "99105",
"caller": "99100001",
"source": "opensips",
"talkMs": null,
"callUid": "e13300239486e25f84c3bf8be16e3a6f128a6a10bde3591a061842ba83eb4f95",
"carrier": "UNKNOWN",
"eventId": "15e2c221e55d9627bcbe9f1752deeccd2b9aa802617769c1689665dba64a0547",
"sequence": 0,
"vendorId": "cra_20260831",
"attemptId": "1",
"startedAt": 1788170055605,
"customerId": "cra_20260831",
"landingCaller": "99100001",
"vendorGatewayId": "cra_20260831",
"customerGatewayId": "cra_20260831"
},
"talkMs": 301,
"endedAt": 1788170056057,
"unknown": false,
"finalCode": 200,
"acceptedAt": 1788170055756,
"durationAt": 1788170056057,
"first180At": 1788170055606,
"first183At": null,
"durationFinal": true,
"durationAuthority": 1
}
},
{
"id": "3b2fb2b38b901cfdde5d25b91ade27f52cc7b4ef092b5dec813e491ad67d7dc9",
"callKey": "b50ad8424436383421d409c141a5297eaad91396105ce565d34a8f80fe1bf9b3",
"startedAt": "2026-08-31T09:54:13.802Z",
"caller": "99100001",
"callee": "99104",
"vendorGatewayId": "cra_20260831",
"counts": {
"talkMs": 1500,
"totalCalls": 1,
"activeCalls": 0,
"failedCalls": 0,
"pendingCalls": 0,
"unknownCalls": 0,
"answeredCalls": 1,
"connectedCalls": 1
},
"payload": {
"event": {
"at": 1788170053802,
"city": "UNKNOWN",
"code": 0,
"kind": "ATTEMPT",
"callId": "cra-99104-b8a5a3f0ceb74407bf522f5dc9a778b3@loopback",
"callee": "99104",
"caller": "99100001",
"source": "opensips",
"talkMs": null,
"callUid": "5114f1cb6f712cb77658f1a2cff32e36dafaaed944658f7b6b062797f621879f",
"carrier": "UNKNOWN",
"eventId": "d20bd87662801f01fd9223917bb2bdb2d4e0335791025d5f9ecb6d8a3e2b4ebf",
"sequence": 0,
"vendorId": "cra_20260831",
"attemptId": "1",
"startedAt": 1788170053802,
"customerId": "cra_20260831",
"landingCaller": "99100001",
"vendorGatewayId": "cra_20260831",
"customerGatewayId": "cra_20260831"
},
"talkMs": 1500,
"endedAt": 1788170055303,
"unknown": false,
"finalCode": 200,
"acceptedAt": 1788170053803,
"durationAt": 1788170055303,
"first180At": null,
"first183At": null,
"durationFinal": true,
"durationAuthority": 1
}
},
{
"id": "9656e68727adc228abf7f4b688830c0e5f8eee429a836c1d6fd20fbbc99c6e86",
"callKey": "69f719276225346fef6ea390d75cb847eddfed12dd62e6286091d6bd443fbb64",
"startedAt": "2026-08-31T09:54:13.501Z",
"caller": "99100001",
"callee": "99103",
"vendorGatewayId": "cra_20260831",
"counts": {
"talkMs": 0,
"totalCalls": 1,
"activeCalls": 0,
"failedCalls": 1,
"pendingCalls": 0,
"unknownCalls": 0,
"answeredCalls": 0,
"connectedCalls": 0
},
"payload": {
"event": {
"at": 1788170053501,
"city": "UNKNOWN",
"code": 0,
"kind": "ATTEMPT",
"callId": "cra-99103-c26878322940440fbedea2256c8a662e@loopback",
"callee": "99103",
"caller": "99100001",
"source": "opensips",
"talkMs": null,
"callUid": "04831b2c1fde34f60dc9790dfd3b200f686c0e8441dffc6be01bbc627c695f94",
"carrier": "UNKNOWN",
"eventId": "bc4297c026cf60b3d7bde50b7a8c9036a2a36295f61e628511ecb1454ca5f5cb",
"sequence": 0,
"vendorId": "cra_20260831",
"attemptId": "1",
"startedAt": 1788170053501,
"customerId": "cra_20260831",
"landingCaller": "99100001",
"vendorGatewayId": "cra_20260831",
"customerGatewayId": "cra_20260831"
},
"talkMs": 0,
"endedAt": 1788170053501,
"unknown": false,
"finalCode": 486,
"acceptedAt": null,
"durationAt": 0,
"first180At": null,
"first183At": null,
"durationFinal": false
}
},
{
"id": "d2875d12dd948b38884daacc2c8bcb26bf430ec7b428ee9db1b61529772034da",
"callKey": "b55748363d4f5c80be6c580c66ffa158f073d9b4e188ade7c36312cc089ca1ea",
"startedAt": "2026-08-31T09:54:13.049Z",
"caller": "99100001",
"callee": "99102",
"vendorGatewayId": "cra_20260831",
"counts": {
"talkMs": 0,
"totalCalls": 1,
"activeCalls": 0,
"failedCalls": 0,
"pendingCalls": 0,
"unknownCalls": 0,
"answeredCalls": 0,
"connectedCalls": 1
},
"payload": {
"event": {
"at": 1788170053049,
"city": "UNKNOWN",
"code": 0,
"kind": "ATTEMPT",
"callId": "cra-99102-b44ee7b04988463b8ca405748e9a0cea@loopback",
"callee": "99102",
"caller": "99100001",
"source": "opensips",
"talkMs": null,
"callUid": "2fa5c0a402cf014ebb163a1c27af3a8b181b240a3b4118a48bc9b2120cedce90",
"carrier": "UNKNOWN",
"eventId": "bbb9ff98dda66a10ac6492a6571f80a1d04b3f30f65d09fef7a58e4c8ef27080",
"sequence": 0,
"vendorId": "cra_20260831",
"attemptId": "1",
"startedAt": 1788170053049,
"customerId": "cra_20260831",
"landingCaller": "99100001",
"vendorGatewayId": "cra_20260831",
"customerGatewayId": "cra_20260831"
},
"talkMs": 0,
"endedAt": 1788170053200,
"unknown": false,
"finalCode": 487,
"acceptedAt": null,
"durationAt": 0,
"first180At": null,
"first183At": 1788170053049,
"durationFinal": false
}
},
{
"id": "e90573a8144e12a7fba17a3db7ec91b712f57f545f9ee938c58686c22920cfad",
"callKey": "e63d5d684a3e1946df6db6cebc40ca71daa8fec59b98fddc81da79d5fbede5bc",
"startedAt": "2026-08-31T09:54:12.596Z",
"caller": "99100001",
"callee": "99101",
"vendorGatewayId": "cra_20260831",
"counts": {
"talkMs": 0,
"totalCalls": 1,
"activeCalls": 0,
"failedCalls": 0,
"pendingCalls": 0,
"unknownCalls": 0,
"answeredCalls": 0,
"connectedCalls": 1
},
"payload": {
"event": {
"at": 1788170052597,
"city": "UNKNOWN",
"code": 0,
"kind": "ATTEMPT",
"callId": "cra-99101-6241ef2089764314b2f5e93e89d172f2@loopback",
"callee": "99101",
"caller": "99100001",
"source": "opensips",
"talkMs": null,
"callUid": "79e429e65f1a6f43c12828690cff4b77bc71dc8fcd31fbc260e956c56c84b770",
"carrier": "UNKNOWN",
"eventId": "09f45529effc705c0c1c5fa7a606c4e31a5c628a3de6af48f9cb3742f3104952",
"sequence": 0,
"vendorId": "cra_20260831",
"attemptId": "1",
"startedAt": 1788170052596,
"customerId": "cra_20260831",
"landingCaller": "99100001",
"vendorGatewayId": "cra_20260831",
"customerGatewayId": "cra_20260831"
},
"talkMs": 0,
"endedAt": 1788170052748,
"unknown": false,
"finalCode": 486,
"acceptedAt": null,
"durationAt": 0,
"first180At": 1788170052597,
"first183At": null,
"durationFinal": false
}
}
],
"breakdown": [
{
"vendorGatewayId": "cra_20260831",
"city": "UNKNOWN",
"carrier": "UNKNOWN",
"totalCalls": 6,
"connectedCalls": 4,
"answeredCalls": 2,
"failedCalls": 2,
"pendingCalls": 0,
"unknownCalls": 0,
"activeCalls": 0,
"talkMs": 1801,
"caller": "",
"customerId": "",
"notConnectedCalls": 2,
"connectionRate": 66.6667,
"overallAnswerRate": 33.3333,
"connectedAnswerRate": 50
}
],
"skip": 0,
"take": 25
},
"sip": [
{
"callId": "cra-99101-6241ef2089764314b2f5e93e89d172f2@loopback",
"direction": "UAC",
"code": 100,
"time": 1788170052.5973315
},
{
"callId": "cra-99101-6241ef2089764314b2f5e93e89d172f2@loopback",
"direction": "UAS",
"code": 180,
"time": 1788170052.597884
},
{
"callId": "cra-99101-6241ef2089764314b2f5e93e89d172f2@loopback",
"direction": "UAC",
"code": 180,
"time": 1788170052.5979683
},
{
"callId": "cra-99101-6241ef2089764314b2f5e93e89d172f2@loopback",
"direction": "UAS",
"code": 486,
"time": 1788170052.7480495
},
{
"callId": "cra-99101-6241ef2089764314b2f5e93e89d172f2@loopback",
"direction": "UAC",
"code": 486,
"time": 1788170052.748236
},
{
"callId": "cra-99102-b44ee7b04988463b8ca405748e9a0cea@loopback",
"direction": "UAC",
"code": 100,
"time": 1788170053.049644
},
{
"callId": "cra-99102-b44ee7b04988463b8ca405748e9a0cea@loopback",
"direction": "UAS",
"code": 183,
"time": 1788170053.049802
},
{
"callId": "cra-99102-b44ee7b04988463b8ca405748e9a0cea@loopback",
"direction": "UAC",
"code": 183,
"time": 1788170053.0499415
},
{
"callId": "cra-99102-b44ee7b04988463b8ca405748e9a0cea@loopback",
"direction": "UAS",
"code": 487,
"time": 1788170053.2000644
},
{
"callId": "cra-99102-b44ee7b04988463b8ca405748e9a0cea@loopback",
"direction": "UAC",
"code": 487,
"time": 1788170053.2002563
},
{
"callId": "cra-99103-c26878322940440fbedea2256c8a662e@loopback",
"direction": "UAC",
"code": 100,
"time": 1788170053.5014744
},
{
"callId": "cra-99103-c26878322940440fbedea2256c8a662e@loopback",
"direction": "UAS",
"code": 486,
"time": 1788170053.5018132
},
{
"callId": "cra-99103-c26878322940440fbedea2256c8a662e@loopback",
"direction": "UAC",
"code": 486,
"time": 1788170053.5018618
},
{
"callId": "cra-99104-b8a5a3f0ceb74407bf522f5dc9a778b3@loopback",
"direction": "UAC",
"code": 100,
"time": 1788170053.8030186
},
{
"callId": "cra-99104-b8a5a3f0ceb74407bf522f5dc9a778b3@loopback",
"direction": "UAS",
"code": 200,
"time": 1788170053.8031552
},
{
"callId": "cra-99104-b8a5a3f0ceb74407bf522f5dc9a778b3@loopback",
"direction": "UAC",
"code": 200,
"time": 1788170053.8033082
},
{
"callId": "cra-99105-3d214eca9545467598ba188705ce4e7b@loopback",
"direction": "UAC",
"code": 100,
"time": 1788170055.6059597
},
{
"callId": "cra-99105-3d214eca9545467598ba188705ce4e7b@loopback",
"direction": "UAS",
"code": 180,
"time": 1788170055.6061337
},
{
"callId": "cra-99105-3d214eca9545467598ba188705ce4e7b@loopback",
"direction": "UAC",
"code": 180,
"time": 1788170055.606258
},
{
"callId": "cra-99105-3d214eca9545467598ba188705ce4e7b@loopback",
"direction": "UAS",
"code": 200,
"time": 1788170055.7563334
},
{
"callId": "cra-99105-3d214eca9545467598ba188705ce4e7b@loopback",
"direction": "UAC",
"code": 200,
"time": 1788170055.7565722
},
{
"callId": "cra-99106-6d80829c329241ce817b249874bf2b66@loopback",
"direction": "UAC",
"code": 100,
"time": 1788170056.3588703
},
{
"callId": "cra-99106-6d80829c329241ce817b249874bf2b66@loopback",
"direction": "UAS",
"code": 100,
"time": 1788170056.35889
},
{
"callId": "cra-99106-6d80829c329241ce817b249874bf2b66@loopback",
"direction": "UAS",
"code": 486,
"time": 1788170057.859125
},
{
"callId": "cra-99106-6d80829c329241ce817b249874bf2b66@loopback",
"direction": "UAC",
"code": 486,
"time": 1788170057.8593173
}
],
"bucketAudit": {
"totalCalls": 24,
"connectedCalls": 16,
"answeredCalls": 8,
"failedCalls": 8,
"pendingCalls": 0,
"unknownCalls": 0,
"activeCalls": 0,
"talkMs": 7204,
"notConnectedCalls": 8,
"connectionRate": 66.6667,
"overallAnswerRate": 33.3333,
"connectedAnswerRate": 50
},
"replayed": 4,
"tests": [
"real SIP six scenarios",
"real API counts and three rates",
"real DB states and minute buckets",
"database replay idempotency",
"customer scope and 403 isolation",
"scoped filter options",
"unauthenticated 401",
"invalid query 400",
"positive subsecond duration"
]
}