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
| Method | Path | Permission |
|---|---|---|
GET | /api/students/{id}/data-room | VIEW_OWN_CLASSES (ClassTeacher) or RUN_BACKOFFICE |
GET | /api/students/{id}/data-room/evidence | VIEW_OWN_CLASSES (ClassTeacher) or RUN_BACKOFFICE |
POST | /api/students/{id}/decision-packets | VIEW_OWN_CLASSES (ClassTeacher) or RUN_BACKOFFICE |
GET | /api/decision-packets/{id} | VIEW_OWN_CLASSES (ClassTeacher) or RUN_BACKOFFICE |
GET | /api/students/{id}/decision-history | VIEW_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 bylint: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_yetskill or sub-floor evidence yieldsinsufficientand 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/statusare mutable), no free text (controlled context flags only), no LLM (V-5), tenant-isolated (organizationId; cross-org404), and never student-facing.
Related guides
- RTI Decisions & Tiers: the engine that owns the only tier write path
- Decision Packets: the workflow that assembles a rich packet on top of this shell
- Intervention Fidelity Tracker: the fidelity rollup read into the dossier
- Progress Monitoring: a source of the evidence the dossier indexes