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:
@@ -106,10 +106,10 @@ func TestSyncAdoptsAnEventByItsMarker(t *testing.T) {
|
||||
// The event exists in Home Assistant, but the item has no id (a crashed
|
||||
// run, or a Home Assistant that reports no uid). The marker adopts it.
|
||||
ha.events = []map[string]any{{
|
||||
"uid": "uid-existing",
|
||||
"summary": "Harry — Year 11 Assembly - HALL - CMA",
|
||||
"description": "source: " + Marker(item.ID),
|
||||
"start_date_time": due.Format(time.RFC3339),
|
||||
"uid": "uid-existing",
|
||||
"summary": "Harry — Year 11 Assembly - HALL - CMA",
|
||||
"description": "source: " + Marker(item.ID),
|
||||
"start": due.Format(time.RFC3339),
|
||||
}}
|
||||
c := testClient(t, ha.URL, "calendar.school")
|
||||
|
||||
@@ -290,3 +290,43 @@ func TestSyncWritesASchoolWideEventOnce(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSyncWithoutEventIDsStillNeverDuplicates proves the fallback path: when
|
||||
// Home Assistant reports no event id, the item keeps the description marker as
|
||||
// its reference, so a re-run writes nothing.
|
||||
func TestSyncWithoutEventIDsStillNeverDuplicates(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
st, childID := testStore(t)
|
||||
due := time.Now().Add(4 * time.Hour).UTC().Truncate(time.Minute)
|
||||
item := seedEvent(t, st, childID, "Year 11 Assembly - HALL", due, "high", "assembly")
|
||||
|
||||
ha := newFakeHA(t, "calendar.school")
|
||||
ha.returnUID = false
|
||||
c := testClient(t, ha.URL, "calendar.school")
|
||||
|
||||
first, err := Sync(ctx, st, c, Options{Now: time.Now()}, silentLogger())
|
||||
if err != nil {
|
||||
t.Fatalf("first Sync: %v", err)
|
||||
}
|
||||
if first.Created != 1 || ha.created() != 1 {
|
||||
t.Fatalf("first result = %+v (create calls %d)", first, ha.created())
|
||||
}
|
||||
ci, err := st.CalendarItemByID(ctx, item.ID)
|
||||
if err != nil || ci == nil {
|
||||
t.Fatalf("item: %v %v", ci, err)
|
||||
}
|
||||
if ci.HAEventUID != Marker(item.ID) {
|
||||
t.Errorf("reference = %q, want the description marker %q", ci.HAEventUID, Marker(item.ID))
|
||||
}
|
||||
|
||||
second, err := Sync(ctx, st, c, Options{Now: time.Now()}, silentLogger())
|
||||
if err != nil {
|
||||
t.Fatalf("second Sync: %v", err)
|
||||
}
|
||||
if second.Candidates != 0 || second.Created != 0 {
|
||||
t.Fatalf("second result = %+v", second)
|
||||
}
|
||||
if ha.created() != 1 {
|
||||
t.Fatalf("re-run duplicated the event (%d create calls)", ha.created())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user