Crate ingest pipeline
In developmentData pipeline
A pipeline that finds concerts on the venues' and promoters' own pages and in the ticketing feeds, and turns them into one clean record for each show. I am building it to feed the concert listings in The Crate, and later to offer as a dataset of its own.
It is switched on for one city, Pittsburgh, and nothing it makes reaches the app yet. The older pipeline still feeds all 31 metro areas in The Crate.
Part of The Crate. The other part: The app
What it is
The Crate shows each user upcoming concerts by the artists in their crate, and other shows nearby they might like. Ticketing feeds such as Ticketmaster and SeatGeek cover many of them, but plenty of shows in bars, breweries, cafes and small clubs never reach those feeds. This pipeline is meant to find every concert in a city, from the venues' and promoters' own pages as well as the ticketing feeds.
What comes out is one clean record per show, with its venue, date, start time and the acts on the bill. Each fact in a record keeps a note of the page it came from. When two pages list the same show, they become one record.
Its first user will be the Shows feature in The Crate. Those listings come today from an older pipeline I built first, which refreshes a feed for each of 31 metro areas every morning. When I reviewed that pipeline on 2 October 2026, about a fifth of its upcoming shows came from one snapshot file that nothing refreshed. Its show ids also shifted, and 1.6% of upcoming ones changed in a single week, which broke saved RSVPs. The new pipeline runs apart from the app, and its daily job compares what it holds with the live feed. There is no date yet for switching the app over to it.
Only Pittsburgh is switched on so far. I score coverage against the old pipeline, as the share of its upcoming Pittsburgh shows that the new one finds when it starts from an empty database. Nothing from the old feed goes in. On 31 October 2026 that share was 37.8%. Measured on a copy of a later test run, it reached 66.1% on 2 November 2026. My bar is full coverage from one run with no follow-up by hand, and the fixes so far should bring it near 85 to 90%.
How it works
A new city starts as one entry in a settings file, with its center point and a radius of 30 miles. A run works through these steps in order, then repeats them on what the last round found, for up to eight rounds, until one adds nothing new.
- Find placesCollects every place in the area that could host a show, starting from the ticketing feeds and open map data.Ticketing feeds, Overture Maps
- Tidy placesGives each place a street address and a map point, then merges records that turn out to be the same building.US Census geocoder, rules, then judge models
- Check websitesOpens each place's website and decides whether it lists real upcoming events and whether it is the venue's own page.Rules, then Jev (a small decision model), Playwright (a browser) for pages that need one
- Pick a readerUses the page's own event data if it has any, and if not, has a model write a recipe that shows where each date and title sits.Rules, Claude Haiku
- TrialReads each new page once and keeps it only if its dates pass a free check and its shows are in the right city.Rules, no model
- Merge showsTurns each listing into a claim, one page's statement about a show, and matches claims so two pages about one show become one record.Rules, then Jev and Claude Sonnet
- Match artistsSplits each bill into acts, ties each act to one artist record, then reads that artist's own tour page, which names more venues for the next round.MusicBrainz (an open music database), rules, Claude Sonnet and Haiku
- ReviewSends what the rules and models could not settle to the judge models first, and what they leave unsure to me.Jev, Claude, then me
- PublishWrites a checked snapshot of upcoming music shows, which the app does not read yet.Cloud Storage, a daily Cloud Run job
Rules do as much of the reading as they can. Many venue sites carry their events in a standard format that code reads for free. For a page without it, Claude Haiku writes the recipe once, and code reads the page with that recipe on every visit after that. The model is asked again only when the recipe's own check fails. A recipe costs about a third of a cent to write.
Where a model has to read, the cheaper one goes first. Claude Haiku reads pages that are plain text. A free check then compares each date with the words around it, and only the parts it doubts go to Claude Sonnet, the larger model. On five test venue pages on 26 October 2026, that found all 120 upcoming shows with the right dates for $0.11, against $0.81 for Sonnet alone. Yes-or-no questions, such as whether two listings are the same show, go first to Jev. It answers with a probability, for a few thousandths of a cent a call.
Every model call goes through one gateway that records what it cost. I approve each call by hand, or approve a whole run in advance as a batch with a call count and a hard cap for each model. The daily job in the cloud never calls a model. It replays answers that were approved and recorded earlier, so the same question is never paid for twice.
Where a person decides
I am the last step for every judgment the rules and models cannot settle. When the judge models cannot decide whether two venue records are one building, or what type an event is, the question comes to a review page. My pick there binds every rule and model after it. I also approve model spending, call by call or a whole run at a time.
By a count on 4 November 2026, the pipeline waits on me in 19 separate places, and many of my answers were never fed back into it. Two review pages hold 167 answers I gave that were never applied, so none of them became a label the models could learn from.
The fix I picked that day is not built yet. Every question will land in one inbox, on one kind of card. Each card carries all the evidence, a reason, a note and a box for what I needed that was not there. Then each kind of question moves to the models in steps, once my own past answers show it is safe. A random 2% of their answers still come back to me. Same-venue merges in a city, for example, go automatic only after 150 checked cases with no wrong merge.
Why build it, and who else could use it
I plan to offer this data to others one day, so I need sources that allow it. Ticketmaster's developer terms bar selling or sublicensing its API. Bandsintown opens its API only to artists and the people working for them. So a dataset I could license to someone else has to be my own. The rule I set on 24 October 2026 is that anything I sell must come from a venue's, promoter's or artist's own pages.
The product I have planned is an API and data files that serve only what may be sold. Beside the shows they would carry each venue's published booking and press contacts, and each artist's own links. One idea I logged on 3 November 2026 is a free tool for small venues that post their shows as images. A venue would enter each show once, and the tool would make the poster, the social posts, the web listing and a print order from that record. Each venue that used it would license its calendar to me. I have not tested the idea.
JamBase is the one concert-data API I know of with a public price list. Its annual plans run from $6,000 to $30,000 a year, and it names Google and Spotify among its customers. PredictHQ sells event data to businesses for demand forecasting.
Nothing is for sale yet, and there is no buyer and no price. The code that would cut a sellable set out of everything the pipeline holds is not written. Under my own rule, shows at venues with no website of their own could not be sold, and about a third of small venues have none.
The hard parts
Telling one record from another
Duplicate shows and venues were my biggest data problem in the old pipeline. In this one a matching name is never enough to merge two records, because names repeat across cities and the ticketing feeds make mistakes too. In one of them, about 1 in 20 of the artist ids point to the wrong act.
Pairs now go through rules first and then a ladder of judge models, and every merge can be undone. On the first full test run, a rule that merged any two places on one street let 275 merges through. On 87 of them, the model's own reason said they were two businesses or two rooms. I fixed that rule on 3 November 2026. A labelled set of artist pairs for setting the thresholds is still to come.
Finding the small rooms
The first time I measured a run from an empty database, on 31 October 2026, it held 37.8% of the old pipeline's upcoming Pittsburgh shows. Of the 757 shows it missed, 412 were at venues it already held, where no page it read listed the show. Another 1,379 places in its records had a website that no step had opened.
I changed the steps so a run checks every website it holds and keeps going round until a round adds nothing. Measured on a copy of the next test run, coverage reached 66.1% on 2 November 2026, and the fixes should bring it near 85 to 90%. A second run from an empty database, still going on 4 November 2026, will test that.
Runs that take all night
Discovery runs by hand on my Mac, one city at a time, and a full run from empty now takes a night or longer. The second Pittsburgh run started on 3 November 2026 and had not finished by the evening of 4 November 2026. Its first round took about 10.9 hours, about twice the 5.5 hours in the plan, and spent $15.67 on model calls. The first full run had spent $15.04 before it stopped.
Long runs could also fail without saying so. A step could report success after doing nothing, and once the artist steps ran for 19 hours with their database connection down and merged nothing. Every step now has to show what it added, and a run that leaves work no later step will pick up ends as a failure. The run script also restarts that database connection when it drops.
Model cost and model mistakes
Up to 30 October 2026, recipe writing on Claude Sonnet made up 68% of all model spend. I moved it to Claude Haiku. On the test pages Haiku found 97.4% of the rows against Sonnet's 97.8%, at about a fourteenth of the cost per recipe.
Haiku put 7 of 120 dates a day early in the first test of venue pages. A judge model once answered "different, confidence 0.1" about two cafes, the code read that as 90% sure they were the same, and it merged them. Doubted dates now get a second read, and an answer under 0.5 confidence counts as unsure and goes up to the next model.
Reading dates as the page means them
A listing that says "Dec 9-11" is three nights, and "Oct 3" with no year could be last month or next year. The old pipeline once stamped the current year on an undated 2016 article, and a past festival went out as a live show. In this one, 28 of the Pittsburgh shows missed on 1 November 2026 came from runs of dates kept as one night or read a day off.
Rows that state several nights now become one show a night. A date with no year that fell in the last 60 days counts as past. Each time is stored as the page wrote it, with a note of how exact it is.
Approving every model run
Every paid model run needs my approval, call by call or as one batch with a cap for each model. In one week I approved all 18 batches I was asked about, so the step cost me time and refused nothing.
The plan I picked on 4 November 2026 replaces most of those asks with a standing budget per task and city. I would then see only a new task, a new model, a line over its budget or a cost per item twice the usual. It is not built yet.
What it runs on
| Layer | What | Notes |
|---|---|---|
| Language | Python 3.12 | It is a service of its own, kept apart from The Crate's TypeScript app. |
| Command line and API | Typer, FastAPI | Each step is a command, and the API so far runs only on my machine. |
| Database | Postgres 17 with PostGIS | Its 45 tables keep every fact as a claim with its source, and the clean records are rebuilt from those claims. |
| Schema | LinkML, SQLAlchemy, Alembic | One schema file generates the Python models and the TypeScript types, and a test fails if the database drifts from it. |
| Fetching | httpx, Playwright with Chromium | Pages are fetched with a plain request first, and a real browser opens only the pages that need one. |
| Reading | extruct, BeautifulSoup, lxml, icalendar | Rules read structured event data and calendar feeds, and a runner reads other pages with their site recipes. |
| Models | Claude Haiku and Claude Sonnet on the Anthropic API, Jev from TypeSafe AI through the Vercel AI Gateway | Every call goes through one gateway, which picks the model from a settings file and checks the spending caps first. |
| Open data | Overture Maps, US Census geocoder, a MusicBrainz copy | Free open data comes before any paid lookup, and the MusicBrainz copy is set to refresh each month. |
| Evals | Braintrust, labelled test sets, recorded model answers | A model takes over a task only after it beats the current one on the same labelled set. |
| Cloud | Google Cloud Run, Cloud Scheduler, Cloud SQL, Cloud Storage, Secret Manager | The daily job runs there, and every fetched page is kept so a change to a reader can be tested on old pages first. |
| Checks | pytest, ruff, a replay of saved pages | One local command runs about 1,000 tests and the replay before a commit, and there is no hosted CI. |
- commits on main
- 557
- lines of Python
- 76,096
- recorded decisions
- 285
- experiment write-ups
- 104
Counts read from the repository on 4 Nov 2026.
Other projects
The Crate
BetaA music ranking and concert discovery app. You sort the music you love into tiers, then use that crate to compare taste with friends and find concerts by those artists near you.
App · iOS · Android · Web
2,380 commitssince Jun 2026LampLit
BetaA phone app that works like a light on your porch. Turn your light on when you're up for company, and the friends who see it can knock or text you.
App · Android · iOS in review
539 commitssince Sep 2026MattressBuilder
PreviewA website for designing your own mattress in 3D. You stack layers of wool, latex, foam and coils, drag them into the order you want, and watch the bed and its price change as you go.
Web app
34 commitssince May 2026