WTF is a Terminschleuder? (And why I built one)

One tech scene. A hundred event calendars. So I built a directory that puts community events on one map.

It started, as these things usually do, in a meeting.

Some time ago I sat down with folks from the GI, and at some point the conversation hit the topic that always comes up when people from different tech organizations end up in the same room:

“Wait, you also have your own event calendar?”

GI runs theirs. The ACM folks maintain theirs. The CCC chapters have wiki pages and mailing lists, RZL has their own thing, and every local group, hackerspace and Meetup has some iCal file on a WebDAV server from 2009.

Individually, most of them are well cared for. Collectively? It’s an archipelago. Every island nice and tidy, and mutually invisible.

I said out loud what everyone was probably thinking: GI, ACM, CCC, RZL and friends are, technology-wise, barely connected at all. We are one big tech scene that schedules itself in a hundred places at once.

Then I did the thing I usually do after saying something like that out loud. I built it.

Say hello to Terminschleuder.

WTF is a Terminschleuder?

The name is German and it is exactly what it sounds like: a Termin (appointment) Schleuder (catapult). An appointment catapult. Flinging tech events onto a map since 2026.

If you speak German you are either smiling or wincing now. Either is fine.

What it actually is: a location-aware tech event directory for meetups, talks, conferences, workshops, whatever the community cares to publish. One catalog, one API, one map.

You ask “what is happening within 20 km of Berlin?” and you get an answer, nearest first:

curl 'https://www.terminschleuder.online/api/events/?near_city=berlin-de&radius_km=20'

It lives at terminschleuder.online, the code is on GitHub (github.com/Terminschleuder), and there is a read-only demo client with maps and filters so you can poke at it without opening a terminal.

Explore Terminschleuder ↗   ·   Browse the code ↗

Finding tech events within 20 km of Berlin

The fun part of a location-aware anything is geography, and geography is where projects like this usually go to die. So the backend is Django + DRF on PostgreSQL with PostGIS, and proximity is a real spatial query (ST_DWithin on geography points), not the “draw a bounding box and pray” approximation.

Results come back distance-annotated and sorted nearest-first. Online-only events are excluded from proximity, because an event without coordinates cannot be near you. Physics.

End users don’t know lat/lon (and shouldn’t have to), so there is a city gazetteer: 2,131 European cities with population ≥ 50,000, seeded from GeoNames. Pick a city, get its centroid, get a sensible default radius, tiered by city size:

City population Default radius
1 million or more 45 km
500,000–999,999 35 km
100,000–499,999 25 km
Below 100,000 15 km

(BTW, PostGIS geography points take longitude first. Yes, lon, lat, in that order. Yes, I double-checked. Twice.)

LLM event extraction: the trust boundary

So where does the event data come from? Two ways in:

  1. Hand-curated: a trusted human types it into the back office. Starts life published.
  2. Everything else: scraped from the web. And scraped data is, politely speaking, untrusted garbage until an operator says otherwise.

The second path is the interesting one, and it is the reason the whole system is shaped the way it is.

A little Python service (the extractor) fetches source pages, cleans the HTML with BeautifulSoup, and asks an LLM to pull out structured events. It supports any OpenAI-compatible endpoint and defaults to my local Ollama, because who doesn’t like a free token budget?

Pydantic validates the output, minimum delays between requests keep it from hammering anyone’s site, and the logs contain counts and IDs instead of secrets.

The rule: the extractor can never write to the catalog.

Its output lands as an EventObservation: quarantine, basically. An operator reviews the observations and promotes the good ones into a draft event, with full provenance: which source it came from, which observation, the original URL. Then it gets published. Observations never mutate a canonical event. The extractor reports; a human decides.

Which is honestly how it should be, because an LLM extractor is a drunk intern with great handwriting: fast, convincing, and not to be trusted with a production database.

Docker deployment: keeping the box boring

The production machine can pull Docker images. That’s it. No apt, no pip, no GIS libraries on the host. The whole point is that the box stays boring.

That constraint shaped everything, and I mean that as a compliment. Backend? Runs in a container. Tests? Run against real PostGIS in a container. Frontend? Container. Extractor? You guessed it.

From manual deployments to releases

Deployments were manual for a while (which sucks). Now every merge to main cuts a CalVer release using YYYY.MINOR.0.

The images (app, database, frontend, extractor) land on ghcr.io, get a Trivy scan, and the git tag and GitHub release are created last, so a failed run can retry with the exact same version. A pull request never publishes. Publishing on a PR would be a bug, not a feature.

There is also a rendered slide deck that walks through the whole architecture, because apparently I make slide decks about my side projects now. You can grab the PDF from the releases page.

Was it worth it?

The software was the easy part, as usual. A weekend of PostGIS, some quality time with Django, a very well-behaved container pipeline.

The hard part is the part no pipeline can fix: a cross-org event calendar only works if the orgs feed it (or let me scrape them 🙂).

Run a community event calendar?

If you run one of those calendars (GI, ACM, CCC, RZL, a hackerspace, or one of the five hundred Meetups I don’t know about), get in touch. The ingestion API is open, the extractor is polite, and your events end up on a map next to everyone else’s.

It turned a complaint from a meeting into a running system with a public API, a map, and an appointment catapult for a name. At the very least, the next such meeting gets to complain about something else.

You’re welcome.

Leave a Reply

Your email address will not be published. Required fields are marked *