☰
pxpipe 成本 A/B 演示的定价引擎规格(SPEC.md)深度解读:orderTotalCents 四步计价流水线与银行家舍入规则
2026/9/28 2:40:46 网站建设 项目流程

【免费下载链接】pxpipe

cut Claude Code token usage by rendering text context as images

项目地址:https://gitcode.com/gh_mirrors/px/pxpipe
点击查看免费下载

orderTotalCents(items, tier)是 pxpipe 仓库demo/cost-ab/template/模板工程中一份"故意埋错"的定价函数规格,它用一张四步计价流水线(小计 → 阶梯量折扣 → 会员折扣 → 销售税)和一条"每一步都必须使用银行家舍入"的硬性规则,把金额精度约束到整数分。本指南完整解读 SPEC.md 的每一条规则,结合 src/pricing.js、src/money.js 与 test/pricing.test.mjs 逐例验算,并说明该规格在 pxpipe 成本 A/B 演示中承担"精确任务回归测试"的定位。读完你既能独立写出通过全部 5 个测试的正确实现,也能理解为什么折扣键、计算顺序和舍入方式三者缺一不可。

规格文档在项目中的定位:一份"可被精确复现"的任务载体

这份 SPEC.md 并不描述 pxpipe 的压缩管线,而是demo/cost-ab/(成本 A/B 演示)使用的真实小项目template/的定价引擎规格。演示的目的在于:让两个 Claude 会话在隔离工作副本里修复同一个失败的测试套件——一个走普通直通代理,一个走 pxpipe 压缩代理——从而回答"pxpipe 把文本上下文渲染成图片后,是否仍然不破坏精确任务"。

正如 demo/cost-ab/README.md 所写,这个任务"同时充当一次召回(recall)测试":修复的关键恰恰在于 SPEC 中容易被视觉化渲染丢失的细节——按总数量划分的量折扣、折扣之后才施加的会员折扣、以及银行家舍入。规格中不存在任何可以被模糊理解的口径,五个测试断言的是精确到整数分的期望值(如8316、9975、45181)。这意味着 pxpipe 压缩后模型看到的"规格图片"必须被逐字符读准,任何一位数字的偏差都会导致测试失败。

函数契约与输入模型

规格为orderTotalCents(items, tier)定义了如下契约:

  • 返回值:整数分(integer cents)的最终订单总额;
  • items:行项目数组,每个元素为{ cents, qty }——cents是整数分的单价,qty是整数数量;
  • tier:三选一的字符串"NONE" | "SILVER" | "GOLD"。

"所有货币都用整数分处理"是 src/money.js 的基础约定,其中formatCents(cents)将整数分格式化为美元字符串(如2468 → "$24.68")。在真实应用里,行项目来自 src/catalog.js 的 SKU 目录解析:toLineItems(order)把[{ sku, qty }]解析为{ cents, qty }列表,价格以整数分存储(如SKU-COFFEE-250为 1299 分),未知 SKU 直接抛错。

四步计价流水线:顺序是规格的第一等公民

规格明确要求"严格按照此顺序"执行以下四步(SPEC.md原文强调in exactly this order):

第 1 步:小计(Subtotal)

对所有行项目求和:Subtotal = Σ (cents × qty)。注意是逐行乘加,不是先汇总数量再乘单价。

第 2 步:量折扣(Volume discount)

折扣档位键控于所有行项目的总数量之和(Σ qty),而不是行项目条数,也不是单行数量:

总数量折扣
≥ 10020%
≥ 5012%
≥ 105%
< 100%

从当前金额中减去roundHalfEven(subtotal × pct)。这是一个"取最高命中档位"的分段折扣,档位不叠加——总数量 120 只享受 20%,而不是 20%+12%+5% 累加。

第 3 步:会员折扣(Loyalty discount)

会员折扣施加在量折扣之后的金额上(而不是原始小计),这一点与直觉相反且极易写错:

会员等级折扣
GOLD3%
SILVER1%
NONE0%

减去roundHalfEven(amount × pct)。

第 4 步:销售税(Tax)

税率 8.25%,在全部折扣之后的金额上计算:tax = roundHalfEven(amount × 0.0825),然后加回金额。返回的即税后总额(整数分)。

整个流水线可以表达为:

