feat(root): wire ROOT 05-10/12/Capstone curriculum expansion

023_phase6b_curriculum.sql — additive on top of 022_course_content.sql:
13 new teaching modules (ROOT 08/09 move from PLANNED to PARTIAL; ROOT
05-07/10 gain a new front-door module sequenced ahead of the existing
bare-lab bank; ROOT 12 gains an incident-response framing lesson +
reflection; Capstone gains a readiness module). No existing row is
deleted; a small number of existing rows have their sequence/
prerequisites updated so a new teaching module supersedes the bare-lab
bank as the front door — never their scenario_id/activity_type/
completion_behavior, so no student's derived completion changes meaning.

is_required (new module_activities column, defaults true — every Phase
6A activity unaffected) lets a module distinguish CORE teaching from
GUIDED/INDEPENDENT-practice duplicates and ADVANCED/optional extras.
course-progress.js's totals() now considers a module complete once
every REQUIRED activity is done, not literally every activity — an
incomplete optional one no longer blocks completion, though it still
counts toward the displayed percentage.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-08-26 09:04:55 -05:00
co-authored by Claude Sonnet 5
parent 82f17a05e8
commit bee57d89fd
2 changed files with 344 additions and 1 deletions
+12 -1
View File
@@ -55,16 +55,27 @@ function shapeActivity(activity, ctx, forInstructor) {
scenario_id: activity.scenario_id,
assessment_definition_id: activity.assessment_definition_id,
completion_behavior: activity.completion_behavior,
is_required: activity.is_required,
complete: isActivityComplete(activity, ctx),
body: content?.shared || null,
...(forInstructor ? { instructor_notes: content?.instructorNotes || null } : {}),
};
}
// Phase 6B §18/§27 — an optional activity (independent-practice duplicates, advanced/CKS-exam-
// style extras) counts toward the displayed total/percent but NOT toward whether the module
// itself is "complete." Every Phase 6A activity has is_required=true (the column's default,
// unaffected by this), so this is purely additive — nothing that was complete before becomes
// incomplete now.
function totals(items) {
const total = items.length;
const completed = items.filter((i) => i.complete).length;
return { total, completed, pct: total ? Math.round((completed / total) * 100) : 0, complete: total > 0 && completed === total };
const required = items.filter((i) => i.is_required !== false);
const requiredComplete = required.filter((i) => i.complete).length;
return {
total, completed, pct: total ? Math.round((completed / total) * 100) : 0,
complete: required.length > 0 && requiredComplete === required.length,
};
}
// A prerequisite with zero content (a PLANNED module/course with nothing built yet) can never