Spain (ES) build pipeline
Self-contained — runs entirely from this repo, no fedex-agents-prototype
dependency. Fixed 2026-08-19: every script computed ROOT = parents[3] and
hardcoded countries/ESP/... / .cache/esp/... / .cache/jobs/ESP/...
subpaths, correct only in that other repo's layout. All paths are now
self-relative (ES_DIR = parent of this scripts/ folder); sources/,
data/, es_es/ live as siblings under datasets/es/.
Two static, hand-authored reference files committed here (not re-derivable by re-running any fetch step):
muni_corrections.csv— 7 manually-verified municipality corrections, each with its own evidence string (CartoCiudad geocoder query results). Read bybuild_dataset.py.delivery_README_template.md— the buyer-facing README.package_delivery.pyasserted it existed in the delivery folder but nothing wrote it there; now copied in automatically.
resolve_po_box_codes.py bootstraps from the currently-published es.csv
on R2 (same bucket this pipeline publishes to — a within-repo dependency, not
an external one) purely as a discovery list of which PO-box-style postal codes
exist; none of that file's data is trusted as-is (its own admin_name3 is the
literal string "nan" and its coordinates are demonstrably wrong for these
rows — see the script's docstring). Every code is independently re-resolved.
Auto-fetches via R2 credentials if the cached copy is missing.
Run order (from this directory) — note the two-pass build_dataset.py
build_dataset.py must run twice: once before resolve_po_box_codes.py
(to produce the WFS-only Spain.csv baseline that script diffs against), and
once after (to fold the resolved PO-box codes and fetched coordinates back
in). Running resolve_po_box_codes.py before any Spain.csv exists, or
skipping the second build_dataset.py pass, are both real, easy-to-hit
mistakes — a prior run this same day silently overwrote the resolved PO-box
table with an empty one by getting this order wrong.
python3 scrape_postal.py # CartoCiudad INSPIRE WFS -> ../data/postal_raw.csv
python3 build_admin.py # INE municipality register -> ../data/admin.csv
python3 build_dataset.py # PASS 1 — WFS-only baseline, no coords/PO-box yet
python3 resolve_po_box_codes.py # needs pass-1 Spain.csv to exist
python3 fetch_coordinates.py # threaded, rate-limited — takes a while
python3 build_dataset.py # PASS 2 — folds in po_box_codes.csv + coords.jsonl
python3 package_delivery.py # copies delivery_README_template.md in, builds es.zip
python3 publish_r2.py # dry run by default; --apply to publish
fetch_coordinates.py and build_dataset.py also need two source dumps
fetched fresh (not committed, regenerable):
curl -sL -o ../sources/ES.zip https://download.geonames.org/export/zip/ES.zip
unzip -o -q ../sources/ES.zip ES.txt -d ../sources/
mv ../sources/ES.txt ../sources/geonames_ES.txt
Sources
- IGN/CNIG CartoCiudad INSPIRE WFS — CC BY 4.0 (postal codes, coordinates)
- INE (Instituto Nacional de Estadística) municipality register — official Spanish government source (admin reference)
- GeoNames ES — CC BY 4.0 (locality cross-check)
See fedex-agents-prototype/CLAUDE.md's Admin Level Mapping Registry entry for
ESP (if available — not required to run this pipeline) for the full admin-tier
and prefix-routing detail: e.g. the 40 postal codes whose prefix legitimately
disagrees with the INE province, and why Wikipedia is explicitly NOT used as
the admin source here (CC BY-SA share-alike would encumber a resold
derivative).
Verification (2026-08-19)
Fresh end-to-end run, entirely within this repo. scrape_postal.py:
14,305 relations, 10,854 distinct codes, 8,188 distinct municipality codes, 0
rejected. build_admin.py: 8,132 municipalities, 52 provinces, 19 communities
— exact match to documented counts. Pass-1 build_dataset.py: 100% admin
coverage. resolve_po_box_codes.py: 288/288 resolved — exact match to the
same-day fedex-agents-prototype run. Coordinate fetch and pass-2 build in
progress at time of writing; this pipeline was already verified matching R2
exactly earlier the same day (14,538/14,538 rows, only a minor
alternative_city_name gap on 148 PO-box rows from independent geocoder
resolution) — the path refactor changes no logic, only where files are
read/written.