docs(tasks): open cost-accounting gaps brief (cache pricing, unbilled calls, released runs)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
0c95a7ecac
commit
137410f4e5
@@ -49,7 +49,7 @@
|
||||
| `TASK-rectification-convergence-20260830.md` | `PROGRESS-rectification-convergence-20260830.md` | 收敛重构 v2 | 已验收 | `3a4396a4`、`86ba17ee` |
|
||||
| `TASK-rectification-decision-authority-20260831.md` | `PROGRESS-rectification-decision-authority-20260831.md` | 决策权威归一与停止语义 | 已合入 | `9f011194` |
|
||||
| `TASK-rectification-billing-20260831.md` | — | run_failed 与开场问题槽为空 | 已合入 | `47b4b06b` |
|
||||
| `TASK-round2-cost-and-delivery-20260831.md` | — | 第二轮真实成本与交付物 | 待核对 | — |
|
||||
| `TASK-round2-cost-and-delivery-20260831.md` | — | 第二轮真实成本与交付物 | 未执行(2026-09-27 核对无 PROGRESS);任务 0 并入 `TASK-cost-accounting-gaps-20260927.md`,任务 1–3 待重开 | — |
|
||||
| `TASK-rectification-provisional-adopt-20260901.md` | — | provisional 采用成为一等成功出口 | 已验收 | `20df8c60`(BUG-463) |
|
||||
| `TASK-rectification-engine-convergence-20260901.md` | — | 引擎侧相邻分钟可区分 | 已验收 | `8743dcb1` |
|
||||
| `TASK-rectification-nonterminal-exit-20260901.md` | — | 非终态轮出口统一 | 已合入 | `e404b6f4` |
|
||||
@@ -243,6 +243,7 @@
|
||||
| — | `PROGRESS-react-compiler-20260817.md` | React Compiler 接管首页 | 已回滚 | 结论见 `BLOCKED.md` |
|
||||
| `TASK-engine-runtime-hygiene-20260901.md` | `PROGRESS-engine-runtime-hygiene-20260901.md` | Python 引擎运行时治理 | 已验收 | `124d3990`(staging 实机三条待部署窗口) |
|
||||
| `TASK-billing-pricing-20260830.md` | `PROGRESS-billing-pricing-20260830.md` | 计费闭环与功能级定价 | 已合入 | `aaaa5fb7`~`a499c344` |
|
||||
| `TASK-cost-accounting-gaps-20260927.md` | — | **成本记账补漏与缓存分价**:七处模型调用不入账(标题 / 摘要 / 校正意图 / 采用旁白 / 校正开场 / 生时引导 / 每日星语);缓存命中按全价算、单价表无缓存列;多事件账本 `metadata.cache` 被覆盖;失败运行无成本记录;测算页不显示 token 分位数。只加「记」不加「扣」,不动售价与会员参数 | 待领取 | BUG-1064~1068 预留;部署后由产品按 `docs/testing/cost-accounting-20260927.md` 采真实成本 |
|
||||
| `TASK-cend-ui-claude-alignment-20260916.md` | `PROGRESS-cend-ui-r1/r2/r3-20260916.md` | **C 端界面向 claude.ai 产品界面对齐(三轮串行 R1→R2→R3,都动 `globals.css`,不得并行)**:根因是 `frontend/CLAUDE_DESIGN.md` 扒的是 **claude.com 营销官网**,它自己在 Known Gaps 里写明 claude.ai 产品界面不在范围内,而 `DESIGN.md:3` 把它当成了产品界面的实现契约。**R1**:`--font-display` 里 Tiempos Headline / StyreneB **从未加载**(无 `@font-face`、`public/` 无字体、`layout.tsx` 只 vendor 了 Inter),中文标题全站落到 **宋体 / SimSun**,波及 20 处含助手回答的 h2/h3(BUG-737);亮色强调色 `#85432f` 与暗色 `#d78064` 不同源,产品拍板亮色换 **Claude coral `#cc785c`**,**易漏点**是 `globals.css:16` 的 `--color-ring` 硬编码在 `@theme inline` 里不跟随 `:root`,另有第四个 `:root` 亮色块(`:4358`)必须同步(BUG-738);`.composer-footer` 常驻 44px + 顶栏 68px + `--composer-reserve` 148px,每屏固定吃掉 216px,模型选择器移进输入框内部、删掉底栏、顶栏收到 46px 并删「分析对象」副标题。**R2**:空状态是营销落地页(hero 卡 + 两张 132px 入口大卡 + 3 列 156px 主题卡),输入框被压在 **800px 以上**内容之下,重排成「问候 + 居中输入框 + 两枚入口 pill + 一排 chip」。**R3**:侧栏两个 `<details>` 拍平成一条「最近」、星盘的两个入口(侧栏分组 + 账户菜单)收敛到一处、删掉逐条助手头像。**决策记录 D3 推翻 DESIGN.md「报告强调色与应用同源」一句**(报告刻意保留深棕)。原型图 https://claude.ai/code/artifact/da275da6-2954-4f50-99aa-32bb8694d38b(三套画面 + 明暗,页面标题就是建议字体栈的实际渲染)。环境缺口:无登录态无 Chrome,四项真机观感留 `docs/testing/`。BUG 段 737–738 | 待领取 | — |
|
||||
| `TASK-cend-surfaces-claude-alignment-20260916.md` | `PROGRESS-cend-shell-20260916.md`、`PROGRESS-cend-report-20260916.md`、`PROGRESS-cend-rectification-20260916.md`、`PROGRESS-cend-chart-eph-20260916.md` | **次级页面对齐(上一单的续篇,R4→R5/R6,R7、R8 可并行)**:星盘 `/chart`、星历 `/ephemeris`、报告 `/reports` **各是脱离 app 外壳的独立全屏页**,顶部只有一个「返回对话」链接、侧栏整个消失,且三家各写了一套一模一样的 `*-shell`/`*-topbar`/`*-hero` 骨架——与上一单 E5 同根因(营销站 band 结构被套到产品界面)。**R4** 抽只读导航外壳 `AppNavRail`(只用现成的 `GET /api/sessions` + `GET /api/account`,会话行走 `sessionHref` 跳 `/?c=<uuid>`;**刻意不带**重命名/删除/收藏/归档——那套连着 `Home()` 的乐观更新与回滚,搬过来会撞 useState 增长门禁)。**R5** 星盘五 tab 下划线化 + 参数合表 + 行星表横向滚动;星历日期导航改 `‹ 日期 ›`。**R6** 报告中心卡片网格改行式列表;阅读页加常驻目录。**R7** 生时校正把可信区间从盘面板标题行提成常驻条(窄屏 `.is-compact` 下盘面板是 overlay,现在默认看不到区间),五个 `technique-audit` 折叠块收成两段。**R8** 设置内容区收窄(880px 弹窗里表单铺了 690px)、套餐卡三修饰符收敛成两态。**已解锁**:原挡路的设置单已于 `111b4a84`(BUG-698)合入。**两条不得回退**:BUG-698 的 `@supports (height: 1dvh)` 写法(重复声明回退会被 Lightning CSS 折叠)、BUG-616/617 的报告盘面 grid 实现。默认不占 BUG 号 | **R4–R8 全部已实现并验收合入** | R4:抽出 `AppNavRail`(只读,两个 GET,零写操作)+ `SecondaryShell`,三个次级页并入 app 外壳并删掉各自的 shell/topbar/hero;四个路由渲染标记**完全不变**(`/` `/chart` `/ephemeris` 仍 Static);CSS gzip −0.25%。`/reports/[reportId]` 留给 R6 与目录一起做。两处自身健壮性问题被测试抓到:`usePathname()` 可为 null、`fetch` 可能不存在。差点弄丢 BUG-717 的 eyebrow 文案(已放回)。R8:表单分区收窄到 440px(列表分区不变)、套餐卡三修饰符收敛成互斥的 `is-current` / `is-recommended`,`--highlighted` 删除改为滚动定位;手机端 `order:-1` 改挂 `[data-plan-alias]`(版位不是状态)。测试 3350→3354(净增 4),失败清单与基线逐条一致;`/` 仍 Static;我的干净构建实测 CSS gzip −3 字节。**遗留待产品拍板**:`?plan=` 深链现在完全没有视觉指向,只有滚动位置。R6:报告中心卡片网格改行式列表、阅读页并入外壳并把目录挪到右侧常驻。**任务书 E10 过期**——目录在 `cfcd369d` 就已存在,本轮是挪位置定稿而非从零加。挂外壳带出一个真实打印风险已处理:`.chat-app`/`.chat-panel` 是 `height:100%;overflow:hidden`,裸 `window.print()` 会把九节报告裁成一页,阅读页因此多挂一条只在挂载期生效的 print 样式解锁外壳。「生成中的分节进度」做不了——`REPORT_LIST_COLUMNS` 不返回节数,按 VOICE.md 不许前端编。R7:区间常驻条与盘面折叠收敛。**任务书 E9 也不准确**——对话区顶部早有常驻条 `RectificationTimeline` 且窄屏可见,真正只在盘面标题行的是**代表分钟**;因此没另造第二条,在既有条上补齐代表分钟与已答题数(与盘面同一次 `workingRectificationTime()` 调用)。折叠块实际是 **8 个**不是 5 个。**触发让步顺序第 5 条**:收窄进度未做——服务端无该字段,且 `candidate_range` 会放宽(BUG-572),前端相减会把一次放宽报成收窄,已写进 `BLOCKED.md`。顺带修掉一个**静默失效的旧断言**(`slice(indexOf(A), indexOf(B))` 在 B 改名后变成几乎整份文件,四条 `doesNotMatch` 假通过)|
|
||||
| — (产品口头拍板,无任务书) | `PROGRESS-chart-glyphs-20260918.md` | **星盘改用行星符号,度数进行星表(非 Bug,无 BUG 号)**:北印星盘(星盘页 / 校正右栏 / 我的报告三处共用 `VedicChartSvg`)宫内不再画「水 19°」「罗逆 29°」这类文本——一宫三颗星就叠三行、字号压到 8。改成九个彩色符号(☉☽☿♀♂♃♄☊☋),每行最多 3 个、逆行改符号下同色横线、第 1 宫加 `As`、盘下两行图例。颜色只认星不表吉凶,罗计共用中性灰,`--color-planet-*` 九个 token 在 `:root` 与两个深色块各一份;颜色挂 `is-*` 修饰类而非行内 `style`(报告标记合同禁 `style=`)。符号字体是 vendored 的 9 字形 Noto Sans Symbols 子集(OFL,1.5 KB),`--font-planet-glyphs`,♀♂ 带 U+FE0E 防 iOS 画成表情。星盘页行星表四列扩到八列(行星 / 黄道经度 / 速度 / 宫位 / 星宿 / 宿主 / pada / 状态),度数精确到分,首行是上升(`vedic.ascendant` 新字段)。数据来源:`toNorthIndianChart()` 加结构化 `occupantGlyphs`;校正与报告两条路线的模型只有引擎/golden 显示文本,按首字解析并有测试锁住。**星宿未译中文**——仓库无对照表,印度 27 宿与中国二十八宿不同源,不自造 | 已实现,待验收 | `codex/chart-glyphs-20260918`:tsc 0 / lint 0 error(118 warning 不变)/ npm test 3471→3484 条、36 红与基线 `84b293fb` 逐条相同、0 新红 / `○ /` Static / 首屏 JS gzip 1,450,774→1,455,017(+0.29%),CSS gzip +2.23%(构成见 PROGRESS §2)。真机走查欠(iOS 符号呈现、深浅两主题配色、375px 横滚、打印) |
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# 任务书 · 成本记账补漏与缓存分价(2026-09-27)
|
||||
|
||||
基线:`origin/staging` @ `0c95a7ec`("docs(bugs): BUG-1061 deployed to staging d9c9e714")。
|
||||
|
||||
**行号只是线索,一律按符号名定位。** 本单是对 `TASK-billing-pricing-20260830.md`、`TASK-round2-cost-and-delivery-20260831.md` 的续单:前者把账本、功能定价、聚合端点、定价测算页都做完了;后者的任务 0(采真实单位成本)**从未执行**,没有 PROGRESS 文件。本单不重做已完成部分,只补让「成本算出来是假的」的那几处漏洞。
|
||||
|
||||
## 为什么要做
|
||||
|
||||
产品负责人 2026-09-27 提问「项目怎么做成本计算 / 后台有没有 token 统计 / 缓存开了没有」。按代码逐条核对后的结论:
|
||||
|
||||
1. **骨架完整**:`usage_reservations → usage_events → usage_ledger` 三表;`authorize_usage / complete_usage / release_usage` 三个 SQL 函数;`feature_pricing` 按功能定价;后台 `/admin/usage`(每行有 Token、缓存读 tokens、成本列)、`/admin/usage/aggregate`(按功能 30 天 runs / avg / p50 / p95 / max)、`/admin/pricing-simulator`(售价 vs 成本 vs 毛利率 + 缓存命中表)、`/admin/models`(输入 / 输出单价)。
|
||||
2. **算出来是 0**:`model_config_versions.input_cost_microusd_per_million / output_cost_microusd_per_million` 默认 0,staging 从未填过;账本里没有一条非零真实成本行。这是运营输入,不是代码问题,产品负责人自己在后台填。
|
||||
3. **即使填了单价,账本也系统性低估**,原因就是下面「事故实证」的五条。这才是本单要修的。
|
||||
|
||||
## 事故实证(按符号定位,已逐条读代码确认)
|
||||
|
||||
### E1 · 七处模型调用不进账本
|
||||
|
||||
| # | 调用点 | 符号 | 现状 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 会话标题 | `frontend/src/lib/session-title-agent.ts` → `generateSessionTitleText` | `agent.generate` 结果只取文本,usage 丢弃 |
|
||||
| 2 | 上下文摘要 | `frontend/src/lib/session-context-summary.ts` → `generateSessionContextSummaryText` | 同上,默认模型,输入是整段历史,**不便宜** |
|
||||
| 3 | 校正轮意图分类 | `frontend/src/lib/rectification-agentic/v9/turn-intent-classifier.ts` → `classifyTurnIntentWithRetry` | 只 `console.info` 诊断,每一轮都调 |
|
||||
| 4 | 采用旁白 | `frontend/src/lib/rectification-agentic/v9/adopt-narration-agent.ts` → `generateAdoptNarrationText` | usage 丢弃 |
|
||||
| 5 | 校正非 `message` 动作(opening 等) | `frontend/src/lib/rectification-agentic/v9/agent-route-billing.ts` → `createRectificationRunBilling` 的 `reserve / complete` 开头 `if (action !== "message") return` | 免费是产品决定,**但 agent run 的 token 一并被丢**:开场轮要加载整段 Skill,是校正里最贵的单轮之一 |
|
||||
| 6 | 生时引导 | `frontend/src/lib/birth-time-guide-service.ts` → `generator.generate(prompt)` 那行(`createBirthTimeGuideService` 上方) | 不计费、不记账 |
|
||||
| 7 | 每日星语 | `frontend/src/app/api/daily-starlanguage/route.ts` → `getDailyStarlanguageAgent(model).generate` | 只限流,不记账 |
|
||||
|
||||
对照:聊天主链已经把小聊分类器的 usage 合进去了(`consult/route.ts` → `usagePayload` 里的 `classificationUsage`),说明「附属调用并入主账」这个模式仓库里已有,不是新发明。
|
||||
|
||||
### E2 · 缓存命中的输入按全价算
|
||||
|
||||
四个成本计算点是同一段公式复制四份,都只有 `inputTokens × 输入单价 + outputTokens × 输出单价`:
|
||||
|
||||
- `frontend/src/app/api/consult/route.ts` → `usagePayload`
|
||||
- `frontend/src/app/api/reports/route.ts` → 报告同步路径的 `costMicrousd` 计算
|
||||
- `frontend/src/lib/personal-report-worker.ts` → worker 路径的 `costMicrousd` 计算
|
||||
- `frontend/src/lib/rectification-agentic/v9/agent-route-billing.ts` → `complete(usage)`
|
||||
|
||||
`metadata.cache.readTokens` 已经被解析并写入账本(`promptCacheUsage` 覆盖 AI SDK、Mastra、OpenAI 原始三种形状;Mastra 1.50.1 会把 OpenAI 兼容返回的 `prompt_tokens_details.cached_tokens` 映射进 usage),**但它只用于展示命中率,不参与算钱**。`model_config_versions` 表也没有缓存读单价列。
|
||||
|
||||
供应商事实(2026-09-27 查官方文档,执行时须再核一次):DeepSeek 上下文缓存**默认对所有用户开启、不需要任何请求参数**,命中与否只看请求前缀是否与已缓存前缀完全一致;`usage` 返回 `prompt_cache_hit_tokens / prompt_cache_miss_tokens`,且 `prompt_tokens_details.cached_tokens` 与 hit 数相同;命中价约为未命中价的 1/30(V4-Pro 峰时 0.044 vs 1.32 美元 / 百万 token;非峰时半价)。所以「咱们是否开缓存了」的答案是:**开着,但账本把命中的那部分按 30 倍价格记**。本仓 `cachedSystemMessage / cachedHistoryMessage` 的 Anthropic 标记对 DeepSeek 无作用也无害。
|
||||
|
||||
### E3 · 多事件账本的 `metadata.cache` 被覆盖而不是累加
|
||||
|
||||
`complete_usage` 的 upsert 分支 `metadata = ledger.metadata || excluded.metadata`(`frontend/supabase/migrations/20260806030000_settle_order_usage_authorization.sql`)。校正一个 case 多轮、报告多章节都是多事件合一行;jsonb `||` 是浅合并,`cache` 键被最后一个事件整个替换,聚合端点与测算页的命中率因此失真。token 与成本列是累加的,只有 `metadata` 不是。
|
||||
|
||||
### E4 · 失败 / 释放的运行不留任何成本痕迹
|
||||
|
||||
`release_usage(p_user_id, p_request_id, p_reason)` 没有 usage 参数,只退积分、改状态。模型流到一半失败、用户中止、`empty_answer` 整轮失败(BUG-633 / 634 / 1049 那类)都已经花了供应商的钱,账本看不见。以校正为例,`runV9AgentTurn` 失败时走 `billing.release()`,此时 `result.totalUsage` 是有的,只是没人接。
|
||||
|
||||
### E5 · 后台聚合只展示成本,不展示 token 分位数
|
||||
|
||||
`/api/admin/usage/aggregate` 已返回 `inputTokens / outputTokens` 的 p50 / p95,但 `components/admin/pricing-simulator.tsx` 的「单功能表」只渲染售价、单位成本、毛利率。产品要看「一次校正到底吃多少 token」目前只能翻每行明细。
|
||||
|
||||
### 非缺陷、本单不做
|
||||
|
||||
- 推理 token:DeepSeek 把推理 token 计入 `completion_tokens` 一起按输出价收费,`outputTokens` 已经包含,**不需要单独定价**;只需把 `completion_tokens_details.reasoning_tokens` 存进 `metadata` 供观察(并入 T4)。
|
||||
- 非模型算力(VedAstro 外部调用、Swiss Ephemeris 重计算):只有限流没有计量。另立单,本单不碰。
|
||||
- 峰 / 非峰半价:不按时段分价,单价表填**峰时价**做保守估计。决策记录 D3。
|
||||
|
||||
## 根因
|
||||
|
||||
计费闭环那一轮的目标是「钱不出错」,账本是围绕**收费动作**建的:有预留才有账,免费的就没账。成本核算需要的是围绕**模型调用**建账。两者的差集就是 E1、E4;E2、E3 是缓存观测在 8 月 31 日那轮只做到「可见」没做到「计价」的遗留(PROGRESS-billing-pricing 的「任务 4 的缺口」已经写明)。
|
||||
|
||||
## 决策记录
|
||||
|
||||
- **D1** 产品负责人 2026-09-27 授权开本单:补记账、缓存分价、后台可看 token。**不动任何售价、积分价、会员公平使用参数**(沿用 0830 单红线 1)。
|
||||
- **D2** 免费调用**照旧免费**,只记账不扣积分。E1 七处不得因此开始收费。
|
||||
- **D3** 单价表按峰时价填;不实现按时段分价。
|
||||
- **D4** `usage_reservations.feature_key` 与 `usage_ledger.source` 的 check 约束需要**加宽枚举**以容纳辅助调用(新 feature_key)与系统来源(新 source `system`)。这是 schema 扩展,不是放宽计费校验;`authorize_usage / complete_usage / release_usage` 的公平使用、配额、`model_not_included` 判定一条不得放宽(0830 单红线 4 继续有效)。辅助调用**不得**通过 `authorize_usage` 走一遍预留再释放来「借道」记账,那会污染公平使用计数。
|
||||
- **D5** 供应商契约(缓存字段名、命中价)以执行当日官方文档为准,任务书里的数字只是线索。
|
||||
- **D6** 8 月 31 日第二轮任务 0(真实成本采集)的**执行主体是产品负责人**,在本单部署后做;执行方只负责把清单写进 `docs/testing/`,并把 `TASK-round2-cost-and-delivery-20260831.md` 状态板行改为「任务 0 并入本单,其余未执行」。
|
||||
|
||||
## 硬红线
|
||||
|
||||
1. **钱不出错**:每条现有计费路径的 `reserve → complete / release` 三态不得改语义;本单只加「记」,不加「扣」。任何改动后 `application-billing-contract`、`consultation-billing`、`consult-reports-billing`、`high-risk-billing-routes-contract`、`feature-pricing-contract` 五个测试文件必须全绿且断言不改。
|
||||
2. **成本公式只保留一份**:新建 `frontend/src/lib/usage-cost.ts`(名字可调),导出一个纯函数 `computeUsageCostMicrousd(model, usage)`;四个调用点全部改成调它,`git grep 'inputCostMicrousdPerMillion ?? 0'` 在 `src/` 下只剩这一处。
|
||||
3. **辅助调用记账失败不得影响主流程**:写账的 promise 用 `void`/`catch` 隔离,且失败要 `console.warn` 带 feature_key,不带用户内容。
|
||||
4. **动表必须真跑 `npm run test:db`**(需要 Docker)。没有 Docker 就把 migration 与 SQL 测试写好、`BLOCKED.md` 记环境缺口,**不得推 staging**。
|
||||
5. 不得修改既有测试断言;确需改的,断言上方写「原值 / 新值 / 原因」三栏并在 PROGRESS 单列。测试总数不低于开工时 `origin/staging` 实测。
|
||||
6. 账本、日志、测试 fixture 不得出现用户内容、姓名、出生资料、模型原文;`metadata` 只放数字与枚举。
|
||||
7. `scripts/jyotish_api_server.py` 与 `frontend/src/app/page.tsx` 零增长;不改 `.gitea/workflows/**`;不动 DNS;不自行提升 `main`。
|
||||
8. 不得顺手升级 `@mastra/core` 或任何依赖。
|
||||
|
||||
让步顺序:**钱不出错 > 数据不损坏 > 功能与测试不回归 > 成本数字诚实(宁缺勿假)> 后台好看 > 代码整洁**。
|
||||
|
||||
## 开工前置
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/cost-accounting-gaps-20260927 \
|
||||
.worktrees/cost-accounting-gaps-20260927 origin/staging
|
||||
```
|
||||
|
||||
读 `AGENTS.md` §1–§7、`frontend/AGENTS.md`、`docs/tasks/PROGRESS-billing-pricing-20260830.md` 全文(尤其「外部核对」一节)、`docs/tasks/TASK-billing-pricing-20260830.md` 的硬红线。在 `docs/BUG_HISTORY.md` 检索 `usage_ledger`、`cost_microusd`、`complete_usage`、`release_usage`、`cache`、`计费`。跑 `python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45`(本单动了 SQL 与运行链,按 §9 要求跑)。
|
||||
|
||||
开工时记录基线:`tsc`、`lint` warning 数、`npm test` 总数与无 Docker 失败清单(存文件,收工逐条 diff)。
|
||||
|
||||
## 任务分解
|
||||
|
||||
### T0(P0,门控)· 核实供应商契约与运行事实
|
||||
|
||||
1. 在 staging 后台 `/admin/models` 或只读查询确认当前发布模型的 `provider_type`、`provider_model`、`model_tier`,写进 PROGRESS(不写 key、不写 base_url 的凭据部分)。
|
||||
2. 按当日 DeepSeek 官方文档(`api-docs.deepseek.com` 的 kv_cache 与 pricing 页)核对:缓存是否默认开启、`usage` 字段名、命中 / 未命中 / 输出三档价格。若 provider 不是 DeepSeek,按对应供应商文档核。
|
||||
3. 写一条单测:用 DeepSeek 形状的原始 `usage`(含 `prompt_tokens_details.cached_tokens` 与 `completion_tokens_details.reasoning_tokens`)喂 `promptCacheUsage`,断言 `readTokens` 正确。若 Mastra 已把它映射成 `cachedInputTokens`,两种形状都要覆盖。
|
||||
|
||||
**验收**:PROGRESS 有「供应商 / 模型 / 缓存契约 / 三档价格 / 文档 URL / 核对日期」一张表;新单测绿。
|
||||
|
||||
### T1(P0)· 缓存分价 + 成本公式收敛
|
||||
|
||||
1. migration:`model_config_versions` 新增 `cache_read_cost_microusd_per_million bigint not null default 0 check (>= 0)` 与 `cache_write_cost_microusd_per_million`(同型;DeepSeek 为 0,Anthropic 有写入溢价,留位)。`admin_save_model_config_draft`(或现名)与 `model_catalog.ts` 的读取、`/admin/models` 表单、返回 JSON 一并加字段;旧行默认 0,行为不变。
|
||||
2. 新建 `computeUsageCostMicrousd(model, usage)`:
|
||||
- `billableInput = inputTokens - cache.readTokens`(下限 0)按输入价;
|
||||
- `cache.readTokens` 按缓存读价;`cache.writeTokens` 按缓存写价(为 0 则等于不计);
|
||||
- `outputTokens` 按输出价;四舍五入到整数微美元。
|
||||
- 缓存读价为 0 且 `readTokens > 0` 时**不得**静默按 0 算:返回值附带 `pricingComplete: false`,调用方写进 `metadata.pricing = "cache_price_missing"`,聚合端点把这类行单独计数。这是「宁缺勿假」。
|
||||
3. 四个调用点改为调用该函数;删掉四份复制的公式。
|
||||
4. 单测:纯函数覆盖「无缓存 / 有命中 / 缓存价缺失 / 全命中」四例;四个调用点各一条合同测试证明走了新函数(现有 `consult-reports-billing.test.ts` 等按需扩展,不改旧断言)。
|
||||
|
||||
**验收**:`git grep -n 'inputCostMicrousdPerMillion ?? 0' frontend/src` 只剩 `usage-cost.ts`;`npm run test:db` 含新列的读写用例;`tsc` 0 错。
|
||||
|
||||
### T2(P0)· 辅助调用进账本(不扣费)
|
||||
|
||||
1. 加宽枚举(D4):`usage_reservations.feature_key`、`usage_ledger.feature_key`、`feature_pricing.feature_key` **不动**(定价表不需要新键);只在 `usage_reservations` / `usage_ledger` / 聚合端点的 `FEATURE_KEYS` 加:`chat.title`、`chat.summary`、`rectification.opening`、`rectification.intent`、`rectification.narration`、`birth_time_guide`、`daily_starlanguage`。`source` 加 `system`。
|
||||
2. 新 SQL 函数 `record_system_usage(p_user_id, p_request_id, p_feature_key, p_actual_usage jsonb)`:自建一条 `status='completed'、source='system'、credit_amount=0` 的预留并直接调 `complete_usage` 落账;幂等键仍是 `(user_id, request_id)`;**不触碰** `profiles.credits`、不写 `credit_transactions`、不参与公平使用计数(`authorize_usage` 的窗口查询若按 `feature_key in (...)` 或 `source` 过滤,要确认 `system` 行不会被算进去,并写测试钉死)。
|
||||
3. TS 封装 `recordSystemUsage(accounting, ...)`,七处调用点接入;request_id 规则写进代码注释与 PROGRESS:
|
||||
- 标题 / 摘要:`<consultRequestId>:title`、`<consultRequestId>:summary`
|
||||
- 校正:`<turnRequestId>:opening`、`:intent`、`:narration`(同一轮可能有多次意图分类,用 `:intent:<n>`)
|
||||
- 生时引导 / 每日星语:各自路由的 requestId
|
||||
4. E1 #5(校正非 `message` 动作):`createRectificationRunBilling.complete` 在 `action !== "message"` 分支改为调 `recordSystemUsage(... "rectification.opening" ...)`,不再直接 `return true`;`reserve / release` 分支不变。
|
||||
|
||||
**验收**:七处各有一条测试证明 usage 被转发(mock accounting 断言 feature_key 与 token 数);`database-billing-*.test.ts` 新增 `record_system_usage` 幂等与「不动积分、不进公平使用窗口」两条 SQL 用例;聚合端点 JSON 出现七个新键且 `hasData` 语义不变。
|
||||
|
||||
### T3(P1)· 失败 / 释放的运行记成本
|
||||
|
||||
1. `release_usage` 增加可选参数 `p_actual_usage jsonb default null`;非空时向 `usage_events` 写一行(`metadata.outcome = "released"`,其余字段同 `complete_usage`),**不写 `usage_ledger`**(保持「ledger = 已完成交付」的语义),退款逻辑逐字不变。
|
||||
2. `releaseUsage()` TS 封装加可选 usage;三条计费路径(聊天 `settleRun` 失败分支、报告 `completeReportFailed`、校正 `billing.release()`)在拿得到 `totalUsage` 的地方传进去,拿不到就照旧传空。
|
||||
3. 聚合端点每个 feature 增加 `released: { runs, costMicrousd: { avg, p50, p95, max } }`,从 `usage_events where metadata->>'outcome' = 'released'` 聚合;测算页「单功能表」加一列「失败成本占比」= released 成本 / (completed + released 成本)。
|
||||
|
||||
**验收**:SQL 用例:释放带 usage 后 `usage_events` 有行、`usage_ledger` 无行、积分退回金额与之前测试一致;`admin-usage-aggregate-contract.test.ts` 扩展 released 结构。
|
||||
|
||||
### T4(P1)· `metadata.cache` 累加 + 推理 token 可见
|
||||
|
||||
1. `complete_usage` 的 upsert 分支:`cache.readTokens / writeTokens / noCacheTokens` 三个数按事件**累加**,`hit` = 累加后 `readTokens > 0`;`metadata` 其他键维持浅合并。写在 SQL 里,不靠调用方先读后写。
|
||||
2. `ActualUsage.metadata` 加可选 `reasoningTokens`;`promptCacheUsage` 旁新增 `reasoningTokenUsage(raw)` 解析 `completion_tokens_details.reasoning_tokens`(及 AI SDK 的 `outputTokenDetails.reasoningTokens`),四个调用点带上。不参与算钱(见「非缺陷」)。
|
||||
|
||||
**验收**:SQL 用例:同一预留两次 `complete_usage` 各带 cache 后 ledger 的 `readTokens` 等于两次之和;`agent-generation-settings.test.ts` 加推理 token 解析用例。
|
||||
|
||||
### T5(P1)· 后台把 token 摆出来
|
||||
|
||||
1. `pricing-simulator.tsx` 「单功能表」增加列:输入 p50 / 输出 p50 / 缓存读 p50 / 命中率(复用聚合端点已有字段,命中率来自 `metadata.cache`);`source === "unavailable"` 时显示「无实测数据」不显示 0。
|
||||
2. 表格上方一行说明文字:「成本 = (输入 − 缓存读) × 输入价 + 缓存读 × 缓存价 + 输出 × 输出价;单价在 `/admin/models` 配置;`cache_price_missing` 行表示有命中但未配缓存价」。
|
||||
3. 写 `docs/testing/cost-accounting-20260927.md`:产品负责人部署后照做的清单——在 `/admin/models` 填三档单价与汇率 → 用可控账号每类功能跑 ≥5 次(聊天含多领域、校正跑到出卡、报告完整生成、另各触发一次标题 / 摘要 / 开场 / 每日星语)→ 在 `/admin/pricing-simulator` 读表 → 把「功能 → p50 成本 → p95 成本 → 命中率 → 失败成本占比」抄回 PROGRESS。
|
||||
|
||||
**验收**:`pricing-simulation.test.ts` / 组件测试覆盖新列的空态与有数态;`docs/testing/` 清单存在且每步可照做;`DESIGN.md` 不需要改(后台 antd 页不在 C 端设计合同内),若改了 C 端任何文件必须说明。
|
||||
|
||||
### T6(P2)· 状态板与文档收口
|
||||
|
||||
- `docs/tasks/README.md`:本单加行;`TASK-round2-cost-and-delivery-20260831.md` 行改为「任务 0 并入 TASK-cost-accounting-gaps-20260927;任务 1–3 未执行」。
|
||||
- `CHANGELOG.md`:一条「后台成本统计:缓存分价、辅助调用与失败运行入账、token 分位数可见」;用户可感知行为无变化要写明。
|
||||
- `docs/BUG_HISTORY.md`:见下节编号。
|
||||
|
||||
## BUG 编号起点
|
||||
|
||||
开工时核对 `docs/BUG_HISTORY.md` 当前最大号(2026-09-27 为 **BUG-1063**),从 **BUG-1064** 起连续编号。建议拆法(执行方可合并,但不得漏记):
|
||||
|
||||
| 建议编号 | 现象 | 对应 |
|
||||
| --- | --- | --- |
|
||||
| BUG-1064 | 缓存命中输入按全价计入 `cost_microusd`,账本高估 / 低估方向取决于单价配置 | E2 / T1 |
|
||||
| BUG-1065 | 七处模型调用不进账本,成本系统性低估 | E1 / T2 |
|
||||
| BUG-1066 | 校正开场轮 agent run 的 token 被 `action !== "message"` 分支丢弃 | E1 #5 / T2.4 |
|
||||
| BUG-1067 | 失败 / 释放运行无成本记录 | E4 / T3 |
|
||||
| BUG-1068 | 多事件账本 `metadata.cache` 被覆盖,命中率失真 | E3 / T4 |
|
||||
|
||||
每条按 §5 格式写:状态、现象、触发条件、根因、修复、验证、防复发、关联记录(关联 `PROGRESS-billing-pricing-20260830.md` 任务 4 缺口)。**部署前状态写 `resolved(待部署)`**,部署核对后由验收方回填 SHA。
|
||||
|
||||
## 验收口径(验收方执行)
|
||||
|
||||
- 前端:`./node_modules/.bin/tsc --noEmit` 0 错;`npm run lint` 0 error 且 warning ≤ 开工基线;`npm test` 失败清单与基线逐条一致、总数不降;`next build` 后 `/` 仍 `○ Static`,首屏 gzip ±2%(本单不应触碰 C 端首屏,超出即异常)。
|
||||
- 数据库:`npm run test:db` 全绿(Docker);无 Docker 视为**未通过**而非缺口,因为本单动表。
|
||||
- 部署:`https://staging.jyotisha.chat/api/health` 的 `deployment.gitCommit` 等于合入 SHA;随后由产品负责人按 `docs/testing/cost-accounting-20260927.md` 采数,采到的表回填 PROGRESS。在采到非零真实成本之前,**本单不得被写成「成本已可信」**,只能写「记账链路已补全」。
|
||||
Reference in New Issue
Block a user