Part of a Data Solution
- Explore →
Postal Codes Solution
Every worldwide postal-code dataset in one bundle — the ultimate global reference for precise postal codes.
Premium
You're viewing a free sample
Get the complete dataset — all rows and files — delivered instantly after checkout, with lifetime access to the latest version.
- Secure checkout via Stripe
- Instant download after payment
- Lifetime access to the latest version
- Creative Commons Attribution 4.0 license
API Access
Access dataset files directly from scripts, code, or AI agents.
Browse dataset files
API Access
Access dataset files directly from scripts, code, or AI agents.
Each file has a stable URL (r-link) that you can use directly in scripts, apps, or AI agents. These URLs are permanent and safe to hardcode.
Start with these files — they give you everything you need to understand and access the dataset.
- 1. Fetch datapackage.yaml to inspect schema and resources
- 2. Download data resources listed in datapackage.yaml
- 3. Read README.md for full context
Data Files
postal-codes-de-sample
| Field | Type | Description | Constraints | Title |
|---|---|---|---|---|
| country_code | string | ISO 3166-1 alpha-2 country code - always DE | { "pattern": "^DE$", "required": true } | Country Code |
| postal_code | string | Five-digit German postal code (Postleitzahl) | { "pattern": "^\\d{5}$", "required": true } | Postal Code |
| place_name | string | Name of the town, city or locality (Ort) | Place Name | |
| admin_name1 | string | Federal state name (Bundesland, e.g. Bayern) | Administrative Name 1 | |
| admin_code1 | string | Federal state code, ISO 3166-2:DE without the DE- prefix (e.g. BY, NW, BE) | Administrative Code 1 | |
| admin_name2 | string | Government region (Regierungsbezirk) where one exists - most states have no such tier, so this is often blank | Administrative Name 2 | |
| admin_code2 | string | Numeric code for the government region where present | Administrative Code 2 | |
| admin_name3 | string | District (Kreis / Landkreis / kreisfreie Stadt) | Administrative Name 3 | |
| admin_code3 | string | Numeric district key (Kreisschluessel) | Administrative Code 3 | |
| latitude | number | Latitude (WGS84, decimal degrees) | Latitude | |
| longitude | number | Longitude (WGS84, decimal degrees) | Longitude | |
| accuracy | string | GeoNames accuracy of latitude/longitude. GeoNames documents 1=estimated, 4=geonameid, 6=centroid of addresses or shape; other values (e.g. 3) occur upstream without documentation. Empty where GeoNames gives none. | Accuracy | |
| alternative_city_name | string | Alternative or historic name for the place where known | Alternative City Name |
Download
Download sample CSVAbout
- Last updated
- 6 October 2026
- Total rows
- 100
- Format
- CSV
- File size
- 13.7 kB
About this dataset
Germany (DE): migration notes
Internal record of the P5 migration from the old scripts to the producer: the discrepancies found, their causes, and who decided what. This is a dated record, so the numbers are as of the migration and not kept current. It is not shipped in the zip.
- Issue: pc-28e.7.15 (closed 2026-10-05)
- Commit:
5192c7e(worktree commit4923c21, same content), landed in batch 6 on 2026-10-05 after the user's decision - Parity verdict:
unexplained(the gate blocks on any unclassified cell); the user accepted replacing the live copy - Producer:
custom, refresh disabled
Sources
| Input | URL | Notes |
|---|---|---|
| GeoNames postal export | https://download.geonames.org/export/zip/DE.zip | |
| GeoNames gazetteer dump | https://download.geonames.org/export/dump/DE.zip | Used for alternative_city_name; replaces the legacy R2 copy |
The old datapackage.yaml sources was a placeholder ("Multiple sources", the datahub.io collection link). It now lists these two URLs.
What live actually is
The live de.csv was never built by the de builder. It is the output of the legacy generic scripts/geonames/geonames_parse_to_R2.py. That job read GeoNames with pandas and no dtype=str, and its clean_and_normalize() turned punctuation into spaces. The de builder (fetch_source.py → build_base.py → recover_alt_names.py → resolve_duplicates.py → integrity_checker.py, 2026-08-19) was written to fix exactly those bugs. Its own README already recorded the result as "23,297 rebuilt vs 23,293 live", but it was never published. So a faithful port can't match live without bringing the bugs back.
Parity against the live zip (2026-10-03)
Rows: 23,293 live and 23,297 fresh. On the exact key (postal_code, place_name): 3,407 only in live, 3,411 only in fresh, 19,885 changed rows. parity.py classes: float_suffix_dropped ×20,309, leading_zero_restored ×32,738, unclassified ×23,667, whitespace_or_case ×2.
The agent then matched rows on the legacy cleaned key. Every live row lines up, and every difference has a known cause. The counts below come from that alignment, so they differ a little from parity.py's exact-key counts.
| Change | Count | Examples | Cause |
|---|---|---|---|
| Codes back to source text | admin_code2 21,177; admin_code3 23,288; accuracy 15,049; lat/lon 151 | 81.0 → 081, 8111.0 → 08111, 4.0 → 4, 53.0 → 53 | Legacy pandas float coercion |
| admin_code1 corrected | 6,819 rows | Baden-Württemberg SH → BW (1,020), Bavaria HH → BY (1,114), Nordrhein-Westfalen RP → NW (1,849) | GeoNames' code column mixes ISO letters with its own alphabetical index 01..16. The legacy copy remapped that index by position as if it were AGS (where 01 = Schleswig-Holstein) |
| Punctuation restored in names | place_name 3,403 rows (these are the only-in-live/only-in-fresh pairs); admin_name1 11,569; admin_name2 841; admin_name3 7,679 | Mercedes Benz io GmbH → Mercedes-Benz. io GmbH; Baden Württemberg → Baden-Württemberg; Rems Murr Kreis → Rems-Murr-Kreis | Legacy clean_and_normalize() |
| State names in German | admin_name1 2,256 | Bavaria → Bayern, Land Berlin → Berlin | Old build_base.py maps every spelling to the endonym |
| +4 rows | 4 | 06917 Jessen (Elster) Jessen (Elster) / Jessen (Elster); also 15518 Briesen, 16835 Lindow, 31582 Nienburg | Legacy cleaning collapsed two distinct upstream names onto one key, and live kept one of each. Fresh keeps both |
| alternative_city_name | 280 cells: 196 filled where live was empty, 81 changed, 3 emptied | 14612 Falkensee "" → Anschlussstelle Achtundzwanzig; 16761 Hennigsdorf Genigsdorf → Anschlussstelle Hennigsdorf; 19230 Bresegard Bresegard → "" | GeoNames dump edits (upstream drift). Falkensee: new entry 13679549 (RRDJCT, modified 2026-07-15) is now the last "Falkensee", and the last entry wins. Hennigsdorf: entry 13679651 (modified 2026-07-15). 14715 Kriele: entry 2883945 (modified 2026-07-13) |
| whitespace_or_case | 2 | 50664 STRABAG AG / Strabag AG | Two upstream rows that differ only in case. The exact key is unique |
223 of the 280 alt-name cells now just repeat the place name (e.g. 14715 Kriele → Kriele). The old de builder had no rule dropping those (pl's had), so none was added.
Decisions
| Decision | By | Basis |
|---|---|---|
| Accept the fixed data in place of the buggy live copy (admin_code1 on 6,819 rows, punctuation, leading zeros, German state names, +4 rows) and the 280 alt-name cells | User, 2026-10-05 ("Accept, land it", session 7df0c5be) | A logic change, so not covered by the standing rules. Recorded in the sign-off section of docs/parity/p5.md |
Rebuild alternative_city_name from the GeoNames dump rather than copying the legacy R2 de/de.csv | D1 (pl precedent) | The old recover_alt_names.py read the live R2 object, which isn't a real source |
| Don't add an "alt name repeats place name" filter | Agent: a faithful port | de's old builder didn't have one |
| Derive admin_code1 from admin_name1, never from GeoNames' code column | Agent: carried over from build_base.py | The code column mixes two encodings |
| A duplicate (postal_code, place_name) now stops the build; no reference table kept | Agent | resolve_duplicates.py's table (sources/duplicate_resolutions.csv) was never committed and was keyed to the legacy cleaned names. On verbatim names the export has no duplicate keys, so it resolved nothing. The old script also failed on any duplicate its table didn't cover |
| Remove the README sentence about "one duplicate pair… less accurate coordinate" | Agent | It described the duplicate resolution, which no longer happens |
Replace the placeholder sources with the two GeoNames URLs | Orchestrator instruction | Agent prompt |
Transforms carried over
Folded from fetch_source.py → build_base.py → recover_alt_names.py → resolve_duplicates.py → integrity_checker.py:
- Cells read with csv as text (no pandas), only stripped of surrounding whitespace.
admin_code1→ ISO 3166-2:DE (prefix stripped) fromadmin_name1; an unknown state name is aProducerError.admin_name1→ the German endonym for every spelling GeoNames uses.- 87491 Jungholz (an Austrian village on the German postal network) keeps no state.
alternative_city_name: first non-numeric alternate name of the dump entry whose asciiname equals the cleaned place_name (parenthesised part dropped, punctuation to spaces, whitespace collapsed; last entry wins). The cleaned name is only the lookup key.- The integrity checks are now
ProducerErrors: 5-digit postal codes, unique primary key, all 16 states present, no literalnanor.0codes, coordinates inside Germany's bounding box. No row-count assert. - Rows keep the export's order.
Open questions
- Resolved (2026-10-05, pc-28e.7.60): marked the follow-up as superseded in
.licensing-notes.md. Was:.licensing-notes.md(2026-08-19 finding) still saysresolve_duplicates.py's table "needs a follow-up update" before a real publish. That no longer applies, and the note wasn't corrected. - The README's fill percentages (e.g.
23.8%) are the old values and now slightly stale..licensing-notes.mdalso holds counts. Both are left for the pc-hfz sweep.
Provenance
Reconstructed 2026-10-05 from the agent transcript 7df0c5be-a523-4080-87f9-6045a6a41515/subagents/agent-ae929f734c2057331.jsonl, the orchestrator session 7df0c5be-… (the user's decision, 2026-10-05), the commit message, the bd close reason and docs/parity/p5.md.