Files
junhong_cmp_fiber/openspec/changes/add-card-traffic-queue-and-observed-deduction/proposal.md
邹进 9c3c258f16
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m27s
feat: 卡流量读数先过采集队列再入账,扣减按采集时刻确定套餐
- 每卡 Redis 采集队列(默认 N=3、跨天清空、回退容差 1 MB),攒满且不下降才写最后一个点
- 测试环境全量切换日快照计量,下线灰度名单、切换水位与卡表基线扣费;全部卡置待建起点
- 读数区分没给值与 0;有套餐的卡读数 0 也写快照与日用量 0 行;重置日比较基准为 0 照常防旧值
- 新增采集记录(按月分区保留 90 天)、人工冲正流水与后台接口、采集告警
- 扣减按采集时刻有效套餐查找,只对生效中套餐做状态迁移,查不到套餐按成功跳过
- 换货新卡(新设备下的卡)打待建起点;采集间隔上限只校验启用中的轮询配置

Change: add-card-traffic-queue-and-observed-deduction

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:18:47 +09:00

39 lines
5.8 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.
## Why
`rebuild-card-traffic-metering-and-stop-resume` 已把计量改为日快照 + 本月最大值 − 已结算,解决了基线冻结、重置窗口重复扣的问题,但运营商单次异常读数仍直接写进快照(当天取最大值,暴涨会留下来);测试环境仍在灰度与卡表基线算法两条路径并存;扣减按扣费时刻的"生效中"套餐查找,采集与扣费对"有没有套餐"口径不一致,套餐用完到停机之间的用量收不到;扣错没有冲正通道,排查也缺少逐次采集记录。依据为 `docs/卡流量采集写入与扣减技术方案.md`(2026-10-28 修订,决策 T1–T25 及 10-28/29 补充决定)。
## What Changes
- 运营商流量读数先判有效性:接口失败、限流、缺字段、`used` 为空/null/无法解析、负数、NaN/Inf 不入队、不写快照;有效读数为有限且 ≥ 0,Gateway 流量读数区分"没值"与 0。
- 新增每卡 Redis 采集队列(默认 N = 3,Lua 原子):按采集时间插入,跨天清空(可配,默认开)、丢弃过期点;新点在队尾、队列满 N 个点且相邻两点回退不超过容差(默认 1 MB)时才写**最后一个点**,否则不写并记录原因。轮询、人工刷新、激活前结算共用同一队列与规则;没有运营商流量推送。
- 队列配置(长度、跨天清空、最长点龄、回退容差)放系统配置,改了即生效;启用中的轮询配置流量采集间隔超过上限(开关开 24h ÷ (N+2),关 `max_point_age_hours` ÷ (N+2))时拒绝保存或启用,停用的配置不校验。
- **BREAKING**(测试环境)一次性全量切换:所有卡走日快照 + 本月最大值 − 已结算;删除计量模式与灰度名单、第一次切换观测与切换水位,卡表基线算法不再产生扣费增量。迁移时全部卡打"待建起点"标记,切换后第一次写入只建起点不扣费。
- 写入范围:有套餐的卡读数为 0 也写日快照,每次采集写采集记录,每个有效套餐每天确保一行日用量(没扣写 0);没套餐的卡读数 > 0 才写快照,增量只记已结算、之后买套餐不补扣。
- 重置日照搬老系统,只认通道配置的重置日:重置日当天全天读数 > 比较基准 × 0.9 按 0 计;比较基准为 0 时不论重置日是几号都照常按 0 计;次日起不再判断;月末尾巴不补、不记回前一天。
- "有套餐"采集侧与扣费侧共用同一判断:采集时刻状态为生效中/已用完/已过期、已激活、未到期、未退款的套餐,卡自己没有时看绑定设备;已用完但在有效期内算有套餐。
- 扣减按采集时刻确定套餐:只对当前"生效中"的套餐做状态迁移;一个套餐都查不到时记审计并按成功跳过;采集后套餐被重置的扣在重置后的周期;套餐日用量改为 upsert,日期取采集时间的上海日期(含消费者日流量缓冲 key)。
- 换卡完成时新卡(设备换货时新设备下全部卡)打"待建起点"标记,第一次通过队列写入时建起点。
- 新增采集记录表(有套餐的卡每次采集一条,按月分区保留 90 天)与后台查询;新增人工冲正流水(锁套餐行、退回后不得为负、提交后走停复机判断)与后台接口。
- 新增告警:开启轮询且有套餐的卡一整天没写入、待建起点超过 1 天、队列拦截率异常、乱序突增、重置日防旧值命中数。
- 不在本 Change:自动对账(运营商 T+1 数据)、月末尾巴补算、对接日志/审计瘦身、跨套餐重置时的用量偏移、生产环境发布与回滚。
## Capabilities
### New Capabilities
- `package-traffic-deduction`:按采集时刻确定扣减套餐、状态迁移范围、查不到套餐的处理、套餐日用量记录、人工冲正。
### Modified Capabilities
- `card-traffic-metering`:读数有效性与快照写入范围(读数 0 写入)、重置日防旧值比较基准为 0 的处理、增量对全部卡生效且"有套餐"改为共用口径、通道阈值只在写入点判断、展示对全部卡生效、激活前结算过队列;新增采集队列、队列配置与采集间隔校验、待建起点(全量切换与换卡)、采集记录、告警;删除切换水位与按扣减载体灰度切换。
## Impact
- 前置:`rebuild-card-traffic-metering-and-stop-resume` 与 `add-tiered-polling-and-pre-activation-settlement` 须先归档(本 Change 修改它们新增的 `card-traffic-metering` 要求);前者剩余的灰度、切换水位验证作废。
- 数据库:新增采集记录表(按月分区)、冲正流水表;`tb_iot_card` 增加待建起点标记,迁移时全部卡置位;套餐日用量 `(package_usage_id, date)` 唯一索引;`tb_card_flow_switch` 不再读写。均为新的成对迁移。
- 代码:`internal/gateway/models.go`、`internal/task/polling_carddata_handler.go`、新增队列组件与系统配置项、`internal/application/cardobservation/`(`traffic.go`、`traffic_flow.go`、`traffic_calc_policy.go`)、`internal/domain/cardflow/rules.go`、`internal/domain/cardobservation/traffic.go`、`internal/infrastructure/cardobservation/event.go`、`internal/service/package/usage_service.go`、`internal/service/exchange`、人工刷新与激活前结算入口、轮询配置保存。
- 接口:人工刷新返回本次读数、是否写入与原因;新增采集记录查询、冲正接口;重新生成接口文档。
- 配置:删除 `JUNHONG_TRAFFIC_CALC_MODE`、`JUNHONG_TRAFFIC_CALC_CARDS`;新增系统配置 `traffic.queue.size`、`traffic.queue.clear_on_new_day`、`traffic.queue.max_point_age_hours`、`traffic.queue.drop_tolerance_mb`。
- Redis:每卡一个队列 key(约 150 字节);测试环境为 `noeviction`。
- 范围:测试环境。