1
0

List lifecycle polish and cycle batches, from live walkthrough feedback

Generating a call list is non-destructive by intent: the button reads
Regenerate when an unused list exists and replaces it freely, with a
confirmation only when the day already has recorded outcomes (which
are always kept). The print view shows resolved calls' outcomes,
offers generate/regenerate in place, and the home page links to the
existing list. The day editor now supports adding calls after the
fact (any roster student, with status/assessment/note), completing
full editability: add, edit, delete.

Cycle mode is now a true rolling rotation driven by non-skipped call
counts: batches of n take the next students of the current pass,
remainder batches come up short rather than wrapping, a new pass
starts when everyone has been called, and the cycle continues across
lists and class days. Live mode uses the same rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 19:08:04 -07:00
parent 921f5d1c8a
commit c7e1058d19
11 changed files with 283 additions and 32 deletions

View File

@@ -320,6 +320,18 @@ def class_has_begun(session, course_id, day, now=None):
return now.time() >= row.start_time
def nonskipped_call_counts(session, course_id):
"""Calls per student excluding skipped ones — a student's position
in the cycle-mode pass. Pending list slots count: a printed line
is a claim on being called."""
rows = session.execute(
select(Call.student_id, func.count())
.where(Call.course_id == course_id, Call.status != STATUS_SKIPPED)
.group_by(Call.student_id)
).all()
return dict(rows)
def students_present(session, course_id, on_date):
"""Active student-role enrollees minus those opted out for the date."""
opted_out = select(OptOut.student_id).where(