# Participants core — phase 1 of NDIS and Home Care

Status: **Phase 1 built 2026-11-04** (steps 1–5: backend, profile, setup wizard and list, funding on booking,
service deliveries and Activity, header search, terminology, guide). Next: Phase 2 (incidents), then the worker app. Decided: archive = `inactive`; participant photo in Phase 1;
medications and medical history later (eMAR module); participants stay admin-only in Phase 1.

Owner module: **Participants** (`participants`, default off). It holds the person and
their care, and never knows who pays. Funding schemes are separate modules that attach
to a participant and listen for what was delivered, the way Agency Billing and Payroll
listen to `ShiftApproved` today:

| Module | Owns | When |
|---|---|---|
| `participants` (this doc) | The person, contacts, consents, alerts, documents, goals and plan, funding arrangements (the shell), service deliveries, progress notes | Phase 1 |
| `participants` | Incidents and complaints | Phase 2 |
| `ndis` | Plan details (number, dates, management type, plan manager), budgets by support category, NDIS price list, agreement lines, bulk payment requests, then PACE | Later |
| `support-at-home` | Classification and pathway, quarterly budgets, care-management share, contributions, monthly statements, Services Australia claims | Later |
| private billing (TBD) | Invoices to private and third-party payers from the agency's own price list | Later |

Rules that follow:

- The Participants module never reads a scheme module's tables. Scheme modules read
  participants and listen to its events.
- No money in Phase 1. A funding arrangement records **who pays and their reference**,
  nothing priced.
- The database keeps the `clients` names (CLAUDE.md, Participants module); new tables are
  `participant_*`, foreign key `client_id` for consistency with `shifts.client_id`.
- Integer ids, like the rest of the schema. Dates without times are `date` columns.
- The worker app comes after the admin side has been seen (decided 2026-11-03).

Background: `Participant Management System — Market Tour & Unified Design` (Nov 2026)
and the UX review in the same conversation (ShiftCare, AlayaCare, Brevity).

## Who counts as a "private" or "third-party" client

Anyone whose services aren't paid by NDIS or Support at Home: self-funded or family-funded
clients, people waiting for a plan or budget, top-ups beyond a plan, services a scheme won't
fund, short-term help after hospital. **Third-party** is the same idea with an outside payer:
TAC, icare, workers' compensation, injury schemes, DVA, a hospital. One person can hold
several arrangements at once (an NDIS plan and private top-ups) and move between them (an
NDIS participant turning 65 moving to Support at Home is a new arrangement, not a new person).

## Data (tenant DB)

### `clients` (existing; the participant)

Kept: names, email, phone, DOB, address and coordinates, organisation (optional), status
(`draft → pending_review → active ⇄ suspended`, `inactive`), approval fields.

Added:

| Column | Notes |
|---|---|
| `preferred_name` | Shown first everywhere when set |
| `gender` | Free text with suggestions (not an enum) |
| `atsi_status` | `aboriginal`, `torres_strait_islander`, `both`, `neither`, `not_stated` |
| `cultural_background` | Free text |
| `preferred_language` | Free text, default English |
| `interpreter_required` | bool |
| `communication_needs` | text: how to communicate (e.g. Auslan, picture cards, slow speech) |
| `living_arrangement` | Free text (lives alone, with family, SIL…) |
| `setup_completed_at` | null while the setup wizard has steps left (resume is derived; this only marks "done") |

`client_type` stays for now and is no longer edited: the list and header show the funding
arrangements instead. Existing values are backfilled into arrangements (below).

### `participant_identifiers`

`client_id`, `type` (`ndis`, `my_aged_care`, `medicare`, `dva`, `other`), `value`, `label`
(for `other`), timestamps. One row per type per participant; NDIS and My Aged Care numbers are
unique within the agency (a duplicate is refused with a link to the existing participant).

### `participant_contacts`

`client_id`, `name`, `relationship` (free text: mother, son, support coordinator, GP…), `phone`,
`email`, `notes`, and role flags:

| Flag | Meaning |
|---|---|
| `is_primary` | First person to call. One per participant: choosing another moves it |
| `is_emergency` | Emergency contact. Several allowed |
| `is_decision_maker` | Can make decisions for the participant (several allowed, e.g. joint guardians); `authority` (required) says what kind: `nominee` (NDIS), `guardian`, `registered_supporter` (Aged Care Act), `power_of_attorney`, `other` |
| `is_billing` | Receives invoices (used later by billing modules) |
| `receives_updates` | Kept informed (later: family portal) |

