Skip to Content

Student Data Room

The Data Room (WP-DATA-ROOM-BE) is the per-student decision-documentation layer: it assembles a child’s decision-relevant evidence into a reviewable, audit-anchored dossier, and provides the decision-packet shell the tier-movement workflow builds on. It never decides; the rules in the skill-status, monitoring, RTI, and profile engines still decide. The Data Room makes every sensitive decision show its work.

The Data Room never writes a tier. It reads and composes the tier_decision_record; the only tier write path is the RTI engine. Evidence is indexed, never copied: every index row points back to its real source record.

Routes

MethodPathPermission
GET/api/students/{id}/data-roomVIEW_OWN_CLASSES (ClassTeacher) or RUN_BACKOFFICE
GET/api/students/{id}/data-room/evidenceVIEW_OWN_CLASSES (ClassTeacher) or RUN_BACKOFFICE
POST/api/students/{id}/decision-packetsVIEW_OWN_CLASSES (ClassTeacher) or RUN_BACKOFFICE
GET/api/decision-packets/{id}VIEW_OWN_CLASSES (ClassTeacher) or RUN_BACKOFFICE
GET/api/students/{id}/decision-historyVIEW_OWN_CLASSES (ClassTeacher) or RUN_BACKOFFICE

Student id and packet id are positive integers. All routes gate on the student’s class through the in-handler ClassTeacher guard; a cross-org or out-of-scope id returns 404. There is deliberately no student endpoint; students never see the Data Room.

GET /data-room: the dossier

The dossier returns the eight summary rollups and a data-sufficiency status. ?refresh=true forces a fresh refresh before reading.

curl https://localhost:3000/api/students/42/data-room \ -H "Authorization: Bearer <accessToken>"
{ "studentId": 42, "dataSufficiencyStatus": "insufficient", "nextRecommendedAction": "...", "summaryBlocks": { "benchmark": { }, "cbmTrend": { }, "practice": { }, "paperChecks": { }, "maintenance": { }, "errorPatterns": { }, "fidelity": { }, "behaviorFlags": { } }, "updatedAt": "2026-06-20T10:00:00.000Z" }

dataSufficiencyStatus is sufficient / insufficient / contextual (or null when no data room exists yet). The summary blocks are system-rendered rollups of referenced evidence, never free text, never an input to a rule. When a contributing skill is do_not_decide_yet, or the evidence is below the comparable floor, the status reports insufficient and nextRecommendedAction carries the next data action, never a movement.

GET /data-room/evidence: the evidence index

The evidence index pages the referenced evidence; every row points back to its source record. It filters on evidenceType, validityStatus (valid / use_with_caution / invalid), and includedInDecisionPacket, with limit (1 to 200, default 50) and offset.

curl "https://localhost:3000/api/students/42/data-room/evidence?validityStatus=valid&limit=20" \ -H "Authorization: Bearer <accessToken>"

Invalid-administration evidence stays indexed (nothing is deleted) but is excluded from the sufficiency count.

POST /decision-packets: open a packet shell

Opening a packet snapshots the evidence by reference and pins the rule versions it read, so the packet replays byte-for-byte years later. The body carries only the reviewType, the trigger metadata, and optional controlled context-flag ids. There is no free-text field.

curl -X POST https://localhost:3000/api/students/42/decision-packets \ -H "Authorization: Bearer <accessToken>" \ -H "Content-Type: application/json" \ -d '{ "reviewType": "tier_review", "triggerSource": "system", "triggerRuleId": "DATA_ROOM_OPEN_PACKET", "contextFlagIds": ["CTX_RECENT_ABSENCE"] }'

reviewType is profile_bundle / tier_review / plan_adjustment / maintenance_review. triggerSource is system / v3 / add. A second open of the same reviewType returns 409; a blocked or unknown context flag returns 422. The response carries the packetId and the snapshottedEvidenceCount.

GET /api/decision-packets/{id} returns the full packet including the pinned rule versions and the evidence snapshot (references). A closed packet is immutable forever.

GET /decision-history

The decision history is a computed chronology: a newest-first merge of tier_decision_record, decision_packet, and teacher_action_log. It is read-only; the Data Room composes the timeline but writes none of the underlying records.

Invariants

  • The Data Room never writes a tier. It reads and composes the tier_decision_record; the only tier write path is the RTI engine (proven by lint:data-room-single-write-path).
  • Evidence is indexed, never copied. Invalid rows stay indexed (nothing is deleted) but are excluded from the sufficiency count.
  • Weak data blocks a strong decision. A do_not_decide_yet skill or sub-floor evidence yields insufficient and the next data action, never a movement.
  • Immutable packet (V-6). Opening a packet pins every version it read; a closed packet replays byte-for-byte.
  • Append-only (only summaryBlocks / includedInDecisionPacket / status are mutable), no free text (controlled context flags only), no LLM (V-5), tenant-isolated (organizationId; cross-org 404), and never student-facing.
Last updated on