# Phase 6b/7b — per-child periods, decoded brief, idempotent calendar writes Status: **done** (AUD-119). Supersedes the scope notes in [`phase-6-calendar-notes.md`](phase-6-calendar-notes.md) and [`phase-7-notify-notes.md`](phase-7-notify-notes.md) where they differ. Spec: `AGENTS.md` §4 FR-8, FR-9, FR-16, FR-17, FR-18; §7 data model; §11 Phase 6/7. Image `compass-teams-nhs/school:0.6.2-phase6b7b`. ## 1. Periods are per child (FR-16) — the bug fixed The Compass **week calendar** is school-wide: its lesson grid belongs to the eldest child, so every child's items carried Harry's Year 11 periods. Finn (Year 7) was shown `11MGMAC2 - B11 - GT`. The per-child timetable is the **day widget** Compass renders on each child's own pages (`/Records/User.aspx?userId=…` and the Learning Tasks page), labelled "Today": ``` 8:55: 1 - 7SCF - H14LAB - KNI ``` - `internal/parse/daywidget.go` reads that widget from the child's own pages and stores the periods against that child. - `internal/parse/calendar.go` now drops routine lessons from the school-wide week calendar and keeps only real school events. - A re-parse deletes the child's leaked routine `calendar`-page rows, so the wrong periods cannot survive. The day-widget label prints a 12-hour clock with no AM/PM (`1:26` is the afternoon period), so the parser applies the school-day rule: 1–6 are PM, 7–11 are AM, 12 is noon. The widget's grid offset is *not* used, because its start hour moves with the day's first event (07:00 on one page, 06:00 on another). ## 2. The decode map (FR-17) `codes` (migration `005_codes.sql`) is the editable map: `subject`, `room`, `teacher` and `period`. It is seeded on first read and never overwrites a correction. An unknown code renders as the **raw code**, never blank. ``` === FINN === PERIODS TODAY - Period 1 — Year 7 Science — Room H14LAB — Mr KNI (08:55) ... - Period 4 — Year 7 Physical Education — Gym 4 — Mr LTA (13:26) ``` Sam edits it without a rebuild: ```bash docker exec compass-teams-nhs /app/school codes list docker exec compass-teams-nhs /app/school codes set --kind teacher --code KNI --name 'Mr Knight' docker exec compass-teams-nhs /app/school codes delete --kind room --code Z9 ``` Teacher rows are placeholders (`KNI` -> `Mr KNI`) because Compass gives only initials; Sam replaces them with surnames. Real unknown-code fallback from the live brief: Harry's period 2 teacher field is `GT AGE_13`, and `AGE_13` is not mapped, so it renders raw (`Mr GT / AGE_13`). Seed size: 65 rows — 23 subjects, 22 teachers, 18 rooms, 2 period labels. ## 3. The calendar (FR-8) For **each child separately**, every pass writes to `calendar.school`: - that day's **periods**; - **work due** — any future date; - **work overdue**; - **major school events** (concerts, excursions, exams). Selection: `internal/store/calendar.go` adds `CalendarPeriodCandidates` (today's day-widget periods) and `CalendarTaskCandidates` (outstanding work with a due date); the existing `CalendarCandidates` keeps the important school events. Submitted work and work with no due date are never written. Idempotency is unchanged and still uses the stable per-item key: an item with a Home Assistant event id is not a candidate, and an event whose description carries `source: [compass-nhs:]` is adopted instead of written again. Periods and work are per child; a school-wide event is still written once and recorded against every child. `scripts/morning-brief.sh` now runs **scan → calendar push → notify**, so the 6:00 pass writes the calendar. `scripts/calendar-push.sh events` gained `--past` so overdue work (which sits at its Compass due date) can be read back. ## Evidence (2026-10-15 UTC / 2026-10-16 AEDT) ### The rendered brief — real pass `Finn` block, first, with his own decoded Year 7 periods: ``` === FINN === PERIODS TODAY - Period 1 — Year 7 Science — Room H14LAB — Mr KNI (08:55) - Period 2 — Year 7 Design — Room B1 — Mr PSI (09:54) - Connect — Year 7 Tutorial — Room G7 — Mr KDI (11:18) - Period 3 — Year 7 English — Room G7 — Mr DCA (11:47) - Period 4 — Year 7 Physical Education — Gym 4 — Mr LTA (13:26) - Period 5 — Year 7 Drama — Theatre — EBA (14:20) EVENTS TODAY - JAZZ ENSEMBLE - ENS - KWO (07:45) - JUNIOR GUITAR ENSEMBLE - THEATRE - HHL, OFG (07:45) - HHL GUITAR E - IM3 - HHL (09:54) OVERDUE - 7PE PHYSICAL EDUCATION: CAT 3: Cardiorespiratory Fitness (due Thu 03 Sep 16:00) [overdue] - 7DES PRODUCT DESIGN: CAT 2: Eco Light - Research and Development (due Thu 17 Sep 16:00) [overdue] DUE IN FUTURE - 7DRA DRAMA: CAT 2: Ancient Greek Theatre (due Mon 26 Oct 16:00) - 7TUT CONNECT: Learning Behaviour Report - Self Reflection Term 4 (due Tue 03 Nov 11:45) - 7DES PRODUCT DESIGN: CAT 3: Eco Light - Making and Evaluating (due Thu 19 Nov 15:15) - 7MA MATHEMATICS: CAT 6: Measurement and Geometry Test (due Fri 20 Nov 16:00) - 7DRA DRAMA: CAT 3: Performance or Research Project (due Mon 07 Dec 16:00) - 8 task(s) with no due date (see the dashboard) ``` `Harry` block, second, with his own Year 11 periods: ``` === HARRY === PERIODS TODAY - Period 1 — Year 11 General Maths — Room B11 — Mr GT / SMH (08:55) - Period 2 — Year 11 General Maths — Room B11 — Mr GT / AGE_13 (09:54) - Connect — Year 11 Tutorial — Room M6 — Mr ANE (11:18) - Period 3 — Year 11 Art Creative Practice — Room C1 — Mr BDR (11:47) - Period 4 — Year 11 English — Room H3 — Mr CHA (13:26) - Period 5 — Year 11 Music — Room A1 — Mr JBR (14:20) EVENTS TODAY - none ``` The combined brief for Jo and Sam carries the same two blocks, `=== FINN ===` first and `=== HARRY ===` second, never interleaved. The undated tail is one line with a count (`17 task(s) with no due date …`), never the old 20+ dump. ### The Home Assistant calendar — per child (summary + start) Read back from the Home Assistant API. 35 events in total. Finn — 16 events: ``` 2026-09-03T16:00 Finn — 7PE PHYSICAL EDUCATION: CAT 3: Cardiorespiratory Fitness 2026-09-17T16:00 Finn — 7DES PRODUCT DESIGN: CAT 2: Eco Light - Research and Development 2026-10-16T07:45 Finn — JAZZ ENSEMBLE - ENS - KWO 2026-10-16T07:45 Finn — JUNIOR GUITAR ENSEMBLE - THEATRE - HHL, OFG 2026-10-16T08:55 Finn — Period 1 — Year 7 Science — Room H14LAB — Mr KNI 2026-10-16T09:54 Finn — HHL GUITAR E - IM3 - HHL 2026-10-16T09:54 Finn — Period 2 — Year 7 Design — Room B1 — Mr PSI 2026-10-16T11:18 Finn — Connect — Year 7 Tutorial — Room G7 — Mr KDI 2026-10-16T11:47 Finn — Period 3 — Year 7 English — Room G7 — Mr DCA 2026-10-16T13:26 Finn — Period 4 — Year 7 Physical Education — Gym 4 — Mr LTA 2026-10-16T14:20 Finn — Period 5 — Year 7 Drama — Theatre — EBA 2026-10-26T16:00 Finn — 7DRA DRAMA: CAT 2: Ancient Greek Theatre 2026-11-03T11:45 Finn — 7TUT CONNECT: Learning Behaviour Report - Self Reflection Term 4 2026-11-19T15:15 Finn — 7DES PRODUCT DESIGN: CAT 3: Eco Light - Making and Evaluating 2026-11-20T16:00 Finn — 7MA MATHEMATICS: CAT 6: Measurement and Geometry Test 2026-12-07T16:00 Finn — 7DRA DRAMA: CAT 3: Performance or Research Project ``` Harry — 17 events: ``` 2026-06-09T12:00 Harry — 11SBIO 11 SCIENCE - BIOLOGY: Unit 1 Biology Exam 2026-09-01T16:00 Harry — 11XMUS 11 PERF ARTS - MUSIC: SAC #2: Creating 2026-09-01T16:00 Harry — 11XMUS 11 PERF ARTS - MUSIC: VCE Unit #2 Outcome #2 Creating 2026-10-05T16:00 Harry — 11XMUS 11 PERF ARTS - MUSIC: SAC #1: Performing 2026-10-05T16:00 Harry — 11XMUS 11 PERF ARTS - MUSIC: VCE Unit #2 Outcome #1 Performing 2026-10-09T16:00 Harry — 11HLEG 11 HUMANITIES - LEGAL STUDIES: SAC 6: Human Rights Presentation 2026-10-16T08:55 Harry — Period 1 — Year 11 General Maths — Room B11 — Mr GT / SMH 2026-10-16T09:54 Harry — Period 2 — Year 11 General Maths — Room B11 — Mr GT / AGE_13 2026-10-16T11:18 Harry — Connect — Year 11 Tutorial — Room M6 — Mr ANE 2026-10-16T11:47 Harry — Period 3 — Year 11 Art Creative Practice — Room C1 — Mr BDR 2026-10-16T13:26 Harry — Period 4 — Year 11 English — Room H3 — Mr CHA 2026-10-16T14:20 Harry — Period 5 — Year 11 Music — Room A1 — Mr JBR 2026-10-19T16:00 Harry — 11EENG 11 ENGLISH - ENGLISH: SAC 3: Responding to Texts: The Handmaid's Tale 2026-10-30T16:00 Harry — 11MGMA 11 MATHS - GENERAL MATHS: SAC 4: Networks 2026-11-03T10:45 Harry — 11TUT CONNECT: Learning Behaviour Report - Self Reflection Term 4 2026-11-11T16:00 Harry — 11XMUS 11 PERF ARTS - MUSIC: SAC #3: Analysing and Responding 2026-11-11T16:00 Harry — 11XMUS 11 PERF ARTS - MUSIC: VCE Unit #2 Outcome #3 Analysing and Responding ``` School-wide — 2 events (written once, shared by both children): ``` 2026-10-15T15:05 Senior Sub-School Community Service for Uniform Infringement - A2 - PSU, CMA, JGE, LHT, QLM, ECU 2026-10-15T18:00 End of Year Senior Music Concert - HALL (Senior Music Concert) - KSH, CFR, AMG, OFG, NBA, CNI, JDF, SLT, RLE ``` ### The 3-run idempotency proof Three real passes (`scripts/scan-compass.sh` then `scripts/calendar-push.sh push`), read back after each: | Pass | `candidates` | `created` | `failed` | HA events in `calendar.school` | |---|---|---|---|---| | 1 | 33 | 33 | 0 | 35 | | 2 | 0 | 0 | 0 | 35 | | 3 | 0 | 0 | 0 | 35 | Runs 2 and 3 added nothing; the event list after run 2 and after run 3 is byte-identical. ## Files changed ``` internal/store/migrations/005_codes.sql the editable codes table internal/store/codes.go CodeMap, seed, list/set/delete internal/store/calendar.go period + work candidate queries, leak cleanup internal/store/store.go DeleteItemsFromPages internal/period/period.go lesson-label split (period/subject/room/teacher) internal/decode/decode.go plain-English lesson rendering + raw fallback internal/parse/daywidget.go the per-child "Today" widget internal/parse/parse.go, calendar.go, importance.go per-child hook, week-calendar filter internal/dashboard/dashboard.go brief buckets + the required 4-section renderers internal/brief/brief.go Finn-first order, codes decoder, new renderers internal/calendar/sync.go periods + due + overdue + events, decoded, once internal/calendar/ha.go Event JSON exposes start/end for evidence cmd/school/{main,parse,calendar,codes,items,notify}.go wiring, codes CLI, purge by page scripts/{morning-brief,scan-compass,calendar-push,parse-compass}.sh calendar step, version docker-compose.yml, Makefile image 0.6.2-phase6b7b ``` ## Not tested - A **real email/WhatsApp send** of the new brief was not repeated; the send path is unchanged and was last proven in Phase 7. The dry run prints exactly what is sent. - The **6:00 n8n job** was not re-published. It calls `scripts/morning-brief.sh`, which now includes the calendar step; Sam owns the shared n8n service. - The **week calendar** is still not fetched by the daily scan, so a *new* major event is only written if a run fetches that page. The two existing school-wide events remain in the calendar. - **Teacher names** are placeholders; only Sam can supply the surnames.