import { roundHalfEven } from './money.js'; export function orderTotalCents(items, tier) { const subtotal = items.reduce((sum, i) => sum + i.cents * i.qty, 0); // 第 2 步:按总数量取最高档 const totalQty = items.reduce((sum, i) => sum + i.qty, 0); const volumePct = totalQty >= 100 ? 0.20 : totalQty >= 50 ? 0.12 : totalQty >= 10 ? 0.05 : 0; let amount = subtotal - roundHalfEven(subtotal * volumePct); // 第 3 步:会员折扣(作用于折扣后金额) const loyaltyPct = { GOLD: 0.03, SILVER: 0.01, NONE: 0 }[tier] ?? 0; amount -= roundHalfEven(amount * loyaltyPct); // 第 4 步:税(作用于全部折扣之后) amount += roundHalfEven(amount * 0.0825); return amount; }

以上实现完全由 SPEC.md 推导,即任务要求的目标形态。注意每一步都调用了roundHalfEven,且后两步的基数都是"上一步完成后的金额"。

舍入规则:为什么Math.round会算错一分钱

规格将舍入规则单列为 "Rounding rule (important)" 一节:每一步货币舍入都必须使用 round-half-to-even(银行家舍入),从src/money.js导入roundHalfEven,禁止使用Math.round。

原因是Math.round是"半进位"(half-up)舍入:遇到恰好.5的平局时向正无穷方向进一。而银行家舍入把平局舍入到最近的偶数整数。规格给出了关键反例:税额16.5分必须舍入为16,而不是 17。

src/money.js 中roundHalfEven的真实实现印证了这一规则:

export function roundHalfEven(value) { const floor = Math.floor(value); const frac = value - floor; if (Math.abs(frac - 0.5) < 1e-9) { return floor % 2 === 0 ? floor : floor + 1; } return Math.round(value); }

实现细节:先取floor与小数部分;当小数部分与0.5的差的绝对值小于1e-9时判定为"恰好平局"(用 epsilon 容差规避浮点表示误差,如0.1 + 0.4类运算的二进制误差),此时若floor为偶数则取floor,否则取floor + 1;非平局时退化为普通Math.round。注释中的断言给出了完整的行为矩阵:roundHalfEven(16.5) === 16、roundHalfEven(17.5) === 18、roundHalfEven(16.4) === 16、roundHalfEven(16.6) === 17。

与"故意埋错"的实现对照:三个典型 bug 逐一拆解

src/pricing.js 当前是错误的实现,文件头注释明确声明"这个实现有 bug,不遵循 SPEC.md,你的任务是修复它"。它浓缩了三个最典型的规格误读:

export function orderTotalCents(items, tier) { const subtotal = items.reduce((sum, i) => sum + i.cents * i.qty, 0); // BUG: discounts by number of line items, not total quantity; half-up rounding. const volume = items.length > 1 ? Math.round(subtotal * 0.05) : 0; // BUG: ignores the loyalty tier entirely, and taxes before discounts are final. const tax = Math.round(subtotal * 0.0825); return subtotal - volume + tax; }

对照规格可以指出三处确凿的错误:

  1. 折扣键错误:用items.length > 1(行项目条数)判断是否打折,而规格要求按所有行 qty 的总和分档;且折扣率被硬编码为 5%,丢失了 12%/20% 档位;
  2. 舍入错误:两处都用了Math.round(half-up),在.5平局时(如测试用例 3 的税16.5分)会多算 1 分;
  3. 结构错误:完全忽略tier(会员折扣从未施加),且税直接算在原始小计上,而不是"全部折扣之后"的金额上——量折扣与会员折扣的顺序关系也一并丢失。

修复要点因此是"三管齐下":折扣键改按总数量、每步改用roundHalfEven、重建"小计 → 量折扣 → 会员折扣 → 税"的完整顺序。测试套件 test/pricing.test.mjs 的注释也点明了期望值的推导依据:"volume tier by total qty, loyalty on the post-volume amount, 8.25% tax last, banker's rounding at each money step"。

测试套件逐例验算:5 个断言的完整演算

测试运行零依赖:package.json声明"type": "module"与脚本"test": "node --test",使用 Node 内置测试运行器(node:test+node:assert/strict),无需安装任何依赖即可执行node --test。五个用例的完整演算如下(每一分钱都可复算):

用例 1no discount, plain:[{cents:200, qty:12}],NONE

  • 小计 200×12 = 2400;总数量 12 ≥ 10 → 5% 折扣roundHalfEven(2400×0.05)=120
  • 折扣后 2280;NONE 无会员折扣;税roundHalfEven(2280×0.0825)=188
  • 合计 2468✓

用例 2volume 12% + gold 3%:[{cents:150, qty:60}],GOLD

  • 小计 150×60 = 9000;总数量 60 ≥ 50 → 12% 折扣roundHalfEven(9000×0.12)=1080
  • 折扣后 7920;GOLD 3%roundHalfEven(7920×0.03)=238→ 7682
  • 税roundHalfEven(7682×0.0825)=634
  • 合计 8316✓

用例 3banker rounding tie (tax 16.5 -> 16):[{cents:200, qty:1}],NONE

  • 小计 200;总数量 1 < 10 无折扣;无会员折扣
  • 税roundHalfEven(200×0.0825)=roundHalfEven(16.5)=16(平局取偶数!)
  • 合计 216✓ —— 若用Math.round会得到 217,这正是规格强调的 tie 场景

用例 4volume 5% then gold 3% (order matters):[{cents:1000, qty:10}],GOLD

  • 小计 10000;总数量 10 ≥ 10 → 5% 折扣roundHalfEven(500)=500→ 9500
  • GOLD 3%roundHalfEven(9500×0.03)=285→ 9215
  • 税roundHalfEven(9215×0.0825)=760
  • 合计 9975✓ —— 注意若会员折扣误算在原始小计上,结果会不同,这就是"顺序很重要"

用例 5multi-line, silver:[{cents:999, qty:50}, {cents:50, qty:55}],SILVER

  • 小计 999×50 + 50×55 = 49950 + 2750 = 52700
  • 总数量 50+55 = 105 ≥ 100 → 20%(此处若误用items.length会得到 5%)
  • 折扣roundHalfEven(52700×0.20)=10540→ 42160;SILVER 1%roundHalfEven(42160×0.01)=422→ 41738
  • 税roundHalfEven(41738×0.0825)=3443
  • 合计 45181✓

五个期望值全部与实现对照一致,任何一个环节(折扣键、顺序、舍入)出错都会使assert.equal失败。

关联工程:目录解析与完整文件清单

模板工程结构清晰,是理解规格落地的完整样例:

  • src/pricing.js —— 待修复的定价引擎(当前有 bug);
  • src/money.js —— 货币工具(roundHalfEven、formatCents);
  • src/catalog.js —— 产品目录与 SKU → 行项目解析(15 个 SKU,全部整数分定价);
  • SPEC.md —— 实现必须遵循的精确计价规则;
  • test/pricing.test.mjs —— 测试套件(5 个用例,node --test运行);
  • package.json —— 零依赖 ESM 工程,npm test即node --test。

验证命令只有一条(template/README.md 中的任务说明):

node --test

输出pass 5 / fail 0即修复完成。

在 pxpipe 成本 A/B 演示中的角色:用精确任务验证压缩不损精度

template/被 setup.mjs 复制为/tmp/pp-demo-left与/tmp/pp-demo-right两个隔离工作副本,每个副本写入独立的.claude/settings.json(仅含模型、无 MCP),并通过--setting-sources project --strict-mcp-config只读取项目配置,从而排除全局配置的混杂变量。演示运行方式(详见 demo/cost-ab/README.md):

# Terminal 1 —— 一次性准备:构建、启动双代理、播种副本 bash demo/cost-ab/setup.sh # 默认模型(Fable 5) bash demo/cost-ab/setup.sh opus # 或指定其他模型,只在这里定一次 # Terminal 2 —— LEFT = 普通 Claude(经直通代理,:47823,同样被记录日志) bash demo/cost-ab/a.sh # Terminal 3 —— RIGHT = pxpipe Claude(经压缩代理,:47824) bash demo/cost-ab/b.sh

两臂的提示词都是同一句:"Read SPEC.md and the source, then fix src/pricing.js so it follows SPEC.md exactly and the test suite (node --test) passes"(见 a.sh 与 b.sh)。两臂都经过代理,因此两侧都被记录日志(~/.pxpipe/ab-on.jsonl与~/.pxpipe/ab-off.jsonl),pxpipe 臂还会把每次渲染的 PNG 转储到/tmp/ab-png供人工检查(setup.sh 中PXPIPE_DUMP_DIR)。

值得注意的两个工程细节:

  • 模型单一来源:demo/models.sh 不硬编码任何模型名,而是从产品自身的 src/core/applicability.ts(DEFAULT_MODEL_BASES,运行时导出为getConfiguredModelBases())读取模型列表,再按唯一子串匹配解析——避免"版本迭代后opus仍指向旧模型"这类静默失效;
  • b.sh 的拒绝门:demo_require_scope会在模型不在压缩作用域内时拒绝启动——因为此时 pxpipe 会原样透传而不压缩,该臂"看起来像 pxpipe 结果,实际什么都没测"。这保证了规格确实以图片形态(而非文本)被送入模型。

结果以两个实时仪表盘呈现(无需命令):http://127.0.0.1:47824/显示 pxpipe 臂"本次会话 N% 更少 token",http://127.0.0.1:47823/显示直通对照臂约 0%(证明方法本身不会凭空捏造节省);也可用node eval/ab/savings.mjs在终端读取同样的数据。关于该任务的最终结论,demo/README.md 记录了两列都通过了全部 5 个测试且拿到精确的期望整数——"pxpipe 通过图片化的规格 + 源码"完成了这一精度任务,即压缩没有破坏精确任务。这正是把 SPEC.md 设计成"整数分 + 银行家舍入 + 严格顺序"这种不容含混的规格的意义所在。

【免费下载链接】pxpipe

cut Claude Code token usage by rendering text context as images

项目地址:https://gitcode.com/gh_mirrors/px/pxpipe
点击查看免费下载
上一篇:NocoDB终极指南:5分钟搭建你的可视化数据库平台
下一篇:DeepSeek Harness 命令行接缝精简:parseCmdline 把应用校验与发布交还 commander 的 action 席位

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询