Intervention Fidelity Tracker
The fidelity tracker (WP-FID-BE) answers one question before any tier escalation: was the plan
actually delivered? It computes a fidelity rate (sessions completed ÷ planned) for an active
bundle assignment, maps it to a rating, cross-references the growth signal from progress
monitoring, and returns a verdict plus an escalationBlocked flag. The RTI engine reads this gate
inline before any Tier 2 → Tier 3 move.
The pedagogical point: a plan that did not work is different from a plan that was not done. Faithful
delivery (≥80%) with weak growth MAY escalate (the plan was tried and did not work) — but ≥80%
alone is no longer sufficient (see Off-track lifecycle
below): a major_fidelity_issue_flag (very short sessions / incomplete materials / mis-delivery)
forces the result not-yet-decidable even at ≥0.80. Under-80% with weak growth is BLOCKED, because
you cannot indict a plan that was never run. No session data returns cannot_evaluate_yet, never a
guessed pass. In every “not decidable” case the gap is in delivery, never student non-response.
Routes
| Method | Path | Permission |
|---|---|---|
GET | /api/fidelity/{bundleAssignmentId}/check | VIEW_OWN_CLASSES (ClassTeacher) or RUN_BACKOFFICE |
GET | /api/fidelity/{bundleAssignmentId} | VIEW_OWN_CLASSES (ClassTeacher) or RUN_BACKOFFICE |
POST | /api/fidelity/{bundleAssignmentId}/recompute | VIEW_OWN_CLASSES (ClassTeacher) or RUN_BACKOFFICE |
POST | /api/fidelity/{bundleAssignmentId}/session | VIEW_OWN_CLASSES (ClassTeacher) or RUN_BACKOFFICE |
GET | /api/students/{id}/fidelity-tracker | VIEW_OWN_CLASSES (ClassTeacher) or RUN_BACKOFFICE |
GET | /api/fidelity/admin/low-fidelity-cohort | RUN_BACKOFFICE (admin only) |
bundleAssignmentId and student id are positive integers. The teacher routes gate on the
student’s class through the in-handler ClassTeacher guard; a cross-org or out-of-scope id returns
404 (existence non-disclosure). There is deliberately no student endpoint; students never
see fidelity.
POST /api/fidelity/{bundleAssignmentId}/session is the primary write path: it logs one delivered
(or scheduled-but-not-delivered) session, triggers a recompute, and returns a fresh projection. See
the Log Intervention Session guide for the request body fields.
GET /check: the escalation gate
This is the synchronous verdict the RTI engine calls inline before an escalation. It returns the latest persisted projection; it does not recompute.
curl https://localhost:3000/api/fidelity/8124/check \
-H "Authorization: Bearer <accessToken>"{
"bundleAssignmentId": 8124,
"fidelityRate": 0.86,
"fidelityRating": "high",
"fidelityIssueFlag": false,
"fidelityIssueCode": null,
"impactOnDecision": "no_issue",
"growthSignal": "low_growth",
"verdict": "adjust_intervention",
"escalationBlocked": false,
"recommendedAction": "...",
"autoDraftedParagraphAr": "...",
"sessionsCompleted": 12,
"sessionsPlanned": 14
}The five-value fidelityRating is high (≥0.80) / adequate (0.60 to 0.79) / partial
(0.40 to 0.59) / low (below 0.40) / unknown (no data). The four-value verdict is continue /
adjust_intervention / cannot_evaluate_yet / fortuitous_complete_dose. impactOnDecision is
block_escalation / reduce_confidence / no_issue. The growthSignal (good_growth /
partial_growth / low_growth / insufficient_data) is read from the monitoring module.
The rating × growth matrix
The verdict and escalationBlocked come from a pure matrix of rating against growth:
| fidelity \ growth | good | low | halt (do_not_decide_yet / acute) |
|---|---|---|---|
| high (≥0.80) | continue (not blocked) | adjust_intervention (not blocked) | cannot_evaluate_yet (blocked) |
| adequate (0.60–0.79) | continue (not blocked) | cannot_evaluate_yet (blocked) | cannot_evaluate_yet (blocked) |
| partial (0.40–0.59) | fortuitous_complete_dose (not blocked) | cannot_evaluate_yet (blocked) | cannot_evaluate_yet (blocked) |
| low (below 0.40) | fortuitous_complete_dose (not blocked) | cannot_evaluate_yet (blocked) | cannot_evaluate_yet (blocked) |
| unknown (no data) | cannot_evaluate_yet (blocked) | cannot_evaluate_yet (blocked) | cannot_evaluate_yet (blocked) |
The halt sentinel always returns cannot_evaluate_yet + blocked (the Tie Rule). Escalation is
permitted only at high or adequate fidelity with a growth signal that the matrix lets through.
Off-track lifecycle and effect validity (R12, FR-FID-13)
R12 (FR-FID-13, spec 35-fidelity-tracker.md) adds a delivery-continuity lifecycle that layers
on top of the rate matrix. It answers a second question the rate alone cannot: has the plan stalled
in delivery? Two new columns capture it.
intervention_track_status ∈ {on_track, partial_delivery, off_track, resumed_pending_new_evidence}
— the delivery-continuity lifecycle. A plan goes off_track when NO planned session was
delivered for offTrackThresholdWeeks consecutive instructional weeks (config-driven via
InterventionTrackingConfig.offTrackThresholdWeeks, default 3). An instructional week is an
ISO week that had ≥1 planned session and was not a holiday week; holidays and weeks with no planned
session do NOT count toward the consecutive total. partial_delivery = some planned weeks were
missed but below the off-track threshold; resumed_pending_new_evidence = the teacher resumed an
off-track plan and fresh comparable evidence has not yet accrued.
intervention_effect_validity_status ∈ {valid, limited, invalid_due_to_implementation_gap}
— whether the student’s response may be read as a real plan effect:
invalid_due_to_implementation_gap— low/unknown fidelity, OR amajor_fidelity_issue_flag, ORoff_track. The gap is in delivery, never student non-response. ≥80% alone is not sufficient: a major fidelity issue (very short sessions / incomplete materials / mis-delivery) forcesinvalideven at ≥0.80.limited—partialfidelity or a resumed plan: interpret with caution, never causal.valid—adequate/highfidelity, no major issue, and on-track.
Off-track BLOCKS all automatic tier movement. When off_track, the recompute FORCES
escalationBlocked = true even when the rate matrix would not block, so the RTI engine’s TM_BLOCK_01
gate bars ALL automatic tier movement — escalation, de-escalation, AND graduation to lighter
support — until the teacher resumes the plan. A tier cannot move while blocked. The numeric
difference between assessments may still be shown as a performance result, but is never attributed
to the intervention. The off-track signal reaches the RTI engine through the existing fidelity-reader
seam (an offTrack boolean on the reading) — no new RTI binding.
Resume. On resume the teacher selects a controlled reason code from the closed
ADD_Context_Flag_Dictionary (B.24 — no free text); the row records resumeReasonCode + resumedAt
and sets intervention_track_status = resumed_pending_new_evidence. Effect validity stays limited
until fresh comparable points accrue.
POST /recompute: append a fresh snapshot
Recompute forces a fresh computation and appends a new immutable snapshot row. It carries no
body: there is no free-text field on any fidelity path; the only issue context is the controlled
fidelityIssueCode vocabulary (missed_sessions / short_duration / wrong_grouping /
incomplete_material / unknown).
curl -X POST https://localhost:3000/api/fidelity/8124/recompute \
-H "Authorization: Bearer <accessToken>"The recompute is advisory-locked per (org, assignment) and idempotent on unchanged inputs. It
returns the fresh projection (the same shape as /check) with a 201.
The auto-drafted paragraph
Each verdict auto-drafts a short Arabic review-meeting paragraph (autoDraftedParagraphAr). It is
rule-written, never an LLM, and is filtered through the language-safety layer for clinical,
framework, and deficit language. It is a teacher/admin surface only.
Invariants
- Fail-closed. A missing assignment is a
404; a missing growth read or no snapshot row yieldsunknown/escalationBlocked = true, never a fabricatedadequate. Absence of fidelity data blocks escalation; it never allows it. - Append-only (V-6).
intervention_fidelityandintervention_session_logare pure audit tables: no update, no delete. The config version is pinned on every row, so a recompute of the same inputs replays byte-for-byte. - No free text. No
reason/note/commentbody field exists on any path; issue context is the controlledfidelityIssueCodevocabulary. - No LLM (V-5). The verdict and the paragraph are deterministic and rule-written.
- Tenant-isolated.
organizationIdscopes every read and write; cross-org returns404. - Teacher-scoped (ClassTeacher) / admin only; never student-facing.
Related guides
- RTI Decisions & Tiers: the engine that reads this gate before a Tier 2 → Tier 3 move
- Progress Monitoring: the source of the growth signal this module cross-references
- Decision Packets: the workflow that wraps a recommendation in an audited approval
- Intervention Bundles: the bundle assignment whose delivery fidelity is tracked