[Scheduling] Standing availability, absences, and session-at-risk warnings #104
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
Voting (
webapp/backend/app/models/vote.py,models/timeslot.py, grid UIwebapp/frontend/src/components/VotingGrid.jsx) handles per-session polling, but nothing captures standing facts like "Sarah is out for all of August" or a group's minimum viable table size. GMs discover conflicts only after proposing times, and a confirmed session can quietly fall below quorum with no warning.Spec
(a) Per-user availability
UserWeeklyAvailability:user_id,weekday(0-6),start_time,end_time, stored with the user's own timezone (user.timezone,models/user.py:34) — campaign-zone-agnostic; converted at query time.UserAbsence:user_id,start_date,end_date, optionalnote("away 8/1–8/15").(b) Scheduling overlay
When a GM picks a time (direct/tentative) or creates vote slots, the UI shows who is unavailable for each candidate time: absences (date overlap) and weekly availability (time-range mismatch, converted between user zone and campaign zone). Backend: an endpoint like
GET /campaigns/{id}/availability?times=...returning per-member availability verdicts, consumed by the session create form andVotingGrid.jsx.(c) Campaign quorum
campaign.quorum_min_players(nullable int, default off) in campaign settings.(d) Session-at-risk warnings
For upcoming confirmed sessions, compute likely attendance = members − (absences overlapping the session time + explicit declines where vote data exists). When it drops below quorum:
publish_bot_event/publish_bot_event_asyncinwebapp/backend/app/services/bot_pubsub.py:29/:38(Redis channelqb:bot:notifications, consumed by the dispatch map inbot/questboard_bot/cogs/notifications.py:169) — with a newevent_type: "session_at_risk"and a matching bot handler;Evaluate inside the existing minutely poller (
poll_session_reminders,webapp/backend/app/tasks/reminder_tasks.py:998) or a sibling Beat task; dedupe with a sent-marker likeSessionReminderSent(models/session_reminder_sent.py) so exactly one at-risk notification fires per session (re-arm only if the session is rescheduled).Out of scope
Acceptance criteria
References
webapp/backend/app/models/user.py:34,models/campaign.py:41(timezone columns)webapp/backend/app/models/vote.py,models/timeslot.py,models/session_attendance.pywebapp/backend/app/services/bot_pubsub.py:29(publish_bot_event, channelqb:bot:notifications)webapp/backend/app/tasks/reminder_tasks.py:998(poll_session_reminders),models/session_reminder_sent.py(dedupe pattern)bot/questboard_bot/cogs/notifications.py:169(event dispatch map)webapp/frontend/src/components/VotingGrid.jsxFiled from the July 2026 full-project review.
Work-in-progress checkpoint (pausing mid-build)
Backend + bot: DONE and verified. Committed on branch
feat/104-availability(eca2357, pushed). Not yet merged — no PR yet because the frontend is unfinished.What's implemented and passing:
b9c0d1e2f3a4(down_revisiona8b9c0d1e2f3):user_weekly_availability,user_absences(both in the user's own timezone),campaigns.quorum_min_players(nullable, off by default), andsession_at_risk_notified(sent-marker keyed on(session_id, confirmed_time)so a reschedule re-arms). Migration verified to apply anddowngrade -1 → upgrade headround-trip against a real Postgres.availability_service: timezone-correct verdicts (a candidate/session UTC instant is converted into each user's own zone, then matched against weekly windows / absences; no windows =unknown, not unavailable).evaluate_session_at_risk= members − |absent ∪ explicit-"no"-voters|, at-risk only when quorum is set and attendance is below it.GET /api/campaigns/{id}/availability?times=…(per-member verdicts),GET/PUT /api/me/availability,GET/POST/DELETE /api/me/absences.quorum_min_playerson the campaign PATCH/response.at_risk: boolcomputed on the confirmed-session read.poll_session_at_risk(5 min): fires exactly onesession_at_riskbot event per(session_id, confirmed_time); idempotent, re-arms on reschedule. Bot: newsession_at_riskhandler in the notifications dispatch → GM-facing embed. Additive event → noBOT_CONTRACT_VERSIONbump.Verified: backend 421 passed (9 new), bot 187 passed (1 new),
ruff check+formatclean, migration round-trips.Remaining (to resume)
Nothing is deployed;
mainis unaffected.Update to the checkpoint above: the frontend delegate finished right as I was pausing, so the frontend is now committed as WIP (
559c30d, pushed) — but flagged unreviewed by me. Both commits are onfeat/104-availability.Delegate self-report (not yet orchestrator-verified): lint clean, 160/160 frontend tests (20 new), build green. It built: availability + absences editor on Profile,
quorum_min_playerson campaign settings, the voting-grid availability overlay + a session-create conflict hint, and the at-risk banner on SessionDetail. Dashboard at-risk badge deliberately skipped (the next-session list payload doesn't carryat_risk, and the spec said not to add a per-card request).On resume: review
559c30d, re-run lint/tests/build myself, then open the #104 PR → CI → merge. Nothing deployed;mainunaffected.