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

@@ -39,7 +39,28 @@ def test_generate_call_list_downweights_within_list():
assert all(35 < counts[s] < 65 for s in STUDENTS)
def test_generate_cycle_list_covers_everyone_once():
def test_generate_cycle_batch_covers_everyone_once():
rng = random.Random(1)
picks = selection.generate_cycle_list(STUDENTS, rng)
picks = selection.generate_cycle_batch(STUDENTS, {}, None, rng)
assert sorted(picks) == sorted(STUDENTS)
def test_generate_cycle_batches_walk_the_pass():
rng = random.Random(1)
counts = {}
first = selection.generate_cycle_batch(STUDENTS, counts, 3, rng)
assert len(first) == 3
for s in first:
counts[s] = counts.get(s, 0) + 1
# The remainder batch is short, not wrapped into the next pass.
second = selection.generate_cycle_batch(STUDENTS, counts, 3, rng)
assert len(second) == 1
assert sorted(first + second) == sorted(STUDENTS)
for s in second:
counts[s] = counts.get(s, 0) + 1
# Everyone called once: a new pass opens with the full roster.
third = selection.generate_cycle_batch(STUDENTS, counts, 3, rng)
assert len(third) == 3
assert set(third) <= set(STUDENTS)