Phase 4: per-child sessions, scan both children, items separated

Fetch and parse Harry and Finn with a stored Compass session per child and
store every item against the right children.id.

- cmd/school/session.go + internal/store/session.go: read and write the
  per-child harness session state in the SQLite `session` table. `show`
  prints metadata only; the state is never printed.
- scripts/compass-fetch-patchright.mjs: reuse the stored session, refresh it
  after login, resolve the child's userId from the parent page by name, and
  keep the shared calendar out of the per-child run (it would leak the
  eldest's timetable into the younger child).
- scripts/scan-compass.sh + make scan: scan one child or both; restore the
  session, fetch, persist the session, then parse. A failing child is
  recorded in `scans` with ok = 0 and the script exits non-zero (FR-12).
- cmd/school/scan.go: `school scan record` writes a fetch failure to the
  scans table. cmd/school/items.go: `school items purge` removes rows that a
  shared page stored against the wrong child.
- internal/store/store_test.go: prove items and sessions are separated by
  child. docs/phase-4-both-children-notes.md; README + image tag 0.3.0-phase4.

Live evidence: Harry 70 tasks / 1 message / 35 events (6 overdue); Finn
42 tasks / 1 message / 0 events (2 overdue). Finn's subjects are Year 7,
Harry's are Year 11. Both sessions stored.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-10-15 17:40:52 +11:00
parent 5d263987dd
commit 207a4978a9
12 changed files with 964 additions and 14 deletions

View File

@@ -4,8 +4,20 @@ 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 3** (parsing
> the stored Compass pages). See §11 of the spec for the phase order.
> Build spec: [`AGENTS.md`](AGENTS.md). This repository is at **Phase 4** (both
> children, per-child sessions). See §11 of the spec for the phase order.
## 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
@@ -29,9 +41,9 @@ Built and deployed on `.13`:
- 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.
Not built yet (later phases): the dashboard data, the calendar write,
notifications, and the Hub API. The service itself does **not** scan the school;
n8n triggers the scan later. Teams (FR-15) is a separate source.
### Fail loudly (FR-12)
@@ -45,7 +57,7 @@ empty day. The dashboard reads the `scans` table and shows one of:
## Layout
```
cmd/school/ service entry point (run, parse, hash-password, gen-secret)
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)
@@ -68,8 +80,8 @@ docker compose up -d --build
Generate secrets with the built image:
```bash
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'
docker run --rm --entrypoint /app/school compass-teams-nhs/school:0.3.0-phase4 gen-secret
docker run --rm --entrypoint /app/school compass-teams-nhs/school:0.3.0-phase4 hash-password 'the-password'
```
Note: Docker Compose interpolates `$VAR` inside `env_file`. A bcrypt hash