Re-probe a city that screening left thin - ask the city's OWN host, enumerate its whole catalogue, read who a 403 is for, and look for a second layer in a different shape (a building register's use class) - then measure the new shape's defect, control each layer, read licences in parallel and record. Use for every row in the master list's open screening gap, and for the built one-bucket cities (Stockholm, Göteborg, Zurich, Bucharest) to find out whether a second source exists. Not for a city screened for the first time - that is add-city Step 0.
Install
npx skillscat add dacekroberts/expanded-heatmap/reprobe-city Install via the SkillsCat registry.
Re-probing a city: its own host, its whole catalogue, a second layer
Distilled from Amsterdam, 2026-09-24. It had been discarded as "register
is aggregate" on the top hits of ONE national search. Re-probed in one
evening, it went discard → Band C (a live hospitality-permit register on
the city's own API) → Band A (plus the BAG's shop units as a second layer).
Every step that moved it was a probe the original screen never ran. Evidence:docs/global_country_shortlist.md, the 2026-09-24 audit section; the brief isdocs/build_briefs/amsterdam.md.
Two uses. The open screening gap (a section of docs/city_master_list.md,
empty since 2026-09-24; its past rows, each naming its next probe, are indocs/city_master_list_evidence.md) — the question is whether a register exists at
all. The one-bucket cities in Band C — the question is whether a SECOND
source exists, and Step 4 is the step that matters.
Step 1 — Ask the city's own host, not the national portal
Amsterdam's discard rested on data.overheid.nl; everything that moved it sat
on api.data.amsterdam.nl, which no one had asked. A national catalogue's
harvest of a city is not the city: the permits were not in its results.
- Find the city's data host — portal, API, ArcGIS org, statistics office.
Trywww.; try the API root withAccept: */*(Amsterdam's refused
without it). Sevilla's lesson also applies: a dead portal's last Internet
Archive capture can name its successor. - If the city's host is blocked, diagnose before recording (Globalping from
inside the country against a foreign control —address-joinStep 3). A
geo-block is Band D's shape, never something to route around.
Step 2 — Enumerate, don't search
- List every dataset. Amsterdam's API root holds 100. It was first
recorded as 383, which counted dataset AND table paths — count datasets. - Read the list by eye for anything premises-shaped, then for each candidate
list its tables with row count and field names — one request per table
with_pageSize=1&_count=trueon a DSO API;package_showon CKAN. - Before believing any server-side filter, send a nonsense value.
Amsterdam's BAG filter returned 11,607 forwinkelfunctieand 0 forzzzzqqq; a filter that is silently ignored returns the whole table. - A read-only API POST counts as a GET (owner, 2026-09-24): a GraphQL or
search-API query that lists or reads data, with no account and no personal
data sent. Poznań's portal refuses GET, and a POST-only catalogue is not a
reason to stop. A public open-data portal's own DOWNLOAD button that POSTs
a form also counts as a GET (owner, 2026-09-24, for Kyoto's portal, whose
files have no direct link). Still not a query: search, contact or order
forms, or anything carrying an e-mail, a name or a login, such as Estonia's
building-register order (Step 3's owner's act).
Step 3 — A 403 is a finding about access: read who it is for
Never worked around. Read the published schema or metadata for the dataset's
authorisation scope: Amsterdam's hr_kvk carries FP/MDW and HR/R,standbedrijven BSK/BEDRIJVEN (schemas.data.amsterdam.nl/datasets/<name>/dataset)
— city-staff scopes, so a written request was judged unlikely to land and was
not drafted. A scope open to public registration is different: that is an
owner's act (a form with a name and e-mail), never this project's.
Step 4 — Look for the second layer in a different SHAPE
The one-bucket problem is usually structural — Sweden has no general business
licence, Switzerland's STATENT is aggregate — so a second REGISTER of the same
shape will not appear. Look for premises by permitted use instead: a
building or address register with a use class.
- Amsterdam's: the BAG,
verblijfsobjectenfiltered togebruiksdoel = winkelfunctie— 10,898 units in use, 100% with a point, 90% shop-only. - What the shape gives: every premises, a location, a current status. What it
lacks: no name, no activity, no occupancy, and retail and personal
services share one class. The owner accepted all of that for Amsterdam on
one condition (Step 5). - Where the shape may exist — ASSERTED, unprobed; probe targets, not
findings: Sweden (Lantmäteriet's building register, purpose codes),
Switzerland (the federal building and dwelling register, GWR), Denmark (BBR
use codes), Norway (Matrikkelen building type), Czechia (RÚIAN building
use), Spain (Catastro uso), Finland (the building register). Singapore
and Bucharest: unknown. Read the use-class field's values and fill before
counting on it — a class that is empty or one catch-all is no layer. - Keep looking for the ordinary shapes too: a sector inspection register
(food), a market or street-trading register, a licensing register for one
trade (hairdressers). Two narrow sources can make the second bucket.
Step 5 — Measure the defect the new shape brings, then filter or disclose
Name the defect and measure it before asking the owner. For a use-class layer
it is vacancy — an empty shop still has the shop class.
- Look for a per-unit filter first: an occupancy or actual-use field
(Amsterdam'sfeitelijkGebruikwas empty on all 10,898), a per-unit energy
or tax field, a join to a business register. Amsterdam's property snapshot
(standvastgoed, 134 fields) had none; energy use was per neighbourhood;
the per-unit sources (Locatus, the KvK) were paid or staff-only. - If none is open, find the RATE. City statistics often republish a
commercial survey: Amsterdam'sbbgacarries Locatus's vacant sales points
— 660 of 14,314, 4.6%, per district and per neighbourhood. - Put the number to the owner: disclosable or not. Amsterdam's was,
and its page now carries it as a bullet under a third heading, Reading
the map: "Read the shop layer as space set aside for shops, not as open
shops. About one Amsterdam shop unit in twenty stood empty at the start of
2026, by the city's own statistics, and no open source says which." - Check overlap between layers: a takeaway can hold a food permit AND sit
in a shop-class unit — de-duplicate by address at build.
Step 6 — Control each layer on its own
Each layer against its own OSM control, with the ratio and its direction:
Amsterdam's permits 0.89× OSM restaurants, BAG 1.77× OSM shops.
Comparators: Prague 1.63×, Rome 2.60×. Overpass 504s are routine — try a
second host before recording anything.
Step 7 — Licences: one licence-read agent per source, in parallel
Run them in the background while the brief is drafted. Amsterdam's took 10–12
minutes and ~135–145k tokens each. Two things they turned up that recur:
- A city's copy of a national register may be SILENT while the national
publisher is explicit — the Kadaster's Public Domain Mark governs the BAG;
use only the national fields, since the city's additions carry no licence. - SILENT with a conflict (the permits: no licence live, CC BY in a retired
record) is not resolved in the project's favour. Offer the owner the display
that satisfies BOTH readings — Amsterdam: credit as CC BY 4.0.
Record access conditions separately from licence terms: Amsterdam's API key,
mandatory from an unset date, is a form the owner fills in.
Step 8 — Record
- A gap row that stays negative goes to the discard table only if
python scripts/check_discard_evidence.pypasses it — kind, methods, host. - A band move is the owner's; recommend with the tradeoff.
- A brief with a
brief-checksblock: the live endpoints, the filter, the
licence pages (present/absent), the gated scope. Amsterdam's reads 8/8. - The shortlist evidence section, a decisions entry in the session's drafts
file (docs/decisions_drafts/, folded intoDECISIONS.mdlater), and
the master list's counts in all four places — the summary box, the band
table, the band heading and the country table.
Step 9 — When a built city's text changes, it changes into the current format
A re-probe of a built city (Stockholm, Göteborg, Zurich and Bucharest are) ends,
if the owner takes the new layer, in a changed page and changed reference
sections. Write each in the format of docs/city_page_format.md, not the
format of the text being replaced:
- The page (section 1): title, the map directly after it, captions under
the map, then bullets under bold headings. The new layer is a bullet under
The businesses; its measured defect, if the owner chose to disclose it,
may need a third heading, as Amsterdam's Reading the map does.render_map_help(...)names the page's layers, so a second layer changes
its argument (Amsterdam's:'two business categories (Shops and services, and Food service)'). Stations left out are "listed below"; no repository
path, script, check or skill name in page text. - What Is Excluded (section 2): the city's
### <City> - ...heading
still names the city, and its subtitle changes when the sources do (Zurich's
names its two layers: "the city's food-and-drink licenses, and its shops
licensed to sell alcohol"). The new layer's gaps and limits go in that
section, never in the shared "What is missing rather than excluded" or
"Honest limits". - About the Data (sections 2 and 3): the new source's rows and notice in
docs/data_sources/<country>.mdunder the city's name; how it was found
stays in the brief and the drafts file, or between<!-- internal -->
markers. - American spelling in all three (section 4), except notices, licence
titles, quotes and the register's own names. - Approval (section 6): a sentence from a template needs no read-back;
a new one is a proposal, flagged in the drafts file and at review time.