Inspiration

This hackathon asks for the use case where cultural intelligence is the difference between "technically functional" and "actually good". Picking an opener is that case. A talent buyer at an independent room books support acts every week, mostly from memory, agent pitches, and national streaming numbers, and none of those know this city or this room. Ask a general model and it names the headliner's famous peers, who are great and far too big for a 350-cap club. The question a buyer actually has is specific: this audience, this city, this capacity. That is the shape of a Qloo query.

What it does

Give Undercard a headliner, a city, and your room's capacity.

  • The bill, ranked. Five openers this headliner's audience over-indexes on in that city, with the ones that fit your room first. Each pick shows local affinity, shared taste with the headliner, Qloo popularity, a size band (right-size, stretch, too big, too small), and a one-sentence rationale that may only cite what Qloo returned. The next ten acts considered are listed underneath.
  • Sized for the room. Rooms under 3,000 also ask Qloo for acts under the room's popularity ceiling, so a 350-cap club is not handed acts that sell out theaters.
  • Gut vs Graph. The same question asked of the model alone, with no Qloo data, next to the Qloo bill, with a count of how many model picks are on the brief at all.
  • Ask Undercard. Edit the bill in plain English: "swap opener 2", "go smaller and more local", "what if Omaha, NE?", "try a 1,200-cap room". The model only picks the action; every change runs against Qloo again.
  • Which one? When a name has no exact match (a typo or a partial name), Undercard shows Qloo's closest artists with descriptions and asks before it builds. Try "Bon Jov".
  • Supporting notes. Where this taste lives (Qloo heatmap), audience age and gender skew, pre-show spots, sponsors, podcasts, and TV for ad buys. Deliberately secondary to the booking decision.
  • Share and agents. Copy a link, save a print-ready PDF for the agent or the venue owner, or call the same tools from any MCP client.

What a judge can check in a minute

  1. Open https://undercard-phi.vercel.app and click "Wednesday in Asheville, NC" (350-cap club). It opens instantly.
  2. The bill: five openers sized for the room, each with its Qloo numbers.
  3. Toggle Gut vs Graph to "LLM alone": the model names Mitski, Big Thief, Snail Mail, Japanese Breakfast, and Alvvays. 0 of those 5 are among the 15 acts Qloo ranked for this audience, city, and room.
  4. Expand "How this brief was built": all 12 steps, each Qloo endpoint, timing, and the locality Qloo matched ("Asheville, Buncombe County, North Carolina").
  5. Open "Khruangbin in Nashville, TN" (2,500-cap theater): a different room gets a different bill.
  6. Agents: point an MCP client at https://undercard-phi.vercel.app/api/mcp and call list_featured_briefs, then build_show_brief.

Technological Implementation (how Undercard uses Qloo)

Without Qloo, Undercard has nothing to rank. Every list, score, and map point comes from the taste graph; the model only writes sentences about entities Qloo returned and picks which edit to run. Every call uses only documented parameters, the X-Api-Key header, and the hackathon base URL.

Step Qloo call Why it matters to a buyer
Pin the headliner; ask "Which one?" if there is no exact match GET /search with query, types=urn:entity:artist, take The wrong artist ruins everything after it
Who this audience loves in this city GET /v2/insights, filter.type=urn:entity:artist, signal.interests.entities, signal.location.query Local taste, not national charts
Acts that fit the room the same call plus filter.popularity.max at the room's ceiling A 350-cap room needs acts that are not yet huge
Shared taste with each opener GET /v2/analysis/compare, a.signal.interests.entities, b.signal.interests.entities Which taste tags the two audiences share, and how strongly
Audience shape GET /v2/insights, filter.type=urn:demographics Who you are marketing to
Where this taste lives GET /v2/insights, filter.type=urn:heatmap, filter.location.query, filter.location.radius Routing context
Pre-show partners GET /v2/insights, filter.type=urn:entity:place, filter.location.query Bars and shops this crowd over-indexes on
Sponsors and ad buys GET /v2/insights with brand, podcast, and tv_show types and signal.location.query Cross-domain taste of this audience

