[Scheduling] Recurring session series with per-occurrence edits #98
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context / Motivation
Weekly groups are the core Quest Board user, yet every session is created one-off today:
models/session.pyhas no series concept — justscheduling_mode(vote/direct/tentative,SchedulingModeenum lines 47-52),status(proposed/confirmed/in_progress/completed/cancelled),confirmed_time/end_timeUTC columns. A GM running "every Tuesday at 19:00" recreates the session by hand every week.Spec
Data model — new
SessionSeries:id,campaign_id(FK, CASCADE)cadence:weekly|biweekly|monthly_by_weekday(e.g. "2nd Tuesday")weekday+time_of_dayinterpreted in the campaign timezone (campaign.timezone,models/campaign.py:41) so DST shifts keep the wall-clock time stablestart_date; end condition:until_date|count| open-ended (nullable pair)default_duration_minutes(materialized intoend_time); reminders inherit from the campaign'sreminder_offsets_minutes— no per-series reminder configtitle_template(optional),created_by, timestamps,activeflagsessions.series_id(nullable FK, SET NULL) linking materialized occurrencesMaterialization — a Celery Beat task (alongside
poll_session_reminders/auto_complete_sessionsinwebapp/backend/app/tasks/reminder_tasks.py:998/:1145) materializes concrete sessions a rolling window ahead (default 4 weeks) as normal direct-mode sessions withstatus=confirmed, linked byseries_id. Idempotent: an occurrence key (series_id + occurrence date) must guarantee exactly-once creation even if the task overlaps or reruns.Edit semantics:
series_id.UI: a "Make this recurring" option on session create (direct mode), plus a small series management panel in campaign settings (list series, edit cadence/end, deactivate).
Reminders: materialized sessions are ordinary confirmed sessions, so the existing reminder poller (
poll_session_reminders, offsets fromcampaign.reminder_offsets_minutes or [7*24*60, 24*60, 60], reminder_tasks.py:1054; idempotency viaSessionReminderSent) picks them up with no special-casing.Out of scope
Acceptance criteria
References
webapp/backend/app/models/session.py(SchedulingMode47-52,SessionStatus55-62,confirmed_time/end_time89-92)webapp/backend/app/tasks/reminder_tasks.py:998(poll_session_reminders),:1054(offsets),:1145(auto_complete_sessions— Beat task pattern to follow)webapp/backend/app/models/session_reminder_sent.py(reminder idempotency pattern to mirror for materialization)webapp/backend/app/models/campaign.py:41(timezone)Filed from the July 2026 full-project review.