Phase 4a: fetch and parse the authoritative Compass learning tasks
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>
This commit is contained in:
@@ -81,8 +81,9 @@ same row. Second run of the same raw pages: `inserted 0, updated 56`.
|
||||
|
||||
- The learning-tasks grid is capped at the most recent 20 tasks
|
||||
(`Compass.attributes.LearningTask_Default_Records_Count = 20`). The parent
|
||||
landing page reports more overdue work than the grid contains, so a later
|
||||
fetch must open the overdue-filtered view. This blocks the dashboard's
|
||||
"overdue" column, not Phase 3.
|
||||
landing page reports more overdue work than the grid contains. **Closed by
|
||||
Phase 4a:** `learning-tasks-all` is now the authoritative per-child list, and
|
||||
the parser fails loudly when it disagrees with the home page. See
|
||||
`docs/phase-4a-overdue-tasks.md`.
|
||||
- Team homework is not parsed here; that is FR-15, a separate source.
|
||||
- No model is used to classify the Teams source yet.
|
||||
|
||||
112
docs/phase-4a-overdue-tasks.md
Normal file
112
docs/phase-4a-overdue-tasks.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# 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|<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.
|
||||
Reference in New Issue
Block a user