Engineering:

  • Server-side agent pipeline (TypeScript, Next.js 16): resolves the artist, runs the independent Qloo calls in parallel, ranks by room fit then local affinity, scores shared taste, and streams each step to the browser as it runs. A fresh brief in production on Oct 19 ran 10 Qloo steps (14 calls, the slowest step 1.1 seconds), then 2 model calls.
  • pnpm live-check: calls every endpoint above once against the live server and checks the exact fields the client reads, and that the location and popularity filters actually change the answer (Qloo ignores unknown parameters silently, so "no error" proves nothing). On Oct 19: 18 of 18 checks passed. It prints counts and field names only, never Qloo values.
  • Grounding: the writing prompt may only mention entities in the Qloo evidence; output is schema-validated; if the model fails or the free quota runs out, templated copy keeps the brief working.
  • Follow-up agent: the model chooses one action (swap, smaller, bigger, change city, change capacity, answer); the action itself runs against Qloo.
  • MCP server (official MCP TypeScript SDK, Streamable HTTP) with build_show_brief, get_show_brief, ask_undercard, list_featured_briefs.
  • Data rules: Qloo responses are cached privately on the server and never committed; a test fails if any Qloo-derived file is tracked. Briefs and edits persist in a private Vercel Blob store so share links work on any instance.
  • 30 tests (pipeline, "Which one?", API routes, MCP tools, daily allowance, storage, the live-check harness against a fake Qloo, location strings, room ceiling, and the benchmark rule), all with no keys and no network.

Gut vs Graph benchmark (measured Oct 19, 2026)

pnpm benchmark runs 12 fixed cases (real headliners, cities, and room sizes from 300 to 3,000 capacity, chosen before any run) through the real pipeline, and asks the model alone (OpenRouter nvidia/nemotron-3-ultra-550b-a55b:free, no Qloo data) for five openers per case. One rule scores both lists: a pick counts if it is in Qloo's top 50 local artists for that headliner and city under the room's popularity ceiling, and right-size or a stretch for the room.

  • Model alone: 0.17, or 10 of 60 picks.
  • Rooms of 1,000 capacity or less (8 cases): 0 of the model's 40 picks count. It names the headliner's best-known peers.
  • Rooms of 1,500 to 3,000 (4 cases): 10 of 20 count.
  • Undercard: 1.00 (60 of 60). This is by construction, because the ruler is Qloo's own data, which Undercard also uses to choose. The informative number is the model's.
  • Every model list was written by the model (no template fallbacks).

The rule changed once, and we kept the first result. Rule v1, written the day before the key arrived, used Qloo's top 50 with no popularity cap. Its first live run scored Undercard 0.35 and the model 0.10, and showed why: the acts an audience loves most in a city are almost all Qloo popularity 0.8 and up, so small rooms got only "too big" picks, from Undercard too. That run is what led to the room ceiling.

Design

  • One screen, one decision: the brief opens with "Who opens for [headliner]" and the ranked bill; supporting notes sit below under a quiet heading.
  • Every number is explained in place (hover "Shared taste" for what it means), and the full Qloo trace is one click away.
  • Streaming progress while a brief builds, mobile layouts, loading, empty, and error states, a print stylesheet for PDF export, and a calm note (not an error) when the free daily allowance runs out.
  • Built with Tailwind CSS 4, shadcn/ui, MapLibre GL with CARTO basemaps.

Potential Impact

  • Who it is for: talent buyers and promoters at independent rooms, roughly 200 to 3,000 capacity, booking without an analytics team.
  • The decision it changes: the opener pick, which today is memory and pitches, now comes with local taste evidence and a room-size check, in seconds.
  • What the benchmark shows: for the rooms that most need help, 1,000 capacity or less, a general model's picks never landed in Qloo's local, room-sized list. That gap is the product.
  • Artists benefit too: emerging acts who genuinely fit a local audience surface on evidence, not only through who the buyer already knows.

