9492bae15f
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
143 lines
11 KiB
Markdown
143 lines
11 KiB
Markdown
# 计费闭环与功能级定价进度(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 成功 complete;durable worker 成功 complete,终态失败 release,retryable 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 errors,23 个既有 warnings)
|
||
- `npm run test:db`(34 passed,0 failed,Docker 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 passed,1 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。
|