DoorDash slug titles — what was built (2026-10-14)
Status: built, applied and scheduled. This started as a proposal; the first design got the city wrong, so this records what shipped and why.
The problem
785,193 of the master's ~936k outlets (84%) are single-record DoorDash stores. DoorDash publishes no store name, only a URL slug — lowercase, the store's city appended: busa wine and spirits woburn. They were titled exactly that.
What the first design got wrong
It assumed the sitemap's recorded city was the store's city, because it matched the slug's tail. It is only the slug's last word (creek, never Battle Creek; 0 of 10,322 multi-word cities were whole). Stripping by it left Burger King Battle, Pizza Hut Lake Havasu, 7 Eleven New. The error was caught by the dry run on live data, before anything was applied.
How it works now (slug_title.py, master_outlet.py)
Every slug ends with its city, so the last word is always part of it; the unknown is the city's length. The full city comes from, in order:
- the store's own address page (
doordash_addr.city, ~78k stores; equals the slug's tail 99.9%); - the longest tail of the slug that is a known city in that state — a ~55k-name gazetteer (
city_centroids,doordash_addr,ubereats_stores,ca_outlets,hemp_retailers,retail_outlets) plus cities discovered from the slugs themselves (below); a-bucket holds stores with no state (Australia, NZ, UK); - otherwise refuse and keep the raw slug. A wrong strip is worse than a slug.
Refusals: city-unknown, city-prefix-left (a name left ending in a city-starting word: "Ace Hardware South"), dangling-word ("Coffee Shop in Woburn": the name may end with the city), city-not-at-tail.
City discovery (discover_cities, table slug_city_learned)
A chain settles a city's length: "subway auckland" and "mcdonald's auckland" both leave "auckland" once a brand name is removed. A brand is a cleaned name seen in >= 25 cities. A city is learned only when >= 2 distinct chains agree, it is not already known, it is not a lone generic tail (valley, park), and a multi-word one is not led by a business word (liquor calgary). Hold-out test (30% of known cities hidden, rediscovered, scored on 22,098 addressed stores): 95.3% right, 0.11% wrong, 4.6% refused. Three plausible guards measured worse and were dropped (a name-frequency head guard, chain-direct at any support, a place-word-head requirement) — recorded in CLAUDE.md.
Casing v2 (CasingDict)
Title Case loses brand casing. The master already holds real spellings, so a slug whose name matches one takes it: an order-sensitive whole-name key (Food Giant is not Giant Food), else acronym/brand casing token by token. ALL-CAPS licence names and sloppy spellings are never borrowed. Leave-one-out exact casing 86.0% -> 98.9%.
What was measured, live
| Leak-free city strip, 60,000 addressed stores (true city held out) | 97.11% exact, 0.04% different, 2.85% refused |
| After discovery + casing | 769,187 of 785,193 cleaned (98.0%); 16,006 keep the raw slug |
| Casing v2 live dry run | 47,222 titles changed, 0 changed a real word |
Honesty rules
- The record is never rewritten.
master_outlet_member.namestays the slug; onlymaster_outlet.display_namechanges. - Derived titles are marked.
master_outlet.display_name_basis=record/slug-cleaned/slug-raw; the Outlets list shows a "derived" marker and the detail page explains it. - Re-derived every run, written only if it differs — a better rule repairs what an older one wrote, and a second run is a no-op.
updated_atis untouched, so "recently changed" is not a mass edit. - Nothing that matches or links reads the cleaned title.
Operation
python -m hoodie_bev.master_outlet --clean-slugs [--apply]— dry run first: counts, 30 before/after pairs, suspects, refusals.--discover-cities [--apply]— dry run first.- Scheduled weekly as registry source
slug-cities(default lane, ~2 min): discover, then re-derive every title. A discovery that would keep < 50% of the learned cities is a failed read and is refused (table kept, rundegraded). - The daily
master-outletbuild re-cleans the outlets it touches.
Known limits (pinned in tests)
& read as and; a few hyphens and acronyms with no real spelling on file; non-US cities that no chain confirms stay raw (9,242 city-unknown); two learned cities carry an extra word ("catering irvine") because the real city is already known and a stricter rule measured worse on the hold-out.