Home / Blog / Thirteen scripts, eleven weeks, one database
May 11, 2026· Wine World Map
Thirteen scripts, eleven weeks, one database
In scripts/ there are thirteen files named seed-wNN.js, where
NN is the ISO week number when the script was written. The first
is seed-w21.js from week 21 of this year; the last is seed-w31.js
from a few weeks ago. Two of them (-w22b-regions, -w26b-regions)
are mid-week patches.
Together they're how the districts table got populated.
Why not one big seed?
The obvious design — a single seed.js that knows every district —
falls apart for two reasons.
First, the data we want isn't static. New regions get added (Etna's contrade), old ones get re-classified (Brouilly graduated from a district to a region), and dozens of small AVAs only became relevant once we'd built the UI to display them. A single seed file would need a sophisticated diff/upsert to remain idempotent.
Second, we genuinely didn't know in week 21 what we'd want by week 31. The script-per-week pattern means each week's additions are self-contained: a flat array of districts plus the same boilerplate insert loop at the bottom.
const D = [
{ region: 'Etna (Sicilia)', name: 'Linguaglossa',
lat: 37.84, lng: 15.14, zoom: 12, type: 'DOC',
desc: 'Norra Etnas vinhuvudstad — Nerello Mascalese.' },
// ...130 more
]
;(async () => {
// load existing, dedup by (region_id, name), insert in batches of 100
})()
Each file is between 100 and 150 lines, of which the data is roughly 80% and the insert harness 20%.
The shared harness
Every seed script ends with essentially the same code:
const { data: regions } = await s.from('regions').select('id, name')
const idByName = new Map((regions || []).map(r => [r.name.toLowerCase(), r.id]))
const { data: existing } = await s.from('districts').select('region_id, name')
const ek = new Set((existing || []).map(d => `${d.region_id}:${d.name.toLowerCase()}`))
const ti = []
for (const d of D) {
const rid = idByName.get(d.region.toLowerCase())
if (!rid) { console.warn(`missing: ${d.region}`); mr++; continue }
const k = `${rid}:${d.name.toLowerCase()}`
if (ek.has(k)) { dup++; continue }; ek.add(k)
ti.push({ region_id: rid, name: d.name, /* ... */ manually_edited: true })
}
Note the manually_edited: true on every insert — these are
hand-curated districts that the OSM importer is forbidden from
overwriting (see the import-seams post).
Note also mr (missing-region) and dup (duplicate skipped) as
counters at the bottom. The output of every seed run looks like:
total: 134, ins: 91, mr: 7, dup: 36
inserted 91
The 36 duplicates are districts that were already in the database
from a previous week's run. We could refactor that away — INSERT … ON CONFLICT DO NOTHING would do it — but the explicit dedup is
useful as a sanity check.
What the scripts encode beyond data
Reading the seed files in order tells you what the project was focused on each week:
| Week | Focus | |---|---| | 21 | Spain (extra DOs), Portugal (Douro/Alentejo), Switzerland | | 22 | New regions split off, including Brouilly/Bolgheri | | 23-24 | German VDPs, Austrian DACs, Hungarian | | 25-26 | New World: Australia GIs, NZ sub-regions | | 27 | Slovenia, Croatia, Georgia | | 28-29 | South America (Mendoza sub-regions, Chile zones) | | 30 | South Africa (wards), Lebanon, Turkey | | 31 | Italy: Etna contrade, Franciacorta, Lambrusco subzones |
You can tell from the descriptions which weeks the author was
deep into wine reading and which weeks were structural. The Etna
contrade in w31 have unusually specific descriptions ("Etna
Bianco Superiore — endast från Milo"). The Australian GIs in
w25-26 are terser ("Klassiska Mt Barker-trakter").
When does this pattern stop scaling?
Probably around seed-w52.js. The friction is that re-running an
old seed script to check what it did takes a roundtrip to Supabase
even if everything's already inserted (it loads existing districts
to dedup). Thirteen scripts is fine. Fifty would be ugly.
The endgame is to flatten all the seeds into a single
districts.json exported from the live database, ship that as
canonical reference data, and retire the weekly scripts. We're not
there yet because the database is still moving — most weeks
something gets restructured, and a flat JSON would have to be
re-exported every time.
For now, seed-wNN.js is both a script and an audit trail. Git
already tells you what was committed each week. The seed scripts
tell you what got inserted into Supabase each week, which is the
data that actually shipped.
#seed#history#process