feat(运营报表): AUG26-015 设备激活与套餐续费日报快照、查询趋势与受控导出
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 15m33s

- 新增成对迁移 000231 与三张快照表:日级头行、设备粒度激活行、到期事件粒度续费行,以快照日期为唯一键
- 新增每日 03:30(Asia/Shanghai)日报快照生成任务与幂等整日替换,失败重试沿用同一目标日
- 新增六条受控入口:两张报表的汇总、日/月趋势与受控导出,配套查询层只读快照事实
- 新增 operations_activation 与 operations_renewal 两个导出场景,创建期冻结筛选与可见店铺范围、派发期冻结表头、执行期只按冻结值复核资格
- 采购数量口径按系统内未删除设备数实施并在 PRD 标注,附实测差额依据
- 同步证据链 requirement-evidence.json 与入口能力矩阵、ARCHITECTURE 与验证记录
- 归档 change add-operations-reports 并新建主 Spec openspec/specs/operations-report/spec.md
This commit is contained in:
2026-09-18 09:42:28 +08:00
parent 6333f4ad13
commit 5e78809b93
46 changed files with 5580 additions and 101 deletions

View File

@@ -0,0 +1,187 @@
# operations-report Specification
## Purpose
为 2026 年 8 月迭代的「报表管理」提供可验证行为契约:每日冻结设备激活与套餐续费日报快照,报表查询、日/月趋势与异步导出只读该快照,上线前日期无快照且不回填历史。
## Requirements
### Requirement: 每日报表快照与不回填历史
系统 SHALL 在每个上海自然日结束后为前一自然日生成一份日报快照,覆盖设备激活情况与套餐续费情况两组指标。快照 MUST 以快照日期为唯一键一日一份;同一日期重复生成 MUST 整日替换三张快照表的当日行,其结果 MUST 等于最后一次执行的结果MUST NOT 产生重复行或第二套口径。
系统 MUST NOT 提供按日期区间批量生成快照的入口MUST NOT 回填上线前日期。查询与导出 MUST 只使用快照事实MUST NOT 回查设备、卡、套餐使用等实时事实。
#### Scenario: 上线前日期无快照且不回退
- **WHEN** 请求人选定的快照日期没有任何快照
- **THEN** 系统返回「无快照」标记与空的实际命中快照日期集合累计类指标及其派生指标为空MUST NOT 用更早日期的快照或实时事实代替
#### Scenario: 同日重复生成口径不变
- **WHEN** 同一快照日期被再次生成(任务重试或受控重跑)
- **THEN** 该日全部快照行先被整日删除再整日写入,行数与指标值等于单次生成的结果
#### Scenario: 生成失败与重试
- **WHEN** 某日快照生成失败并进入既有任务重试
- **THEN** 重试仍以同一目标日期生成,成功前该日不残留部分口径,失败摘要不泄露内部细节
#### Scenario: 上线后形成连续序列
- **WHEN** 功能上线后连续自然日正常生成
- **THEN** 每个已上线日期各存在一份快照,构成可推导新增指标的连续序列
### Requirement: 激活情况指标口径
系统 SHALL 按下列口径在快照中记录设备激活指标:
- 采购数量 MUST 为截至快照日系统内未删除的设备数。
- 累计激活数 MUST 为「任一当前有效关联卡已实名」的设备数。
- 激活率 MUST 为累计激活数除以采购数量;采购数量为零时该指标不可计算。
- 新增激活数 MUST 为快照日累计激活数减去前一快照日累计激活数。
- 累计在网数 MUST 为已实名且存在有效主套餐的设备数,有效主套餐 MUST 为主套餐、状态为生效或已用完且未退款。
- 活跃用户数 MUST 为真流量合计大于零的设备数。
- 累计用量 MUST 为该设备自身与当前有效关联卡的当前有效使用记录的真已用量之和,并以 1 GB = 1024 MB 折算为 GB。
- 单用户卡均 MUST 为累计用量除以累计在网数;含零预测卡均 MUST 为累计用量除以累计在网数再按当月已过天数与当月总天数年化;不含零预测卡均 MUST 以活跃用户数为分母按同一方式年化。
累计类指标 MUST 取所选结束日的快照。实名状态可逆转,因此累计激活数 MAY 下降、新增激活数 MAY 为负;系统 MUST NOT 以零为下限MUST NOT 改用首次实名时间代替当前实名状态。
真流量 MUST 只取套餐使用记录的真已用量MUST NOT 使用卡级全生命周期累计、自然月累计、运营商通道累计读数、虚用量或展示用量。
#### Scenario: 设备多卡仅一卡已实名
- **WHEN** 某设备当前有效关联多张卡且仅其中一张已实名
- **THEN** 该设备计入累计激活数一次
#### Scenario: 实名逆转导致累计下降
- **WHEN** 已实名设备在后续快照日不再满足任一关联卡已实名
- **THEN** 当日累计激活数低于前一日,且新增激活数为负数
#### Scenario: 分母为零
- **WHEN** 采购数量为零
- **THEN** 激活率在接口中为空值,在导出中写作「-」,其余比率与卡均同样处理
#### Scenario: 真流量来源受控
- **WHEN** 某设备的关联卡同时存在全生命周期累计、自然月累计与通道累计读数
- **THEN** 累计用量与活跃用户数只按套餐使用记录的真已用量计算,不因上述读数改变
### Requirement: 套餐续费指标口径
系统 SHALL 以到期日锚定记录套餐续费指标:
- 到期事实 MUST 为未退款主套餐的到期日(上海自然日)等于快照日的记录。
- 续费事实 MUST 为该到期资产存在另一条未退款主套餐记录,其生效时间晚于本条到期时间,或处于待生效状态。
- 到期数与续费数 MUST 按资产去重;同一资产在同一统计期内多次到期或多次续费 MUST 各计一次。
- 续费率 MUST 为续费数除以到期数;新增未续费数 MUST 为到期数减续费数。
- 续费资产集合 MUST 为到期资产集合的子集,使续费率不超过 100% 由构造保证;系统 MUST NOT 通过截断或钳制掩盖真实数据。
到期日与相关套餐事实 MUST 在生成快照时冻结,后续到期时间被修改 MUST NOT 改写已生成快照。自外部系统迁移进入本地事实的资产 MUST 与本地购买资产采用同一口径。
#### Scenario: 到期当天存在待生效后续主套餐
- **WHEN** 某资产主套餐在快照日到期且当天已存在待生效的后续主套餐
- **THEN** 该资产计一次到期并计一次续费,续费率为 100%
#### Scenario: 同一资产期内两次到期
- **WHEN** 某资产在同一统计期内有两条主套餐分别到期
- **THEN** 到期数与续费数各计该资产一次
#### Scenario: 到期时间在快照后被修改
- **WHEN** 某主套餐的到期时间在快照生成后被修改
- **THEN** 已生成快照的到期日与全部指标保持不变
#### Scenario: 新增未续费数
- **WHEN** 某统计期到期资产数为 N、其中续费资产数为 M
- **THEN** 新增未续费数为 N 减 M且不小于零
### Requirement: 报表维度分组与归属冻结
系统 SHALL 支持单一分组维度:激活情况表按设备名称、设备型号、制造商、用户组、代理、店铺、业务员中的一个维度分组,套餐续费情况表按套餐系列、套餐名称、用户组、代理、店铺、业务员中的一个维度分组;未选择分组维度时 MUST 汇总为一行且分组列值为「全部」。系统 MUST NOT 同时按多个维度分组。
快照行 MUST 在生成时冻结设备所属店铺、店铺业务员与业务员所属用户组(用户组 MUST 按既有实时推导口径取值后冻结),并 MUST 同时冻结代理的两个取值:沿上级店铺上溯至根的代理店铺及其名称、归属该店铺的代理账号及其名称;查询侧 MUST 将二者映射为单一「代理」维度。设备名称、设备型号与制造商 MUST 取生成时的设备取值,为空时以固定占位展示。
历史快照行 MUST NOT 因后续店铺、业务员、用户组或代理变化被改写。同一快照日期、同一维度下,分组行各指标之和 MUST 等于该日头行指标值。
#### Scenario: 未选择分组维度
- **WHEN** 请求未指定分组维度
- **THEN** 系统返回一行汇总结果,分组列值为「全部」
#### Scenario: 快照后归属变更
- **WHEN** 某设备在快照生成后变更所属店铺、业务员或用户组
- **THEN** 已生成快照行的店铺、业务员与用户组保持生成时的取值
#### Scenario: 分组行之和等于头行值
- **WHEN** 以任一受支持维度分组查询同一快照日期
- **THEN** 各组指标之和等于该日头行指标值
### Requirement: 报表查询与趋势
系统 SHALL 提供汇总与趋势查询。时间筛选用 `start_time``end_time` 两个可选参数,取值 MUST 为带显式时区的 RFC3339 秒级时间,区间 MUST 为闭区间(含两端),解析结果 MUST 归一为 UTC 瞬时;非法格式与开始晚于结束 MUST 以既有参数非法错误码拒绝MUST NOT 提供宽松格式兼容或运行时开关。
快照日期入选规则 MUST 为:快照日期 D 入选,当且仅当 D 的零点(+08:00落在请求区间内请求人 MUST 以整日边界表达区间。
趋势查询 MUST 以 `granularity` 取值 `day``month` 表达粒度MUST NOT 接受月份参数:按日每个快照日一点,按月每个自然月一点;累计类指标 MUST 取该期最后一个有快照日的快照值,新增类指标 MUST 取相邻期同口径之差;无快照的期 MUST NOT 出现在结果中。系统 MUST 只返回数据,图表渲染 MUST 由前端负责。
#### Scenario: 闭区间含两端
- **WHEN** 请求的开始时间与结束时间均等于某快照日零点
- **THEN** 该快照日入选结果
#### Scenario: 非法时间参数
- **WHEN** 请求携带无时区时间、date-only、空格分隔时间或开始晚于结束的参数
- **THEN** 系统以参数非法错误码拒绝,且不返回任何行
#### Scenario: 按月趋势跳过无快照月份
- **WHEN** 请求按月趋势且区间内某自然月没有快照
- **THEN** 该月不出现在趋势结果中
#### Scenario: 累计取期末快照
- **WHEN** 请求自定义时间段且区间内多个日期存在快照
- **THEN** 累计类指标取结束日快照值,新增激活数按结束日与起始日前一日的累计差值计算
### Requirement: 报表权限、数据范围与导出冻结
仅超级管理员与平台用户 SHALL 查询或导出报表;其余用户类型 MUST 被拒绝,且无权限与目标不存在 MUST NOT 形成可枚举差异。
查询与导出 MUST 按请求人可见店铺范围过滤快照行;越权与不存在 MUST 统一按资源不可见处理。
导出 MUST 复用既有异步导出任务:创建时冻结筛选条件、操作者与可见店铺范围,派发时冻结表头,执行期 MUST 只读快照表并施加冻结的店铺范围。导出列 MUST 与页面展示字段一致并包含合计行;分母为零的比率或卡均 MUST 写作「-」;导出 MUST NOT 包含顶部文字总结。执行期 MUST 按任务内冻结的账号类型复核导出资格MUST NOT 因创建者角色、店铺归属或筛选条件变化扩大数据集或重新解释筛选。
#### Scenario: 无权用户类型请求报表
- **WHEN** 代理、企业或个人客户账号请求报表或创建报表导出
- **THEN** 系统拒绝请求且不返回任何统计结果,不区分无权限与不存在
#### Scenario: 导出与列表同筛选同口径
- **WHEN** 以同一筛选条件调用汇总查询并创建导出
- **THEN** 导出行集合与汇总结果一致,列与页面展示字段一致且包含合计行
#### Scenario: 创建后权限变化
- **WHEN** 导出任务创建后创建者的可见店铺范围发生变化再执行该任务
- **THEN** 导出结果仍不超过创建时冻结的范围与筛选条件
#### Scenario: 执行期账号类型复核
- **WHEN** 导出任务执行时任务内冻结的账号类型不是超级管理员或平台用户
- **THEN** 任务失败并记录安全失败摘要,不产出数据文件
## 可达操作索引
本节只用于入口导航,不是行为 Requirement业务义务以上述 Requirements 为准。
`GET /api/admin/operations-reports/activation-summary`(激活情况汇总);`GET /api/admin/operations-reports/activation-trend`(激活情况日/月趋势);`POST /api/admin/operations-reports/activation-summary/export`(创建激活情况导出任务);`GET /api/admin/operations-reports/package-renewal-summary`(套餐续费汇总);`GET /api/admin/operations-reports/package-renewal-trend`(套餐续费日/月趋势);`POST /api/admin/operations-reports/package-renewal-summary/export`(创建套餐续费导出任务)。