feat: auto-fill attendance from recording speakers (#114) #195
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/114-attendance-autofill"
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?
Closes #114. Fourth feature of v3.7.0 Table Tools.
Proposes session attendance from who was in the voice channel, so the GM doesn't hand-enter it. Manual attendance is never overwritten.
Backend + bot (
80f14f8)AttendanceProposalside table (sourcespoke|in_channel_silent), distinct fromSessionAttendance; migrationc6d7e8f9a0b1(round-trips). Campaign gainsattendance_autofill(propose|auto_apply|off, default propose); Session gainsattendance_unmatched_speakers(JSONB).generate_attendance_proposals: resolves Discord IDs via verified discordPlatformLinks only; captured speakers →spoke, in-channel-silent →in_channel_silent(spoke wins), unlinked speakers →unmatched(never applied). Idempotent (delete-then-reinsert).bulk_insert_attendance_absent(insert-if-absent), used by bothapplyandauto_apply. The overwritingupsert_attendanceis untouched/unused here.process_audiohook is savepoint-isolated + try/except (a failure never fails audio processing or changes status) and mode-aware (offwrites nothing).GET .../attendance-proposals,POST .../apply({applied, skipped_existing}, then clears proposals),DELETE(dismiss).BOT_CONTRACT_VERSIONbump): sendsuncaptured_member_ids(silent voice members) in the audio-notify payload; backend persists them topresence.jsonin the session dir so a reprocess still has them.Frontend (
<head>)attendance_autofillcampaign setting with helper text.Tests
test_attendance_proposals.py) — spoke/silent/unmatched split, verified-link-only, idempotent regen,off, the apply/auto_apply invariant (manual row survives), endpoint counts, non-GM 403. Full suite 486 pass; migration round-trip verified.Notes
🤖 Generated with Claude Code
Proposes session attendance from who was in voice: captured speakers → "spoke", in-channel-but-silent members → "in_channel_silent", unlinked Discord users → surfaced as "unmatched" (never applied). Manual attendance is never overwritten. Backend: - AttendanceProposal side table (source spoke|in_channel_silent) distinct from SessionAttendance; migration c6d7e8f9a0b1 (round-trips). Campaign gains attendance_autofill (propose|auto_apply|off, default propose); Session gains attendance_unmatched_speakers (JSONB) for the banner. - attendance_service.generate_attendance_proposals: resolves Discord IDs via VERIFIED discord PlatformLinks only; spoke takes precedence over silent; idempotent (delete-then-reinsert). THE INVARIANT lives in one chokepoint, bulk_insert_attendance_absent — inserts only where no attendance row exists, used by both apply and auto_apply; upsert_attendance (overwriting) is unused here. - process_audio hook: savepoint-isolated + try/except so a failure never fails audio processing or changes status; mode-aware (off writes nothing). - Endpoints (GM): GET .../attendance-proposals (proposals + unmatched), POST .../apply (insert-if-absent, returns {applied, skipped_existing}, clears proposals), DELETE (dismiss). Bot (additive — no BOT_CONTRACT_VERSION bump): - post_audio_tracks sends optional uncaptured_member_ids; recording snapshots silent voice members before disconnect. Backend persists them to presence.json in session_dir so a reprocess still has them. Tests: +8 (test_attendance_proposals.py) — spoke/silent/unmatched split, verified-link-only, idempotent regen, off, the auto_apply/apply invariant (manual row survives), endpoint counts, non-GM 403. Full suite 486 pass; migration round-trip verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>