Files
memind/billing.test.mjs
T
john 441ba155cc Bill H5 usage by upstream cost × margin, not flat token rate
Flat 2分/1k input overcharged the DeepSeek relay ~20x (verified on 100:
H5-charged / goose accumulated_cost stayed at ~20x across every session).
The flat rate is decoupled from real provider cost, so it overcharges
cheap providers and would underbill expensive ones.

Extend the H5_USE_BACKEND_COST path with H5_MARGIN_MULTIPLIER: charge
upstream accumulated_cost(USD) × rate × margin, which tracks the provider
automatically. Falls back to the token path when the upstream omits
accumulatedCost, so the change is safe to ship dormant.

See docs/h5-metering-gateway.md for the metering-gateway design.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 14:25:47 +08:00

79 lines
3.0 KiB
JavaScript
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.
import test from 'node:test';
import assert from 'node:assert/strict';
import { computeDeltaCostCents, normalizeTokenState } from './billing.mjs';
const config = {
useBackendCost: false,
usdCnyRate: 7.2,
inputCentsPer1k: 2,
outputCentsPer1k: 6,
minBillCents: 1,
};
test('normalizeTokenState reads camelCase fields', () => {
const state = normalizeTokenState({
inputTokens: 10,
outputTokens: 20,
accumulatedInputTokens: 100,
accumulatedOutputTokens: 200,
});
assert.equal(state.accumulatedInputTokens, 100);
assert.equal(state.accumulatedOutputTokens, 200);
});
test('computeDeltaCostCents bills token deltas', () => {
const previous = { lastInputTokens: 1000, lastOutputTokens: 500 };
const current = normalizeTokenState({
accumulatedInputTokens: 2000,
accumulatedOutputTokens: 1500,
});
const cost = computeDeltaCostCents(previous, current, config);
assert.equal(cost, 8);
});
test('computeDeltaCostCents returns zero when no usage delta', () => {
const previous = { lastInputTokens: 1000, lastOutputTokens: 500 };
const current = normalizeTokenState({
accumulatedInputTokens: 1000,
accumulatedOutputTokens: 500,
});
assert.equal(computeDeltaCostCents(previous, current, config), 0);
});
test('computeDeltaCostCents ignores backend USD cost unless explicitly enabled', () => {
const previous = { lastInputTokens: 0, lastOutputTokens: 0, lastAccumulatedCost: 0.01 };
const current = normalizeTokenState({
accumulatedInputTokens: 1000,
accumulatedOutputTokens: 500,
accumulatedCost: 0.02,
});
const rmbConfig = { ...config, useBackendCost: false };
assert.equal(computeDeltaCostCents(previous, current, rmbConfig), 5);
// 成本模式默认 margin=1deltaUsd 0.01 × 7.2 × 100 = 7.2 → ceil = 8
const usdConfig = { ...config, useBackendCost: true, marginMultiplier: 1 };
assert.equal(computeDeltaCostCents(previous, current, usdConfig), 8);
});
test('computeDeltaCostCents applies margin multiplier in cost mode', () => {
const previous = { lastInputTokens: 0, lastOutputTokens: 0, lastAccumulatedCost: 0 };
const current = normalizeTokenState({
accumulatedInputTokens: 1_000_000,
accumulatedOutputTokens: 10_000,
accumulatedCost: 0.25,
});
// deltaUsd 0.25 × 7.2 × 100 × 3 = 540 分(而非 flat token 的 2060 分)
const costConfig = { ...config, useBackendCost: true, marginMultiplier: 3 };
assert.equal(computeDeltaCostCents(previous, current, costConfig), 540);
});
test('computeDeltaCostCents falls back to token path when accumulatedCost missing', () => {
const previous = { lastInputTokens: 0, lastOutputTokens: 0 };
const current = normalizeTokenState({
accumulatedInputTokens: 1000,
accumulatedOutputTokens: 500,
// 上游未回传 accumulatedCost → 即便开启成本模式也回退 token 计费
});
const costConfig = { ...config, useBackendCost: true, marginMultiplier: 3 };
assert.equal(computeDeltaCostCents(previous, current, costConfig), 5);
});