Soft-deleted. A participant may also be their own decision-maker (no contact needed).

### `participant_consents`

`client_id`, `type` (`service_delivery`, `information_sharing`, `photos_media`,
`emergency_treatment`, `other` with `label`), `status` (`granted`, `declined`, `withdrawn`),
`given_by` (`participant` or a contact id), `given_on`, `expires_on` (null = no expiry),
`document_id` (optional, the signed form), `notes`, `recorded_by`. New consent of the same type
supersedes the old one (kept as history, `superseded_at`).

### `participant_alerts`

`client_id`, `category` (`clinical`, `behaviour`, `home_safety`, `communication`, `other`),
`severity` (`critical`, `important`, `info`), `title` (short, shown in the header strip),
`details`, `review_on`, `active`, `created_by`, `ended_by`, `ended_at`. Alerts are ended, never
deleted. Critical and important ones are shown on the participant header and the shift page
(and later in the app).

### `participant_documents`

Uploads only (decided 2026-11-03). `client_id`, `type` (`plan`, `assessment`,
`service_agreement`, `consent_form`, `report`, `other`), `title`, `file_name`,
`original_name`, `size`, `mime`, `issued_on`, `expires_on`, `notes`, `uploaded_by`;
soft-deleted (the file stays until a cleanup like the HCP offboarding's). Storage folder
`participant_documents` → `uploads/participants/{id}/documents` (config/storage_paths.php),
through `TenantFileStorageService`.

### Support or care plan and goals

A participant has one **active** plan at a time; a new version starts as a draft copied from the
active one, and publishing it supersedes the old one (history kept, like contract versions).

`participant_plans`: `client_id`, `version`, `status` (`draft`, `active`, `superseded`),
`summary` (who they are, what matters to them), `support_needs` (text), `effective_from`,
`review_on`, `published_by`, `published_at`.

`participant_goals`: `plan_id`, `domain` (from config: daily living, health and wellbeing,
home, relationships, social and community, learning, work, choice and control), `statement`
(in the participant's words), `target_on`, `status` (`not_started`, `in_progress`,
`achieved`, `discontinued`), `sort`.

`participant_goal_strategies`: `goal_id`, `description`, `sort`. Workers will score progress
on these from the app later; Phase 1 only records them.

### `funding_arrangements`

The join point the scheme modules hang their detail on.

| Column | Notes |
|---|---|
| `client_id` | |
| `scheme` | `ndis`, `support_at_home`, `private`, `third_party`, `other` |
| `status` | `pending` (waiting for a plan or budget), `active`, `ended` |
| `starts_on`, `ends_on` | `ends_on` null = open-ended |
| `reference` | Plan number, approval or claim number |
| `payer_name` | Third-party: TAC, icare, DVA…; private: who pays (or the billing contact) |
| `payer_contact_id` | Private: the participant's billing contact |
| `case_manager_name/phone/email` | Third-party |
| `notes` | |

Scheme modules add their own tables keyed by `funding_arrangement_id` (`ndis_plans`,
`sah_classifications`, …) and may require fields before an arrangement can be active. Phase 1
offers NDIS and Support at Home arrangements with just the reference and dates.

Backfill from `client_type`: `ndis` → `ndis`, `aged_care` → `support_at_home`, `dva` →
`third_party` (payer DVA), `private` → `private`, `other` → `other`; all `pending` with no
dates, so an admin confirms them.

### Shifts

`shifts.funding_arrangement_id` (nullable): which arrangement a participant shift is under,
chosen when booking when the participant has more than one active (one is picked
automatically). Facility shifts leave it null. Group shifts (several participants on one
shift) are not in Phase 1.

### `participant_service_deliveries`

What was delivered, per participant per worked shift; the record scheme modules price.

`client_id`, `shift_id`, `timesheet_id`, `funding_arrangement_id`, `hcp_id`, `service_date`,
`started_at`, `ended_at` (shift-local, from the approved timesheet), `worked_minutes`,
`break_minutes`, `km` (null until the app records it), `status` (`delivered`, `voided`),
`voided_reason`. Unique on `shift_id + client_id`.

Created by a listener, never by Rostering (CLAUDE.md "Rostering must not depend on downstream
modules"):

- `ShiftApproved` (final) on a participant shift, module on → upsert the delivery from the
  shift's timesheet → fire **`ParticipantServiceDelivered`**.
- `ShiftVoided`, or a cancel of a completed shift → mark it voided → fire
  **`ParticipantServiceVoided`**.
- An approval changed later (re-approve) updates the delivery and fires
  `ParticipantServiceDelivered` again; listeners must be idempotent.

With no scheme module on, nothing listens and nothing is billed.

### `client_progress_notes` (existing)

Added: `author_type` (`admin`, `worker`), `visibility` (`team`, `internal`), `goal_id`
(optional), `edited_at`. Admins can write notes from the SPA in Phase 1; workers from the app
later. Notes are never deleted; edits are recorded.

## API (SPA, `admin.only` + `module:participants`)

Existing `/participants` routes stay. New, all under `/participants/{id}`:

- `GET/PUT /` profile (incl. the new columns), `GET /summary` (header + overview cards in one call)
- `/identifiers`, `/contacts`, `/consents`, `/alerts`, `/documents` (+ `/{doc}/url`),
  `/funding` — index, store, update, end/delete as each allows
- `/plans` (list), `POST /plans` (new draft from the active one), `PUT /plans/{plan}`,
  `POST /plans/{plan}/publish`, goals and strategies nested under the plan
- `/progress-notes` (exists; gains store and edit)
- `/activity` — shifts, deliveries and notes merged, newest first

Permissions: `view-participants` reads, `manage-participants` writes (existing). A separate
`view-participant-care` for clinical detail is a later question.

## SPA (UX decided 2026-11-03)

**Participants list** (Service Recipients → Participants): tabs All, **Needs attention**
(setup incomplete, consent or document expired or due in 30 days, plan review due, alert
review due), Archived; filters funding scheme and organisation; columns name (preferred first),
reference, funding chips, organisation, next shift, status.

**Participant profile**

- **Header, visible on every tab**: preferred name (legal name under it), reference, age,
  status, organisation, **funding chips** with dates, and a **"Need to know" strip** of
  critical (red) and important (amber) alerts that can't be dismissed and links to the full
  list. Actions: **Book shift**, **Add note**, ⋮ (upload document, add contact, add alert,
  archive) — the same pattern as the worker profile.
- **Five tabs**:
  - **Overview** — cards: About (language, interpreter, communication, culture, living
    arrangement), Key contacts, Funding, Active plan and goals, Upcoming shifts, Recent notes,
    Expiring consents and documents. Each card has "Manage" to its tab.
  - **Care** — alerts and needs, the active plan with goals and strategies, draft and
    previous versions.
  - **People** — contacts with role chips, consents as a checklist with expiry chips.
  - **Documents** — drag-and-drop upload, type, expiry chips (red expired, amber ≤ 30 days).
  - **Activity** — shifts, deliveries and notes in one timeline.
- **Edit per card, in place** (the card becomes a form), never a page-wide edit mode.
- **Forms start short**: creating a participant asks only what booking a first shift needs;
  the rest is "Add more" sections collapsed on their cards.

**Setup wizard** (`WizardDialog`, never closes by itself, each step saves): details → contacts
and decision-maker → funding → alerts and needs → done ("Book a first shift"). The profile
shows "Setup incomplete · Continue setup" while steps are missing (derived, not stored).

**Shift page and booking**: the participant's critical and important alerts on the shift
page; the funding arrangement chosen when booking (when there's more than one active).

**Elsewhere**: header search gains a Participants group (name, preferred name, NDIS / My Aged
Care number, phone); terminology gains `participant` (Participant / Client / Resident) in
`TenantSettingsService::TERMS` and `useTerms()`; guides: a "Participants" guide in plain words.

## Build order

1. Migrations, models, backfill; services per area (`ParticipantProfileService`,
   `ParticipantContactService`, `ParticipantCareService` for alerts and plans,
   `ParticipantDocumentService`, `FundingArrangementService`); API.
2. Profile: header, Overview, People, Documents, Care.
3. Setup wizard, list tabs and filters.
4. Funding on booking; `participant_service_deliveries` listener and events; Activity tab;
   admin progress notes.
5. Header search, terminology, guide, CLAUDE.md.

Each step is usable on its own and pushed separately.

## Open questions

1. Archive: is `inactive` enough, or a separate `archived` status with an offboarding-like
   cleanup later?
2. Participant photo on the header: wanted in Phase 1?
3. Medications and medical history (conditions, allergies): Phase 1 alerts cover safety; a
   proper medication module (eMAR) is later. Confirm.
4. Facility and organisation logins: participants stay admin-only in Phase 1?
