Files
event_curator/README.md

62 lines
3.5 KiB
Markdown
Raw Permalink Normal View History

# events-calendar-bot
A weekly job that curates upcoming NYC events matching a fixed taste profile and
writes them to a private Google Calendar named **"Events"**, idempotently (re-runs
never create duplicates).
It runs as a **Claude Code scheduled routine** (claude.ai/code/routines): the
routine itself web-searches and writes to the calendar through the Google
Calendar connector. There is no server, no Anthropic API key, and no Google
service account — the only deployed artifact is the routine prompt.
## Layout
| File | Role |
|---|---|
| `ROUTINE_PROMPT.md` | The paste-ready routine prompt. **This is the deliverable** — paste it into the routine. Generated from the template + `reconcile.py`. |
| `ROUTINE_PROMPT.template.md` | Editable source for the prompt (everything except the embedded script). |
| `reconcile.py` | Deterministic dedup + a CLI. Embedded verbatim into `ROUTINE_PROMPT.md`; the routine writes it to `/tmp` and runs it. Pure stdlib, zero deps. |
| `tests/test_reconcile.py` | Unit tests, built from real calendar data. |
## How dedup works (the thing that must not break)
Each run lists what's already on the Events calendar and runs `reconcile.py` to
drop any candidate already present. Identity is `(normalized title, start date)`
with **fuzzy** title matching — it strips a trailing `(tickets req'd)` / `(day-of
option)` tag and a trailing `— Venue` segment, then compares by token overlap —
so the same event reported with a slightly different title across runs still
dedups. It does **not** rely on a stored key: the claude.ai Calendar connector's
`create_event` can't write `extendedProperties`, and the existing hand-created
events have no key, so matching works purely from title + date.
## Local test (no network, no calendar access)
```sh
python3 -m unittest discover -s tests -v
# or run the deduper against two JSON files (candidate list + a calendar export):
python3 reconcile.py candidates.json existing.json --explain
```
## Regenerate the prompt after editing
If you change `reconcile.py` or `ROUTINE_PROMPT.template.md`, rebuild the prompt:
```sh
python3 - <<'PY'
import pathlib
tpl = pathlib.Path('ROUTINE_PROMPT.template.md').read_text()
src = pathlib.Path('reconcile.py').read_text().rstrip('\n')
pathlib.Path('ROUTINE_PROMPT.md').write_text(tpl.replace('<<<RECONCILE_PY>>>', src))
PY
```
## Set up the routine (one time)
1. Open **claude.ai/code/routines** → create a routine.
2. **Prompt:** paste the full contents of `ROUTINE_PROMPT.md`, then replace `<YOUR_CALENDAR_ID>` with your calendar's ID and edit the curation profile to your own interests and neighborhoods.
3. **Connectors:** include the **Google Calendar** connector (it's opt-in per routine — remove the others to limit tool access). No GitHub repo is needed.
4. **Schedule:** weekly, **Thursday 11:00 America/New_York** (routines floor at 1-hour intervals; weekly is fine).
5. **Network:** default/trusted is enough (web search + the connector).
6. **Dry run first:** the prompt ships with `DRY_RUN = true`. Use **"Run now"** and review the printed plan — this is also the end-to-end check that the calendar connector works inside a scheduled run.
7. **Go live:** edit the prompt's `DRY_RUN` to `false`, save, and enable the schedule.
## Target calendar
- Name: **Events** (secondary calendar)
- ID: `<YOUR_CALENDAR_ID>` (your secondary calendar, e.g. `c_xxxxxxxx@group.calendar.google.com`)
- Time zone: `America/New_York`
- Events are written non-blocking (`availability: AVAILABILITY_FREE`) and tagged Graphite (`colorId 8`).