Capstone dossier · infrastructure, not discovery, and honest about it · doubles as the formal M2 close
Author
Dei Martinez Elurbe
Published
July 2, 2026
Dossier, not the essay. Backstage layer for technical readers; the capstone essay is the general-audience artifact. Corpus: Snapshot · June 2026, ~900 priced venues, roughly 4 in 10 of Amsterdam’s ~2,028 restaurants. No venue is named.
The verdict
Amsterdam’s restaurants cluster into 14 empirical types built from nothing but their menus, and the most important fact about that typology is one we measured against ourselves: 82% of it is tautological. Label each venue by its dominant concept family and you reproduce most of the clustering. That makes the typology valid infrastructure (the apples-to-apples strata the price analyses needed) rather than a discovery, and it is presented as exactly that.
The genuine discovery lives in the half the clustering could not do alone: the café ecosystem, ~a third of the corpus, which refuses to organise by food family and organises instead along an occasion axis (morning coffee → lunch → borrel → evening dining), validated on four independent signals. Its deliverable is frozen: seven named café subtypes (brunchcafé, borrelcafé, koffie-lunchroom, eetcafé-grill, brasserie, broodjeszaak, allrounder) over a 311-venue population.
Because confidence varies enormously across venues, every public claim is gated by a per-venue confidence artifact: 345 anchored / 334 leaning / 92 weak / 144 no-concept. The specialist poles are rock-solid; the gradient middle is honestly soft.
This dossier also serves as the formal M2 close (Stage-1 accepted 2026-06-08, café Stage-2 frozen 2026-06-12). The one originally-descoped acceptance criterion, the saturation/whitespace market-gap analysis, was revived and run for this dossier (2026-07-02, at the leader’s request): the saturation map is delivered below, and the whitespace shortlist came back a disciplined negative: every candidate gap is refuted by uncaptured venues in the full OSM universe, which at 4-in-10 coverage is itself the honest deliverable. Ratifying the close-out table closes M2.
Why the obvious approaches lie
Cuisine tags lie by sparsity. The available label (OpenStreetMap’s cuisine tag) is missing or generic for a large share of venues, and “italian” flattens a trattoria, a pizza-slice counter, and a €70 ristorante into one word. Menus are what venues do; the typology is built from them.
Clustering lies by flattery. An unsupervised clustering will always produce clusters, and it is easy to mistake “the algorithm grouped them” for “we discovered structure”. Two self-checks guarded against that. The tautology check: how much of the clustering does a one-line rule (label by dominant concept) reproduce? Answer: 82%, disclosed everywhere the typology is used. And the silhouette trap: the usual cluster-quality score kept rising with sparser, finer vocabularies (a sparsity artifact, not real compactness), so it was rejected as the judge. What replaced it: dendrogram merge-height gaps to defend the cut (k=14 sits at a real gap), and within-cluster price coherence as the fitness-for-purpose test, because the typology’s actual job was price strata.
Stage 1: fourteen types, tiered confidence
The specialist poles are what a restaurateur would name unprompted: pizzeria, sushi, ramen, Indian curry-house, Indonesian, steakhouse, raw-bar/seafood, trattoria/pasta, and so on. The gestalt vocabulary rebuilt (D1) is what surfaced the last two: steakhouses and deluxe seafood had been invisible because their defining dishes lived in muted generic buckets.
Stability is unevenly distributed, and the honest response was to measure it and gate on it, not to hide it. Bootstrap resampling of menus flips cluster co-membership for most of the gradient middle (brasseries, eclectic cafés) while the specialist poles hold at 65 to 82%. Attempts to fix this in the geometry were tested and rejected on evidence: evidence-count and share gates lost price coherence without stabilising membership; including zero-concept venues degenerated the solution into a mega-blob. The fix that shipped is a claims layer: a per-venue confidence artifact (evidence counts, bootstrap stability, tier) that public surfaces must respect. Weak-tier venues simply do not get type claims.
Figure 1: The confidence gate: per-venue concept-confidence tiers over the 915-venue population. Public type claims are gated on this artifact; the typology’s softness is priced in, not hidden.
One structural note that protects the rest of the project: the tourist tax does not stand on this clustering. M3’s price strata are each venue’s dominant concept family (a capture-robust assignment), not k=14 membership, and the tax is stress-tested robust to thin-evidence mislabels. The typology’s fragility is disclosed here without endangering D5.
Stage 2: the café ecosystem, where the real finding lives
About a third of the corpus is the café mass: broad menus, no dominant food family, exactly the venues a food-family typology cannot see. The finding is that they organise on a different axis entirely: occasion. Not what they serve but when and how you use them.
The validation is the strongest in the project, because the axis was confirmed on four independent signals: menu composition; the coffee-versus-alcohol mix in the drinks data; scraped opening hours; and each venue’s own website self-description, adjudicated against a pre-committed rubric with an evidence quote recorded per verdict. Four data sources, four different failure modes, one axis.
Figure 2: The frozen café subtypes (311-venue population; 309 included, 2 excluded, 4 without a subtype label). Counts computed from the frozen artifact, which reflects the final boundary audit.
Figure 3: The occasion map: the café population arranged on observable axes (daypart signals), the independent validation surface for the subtypes.
Two methodological beats from Stage 2 deserve the record:
The allrounder is a label, not a cluster. The intuition said the generic open-late-serve-everything café is a native Amsterdam category. A joint menu × opening-hours clustering experiment was run with pre-committed judges to find it, and failed structurally: being a catch-all means sitting at the centre of the space, and a centroid-based method cannot carve out the middle. The category is real; it is assigned by rule (generic menu × open late), not discovered by geometry. Running the experiment and logging the failure is the difference between a rule-class and a rationalisation.
The final labels came from a human sitting, and the record shows why. All contested and weak assignments were walked venue-by-venue with the project leader; on the contested set, the model’s second-choice instrument beat the raw clustering (7 versus 2 of 13), and neither caught third options or the catch-all class. The frozen artifact records the basis per venue: 254 bootstrap-settled, 46 human-adjudicated, 7 by documented decree.
A capture lesson ships inside the artifact. One venue’s three-item captured menu fragment anchored a confidently wrong subtype; the fix is a menu-size gate (n_food_items) carried in the artifact so downstream consumers can filter, because bootstrap confidence cannot detect capture incompleteness.
Saturation and whitespace, revived
The market-gap analysis was descoped on 2026-05-27 (when the typology lost its headline role, these analyses lost their deliverable) and revived on 2026-07-02 for this close. It runs under the audit’s conditions: anchored-tier venues only (344 with geography), archetypes = the ten concept families with at least 12 anchored venues (the capture-robust unit, the same one the tourist tax trusts), stadsdeel level. The denominator is anchored venues per stadsdeel, so differences in our capture density across areas cancel out of the lift.
Figure 4: Saturation: representation lift per family × stadsdeel (observed ÷ expected share), anchored venues only. Numbers are observed venue counts; cells with expected < 2 are greyed (too thin to read). Noord, Zuidoost and Weesp have ≤ 11 anchored venues each and are indicative at best.
The saturation readings that survive the thin-cell filter are believable and useful. Steakhouses concentrate in Centrum at 1.67× their city share, which is direct, independent evidence for the composition channel the tourist-tax dossier (D5) reasons about: the centre does not just mark prices up, it attracts pricier concepts. Indian concentrates in West at 1.66× (the Oud-West/De Baarsjes corridor), pizza tilts to Zuid and West, and the kebab/Levantine families run structurally thin in Centrum and Zuid relative to their city share.
Whitespace came back empty, and that is the finding. Three candidate gaps cleared the shortfall bar (kebab in Zuid, expected 6.5 observed 1; Indian in Oost, expected 4.2 observed 1; burger in West, expected 3 observed 0). The design then required each candidate to survive a cross-check against the full 2,040-venue OSM universe, captured or not, before being claimed: one uncaptured counterexample kills a public gap claim. None survived: the universe holds 2 to 4 cuisine-tagged venues in every candidate cell.
Show the code
readr::read_csv("_data/whitespace.csv", show_col_types =FALSE) |>transmute(family, stadsdeel, observed, expected, shortfall,`OSM venues in the gap`= osm_candidates) |> knitr::kable()
Table 1: The whitespace shortlist and its refutation: every candidate gap in the anchored corpus is filled by venues our corpus did not capture (or did not anchor). At 4-in-10 coverage, apparent whitespace measures our sampling, not the market.
family
stadsdeel
observed
expected
shortfall
OSM venues in the gap
kebab_tr
Zuid
1
6.5
5.5
4
indian
Oost
1
4.2
3.2
3
burger
West
0
3.0
3.0
2
The honest deliverable is therefore a disciplined negative: on this snapshot, no market-gap claim is defensible, and the two-step design (find the gap, then try to kill it with the universe) is what kept a capture artifact from becoming a headline. Had the cross-check not existed, “no burger joint in West” would have shipped, and it would have been false.
✅ run 2026-07-02; result = disciplined negative (all candidates refuted by the full-universe OSM cross-check; no gap claim survives at 4-in-10 coverage)
(added) Café Stage-2 subtypes
✅ frozen 2026-06-12, m2_cafe_subtypes.csv
(added) Per-venue confidence gating
✅ m2_venue_confidence.csv, Decision #37
The close, stated plainly. Every original acceptance item is now either met or delivered with its honest outcome recorded; nothing is silently dropped. Ratifying this table closes M2.
The stress-test battery
The threat
The test
The verdict
The clustering “discovers” its own labels
tautology check: dominant-concept rule vs cluster membership
82% reproduced: reframed as infrastructure, disclosed everywhere
Silhouette flatters sparse vectors
silhouette vs vocabulary sparsity scan
monotonic (artifact): rejected as judge, replaced by dendrogram gaps + price coherence
Fewer clusters would serve better
k=6 vs k=14 as price strata
η² 0.118 vs 0.245: k=14 wins, the coarse-strata claim retired
Membership is fragile
bootstrap menu resampling
specialist poles hold (65–82%); gradient mass flips: gated at the claims layer, geometry fixes tested and rejected
Chains pseudo-replicate structure
full dedup and weighted-Ward variants
both degrade the solution; chains kept (they seed structure four independent signals validate), dedup disclosed as sensitivity
The protein axis deserves a block
its own pre-committed residual test
rejected (price coherence drops); protein lives in the cell key (D1), not the venue distance
The allrounder is wishful
joint menu × hours clustering, pre-committed judges
Infrastructure with one discovery attached. The k=14 map is a defensible organisation of the corpus and the price strata’s ancestor; the occasion axis is the finding worth telling.
Centers of gravity, not borders. Concept boundaries are fuzzy in a real city; venues pad menus defensively. This is a standing design assumption of the whole typology, not a surprise to be explained away.
A snapshot of menus, not of businesses. Membership is capture-sensitive: a venue whose dinner card was missed reads more lunchroom than it is. The confidence tiers and the menu-size gate exist precisely because of this.
Dutch labels are deliberate. The café subtypes are named in the local register (borrelcafé, broodjeszaak) because the categories are local; translation would blur them.
Lineage
Method + verdicts:docs/m2_gestalt.md (architecture + the four diagnoses), docs/m2_gestalt_ledger.md (family verdicts), docs/m2_cafe_split.md (Stage-2 work order + close-out §0-quater), PROJECT.md decisions #33, #37, #38.
Code:R/13–R/14 (concept column + clustering), R/32 (confidence artifact), R/33–R/45 (café Stage-2: self-description bank, representation, bootstrap, hours, subtypes, occasion map), _prep/saturation_whitespace.R (the revived market-gap analysis, this dossier).