Phase A · Acquire
01 Map the poll's URL surface scrape
Know exactly what to fetch before fetching anything.
Best of Austin publishes at austinchronicle.com/best-of-austin/year:YYYY/poll:PPP/category:CCC/. Enumerate every valid (year, poll, category) tuple for years 2024 and 2025, polls "readers" and "critics", across all 8 sections (Food & Drink, Services, Shopping, Nightlife, Arts & Entertainment, Sports & Recreation, Kids & Family, Politics & Media). Output a checklist of the exact index URLs to crawl. Do NOT fetch yet — just produce the target list and confirm the URL pattern against two live pages.
02 Fetch winners politely scrape
Pull the raw winner lists without hammering the Chronicle.
Fetch each index URL from prompt 01 with a 3-second delay between requests, an honest
User-Agent ("WholeATX-research/1.0 (+https://wholeatx.com; walhus@gmail.com)"), and on-disk
caching so a re-run never re-fetches. Save raw HTML to /opt/wholeatx/bestof/raw/. Stop and
report if you see 403/429 — do not retry in a loop.03 Parse to structured winners parse
Turn HTML into clean records.
Parse the cached Best of Austin pages into /opt/wholeatx/bestof/winners.json — one record per
award: {name, category (the exact Chronicle category label), section, year, poll, place
(1st/finalist), url (the specific poll page), blurb_excerpt (<=160 chars, for attribution
context only)}. Preserve the Chronicle's exact business name string verbatim in raw_name.04 Capture the runners-up too parse
Finalists are leads too.
Extend the parser to also capture finalists / runners-up where listed, tagged place:"finalist". A finalist with a broken website is nearly as good an outreach lead as a winner — we want them.
Phase B · Normalize
05 Canonicalize business names normalize
Names must match across two very different datasets.
Add a canonical_name to every winner: lowercase, strip legal suffixes (LLC, Inc, Co), collapse
"&"/"and", drop location qualifiers ("- South Lamar"), normalize whitespace and punctuation.
Keep raw_name untouched. This canonical form is the join key against the roster.06 Detect chains & multi-location winners normalize
One award, many storefronts.
Some winners are chains with many roster rows (e.g. a coffee brand with 12 Austin locations). Flag winner records as multi_location and decide the rule: the badge attaches to the BRAND (dedupe by canonical_name) not to each storefront. Document the rule in the file.
Phase C · Match to real businesses
07 Exact-match pass match
Get the easy, high-confidence joins first.
Load the roster via roster.records(osm-travis.json). For every winner, attempt an exact canonical_name match against roster canonical names. Write matches to /opt/wholeatx/bestof/matched.json with match_confidence:"exact" and the roster osm_id + website. Report the exact-match rate per category.
08 Fuzzy-match pass with a human gate match
Catch the near-misses without inventing bad joins.
For unmatched winners, run token-set fuzzy matching (rapidfuzz) against the roster, restricted to the same sector where possible. Auto-accept >=92 similarity; queue 80–92 into /opt/wholeatx/bestof/review.csv for a human yes/no; discard <80. Never guess a match into production — a wrong badge on the wrong business is worse than no badge.
09 Reconcile the misses match
A winner with no roster row is itself a signal.
List winners that matched NO roster business. Split them: (a) genuinely missing from our roster — queue for a targeted add; (b) present but under a different name — fix the alias. Producing this "beloved businesses WholeATX doesn't even know about yet" list is a deliverable.
Phase D · Map to sectors
10 Category → sector crosswalk map
Send each award to the right subdomain.
Build /opt/wholeatx/bestof/crosswalk.json mapping every distinct Chronicle category string to
one or more of the 85 WholeATX sector slugs (see the brainstorm's starter table). Where a
category is ambiguous ("Best Local Business"), map to a network-wide bucket, not a wrong sector.
Flag any category that maps to zero sectors for review.11 Attach awards to sector records map
The join, finalized.
Produce /opt/wholeatx/bestof/by_sector.json: for each sector slug, the list of its matched
winners with {business, category, year, poll, place, website_score (from audit-austin.json),
chronicle_url}. This single file is what the generator will read.Phase E · Enrich & find the stories
12 The “Beloved but Broken” list enrich
The single most valuable output of the whole project.
Join every matched winner to its website score. Emit /opt/wholeatx/bestof/beloved_but_broken.csv
sorted by lowest score: {business, award, year, score, top 3 failed checks, phone, sector}.
This is the outreach call sheet — an award-winner losing customers to a bad website is the
warmest lead we will ever have.13 Dynasties, risers, and fallers enrich
Two years turns a list into a narrative.
Compare 2024 vs 2025 winners per category. Tag each business: "hall_of_fame" (won both years), "rising" (2025 only), "faded" (2024 not 2025). Add these tags to by_sector.json. They drive badges and a genuinely interesting trends page.
14 Award-winner with no website at all enrich
The most quotable cross-section.
Intersect the winner list with roster tier-1 (no website found). Any Austin favorite the crawler couldn't find a website for is a headline. Output the list; it feeds both a press angle and the highest-priority "get online" outreach.
15 Write the pairing blurbs enrich
Warm + useful, in our own words.
For each matched winner, draft a 2-sentence WholeATX blurb: sentence 1 acknowledges the award (our words, attributing the Chronicle, never copying their prose); sentence 2 gives the honest one-line website read and the single highest-impact fix. Neutral, non-snarky, genuinely helpful.
Phase F · Publish across the subdomains
16 Award badge in the generator publish
Make it appear everywhere automatically.
In /opt/wholeatx/wholeatx.py, load by_sector.json and render a gold "🏆 Best of Austin YEAR — CATEGORY" badge next to any matched business in the sector worksheet and at the top of its /biz/ detail page, linking to the specific Chronicle poll URL. Idempotent; safe on re-run.
17 Award JSON-LD publish
Let AI answer engines cite us.
On each winner's per-business page, add schema.org "award" to its LocalBusiness node
("Best [Category], Austin Chronicle Best of Austin YEAR"). This makes WholeATX the structured,
machine-readable source for "who won Best X in Austin" — exactly what LLM answer engines quote.18 Sort winners to the top publish
Reward the businesses people came for.
Add an optional "Best of Austin" view/filter on each sector page that pins matched winners to the top of the worksheet, badge visible, score visible. Default sort stays by score; this is a toggle.
19 The network Best-of hub publish
One page to rule the award terms.
Generate wholeatx.com/bestof/index.html: every matched winner, both years, filterable by category and sector, each with live website score and Chronicle link. Plus wholeatx.com/bestof/ beloved-but-broken/ as a public teaser of the gap (names shown, full report gated). Rich ItemList + Award JSON-LD.
20 Per-category landing pages publish
Own "best [thing] in austin" search.
For high-value categories (Best Coffee, Best Tacos, Best Plumber, Best Realtor...), generate a focused page pairing the winner + finalists with their website scores and a short guide. These target the exact high-volume searches and are prime AI-citation bait.
Phase G · Market it
21 Outreach copy from the badge market
Turn the flattery into a conversation.
For each beloved-but-broken business, draft a short, warm outreach email: congratulate them on the Best of Austin win (specific category + year), note one concrete thing their website is missing, offer the free full report. No pressure, no pricing. Save as drafts per your rules (never auto-send).
22 The press one-pager market
A story journalists will actually run.
From the joined data, write a factual findings one-pager: "N of Austin's Best-of winners have websites that score under 45," "M award winners have no website at all," the most surprising gaps. Numbers only, attributed, with methodology. This is link-worthy and AI-quotable.
23 The partnership pitch to the Chronicle market
Do the biggest version of this the right way.
Draft a partnership proposal to the Austin Chronicle: they own the love and the brand; WholeATX brings the website-readiness layer they don't have. Offer co-branded "Best of Austin — is your site ready?" reports, proper licensing of the marks, and traffic back to them. Frame it as additive to their franchise, not competitive.
Phase H · Keep it honest
24 Attribution & freshness guardrail qa
Never drift from correct and fair.
Add a build-time check: every rendered badge and winner card must carry "Austin Chronicle Best of Austin YEAR" attribution and a link to the source poll page; no Chronicle editorial prose may appear verbatim beyond a short attributed quote. On each new annual poll, re-run 01–19 and mark prior-year badges as "20XX" (historical), never silently dropping them. Fail the build if any winner card is missing attribution.