Phase 9: publish the documentation set (README, map, Outline, Vikunja)
- README: Phase 9 status, the architecture diagram + map link, and the explicit "Teams (FR-15) is DEFERRED" statement. - docs/architecture.mmd + docs/diagrams/architecture.png (mmdc). - docs/archify/architecture.architecture.json -> docs/architecture-map.html (Archify showcase, 9/9 checks). - docs/outline/: the five AI-context documents (STE style). - docs/vikunja-tasks.json: the live Vikunja board (project 21). Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
73
README.md
73
README.md
@@ -1,11 +1,71 @@
|
|||||||
# Compass / NHS
|
# Compass / NHS
|
||||||
|
|
||||||
A school service for Harry and Finn: **Compass** for the daily schedule and
|
A school service for Harry and Finn: **Compass** gives the daily schedule, the
|
||||||
notices, **Teams** for homework and assignments. It stores what is due per child,
|
homework and the notices. It stores what is due per child, puts overdue work
|
||||||
puts overdue work first, and tells the family before work is late.
|
first, and tells the family before work is late.
|
||||||
|
|
||||||
> Build spec: [`AGENTS.md`](AGENTS.md). This repository is at **Phase 8** (the
|
> **Teams (FR-15) is DEFERRED.** No Teams section is scanned. Compass answers
|
||||||
> Hub API). See §11 of the spec for the phase order.
|
> what is due and overdue. This is a known gap, not a defect.
|
||||||
|
|
||||||
|
> Build spec: [`AGENTS.md`](AGENTS.md). This repository is at **Phase 9**
|
||||||
|
> (documentation). See §11 of the spec for the phase order.
|
||||||
|
|
||||||
|
## Status — Phase 9
|
||||||
|
|
||||||
|
Phase 9 publishes the project documentation across five places.
|
||||||
|
|
||||||
|
| Layer | Artifact | Location |
|
||||||
|
|---|---|---|
|
||||||
|
| Technical README | This file | the Gitea repo |
|
||||||
|
| Architecture source | [`docs/architecture.mmd`](docs/architecture.mmd) | repo |
|
||||||
|
| Rendered diagram | [`docs/diagrams/architecture.png`](docs/diagrams/architecture.png) | repo |
|
||||||
|
| Interactive map | [`docs/architecture-map.html`](docs/architecture-map.html) | <https://maps.lab.audasmedia.com.au/compass_teams_nhs/docs/> |
|
||||||
|
| Human note | Obsidian `010 inbox/` on `.27` | filed by the sorter |
|
||||||
|
| AI context | [`docs/outline/`](docs/outline/) | Outline collection *Compass / NHS (School)* |
|
||||||
|
| Tasks | [`docs/vikunja-tasks.json`](docs/vikunja-tasks.json) | Vikunja project id 21 |
|
||||||
|
|
||||||
|
The daily trigger is **06:00** in `Australia/Melbourne` (n8n). The briefs go out
|
||||||
|
on **Email and WhatsApp** through Apprise on `.35:8210`: one brief per boy, plus
|
||||||
|
one combined brief to Jo and Sam. Important school events go to the Home
|
||||||
|
Assistant Local Calendar `calendar.school`. The Hub reads items through the
|
||||||
|
read-only `POST /ask` contract.
|
||||||
|
|
||||||
|
The accepted scraper is **patchright** (headless Playwright), not the
|
||||||
|
pi-browser-harness: the harness cannot pass the Cloudflare managed challenge on
|
||||||
|
the Compass login POST. The Compass parent login required **no** two-factor code
|
||||||
|
during the Phase 2 proof.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
N8N["n8n on .13<br/>06:00 daily trigger"]
|
||||||
|
SAM["Sam<br/>browser dashboard"]
|
||||||
|
CADDY["Caddy on .35<br/>school.lab.audasmedia.com.au"]
|
||||||
|
SVC["Compass / NHS service<br/>Go + SQLite :8792"]
|
||||||
|
PATCH["patchright scraper<br/>headless Playwright"]
|
||||||
|
COMPASS["Compass NHS<br/>read-only login + pages"]
|
||||||
|
OMNI["OmniRoute<br/>importance classification"]
|
||||||
|
HA["Home Assistant calendar<br/>calendar.school"]
|
||||||
|
APPRISE["Apprise on .35:8210<br/>Email + WhatsApp"]
|
||||||
|
BOYS["Harry and Finn<br/>per-boy morning brief"]
|
||||||
|
PARENTS["Jo and Sam<br/>combined morning brief"]
|
||||||
|
HUB["Hub :8788<br/>read-only /ask"]
|
||||||
|
|
||||||
|
N8N -->|"scripts/morning-brief.sh"| SVC
|
||||||
|
SAM -->|HTTPS| CADDY
|
||||||
|
CADDY --> SVC
|
||||||
|
SVC -->|scan| PATCH
|
||||||
|
PATCH -->|"login + fetch pages"| COMPASS
|
||||||
|
SVC -.->|"important only"| OMNI
|
||||||
|
SVC -->|"create event"| HA
|
||||||
|
SVC -->|briefs| APPRISE
|
||||||
|
APPRISE -->|"email + WhatsApp"| BOYS
|
||||||
|
APPRISE -->|"email + WhatsApp"| PARENTS
|
||||||
|
HUB -->|"Bearer /ask"| SVC
|
||||||
|
```
|
||||||
|
|
||||||
|
*Interactive map:* <https://maps.lab.audasmedia.com.au/compass_teams_nhs/docs/>
|
||||||
|
|
||||||
## Status — Phase 8
|
## Status — Phase 8
|
||||||
|
|
||||||
@@ -126,7 +186,7 @@ Built and deployed on `.13`:
|
|||||||
- Its own container and port **8792**, behind Caddy at
|
- Its own container and port **8792**, behind Caddy at
|
||||||
`school.lab.audasmedia.com.au`.
|
`school.lab.audasmedia.com.au`.
|
||||||
|
|
||||||
Not built yet (later phases): Teams (FR-15) and the Phase 9 documentation set.
|
Not built yet: Teams (FR-15). It is deferred on purpose.
|
||||||
The service itself does **not** scan the school; n8n triggers the scan.
|
The service itself does **not** scan the school; n8n triggers the scan.
|
||||||
|
|
||||||
### Fail loudly (FR-12)
|
### Fail loudly (FR-12)
|
||||||
@@ -155,6 +215,7 @@ internal/web/ HTTP handlers, templates, static assets
|
|||||||
deploy/ Caddy snippet, the n8n workflow, deployment notes, verify script
|
deploy/ Caddy snippet, the n8n workflow, deployment notes, verify script
|
||||||
scripts/ Compass fetchers, the parse and morning-brief wrappers, the calendar push
|
scripts/ Compass fetchers, the parse and morning-brief wrappers, the calendar push
|
||||||
environment.d/ env example (the real file is gitignored)
|
environment.d/ env example (the real file is gitignored)
|
||||||
|
docs/ phase notes, the architecture source and map, the Outline set, Vikunja tasks
|
||||||
```
|
```
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|||||||
326
docs/archify/architecture.architecture.json
Normal file
326
docs/archify/architecture.architecture.json
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"diagram_type": "architecture",
|
||||||
|
"meta": {
|
||||||
|
"title": "Compass / NHS school service",
|
||||||
|
"output": "architecture-map.html",
|
||||||
|
"quality_profile": "showcase",
|
||||||
|
"viewBox": [
|
||||||
|
1180,
|
||||||
|
610
|
||||||
|
],
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"id": "morning-run",
|
||||||
|
"label": "Morning run",
|
||||||
|
"focus": [
|
||||||
|
"n8n",
|
||||||
|
"service",
|
||||||
|
"patchright",
|
||||||
|
"compass",
|
||||||
|
"apprise",
|
||||||
|
"family"
|
||||||
|
],
|
||||||
|
"note": "n8n runs the scan and the sends at 06:00 every day."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "read-path",
|
||||||
|
"label": "Read path",
|
||||||
|
"focus": [
|
||||||
|
"sam",
|
||||||
|
"caddy",
|
||||||
|
"service",
|
||||||
|
"hub"
|
||||||
|
],
|
||||||
|
"note": "Sam and the Hub both read the same Go service over HTTP."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "writes",
|
||||||
|
"label": "Writes outside the school",
|
||||||
|
"focus": [
|
||||||
|
"service",
|
||||||
|
"ha",
|
||||||
|
"apprise",
|
||||||
|
"omni"
|
||||||
|
],
|
||||||
|
"note": "The calendar write and the briefs are the only writes."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"id": "n8n",
|
||||||
|
"type": "external",
|
||||||
|
"label": "n8n (.13)",
|
||||||
|
"sublabel": "06:00 daily trigger",
|
||||||
|
"pos": [
|
||||||
|
40,
|
||||||
|
200
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
150,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sam",
|
||||||
|
"type": "external",
|
||||||
|
"label": "Sam",
|
||||||
|
"sublabel": "browser dashboard",
|
||||||
|
"pos": [
|
||||||
|
40,
|
||||||
|
430
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
150,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "caddy",
|
||||||
|
"type": "security",
|
||||||
|
"label": "Caddy (.35)",
|
||||||
|
"sublabel": "session login",
|
||||||
|
"pos": [
|
||||||
|
250,
|
||||||
|
315
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
150,
|
||||||
|
60
|
||||||
|
],
|
||||||
|
"tag": "school.lab"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "service",
|
||||||
|
"type": "backend",
|
||||||
|
"label": "Compass / NHS service",
|
||||||
|
"sublabel": "Go + SQLite :8792",
|
||||||
|
"pos": [
|
||||||
|
470,
|
||||||
|
310
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
165,
|
||||||
|
70
|
||||||
|
],
|
||||||
|
"tag": "source of truth"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "patchright",
|
||||||
|
"type": "backend",
|
||||||
|
"label": "patchright scraper",
|
||||||
|
"sublabel": "headless Playwright",
|
||||||
|
"pos": [
|
||||||
|
700,
|
||||||
|
315
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
150,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "compass",
|
||||||
|
"type": "external",
|
||||||
|
"label": "Compass NHS",
|
||||||
|
"sublabel": "read-only login + pages",
|
||||||
|
"pos": [
|
||||||
|
920,
|
||||||
|
315
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
160,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "omni",
|
||||||
|
"type": "cloud",
|
||||||
|
"label": "OmniRoute",
|
||||||
|
"sublabel": "importance only",
|
||||||
|
"pos": [
|
||||||
|
470,
|
||||||
|
120
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
160,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ha",
|
||||||
|
"type": "cloud",
|
||||||
|
"label": "Home Assistant",
|
||||||
|
"sublabel": "calendar.school",
|
||||||
|
"pos": [
|
||||||
|
700,
|
||||||
|
120
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
160,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hub",
|
||||||
|
"type": "external",
|
||||||
|
"label": "Hub",
|
||||||
|
"sublabel": "read-only /ask :8788",
|
||||||
|
"pos": [
|
||||||
|
250,
|
||||||
|
500
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
150,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "apprise",
|
||||||
|
"type": "cloud",
|
||||||
|
"label": "Apprise (.35)",
|
||||||
|
"sublabel": "Email + WhatsApp :8210",
|
||||||
|
"pos": [
|
||||||
|
470,
|
||||||
|
500
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
160,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "family",
|
||||||
|
"type": "external",
|
||||||
|
"label": "Family briefs",
|
||||||
|
"sublabel": "per-boy + combined",
|
||||||
|
"pos": [
|
||||||
|
790,
|
||||||
|
500
|
||||||
|
],
|
||||||
|
"size": [
|
||||||
|
160,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"boundaries": [
|
||||||
|
{
|
||||||
|
"kind": "region",
|
||||||
|
"label": "Home lab - Docker on .13, Caddy and Apprise on .35",
|
||||||
|
"wraps": [
|
||||||
|
"caddy",
|
||||||
|
"service",
|
||||||
|
"patchright",
|
||||||
|
"omni",
|
||||||
|
"ha",
|
||||||
|
"apprise",
|
||||||
|
"hub"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
{
|
||||||
|
"id": "n8n-to-service",
|
||||||
|
"from": "n8n",
|
||||||
|
"to": "service",
|
||||||
|
"label": "scripts/morning-brief.sh",
|
||||||
|
"variant": "dashed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sam-to-caddy",
|
||||||
|
"from": "sam",
|
||||||
|
"to": "caddy",
|
||||||
|
"label": "HTTPS",
|
||||||
|
"variant": "emphasis",
|
||||||
|
"fromSide": "top",
|
||||||
|
"toSide": "bottom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "caddy-to-service",
|
||||||
|
"from": "caddy",
|
||||||
|
"to": "service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "service-to-patchright",
|
||||||
|
"from": "service",
|
||||||
|
"to": "patchright",
|
||||||
|
"label": "scan",
|
||||||
|
"labelDy": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "patchright-to-compass",
|
||||||
|
"from": "patchright",
|
||||||
|
"to": "compass",
|
||||||
|
"label": "login + fetch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "service-to-omni",
|
||||||
|
"from": "service",
|
||||||
|
"to": "omni",
|
||||||
|
"label": "importance",
|
||||||
|
"variant": "dashed",
|
||||||
|
"fromSide": "top",
|
||||||
|
"toSide": "bottom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "service-to-ha",
|
||||||
|
"from": "service",
|
||||||
|
"to": "ha",
|
||||||
|
"label": "create event",
|
||||||
|
"fromSide": "right",
|
||||||
|
"toSide": "left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "service-to-apprise",
|
||||||
|
"from": "service",
|
||||||
|
"to": "apprise",
|
||||||
|
"label": "briefs",
|
||||||
|
"fromSide": "bottom",
|
||||||
|
"toSide": "top"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "apprise-to-family",
|
||||||
|
"from": "apprise",
|
||||||
|
"to": "family",
|
||||||
|
"label": "email + WhatsApp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hub-to-service",
|
||||||
|
"from": "hub",
|
||||||
|
"to": "service",
|
||||||
|
"label": "Bearer /ask",
|
||||||
|
"variant": "dashed"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cards": [
|
||||||
|
{
|
||||||
|
"dot": "cyan",
|
||||||
|
"title": "Compass / NHS service",
|
||||||
|
"items": [
|
||||||
|
"Go and SQLite are the source of truth",
|
||||||
|
"Go stores items, scans and the send log",
|
||||||
|
"Serves the dashboard, the Hub API and the briefs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dot": "amber",
|
||||||
|
"title": "Read-only against the school",
|
||||||
|
"items": [
|
||||||
|
"patchright logs in and fetches pages only",
|
||||||
|
"The Cloudflare login POST needs a real browser",
|
||||||
|
"No post, message or submit ever runs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dot": "rose",
|
||||||
|
"title": "Deferred: Teams (FR-15)",
|
||||||
|
"items": [
|
||||||
|
"Teams homework is DEFERRED and not built",
|
||||||
|
"Compass answers what is due and overdue",
|
||||||
|
"No Teams section is scanned"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13698
docs/architecture-map.html
Normal file
13698
docs/architecture-map.html
Normal file
File diff suppressed because it is too large
Load Diff
30
docs/architecture.mmd
Normal file
30
docs/architecture.mmd
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
flowchart LR
|
||||||
|
N8N["n8n on .13<br/>06:00 daily trigger"]
|
||||||
|
SAM["Sam<br/>browser dashboard"]
|
||||||
|
CADDY["Caddy on .35<br/>school.lab.audasmedia.com.au"]
|
||||||
|
SVC["Compass / NHS service<br/>Go + SQLite :8792"]
|
||||||
|
PATCH["patchright scraper<br/>headless Playwright"]
|
||||||
|
COMPASS["Compass NHS<br/>read-only login + pages"]
|
||||||
|
OMNI["OmniRoute<br/>importance classification"]
|
||||||
|
HA["Home Assistant calendar<br/>calendar.school"]
|
||||||
|
APPRISE["Apprise on .35:8210<br/>Email + WhatsApp"]
|
||||||
|
BOYS["Harry and Finn<br/>per-boy morning brief"]
|
||||||
|
PARENTS["Jo and Sam<br/>combined morning brief"]
|
||||||
|
HUB["Hub :8788<br/>read-only /ask"]
|
||||||
|
TEAMS["Teams homework<br/>FR-15 DEFERRED"]
|
||||||
|
|
||||||
|
N8N -->|"scripts/morning-brief.sh"| SVC
|
||||||
|
SAM -->|HTTPS| CADDY
|
||||||
|
CADDY --> SVC
|
||||||
|
SVC -->|scan| PATCH
|
||||||
|
PATCH -->|"login + fetch pages"| COMPASS
|
||||||
|
SVC -.->|"important only"| OMNI
|
||||||
|
SVC -->|"create event"| HA
|
||||||
|
SVC -->|briefs| APPRISE
|
||||||
|
APPRISE -->|"email + WhatsApp"| BOYS
|
||||||
|
APPRISE -->|"email + WhatsApp"| PARENTS
|
||||||
|
HUB -->|"Bearer /ask"| SVC
|
||||||
|
TEAMS -.->|"not built"| SVC
|
||||||
|
|
||||||
|
click SVC "https://maps.lab.audasmedia.com.au/compass_teams_nhs/docs/" "View Archify map"
|
||||||
|
click COMPASS "https://maps.lab.audasmedia.com.au/compass_teams_nhs/docs/" "View Archify map"
|
||||||
BIN
docs/diagrams/architecture.png
Normal file
BIN
docs/diagrams/architecture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
43
docs/outline/00-index.md
Normal file
43
docs/outline/00-index.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Compass NHS — AI Document Index
|
||||||
|
|
||||||
|
> **Target**: AI Agents
|
||||||
|
> **Writing Style**: ASD-STE100 Simplified Technical English.
|
||||||
|
|
||||||
|
## Document Catalog (Inject Specific Document)
|
||||||
|
|
||||||
|
Read this index first. Inject the exact file you need. Do not search every file.
|
||||||
|
|
||||||
|
| Document Title | Path | Scope |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **System Overview** | `docs/outline/01-system-overview.md` | States the purpose, the main parts and the live services. |
|
||||||
|
| **Configuration and Environment** | `docs/outline/02-config-and-env.md` | Lists variables, paths, ports and secrets. |
|
||||||
|
| **Code Map** | `docs/outline/03-code-map.md` | Shows the folder layout, entry points and dependencies. |
|
||||||
|
| **Agent Runbook** | `docs/outline/04-agent-runbook.md` | Gives the commands to build, test, deploy and restart. |
|
||||||
|
|
||||||
|
## System Summary
|
||||||
|
|
||||||
|
This project reads the school systems for two children, Harry and Finn. It stores
|
||||||
|
what is due, finds overdue work, writes important events to the family calendar,
|
||||||
|
and sends morning briefs.
|
||||||
|
|
||||||
|
The service is one Go binary with SQLite. It runs in its own container on `.13`,
|
||||||
|
port 8792, behind Caddy on `.35` at `school.lab.audasmedia.com.au`.
|
||||||
|
|
||||||
|
**Teams (FR-15) is DEFERRED.** The project does not scan Teams. Compass answers
|
||||||
|
what is due and overdue.
|
||||||
|
|
||||||
|
## Core Configuration
|
||||||
|
|
||||||
|
- **Env File**: `environment.d/school-service.env` (gitignored)
|
||||||
|
- **Env Example**: `environment.d/school-service.env.example`
|
||||||
|
- **Config Code**: `internal/config/`
|
||||||
|
- **Active Ports**: `8792` (service), `8210` (Apprise on `.35`), `8788` (Hub)
|
||||||
|
- **Database**: SQLite in the Docker volume `compass-teams-nhs-data`
|
||||||
|
|
||||||
|
## Agent Restart Steps
|
||||||
|
|
||||||
|
1. Read the Document Catalog above. Inject only the required files.
|
||||||
|
2. Check the checkout: `git status`.
|
||||||
|
3. Read the recent history: `git log -n 5 --oneline`.
|
||||||
|
4. Run the checks: `make test` and `make verify-phase8`.
|
||||||
|
5. Read the current state: `curl -s http://127.0.0.1:8792/health`.
|
||||||
70
docs/outline/01-system-overview.md
Normal file
70
docs/outline/01-system-overview.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Compass NHS — System Overview
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
The service reads the school systems for Harry and Finn. It answers three
|
||||||
|
questions: what is due, what is overdue, and what important events are coming.
|
||||||
|
|
||||||
|
Sam sees both children on one page. The family receives the briefs before work is
|
||||||
|
late. Nobody logs into Compass each morning.
|
||||||
|
|
||||||
|
## Users
|
||||||
|
|
||||||
|
| User | Role |
|
||||||
|
| :--- | :--- |
|
||||||
|
| Sam | Owner. Uses the dashboard. |
|
||||||
|
| Jo, Harry, Finn | Read-only. Receive the briefs. |
|
||||||
|
|
||||||
|
## Main Parts
|
||||||
|
|
||||||
|
| Part | Function |
|
||||||
|
| :--- | :--- |
|
||||||
|
| Go service | Serves the dashboard and the Hub API. Holds the state. |
|
||||||
|
| SQLite store | Keeps children, items, scans, notifications and calendar pushes. |
|
||||||
|
| patchright scraper | Logs into Compass and fetches the pages. |
|
||||||
|
| n8n workflow | Runs the scan and the sends at 06:00 daily. |
|
||||||
|
| Apprise | Sends the briefs on Email and WhatsApp. |
|
||||||
|
| Home Assistant | Holds the calendar `calendar.school`. |
|
||||||
|
| Hub | Calls the read-only `/ask` endpoint. |
|
||||||
|
|
||||||
|
## The Data Flow
|
||||||
|
|
||||||
|
1. n8n triggers `scripts/morning-brief.sh` at 06:00.
|
||||||
|
2. The script scans Compass for each child and stores the raw pages.
|
||||||
|
3. The parser turns the pages into items: tasks, messages and events.
|
||||||
|
4. The classifier marks important events. OmniRoute helps only here.
|
||||||
|
5. The brief builder makes one brief per boy and one combined brief.
|
||||||
|
6. Apprise sends the briefs on Email and WhatsApp.
|
||||||
|
7. The calendar sync writes today's and future important events to Home Assistant.
|
||||||
|
|
||||||
|
## Read-Only Rule
|
||||||
|
|
||||||
|
The service never writes to the school. It logs in and fetches pages only. The
|
||||||
|
scraper never posts, messages or submits anything.
|
||||||
|
|
||||||
|
## Fail Loudly
|
||||||
|
|
||||||
|
A missing scan or a failed scan is never an empty day. The dashboard, the briefs
|
||||||
|
and `/ask` each state the real scan state. A failed scan says so in plain words.
|
||||||
|
|
||||||
|
## Teams
|
||||||
|
|
||||||
|
**Teams (FR-15) is DEFERRED.** The project does not scan Teams. This is a known
|
||||||
|
gap, not a defect. Compass gives the schedule, the tasks and the due dates.
|
||||||
|
|
||||||
|
## Live Targets
|
||||||
|
|
||||||
|
| System | Address |
|
||||||
|
| :--- | :--- |
|
||||||
|
| Dashboard | `https://school.lab.audasmedia.com.au` |
|
||||||
|
| Service container | `.13`, port 8792 |
|
||||||
|
| Health | `http://127.0.0.1:8792/health` |
|
||||||
|
| Apprise API | `.35`, port 8210 |
|
||||||
|
| Hub inbound | `http://192.168.20.13:8788/ask` |
|
||||||
|
|
||||||
|
## Repository
|
||||||
|
|
||||||
|
- Git: `ssh://git@gitea.lab.audasmedia.com.au:2222/sam/compass_teams_nhs.git`
|
||||||
|
- Branch: `main`
|
||||||
|
- Spec: `AGENTS.md`
|
||||||
|
- Interactive map: `https://maps.lab.audasmedia.com.au/compass_teams_nhs/docs/`
|
||||||
101
docs/outline/02-config-and-env.md
Normal file
101
docs/outline/02-config-and-env.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Compass NHS — Configuration and Environment
|
||||||
|
|
||||||
|
## Rules for Agents
|
||||||
|
|
||||||
|
1. Never write a credential, token, cookie or session value into a document.
|
||||||
|
2. Keep every secret in `environment.d/school-service.env`. The file is gitignored.
|
||||||
|
3. Copy the shape from `environment.d/school-service.env.example`. Never copy a value.
|
||||||
|
4. Docker Compose interpolates `$VAR` inside `env_file`. Escape each bcrypt `$` as `$$`.
|
||||||
|
|
||||||
|
## Paths
|
||||||
|
|
||||||
|
| Item | Path |
|
||||||
|
| :--- | :--- |
|
||||||
|
| Real env file | `environment.d/school-service.env` |
|
||||||
|
| Env example | `environment.d/school-service.env.example` |
|
||||||
|
| Config code | `internal/config/` |
|
||||||
|
| SQLite schema | `internal/store/migrations/` |
|
||||||
|
| Service binary | `/app/school` inside the container |
|
||||||
|
| Database | `/data/school.db` inside the container |
|
||||||
|
| Docker volume | `compass-teams-nhs-data` |
|
||||||
|
|
||||||
|
## Ports
|
||||||
|
|
||||||
|
| Port | System | Host |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| 8792 | This service | `.13` |
|
||||||
|
| 8210 | Apprise API | `.35` |
|
||||||
|
| 8788 | Hub inbound API | `.13` |
|
||||||
|
| 8123 | Home Assistant | `.30` |
|
||||||
|
| 5678 | n8n | `.13` |
|
||||||
|
|
||||||
|
Caddy on `.35` publishes `school.lab.audasmedia.com.au` and proxies to
|
||||||
|
`192.168.20.13:8792`.
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
### Service
|
||||||
|
|
||||||
|
| Variable | Meaning |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `SCHOOL_ADDR` | Listen address. Default `:8792`. |
|
||||||
|
| `SCHOOL_DB_PATH` | SQLite path. Container value `/data/school.db`. |
|
||||||
|
| `SCHOOL_SESSION_SECRET` | Signs the session cookie. Secret. Minimum 16 characters. |
|
||||||
|
| `SCHOOL_ADMIN_USERNAME` | The single dashboard login. |
|
||||||
|
| `SCHOOL_ADMIN_PASSWORD_HASH` | Bcrypt hash. Prefer this over the plaintext. |
|
||||||
|
| `SCHOOL_ADMIN_PASSWORD` | Plaintext fallback. |
|
||||||
|
| `SCHOOL_COOKIE_SECURE` | `true` behind HTTPS. |
|
||||||
|
| `SCHOOL_SESSION_TTL` | Session life. Default `12h`. |
|
||||||
|
| `SCHOOL_TIMEZONE` | Family timezone for due dates. |
|
||||||
|
| `SCHOOL_LOG_LEVEL` | Log level. |
|
||||||
|
| `SCHOOL_APP_ENV` | Environment name. |
|
||||||
|
|
||||||
|
### Home Assistant Calendar
|
||||||
|
|
||||||
|
| Variable | Meaning |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `SCHOOL_HA_URL` | Home Assistant base URL. |
|
||||||
|
| `SCHOOL_HA_TOKEN` | Long-lived access token. Secret. |
|
||||||
|
| `SCHOOL_HA_CALENDAR_ENTITY` | Calendar entity. Default `calendar.school`. |
|
||||||
|
| `SCHOOL_HA_INSECURE` | Skips TLS verification. Use only for the internal `.lab` URL. |
|
||||||
|
| `SCHOOL_HA_TIMEOUT` | Per-request timeout. Default `20s`. |
|
||||||
|
|
||||||
|
### Apprise
|
||||||
|
|
||||||
|
| Variable | Meaning |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `SCHOOL_APPRISE_URL` | Apprise API base. Default `http://192.168.20.35:8210`. |
|
||||||
|
| `SCHOOL_APPRISE_KEY` | Stored configuration key. Secret. |
|
||||||
|
| `SCHOOL_APPRISE_TOKEN` | Optional bearer token. |
|
||||||
|
| `SCHOOL_APPRISE_TIMEOUT` | Per-request timeout. Default `15s`. |
|
||||||
|
| `SCHOOL_APPRISE_CHANNELS` | `email,whatsapp`. |
|
||||||
|
| `SCHOOL_APPRISE_TAGS` | Recipients as `Name=tag-base`. |
|
||||||
|
| `SCHOOL_APPRISE_PARENTS` | Who receives the combined brief. `Jo,Sam`. |
|
||||||
|
| `SCHOOL_NOTIFY_TOKEN` | Bearer token for `POST /notify/morning`. Secret. |
|
||||||
|
|
||||||
|
### Hub API
|
||||||
|
|
||||||
|
| Variable | Meaning |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `SCHOOL_HUB_TOKEN` | Shared bearer token. Secret. Empty turns the Hub endpoints off with `503`. |
|
||||||
|
|
||||||
|
The Hub holds the same token as `HUB_PROJECT_COMPASS_NHS_TOKEN`. The Hub reaches
|
||||||
|
this service through `HUB_PROJECTS=compass_nhs=http://192.168.20.13:8792`.
|
||||||
|
|
||||||
|
## Secrets Outside This Repo
|
||||||
|
|
||||||
|
| Secret | Location |
|
||||||
|
| :--- | :--- |
|
||||||
|
| Apprise destinations (address, number, SMTP) | Apprise stored configuration on `.35` |
|
||||||
|
| Hub token copy | `~/.config/environment.d/10-secrets.conf` on `.13` |
|
||||||
|
| Compass account and password | Paperclip secret or the env file |
|
||||||
|
| n8n trigger SSH key | `/home/sam/.ssh/id_ed25519_school_scan` |
|
||||||
|
|
||||||
|
The Apprise destinations stay outside this repo on purpose. This service names a
|
||||||
|
tag such as `harry_email` only.
|
||||||
|
|
||||||
|
## Environment Access for an Agent
|
||||||
|
|
||||||
|
The service does not scan the school. n8n runs `scripts/morning-brief.sh`
|
||||||
|
through a restricted SSH key. The key has a forced command and `restrict`. It
|
||||||
|
cannot open a shell.
|
||||||
82
docs/outline/03-code-map.md
Normal file
82
docs/outline/03-code-map.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Compass NHS — Code Map
|
||||||
|
|
||||||
|
## Module
|
||||||
|
|
||||||
|
`gitea.lab.audasmedia.com.au/sam/compass_teams_nhs`
|
||||||
|
|
||||||
|
Language: Go 1.25. Database: SQLite through `modernc.org/sqlite`. Passwords use
|
||||||
|
`golang.org/x/crypto/bcrypt`. There is no other direct dependency.
|
||||||
|
|
||||||
|
## Entry Point
|
||||||
|
|
||||||
|
`cmd/school/main.go` holds the entry point. The first argument selects a
|
||||||
|
subcommand. With no argument it starts the HTTP service.
|
||||||
|
|
||||||
|
| Subcommand | Function |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `run` (default) | Starts the HTTP service, migrates the schema and seeds the children. |
|
||||||
|
| `parse` | Turns stored raw pages into items. |
|
||||||
|
| `session` | Shows or clears the harness session per child. |
|
||||||
|
| `scan` | Fetches and parses one child or both. |
|
||||||
|
| `items` | Reads or deletes items. |
|
||||||
|
| `brief` | Builds a brief. |
|
||||||
|
| `notify` | Sends briefs and shows the send status. |
|
||||||
|
| `calendar` | Pushes events to Home Assistant. |
|
||||||
|
| `hash-password` | Prints a bcrypt hash. |
|
||||||
|
| `gen-secret` | Prints a fresh secret. |
|
||||||
|
| `version` | Prints the version. |
|
||||||
|
|
||||||
|
## Folders
|
||||||
|
|
||||||
|
| Folder | Function |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `cmd/school/` | One file per subcommand plus `main.go`. |
|
||||||
|
| `internal/web/` | HTTP server, handlers, HTMX templates, static assets. Holds `hub_api.go`. |
|
||||||
|
| `internal/auth/` | Session login with signed cookies and CSRF. |
|
||||||
|
| `internal/store/` | SQLite open, migrations and queries. |
|
||||||
|
| `internal/store/migrations/` | `001_init`, `002_items_parse`, `003_calendar`, `004_notifications_send`. |
|
||||||
|
| `internal/parse/` | HTML to items. Readings for tasks, news, calendar and importance. |
|
||||||
|
| `internal/llm/` | OmniRoute client for importance classification only. |
|
||||||
|
| `internal/dashboard/` | Builds the per-child buckets. Classifies overdue and due-today. |
|
||||||
|
| `internal/brief/` | Builds and delivers the morning briefs. |
|
||||||
|
| `internal/apprise/` | Apprise API client. Uses tags and redacts errors. |
|
||||||
|
| `internal/calendar/` | Home Assistant client and the idempotent sync. |
|
||||||
|
| `internal/hub/` | The Hub contract: capabilities and read-only answers. |
|
||||||
|
| `internal/config/` | Reads the environment. |
|
||||||
|
| `deploy/` | Caddy snippet, n8n workflow, import and key scripts, verify scripts. |
|
||||||
|
| `scripts/` | Scrapers, parse and morning-brief wrappers, calendar push. |
|
||||||
|
|
||||||
|
## The Read-Only Boundary
|
||||||
|
|
||||||
|
`internal/hub` depends on a `Reader` interface. The interface has three read
|
||||||
|
methods and no write method. Test `TestHubAskReadsOnly` proves that `/ask` does
|
||||||
|
not change the `items`, `scans` or `notifications` row counts.
|
||||||
|
|
||||||
|
## The Scraper
|
||||||
|
|
||||||
|
`scripts/compass-fetch-patchright.mjs` logs into Compass with patchright. The
|
||||||
|
login POST meets a Cloudflare managed challenge. patchright, a headless
|
||||||
|
Playwright build, passes the challenge. The pi-browser-harness does not.
|
||||||
|
|
||||||
|
The Compass parent login required no two-factor code during the Phase 2 proof.
|
||||||
|
|
||||||
|
## Database Tables
|
||||||
|
|
||||||
|
| Table | Holds |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `children` | id, name, year, Compass id. |
|
||||||
|
| `items` | Child, type, subject, teacher, title, body, due date, URL. |
|
||||||
|
| `scans` | Start time, child, pages fetched, items found, error. |
|
||||||
|
| `notifications` | Recipient, scope, summary, channel, status, sent time. |
|
||||||
|
| `session` | Harness session state per child. |
|
||||||
|
| `calendar_pushes` | Calendar write attempts, result and error. |
|
||||||
|
|
||||||
|
The raw Compass pages are not in the database. The fetcher writes them to
|
||||||
|
`var/raw/<child>/<timestamp>/` on the host. Each folder holds the HTML, a meta
|
||||||
|
file per page, and a manifest with a SHA-256 and a byte count per page.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Run `make test`. The suite uses standard Go tests. The tests cover the store,
|
||||||
|
the parser, the dashboard buckets, the brief, the Apprise client, the calendar
|
||||||
|
sync, the Hub API and the read-only rule.
|
||||||
98
docs/outline/04-agent-runbook.md
Normal file
98
docs/outline/04-agent-runbook.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Compass NHS — Agent Runbook
|
||||||
|
|
||||||
|
## First Checks
|
||||||
|
|
||||||
|
Run these commands before any change.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git -C /home/sam/paseo/projects/compass_teams_nhs status
|
||||||
|
git -C /home/sam/paseo/projects/compass_teams_nhs log -n 5 --oneline
|
||||||
|
curl -s http://127.0.0.1:8792/health
|
||||||
|
```
|
||||||
|
|
||||||
|
`/health` must return `"ok": true`. It also shows the scan state and
|
||||||
|
`hub_api`. A red scan state is real data, not a test failure.
|
||||||
|
|
||||||
|
## Build and Test
|
||||||
|
|
||||||
|
The host `.13` has no Go toolchain. Build and test inside `golang:1.25-alpine`
|
||||||
|
through `make`. Bound every Docker command.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test # go test ./... in golang:1.25-alpine
|
||||||
|
make vet # go vet ./...
|
||||||
|
make build # build the container image
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deploy and Restart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make up # docker compose up -d --build
|
||||||
|
make ps # container state
|
||||||
|
make logs # follow the service log
|
||||||
|
make restart # restart the service only
|
||||||
|
```
|
||||||
|
|
||||||
|
The container is `compass-teams-nhs`. The image tag is
|
||||||
|
`compass-teams-nhs/school:0.6.1-phase8`.
|
||||||
|
|
||||||
|
## Operate
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make scan # fetch and parse both children
|
||||||
|
make scan SCAN_CHILD=Finn # one child
|
||||||
|
make parse CHILD=Harry RUN=latest # parse stored pages
|
||||||
|
make preview # print the briefs, send nothing
|
||||||
|
make notify # send the briefs from the last scan
|
||||||
|
make morning-brief # scan, then send (the n8n job)
|
||||||
|
make calendar CALENDAR_ARGS='push --dry-run'
|
||||||
|
make calendar CALENDAR_ARGS='status'
|
||||||
|
```
|
||||||
|
|
||||||
|
The send log:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec compass-teams-nhs /app/school notify status
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make verify-phase1 # /health, the login, the Caddy route
|
||||||
|
make verify-phase8 # /capabilities, /ask, /notify auth
|
||||||
|
make verify-phase8 ARGS=--send # also deliver one real /notify message
|
||||||
|
```
|
||||||
|
|
||||||
|
## The n8n Schedule
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make n8n-key # dry run: print the key plan
|
||||||
|
make n8n-key APPLY=1 # create the restricted key
|
||||||
|
make n8n-import # dry run: print the import plan
|
||||||
|
make n8n-import APPLY=1 # import, publish and restart n8n
|
||||||
|
docker exec n8n n8n list:workflow --active=true
|
||||||
|
```
|
||||||
|
|
||||||
|
The workflow runs at 06:00 in `Australia/Melbourne`. The trigger key has a forced
|
||||||
|
command and `restrict`. It can run one command only.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Publish the maps and diagrams to the LAN maps site.
|
||||||
|
~/.agents/skills/project-docs/scripts/publish-maps.sh \
|
||||||
|
/home/sam/paseo/projects/compass_teams_nhs
|
||||||
|
|
||||||
|
# Force one Obsidian sorter pass on .27.
|
||||||
|
ssh sam@192.168.20.27 'obsidian-sort watch --once'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. **Read-only against the school.** Never post, message or submit.
|
||||||
|
2. **Fail loudly.** Never report "nothing due" when a scan failed.
|
||||||
|
3. **Never commit a secret.** Credentials, tokens and cookies stay in
|
||||||
|
`environment.d/school-service.env` or a Paperclip secret.
|
||||||
|
4. **Never write personal page content into a document.**
|
||||||
|
5. Use one commit and one push for a change. End the message with
|
||||||
|
`Co-Authored-By: Paperclip <noreply@paperclip.ing>`.
|
||||||
19
docs/vikunja-tasks.json
Normal file
19
docs/vikunja-tasks.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"title": "Compass / NHS (School) - Human Task Overview",
|
||||||
|
"description": "High-level task checklist for human review. The live board is the Vikunja project 'Compass / NHS (School)' (id 21). This file is the repo copy.",
|
||||||
|
"vikunja_project": {
|
||||||
|
"name": "Compass / NHS (School)",
|
||||||
|
"id": 21,
|
||||||
|
"url": "https://vikunja.lab.audasmedia.com.au/projects/21"
|
||||||
|
},
|
||||||
|
"teams_status": "DEFERRED - FR-15 is not built. No Teams section is scanned.",
|
||||||
|
"tasks": [
|
||||||
|
{ "id": 196, "title": "Open the dashboard on a phone and confirm overdue work is first for Harry and Finn", "done": false },
|
||||||
|
{ "id": 197, "title": "Confirm the 06:00 n8n run sends the per-boy and combined briefs on Email and WhatsApp", "done": false },
|
||||||
|
{ "id": 198, "title": "Check the important school events appear in the Home Assistant calendar calendar.school", "done": false },
|
||||||
|
{ "id": 199, "title": "Ask the Hub 'what does Harry have on today at school?' and check the answer", "done": false },
|
||||||
|
{ "id": 200, "title": "Confirm a failed scan shows an error and never an empty day", "done": false },
|
||||||
|
{ "id": 201, "title": "Review the published docs: README, Obsidian note, Archify map and Outline collection", "done": false },
|
||||||
|
{ "id": 202, "title": "Decide the Teams scope (FR-15): build it, or close the project with Teams deferred", "done": false }
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user