Requirements & Use Cases
SHS Digital Wellness Passport — CSUB Student Health Services (DxHub 2026)
Companion to architecture-plan.md. This is an initial set — a working draft to confirm with SHS
(Erika, Lauren) and ITS (Brian, Ydalia). Each requirement has a stable ID for traceability; each
use case maps back to the requirements it satisfies.
Priority: M = MVP / must-have for the camp demo · S = should-have · C = could-have / stretch.
1. Actors
| Actor | Description |
|---|---|
| Student | CSU student who opts into a wellness challenge; primary end user. |
| SHS Staff (Admin) | Health Services staff who author challenges, run events, and report. Erika (Director), Lauren (Health Ed Coordinator). |
| Event Attendant | Staff/volunteer stationed at an event who displays/scans QR codes (may be the same person as Admin). |
| Campus IdP | SAML identity provider that authenticates users and asserts current-student attributes. |
| AI Wellness Guide | System actor: Claude-backed assistant for education, conversation, and scoring. |
| Reporting Consumer | SHS leadership / CSU stakeholders who read participation & outcome reports. |
2. Functional Requirements
2.1 Identity & Access (FR-A)
| ID | Requirement | Pri |
|---|---|---|
| FR-A1 | Users authenticate via campus SAML SSO; no manual ID/9-digit entry. | M |
| FR-A2 | System stores only an opaque SSO subject + affiliation attributes (no PHI, no password). | M |
| FR-A3 | Participation may be gated on current-student attributes from the SAML assertion. | S |
| FR-A4 | Admin access is role-restricted (student role cannot reach builder/reports). | M |
| FR-A5 | Every record is scoped by campus_id to support multi-campus (CSU-wide) deployment. |
S |
2.2 Challenge Authoring (FR-B) — Admin
| ID | Requirement | Pri |
|---|---|---|
| FR-B1 | Admin creates a challenge with name, semester, start/end, and an ordered list of weekly tasks. | M |
| FR-B2 | Each task has: title, caption, activity type, location, date window, prize, required flag. | M |
| FR-B3 | Admin attaches assessment items (MCQ and/or reflection) to a task, each tagged to a learning outcome. | S |
| FR-B4 | Admin selects/edits a theme (palette, logo, hero art, copy tone) applied to the student app. | S |
| FR-B5 | (Retired by PASS-379.) Admin could AI-import a source document (Word/PDF) to auto-draft weeks, tasks, captions, and quiz items for editing. Retired so the deployed application holds no AI vendor SDK and makes no model call of any kind; staff-authored challenges (ChallengeBuilder.tsx) are the only way a challenge is created. |
S |
| FR-B6 | Admin can duplicate a prior challenge as a starting point. | C |
| FR-B7 | Staff review every proposed notification and explicitly send, edit, or skip it before it reaches a student; staff can also compose a broadcast (title, body, audience, send-now or schedule-for) from scratch. Approval publishes immediately to the student's in-app feed — there is no further dispatch step (web push is retired, PASS-628). Gated by Challenge.notifications_enabled; every decision is audited. The queue shows only audience size (enrollment) — the deliverable-reach preview is retired with the push transport it previewed (PASS-629). |
S |
| FR-B8 | Publishing a challenge that would displace the campus's current live challenge is refused, naming what would be displaced (challenge, enrolled students, recorded check-ins), unless staff explicitly acknowledge it. Publishing when nothing is live, re-publishing the live challenge, or publishing a challenge that would not win the active-challenge resolution needs no acknowledgment. | S |
| FR-B9 | Staff can preview any challenge at their campus (draft, published-but-shadowed, or live) rendered as a student would see it, without publishing it and without writing any enrollment, check-in, or assessment data. | S |
| FR-B10 | A semester's challenge can be retired. Archive is the operation for anything that ran: a third status alongside draft/published, retaining every task, response, check-in and audit row, excluded from the active-challenge resolver and from the builder's default list (reachable behind a filter), with reminders muted through the existing notifications_enabled kill switch. It is reversible via unarchive, which restores published and is held to FR-B8's displacement guard. Delete is permitted only for a challenge still in draft that carries no enrollment, check-in, assessment response or audit history; anything else is refused, naming every blocker, with archive offered instead. No force flag. Archived challenges stay reportable by explicit id. |
S |
FR-B7 policy ruling (PASS-433, US-78). PASS-390 shipped the staff authoring surface and
PASS-391 shipped dispatch, but nothing between them answered what a broadcast needs from
notification_preferences: compose_broadcast writes category="broadcast", dispatch only sends
to an opted_in=True row whose category matches, and nothing minted a broadcast row — every
broadcast inner-joined to zero devices, forever, silently.
Three candidates were on the table: (1) a real third student-facing category; (2) treat any
existing opt-in as blanket consent to be contacted at all; (3) bypass the preference join entirely,
gated only by the kill switches. Ruling: (1). "Silence is not consent"
(app/models/notification.py's own NotificationPreference docstring) stays true for a broadcast
exactly as it already was for new_week/expiring — a student who opted in to reminders has not
thereby agreed to announcements, and (3) would make "turning reminders off stops delivery" false,
a claim US-69/US-73 already make to ITS. The cost is explicit: on ship day nobody has a broadcast
row, so push reach is 0 for every broadcast — but the in-app feed
(app/services/notification_feed.py) is not preference-gated, so a broadcast still reaches every
enrolled student in-app, and the queue now shows the 0 instead of hiding it behind a sent status
that used to lie. Framed as "Announcements from staff", not a third reminder, so US-69's shipped
scenario titles ("both reminder categories read as off") stay literally true without amending a
merged feature file. No student-facing toggle for this category shipped in this story — the
preference row exists and is respected by dispatch, but nothing in the client UI yet lets a student
set it; that surface is left to a follow-up story rather than expanding this one's scope.
This ruling also fixed a failure mode that was not specific to broadcasts: dispatch_notifications
used to flip a row to sent unconditionally once its fan-out loop completed, even when it reached
zero devices. The status was sent only if at least one NotificationDelivery row existed for
the notification (checked against the table, not the current call's own send list, so a device an
earlier run already reached was not mislabeled); otherwise it was undeliverable. undeliverable was
included in the in-app feed's own status filter (_FEED_STATUSES) — the exact students who made a
broadcast undeliverable (no device, no consent) were the ones who most needed the fallback channel to
still show it. PASS-628 supersedes this paragraph — see below.
FR-B7 / FR-C8 policy ruling (PASS-628, US-82). Web push is retired. With no transport left, the
sent/undeliverable distinction the paragraph above describes has no producer: dispatch_notifications
(the only thing that ever wrote either status) is deleted, along with TERMINAL_STATUSES,
resolve_delivered_count, count_deliveries, count_deliveries_by_notification,
_has_any_delivery, and _already_delivered. The lifecycle collapses to
draft -> scheduled -> skipped; scheduled is now terminal, because approval itself publishes to
the in-app feed immediately. notification_feed._FEED_STATUSES narrows to ("scheduled",) only — a
row still carrying a legacy sent/undeliverable status (there is no migration; push was never
enabled in production, so no such row is expected to exist) is excluded, not shown. The staff queue's
delivered_count field is removed with its producer: there is no delivery receipt to wait for and no
honest device count to report anymore.
resolve_deliverable_reach (deliverable_reach on the queue) and the NotificationDelivery
table/model are kept, unused — deciding their fate belongs to the transport-removal story that
follows this one, not to a lifecycle-collapse story. This ruling deliberately does not touch the
student preference model (NotificationPreference, FR-C7's opt-in toggle): the in-app feed has
never been preference-gated (notification_feed.list_in_app_feed reads no preference row), so
"turning reminders off" no longer stops a student from seeing a reminder anywhere — a real policy
gap this story surfaces rather than resolves. A fresh ruling on whether the banner needs its own
consent gate is owed as a follow-up.
FR-C7 / FR-B7 policy ruling (PASS-629, US-85). The follow-up PASS-628 named: this story deletes
the transport resolve_deliverable_reach/_eligible_subscriptions previewed (PushSubscription),
the client-side surface that offered to turn push on (Reminders.tsx, pushClient.ts,
push-sw.js), and the consent storage nothing sends against anymore (NotificationPreference).
The "fresh ruling on whether the banner needs its own consent gate" the paragraph above left open is
answered here: no gate is added. Building one would be new work — the in-app feed has no
preference read path today — not a restoration of what this story removes, and is left to a future
story if a real need for per-category muting of the banner itself emerges. This also moots
PASS-426 and PASS-554 (both proposed broadcast opt-in paths over the now-deleted preference
surface); neither had started. deliverable_reach is dropped from the staff queue response —
audience_count (enrollment) is the only preview a row carries now, since there is no transport
left for a device count to describe.
2.3 Enrollment & Passport (FR-C) — Student
| ID | Requirement | Pri |
|---|---|---|
| FR-C1 | Student enrolls in the active challenge for their campus. | M |
| FR-C2 | Student views all weeks/tasks with status (locked / available / complete / missed). | M |
| FR-C3 | Student sees a progress countdown ("3 of 7 complete, 4 remaining"). | M |
| FR-C4 | App is mobile-first / installable (PWA) and usable offline for viewing progress. | M |
| FR-C5 | Student sees prize eligibility status derived from required-task completion. | S |
| FR-C6 | A week's status is gated by its own date window, not by completion of earlier weeks: a missed week stays open for catch-up instead of locking, and a week whose window hasn't opened yet stays locked. | M |
| FR-C7 | (Retired by PASS-629.) Student could register a device for push reminders (Web Push subscription storage) and choose per-category opt-in preferences. Retired along with the client-side opt-in surface (Reminders.tsx) that offered it — see the FR-C7 / FR-B7 policy ruling below. |
S |
| FR-C8 | A scheduled worker proposes draft reminders from challenge/task data (new-week, expiring); staff moving a row to scheduled publishes it immediately to the student's in-app feed (PASS-628 — web push is retired, so there is no further dispatch/fan-out step). |
S |
2.4 Check-In (FR-D)
| ID | Requirement | Pri |
|---|---|---|
| FR-D1 | Student marks a task complete by scanning an event QR in-app. | M |
| FR-D2 | Backend validates the scan (task active, student eligible, not duplicate) before recording. | M |
| FR-D3 | Alternative: Event Attendant scans the student's passport QR for higher-assurance events. | S |
| FR-D4 | Each check-in records student, task, timestamp, and method (event_qr / staff / manual). |
M |
| FR-D5 | Production: event QR is a signed, short-lived rotating token to prevent off-site sharing. | C |
| FR-D6 | Admin can manually add/override a completion with an audit trail. | S |
| FR-D7 | A student's own check-in requires scanning the live event QR — no self-service completion path. | M |
2.5 AI Education, Guide & Assessment (FR-E)
| ID | Requirement | Pri |
|---|---|---|
| FR-E1 | On check-in, the student receives a personalized tip/resource grounded in SHS content. | M |
| FR-E2 | Student can converse with a themed wellness guide grounded in SHS content. Ships flag-disabled for the pilot (PASS-378, PATH B) — the guide answers from a static corpus, never a model, so this was a product decision, not a data-processing one. | M |
| FR-E3 | Guide has guardrails: educational only, no diagnosis, crisis routing to real resources. Crisis resources stay reachable regardless of FR-E2's pilot flag (PASS-378). | M |
| FR-E4 | MCQ knowledge checks are auto-scored instantly; an admin may correct a score by hand should the answer key prove wrong (narrowed from FR-E5 by PASS-378). | S |
| FR-E5 | (Retired by PASS-378.) Free-text reflections are no longer AI-scored — reflections are stored and staff review them read-only ("mark reviewed"), never scored. The human-override mechanism this requirement introduced survives, narrowed to MCQ responses under FR-E4. See FR-E6. | S |
| FR-E6 | No student-authored text reaches a third-party model (strengthened by PASS-378). Model calls that do occur are server-side and staff-facing only (challenge import); no PHI sent; conversations minimally logged. (The clause extending this rule to the Web Push path — a push payload leaving the cluster to a browser vendor's push service, US-73 — is retired by PASS-629 with the transport it governed: no notification payload leaves the cluster at all, and hecvat-lite SUB-1 now reads N/A. An automatically-proposed reminder reaches one place, the in-app feed of an enrolled student already authorized to see the challenge it names.) | M |
2.6 Reporting (FR-F) — Admin
| ID | Requirement | Pri |
|---|---|---|
| FR-F1 | Report on participation and per-week completion funnel. | M |
| FR-F2 | Report auto-captured vs. manual attendance counts. | S |
| FR-F3 | Report engagement (content views, guide usage). | S |
| FR-F4 | Report aggregated learning-outcome scores by outcome tag. | S |
| FR-F5 | Export prize-eligible students (CSV) for the drawing. | M |
| FR-F6 | Reports are aggregated/privacy-aware; optionally surfaced in QuickSight. | C |
| FR-F7 | Report data is exportable as CSV at per-student, per-event, and aggregate grain — not only the prize list. | M |
| FR-F8 | Export a per-week prize-eligibility drawing list (CSV): one row per student per week they checked in to, independent of the task's required flag and of every other week. Distinct from FR-F5's challenge-level, all-required-tasks roster — the two describe different populations by design. |
M |
| FR-F9 | "Download all reports (ZIP)": one click downloads the five AGGREGATE reports (participation, attendance, engagement, per-event, learning-outcome) plus a manifest, in a single server-side-read archive. Excludes the two per-student exports (FR-F5, FR-F7's per-student grain) by design — they stay separate, explicitly-labelled clicks. | M |
2.7 Onboarding (FR-H) — Admin
Added by PASS-718. SHS asked at the 2026-09-02 call for a way to learn the app by doing, and the fall challenge will be administered by more people than the two named admins. A 35-page manual and a long walkthrough neither prove anyone did anything nor fit a nurse who needs one screen.
(Filed here rather than tagged straight onto a feature file. FR-G1..FR-G7 were introduced by PASS-717's feature-file tags alone and never reached this catalog. That is a gap to close, not a precedent to follow.)
| ID | Requirement | Pri |
|---|---|---|
| FR-H1 | A Getting started checklist inside the admin app, one short list per persona, whose every step completes from state already in the database. No control anywhere marks a step done, and no client-supplied value is read when deciding: the page reports what a person has been observed to do, never what they claim. | S |
| FR-H2 | Completion is durable and monotonic. Once a step has been observed complete it stays complete, because the evidence behind four of them is destructible for reasons unrelated to the person — the security-event ledger is purged on a retention schedule, Reset rehearsal (FR-G) deletes the rehearsal check-in by design, and archiving a challenge changes the challenge-shaped answers. A checklist that reverts cannot serve as a record. | S |
| FR-H3 | Each persona list is printable as a one-page view with no admin chrome and no session requirement, for staff who will never sign in (an attendant holds a display link, FR-D7, and has no account). The printed copy is generated from the same source as the on-screen list, so the two cannot drift. | C |
| FR-H4 | The page carries the campus's onboarding record: who has completed which list, and when. It names a person by a display name or the local part of their identifier, and never renders an sso_subject in the clear — the record is read on a shared screen. |
C |
3. Non-Functional Requirements
| ID | Requirement |
|---|---|
| NFR-1 | Privacy: no PHI; FERPA-aware handling of participation records; minimal identity. |
| NFR-2 | Security: SAML SSO, role-based access, encryption at rest (S3/RDS) and in transit (TLS). |
| NFR-3 | Supportability: production stack is C# / ASP.NET Core + Microsoft SQL Server for CSU teams. |
| NFR-4 | Portability / multi-tenant: single codebase deploys per CSU campus via campus↔IdP mapping. |
| NFR-5 | Usability: mobile-first, low-friction; QR scan-to-complete in a few taps. |
| NFR-6 | Themeability: semester re-skin is a config change, not a code change. |
| NFR-7 | Availability/scale: handle event-time bursts for ~hundreds of concurrent students; low steady load. |
| NFR-8 | AI safety: grounded responses, refusal on out-of-scope, hard-coded crisis escalation. |
| NFR-9 | Standalone: no dependency on the EHR ("Point and Click"); no external clinical integration. |
| NFR-10 | Observability: check-ins, scores, and errors are logged for reporting and audit. |
4. Use Cases
Each use case is written in Cockburn form on its own page under
docs/architecture/use-cases/, where it links the Gherkin feature files that are its
acceptance criteria, the test modules that bind them, and the architecture views that
realise it. The map is docs/architecture/README.md.
5. Traceability (use case → requirements)
| Use case | Requirements |
|---|---|
| UC-1 Auth & enroll | FR-A1, FR-A2, FR-A3, FR-C1 |
| UC-2 View passport | FR-C2, FR-C3, FR-C4, FR-C5, FR-C6 |
| UC-3 QR check-in | FR-D1, FR-D2, FR-D4, FR-D7, FR-E1 |
| UC-4 Staff verify | FR-D3, FR-D4 |
| UC-5 Build challenge | FR-B1, FR-B2, FR-B3, FR-B4 |
| UC-6 Personalized education | FR-E1, FR-E6 |
| UC-7 Wellness guide | FR-E2, FR-E3, FR-E6 |
| UC-8 Assessment | FR-E4, FR-E6 |
| UC-9 AI doc import | FR-B5 |
| UC-10 Reporting/export | FR-F1–FR-F9 |
| UC-11 Live event | FR-D4, FR-D6 |
| UC-12 Multi-campus | FR-A5, NFR-4 |
| UC-21 Staff reminder → student's in-app feed | FR-C8, FR-B7 |
6. Out of scope (confirmed by discovery call)
- EHR / "Point and Click" integration — proprietary, non-integrating (NFR-9).
- Storing or processing any PHI / clinical results.
- Native iOS/Android apps (PWA covers the need).
- Payment/prize fulfillment logistics (system only determines eligibility).
7. Assumptions to confirm with SHS / ITS
- CSUB test IdP/SP available for the demo, or SAML mocked for Friday.
- Amazon Bedrock (Claude) enabled in the account/region on the landing zone.
- Which SHS wellness materials are cleared to ground the guide/tips (RAG corpus).
- Exact crisis-routing resources (988, campus counseling, SHS front desk).
- "Good faith" check-in is acceptable for MVP; rotating signed QR deferred to production.
- Prototype stack confirmed as .NET/SQL Server (recommended) vs. Python.