16 KiB
Compass / NHS (school)
Build spec for Paperclip. Follow the phases in section 11 in order. Do not skip a phase's Definition of Done. When something is unclear, ask Sam — do not guess.
1. Purpose and outcome
Compass carries everything we need, for Harry and Finn: the daily timetable, the learning tasks (what is due), and notices.
Store it, surface overdue and pending work, write the day's periods and due work to a dedicated Home Assistant calendar, and send the morning briefs.
Teams is DEFERRED — do not build it. The Phase 2 fetch proved Compass's Learning
Tasks already carries the due work: learning-tasks-all.json holds 70 structured
tasks with names, dates and descriptions. Teams is not on the critical path.
Outcome (the single success statement):
Sam sees, in one page, what each child has due and what is overdue — without logging into Compass — and the family is told before work is late.
2. Scope
In scope
- Scan Compass per child: the daily schedule, notices, and due dates.
- Scan Compass Learning Tasks per child: what is due, overdue, and coming up.
- Store and display per child.
- Detect overdue and pending work.
- Write important events to Sam's Home Assistant calendar.
- Send the morning briefs via Apprise.
- Answer the Hub.
Out of scope
- The React calendar — that is a separate project. Do not build a calendar UI here.
- Teams — deferred. Revisit only if work is found that exists nowhere in Compass.
- The school's own systems. Read-only.
2a. Prerequisites (verify before Phase 1)
Check these first. The values come from the prerequisite check on .13 (AUD-102).
Items marked not verified need a decision or a test.
| Need | Status / note |
|---|---|
| Go toolchain | MISSING on .13 — go is not in the PATH, not in nix-env, and not in /etc/nixos/configuration.nix. Sam must add go to environment.systemPackages and run nixos-rebuild switch. Phase 1 can build in the golang Docker image instead. |
| Docker + compose | Present on .13. Docker client and server 29.6.2, Compose 5.3.1. Run as user sam (in the docker group, no sudo). |
| Port 8792 | Free on .13. No listener and no container maps this port. Ports 8790 and 8791 are in use by other services. |
Caddy route on .35 |
Caddy runs as the Docker container caddy (v2.8.4) on sam-Ubuntu1 (.35). Routes are site blocks in /Docker/Containers/caddy/Caddyfile, bind-mounted to /etc/caddy/Caddyfile. Use school.lab.audasmedia.com.au { import basic-auth; reverse_proxy 192.168.20.13:8792 }. No school.* block exists yet — Phase 1 must add one. Reload with docker exec caddy caddy reload --config /etc/caddy/Caddyfile. DNS for the name resolves (wildcard) to 144.6.86.11. |
| pi-browser-harness | Present on .13 at /home/sam/.agents/extensions/pi-browser-harness, version 0.11.0. It is a pi extension, not a CLI. Load it as a package extension, then use the commands /browser-setup and /browser-profile. It exposes browser_* tools and drives a long-lived headless Chrome over CDP (--user-data-dir=/home/sam/.chrome-pi, debug port 9222). Session reuse works. The extension is not enabled in this repo's .pi/settings.json — Phase 2 must enable it before the login proof. |
| Compass URL + reachability | https://nhs-vic.compass.education/login.aspx (Northcote High School). DNS goes to Cloudflare. TCP and TLS reach the edge, but the page returns HTTP/2 403 with cf-mitigated: challenge (Cloudflare bot challenge). A real browser session is necessary. This is the Phase 2 risk. |
| Compass credentials + 2FA | NOT AVAILABLE — no Compass, school, or child key exists in ~/.config/environment.d/10-secrets.conf or its backups, and no school-named secret is in the instance config. Sam must supply the account name, the username, and the password as a Paperclip secret (or in 10-secrets.conf). Never write them in this file. 2FA is not verified — no login was attempted. Confirm whether Compass enforces 2FA or new-device email verification, and define the manual step. |
| Apprise | Running on .35:8210 (container apprise, host 8210 to container 8000). GET / and GET /status return 200. The Caddy route apprise.lab.audasmedia.com.au has basic auth. |
Gating read-out
- Phase 1 is unblocked except for Go. Build in the
golangDocker image, or ask Sam to install Go on the host. - Phase 2 is blocked until Sam supplies a Compass credential and confirms the 2FA step. Phase 2 must also enable the
pi-browser-harnessextension in this repo. - The Cloudflare challenge is the main technical risk. Prove it first.
Not verified
- Whether Compass enforces 2FA.
- The exact per-child account model.
- Whether the headless Chrome profile clears the Cloudflare challenge (no login was attempted).
3. Users and roles
| User | Can do |
|---|---|
| Sam | Everything. Session login. |
| Jo / Harry / Finn | Receive notifications. Read-only. |
4. Functional requirements
| ID | Requirement |
|---|---|
| FR-1 | Scan Compass with the pi-browser-harness on an n8n schedule, once per child. |
| FR-2 | Log in and reuse a stored session. A manual step is allowed when the school demands it. |
| FR-3 | Parse messages, homework, due dates, and calendar events into structured items. |
| FR-4 | Use JEV or the LLM to structure the page content and classify importance. |
| FR-5 | Store every item with its child, source page, type, subject, due date, and link. |
| FR-6 | Detect overdue work and work due today and this week. |
| FR-7 | Show a per-child view: overdue, due today, due this week, and messages. |
| FR-8 | Write to a dedicated Home Assistant calendar (suggested name School), per child: (a) that day's periods, (b) work due today, (c) overdue work, (d) major events (excursions, exams). Idempotent — an item already in the calendar is never re-inserted. |
| FR-9 | Send a morning brief to each boy (Harry, Finn) by email: (a) today's classes — period, subject, room, teacher, decoded; (b) overdue work; (c) due today; (d) due this week; (e) notices. Overdue first. Lists only, no fluff. |
| FR-10 | Provide a Delete action and a Send action on items and summaries. |
| FR-11 | Hub API: GET /capabilities, POST /ask (read-only), POST /notify. |
| FR-12 | Fail loudly on a scrape failure. Never report "nothing due" when the scan failed. |
| FR-13 | Log every scan: time, child, pages, items, errors. |
| FR-14 | Send a combined brief for both boys to Jo and Sam via Apprise. |
| FR-15 | Teams — DEFERRED. Do not build. Compass Learning Tasks supplies the due work (see §13). |
5. Screens and UX flow
Screens
- Login — session form.
- Child dashboard — a tab per child: overdue, due today, due this week, messages.
- Item detail — subject, teacher, due date, link, and full text.
- Send panel — choose recipients, preview the brief, send.
- Scan history — last scans, items found, errors.
Main flow
n8n triggers the morning scan → items stored
→ each boy gets his own morning brief (today, overdue, pending, notices)
→ Jo and Sam get one combined brief
→ important events are written to Sam's Home Assistant calendar
Output style: brief, concise, lists only. No fluff. Overdue first.
6. Look and feel
Reuse the Family Home Lab design system.
- Source:
/home/sam/Docker/Containers/family-home-lab/DESIGN.md - Tokens:
/home/sam/Docker/Containers/family-home-lab/portal/static/tokens.css - Copy
tokens.cssinto this project.
Canvas #f6f5f4, one action blue #0075de, Inter, 8px grid, pill CTAs, hairline
borders. Dark mode. Works on a phone browser.
7. Data model
SQLite inside the service. Suggested tables:
| Table | Holds |
|---|---|
children |
id, name, year, compass id |
items |
child, type (message, task, event), subject, teacher, title, body, due-at, url |
scans |
started-at, child, pages fetched, items found, error |
notifications |
recipient, summary text, sent-at |
session |
harness session state per child |
calendar_index |
what has already been written to the HA calendar (child, kind, uid, date) — this is what keeps writes idempotent |
codes |
the editable decode map — subject codes, room codes, teacher initials. Never hardcode these; they change. |
Retention: keep items while the term is current. Scans are append-only and size-capped.
How to read a Compass timetable entry
Format:
[Period Number] - [Subject Code] - [Room Code] - [Teacher Initials]
Real examples from the Phase 2 capture:
8:55: 1 - 11XMUSA6 - A1 - JBR
9:54: 2 - 11XMUSA6 - A1 - JBR
11:23: 3 - 11SBIOA1 - M4 - AFR
1:07: RLE GROUP D - IM1 - RLE
1:07: 4 - 11HLEGB5 - D8 - LHT
| Component | Meaning | Example |
|---|---|---|
| Period | The slot in the school day. Text blocks like C or RLE GROUP D are tutorials, Connect, or assembly. |
1, 2, C, 3 |
| Subject code | VCE unit / class identifier. <year><faculty><subject><block>. |
11MGMAC2 |
| Room code | <wing letter><room number>. |
B11, M6, A1 |
| Teacher initials | Internal 3-letter staff code. | GT, ANE, JBR |
Decoding 11MGMAC2: 11 = Year level · M = Faculty (Maths) · GM = Subject
(General Maths) · AC2 = timetable block.
Store this as the editable codes map. Do not hardcode it — teacher initials and
room codes change between years, and new subject codes appear each term.
8. Integrations
| System | Use |
|---|---|
| pi-browser-harness | Log in and fetch Compass pages. Already built and working. |
| JEV | Structure page content; classify importance. HTTP API. |
| OmniRoute | All other models. |
| n8n | Triggers the daily scan. |
| Apprise | Per-boy morning briefs, and one combined brief to Jo and Sam. Running on .35:8210. |
| Calendar | Sam's Home Assistant calendar. The Hub or Jervis pushes into HA. |
| Hub | POST /ask, POST /notify, GET /capabilities. |
The Hub interface is frozen in central_ai_llm_master/docs/ask-contract.md.
/ask is read-only.
9. Technology (locked)
| Item | Value |
|---|---|
| Backend | Go |
| UI | HTMX + server-rendered HTML |
| Database | SQLite |
| Scraper | patchright (headless Playwright). The pi-browser-harness cannot do the daily scan — Cloudflare 403 managed challenge on the login POST. |
| Data source | Structured JSON, not HTML scraping: learning-tasks-all.json (~70 tasks: name, start, description, category) plus the timetable string time: period - SUBJECT - ROOM - TEACHER. |
| Design | Family Home Lab tokens.css |
| Host | .13, Docker |
| Container | This project's own image and compose file. |
| Port | Allocate one (suggest 8792). Record it once chosen. |
| URL | school.lab.audasmedia.com.au via Caddy on .35 |
| Auth | Separate session login. |
10. Rules and constraints
- Read-only against the school. Never post, message, or submit anything.
- A failed scan must be loud. Never show an empty day as "nothing due".
- School credentials and session state are secrets. Never commit them.
- Models go through OmniRoute, except JEV.
/askis read-only.- Output is brief: lists, no fluff, overdue first.
- Writing a calendar event needs no approval.
- Own folder, own container, own port.
11. Build order
Phase 1 — Skeleton
Go service, SQLite, config, session login, Dockerfile, compose, Caddy route.
DoD: the app runs on .13, login works, /health returns ok.
Phase 2 — Prove the login and the fetch Point the pi-browser-harness at Compass. Log in once. Fetch the pages needed for one child. Store the raw pages. DoD: a real Compass page for one child is fetched and stored. This is the risk phase — a failed login stops the project, so prove it early.
Phase 3 — Parse into items Turn pages into structured items: messages, tasks, events, due dates, links. DoD: real homework and messages for one child appear as structured rows.
Phase 4 — Both children Repeat for Harry and Finn, with per-child sessions. DoD: both children's items are stored and separated.
Phase 5 — Dashboard Per-child view with overdue, due today, due this week, and messages. DoD: Sam can see, per child, what is overdue and due, on a phone.
Phase 6 — Calendar write
Write to a dedicated HA calendar (suggested School), per child:
- that day's periods
- work due today
- overdue work
- major events (excursions, exams)
Idempotent: never re-insert something already present. Track what has been written
in calendar_index.
DoD: a real day's periods and due work appear in the calendar, and running the scan twice adds nothing the second time.
NOTE: this calendar is not spoken by Jervis — it will only speak if Sam creates an automation for it. Google Calendar is separate and already speaks.
Phase 7 — Notify and send Apprise notifications, recipient selection, and the Send panel. DoD: a brief is sent to a real recipient.
Phase 8 — Hub
/capabilities, /ask, /notify.
DoD: the Hub asks "what does Harry have on today at school?" and gets a correct answer.
Phase 9 — Documentation DoD: project-docs run; Gitea, Obsidian, Outline, Vikunja, and the map are updated.
12. Acceptance criteria
- The scan runs daily and stores real items for both children.
- Overdue work is detected and shown first.
- A failed login shows an error, not an empty day.
- A school event can be written to the calendar.
- A brief can be sent to a real recipient from the app.
- The Hub answers a school question correctly.
- Runs in its own container on its own port, behind Caddy.
13. Decisions and remaining questions
Resolved:
- React calendar dropped. Separate project.
- Login is on-screen, via the pi-browser-harness or JEV.
- Go + HTMX + SQLite, own container,
school.lab.audasmedia.com.au, session login. - Calendar = Home Assistant (Sam's). Important events are written there.
- Recipients: each boy gets his own morning brief; Jo and Sam get one combined brief.
- Focus: Compass gives the daily schedule; Teams gives homework and assignments.
- Trigger time: 6:00 am.
- Apprise channels: Email and WhatsApp.
- Scraper = patchright. The daily scan uses patchright (headless Playwright). The pi-browser-harness cannot pass the Cloudflare managed challenge on the login POST.
- Teams dropped from v1. Compass Learning Tasks already carries the due work
(
learning-tasks-all.json, 70 structured tasks). Teams is deferred, not cancelled. - Calendar rule. A dedicated HA calendar gets: the day's periods, due work, overdue work, and major events — per child, and idempotently. The timetable is written to the calendar (not brief-only).
- Not spoken unless asked. The HA calendar stays silent unless Sam adds an automation. Google Calendar is separate and already speaks.
- Email is the report channel. Each boy gets his own by email; Jo and Sam get one combined brief. WhatsApp is optional.
- 2FA: the Compass parent login required no two-factor or one-time code during the Phase 2 proof.
Remaining:
- Teams scope: find the smallest section that answers "what is overdue". Teams may be hard to reach. This is a decision for the brief and for Phase 2.
- Teams two-factor auth: not verified. Confirm whether Teams needs 2FA, then define the manual step.