Weekly Events-calendar curation routine and deterministic deduper
This commit is contained in:
61
README.md
Normal file
61
README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# 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`).
|
||||
Reference in New Issue
Block a user