Files
story/docs/usage-cost-quota.md
T
2026-08-29 21:56:51 +08:00

11 KiB
Raw Blame History

用量、费用与额度开发说明

对应数据库迁移:V21
对应接口契约:OpenAPI 0.24.0
适用范围:本地模拟供应商、客户组织运营工作台和家庭通话详情

1. 目标与边界

本模块解决四个问题:按通话接收并去重供应商最终用量;使用当时有效的不可变价格版本核价;向运营人员和家庭成员提供不含供应商内部编号的汇总;在新通话创建前执行客户组织或设备的日/月费用、并发通话和单次时长策略。

当前实现覆盖本地完整业务链,但不把模拟结果扩张为真实供应商验收。供应商账单对账、真实报价同步、汇率换算、异常费用增长告警和真实Agent“完成当前轮后停止”仍需后续外部联调或告警批次完成。

2. 核心设计

2.1 标准化用量分类

分类代码 含义 示例指标与单位
RTC 实时音视频资源 connected_minute / minute
END_TO_END_VOICE 端到端语音智能体 agent_minute / minute
ASR 语音识别 audio_minute / minute
LLM 大模型推理 token / token
TTS 语音合成 character / character
STORAGE 媒体或结果存储 gigabyte_day / gb_day
POST_PROCESSING 通话后整理任务 job / job

数量以最多六位小数的十进制字符串接收,避免JSON浮点数导致计量漂移。单价和费用均使用bigint保存百万分之一货币单位,例如132450表示CNY 0.132450。

2.2 价格版本

  • 运营操作人员创建DRAFT价格版本,价格正文和全部费率一经创建不可修改。
  • 创建人不能发布自己的版本;运营审批人员使用If-Match发布。
  • 同一客户组织、同一供应商只保留一个PUBLISHED版本,旧版本变为RETIRED。
  • 费用行冻结价格版本、具体费率、数量、单价、费用和币种。后续调价不会重算历史费用。
  • 已发布的其他供应商价格和活动额度策略必须使用兼容币种,防止额度统计绕过。

管理端从具体草稿行打开页面内“审批并发布价格版本”确认区,不再使用浏览器输入弹窗。确认区固定价格版本公开编号、供应商、版本号、币种、费率项数、生效时间、创建人和行版本;发布依据必填且最长500字符。提交前必须确认审批人不是创建人,且报价/合同引用、币种、全部费率和生效时间已经复核,并明确旧发布版本会退役、历史费用不会重算。取消、切换客户组织或列表刷新会清除草稿,不发送发布请求;服务端继续执行角色、禁止自批、状态和If-Match校验。

2.3 用量幂等与核价事务

供应商发送voice_chat.usage / FINAL事件。服务端先验证回调签名、时间偏差和字段,再在同一数据库事务内完成:

  1. 以provider + event_id保存供应商事件摘要;相同编号、不同正文返回冲突。
  2. 以客户组织、供应商、来源事件、分类、指标和单位建立用量唯一键。
  3. 完全相同的重复用量读取已有费用行,不产生第二次费用。
  4. 按事件发生时间选择已经发布且生效的价格版本和精确费率。
  5. 使用数据库十进制运算计算并四舍五入为整数微单位。
  6. 同时提交供应商事件、用量事件和冻结费用行;任一步失败均整体回滚。

供应商原始事件表只保存正文摘要和usage_count,不复制用量明细或业务正文。

2.4 额度策略

额度策略按TENANT或DEVICE范围版本化保存;同一范围的新版本激活时,原活动版本退役。策略包含:

  • 账期时区和币种;
  • 日费用上限、月费用上限;
  • 最大同时通话数;
  • 单次最长通话时长;
  • STOP_IMMEDIATELY或COMPLETE_CURRENT_TURN在途处置动作。

创建通话时先使用策略时区计算日/月边界,再分别检查客户组织和设备累计费用及并发数。两级策略同时存在时逐级校验,并冻结更严格的费用及时长边界。达到硬额度返回HTTP 429和稳定错误码USAGE_QUOTA_EXCEEDED。

通话创建成功后,策略编号、账期边界、限额、最长时长、处置动作和创建时累计费用写入通话预算快照。后续修改策略不会改写已有通话。达到最长时长时,心跳返回stop或stop_after_turn;后台恢复扫描会对STOP_IMMEDIATELY通话可靠投递停止任务。

3. 接口设计

方法与路径 角色 用途 关键控制
GET /api/v1/ops/price-books 运营查看及以上 查询价格历史和费率 必须选择客户组织
POST /api/v1/ops/price-books 运营操作及以上 创建不可变价格草稿 Idempotency-Key
PATCH /api/v1/ops/price-books/{id}/publish 运营审批及以上 双人发布价格版本 If-Match、禁止自批
GET /api/v1/ops/quota-policies 运营查看及以上 查询活动与历史额度 客户组织隔离
POST /api/v1/ops/quota-policies 运营审批及以上 创建并激活策略版本 Idempotency-Key、事务审计
GET /api/v1/ops/cost-summary 运营查看及以上 按时间范围聚合费用 最大366天、不返回任务编号
GET /api/v1/ops/usage-overview 运营查看及以上 查看整体通话时长与可用费用 客户组织授权;可按家庭、设备筛选
GET /api/v1/households/{id}/usage-overview 活动家庭成员 查看本家庭通话时长与可用费用 服务端反查家庭关系;只能筛选本家庭设备
GET /api/v1/conversations/{id}/usage 活动家庭成员 查看单次通话用量和费用 服务端反查家庭关系
POST /callbacks/v1/vertc/voice-chat 签名供应商 接收voice_chat.usage HMAC、时间窗、事件幂等
POST /device/v2/conversations 签名设备 创建通话并冻结预算 创建前统一额度检查

