Implementation PR Roadmap
This chapter breaks down the Registration system into small, reviewable pull requests. It complements the Implementation Plan with concrete PR scopes, dependencies, and acceptance criteria.
Guiding principles:
- Keep PRs tight and shippable behind feature flags.
- Prefer vertical slices that produce visible value.
- Add tests and docs incrementally with each PR.
Plan ↔ PR crosswalk (Registration workstream):
- Step 2 — Foundations (schema/backend): PR-2.x
- Step 3 — FCFS mode (admin + student): PR-3.x
- Step 4 — Preference-based mode (student + allocation): PR-4.x
- Step 5 — Roster maintenance: PR-5.x
- Scope: AR models
Registration::Campaign,Item,UserRegistration,Policyand additive migrations. - Migrations:
20251028000000_create_registration_campaigns.rb20251028000001_create_registration_items.rb20251028000002_create_registration_user_registrations.rb20251028000003_create_registration_policies.rb
- Refs: Models — Campaign, Item, UserRegistration, Policy
- Acceptance: Migrations run cleanly; models have correct associations and validations; no existing tables altered.
- Scope:
Registration::PolicyEnginewith two policy kinds. - Implementation:
Registration::PolicyEngine#evaluate_policies_for,Registration::Policy#evaluateforinstitutional_emailandprerequisite_campaignkinds. - Test doubles: Tests use doubles for checking roster membership in prerequisite campaigns.
- Refs: PolicyEngine, Policy#evaluate
- Acceptance: Policy engine evaluates ordered policies with short-circuit; tests pass with doubled roster data;
student_performancepolicy kind deferred to Step 11.
- Scope: Include
Registration::CampaignableinLectureandRegistration::RegisterableinTutorial. - Implementation:
Campaignable:has_many :registration_campaigns, as: :campaignableRegisterable:capacity,allocated_user_ids(raises NotImplementedError),materialize_allocation!(raises NotImplementedError)
- Refs: Campaignable, Registerable
- Acceptance: Tutorial includes Registerable; methods raise NotImplementedError when called; no functional changes to existing semester.
- Scope: Include
Registration::CampaignableinSeminarandRegistration::RegisterableinTalk. - Same pattern as PR-2.3 but for seminars.
- Refs: Same concerns, different models.
- Acceptance: Same as PR-2.3 for seminar context.
- Scope: Teacher/editor UI for campaign lifecycle (draft → open → closed → processing → completed).
- Controllers:
Registration::CampaignsController(new/create/edit/update/show/destroy). - Actions:
open(validates policies, updates status to :open),close(background job triggers status → :closed),reopen(reverts to :open if allocation not started). - Freezing: Campaign-level attributes freeze on lifecycle transitions (
allocation_mode,registration_opens_atafter draft; policies freeze on open). - UI: Turbo Frames for inline editing; flash messages for validation errors and freeze violations; feature flag
registration_campaigns; disabled fields for frozen attributes. - Refs: Campaign lifecycle & freezing, State diagram
- Acceptance: Teachers can create draft campaigns, add policies, open campaigns (with policy validation); campaigns cannot be deleted when open/processing; freezing rules enforced with clear error messages; frozen fields disabled in UI; feature flag gates UI.
- Scope: Manage registerable items within a campaign, including cohorts.
- Controllers:
Registration::ItemsController(nested routes under campaigns). - Freezing: Items cannot be removed when
status != :draft(prevents invalidating existing registrations); adding items always allowed. - UI: Turbo Frames for inline item addition/removal; capacity editing; delete button disabled for items when campaign is open.
- Refs: Item model, Freezing rules
- Acceptance: Teachers can add items anytime; items cannot be removed if campaign is open or has registrations for that item; capacity edits validated.
- Scope: Student registration for single-item campaigns (e.g., one tutorial per lecture).
- Controllers:
Registration::UserRegistrationsController(create/destroy). - Logic: FCFS mode with capacity checks; policy evaluation on create.
- Freezing: Item capacity can increase anytime; can decrease only if
new_capacity >= confirmed_count(prevents revoking confirmed spots). - UI: Registration button; Turbo Stream updates for immediate feedback; capacity editing validates against confirmed count.
- Refs: FCFS mode, Freezing rules
- Acceptance: Students can register for open campaigns; capacity enforced; policy violations shown with error messages; confirmed status set immediately; capacity decrease blocked if it would revoke spots.
- Scope: Extend PR-3.3 for multi-item campaigns (e.g., tutorial selection from multiple options).
- Controllers: Extend
Registration::UserRegistrationsControllerto handle item selection. - UI: Item selection dropdown; Turbo Stream for dynamic item list updates.
- Refs: Multi-item campaigns
- Acceptance: Students can select from available items; capacity per item enforced; switching items updates previous registration.
- Scope: "New Campaign" wizard that forces an explicit choice of Registration Pattern.
- Controllers:
Registration::CampaignsController#newhandles?pattern=param. - Logic:
- Step 1 (Pattern Selection): User chooses between:
- Group Track (Pattern 1): Creates "Group Registration" campaign (Tutorials/Talks) + optional "Special Groups" campaign (Cohorts).
- Enrollment Track (Pattern 2): Creates "Course Enrollment" campaign (Lecture).
- Mixed Track (Pattern 3): Creates both (gated by explicit acknowledgement).
- Step 2 (Configuration):
- For Group Track: Select group source (Tutorials vs Talks), optionally add Cohorts.
- For Enrollment Track: Confirm Lecture target.
- Step 1 (Pattern Selection): User chooses between:
- UI: Modal with 3 distinct choices; "Mixed Track" is visually distinct/gated.
- Acceptance: Wizard guides user through pattern selection; correctly creates 1 or 2 campaigns based on choice; enforces acknowledgement for Mixed Track.
- Scope: UI for students to rank items by preference.
- Controllers: Extend
Registration::UserRegistrationsControllerwithupdate_preferencesaction. - UI: Drag-and-drop ranking interface; persisted as JSONB array in
preferencescolumn. - Refs: Preference mode
- Acceptance: Students can rank items; preferences saved; cannot submit incomplete rankings.
- Scope: Implement minimal roster persistence for
materialize_allocation!. - Models: Add
source_campaign_idtotutorial_membershipsjoin table. - Concerns: Implement
Roster::Rosterablewithallocated_user_ids,materialize_allocation!,roster_entries,mark_campaign_source!. - Implementation in Tutorial: Override
allocated_user_idsto delegate toroster_user_ids; implementmaterialize_allocation!usingreplace_roster!pattern. - Refs: Rosterable concern, Tutorial implementation
- Acceptance: Tutorial implements Rosterable methods;
materialize_allocation!replaces roster entries tagged with campaign;allocated_user_idsreturns current roster user IDs.
- Scope: Wire solver and finalize end-to-end.
- Services:
Registration::Allocation::Solver(delegates to CP-SAT or placeholder greedy),Registration::FinalizationGuard. - Controllers:
Registration::AllocationController(trigger/retry/finalize actions). - Background:
AllocationJobruns solver and updates UserRegistration statuses via Turbo Streams. - Logic: On finalize, call
FinalizationGuard#check!, thenmaterialize_allocation!for each confirmed user. - Freezing: Item capacity freezes once
status == :completed(results published); can be adjusted freely duringdraft,open,closedstates. - Refs: Solver, Finalization, Freezing rules
- Acceptance: Teachers can trigger allocation; results streamed to UI; finalize materializes rosters; capacity changes blocked after completion; unconfirmed users stay in limbo; confirmed users added to rosters via
materialize_allocation!.
- Scope: Post-allocation roster management for Sub-Groups (Tutorial/Cohort).
- Controllers:
Roster::MaintenanceController(move/add/remove actions). - Logic: Support
Tutorial,TalkandCohortas rosterable types. - UI: Roster Overview with detail views for individual groups; candidates panel (unassigned users from campaign); basic capacity enforcement.
- Refs: Roster maintenance
- Acceptance: Teachers can move students between tutorials/talks/cohorts; capacity enforced; candidates panel lists unassigned users; manual add/remove works for groups.
- Scope: Implement Lecture Roster as the superset of all sub-groups.
- Backend:
- Implement
Lecture#ensure_roster_membership!. - Update
Tutorial/Talk/Cohort#materialize_allocation!to propagate users to Lecture. - Update
Roster::MaintenanceServiceto handle Enrollment/Drop logic (cascading delete).
- Implement
- UI: "Enrollment" tab in
RosterOverviewComponentshowing all lecture members; "Unassigned" status calculation. - Refs: Superset Model, Enrollment Track
- Acceptance: Adding user to tutorial adds them to lecture; Removing from lecture removes from tutorial; Enrollment tab lists all students.
- Scope: Add
self_materialization_modeenum toRosterable; implement permission methods; admin UI for configuration. - Backend:
- Migration: Add
self_materialization_modeenum column to tutorials, talks, cohorts, lectures. - Update
Roster::Rosterableconcern withcan_self_add?,can_self_remove?,self_add!,self_remove!. - Validation: Block enabling mode during active campaigns (non-planning, non-completed).
- Migration: Add
- Admin UI:
- Add mode selector dropdown to roster management UI (per-group basis).
- Four options: disabled/add_only/remove_only/add_and_remove.
- Turbo Frame update on change; validation errors shown inline.
- Feature flag:
self_materialization_enabled.
- Refs: Self-materialization, Rosterable concern
- Acceptance: Backend methods work; validation enforced; admin can configure mode per tutorial/cohort/talk; changes blocked with clear error if campaign active; feature flag gates both backend and UI.
- Scope: Student-facing join/leave buttons.
- Controllers:
Roster::SelfMaterializationController(join/leave actions). - UI:
- Join/Leave buttons on Tutorial/Cohort/Talk show pages.
- Turbo Stream updates for roster list.
- Buttons hidden when mode is
disabled. - Capacity/duplicate guards with error messages.
- Refs: Self-materialization
- Acceptance: Students can join/leave when enabled; buttons respect mode settings; capacity enforced; clear error messages for violations; feature flag gates UI.
- Scope: Tutors can view rosters for their assigned groups.
- Abilities: Update CanCanCan to allow read-only roster access for tutors.
- UI: Tutors see Detail view without edit actions.
- Refs: Abilities
- Acceptance: Tutors can view rosters for their tutorials; cannot edit; exams do not show candidates panel.
- Scope: Add students to rosters manually.
- Controllers: Extend
Roster::MaintenanceControllerwithadd_studentaction. - UI: "Add student" button on Overview; search input for arbitrary student addition.
- Refs: Manual operations
- Acceptance: Teachers can add students from candidates or search; capacity enforced; duplicate prevention.
- Scope: Email notifications for roster changes affecting students.
- Mailer:
Roster::ChangeMailerwith methods:added_to_group,removed_from_group,moved_between_groups. - Triggers: Called from
Roster::MaintenanceService,Registration::Campaign#finalize!, andRoster::SelfMaterializationController. - Content: Email includes group name, lecture context, action taken, timestamp; for moves, includes both source and target groups.
- Configuration: Feature flag
roster_notifications_enabled; teacher toggle per lecture for notification verbosity (all changes vs finalization-only). - Background: Deliver via
ActionMailer::DeliveryJob(async). - Refs: Roster maintenance
- Acceptance: Students receive emails on add/remove/move; emails queued asynchronously; feature flag gates delivery; teachers can configure notification timing per lecture.
- Scope: Background job to verify lecture roster superset principle.
- Job:
RosterSupersetCheckerJobvalidates thatLecture#roster_user_ids⊇ (tutorials + talks + propagating cohorts).roster_user_ids. - Detection: Identifies users in sub-groups who are missing from lecture roster.
- Monitoring: Logs violations for admin review; potential causes include callback failures, race conditions, or manual database edits.
- Refs: Superset Model
- Acceptance: Job runs nightly; reports missing users; no auto-fix (manual review required); clear log format with lecture ID, user IDs, and affected groups.
- Scope: Extract stream logic from controllers into
Turbo::LectureStreamService. - Pattern: Controllers declare "what happened" (e.g.,
roster_changed); service returns appropriate streams. - Refactor:
TutorialsController,CohortsController,Registration::CampaignsController,Roster::MaintenanceController. - Implementation:
Turbo::LectureStreamServicewith methods:roster_changed,campaign_changed,enrollment_changed.- Each method returns array of turbo streams for all dependent UI components.
- Controllers call service instead of building streams inline.
- Refs: Turbo Streams
- Acceptance: Controllers contain no DOM IDs or partial paths; all cross-tab updates centralized in service; adding new dependent views requires editing only the service.
- Scope: Create
assessment_assessments,assessment_tasks,assessment_participations,assessment_task_points. - Migrations:
20251105000000_create_assessment_assessments.rb20251105000001_create_assessment_tasks.rb20251105000002_create_assessment_participations.rb20251105000003_create_assessment_task_points.rb
- Refs: Assessment models
- Acceptance: Migrations run; models have correct associations; no existing tables altered.
- Scope: Core assessment concerns plus Assignment/Talk integration.
- Backend:
- Create
Assessment::Assessableconcern (interface for all assessable types) - Create
Assessment::Pointableconcern (for task-based grading: assignments, exams) - Create
Assessment::Gradableconcern (for final grade: assignments, exams, talks) - Include
Assessable + Pointable + Gradablein Assignment model (behind feature flag) - Include
Assessable + Gradablein Talk model (behind feature flag) - Add
after_create :setup_assessmenthooks
- Create
- Feature Flag:
assessment_grading_enabled(per-lecture) - Behavior: When enabled, new assignments/talks automatically create Assessment record (participations created lazily)
- Refs: Assessment::Assessable, Pointable, Gradable
- Acceptance: All three concerns exist; Assignment has all three; Talk has Assessable+Gradable; participations seeded on creation; feature flag gates behavior; old assignments unaffected.
- Scope: Complete assessment management UI without grading interface.
- Controllers:
Assessment::AssessmentsController(full CRUD),Assessment::TasksController(nested CRUD),Assessment::ParticipationsController(index only) - UI:
- "New Assessment" form (depending on assessable - only for assignments here)
- Index page (list)
- Show page with tabs (Overview, Settings, Tasks, Grading)
- Task management (add/edit/reorder problems)
- Grading tab shows aggregated progress from roster (expected count from roster, graded count from participations)
- Limitations: No point entry, no grade calculation, no result publication (deferred to PR-8.x)
- Feature Flag: Same
assessment_gradingflag gates entire UI - Refs: Assessment controllers, Views
- Acceptance: Teachers can create assessments via UI; grading overview shows progress; tasks configurable; no grading actions available; feature flag gates access.
Step 8 is split into a read-only path (PRs 8.1, 8.3–8.5) and an interactive write path (PRs 8.2, 8.6–8.9) that can be developed in parallel by different team members.
The read-only PRs deliver views that display grade and point data seeded via rake playground tasks. The interactive PRs later add services and inline editing on top of the same components. This separation allows the analysis pipeline (Steps 9–12) to proceed without waiting for the interactive entry UI.
- Scope: Exam model, backend implementation, teacher CRUD, campaign creation for exams.
- Migrations:
20251110000000_create_exams.rb20251110000001_create_exam_rosters.rb
- Backend:
- Create
Exammodel with concerns:Registration::Registerable,Roster::Rosterable,Assessment::Assessable,Assessment::Pointable,Assessment::Gradable - Implement
materialize_allocation!(delegates toreplace_roster!) - Implement
allocated_user_ids(returns roster user IDs) - Extend
Registration::CampaignsControllerto support exam campaigns (campaignable_type: "Exam")
- Create
- Controllers:
ExamsController(CRUD, scheduling) - teacher-facing only - UI:
- Basic exam creation/editing form for teachers
- Extend campaign creation UI to support exams (reuses existing campaign views)
- Teachers can create campaigns with exams as registerable items
- Limitations: No student registration flows, no grading UI, no grade schemes (deferred to later PRs)
- Feature Flag: Same
assessment_grading_enabledflag gates exam creation and campaign setup - Refs: Exam model
- Acceptance: Exam model exists with all concerns; teachers can create/edit exams; teachers can create exam campaigns; backend methods implemented; no student-facing features yet; feature flag gates UI.
- Scope: Enable students to view and register for exams via campaigns.
- Dependencies: Requires PR-8.1 (Exam model + campaign support)
- Backend:
- Extend
Registration::UserRegistrationsControllerto handle exam registrations - Policy evaluation for exam eligibility (uses existing PolicyEngine)
- Extend
- UI:
- Student-facing exam registration flows (reuses existing registration views)
- Display available exam campaigns
- Registration button/form for eligible students
- Confirmation and status display
- Feature Flag: Same
assessment_grading_enabledflag gates student exam registration - Refs: Exam registration flow
- Acceptance: Students can view available exam campaigns; eligible students can register; allocation works for exams; roster materialization works; both FCFS and preference modes supported; ineligible students see appropriate error messages.
- Scope: Read-only table displaying students and their final grades, with distinct indicators for absent/exempt statuses.
- Dependencies: Requires PR-7.2 (assessment show page with tabs)
- ViewComponent:
GradeTableComponent— main table shows gradeable participations (pending + reviewed) with name, tutorial, grade,graded_at. The"—"indicator marks "not yet graded" (statuspending, grade nil). Absent, exempt, and not-submitted participations are excluded from the main table and displayed in a separate "Special Cases" card below with a per-row reason label. - Rake: Extend
assessment_playground.rakewithseed_gradestask that writesgrade,graded_at,grader_iddirectly on participations. Seed ~5% of exam participations asabsentand ~2% asexemptfor realistic test data. - Rationale: Provides the visual foundation for grade display; the same component is reused when interactive editing is added later (PR-8.7). Distinct absent/exempt indicators are needed for Step 9 (grading schemes) so that distribution stats exclude non-participants. Seeded data via rake tasks is sufficient for testing the read path and unblocking Steps 9–12.
- Refs: Grade Entry UI, Absence Tracking
- Acceptance: Grade table renders on assessment show page; displays seeded grades correctly; absent/exempt/not-submitted shown in a separate "Special Cases" card; not-yet-graded shown as
"—"in the main table; works for any Gradable (assignments, exams, talks); feature flag gates UI.
- Scope: Read-only students × tasks matrix with per-task scores and row totals, with distinct indicators for absent/exempt statuses.
- Dependencies: Requires PR-7.2 (tasks exist on assessments)
- ViewComponent:
PointGridComponent— main table shows scoring participations (pending + reviewed, withsubmitted_atpresent) with dynamic task columns and a total column. The"—"indicator marks "not yet graded" cells (points nil, statuspending). Absent, exempt, and not-submitted participations are excluded from the main table and displayed in a separate "Special Cases" card below with a per-row reason label. - Rake: Extend
assessment_playground.rakewithseed_task_pointstask that createsAssessment::TaskPointrecords with random scores and updatesparticipation.points_total. Only reviewed participations get task points. Absent/exempt and not-submitted participations get no task points. Future-deadline assessments are skipped. - Rationale: Provides the visual foundation; the same component is reused when interactive editing is added later (PR-8.8). Distinct absent/exempt indicators ensure Step 9 (grading schemes) can display meaningful distribution stats that exclude non-participants. Seeded data unblocks the grade scheme pipeline.
- Refs: Point Entry UI, Absence Tracking
- Acceptance: Point grid renders with dynamic task columns; totals calculated correctly; absent/exempt/not-submitted shown in a separate "Special Cases" card; not-yet-graded shown as
"—"in the main table; works for any Pointable (assignments, exams); feature flag gates UI.
- Scope: All paths that create
Assessment::Participationrecords: lazy creation on student submission, deadline backfill job, and team fanout. - Controllers: Update
SubmissionsControllerto conditionally create/update participation - Logic — Lazy creation (on submission upload, when
assessment_grading_enabled?):- Create
Assessment::Participationif not exists - Set
status: :pending,submitted_at = Time.current,tutorial_id = student.tutorial - For team submissions: Create/update participations for all team members
- When flag disabled: Use existing submission flow (no participation records created)
- Create
- Logic — Deadline backfill job:
- Sidekiq job triggered after assignment deadline passes
- For each roster student without a participation: create one with
status: :pending,submitted_at: nil - Idempotent (safe to re-run)
- Configurable via
config/schedule.ymlor triggered manually by teacher - Grace period: The backfill fires at
deadline, notfriendly_deadline. Students submitting during the grace period get theirsubmitted_atpatched by the lazy-creation path. This is by design — no special handling needed.
- Migration: Add
notetext column toassessment_participations(status-agnostic free-text annotation, teacher-facing only). - Shared concern:
Assessment::AbsenceHandlingextracted here for reuse by both PR-8.7 and PR-8.8:mark_absent(participation)setsstatus: :absent, leaves points/gradenilmark_exempt(participation, note:)setsstatus: :exemptwith optional note- Validation:
absent→exempttransition allowed
- Refs: Submission workflow, Assignment Lifecycle, Absence Tracking
- Acceptance: Feature flag controls submission behavior; new submissions create participations; team fanout works; backfill job seeds remaining roster students after deadline;
submitted_atdistinguishes submitters from backfilled;AbsenceHandlingconcern works standalone; old submissions continue working unchanged; no breaking changes to existing functionality.
- Scope: Remove manual tutorial selection from the student submission flow. With rosters, a student's tutorial is determined by their roster entry, not by a per-submission dropdown.
- Dependencies: Requires PR-8.5 (participations exist, roster is the source of truth for tutorial assignment)
- Changes:
- Remove tutorial dropdown from
submissions/_form.html.erb— derivetutorial_idfrom the student's roster membership instead. - Remove "Move to tutorial" UI (
_select_tutorial.html.erb,select_tutorial/movecontroller actions). - Update
SubmissionsControllercreate/update params to no longer requiretutorial_id. - Adapt tutor-facing views (
tutorials/_submission_row) to use roster-based filtering. - Clean up CoffeeScript response handlers referencing tutorial errors (
create.coffee,update.coffee).
- Remove tutorial dropdown from
- Refs: Submission workflow
- Acceptance: Students can submit without choosing a tutorial; tutorial is auto-derived from roster; tutors still see submissions grouped by their tutorial; existing submissions retain their tutorial association; feature flag gates new behavior.
PR-8.6 is an independent UX cleanup and can also be implemented at the end of Step 8 (or even later). It does not block any of the interactive entry PRs (8.7, 8.8) since those rely on the rake playground task for development data, not on the live submission flow.
- Scope: Add write capability to the read-only grade table from PR-8.3.
- Dependencies: Requires PR-8.3 (read-only grade view with absent/exempt display). Uses
AbsenceHandlingconcern from PR-8.5. During development, the rake playground task provides all necessary participation data. - Service:
Assessment::GradeEntryServiceset_grade(participation, grade, grader)setsparticipation.grade- Reuses
mark_absent/mark_exemptfromAssessment::AbsenceHandling(PR-8.5) - Validation: grade format/range checks
- Audit: tracks
graded_by_id,graded_at - Works for: Talks, oral exams, manual grade entry, output target for grade schemes
- Controller: Create
Assessment::GradesControllerwithupdate,mark_absent,mark_exemptactions (RESTful controller scoped to Gradable assessments). Also addAssessment::ParticipationsController#index(read-only, deferred from PR-8.3). - Tutor access: Tutors can enter grades for exams when the teacher enables it (per-assessment permission). For exams, teachers are the primary graders but can delegate to tutors. Authorization scoped to the tutor's assigned tutorial group.
- Refs: GradeEntryService, Absence Tracking, Grade Entry UI, Tutor Grading View
- UI: Inline editing on the existing
GradeTableComponent— click a grade cell, input field, save; bulk "Mark as absent" action for no-shows;notefield editable per participation - Acceptance: Teachers can enter grades directly for any Gradable including talks; tutors can enter grades for exams when permitted by teacher; teachers can mark students as absent (bulk) or exempt;
notefield editable; validation works; audit tracking visible; feature flag gates UI.
PR-8.7 (grade entry for Gradable assessments) and PR-8.8 (point entry
for Pointable assessments) are fully independent and can be developed
in parallel. Both share only the AbsenceHandling concern from PR-8.5.
Each PR creates its own controller (GradesController / TaskPointsController)
with no overlap.
- Scope: Add write capability to the read-only point grid from PR-8.4.
- Dependencies: Requires PR-8.4 (read-only point grid with absent/exempt display). Uses
AbsenceHandlingconcern from PR-8.5. Independent of PR-8.7 (both can be developed in parallel). During development, the rake playground task provides all necessary participation and task point data. - Service:
Assessment::PointEntryService- Fanout pattern creates TaskPoints per student (or team)
- Supports any Pointable (assignments, task-based exams)
- Calculates
participation.points_totalfrom task points - For Pointable+Gradable (exams): can optionally trigger grade scheme calculation
- Reuses
mark_absent/mark_exemptfromAssessment::AbsenceHandling(PR-8.5)
- Controller: Create
Assessment::TaskPointsControllerwithindex,update, andupdate_teamactions (single controller for all point entry on Pointable assessments). Theindexaction supports a?tutorial_id=filter — teachers see all students or filter by tutorial, tutors are automatically scoped to their own tutorials by authorization. Theupdate_teamaction saves points for a team viaAssessment::TeamGradingService, which fans out to individualAssessment::TaskPointrecords for each team member. - Authorization: Teachers can access any tutorial's view; tutors can only access their own tutorials. Same controller, same actions — the authorization layer determines scope.
- UI: Inline editing on the existing
PointGridComponent— click a cell, number input, save, total updates; bulk "Mark as absent" action reusesAbsenceHandlingconcern (PR-8.5). Tutorial-scoped view (sameindex, filtered): team-based table with per-task point inputs, progress indicator (graded / total), filter by graded/not graded, submission file links, auto-calculated totals.- The grading view must show all roster students (not only those with
submitted_atpresent). Students who missed the deadline but submitted externally (e.g. by email) should still be gradable by the tutor — no model-level constraint prevents this.
- The grading view must show all roster students (not only those with
- Refs: PointEntryService, Absence Tracking, Point Entry UI, Tutor Grading View, TaskPointsController
- Acceptance: Teachers can enter points for tasks; tutors can enter points for their tutorial's students (primary workflow for assignments); team grading propagates to individual records; totals calculated; bulk absent marking works; tutorial-scoped view filtered by authorization; UI agnostic to assessable type; feature flag gates UI.
- Scope: Student-facing views for assessment results.
- Dependencies: Requires PR-8.7 (grade entry for Gradable assessments) and PR-8.8 (point entry for Pointable assessments)
- Controllers:
Assessment::ParticipationsController(index, show for students) - UI:
- Results Overview: Progress dashboard (points earned, graded count, certification status), assignment list with filters (All/Graded/Pending), collapsible older assignments section
- Results Detail: Per-assessment view with task breakdown table (if Pointable), final grade (if Gradable), submitted files, tutor feedback, team info, overall progress sidebar
- Results filtered by
results_published_at(null = hidden from students) - Works for assignments, exams, and talks (unified interface)
- Authorization: Students see only their own participations; results hidden when
results_published_atis nil - Refs: Student results views
- Acceptance: Students can view results; task points visible (if Pointable); final grade visible (if Gradable); feedback displayed; unpublished assessments hidden; works for any assessable type; feature flag gates access.
- Scope: Teacher-facing toggle for result visibility.
- Dependencies: Requires PR-8.9 (student results interface exists to verify visibility)
- Controllers: Extend
Assessment::AssessmentsControllerwithpublish_resultsandunpublish_resultsactions - UI: Toggle button on assessment show page; works for grades, points, or both
- Refs: Publication workflow
- Acceptance: Teachers can publish/unpublish results; students see results only when published; toggle works via Turbo Frame; works for any assessable type; feature flag gates UI; end-to-end testable against student view from PR-8.9.
- Scope: Create
assessment_grade_schemestable;Assessment::GradeSchememodel with factory and spec. - Design decision: bands are stored as JSONB in
config(no separate thresholds table). Bands are always read/written as a unit, JSONB keeps versioning viaversion_hashatomic, and the schema stays flexible for futurekindvalues. - Migration:
20260220000000_create_grade_schemes.rb - Refs: Assessment::GradeScheme
- Acceptance: Migration runs;
Assessment::GradeSchememodel has correct associations, validations,applied?,compute_hash, andconfig_matches_kind; factory covers absolute-points, percentage, and applied traits; spec covers all validations and uniqueness of active scheme per assessment.
- Scope:
Assessment::GradeSchemeApplierfor converting exam points to grades. - Implementation: Supports absolute points and percentage-based bands; idempotent application via version_hash; respects manual overrides
- Refs: Assessment::GradeSchemeApplier
- Acceptance: Service computes grades from points; handles both absolute and percentage schemes; version_hash prevents duplicate applications.
- Scope: UI for grade scheme configuration and application (layers on top of PR-8.4 point grid).
- Controllers:
Assessment::GradeSchemesController(configuration, preview, apply) - UI:
- Distribution analysis (histogram, statistics) based on entered points
- Scheme configuration (two-point auto-generation + manual adjustment)
- Grade preview showing how scheme maps to students
- Apply action (runs Assessment::GradeSchemeApplier)
- Integration: Uses existing read-only point grid from PR-8.4; adds grade scheme layer
- Refs: Exam grading workflow
- Acceptance: Teachers can create and apply grade schemes; preview grade distribution; apply action creates final grades; publication uses existing PR-8.10 toggle; feature flag gates UI.
Step 10 was promoted ahead of Activity Tracking (now Step 12) because student performance and exam eligibility are MVP-critical: the system cannot gate exam registration without certifications. Achievement tracking is a nice-to-have enhancement that can follow later.
Step 10 was consolidated from 7 PRs to 4. The original split had several very small PRs (pure service objects, thin controllers, tiny background jobs) that are better reviewed together:
- Old 10.2 (ComputationService) + 10.3 (Evaluator) + 10.7 (jobs) → new PR-10.2 (all backend services + jobs, no UI)
- Old 10.4 (RecordsController) + 10.6 (EvaluatorController) → new PR-10.3 (all read-only/proposal UI)
- Old 10.5 (CertificationsController) → new PR-10.4 (unchanged, the main write-heavy decision UI deserves isolated review)
- Scope: Create
student_performance_records,student_performance_rules,student_performance_rule_achievements,student_performance_certifications. Also createachievementstable andAchievementmodel shell (migration + associations only, no CRUD or UI) so thatStudentPerformance::Rule.required_achievementsresolves. - Migrations:
20251120000000_create_achievements.rb20251120000001_create_student_performance_records.rb20251120000002_create_student_performance_rules.rb20251120000003_create_student_performance_rule_achievements.rb20251120000004_create_student_performance_certifications.rb
- Rationale: The Achievement table is created here as a schema stub.
Rules can reference achievements via the join table, but with no
Achievement CRUD yet,
required_achievementsis simply empty. The computation service handles this gracefully (empty = all met). Full Achievement CRUD and UI are deferred to Step 12 (post-MVP). - Refs: Student Performance models
- Acceptance: Migrations run; models have correct associations; unique constraints on certifications; Achievement model exists but has no controller or UI.
- Scope: All backend service objects and background jobs for the student performance pipeline — no controllers or UI.
- Services:
StudentPerformance::ComputationService: reads fromassessment_participationsandassessment_task_points; upsertsstudent_performance_records. When no achievements are configured on a rule, treats achievement criteria as met.StudentPerformance::Evaluator: reads Records and Rules; returns proposed status (passed/failed) per student. Generates bulk proposals for the teacher certification UI. Does NOT create Certifications.
- Jobs:
PerformanceRecordUpdateJob: recomputes Records after grade changes (thin wrapper aroundComputationService).CertificationStaleCheckJob: flags stale certifications when Records change.
- Refs: ComputationService, Evaluator, Background jobs
- Acceptance: ComputationService computes points and achievements; upserts Records; handles missing data gracefully; works with points-only rules (no achievements). Evaluator generates proposals; does NOT create Certifications. Jobs run on schedule; recomputed Records are accurate; stale certifications flagged for teacher review.
- Scope: All read-only and proposal-generating UI for student
performance — everything teachers see before making certification
decisions. Adds the Assessments overview subtab navigation
(
AssessmentsOverviewComponent) with three subtabs: Assessments, Performance, and Rules (read-only). - Controllers:
StudentPerformance::RecordsController: index/show actions for factual performance data.StudentPerformance::EvaluatorController:bulk_proposals,preview_rule_change,single_proposal.StudentPerformance::RulesController:showaction (read-only display of the active rule and its criteria).
- UI:
AssessmentsOverviewComponentwith subtab navigation (Assessments | Performance | Rules), feature-flag gated.- Records table view with points, achievements,
computed_attimestamp. - Evaluator proposal list (bulk proposals for all students).
- Modal for rule change preview showing diff of affected students.
- Rules subtab: read-only display of the active rule (thresholds, linked achievements). Info alert: "Rule editing available in a future update."
- Refs: RecordsController, EvaluatorController, RulesController
- Acceptance: Teachers can view Records; teachers can generate proposals; preview rule changes; Rules subtab shows current criteria read-only; does NOT create Certifications automatically; feature flag gates access.
- Scope:
StudentPerformance::CertificationsControllerfor teacher certification — the write-heavy decision-making UI. Also adds write-side Rules (edit/update) and the Certifications subtab toAssessmentsOverviewComponent. - Controllers:
StudentPerformance::CertificationsController: index (dashboard), create (bulk), update (override), bulk_accept.StudentPerformance::RulesController:edit,updateactions (criteria editing with rule change preview).
- UI:
- Certifications subtab added to
AssessmentsOverviewComponent(Assessments | Performance | Rules | Certifications). - Certification dashboard with proposals; bulk accept/reject; manual override with notes.
- Rules subtab upgraded from read-only to editable: inline form for thresholds, achievement checkboxes, preview + save.
- Certifications subtab added to
- Refs: CertificationsController, RulesController
- Acceptance: Teachers can review proposals; bulk accept; override with manual status; edit rules with preview; Certifications subtab visible; remediation workflow for stale certifications.
- Scope: Add
student_performancepolicy kind toRegistration::PolicyEngine. - Implementation:
Registration::Policy#eval_student_performancechecksStudentPerformance::Certification.find_by(...).status. - Phase awareness: Returns different errors for registration (missing/pending) vs finalization (failed).
- Refs: Policy evaluation
- Acceptance: Policy checks Certification table; phase-aware logic; tests use Certification doubles.
- Scope: Add certification completeness checks to campaign lifecycle.
- Controllers: Update
Registration::CampaignsController#opento check for missing/pending certifications; block if incomplete. - Update
Registration::AllocationController#finalizeto check for missing/pending; auto-reject failed certifications. - Refs: Pre-flight validation
- Acceptance: Campaigns cannot open without complete certifications; finalization blocked if pending; failed certifications auto-rejected.
- Scope: Wire eligibility checks into exam registration UI.
- Controllers: Extend existing exam registration controllers to handle student_performance policy errors.
- UI: Eligibility status displays; blocked registration with clear messaging; links to performance overview.
- Refs: Exam registration flow
- Acceptance: Students see eligibility status; policy blocks ineligible users; clear error messages; links to certification details; feature flag gates UI.
- Scope: UI for teachers to resolve pending certifications during finalization.
- Controllers: Add remediation actions to
StudentPerformance::CertificationsController. - UI: Remediation modal during finalization showing pending students; quick-resolve actions; bulk accept/reject.
- Refs: Remediation workflow
- Acceptance: Teachers can resolve pending certifications inline during finalization; finalization retries after resolution; auto-rejection of failed students.
Activity tracking is a post-MVP enhancement. The Achievement table already exists from Step 10 as a schema stub. This step adds the full CRUD, marking UI, and student views.
- Scope: Full Achievement feature on top of the model shell from Step 10.
- Model: Wire
Assessment::Assessableconcern (but NOT Pointable or Gradable). Add value type support (boolean/numeric/percentage). - Controllers:
AchievementsController(CRUD), extendAssessment::ParticipationsControllerwith achievement marking actions - UI: Checkbox/numeric input for marking; student list view
- Rationale: Achievements track attendance/involvement but don't contribute to grades. Rules that only use point thresholds work without any achievements.
- Refs: Achievement model, Activity tracking
- Acceptance: Achievement CRUD works; teachers can mark achievements; students see progress; value_type validated; feature flag gates UI.
Status of Steps 13-14: Steps 13 and 14 (Dashboards) remain at high-level outline stage. Detailed PR breakdowns will be added during implementation planning.
- Scope: Student dashboard with widgets for registrations, grades, exams, deadlines.
- Controllers:
Dashboards::StudentControllerwith widget partials. - Widgets: "My Registrations", "Recent Grades", "Upcoming Exams", "Deadlines".
- Refs: Student dashboard mockup
- Acceptance: Students see dashboard; widgets show data from new tables including exam registrations; exam eligibility widget hidden (added in Step 14).
- Scope: Teacher dashboard with widgets for campaigns, rosters, grading, exams.
- Controllers:
Dashboards::TeacherControllerwith widget partials. - Widgets: "Open Campaigns", "Roster Management", "Grading Queue", "Exam Management".
- Refs: Teacher dashboard mockup
- Acceptance: Teachers see dashboard; widgets show actionable items including exam grading; certification widget hidden (added in Step 14).
- Scope: Add student performance and exam registration widgets.
- Widgets: "Exam Eligibility Status", "Performance Overview".
- Refs: Student dashboard complete
- Acceptance: Students see eligibility status; performance summary; links to certification details.
- Scope: Add certification and exam management widgets.
- Widgets: "Certification Pending List", "Eligibility Summary".
- Refs: Teacher dashboard complete
- Acceptance: Teachers see pending certifications; summary of eligible students; links to remediation UI.