# 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=readonly` query.** 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}}`. `total` is 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). ```bash 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|` (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///` and are git-ignored. The service response holds the family's personal data; it is never committed.