所有Web响应使用统一request_id + data信封。运营和家庭汇总只返回标准化分类、指标、数量、单位、费用和币种,不返回供应商任务、供应商事件、内部价格版本或费率编号。

通话用量概览以平台已经持久化的conversation.started_at / ended_at计算时长,因此不依赖供应商账单接口。费用只有在最终用量已经核价并生成conversation_cost_line时才标记为可用;前端默认展示通话时长,在当前筛选范围存在费用行时开放费用视图。家庭端的家庭编号取自URL,但服务端仍以登录身份的活动家庭成员关系重新授权;传入其他家庭或其他家庭设备都会被拒绝。运营端先验证所选客户组织的运营账号,再将家庭、设备筛选限制在该客户组织内。

4. 数据库设计

表 作用 关键约束
billing_price_book 供应商价格版本 租户+供应商+版本唯一;单一发布版
billing_price_rate 价格版本下的费率 价格版本+分类+指标+单位唯一
usage_quota_policy 客户组织/设备额度版本 每范围单一活动版本;时区和币种受校验
conversation_usage_event 标准化最终用量 来源事件和计量维度唯一;通话删除级联
conversation_cost_line 不可变费用行 每用量事件一条;冻结版本、单价和费用
conversation_usage_budget 通话预算快照 每通话一条;随通话删除级联

金额不使用浮点字段,币种使用三位大写代码,数量使用numeric(24,6)。费用行对价格版本和费率使用外键,保证历史可追溯;主体隐私删除时,用量、费用和预算随通话级联清理。

5. 业务流程

5.1 价格发布与用量入账

运营创建价格草稿
  → 另一名审批人复核并发布
  → 供应商发送最终用量签名回调
  → 事件编号与正文摘要去重
  → 标准化分类/指标/单位校验
  → 按发生时间命中价格版本与费率
  → 冻结用量、单价和费用
  → 运营汇总与家庭通话详情可见

5.2 新通话额度检查

设备提交签名创建请求
  → 读取客户组织与设备活动策略
  → 按策略时区计算日/月账期
  → 校验累计费用
  → 校验同时通话数
  ├─ 达到任一硬边界:429 / USAGE_QUOTA_EXCEEDED
  └─ 未达到:创建通话并冻结预算快照

5.3 通话时长处置

通话持续运行
  → 当前时间超过冻结的最长时长
  ├─ STOP_IMMEDIATELY:心跳返回stop,Worker可靠投递停止
  └─ COMPLETE_CURRENT_TURN:心跳返回stop_after_turn

COMPLETE_CURRENT_TURN目前已经形成设备动作契约,但真实供应商/Agent对“轮次结束”的确认和可靠停止仍属于外部联调项。

6. 错误与安全处理

  • 缺少费率或价格尚未发布:整个回调事务回滚,供应商可重试。
  • 同一供应商事件编号对应不同正文:HTTP 409 PROVIDER_EVENT_CONFLICT。
  • 达到费用或并发硬额度:HTTP 429 USAGE_QUOTA_EXCEEDED。
  • 价格创建人自批:HTTP 409 SELF_APPROVAL_FORBIDDEN。
  • If-Match过期:HTTP 412 VERSION_CONFLICT。
  • 跨客户组织、非家庭成员或角色不足:HTTP 403,不泄露资源是否存在。
  • 回调处理内部失败记录结构化服务端错误,但客户端只得到可重试通用错误,不返回SQL或内部资源信息。

7. 验证证据

scripts/smoke-billing.ps1使用真实本地OIDC、设备HMAC、供应商回调HMAC和PostgreSQL验证:租户隔离、价格草稿幂等、禁止自批、双人发布、七类最终用量、重复计费去重、同编号异文冲突、冻结总费用、运营汇总脱敏、家庭汇总脱敏、预算快照和硬额度429。脚本结束后清理全部合成价格、额度、通话、用量、费用、审计和幂等记录。

V21已完成单版本down 1和重新up,最终为21|false。实际Chrome 1440×1100检查了运营用量费用工作台,未发现失败请求、控制台错误或横向溢出;截图保存在test/e2e/screenshots/operations/ops-billing-workspace.png。

价格发布页面增量按低强度策略复用真实OIDC登录,只使用一条由另一运营账号创建的临时草稿检查目标/行版本绑定、空原因阻断、双人审批影响确认、取消关闭以及零发布PATCH;同时检查零横向溢出、零控制台错误和零非预期失败请求。脚本为test/e2e/visual-ops-billing-publish.mjs,截图为test/e2e/screenshots/ops-billing-price-publish.png,价格及费率夹具在finally中精确清理。该无提交检查用于证明页面不会误发布,不替代既有服务端双人审批纵向烟测。

8. 后续工作

  1. 在告警批次增加费用异常增长、额度临界、核价失败和供应商用量迟到告警及处置台账。
  2. 对接真实供应商账单接口,建立供应商账单与平台费用行的日对账和差异处理。
  3. 在真实Agent能力明确后完成stop_after_turn可靠确认与超时降级。
  4. 在类生产环境执行高并发回调、活动通话和大时间范围汇总压测。