Add the Home Assistant REST calendar client and the idempotent sync that writes important school events to Sam's Home Assistant calendar (FR-8). The school stays read-only; the only write is to the calendar (rule 7). - internal/calendar: HA REST client (states, create_event, get_events, delete_event) and the sync that selects important future events, adopts an existing event by its description marker, records the HA event id on the item, and records every attempt. - internal/store: migration 003 (items.ha_event_uid, items.ha_pushed_at and the append-only calendar_pushes table), candidates, mark-synced and the push log. - internal/parse: a school event is important; a routine class period is not. - cmd/school: `school calendar entities|status|push|events`. - web: an "Add to calendar" item action and a fail-loud calendar banner, so a failed write is never swallowed (FR-12). - scripts/calendar-push.sh, Makefile target, env example, docs and tests. The target is configurable (SCHOOL_HA_CALENDAR_ENTITY). Home Assistant has no non-Google writable calendar today, so the entity is asked on AUD-110. Co-Authored-By: Paperclip <noreply@paperclip.ing>
142 lines
5.4 KiB
Markdown
142 lines
5.4 KiB
Markdown
# Compass / NHS
|
|
|
|
A school service for Harry and Finn: **Compass** for the daily schedule and
|
|
notices, **Teams** for homework and assignments. It stores what is due per child,
|
|
puts overdue work first, and tells the family before work is late.
|
|
|
|
> Build spec: [`AGENTS.md`](AGENTS.md). This repository is at **Phase 6** (the
|
|
> calendar write). See §11 of the spec for the phase order.
|
|
|
|
## Status — Phase 6
|
|
|
|
Phase 6 writes the **important** school events to **Sam's Home Assistant
|
|
calendar** through the Home Assistant REST API (FR-8). It is idempotent (a
|
|
re-run never duplicates an event) and it fails loudly (a failed write is shown
|
|
on the dashboard and recorded in `calendar_pushes`). See
|
|
[`docs/phase-6-calendar-notes.md`](docs/phase-6-calendar-notes.md).
|
|
|
|
**Open decision:** the brief says *not Google*, and Home Assistant has no
|
|
non-Google writable calendar yet. The target entity is configurable
|
|
(`SCHOOL_HA_CALENDAR_ENTITY`) and the question is on
|
|
[AUD-110](/AUD/issues/AUD-110).
|
|
|
|
```bash
|
|
scripts/calendar-push.sh entities # list the writable calendar entities
|
|
scripts/calendar-push.sh status # target, reachability, pending, last push
|
|
scripts/calendar-push.sh push --dry-run
|
|
scripts/calendar-push.sh push # write the pending important events
|
|
scripts/calendar-push.sh events # read the calendar back
|
|
make calendar CALENDAR_ARGS='push --dry-run'
|
|
```
|
|
|
|
## Status — Phase 5
|
|
|
|
Phase 5 adds the per-child dashboard: one tab per child, **overdue first**, then
|
|
due today, due this week and messages, plus the item detail screen and the
|
|
Delete actions. See [`docs/phase-5-dashboard-notes.md`](docs/phase-5-dashboard-notes.md).
|
|
|
|
```bash
|
|
# Phone-width evidence screenshots (writes under var/, git-ignored).
|
|
SCHOOL_USER=sam SCHOOL_PASS=... node scripts/screenshot-phase5.mjs
|
|
|
|
# Build a draft brief for one child.
|
|
docker exec compass-teams-nhs /app/school brief build --child Harry
|
|
```
|
|
|
|
## Status — Phase 4
|
|
|
|
Phase 4 fetches and parses **Harry and Finn** with a stored session per child,
|
|
and stores every item against the right `children.id`. See
|
|
[`docs/phase-4-both-children-notes.md`](docs/phase-4-both-children-notes.md).
|
|
|
|
```bash
|
|
make scan # both children
|
|
make scan SCAN_CHILD=Finn # one child
|
|
school session show # session state per child (metadata only, no secrets)
|
|
```
|
|
|
|
## Status — Phase 3
|
|
|
|
Phase 3 parses the raw pages stored by Phase 2 into the SQLite `items` table
|
|
(tasks, messages, events). See [`docs/phase-3-parse-notes.md`](docs/phase-3-parse-notes.md).
|
|
|
|
```bash
|
|
scripts/parse-compass.sh --child Harry --run latest --classify auto
|
|
```
|
|
|
|
## Status — Phase 1
|
|
|
|
Built and deployed on `.13`:
|
|
|
|
- Go service, SQLite schema for `children`, `items`, `scans`, `notifications`,
|
|
`session` (`internal/store/migrations/001_init.sql`).
|
|
- The service's **own session login** — a signed cookie, not SSO.
|
|
- Server-rendered **HTMX** shell using the Family Home Lab design tokens
|
|
(`internal/web/static/tokens.css`), phone-friendly with dark mode.
|
|
- `GET /health`.
|
|
- Its own container and port **8792**, behind Caddy at
|
|
`school.lab.audasmedia.com.au`.
|
|
|
|
Not built yet (later phases): notifications and the Hub API. The calendar write
|
|
is Phase 6. The service itself does **not** scan the school; n8n triggers the
|
|
scan later. Teams (FR-15) is a separate source.
|
|
|
|
### Fail loudly (FR-12)
|
|
|
|
A missing or failed scan is shown as an explicit warning or error, never as an
|
|
empty day. The dashboard reads the `scans` table and shows one of:
|
|
|
|
- **NO SCAN HAS RUN YET** — amber banner.
|
|
- **SCAN FAILED** — red banner with the failure on record.
|
|
- **Last scan completed** — green banner.
|
|
|
|
## Layout
|
|
|
|
```
|
|
cmd/school/ service entry point (run, parse, session, scan, items, hash-password, gen-secret)
|
|
internal/config/ environment configuration
|
|
internal/store/ SQLite: schema, migrations, queries
|
|
internal/parse/ HTML -> structured items (tasks, messages, events)
|
|
internal/llm/ OmniRoute client (importance classification only)
|
|
internal/calendar/ Home Assistant calendar client + the idempotent sync
|
|
internal/auth/ session login (signed cookies, CSRF)
|
|
internal/web/ HTTP handlers, templates, static assets
|
|
deploy/ Caddy snippet, deployment notes, verify script
|
|
scripts/ Compass fetchers, the parse wrapper and the calendar push
|
|
environment.d/ env example (the real file is gitignored)
|
|
```
|
|
|
|
## Run
|
|
|
|
```bash
|
|
cp environment.d/school-service.env.example environment.d/school-service.env
|
|
chmod 600 environment.d/school-service.env # then fill in the secrets
|
|
docker compose up -d --build
|
|
```
|
|
|
|
Generate secrets with the built image:
|
|
|
|
```bash
|
|
docker run --rm --entrypoint /app/school compass-teams-nhs/school:0.4.0-phase6 gen-secret
|
|
docker run --rm --entrypoint /app/school compass-teams-nhs/school:0.4.0-phase6 hash-password 'the-password'
|
|
```
|
|
|
|
Note: Docker Compose interpolates `$VAR` inside `env_file`. A bcrypt hash
|
|
contains `$`, so escape each one as `$$` in the env file.
|
|
|
|
## Verify
|
|
|
|
```bash
|
|
make verify-phase1
|
|
```
|
|
|
|
It checks the container is healthy, `GET /health` returns 200, the login page
|
|
renders and a session login succeeds, and Caddy serves the public URL.
|
|
|
|
## Rules
|
|
|
|
- **Read-only against the school.** Never post, message, or submit anything.
|
|
- A failed scan must be loud — never report an empty day as "nothing due".
|
|
- School credentials and harness session state are secrets; never commit them.
|
|
- This project has its own folder, container, and port.
|