Beatmap
Beatmap treats the media list as a graph instead of a table. Each journalist gets an AI-generated profile, inferred topics, and weighted edges to writers covering adjacent ground — and pitches and coverage suggestions are tailored to the client, not generated from scratch.
In journalism, a "beat" is the territory a reporter covers — tech, climate, municipal politics, restaurant openings. Beatmap is the literal map of those territories: who owns which beat, where beats overlap, and which writers sit one hop away from the one you actually want.
PR teams maintain media lists in spreadsheets — names, outlets, beats, last-contacted dates. The data is static, the relationships are tribal knowledge, and the pitch-writing happens from scratch every campaign. Beatmap treats the media list as a graph instead of a table: each journalist gets an AI-generated profile, a set of inferred topics and themes, and weighted edges to other journalists who cover adjacent ground. Pitches and coverage suggestions are generated against a specific client, so the output is always tailored, not generic.
Earned media is a relationship business pretending to be a contact-management business. The CRMs that dominate the space (Cision, Muck Rack, Meltwater) optimize for list-building and clip-tracking — they tell you who exists and what they wrote, but not who's adjacent to your story or how to actually open a conversation. Junior PRs spend their first six months learning the map by hand.
A queued OpenAI pipeline runs five generators per journalist: a profile (overview + insights), a topic and theme extraction, article summarization, client-specific pitch suggestions, and a coverage report. A separate edges pass infers relationships between journalists based on overlapping topics, outlets, and themes, producing a weighted graph.
The frontend renders the graph in D3 with filterable beats and client overlays. Click a journalist to get their AI profile, pitch angles for the selected client, and a coverage log. Better-sqlite3 holds everything locally — no cloud DB, no auth surface, fast iteration.
Working app: client sidebar, journalist panel, force-directed network graph, pitch log, coverage log, CSV export. ~10 routes on the server, 9 React components, 5 generator modules sharing a single pipeline with a 2-concurrency throttle so the OpenAI bill stays sane.
Scraper hardening for outlets that block headless requests, an "outreach thread" view that stitches pitches → replies → coverage into a single timeline, and a hosted multi-tenant version once the data model stops shifting weekly.
