Published

France (FR) build pipeline

Postal Codes Dataset for France, FR including name of the city, town, or place, various administrative divisions and alternative city names.

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 exporthttps://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:

DefectBeforeAfter
Postal codes zero-padded to 15 chars00000000007500075000
admin_code1 float-coerced11.011
Literal nan strings461 cells0
Punctuation stripped from namesLe Bellay en VexinLe 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 in build_stats.json.
  • 13/13 official post-2016 INSEE regions · 96 departments · 20,418 distinct postal codes.
  • Spot-checked: Paris 75000, Lyon 69000, Marseille 13000, leading zeros preserved (01410, department 01), 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_code is not always 5 digits. 37,232 plain NNNNN, 14,332 CEDEX (77039 CEDEX 9), plus 21 SP/AIR/CITYSSIMO codes. Never load it as a number.
  • admin_code1 is taken from the source, not re-derived. For France GeoNames already supplies the current official post-2016 INSEE region codes, unlike Italy where its admin_code1 is a non-official internal numbering that had to be replaced.
  • alternative_city_name is always blank — not supplied by this source.
  • admin_name3/admin_code3 are blank on 361 CEDEX rows — genuinely empty upstream.
  • One row (98799 Clipperton Island) has no region or department — a real uninhabited French possession in the Pacific, so its coordinates sit far outside metropolitan France.