Skip to content

Curate two Maltese localities misfiled under Mali - #13

Merged
sebschlo merged 2 commits into
masterfrom
curation/malta-misfiled-records
Aug 20, 2026
Merged

sebschlo merged 2 commits into
masterfrom
curation/malta-misfiled-records

Conversation

@sebschlo

Copy link
Copy Markdown
Owner

Who's On First ships duplicates of two Maltese localities inside whosonfirst-data-admin-ml, the Mali repository:

id name tagged coordinates population parent
101912869 Mellieħa ML 35.9707, 14.3656 (Malta) 5976 wof:parent_id: -1
101846471 Naxxar ML 35.9418, 14.4590 (Malta) 10378 wof:parent_id: -1

Both carry Malta's coordinates, name and population — Mellieħa's 5976 is exactly the Maltese record's — with iso:country: ML and a hierarchy containing nothing but Mali's country id. They are misfiled records, not places in Mali.

Why it matters beyond one town

Between them they own three precision-5 cells over the real towns, so those coordinates reverse geocode into Mali while forward search returns the Maltese ids. In the app that means a resident and someone selecting the same town from search receive different city_key values and cannot be grouped — the town silently splits in two.

Why curation rather than a build rule

Curation is the last resort in this repo, so each generic rule was tried against a world build and measured before falling back:

candidate rule measured result
Distance from nearest same-country neighbour Useless here — the misfiled records cluster, nearest ML neighbour is 1 km. Also unsafe: 973 places world-wide are >100 km from a same-country neighbour and are legitimate (Svalbard, Bonaire, French Polynesia).
Missing admin1 parent Only 20 localities world-wide, but they include Lilongwe, Banjul, Honiara, Tarawa. Would drop capitals.
Validate country tag against the country polygon holding the centroid Impossible from this dataset — whosonfirst-data-admin-* ships no country records: 0 in Mali's 15,060 files, 0 in Malta's 395. Needs a new data dependency.
Cross-country name + population duplicate Narrow enough (14 pairs world-wide, 4 with identical population) but misses this very record: ħ has no Unicode decomposition to h, so the names never normalise equal.

That evidence lives in the entry's own rationale, so a future contributor does not have to re-litigate it.

Format additions

Two small, guarded extensions the entry needed, both covered red-green:

  • entries[].absorbForeignTagged — lets an absorb id carry another country's tag, which is the defect. Opt-in per entry so it can never result from a typo'd id, and into is never exempt: a file can still only ever produce labels for the country it is named for.
  • probes[].country — lets a guard probe name the country its expected label belongs to, so the entry can assert Mali itself is untouched. Verification resolves the country from the database, so a same-named place across the border cannot satisfy the guard.

Scope

Two further Mali-tagged records inside Malta's bounding box are deliberately left out, and the entries say so: San Giljan (101752435) owns no cells so it can produce no wrong answer, and Xghajra (101846463) has a population of 837 and does not clear the build's floor. Recorded rather than omitted, so a later build that gives either of them cells has a note to act on.

Verification

Strict — --verify, no --skip-unresolvable — against a Mali + Malta build at the world configuration:

merge into 101752447 (Mellieha), absorbing [101912869] at precision >= 5: relabeled 2 cell(s)
merge into 101752441 (Naxxar),   absorbing [101846471] at precision >= 5: relabeled 1 cell(s)
PASS entry 1 probe (35.958386, 14.367172) [Mellieħa's own centre] -> "Mellieha"
PASS entry 1 probe (35.939221, 14.400375) [guard: neighbouring Maltese locality] -> "San Pawl Il-Bahar"
PASS entry 1 probe (12.65, -8)            [guard: Mali untouched] -> "Bamako"
PASS entry 2 probe (35.969238, 14.436035) [cell the Mali-tagged record owned] -> "Naxxar"
PASS entry 2 probe (35.939221, 14.400375) [guard: no reach west] -> "San Pawl Il-Bahar"
PASS entry 2 probe (16.27, -0.05)         [guard: real Malian places] -> "Gao"
Probes passed: 6, skipped: 0, failed: 0
Cells relabeled: 3

Full suite green: 227 specs, 0 failures.

Who's On First ships duplicates of Mellieħa (101912869) and Naxxar
(101846471) inside whosonfirst-data-admin-ml. Each carries Malta's
coordinates, name and population - Mellieħa's population is 5976, exactly
the Maltese record's - but wof:country ML, wof:parent_id -1 and a
hierarchy containing nothing but Mali's country id. Between them they own
three cells over the real towns, so those coordinates reverse geocode
into Mali while forward search returns the Maltese ids, and the two can
no longer be grouped.

Curation is the last resort here, so the rationale records the four
generic rules that were tried against a world build and the measured
reason each was rejected: distance from the nearest same-country
neighbour (useless - the misfiled records sit beside each other, 1 km
apart - and unsafe, since 973 legitimate places are more than 100 km from
one); a missing admin1 parent (only 20 localities world-wide, but they
include Lilongwe, Banjul, Honiara and Tarawa); validating the country tag
against the country polygon holding the centroid (impossible - the admin
repositories ship no country records at all, 0 in Mali's 15,060 files and
0 in Malta's 395); and cross-country name-and-population duplicate
matching (narrow enough at 14 pairs world-wide, but it misses this record
because "ħ" has no Unicode decomposition to "h").

The entry needed two small, guarded additions to the overlay format:

  - "absorbForeignTagged" lets an absorb id carry another country's tag,
    which is the whole defect. It is opt-in per entry so it can never be
    the result of a typo'd id, and the target is never exempt - a file
    can still only produce labels for the country it is named for.
  - "probes[].country" lets a guard probe name the country its expected
    label belongs to, so the entry can assert that Mali itself is
    untouched. Verification resolves the country from the database, so a
    same-named place across the border cannot satisfy the guard.

Both are covered red-green. Two Mali-tagged records inside Malta's
bounding box are deliberately left out, and the entries say so: San
Giljan owns no cells, and Xghajra's population of 837 does not clear the
build's floor.

Verified strictly against a Mali+Malta build with --verify and no
--skip-unresolvable: 6 probes passed, 0 skipped, 0 failed, 3 cells
relabeled.
@sebschlo

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 261de9dc81

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/apply_curation.js Outdated
The country check short-circuited on `!probe.country`, so it only ever
ran for probes that spelled a country out. An omitted "country" is
documented to mean the entry's own country, and that default was never
enforced: every probe written without one - including all six in gt.json
- could be satisfied by a same-named place across a border, which is the
exact confusion the field was added to prevent.

The comparison now runs unconditionally.

Case: no mismatch to reconcile. Entry and probe countries are validated
as /^[A-Z]{2}$/ and the files declare "GT"/"MT"; placeCountry() reads
country_id, which the builds store uppercase, and uppercases it anyway.
Both sides are normalised explicitly so the comparison stays correct if
either validator is ever relaxed.

Null: fails closed. compact_places.country_id is NOT NULL and no built
database has an empty one, so this can only mean the reverse result
carried no id - and a verification tool must never pass a probe it was
unable to check. Pinned by its own spec, because nothing else was
holding that decision in place.

The failure hint names the resolved country and, for an unqualified
probe, says probes default to the entry's country, so an author who
meant to cross a border knows to add the field rather than guessing.

Verified strictly over both curation files against a gt+ml+mt build:
12 probes passed, 0 skipped, 0 failed, 7 cells relabeled. Forcing every
resolved country to a wrong value turns all of them red - including the
six unqualified gt.json probes - which is what proves they were checked
rather than skipped.
@sebschlo

Copy link
Copy Markdown
Owner Author

@codex review

@sebschlo
sebschlo merged commit 2583a49 into master Aug 20, 2026
2 checks passed
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: 42ad8afd98

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant