Files
Jyotisha/PROGRESS-billing-pricing-20260830.md
T

115 lines
8.6 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-30)
## 任务 0 · 真实单位成本门控
已完成只读聚合端点:`frontend/src/app/api/admin/usage/aggregate/route.ts`
- 权限:`billing.orders.read`
- 窗口:最近 30 天
- 输出:每个预留功能的 `runs``avg``p50``p95``max`,覆盖 `cost_microusd``input_tokens``output_tokens``duration_ms`
- 无数据:`hasData=false`,统计值为 `null`,不会静默伪造为 0
- `report.full`:当前账本 0 行,这是报告接入计费前的已知空缺
### 当前可见数据库观测
本机 `jyotisha-local-preview-postgres-1`(不是 staging/production)截至 2026-08-30 的最近 30 天账本:
| feature_key | runs | cost p50 | cost p95 | input p50 | output p50 | duration p50 |
|---|---:|---:|---:|---:|---:|---:|
| `chat.standard` | 0 | 无实测数据 | 无实测数据 | 无实测数据 | 无实测数据 | 无实测数据 |
| `rectification` | 3 | 0 | 0 | 110566 | 5835 | 52879 |
| `report.full` | 0 | 无实测数据 | 无实测数据 | 无实测数据 | 无实测数据 | 无实测数据 |
上述 3 条校正账本行的 `request_id` 都是 `rectification:case:<caseId>`,每个 case 只有 1 行。代码链路确认 `runV9AgentTurn` 在一次 agent run 结束时调用 `billing.complete({ ...outcome.usage, durationMs })`,而 `outcome.usage` 来自该次 run 的 `result.totalUsage`;它不是跨 case 多轮累计值。当前本机发布模型的 input/output 单价也都是 0,因此这组本地 cost=0 不能作为生产定价依据。
**门控结论:未取得 staging/production 的非零真实单位成本,禁止写入任何新价格数字、会员公平使用数字或价格种子。**
## 尚未执行
任务 1–6 依赖任务 0 的真实成本口径;在取得可审计的 staging/production 聚合数据前,不接入会导致线上默认失败的空定价配置,也不修改商品售价或公平使用参数。
## 任务 3 · 功能级定价服务端配置
已完成结构与后台管理接线:
- 新增 `public.feature_pricing`,按 `feature_key × model_tier × version` 保存草稿、发布、退休状态;迁移事务化、幂等、RLS 与 service/admin runtime 权限均已配置。
- 新增 `resolve_feature_pricing(feature_key, model_id)`:只解析 published/enabled 模型与价格;模型或价格缺失时分别 fail-closed 为 `feature_pricing_model_unavailable` / `feature_pricing_missing`
- 新增 security-definer 管理函数 `admin_save_feature_pricing_draft``admin_publish_feature_pricing`,均要求既有权限、原因与 request id,并写入 admin 审计日志。
- 新增 `/admin/feature-pricing``/api/admin/feature-pricing`,前端不携带默认价格数字;价格只来自服务端配置。
- 已为 `profile.extra` / `report.export` 预留 feature key,未接入业务路径。
本轮必要地更新了 `database-local-business.test.ts` 的 schema 表清单:原断言锁定基线表集合,本迁移有意新增 `feature_pricing`,因此仅补入该表并在断言旁注明原因。
验证:
- `./node_modules/.bin/tsc --noEmit`:通过
- `./node_modules/.bin/tsx --test tests/feature-pricing-contract.test.ts`2 通过
- `npm run test:db`34 通过,0 失败;Docker 中 migration apply 与业务 schema 检查通过
价格门控仍生效:没有 staging/production 非零真实成本前,migration 未写入任何价格 seed。
## 任务 1 · 报告生成计费
已接入 `report.full` 的完整 `reserve → complete/release` 闭环:
- 日限检查之后、创建报告之前 reserve;创建或入队失败立即 release。
- inline 成功 completedurable worker 成功 complete,终态失败 releaseretryable failure 在重试耗尽前不 release。
- request id 使用既有 `payload.requestId`,保持幂等。
- token 按 plan、章节、summary、repair 的实际调用累计后 complete;不使用伪造的固定 token 数。
## 任务 2 · 校正按功能定价
已接入 `resolveFeaturePricing(accounting, "rectification", selectedModel.id)`,不再把模型 `creditCost` 当作校正价格。继续使用既有 case 级幂等键 `rectification:case:<caseId>`,未修改公平使用、配额或 `model_not_included` SQL 校验。错误响应区分点数不足、分钟/日/月公平使用限制与 billing denied。
## 任务 3 · 对话按功能定价
已接入 `resolveFeaturePricing(accounting, "chat.standard", model.id)`,仍使用既有 `reserve_consultation_usage` RPC,仅替换 `p_credit_cost` 的来源为服务端已发布功能价格。
## 任务 4 · 上下文缓存
已完成安全的 provider-aware usage 贯通:
- `agent-generation-settings.ts` 统一解析 AI SDK `inputTokenDetails`、legacy `cachedInputTokens` / `cacheCreationInputTokens` 与 OpenAI `prompt_tokens_details.cached_tokens`
- Anthropic 请求在稳定 system 前缀上加 `cacheControl: { type: "ephemeral" }`;非 Anthropic provider 返回 `null`,保持原请求形状,不报错、不改变行为。
- 对话、校正、报告的实际 usage 都把 `readTokens``writeTokens``noCacheTokens``hit` 写入既有 `usage_events` / `usage_ledger` 的结构化 `metadata.cache`,未新增列,避免重复存储并保持账本兼容。
- 已增加 helper contract tests,覆盖 AI SDK、OpenAI、legacy 形状、聚合、命中判定与非 Anthropic 降级。
真实成本验收仍未完成:当前没有 staging/production 的第二轮真实命中与可比较基线,因此不能诚实宣称命中率或 p50 `cost_microusd` 降幅;需在部署后用同一 case/稳定前缀采集两轮账本数据并计算。
## 任务 5 · 会员档参数与权益
已完成语义与提示约束,但保留参数/权益不变:
- admin 商品/权益编辑区明确说明:`billingLimit` 是异常账号熔断,不是“随便聊”的总量承诺;会员总量主要由 `minuteLimit` + `dayLimit` 保护真人容量;生时校正与完整报告单独计费。
- `fair_use_billing_period` 的用户提示使用“当前账户触发安全熔断”,不写成“配额用完了”,避免与“随便聊”承诺冲突。
- 由于尚无 staging/production 非零真实单位成本,且未取得存量订阅依赖核查证据,本轮不凭空重设 `minuteLimit` / `dayLimit` / `billingLimit`,不移除 standard 月卡/年卡既有 `rectification` / `report.full` 权益,也不修改售价。后续必须先完成成本与存量订阅审计,再用幂等迁移或既有 admin 审计路径调整。
- 新增 contract test 锁住熔断语义、会员速率/日限说明与公平使用文案。
## 任务 6 · 管理端定价测算页
已完成只读 `/admin/pricing-simulator`
- 数据来自模型价格、功能定价、商品/权益与账本聚合端点;页面仅调用 GET,未提供保存价格入口。
- 单功能、会员平均/打满、盈亏平衡与 21 秒串行日吞吐/会员承载量均由纯函数实时计算。
- 权重不为 1 时自动归一化;`billingLimit=null` 显示不限;账本无数据显示“无实测数据”,不转成 0。
- 模型价格为 0、账本无数据、权重归一化与不限配额均有单测。
- 空数据、分布权重、汇率固定规则之外的固定成本与用户画像均保持“假设/待填”语义;未写入任何价格 seed。固定换算规则为 1 元人民币 = 10 积分。
## 验证与剩余环境缺口
已通过:
- `./node_modules/.bin/tsc --noEmit`
- `./node_modules/.bin/tsx --test tests/personal-report-api.test.ts tests/personal-report-worker.test.ts tests/feature-pricing-contract.test.ts tests/application-billing-contract.test.ts tests/pricing-simulation.test.ts`72 passed
- `npm run lint`0 errors23 个既有 warnings
- `npm run test:db`34 passed0 failedDocker migration/schema tests
- `npm run build`(成功;仅既有 Turbopack dynamic filesystem tracing warnings
`npm run db:migrate:check` 尚未能执行有效检查:环境未提供 `SCHEMA_DATABASE_URL`,命令会 fail-closed 为 `SCHEMA_DATABASE_URL is required`。补充 schema database 连接串后需连续运行三次;当前不因该环境缺口修改迁移内容。
本轮变更集中在隔离 worktree;主工作树保持不变。push 前仍需在本分支重新 fetch 并确认与最新 `origin/staging` 的关系。
### 全量测试补充
`./node_modules/.bin/tsx --test tests/*.test.ts`2369 passed1 failed。唯一失败为既有的 `tests/staging-backend-workflows.test.ts` YAML 语法检查,失败原因是运行环境的 Python 缺少 `yaml` 模块(`ModuleNotFoundError: No module named 'yaml'`);本轮未修改 `.gitea/workflows/**`,因此不是本轮回归。该环境缺口未通过新增依赖绕过。