Quality of the Idea

  • Not a recommender for consumers: a booking tool for the person who decides the bill.
  • Uses Qloo the way a buyer thinks: a specific audience (the headliner's), a specific place (this city), and a specific constraint (this room).
  • Gut vs Graph and the benchmark make the value of the taste data visible instead of asserted, including where our first ruler was wrong.

What Undercard does not claim

  • It does not predict ticket sales. Qloo describes aggregate taste affinities, not individuals; no personal data is sent to Qloo.
  • The size bands and room ceilings are our own simple mapping from capacity to Qloo popularity percentile, not an industry standard.
  • "Shared taste" is the mean score of the top 10 tags Qloo's compare endpoint says two artists share. It is a taste signal, not a count of shared fans.
  • It does not know availability, fees, routing dates, or radius clauses. The buyer still makes the call.
  • The benchmark's ruler is Qloo's own data, so Undercard's 1.00 is by construction and only the model-only score is informative. It measures fit to local taste and room size as Qloo sees them, not commercial outcomes.

How we built it, and the use of AI

  • Built for Joe Simo by Grok Bot, an AI coding agent, which wrote and tested the code, the README, the demo video, and this text under Joe's direction.
  • In the product, an LLM (OpenRouter free models: nvidia/nemotron-3-ultra-550b-a55b:free, with google/gemma-4-31b-it:free and nvidia/nemotron-3-super-120b-a12b:free as fallbacks) writes rationales and copy from Qloo evidence, chooses follow-up actions, and answers the Gut vs Graph question without Qloo data. It never chooses or ranks the openers.
  • The demo video narration is a synthetic voice (Piper TTS, LJ Speech).

Challenges

  • Building before the key arrived: the client was written against Qloo's docs with every parameter audited, and the product ran on a clearly labeled mock. On key day, pnpm live-check passed every endpoint, then real data exposed two things no mock could.
  • City names: Qloo matches signal.location.query by name, and two-letter state codes are read loosely. "Lawrence, KS" matched a Toronto neighborhood and "Richmond, VA" matched Staten Island (which returned no artists at all). "Lawrence, Kansas" and "Richmond, Virginia" resolve correctly, so Undercard now spells the state out and shows the matched locality in the trace.
  • Room size: Qloo popularity is a percentile, and the acts an audience loves most sit near the top of it. Small rooms now ask Qloo directly for acts under the room's ceiling.
  • Free, no-card models with daily limits: responses are cached by prompt hash, retried with backoff, and fall back to templates; a short retry also covers Qloo rate limits when calls burst in parallel.

What we learned

  • On the hackathon server, an exact artist name resolves to one entity, so "Which one?" matters for typos and partial names rather than namesakes.
  • Qloo's compare endpoint returns the shared tags (sorted by score) plus the tags only one side has, which is a clean way to explain why two audiences fit.
  • A benchmark written before seeing real data can be wrong in a way that hides the product's own bug. Running it live is what found the room-size problem.

What's next

  • Venue-aware partners ranked by walking distance from the actual room.
  • A season view: briefs and edits saved per room.
  • Flag competing shows that pull from the same audience on the same night.

Try it out

No login, no account, no key.

  1. Open https://undercard-phi.vercel.app and click a featured brief.
  2. Toggle Gut vs Graph, expand "How this brief was built", try Ask Undercard ("go smaller and more local", "what if Omaha, NE?"), and click Copy link or Save as PDF.
  3. Build your own: headliner, city, capacity, Build brief. Type "Bon Jov" to see "Which one?".
  4. Agents: connect an MCP client to https://undercard-phi.vercel.app/api/mcp and call list_featured_briefs, then build_show_brief.

The demo runs on free model quotas, so each visitor gets 3 fresh briefs and 8 edits a day, with a shared budget of 40 model calls a day per server instance. Featured briefs need no model calls and always open. If the provider's quota runs out anyway, briefs fall back to templated copy instead of failing.

Source: https://github.com/Joe-Simo/undercard (MIT license).

Built With

  • carto-basemaps
  • maplibre-gl
  • mcp-handler
  • mcp-typescript-sdk
  • next.js-16
  • openrouter-free-models
  • openstreetmap-data
  • qloo-insights-api-(hackathon-environment)
  • react-19
  • shadcn/ui
  • tailwind-css-4
  • typescript
  • vercel
  • vercel-blob
  • vitest
  • zod
Share this project:

Updates

Submission history