Vault
plan

Seattle Events Radar — Design for a Weekly What's-On System

Created

Seattle Events Radar — Design for a Weekly What's-On System

Related: seattle-cafe-and-night-guide, adhd-family-operating-manual Builds-on: the places guide (places/, scripts/places.sh, the places skill) Informs: Projects/tech-blog (optional later integration, same pattern as /guide)

The Gap the Places Guide Doesn't Cover

The places guide answers "where should we go?" It works because places are durable: Canon's frontmatter from April is still true in August. Events are the opposite — they expire, and the interesting ones (ZooTunes on-sale day, a Movies at the Mural night, a one-off kids' festival) punish you for finding out late. So this can't be a corpus of markdown files that slowly grows. It has to be a pipeline that runs weekly and emits a digest, with only a thin layer of durable state (the seasonal series that recur every year).

Same architecture split as places, though: deterministic scripts for facts, Claude for curation, markdown as the artifact. That split is what made places maintainable, and it transfers cleanly.

Concrete misses this design would have caught: Movies at the Mural ran Princess Bride July 31 and Hamilton August 7 (free, Seattle Center); ZooTunes' remaining August run includes The Beths/Beach Bunny (Aug 16), Courtney Barnett with Built to Spill (Aug 18), and The Breeders (Aug 20) — most at the family-friendly 6pm slot, kids 2 and under free. And the deeper lesson from ZooTunes: tickets went on sale February 27 for a June–August season. A weekly radar has to track on-sale dates, not just event dates, or the good shows are gone before the digest ever mentions them.

Source Audit

Three tiers, by how they're consumed. The tier determines where they live in the pipeline.

Tier 1 — Scriptable (APIs and feeds, the events.sh layer)

Source Access Verdict
Ticketmaster Discovery API Free key, 5,000 calls/day, geo + date-range + genre search The backbone for ticketed concerts/shows. One call covers "everything within N miles of Shoreline in the next 14 days."
TMDB API Free key, now_playing + upcoming endpoints with region filter Movies coming up. No showtimes (those APIs are all paid/licensed), but "what's releasing and what's in theaters" is the actual question.
Trumba feeds Append .ics/.rss/.json to any Trumba calendar URL Seattle's civic layer runs on Trumba — Seattle Center's event calendar exports ICS directly. Free, structured, no scraping.
KCLS BiblioCommons kcls.bibliocommons.com/v2/events?locations=1535 (Shoreline branch) Storytimes and kids' programs at the neighborhood library. Structured pages, filterable by branch.
Venue ICS feeds Varies per venue A registry file (events/feeds.yaml) accumulates these over time, same way reserves.md accumulates places.

Tier 2 — Curation layers (Claude-side, no API)

Source Access Verdict
EverOut (The Stranger) Blocks non-browser fetches — verified a 403 on plain fetch today. WebSearch snippets work; their weekly newsletters work better. The best human curation in the city. Route around the bot-block via newsletter-as-feed: subscribe to Stranger Suggests / EverOut weekly emails, read them at digest time via Gmail. Zero scraping, fully in-terms.
ParentMap Open calendar + monthly "Best Family Activities" roundups, fetchable The kids-4-and-2 filter already applied by someone else. Also has a weekend newsletter.
Seattle's Child Open, fetchable Seasonal family guides (outdoor movies, festivals).
Seattle Times / Seattle Refined Open, fetchable Seasonal "guide to summer" style anchors; monthly PNW festival roundups.

Tier 3 — Seasonal anchors (durable state, refreshed ~quarterly)

These recur annually and belong in a standing file, not the weekly pull: ZooTunes (Jun–Aug, on-sale late Feb), Movies at the Mural (Jul–Aug, free), Seattle Parks outdoor movies (22 nights across 7 parks), Bellevue Movies in the Park (Jul–Aug Tuesdays/Thursdays), Celebrate Shoreline (mid-August, Cromwell Park), SIFF (May), Chateau Ste. Michelle summer concerts, farmers markets. Each entry carries when to check for the next season's announcement — that's the field that makes February's on-sale date show up in a February digest.

