Phase 6: fix the live Home Assistant write (Local Calendar, no event id)
The first live run against the new calendar.school entity failed loudly twice, and both causes are fixed here. - The Local Calendar rejects `return_response` on calendar.create_event. The client retries the call without it and ignores the changed-states array that Home Assistant returns instead. - Home Assistant then reports no event id. The item keeps the description marker ([compass-nhs:<id>]) as its reference, so it leaves the candidate set and is never written twice. The live retry proved it: both events already in the calendar were adopted by their markers, with no duplicate. - calendar.get_events reports `start` / `end` (a date-time or a bare date), not `start_date_time`; the reader now accepts all three shapes. A model must no longer classify events. The calendar write depends on the school-event rule, and the LLM labelled every event "normal"; events now keep the deterministic rule under both classifiers. Also fix the Phase 5 screenshot script: the calendar banner shares the `.scan` class, so select each banner by its labelled section. Docs record the accepted decision: Sam chose a Home Assistant Local Calendar, so the target is calendar.school. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
# Phase 6 — the calendar write (Sam's Home Assistant calendar)
|
||||
|
||||
Status: **code complete and tested; the calendar target needs Sam's decision**
|
||||
(AUD-110). The service now writes important school events through the Home
|
||||
Assistant REST API. Nothing is written until a calendar entity is chosen.
|
||||
Status: **done** (AUD-110). Sam chose a **Home Assistant Local Calendar**, and
|
||||
the service writes important school events to it through the Home Assistant REST
|
||||
API.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Target entity | `calendar.school` (Local Calendar "School", added 2026-10-15) |
|
||||
| Configuration | `environment.d/school-service.env` (gitignored): `SCHOOL_HA_URL`, `SCHOOL_HA_TOKEN`, `SCHOOL_HA_CALENDAR_ENTITY`, `SCHOOL_HA_TIMEOUT` |
|
||||
| First live events | *Senior Sub-School Community Service for Uniform Infringement* and *End of Year Senior Music Concert — HALL* (2026-10-15) |
|
||||
|
||||
## What it does
|
||||
|
||||
@@ -82,32 +88,37 @@ scripts/calendar-push.sh push --dry-run --json
|
||||
The item screen has an **Add to calendar** button for an important future event,
|
||||
and shows `in the calendar` with the Home Assistant event id once it is written.
|
||||
|
||||
## The open decision: which calendar entity?
|
||||
## Decision (accepted 2026-10-15)
|
||||
|
||||
The Home Assistant instance (`192.168.20.30`, HA 2026.9.4) has three calendar
|
||||
entities, and only two are writable:
|
||||
The Home Assistant instance (`192.168.20.30`, HA 2026.9.4) had three calendar
|
||||
entities, and all the writable ones were Google-backed
|
||||
(`calendar.samuelrolfe_googlemail_com`, `calendar.birthdays`); the brief says
|
||||
**Sam's Home Assistant calendar (not Google)**, and no non-Google calendar
|
||||
existed. Sam chose to **add a Local Calendar**, so:
|
||||
|
||||
| Entity | Writable | Backing |
|
||||
|---|---|---|
|
||||
| `calendar.samuelrolfe_googlemail_com` | yes | Google, synced into Home Assistant |
|
||||
| `calendar.birthdays` | yes | Google |
|
||||
| `calendar.holidays_in_australia` | no | Google |
|
||||
- The Local Calendar integration was added through the Home Assistant config
|
||||
flow, creating the entity **`calendar.school`** (title "School"). It is
|
||||
Home-Assistant-local, never touches Google, and the change is purely additive.
|
||||
- `SCHOOL_HA_CALENDAR_ENTITY=calendar.school`.
|
||||
|
||||
The brief says **Sam's Home Assistant calendar (not Google)**. Today Home
|
||||
Assistant has **no non-Google writable calendar**: the `local_calendar`
|
||||
integration is not installed, and no CalDAV/remote calendar is configured.
|
||||
The calendar is a family calendar, so an event carries the child's name only
|
||||
when it belongs to one child.
|
||||
|
||||
So the target must be chosen before the event is written:
|
||||
## Home Assistant quirks found on the first live run
|
||||
|
||||
1. **Add a Local Calendar to Home Assistant** (recommended). One Home Assistant
|
||||
config-flow entry creates `calendar.school`. It is Home-Assistant-local, never
|
||||
touches Google, and is entirely additive. The service then uses
|
||||
`SCHOOL_HA_CALENDAR_ENTITY=calendar.school`.
|
||||
2. **Use `calendar.samuelrolfe_googlemail_com`.** No Home Assistant change, but
|
||||
every school event also appears in Sam's personal Google Calendar.
|
||||
Both are handled, and both were caught by the fail-loudly design rather than
|
||||
silently writing nothing:
|
||||
|
||||
The question is posted on AUD-110. The code needs only the configuration line
|
||||
changed once the answer is in.
|
||||
- **The Local Calendar rejects `return_response`.** `calendar.create_event` is
|
||||
called with the service response to learn the event id; the Local Calendar
|
||||
answers `Service does not support responses`. The client retries the call
|
||||
without it. Home Assistant then replies with the changed states, a JSON array,
|
||||
which the client ignores.
|
||||
- **The Local Calendar reports no event id.** With no id to store, the item keeps
|
||||
the description marker (`[compass-nhs:<item id>]`) as its reference, so the
|
||||
item leaves the candidate set and is never written twice. The live retry
|
||||
proved it: after a failed first attempt the two events already in the calendar
|
||||
were adopted by their markers, and nothing was duplicated.
|
||||
|
||||
## Where the code lives
|
||||
|
||||
@@ -141,8 +152,12 @@ make test # go test ./...
|
||||
```
|
||||
|
||||
The tests cover the Home Assistant client against a fake Home Assistant (create,
|
||||
read-back, error mapping), the sync run (write once, adopt by marker, dry run,
|
||||
loud failure, refuse-to-write-blind), the store (candidates, the push log) and
|
||||
the web surface (the action, the CSRF guard, the failure banner).
|
||||
read-back, error mapping, the no-response fallback, the `start`/`end` shapes),
|
||||
the sync run (write once, adopt by marker, dry run, loud failure,
|
||||
refuse-to-write-blind, no-event-id idempotency), the store (candidates, the push
|
||||
log), the parser (a model cannot label an event) and the web surface (the
|
||||
action, the CSRF guard, the failure banner).
|
||||
|
||||
Not tested here: the live Home Assistant write. That needs the entity decision.
|
||||
Live proof on `.13` (2026-10-15): two Compass school events were written to
|
||||
`calendar.school`, read back through the Home Assistant API, and the dashboard
|
||||
and item screen show the calendar state.
|
||||
|
||||
Reference in New Issue
Block a user