Skip to content

Work Based Learning Internals

benjamindobbs edited this page Sep 8, 2026 · 1 revision

Work-Based Learning Internals

Technical companion to the Work-Based Learning Guide. Canonical design docs live in the main repo: WBL_Assessment_Framework.md (philosophy), WBL_Schema_Design.md (data model + numbered decisions), WBL_API_Design.md (routes). Code: server/routes/wbl.js + server/wbl/logic.js. Tables: Database Schema.


Model in one paragraph

A program (wbl_programs) owns curriculum and progression — skills, credentials, QC criteria, phase, awards — and ownership (teacher_key) lives there and nowhere else. A class owns roster and gradebook. wbl_class_programs links them many-to-many. Student history keys to (program_id, student_id), so credentials and phase survive a section change or a new school year. Every assessment attaches to a Work Event (wbl_work_events) via a per-student participant row (wbl_work_event_participants).

Key schema decisions (from WBL_Schema_Design.md)

# Decision Mechanism
7 Skill versions pinned on assessments; evidence carries forward wbl_skill_assessments.skill_version_id is fixed at assessment time; wbl_credential_skills references the skill lineage (wbl_skills.id), not a version.
8 Program = progression, class = roster/gradebook history on (program_id, student_id).
9 Invariants enforced by triggers, not convention Do Now daily cap and Exit Slip UPDATE block are DB triggers (they fire despite foreign_keys = OFF).
10 / 15 Exit slips are voided, never edited append-only wbl_exit_slip_voids; trigger blocks UPDATE, permits DELETE.
11 QC floor alerts, doesn't gate denominator is active weeks, not calendar weeks.
13 Transfer claims may cite across programs wbl_transfer_claims.cited_program_id may differ from program_id; verification searches the student's whole record.
16 Program enrollment is explicit wbl_program_enrollments, not derived from class rosters (but Sync Roster populates it from them).
17 student_id canonical form normalizeStudentId on ingest; WBL tables store normalized, class_students stores raw — dual lookup in code.

Phase (wbl/logic.js)

computed_phase = 2  iff  the student has a wbl_credential_award (not revoked) for EVERY credential
                          in wbl_phase2_prereqs for the program; else 1
effectivePhase = override_phase ?? computed_phase          # wbl_student_phase

recomputePhase runs after attainment changes. An instructor override (wbl_student_phase.override_phase) always wins. Transfer-claim scoring and the Habits of Work transfer sync are gated on effectivePhase == 2.

Credentialing rule (credentialProgress)

A demonstration of a required skill counts when all hold:

  1. wbl_skill_assessments.result = 'mastered', for this (program, student, skill).
  2. The same participant has a wbl_holistic_calls row whose tier rank ≥ the credential's min_holistic_tier rank. (Grading a job to spec can therefore complete a credential with no new assessment — recomputeAttainment is called after every Holistic Call for exactly this reason.)
  3. Demonstrations are counted across distinct work_event_ids — one job contributes at most one.

Plus: a wbl_skill_checks row (roster-wide standing credit, UNIQUE(program, student, skill)) is one unconditional demonstration — crediting it is the instructor attesting mastery, so no Holistic gate.

A skill is satisfied when demos ≥ required_demonstrations. A credential is earned when every required skill is satisfied. recomputeAttainment writes a wbl_credential_awards row (source = 'auto') and snapshots the satisfying evidence into wbl_award_evidence / wbl_award_skill_check_evidence so the trail survives later catalog edits.

QC rotation & floor

  • Weekly cap: UNIQUE(program_id, student_id, iso_week) on wbl_qc_checks is the cap.
  • Rotation (rotationQueue): students on an active job with no check this ISO week, longest-unchecked first.
  • Floor (qcFloorReport): flags students below wbl_programs.qc_max_weeks_unchecked, measured against weeks the student actually had an active job — gaps between jobs and absences don't count as missed coverage.

Attendance blend

attendanceRatio(program, class, student, from, to) over wbl_attendance — see Grading Algorithms. A wbl_called_outs row for a date drops that day's UNV from the ratio. Returns null when no attendance rows exist in range (callers treat that as "not pulled").

Dispositional scoring (dispositionalScore)

Cumulative rating average over the marking period, one value per class meeting day (meetingDates), clamped to the student's enrollment span. Per day: no Do Now → 0; code not picked → excluded; picked + Exit Slip voided → 0; picked + unverified → excluded; picked + verified with rating (0–4) → rating × 25; picked + no slip → 0. Score = average of what counted; no countable days ⇒ blank. Full rules: Grading Algorithms.

Sync state machine

wbl_credential_sync.state / wbl_credential_skill_sync.state ∈ not_started | in_progress | due, per class.

  • GET /api/wbl/sync/progress?class_id= returns the sync sets and, for every not_started row, lazily auto-promotes it to in_progress if hasSkillEvidence finds any mastered assessment or skill check for a roster student. No write-side hooks — it's evaluated fresh each time the panel loads.
  • PATCH /api/wbl/credentials/:id/state cascades the new state to all the credential's skill rows once (creating rows via INSERT OR IGNORE first); skills are independently editable afterward.
  • POST /api/wbl/sync/ids upserts the PowerSchool assignment IDs without touching state (named-column ON CONFLICT DO UPDATE, not INSERT OR REPLACE).

The old sync_enabled column and wbl_transfer_sync table are retained but unread — superseded by state and wbl_habits_sync respectively. wbl_habits_sync is keyed by soft_skill_code (not the CHECK(kind IN ('application','extension')) of wbl_transfer_sync, which can't be widened in place); a migration copied existing transfer PS-assignment IDs across.

Student-facing (/api/wbl/me/*, requireAuth)

Resolves user_key → student_id and exposes, for programs the student is enrolled in via a linked class: current credentials and progress, skill checks, active/'past work events, work history, the day's Do Now (GET/POST, one per day), Exit Slip submission, and transfer-claim filing. The student dashboard's WBL section is built entirely from these.

Clone this wiki locally