Intervention Bundles
When a student has an active educational profile, the modules/bundle module
takes a single step: it looks the profile up in a 21-row deterministic map
and returns either one named intervention bundle or a non-bundle
routing/action outcome. Per R12 Tier-D, 7 of the 21 MAP rows
(MAP-000/001/002/003/018/019/020) resolve to a non-bundle object with
recommended_bundle_id = NULL — a monitoring process, a maintenance/enrichment
option, a light-support action, a support module, a routing guard, or a
future-inactive object. Only MAP-004..017 return a real bundle that writes a
bundle_assignment. The teacher then decides whether to activate it. Nothing
reaches a student until the teacher acts (V-12).
The catalog
The bundle catalog is 14 intervention bundles + 7 reclassified non-bundle
objects = 23 physical rows in bundle_definition (16 bundle-type rows —
including the 3 VOCAB_SYNTAX_MORPH variant rows — plus 7 non-bundle objects).
Each row carries a BundleObjectType enum (9 values); only
INTERVENTION_BUNDLE / COMPOSITE_SEQUENCE_PLAN /
COMPOSITE_INTERVENTION_BUNDLE rows write a bundle_assignment on activation.
Bundles are closed catalog items identified by a string bundleId. They are
not generated, not freeform, and not per-student. What changes per student is
the scaffold tier assigned to each skill within that bundle.
The catalog is versioned and publish-then-freeze: once a catalog version is published it is never mutated. A V-6 dry-run replay endpoint lets you preview how a catalog change would affect existing recommendations before publishing.
From profile to bundle: the map
The 21-row profile_bundle_map is keyed on
(primary_profile_code, access_driver | comprehension_subprofile | modifier_driver).
The extra driver column narrows which bundle is selected for profiles that have
multiple instructionally distinct sub-types.
Row MAP-000 is the halt case. The NO_BUNDLE_SYSTEM_GUARD sentinel-bundle row
is retired (D-015, GAP-021); MAP-000 is now a ROUTING_GUARD object. When
the student has no active profile, a DATA_INCOMPLETE guard state, or a
do_not_decide_yet anchor skill, the map returns recommended_bundle_id = NULL,
object_type = ROUTING_GUARD, routing_state = BLOCKED_MISSING_EVIDENCE, and
recommendation_status = blocked_insufficient_evidence, plus a
next_recommended_data_action that names the specific data action needed. No
bundle is recommended and there is no override path around this.
MAP-000 is a hard halt. It is no longer a placeholder bundle row — it is a
nullable-bundle routing outcome. It surfaces only the next data action, never a
bundle. No override code bypasses it. The teacher’s action is to complete the
named data collection step, not to activate an alternative plan.
The Threaded Path Model
Every recommended bundle has three parts:
| Part | Role | Rule |
|---|---|---|
| Anchor | Primary focus of the cycle | Addresses the primary skill group; always the largest allocation band |
| Supporting threads (1–2) | Linked to the anchor | Address skills related to the anchor; maximum 2 |
| Asset bridge | Strength-based bridge | Builds on a documented strength; not_enough_evidence when support data is absent |
R12 Tier-D (D-003) — dosage model changed. Per-session fixed-percentage
splits are retired. Dosage is now expressed as cumulative-cycle allocation
ranges across the intervention cycle (2 master recipes in Q11 sheet 07), stored
via the cycle_allocation_model column on bundle_definition. The old fixed
40–60% / 25–35% / 10–15% percentages are the old model — illustrative only.
The old dosageSplit JSONB (sum == 100 + anchor-strictly-largest) DB CHECK
constraint is dropped. The engine (threaded-path.ts) now asserts the anchor
band is the largest (range-based dosageSplitWellFormed), not a fixed-percentage
strict-largest CHECK. The CI test test:ibc:weights (= test:qa:020) asserts the
cumulative-cycle anchor-band-largest invariant, not sum == 100.
Scaffold tier
The scaffold tier is assigned per (student × skill × bundle × task_type),
not per student and not per bundle. A student may have Level_1_Foundation on
one skill within a bundle and Level_2_Targeted on another.
| Tier | Identifier |
|---|---|
| Foundation | Level_1_Foundation |
| Targeted | Level_2_Targeted |
| Independence | Level_3_Independence |
The scaffold tier is never shown to the student. It drives which level of scaffolded task the task-delivery layer selects. Teacher and admin routes can see tier distribution (the cluster preview and the scaffold adjustment endpoint expose it), but no student-facing route does.
Scaffold tier adjustments use override rule OVR_SCAFFOLD with a reasonCode
from the 9-code catalog. RTI tier is in every override’s forbidden_fields and
is never written through any bundle endpoint.
Bundle assignment lifecycle
A bundle_assignment is written only for the bundle-type object types
(INTERVENTION_BUNDLE / COMPOSITE_SEQUENCE_PLAN /
COMPOSITE_INTERVENTION_BUNDLE — MAP-004..017). The 4 non-bundle outcome types
are recorded by TAW as routing/action codes with no bundle_assignment row.
Once a teacher activates a bundle, the assignment follows a 17-status state
machine (including paused as a transient hold state) reflecting the monitoring
loop:
recommended → in_review → activated → monitoring
├─ paused
├─ reviewed → continued | adjusted | modified
├─ escalated → immediate_review
├─ data_completion_required
├─ met_criteria → exited
├─ teacher_modified
├─ regression_review
└─ rescindedThe BundleAssignmentStatus enum is the authoritative list returned in
ActivateBundleResponse and exposed on the class profile distribution endpoint.
Teacher adjustments (override codes)
Teachers adjust bundle parameters (delivery mode, schedule, context flags)
through controlled override operations. Every adjustment picks a reasonCode
from the 9-code OverrideCodeCatalog:
| Code | Applicable scope |
|---|---|
Override_Contextual_01 | All |
Override_Procedural_02 | All |
Override_Scheduled_03 | All |
Override_DataGap_04 | All |
Gam_Award_Invalid_05 | Gamification rescind only |
Bundle_Met_Criteria_06 | Bundle exit |
Bundle_Teacher_Modify_07 | Bundle exit |
Bundle_Acute_Regression_08 | Bundle exit |
Bundle_Data_Incomplete_09 | Bundle exit |
No free-text reason fields exist anywhere in the bundle module. All
adjustment endpoints accept only a reasonCode FK into this catalog. This is
enforced at the CI lint level (pnpm lint:bundle).
Context flags (e.g. CTX_SESSION_INTERRUPTED) may optionally accompany an
adjustment. The 7 allowed ContextFlagId values are the only representable
flags; the 3 blocked ones are not constructible through the API.
Key endpoints
| Endpoint | Purpose |
|---|---|
POST /api/bundles/recommend | Compute and write a bundle recommendation for a student |
GET /api/bundles/catalog | All 22 catalog rows (14 intervention bundles + 7 non-bundle objects; 23 physical rows in bundle_definition total, the internal NO_BUNDLE_SYSTEM_GUARD sentinel is excluded from this response) |
GET /api/bundles/:id/definition | One bundle with delivery options and cluster rule |
GET /api/bundles/profile-map | All 21 map rows and integration rules |
POST /api/students/:id/activate-bundle | Teacher activates a recommendation |
PATCH /api/students/:id/scaffold-assignment | OVR_SCAFFOLD adjustment |
PATCH /api/assignments/:id/delivery | OVR_DELIVERY adjustment |
PATCH /api/assignments/:id/schedule | OVR_SCHEDULE adjustment |
POST /api/assignments/:id/context-flag | OVR_CONTEXT_FLAG (7 allowed flag ids) |
POST /api/admin/bundle-config-sets | Publish new catalog version |
POST /api/admin/bundle-replay | V-6 dry-run; writes nothing |
Smart Cluster + bulk-activation endpoints are in Smart Clustering.
Connections
- Upstream: Student Profiles: the active profile is the input to the profile-to-bundle map.
- Downstream: Smart Clustering: cluster logic groups students who share bundle, anchor, tier, and delivery mode.
- Monitoring: Progress Monitoring: the 5 GSR safety rules and 19 per-bundle success criteria that govern when a bundle transitions.
- Workflow: Teacher Activation Workflow: the 17-step UI workflow that drives teacher confirmation, adjustment, and review.
- Task delivery: Task Delivery: once a bundle is active, task delivery selects and serves stored items using the scaffold tier.