diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3596aa5..16a573f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## 0.5.2 - 2026-09-18
+
+### Changed
+
+- **The cache tip quotes the session's own model.** "A stable prompt prefix
+ bills at 1/N of the miss rate" used the smallest discount on the whole
+ card, which is Pro's 30x. V4.1 Flash, the default model, discounts a hit
+ 50x, so every Flash session was told the payoff was 40% smaller than it
+ is. `cacheDiscountOf(models)` takes the smallest discount among the models
+ that actually billed; the rate-table footnote still quotes the card-wide
+ floor, which is right there as an "at least".
+- README examples price `deepseek-flash`, the model most sessions now run.
+
## 0.5.1 - 2026-09-18
### Fixed
diff --git a/README.md b/README.md
index f156556..318474f 100644
--- a/README.md
+++ b/README.md
@@ -314,7 +314,7 @@ In JavaScript, skip the fetch and call the same module directly:
import { costOf, tariffAt } from '@dshworks/dsh-meter/core'
const tokens = { miss: 188_542, hit: 1_204_880, out: 9_310 }
-costOf(tokens, 'deepseek-v4-pro', tariffAt(Date.now()), 'cny')
+costOf(tokens, 'deepseek-flash', tariffAt(Date.now()), 'cny')
```
`lib/core.js` imports nothing. Rate card, tariff clock, and cost fold are
@@ -333,7 +333,7 @@ running:
curl -s https://dsh.works/dsh-meter/pricing.json | jq -r '
((now + 8*3600) | gmtime) as $b
| .timeOfUse.scheduleBeijing[$b[6]][$b[3]] as $t
- | "\($t) · v4-pro out $\(.models["deepseek-v4-pro"].rates[$t].usd.out)/1M"'
+ | "\($t) · flash out $\(.models["deepseek-flash"].rates[$t].usd.out)/1M"'
```
Shift by `8*3600` first, then break down **once**, and take both indices
diff --git a/README.zh.md b/README.zh.md
index 09f4dac..8f677f3 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -175,7 +175,7 @@ JavaScript 里可以跳过这次 fetch,直接调同一个模块:
import { costOf, tariffAt } from '@dshworks/dsh-meter/core'
const tokens = { miss: 188_542, hit: 1_204_880, out: 9_310 }
-costOf(tokens, 'deepseek-v4-pro', tariffAt(Date.now()), 'cny')
+costOf(tokens, 'deepseek-flash', tariffAt(Date.now()), 'cny')
```
`lib/core.js` 不依赖任何包。价目表、档位时钟、成本折叠都是纯函数,内部不读时钟,所以历史会话按它当时真实的档位回算。
diff --git a/docs/index.html b/docs/index.html
index f878536..efe3d2a 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -742,6 +742,21 @@
Saving mode — let the meter talk back
Object.values(byTariff[tariff]).map((rate) => rate.miss / rate.hit)))),
)
+/**
+ * The same ratio for the models one session actually billed, so the cache
+ * tip speaks about the model in front of the user. On the live card Flash
+ * discounts a hit 50x and Pro 30x; quoting the card-wide 30 to a Flash
+ * session understated the payoff by 40%.
+ * @param {string[]} models - billed model ids.
+ * @returns {number} the smallest live discount among them, or CACHE_DISCOUNT when none is on the live card.
+ */
+function cacheDiscountOf(models) {
+ const ratios = models.filter(model => RATES[model] !== undefined).flatMap(model =>
+ TARIFFS.filter(tariff => tariff !== 'flat' && RATES[model][tariff] !== undefined).flatMap(tariff =>
+ Object.values(RATES[model][tariff]).map(rate => rate.miss / rate.hit)))
+ return ratios.length === 0 ? CACHE_DISCOUNT : Math.floor(Math.min(...ratios))
+}
+
/** Currency symbols for the two published tables. */
const CURRENCY_SYMBOL = { usd: '$', cny: '¥' }
diff --git a/docs/pricing.json b/docs/pricing.json
index 1998261..aa982eb 100644
--- a/docs/pricing.json
+++ b/docs/pricing.json
@@ -1,6 +1,6 @@
{
"schema": "dsh-meter/pricing@2",
- "generator": "@dshworks/dsh-meter@0.5.1",
+ "generator": "@dshworks/dsh-meter@0.5.2",
"homepage": "https://dsh.works/dsh-meter/",
"source": {
"usd": "https://api-docs.deepseek.com/quick_start/pricing",
diff --git a/lib/client.js b/lib/client.js
index ab517ea..497c5ec 100644
--- a/lib/client.js
+++ b/lib/client.js
@@ -197,6 +197,21 @@ const CACHE_DISCOUNT = Math.floor(
Object.values(byTariff[tariff]).map((rate) => rate.miss / rate.hit)))),
)
+/**
+ * The same ratio for the models one session actually billed, so the cache
+ * tip speaks about the model in front of the user. On the live card Flash
+ * discounts a hit 50x and Pro 30x; quoting the card-wide 30 to a Flash
+ * session understated the payoff by 40%.
+ * @param {string[]} models - billed model ids.
+ * @returns {number} the smallest live discount among them, or CACHE_DISCOUNT when none is on the live card.
+ */
+function cacheDiscountOf(models) {
+ const ratios = models.filter(model => RATES[model] !== undefined).flatMap(model =>
+ TARIFFS.filter(tariff => tariff !== 'flat' && RATES[model][tariff] !== undefined).flatMap(tariff =>
+ Object.values(RATES[model][tariff]).map(rate => rate.miss / rate.hit)))
+ return ratios.length === 0 ? CACHE_DISCOUNT : Math.floor(Math.min(...ratios))
+}
+
/** Currency symbols for the two published tables. */
const CURRENCY_SYMBOL = { usd: '$', cny: '¥' }
@@ -1301,7 +1316,7 @@ function MeterCard({ value, balance, currency, now, t, position }) {
if (value.tokens.hit > 0) {
notes.push(t('meter.cacheSaved', { amount: formatMoney(saved, currency), percent: hitShare }))
} else if (promptTokens > 0) {
- notes.push(t('meter.cacheCold', { factor: CACHE_DISCOUNT }))
+ notes.push(t('meter.cacheCold', { factor: cacheDiscountOf(value.models.map(entry => entry.model)) }))
}
if (clock.tariff === 'flat') {
notes.push(t('meter.ifNewRates', {
diff --git a/lib/core.js b/lib/core.js
index 197658f..d190dd7 100644
--- a/lib/core.js
+++ b/lib/core.js
@@ -188,6 +188,21 @@ export const CACHE_DISCOUNT = Math.floor(
Object.values(byTariff[tariff]).map((rate) => rate.miss / rate.hit)))),
)
+/**
+ * The same ratio for the models one session actually billed, so the cache
+ * tip speaks about the model in front of the user. On the live card Flash
+ * discounts a hit 50x and Pro 30x; quoting the card-wide 30 to a Flash
+ * session understated the payoff by 40%.
+ * @param {string[]} models - billed model ids.
+ * @returns {number} the smallest live discount among them, or CACHE_DISCOUNT when none is on the live card.
+ */
+export function cacheDiscountOf(models) {
+ const ratios = models.filter(model => RATES[model] !== undefined).flatMap(model =>
+ TARIFFS.filter(tariff => tariff !== 'flat' && RATES[model][tariff] !== undefined).flatMap(tariff =>
+ Object.values(RATES[model][tariff]).map(rate => rate.miss / rate.hit)))
+ return ratios.length === 0 ? CACHE_DISCOUNT : Math.floor(Math.min(...ratios))
+}
+
/** Currency symbols for the two published tables. */
export const CURRENCY_SYMBOL = { usd: '$', cny: '¥' }
diff --git a/package.json b/package.json
index f33c413..3ce9cad 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@dshworks/dsh-meter",
"description": "The DeepSeek time-of-use meter for dsh: what this session cost, which tariff is running, when it flips, and the account balance behind it — one line under the composer.",
- "version": "0.5.1",
+ "version": "0.5.2",
"license": "MIT",
"homepage": "https://dsh.works/dsh-meter/",
"publishConfig": {
diff --git a/src/ui.js b/src/ui.js
index c919cbb..f9716fa 100644
--- a/src/ui.js
+++ b/src/ui.js
@@ -495,7 +495,7 @@ function MeterCard({ value, balance, currency, now, t, position }) {
if (value.tokens.hit > 0) {
notes.push(t('meter.cacheSaved', { amount: formatMoney(saved, currency), percent: hitShare }))
} else if (promptTokens > 0) {
- notes.push(t('meter.cacheCold', { factor: CACHE_DISCOUNT }))
+ notes.push(t('meter.cacheCold', { factor: cacheDiscountOf(value.models.map(entry => entry.model)) }))
}
if (clock.tariff === 'flat') {
notes.push(t('meter.ifNewRates', {
diff --git a/tests/tariff.spec.mjs b/tests/tariff.spec.mjs
index 93dc9a7..8e14fe2 100644
--- a/tests/tariff.spec.mjs
+++ b/tests/tariff.spec.mjs
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'
import {
CACHE_DISCOUNT, CURRENCY_SYMBOL, PEAK_WINDOWS_UTC, RATES, TIME_OF_USE_FROM,
- WEEKEND_OFFPEAK_FROM, bucketCostOf, costOf, formatCountdown, formatMoney,
+ WEEKEND_OFFPEAK_FROM, bucketCostOf, cacheDiscountOf, costOf, formatCountdown, formatMoney,
formatTokens, isBeijingWeekend, nextTariffChange, tariffAt, tariffSchedule,
} from '../lib/core.js'
@@ -263,3 +263,22 @@ describe('display helpers', () => {
expect(formatCountdown(-1)).toBe('0s')
})
})
+
+describe('the cache tip speaks about the session\'s own model', () => {
+ const cardWide = CACHE_DISCOUNT
+
+ it('quotes Flash its own discount, not the card-wide minimum', () => {
+ // ¥1 miss against ¥0.02 hit, $0.15 against $0.003: 50x in both tables.
+ expect(cacheDiscountOf(['deepseek-flash'])).toBe(50)
+ expect(cardWide).toBe(30)
+ })
+
+ it('takes the smaller discount when a session mixed models', () => {
+ expect(cacheDiscountOf(['deepseek-flash', 'deepseek-v4-pro'])).toBe(30)
+ })
+
+ it('falls back to the card-wide figure for a model the live card does not price', () => {
+ expect(cacheDiscountOf(['mystery-model'])).toBe(cardWide)
+ expect(cacheDiscountOf([])).toBe(cardWide)
+ })
+})