Dead ends (checked so we don't re-litigate)

Architecture

Mirror of places, with the digest replacing the per-place file as the artifact:

flowchart TB
    A[events.sh pull<br/>Ticketmaster + TMDB + ICS feeds] --> C[normalized JSON cache<br/>events/.cache/]
    B[Curation pass<br/>EverOut newsletter via Gmail,<br/>ParentMap, WebSearch] --> D
    C --> D[Claude: filter + rank<br/>distance, kid-fit, taste]
    E[events/seasonal.md<br/>anchor series + on-sale dates] --> D
    D --> F[Weekly digest<br/>events/weekly/YYYY-MM-DD.md]
    F -.optional later.-> G[Blog: events.json<br/>same pattern as places.json]

The pieces:

Digest shape (sections, in priority order):

  1. This weekend — the actual decision the digest serves. Each entry: what, when, where, drive time from home, kid-fit (both kids / Niko-only / date-night / solo), price, link.
  2. Next two weeks — same format, lighter.
  3. On the radar — on-sale dates and just-announced series. This is the section that earns the system its keep.
  4. Movies — in theaters now + releasing soon, flagged for kid-viability.

The family filter is the point. Generic event lists exist everywhere; the digest is worth running because it applies the constraints no aggregator knows: Shoreline drive radius, a 4-year-old and a 2-year-old (6pm shows good, late shows are date-night-with-sitter territory), Costco-runs-on-weekends rhythm, and taste (neo-soul/electronic bias for grown-up shows — the Ticketmaster genre filter can carry a taste profile).

Cadence and Trigger

Run Wednesday or Thursday — late enough to catch weekend announcements, early enough to actually plan (and book a sitter if it's a date-night pick).

Phase it: start as a manual /events skill invocation, weekly. Once the source mix stabilizes (2–3 runs to learn which sources actually produce the picks), promote it to a scheduled routine. One real constraint to know upfront: a scheduled cloud run can't use this machine's Chrome or local gws Gmail auth, so the newsletter-reading step ties the workflow to a local run unless it's re-plumbed. The Ticketmaster/TMDB/ICS layer is headless-safe either way. Manual-first is not a compromise — it's how places worked too, and the skill makes each run a one-liner.

Build Plan

Human blockers (yours, ~10 min each, one-time):

  1. Ticketmaster developer key (developer.ticketmaster.com)
  2. TMDB API key (themoviedb.org)
  3. Newsletter signups: EverOut weekly + ParentMap weekend, to the household Gmail

Claude work (one session, well under an hour):

  1. scripts/events.sh with pull + normalization
  2. events/feeds.yaml seeded with Seattle Center Trumba ICS + KCLS Shoreline
  3. events/seasonal.md seeded from the Tier 3 list above
  4. The events skill file, modeled on places/SKILL.md
  5. First digest run as the shakedown

Open Questions

Revision — CI-First Architecture (2026-08-07, same day)

Discussion immediately after the first draft pivoted the architecture. Decisions, in order:

  1. CI-only, scheduled. Everything runs in GitHub Actions on cron; anything that can't run headless is cut from phase 1. The local /events skill and the private family digest move to "later, maybe."
  2. No separate events workflow. The cron trigger goes on the blog's existing deploy.yml; events are fetched at build time and events.json is never committed. If the build fails, Pages keeps serving the last deploy — graceful staleness for free.
  3. Claude runs in the pipe after all — as a fail-soft curation stage via claude -p, authenticated with the Max subscription (not API billing).

What the mechanical layer keeps without any curation: Ticketmaster's Family classification segment (kid-friendly as a filter, not a judgment), sales.public.startDateTime (the on-sale radar is structured data), TMDB certifications (G/PG flags). What only Claude adds: editorial picks and blurbs, plus EverOut/ParentMap coverage via WebSearch (EverOut 403s direct fetches, and datacenter IPs fare worse — search snippets route around it).

Auth (verified against docs, Aug 2026)

Pipeline spec (deploy.yml)

Triggers: push to main, workflow_dispatch, schedule: cron "0 14 * * 4" (Thu ~7am PT; GH cron is UTC and can slip minutes — irrelevant here).

1. node scripts/build-events.cjs        # deterministic pull → src/data/events.json
2. claude -p (curation)                 # fail-soft → src/data/events-curated.json
3. node scripts/validate-curated.cjs    # zod gate; on failure, delete curated file
4. astro build → deploy Pages           # page works with or without curated layer

Stage 1 — build-events.cjs. Ticketmaster Discovery (geo radius around Seattle, next ~21 days, keep classification + onsale fields), TMDB now_playing + upcoming (region US, certifications), every feed in the committed ICS registry. Fail-soft per source — one feed being down must never block a blog-post deploy. Dedupe, sort, strip nothing-burger fields. Secrets: TICKETMASTER_API_KEY, TMDB_API_KEY.

Stage 2 — Claude curation. npx @anthropic-ai/claude-code -p with:

Stage 3 — validation gate. Zod schema at the boundary (the LLM step is untrusted input to the build). Invalid → delete the file, log loudly, build proceeds. The Astro page renders the curated layer only when the file exists and filters already-past events client-side, so stale data never shows expired listings.

Secrets total: TICKETMASTER_API_KEY, TMDB_API_KEY, CLAUDE_CODE_OAUTH_TOKEN — all free-tier or already-paid. Marginal cost of the whole system: $0/month.

Superseded from the original design

Newsletter-as-feed via Gmail, the Chrome fallback, the local weekly skill, and the events/weekly/ digest directory are all out of phase 1. events/seasonal.md survives but moves into the blog repo as a committed file the build merges in (free outdoor movie series and civic one-offs aren't in Ticketmaster). The private curated digest remains possible later as a thin local consumer of the same build-events.cjs output — nothing in the CI design blocks it.

Sources