Files
Jyotisha/PROGRESS-billing-pricing-20260830.md
T
Jesse_Chen 9492bae15f
Independent Staging Quality Gate / validate (push) Successful in 10m5s
Independent Staging Quality Gate / publish (push) Successful in 9m32s
docs: record round-1 audit and open round-2 brief
Verified both round-1 briefs against the code rather than the progress
notes. Billing 0/1/2/3/6 and rectification 0/1/2/3/6 are in and clean, and
neither the confirmation gate nor the sealed holdout was loosened.

Two gaps remain. Prompt caching only emits its marker for Anthropic, so on
a DeepSeek-class provider it buys observability and no cost reduction, and
the membership fair-use numbers are untouched — correctly so, since no
real unit cost has been measured yet. The round-2 brief gates those on a
staging measurement pass and pulls the per-answer narrowing table forward,
since it is the deliverable and depends on nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
2026-08-31 05:00:55 +00:00

143 lines
11 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/**`,因此不是本轮回归。该环境缺口未通过新增依赖绕过。
---
## 外部核对(2026-08-31,按代码逐条验证,不依据本文件自述)
| 任务 | 状态 | 验证依据 |
| --- | --- | --- |
| 0 用量成本聚合 | 已完成 | `frontend/src/app/api/admin/usage/aggregate/route.ts` |
| 1 报告接计费 | 已完成 | `api/reports/route.ts:203` authorize`lib/personal-report-worker.ts:211,219` complete / release |
| 2 校正按功能定价 | 已完成 | `api/rectification/agent/route.ts:573``creditCost: pricing.credit_cost`,已脱离 `model.creditCost` |
| 3 feature_pricing 可配置 | 已完成 | `supabase/migrations/20260831010000_feature_pricing.sql` + `admin/feature-pricing` |
| 4 上下文缓存 | **部分完成** | 见下 |
| 5 会员参数 | **仅语义** | 见下 |
| 6 定价测算页 | 已完成 | `admin/pricing-simulator` + `lib/pricing-simulation.ts` + 单测 |
### 任务 4 的缺口
`lib/agent-generation-settings.ts``cachedSystemMessage()` 对非 Anthropic provider 直接返回 `null`。用量解析(`readTokens` / `writeTokens` / `hit`)覆盖了全部 provider,但**真正产生降本的缓存标记只对 Anthropic 生效**。
同一文件顶部注释与 `api/daily-starlanguage/route.ts:105``deepseek-v4-flash`)都指向 DeepSeek 系。若生产 provider 非 Anthropic,本任务目前交付的是**可观测性,不是降本**。会员「随便聊」档的毛利模型依赖这项降本,因此这是阻塞项。**下一轮任务 1 处理。**
### 任务 5 的缺口与判断
`product_entitlements` 的公平使用参数一个未改,仍为种子值:月卡 `billingLimit 2000`、年卡 `24000`、Pro 月卡 `5000`、Pro 年卡 `60000``20260806020000_billing_products_subscriptions.sql:146-155`)。
**这一保留是正确的**,符合任务书红线「不得凭估算改价」。缺的是上游输入:聚合端点已就绪但 staging 尚无真实单位成本数据。**这是流程阻塞,不是实现缺陷。下一轮任务 0 处理。**
同时记录一个尚未解决的商业事实:在参数未调整前,月卡按 `billingLimit` 打满的成本高于售价,「随便聊」档在上线前必须先完成任务 0 与任务 1。