Group session attendance
Outcome
Session supervisors take attendance and review supervision compliance before charges are committed, with the same logic as the post-claim scrubber so issues are caught in the room.
Prerequisites
| Scope | Action |
|---|---|
clinical.group-session.read | Read list/detail/preview |
clinical.attendance.write | Mutate attendance |
PLATFORM_ADMIN continues to bypass scope checks in code.
List → Detail flow
Filter
/group-sessionsby date range (?from=YYYY-MM-DD&to=…), facility ID, or group type.Click a row to land on
/group-sessions/:id.The header shows date/time/facility. The Supervisors card lists every distinct provider whose
staff_assignment.role = 'SUPERVISING'is joined to a service event whoseparticipant_idbelongs to this session.The Roster table presents per-participant attendance buttons limited to legal next states for the current status:
From Allowed next SCHEDULEDATTENDED,ABSENT,LATEATTENDEDABSENT,LATELATEATTENDED,ABSENTABSENTATTENDED,LATEIllegal transitions return HTTP 409 — no client-side regression ever lands.
Billing preview semantics
GET /api/v1/group-sessions/:id/billing-preview re-runs the same helpers
GroupBillingStage consumes (countBillableParticipants,
resolveGroupSizeBand) so the warnings the UI shows are the warnings
the scrubber will fire later.
The preview resolves the primary active member.coverage_policy for
any participant in the session and uses that payer + the session's
facility state for supervision-rule + size-band lookups. When no
coverage row matches (e.g. unenrolled roster), the preview reports
billableCount and any GROUP_UNATTENDED_BILLED issue but skips
payer-scoped checks.
Supervision-ratio breach
When the supervisor banner reads:
Session …: BCBA:RBT ratio 8:1 exceeds max 6
the same condition will fire GROUP_SUPERVISION_RATIO at scrub time.
Resolve by either:
Post-billing lock
Once billing.claim_line.group_session_id references the session
(typically once charges have been built and grouped), attendance
mutations return HTTP 409:
Session is referenced by claim lines; pass force=true to override
Pass {"force": true} in the PATCH body only when:
- A scrubber error needs to be cleared by correcting an in-flight attendance record, and
- You will manually re-run
claim-builderafterwards to refresh the affected claim lines.
The detail page surfaces a footer caveat ("Attendance changes after
billing apply with a force flag — use carefully") whenever
hasLinkedClaims=true.
Validation
| Check | Expected |
|---|---|
| Roster transitions | Match allowed-next table |
| Billing preview warnings | Match what the scrubber would later fire |
| Forced post-billing edit | Followed by a manual claim-builder rebuild |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| 409 on attendance change | Illegal transition, OR session referenced by claim lines | Either pick a legal next state, or pass force=true if you're rebuilding lines after. |
| Supervision banner won't clear after assigning a new supervisor | New supervisor's staff_assignment.role not SUPERVISING | Update the assignment role; the Supervisors card pulls from this column. |
| Billing preview shows 0 billableCount | Roster all ABSENT / LATE / unenrolled | Expected behavior. |
Latent schema note
Migration 108 dropped a legacy chk_participant_status constraint left
behind by 068. Pre-fix, every insert against a real DB into
service.service_participant would have failed silently through tests
that mocked the table. Post-108, both API and direct SQL inserts of
SCHEDULED attendance succeed. No data fix is needed — the column
already holds canonical values from migration 068.
Cross-references
- Per-diem & institutional — group sessions feed institutional billing where applicable.
- Bulk charge entry — entry-side equivalent for outpatient.