Scrum4Me/docs/plans/sync-model-prices.md
Janpeter Visser eaabec8471
feat(PBI-66): wekelijkse sync van model_prices via Anthropic /v1/models (#167)
Nieuw script `npm run db:sync-model-prices` haalt de actuele Claude 4.x
modellijst op bij de Anthropic API en upsert prijzen in `model_prices`.
Anthropic biedt geen prijs-API, dus prijzen blijven onderhouden in een
PRICE_TABLE constante in het script. Cache-tier-prijzen worden afgeleid
via vaste multipliers (read 0.1x, write 1.25x). Nieuwe Claude 4.x modellen
worden gedetecteerd en gelogd als warning zodat duidelijk is wanneer de
tabel handmatig moet worden bijgewerkt.

- scripts/sync-model-prices.ts: idempotent upsert, --dry-run, retry op 5xx
- ANTHROPIC_API_KEY als optional env-var (.env.example, lib/env.ts)
- scripts/README.md: gebruiksinstructies + edge cases
- docs/plans/sync-model-prices.md: ontwerpdocument

Verificatie: `npm run lint`, `vitest` (563/563), TypeScript clean.
Echt gedraaid tegen DB: 3 created (eerste run) -> 3 unchanged (tweede run).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:38:33 +02:00

106 lines
4.5 KiB
Markdown
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.

# Plan: wekelijkse sync van `model_prices` (PBI-66 / ST-1296)
## Context
De tabel `model_prices` ([prisma/schema.prisma:465](../../prisma/schema.prisma)) bevat nu 3 hardcoded rijen via [prisma/seed.ts:198](../../prisma/seed.ts) (Opus 4.7, Sonnet 4.6, Haiku 4.5). Die wordt door [lib/insights/token-stats.ts](../../lib/insights/token-stats.ts) en [lib/insights/token-history.ts](../../lib/insights/token-history.ts) ge-`LEFT JOIN`-d voor kostberekening.
Probleem: prijzen + nieuwe modellen worden alleen bijgewerkt bij een full re-seed. Dat vergeet je. We willen een wekelijks handmatig draaibaar script dat:
1. De actuele Claude 4.x modellijst ophaalt bij Anthropic (`GET /v1/models`),
2. Per model de prijzen bepaalt uit een onderhouden tabel in code,
3. Nieuwe modellen detecteert en logt (zodat we weten dat de tabel update nodig heeft),
4. Idempotent upsert in `model_prices`.
**Belangrijke realiteit:** Anthropic biedt geen prijs-API. `/v1/models` levert id, display_name, max_tokens, capabilities — maar **geen pricing**. De prijzen onderhouden we daarom als constanten in het script. De API-call dient om de modellijst te valideren en nieuwe modellen op te merken.
## Aanpak
Eén nieuw TypeScript-script `scripts/sync-model-prices.ts` in dezelfde stijl als [scripts/insert-milestone.ts](../../scripts/insert-milestone.ts):
- dotenv → DATABASE_URL + ANTHROPIC_API_KEY
- `pg.Pool` + `PrismaPg` adapter + `PrismaClient` (zelfde patroon als bestaande scripts)
- `--dry-run` flag voor preview zonder schrijven
- Aangeroepen via `npm run db:sync-model-prices`
### Datastromen
```
ANTHROPIC API /v1/models
▼ (filter: model_id matcht /^claude-(opus|sonnet|haiku)-4/)
API model list ───────────┐
PRICE_TABLE (in script) ──┤── join op model_id
Per model:
- input_price = PRICE_TABLE[id].input
- output_price = PRICE_TABLE[id].output
- cache_read_price = input * 0.1
- cache_write_price = input * 1.25
prisma.modelPrice.upsert
```
### PRICE_TABLE in script
```ts
const PRICE_TABLE: Record<string, { input: number; output: number }> = {
'claude-opus-4-7': { input: 15.0, output: 75.0 },
'claude-sonnet-4-6': { input: 3.0, output: 15.0 },
'claude-haiku-4-5-20251001': { input: 0.8, output: 4.0 },
}
const CACHE_READ_RATIO = 0.1
const CACHE_WRITE_RATIO = 1.25 // 5-minute cache write
```
Cache-ratio's komen overeen met de huidige seed: 1.5/15 = 0.1 en 18.75/15 = 1.25 — dus geen waarde-shift voor bestaande rijen.
### Filter Claude 4.x
Regex op `id` uit de API: `/^claude-(opus|sonnet|haiku)-4/`. Dit matcht `claude-opus-4-7`, `claude-sonnet-4-6`, `claude-haiku-4-5-20251001` en toekomstige 4.x varianten. Filtert oudere 3.x modellen weg.
### Detectie nieuwe modellen
Per Claude 4.x model uit de API:
- **In PRICE_TABLE** → upsert met de prijs
- **Niet in PRICE_TABLE** → log warning, sla over, exit code blijft 0
## Bestanden
| Bestand | Actie |
|---|---|
| `scripts/sync-model-prices.ts` | **Nieuw** — sync-script |
| `package.json` | **Wijzigen** — entry `"db:sync-model-prices"` toevoegen |
| `.env.example` | **Wijzigen**`ANTHROPIC_API_KEY=""` toevoegen |
| `lib/env.ts` | **Wijzigen**`ANTHROPIC_API_KEY` als optional env var |
| `scripts/README.md` | **Wijzigen** — sectie "Sync model prices" toevoegen |
| `prisma/seed.ts` regels 198229 | **Behouden** — fallback voor verse DB |
## Edge cases
| Geval | Gedrag |
|---|---|
| `ANTHROPIC_API_KEY` ontbreekt | Error + exit 1 |
| API geeft 401 | Error met hint "controleer API key" |
| API geeft 5xx | Retry 1× met 2s delay, dan falen |
| API levert 0 Claude 4.x modellen | Warning, exit 1 |
| Model uit DB staat niet meer in API | Niet verwijderen — alleen loggen |
| `--dry-run` | API-call gewoon doen, alleen geen `upsert` |
## Verificatie
```bash
npm run db:sync-model-prices -- --dry-run
npm run db:sync-model-prices
psql $DATABASE_URL -c "SELECT model_id, input_price_per_1m, output_price_per_1m, updated_at FROM model_prices ORDER BY model_id"
npm run lint && npm test && npm run build
```
## Buiten scope
- Geen Vercel cron route — bewust gekozen: handmatig draaien geeft moment om PRICE_TABLE bij te werken.
- Geen pricing-page scraping — fragiel.
- Geen 1-uurs cache write tier — schema heeft één veld.