Skip to Content

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:

PartRoleRule
AnchorPrimary focus of the cycleAddresses the primary skill group; always the largest allocation band
Supporting threads (1–2)Linked to the anchorAddress skills related to the anchor; maximum 2
Asset bridgeStrength-based bridgeBuilds 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.

TierIdentifier
FoundationLevel_1_Foundation
TargetedLevel_2_Targeted
IndependenceLevel_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 └─ rescinded

The 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:

CodeApplicable scope
Override_Contextual_01All
Override_Procedural_02All
Override_Scheduled_03All
Override_DataGap_04All
Gam_Award_Invalid_05Gamification rescind only
Bundle_Met_Criteria_06Bundle exit
Bundle_Teacher_Modify_07Bundle exit
Bundle_Acute_Regression_08Bundle exit
Bundle_Data_Incomplete_09Bundle 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

EndpointPurpose
POST /api/bundles/recommendCompute and write a bundle recommendation for a student
GET /api/bundles/catalogAll 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/definitionOne bundle with delivery options and cluster rule
GET /api/bundles/profile-mapAll 21 map rows and integration rules
POST /api/students/:id/activate-bundleTeacher activates a recommendation
PATCH /api/students/:id/scaffold-assignmentOVR_SCAFFOLD adjustment
PATCH /api/assignments/:id/deliveryOVR_DELIVERY adjustment
PATCH /api/assignments/:id/scheduleOVR_SCHEDULE adjustment
POST /api/assignments/:id/context-flagOVR_CONTEXT_FLAG (7 allowed flag ids)
POST /api/admin/bundle-config-setsPublish new catalog version
POST /api/admin/bundle-replayV-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.