The Learning Tasks grid is capped at 20 rows and, on the parent page, mixes both children, so it cannot answer "what is overdue". The parent home page reported 6 overdue tasks for Harry while the stored grid held 1. Fetch the per-child task list (read-only) from POST /Services/LearningTasks.svc/GetAllLearningTasksByUserId, parse it into task rows, and refuse to store it when the overdue count disagrees with the home page summary (FR-12). - fetcher: store learning-tasks-all.json + meta (userId, status, task count) - parse: new learning-tasks-all page, JSON parser, home-page reconciliation - store: drop superseded grid-derived task rows before the upsert - docs: docs/phase-4a-overdue-tasks.md; close the Phase 3 known gap Verified with live runs: Harry 70 tasks / 6 overdue, Finn 42 tasks / 2 overdue, both equal to the home page; a tampered home page stops the parse. Note: the fetcher and store.go files also carry the per-child session work that was already uncommitted in this shared workspace (AUD-108). Co-Authored-By: Paperclip <noreply@paperclip.ing>
4.2 KiB
Phase 4a — the overdue-filtered learning-tasks view
Status: done. The authoritative per-child learning-task list is fetched and parsed, and the parser fails loudly when it disagrees with the home page. See AUD-114.
Problem
The Learning Tasks grid is capped at the most recent 20 records
(Compass.attributes.LearningTask_Default_Records_Count = 20). On the parent
account the same grid mixes both children. The parent landing page reported
6 overdue learning tasks for Harry, but the stored grid held 1 Overdue row
for him. The dashboard's "overdue" column cannot be built from that page.
The endpoint
The grid's ExtJS store reads one service:
POST /Services/LearningTasks.svc/GetAllLearningTasksByUserId
Content-Type: application/json
{ "userId": 27038, "page": 1, "start": 0, "limit": 1000 }
- No
?sessionstate=readonlyquery. Adding it makes the service answer 500. - The request must run inside the logged-in page (
fetch,credentials: include). A request from the browser context's own API client is answered 403 by the Compass request capture. - The response is
{"d":{"data":[...],"total":n}}.totalis the number of tasks for that child across the current and past enrolments.
The service returns every learning task for the child, with the submission status, so the overdue count can be checked against the home page.
Status codes
Compass.enums.LearningTasksSubmissionStatus:
| Code | Label |
|---|---|
| 1 | Pending |
| 2 | Overdue |
| 3 | On Time |
| 4 | Received Late |
LearningTasks_OverdueShowAsPending = false on the parent account, so an
Overdue row stays Overdue.
Due dates
Most tasks carry dueDateTimestamp at the task level (2026-10-09T05:00:00Z,
UTC). A few carry it on the student row instead, so the parser reads both and
falls back to the student value.
Fetcher
scripts/compass-fetch-patchright.mjs stores the response as
learning-tasks-all.json plus learning-tasks-all.meta.json. The meta records
the userId, the HTTP status, the byte count, the SHA-256, and the task count,
so the Go parser can tie the list to the child and to the home page. A non-200
response or a non-JSON body is an explicit fetch error (FR-12).
cd scripts && npm install
COMPASS_USERNAME=... COMPASS_PASSWORD=... \
node scripts/compass-fetch-patchright.mjs --child Harry --user-id 27038
Read-only against the school.
Parser
internal/parse/tasks_json.go turns the JSON into task rows:
- one row per task for the child's
userId(a sibling's rows are skipped), external_ref = learning-task|<taskId>(stable; the task id is unique),- the submission status and due date are written into the item body, so the
offline importance rule marks an overdue task
high.
When a run holds learning-tasks-all, the parser ignores the legacy HTML
grid (learning-tasks and child-profile): the grid is capped and would store
the same task a second time. school parse then deletes the child's old
grid-derived task rows before the upsert.
Fail loudly (FR-12)
parse.Parse reads the child's overdue count from the home page (the
N overdue learning tasks link that carries the child's userId) and compares
it with the number of Overdue rows in the JSON list.
- They must be equal. A disagreement stops the parse, records a failed scan
(
ok = 0), and stores nothing. - A home page with no record of the child also stops the parse; a missing summary is never read as zero.
Verification (2026-10-15)
| Child | Compass id | Tasks | Overdue | Home page |
|---|---|---|---|---|
| Harry | 27038 | 70 | 6 | 6 overdue |
| Finn | 31460 | 42 | 2 | 2 overdue |
school parse --child Harry --classify none→overdue 6 (reconciled against the home page),Harry task 70.school parse --child Finn --classify none→overdue 2 (reconciled against the home page),Finn task 42.- Tampered home page (6 → 5):
parse: Harry: home page reports 5 overdue learning task(s) but the stored learning-task list has 6; refusing to store a partial list (FR-12)and exit 1.
Raw pages stay under var/raw/<child>/<runId>/ and are git-ignored. The service
response holds the family's personal data; it is never committed.