France (FR) build pipeline
Self-contained — runs entirely from this repo. All paths are relative to this
scripts/ folder; sources/ and data/ are siblings under datasets/fr/ and are
gitignored (regenerable).
Built 2026-08-24 as a corrected republish. France previously shipped as a bare
fr/fr.csv with no datapackage, README or zip, and with every postal code destroyed. See
../.licensing-notes.md for the full audit.
Run order
python3 fetch_source.py # GeoNames FR.zip -> ../sources/geonames/FR.txt + manifest
python3 build_base.py # -> ../data/fr.csv (13-column schema) + build_stats.json
python3 integrity_checker.py # gate; writes consolidated_stats.json, exits 1 on BLOCK
python3 package.py # -> datapackage.json + README.md + fr.zip (requires PASS)
python3 publish_r2.py # dry run by default; --apply to upload
publish_r2.py deliberately does not touch _meta/catalog.json — that is shared
state describing every dataset in the bucket and is updated separately following the
backup / re-read / assert-count sequence in docs/R2_ACCESS.md §7.
Source and licence
GeoNames FR postal export — https://download.geonames.org/export/zip/FR.zip,
CC BY 4.0. Attribution is a condition of use and is carried in the published
README.md and datapackage.json. France is not on GeoNames' restricted-coverage
list (that list names Chile, Ireland and Malta as truncated "for copyright reasons", and
China, Argentina and Brazil truncated without stated reason — France appears in none).
Provenance was not recorded anywhere when this was inherited; the file on sale was identified as GeoNames by forensic fingerprinting (51,611 rows against GeoNames' 51,611, every row traced, zero unexplained). La Poste's official base was ruled out — 39,192 rows, matching at 80.55% versus GeoNames' 99.53%. La Poste (Licence Ouverte 2.0, commercial reuse permitted) remains a viable re-source option if authoritative provenance is ever preferred over GeoNames' coverage, but it omits the CEDEX layer.
What this build fixes
The previous R2 file carried four defects, all from the shared legacy
scripts/geonames/geonames_parse_to_R2.py reading with pandas and no dtype=str:
| Defect | Before | After |
|---|---|---|
| Postal codes zero-padded to 15 chars | 000000000075000 | 75000 |
admin_code1 float-coerced | 11.0 | 11 |
Literal nan strings | 461 cells | 0 |
| Punctuation stripped from names | Le Bellay en Vexin | Le Bellay-en-Vexin |
integrity_checker.py asserts against all four so a regression cannot reach R2 silently.
The datapackage.json also declares postal_code as a string with an explicit pattern,
so a schema-aware reader cannot re-coerce it downstream.
The shared parser itself was NOT modified — it is used by many countries and fixing it is a separate, wider job. The same defect class is already documented for PT and DE.
Verification (2026-08-24)
- 51,611 source rows → 51,585 published. The 26-row difference is 24 groups of
coordinate-only duplicates (same postal code, place name and full admin hierarchy,
differing by ~100–200 m — two gazetteer entries for one commune centroid). Dropped so
(postal_code, place_name)holds as a primary key; counted inbuild_stats.json. - 13/13 official post-2016 INSEE regions · 96 departments · 20,418 distinct postal codes.
- Spot-checked: Paris
75000, Lyon69000, Marseille13000, leading zeros preserved (01410, department01), accents and hyphens intact (Île-de-France,Bouches-du-Rhône,Chézery). - Integrity check PASS: 0 zero-padded, 0
nan, 0 float-suffixed, 0 duplicate keys. - Published to R2 and round-trip verified: downloaded copy re-validated with Frictionless from inside its own directory — valid.
Known characteristics (not defects)
postal_codeis not always 5 digits. 37,232 plainNNNNN, 14,332 CEDEX (77039 CEDEX 9), plus 21 SP/AIR/CITYSSIMO codes. Never load it as a number.admin_code1is taken from the source, not re-derived. For France GeoNames already supplies the current official post-2016 INSEE region codes, unlike Italy where itsadmin_code1is a non-official internal numbering that had to be replaced.alternative_city_nameis always blank — not supplied by this source.admin_name3/admin_code3are blank on 361 CEDEX rows — genuinely empty upstream.- One row (
98799Clipperton Island) has no region or department — a real uninhabited French possession in the Pacific, so its coordinates sit far outside metropolitan France.