Rebuilding the Seed
Refreshing the catalog from the legacy console — read-only, deterministic, reviewable.
The catalog was seeded from the console database (animated-spoon). When the console gains languages or provider mappings worth importing, the seed is rebuilt from a fresh dump.
The console database is production, and is only ever read
scripts/dump_console_db.sh opens the connection with default_transaction_read_only=on and a 30-second statement timeout, set at the server through PGOPTIONS. Every statement in it is a SELECT. Even a typo introducing a write would be refused by Postgres rather than executed.
Data flows one way only: console → JSON dump → seed files → this service's own database. There is no path back, and the application itself never holds a connection to the console.
Rebuilding
# 1. Dump (needs .env-console-db in the repo root — gitignored, never committed)
./scripts/dump_console_db.sh build/console-dump
# 2. Transform
python scripts/build_seed.py build/console-dump --out data/seed
# 3. Read the report
$EDITOR data/seed/REPORT.md
# 4. Review the diff and commit
git diff --stat data/seed/
The transformation is pure and deterministic: the same dump in produces byte-identical files out, so the diff is genuinely reviewable.
Reading the report
data/seed/REPORT.md lists every decision the builder made:
Multi-code (provider, language) pairs collapsed — where the console offered several codes, which one won and what the runners-up were. Every runner-up is kept as an inbound alias, so nothing that resolved before stops resolving.
Alias contests resolved — where one code claimed two languages, which one got it. fa-af going to "Farsi (Afghanistan)" rather than "Dari" is the kind of call worth a second opinion.
Merged duplicate console languages — rows describing the same language under different ids.
Languages with no usable code — imported inactive, with a placeholder code, so nothing is lost.
Aliases dropped (key already owned) — a curated alias that collided with an existing language code. Usually correct: de-DE is its own language row, so de does not also get de-de.
Providers with no regional overrides — not a gap. The console only stored codes for regional languages.
Applying it
Committing is enough — the deploy runs python -m app.cli seed after migrations. Locally:
python -m app.cli seed
python -m app.cli stats
The seed is idempotent and upsert-shaped: rows are matched on their natural key and updated in place. It never deletes.
The seed wins for rows it knows about
A code pinned in the admin panel for a pair the seed also carries is reset to the seeded value on the next deploy. Permanent corrections belong in the seed — either fix the console and re-dump, or add the pair to the catalog table in scripts/build_seed.py. Rows the seed has never heard of are untouched.
Adding a provider to the seed
CATALOG in scripts/build_seed.py mirrors provider_credentials' config/providers.yaml. A new entry needs code, vendor_code, vendor_name, name, category, fallback_policy, and legacy — the console service_names that map onto it.
Getting legacy right is what keeps old callers working: every listed name becomes a provider alias, and every console mapping under those names is folded into this provider's table.