---
title: "Intervention Fidelity Tracker"
url: https://docs.growamal.com/en/guides/fidelity-tracker/
language: en
---

# 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.

> **Note:** 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](#off-track-lifecycle-and-effect-validity-r12-fr-fid-13)
> 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](https://docs.growamal.com/en/guides/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.

```bash
curl https://localhost:3000/api/fidelity/8124/check \
  -H "Authorization: Bearer <accessToken>"
```

```json
{
  "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 a `major_fidelity_issue_flag`,
  OR `off_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)
  forces `invalid` even at ≥0.80.
- **`limited`** — `partial` fidelity or a resumed plan: interpret with caution, never causal.
- **`valid`** — `adequate`/`high` fidelity, no major issue, and on-track.

> **Warning:** **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`).

```bash
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 yields
  `unknown` / `escalationBlocked = true`, never a fabricated `adequate`. Absence of fidelity data
  blocks escalation; it never allows it.
- **Append-only (V-6).** `intervention_fidelity` and `intervention_session_log` are 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` / `comment` body field exists on any path; issue context is
  the controlled `fidelityIssueCode` vocabulary.
- **No LLM (V-5).** The verdict and the paragraph are deterministic and rule-written.
- **Tenant-isolated.** `organizationId` scopes every read and write; cross-org returns `404`.
- **Teacher-scoped (ClassTeacher) / admin only; never student-facing.**

## Related guides

- [RTI Decisions & Tiers](https://docs.growamal.com/en/guides/rti-decisions/): the engine that reads this gate before a Tier 2 → Tier 3 move
- [Progress Monitoring](https://docs.growamal.com/en/guides/progress-monitoring/): the source of the growth signal this module cross-references
- [Decision Packets](https://docs.growamal.com/en/guides/decision-packets/): the workflow that wraps a recommendation in an audited approval
- [Intervention Bundles](https://docs.growamal.com/en/guides/intervention-bundles/): the bundle assignment whose delivery fidelity is tracked
