From 80333bcc872148af31372a2e3d3ab882dcc81759 Mon Sep 17 00:00:00 2001 From: Sheng Kun Chang Date: Tue, 7 Jul 2026 07:43:32 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(billing):=20migration=20042=20?= =?UTF-8?q?=E2=80=94=20seed=20Claude=20Sonnet=205=20COGS,=20retire=20Sonne?= =?UTF-8?q?t=204.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seeds per-model raw COGS for Claude Sonnet 5 into metric_model_prices (input 2000 / output 10000 / cache_write 2500 / cache_read 200 µ$/1k = USD-per-1M x1000; the x1.2 markup is applied once at rollup, not stored) and deactivates the dropped Sonnet 4.6 rows (active=false, kept for historical reproducibility — never deleted). Opus 4.8 / Fable 5 stay unseeded while disabled (no adapter emit = dead pricing); Haiku 4.5 untouched. Down re-activates Sonnet 4.6 and drops the Sonnet 5 rows. Also repairs init-db.sql, which had drifted (missing the 041 \i line). Co-Authored-By: Claude Fable 5 --- .../042_ai_model_prices_sonnet5.down.sql | 15 +++++ .../042_ai_model_prices_sonnet5.up.sql | 62 +++++++++++++++++++ scripts/init-db.sql | 6 ++ 3 files changed, 83 insertions(+) create mode 100644 migrations/billing/042_ai_model_prices_sonnet5.down.sql create mode 100644 migrations/billing/042_ai_model_prices_sonnet5.up.sql diff --git a/migrations/billing/042_ai_model_prices_sonnet5.down.sql b/migrations/billing/042_ai_model_prices_sonnet5.down.sql new file mode 100644 index 0000000..501a487 --- /dev/null +++ b/migrations/billing/042_ai_model_prices_sonnet5.down.sql @@ -0,0 +1,15 @@ +-- Down 042 — reverse the AI roster refresh: re-activate Sonnet 4.6, drop Sonnet 5. +-- +-- Mirrors 018's down style: undo ONLY what 042 up did. +-- * re-activate the Sonnet 4.6 rows the up deactivated, +-- * DELETE the Sonnet 5 rows the up seeded. +-- Leaves Haiku 4.5 and every other pre-042 row untouched. + +-- 1) Restore the pre-042 Sonnet 4.6 price rows to active. +UPDATE ms_billing.metric_model_prices +SET active = true +WHERE model = 'anthropic.claude-sonnet-4-6'; + +-- 2) Remove the Sonnet 5 seed rows. +DELETE FROM ms_billing.metric_model_prices +WHERE model = 'anthropic.claude-sonnet-5'; diff --git a/migrations/billing/042_ai_model_prices_sonnet5.up.sql b/migrations/billing/042_ai_model_prices_sonnet5.up.sql new file mode 100644 index 0000000..e966ede --- /dev/null +++ b/migrations/billing/042_ai_model_prices_sonnet5.up.sql @@ -0,0 +1,62 @@ +-- Migration 042 — AI model roster refresh: seed Claude Sonnet 5, retire Sonnet 4.6. +-- +-- WHY: the platform's AI model roster (api-platform/internal/agent/models.go) +-- becomes EXACTLY the 4-model Claude line — Haiku 4.5 (default), Sonnet 5, +-- Opus 4.8, Fable 5 — dropping the old Sonnet 4.6 and every Gemini/GPT +-- placeholder. This migration realigns the billing-engine per-model price +-- side-table (metric_model_prices, migration 018) with that new roster: +-- * NEW → seed Claude Sonnet 5 ('anthropic.claude-sonnet-5') COGS rows, +-- * GONE → deactivate (NOT delete) the removed Sonnet 4.6 rows. +-- +-- µ$/1K-TOKEN BASIS (unchanged from 018 — see its header for the full rationale): +-- unit_price_micros is µ$ PER 1K TOKENS = (USD per 1M tokens) × 1000. Billing AI +-- tokens in the coarsest per-1k unit keeps every per-unit price ≥ 1 µ$ so the +-- integer BIGINT never floors a sub-micro per-token price to 0. +-- Sonnet 5 raw COGS (Anthropic list, intro price, July 2026): +-- input $2/1M → 2 × 1000 = 2000 µ$/1k +-- output $10/1M → 10 × 1000 = 10000 µ$/1k +-- cache_write $2.50/1M → 2.5 × 1000 = 2500 µ$/1k +-- cache_read $0.20/1M → 0.2 × 1000 = 200 µ$/1k +-- +-- RAW COGS, NOT ×1.2 (same contract as 017/018): these are the RAW PROVIDER LIST +-- cost. The agent's 1.2× DISPLAY markup (models.go priceMarkup, served by +-- GET /v1/models) and the flat reserved-metric ×12/10 customer markup (applied +-- EXACTLY ONCE at rollup via isReservedMetric on the `infra.` prefix) sit ON TOP. +-- Baking 1.2× in here would double-bill. +-- +-- WHY OPUS 4.8 / FABLE 5 ARE NOT SEEDED: both ship DISABLED in the roster +-- (Enabled: false). No adapter serves a disabled model, so the producer can +-- never emit it — a metric_model_prices row for it would be dead pricing that +-- resolves to nothing. Their prices live ONLY in the roster for DISPLAY +-- (GET /v1/models returns all 4 with ×1.2 + cache). They join this seed the +-- moment their Enabled flag flips, exactly as Gemini/GPT would have in 018. +-- +-- WHY SONNET 4.6 IS DEACTIVATED, NOT DELETED: a retired (metric, model) price +-- must stop resolving (the rollup filters active = true) but the rows stay for +-- historical reproducibility — re-pricing/replaying a past billing period that +-- metered Sonnet 4.6 events must still find the price that was in force. Same +-- active=false "retire, don't delete" convention 018's schema comment documents. +-- +-- Idempotent seed: ON CONFLICT (metric, model) DO NOTHING (NOT DO UPDATE) so a +-- finance edit to a seeded Sonnet 5 COGS row survives a re-run / re-init — +-- identical to migration 018. The seed is the INITIAL value only. +-- +-- Spec: mirrorstack-docs/db/ms_billing/tables.md (metric_model_prices). + +-- 1) NEW — Claude Sonnet 5 ('anthropic.claude-sonnet-5') raw-COGS rows, active. +-- µ$ per 1K tokens = (USD per 1M) × 1000. +INSERT INTO ms_billing.metric_model_prices ( + metric, model, unit_price_micros, active +) VALUES + -- Claude Sonnet 5 — in $2/1M, out $10/1M, cache_write $2.50/1M, cache_read $0.20/1M. + ('infra.ai.input.tokens', 'anthropic.claude-sonnet-5', 2000, true), + ('infra.ai.output.tokens', 'anthropic.claude-sonnet-5', 10000, true), + ('infra.ai.cache_write.tokens', 'anthropic.claude-sonnet-5', 2500, true), + ('infra.ai.cache_read.tokens', 'anthropic.claude-sonnet-5', 200, true) +ON CONFLICT (metric, model) DO NOTHING; + +-- 2) RETIRE — deactivate the removed Sonnet 4.6 rows. KEEP the rows (historical +-- reproducibility); the rollup's active = true filter stops them resolving. +UPDATE ms_billing.metric_model_prices +SET active = false +WHERE model = 'anthropic.claude-sonnet-4-6'; diff --git a/scripts/init-db.sql b/scripts/init-db.sql index f4c5f2b..1cff6c6 100644 --- a/scripts/init-db.sql +++ b/scripts/init-db.sql @@ -60,3 +60,9 @@ -- facts in ServiceBlockSignals, so it needs no stored column). \i migrations/billing/038_payment_method_fraud.up.sql \i migrations/billing/039_invoice_ever_failed.up.sql + +-- 041: org billing designations (per-org PSP/distributor routing). +\i migrations/billing/041_org_billing_designations.up.sql + +-- 042: AI roster refresh — seed Claude Sonnet 5 COGS, retire Sonnet 4.6. +\i migrations/billing/042_ai_model_prices_sonnet5.up.sql From 905de7173c0606e9d261e4790733d3d4b62dcc4c Mon Sep 17 00:00:00 2001 From: Sheng Kun Chang Date: Tue, 7 Jul 2026 07:55:27 +0800 Subject: [PATCH 2/2] =?UTF-8?q?chore(billing):=20Sonnet=205=20seed=20to=20?= =?UTF-8?q?standard=20$3/$15=20(3000/15000/3750/300=20=C2=B5$/1k)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Match the api-platform roster: seed Sonnet 5 at the standard list price rather than the intro $2/$10, so it doesn't need a Sep 1 2026 hotfix. Co-Authored-By: Claude Fable 5 --- .../042_ai_model_prices_sonnet5.up.sql | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/migrations/billing/042_ai_model_prices_sonnet5.up.sql b/migrations/billing/042_ai_model_prices_sonnet5.up.sql index e966ede..cc44daa 100644 --- a/migrations/billing/042_ai_model_prices_sonnet5.up.sql +++ b/migrations/billing/042_ai_model_prices_sonnet5.up.sql @@ -12,11 +12,11 @@ -- unit_price_micros is µ$ PER 1K TOKENS = (USD per 1M tokens) × 1000. Billing AI -- tokens in the coarsest per-1k unit keeps every per-unit price ≥ 1 µ$ so the -- integer BIGINT never floors a sub-micro per-token price to 0. --- Sonnet 5 raw COGS (Anthropic list, intro price, July 2026): --- input $2/1M → 2 × 1000 = 2000 µ$/1k --- output $10/1M → 10 × 1000 = 10000 µ$/1k --- cache_write $2.50/1M → 2.5 × 1000 = 2500 µ$/1k --- cache_read $0.20/1M → 0.2 × 1000 = 200 µ$/1k +-- Sonnet 5 raw COGS (Anthropic standard list price): +-- input $3/1M → 3 × 1000 = 3000 µ$/1k +-- output $15/1M → 15 × 1000 = 15000 µ$/1k +-- cache_write $3.75/1M → 3.75 × 1000 = 3750 µ$/1k +-- cache_read $0.30/1M → 0.3 × 1000 = 300 µ$/1k -- -- RAW COGS, NOT ×1.2 (same contract as 017/018): these are the RAW PROVIDER LIST -- cost. The agent's 1.2× DISPLAY markup (models.go priceMarkup, served by @@ -48,11 +48,11 @@ INSERT INTO ms_billing.metric_model_prices ( metric, model, unit_price_micros, active ) VALUES - -- Claude Sonnet 5 — in $2/1M, out $10/1M, cache_write $2.50/1M, cache_read $0.20/1M. - ('infra.ai.input.tokens', 'anthropic.claude-sonnet-5', 2000, true), - ('infra.ai.output.tokens', 'anthropic.claude-sonnet-5', 10000, true), - ('infra.ai.cache_write.tokens', 'anthropic.claude-sonnet-5', 2500, true), - ('infra.ai.cache_read.tokens', 'anthropic.claude-sonnet-5', 200, true) + -- Claude Sonnet 5 — in $3/1M, out $15/1M, cache_write $3.75/1M, cache_read $0.30/1M. + ('infra.ai.input.tokens', 'anthropic.claude-sonnet-5', 3000, true), + ('infra.ai.output.tokens', 'anthropic.claude-sonnet-5', 15000, true), + ('infra.ai.cache_write.tokens', 'anthropic.claude-sonnet-5', 3750, true), + ('infra.ai.cache_read.tokens', 'anthropic.claude-sonnet-5', 300, true) ON CONFLICT (metric, model) DO NOTHING; -- 2) RETIRE — deactivate the removed Sonnet 4.6 rows. KEEP the rows (historical