Sam Rolfe d7d529eb0f Phase 3: parse stored Compass pages into structured items
Turn the raw pages stored by Phase 2 into rows in the SQLite items table
(AGENTS.md §11 Phase 3, FR-3/FR-4/FR-5/FR-12).

- internal/parse: dependency-free HTML reader plus extractors for the ExtJS
  learning-task grid (subject, title, category, kind, due date, status), the
  home news feed (author, posted time, title, body) and the calendar week grid
  (day column + grid offset give the time; label gives title/room/teacher).
- internal/parse: fail loudly. A challenge page, login page, changed layout or
  unknown due-date format is an explicit error, never an empty day.
- FR-4: structure is parsed deterministically; importance is classified by an
  OmniRoute model (default openrouter/google/gemini-3.5-flash-lite) with a
  deterministic fallback. The classifier used is printed and recorded.
- internal/llm: small OpenAI-compatible OmniRoute client (handles SSE replies).
- internal/store: migration 002 adds source_page, importance and external_ref
  with a unique (child_id, source, type, external_ref) index; UpsertItems,
  CountItemsByType, SampleItems and ChildByName.
- cmd/school parse: reads var/raw/<child>/<run>, upserts items, records the scan
  row (ok=0 plus the error on failure) and prints counts and samples.
- scripts/parse-compass.sh and make parse run it in a one-off container against
  the service's own volume without restarting the service.
- docs/phase-3-parse-notes.md records the approach, the FR-4 decision and the
  known gaps.

Evidence on the live database: 56 rows for Harry (20 task, 1 message, 35 event).
Re-running the same raw pages inserts 0 and updates 56.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-15 17:27:25 +11:00

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. This repository is at Phase 3 (parsing the stored Compass pages). See §11 of the spec for the phase order.

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.

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): both children's data, the dashboard data, the calendar write, notifications, and the Hub API. The service itself does not scan the school; the daily scan and the Teams source come later.

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, 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/auth/              session login (signed cookies, CSRF)
internal/web/               HTTP handlers, templates, static assets
deploy/                     Caddy snippet, deployment notes, verify script
scripts/                    Compass fetchers and the parse wrapper
environment.d/              env example (the real file is gitignored)

Run

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:

docker run --rm --entrypoint /app/school compass-teams-nhs/school:0.2.0-phase3 gen-secret
docker run --rm --entrypoint /app/school compass-teams-nhs/school:0.2.0-phase3 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

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.
Description
Scan Compass and Teams for the boys school schedule and homework.
Readme 1.3 MiB
Languages
Go 80.3%
JavaScript 6.2%
Shell 5.6%
HTML 3.6%
CSS 3.3%
Other 0.9%