Patients
A patient is a real-world person receiving care. Their portable profile travels with them across clinics. Their clinical history stays private to each clinic.
What this enables
- A patient registers once and brings their profile — demographics, blood type, allergies, insurance — to every clinic they visit on the platform, without re-entering anything
- A daughter can book appointments and manage care for her elderly father who has no email or account
- Each clinic has its own patient list with its own clinical history — appointments, forms, files, and treatment plans from Clinic A are never visible to Clinic B
- Patient records are never deleted, only soft-deleted — preserving the clinical history required by HIPAA
- Admins can temporarily act on behalf of a patient (impersonation) to provide support, such as helping fill out a form
- Patients can be grouped dynamically into segments based on their profile data, form answers, and appointment history
How it works
The design separates two concerns:
patient_persons (who the person is — owned by the patient, portable)
└── patients @ Clinic A (that they are a patient here — owned by the clinic)
└── patients @ Clinic B (that they are a patient here — owned by the clinic)When a specialist at Clinic B looks up a patient, they see the person's name immediately. Once the patient signs the profile-sharing consent form, the specialist also sees date of birth, blood type, allergies, and insurance — without re-entry. They never see anything from Clinic A's appointments, forms, or files.
The portable profile
patient_persons is the patient's universal health identity. It holds:
- Core identity: name
- Demographics: date of birth, sex, phone, occupation, residence
- Universal health facts: blood type, allergies, chronic conditions
- Emergency contact: name and phone
- Insurance: a list of policies (employer, private, state)
This data is owned by the patient, not the org. However, a clinic can only see the full profile after the patient consents — see Profile sharing consent below.
Family and dependent accounts
A user account (login) and a patient identity are separate things. One login can manage multiple patient identities — useful when a family member handles someone's healthcare:
Ana (user_id: 42, email: [email protected])
├── manages herself → patient_persons id:1 (user_id: 42)
└── manages her father → patient_persons id:2 (user_id: NULL — no account)When Ana logs in, she sees a "booking for" switcher. Appointments, forms, and treatment plans she manages for her father are linked to his identity (id:2), not hers. If her father later creates his own account, the link is set and all his history is preserved.
The org-patient link
patients is a thin table that records that a person is a patient at a specific org. It holds nothing about the person — just the relationship and any external system references.
Profile sharing consent
When a patient registers at a new clinic, the clinic can only see the patient's name — the minimum needed for scheduling. The rest of the portable profile (DOB, blood type, allergies, insurance, etc.) is hidden until the patient explicitly consents.
This is controlled by a profile_shared boolean on the patients table (the org-patient link):
profile_shared | What org staff sees |
|---|---|
false (default) | person.name only — enough to schedule appointments |
true | Full portable profile — DOB, blood type, allergies, insurance, etc. |
The patient themselves always sees their own full profile regardless of this flag.
How it works:
- Patient is onboarded at a new clinic →
patientsrecord created withprofile_shared = false - Automation rules create a "Profile Sharing Consent" disclaimer form (blocking)
- Patient signs the consent form
- System sets
profile_shared = trueon thepatientsrecord - Clinic staff now sees the full portable profile
This consent is per-org — the patient must consent separately for each clinic. Signing at Clinic A does not grant Clinic B access.
Enforcement: RLS on patient_persons stays permissive (org staff can read the row for JOINs and name display). The field-level filtering is enforced at the application layer — the API strips extended fields when profile_shared = false.
What crosses clinic boundaries — and what doesn't
The portable profile and clinical records sit in two different legal categories:
| Data | Crosses clinics? | Why |
|---|---|---|
| Portable profile (name, DOB, blood type, allergies, insurance) | Yes — with consent | This is the patient's own identity data. They carry it themselves, like handing an insurance card to a new doctor. |
| Clinical records (appointments, forms, reports, treatment plans, files) | No — never | These are created by the clinic in the context of a care relationship. The clinic is a data controller for this information. |
If a patient needs to share a report from Clinic A with Clinic B, they download the PDF from their portal and upload it at the new clinic — the same workflow used in traditional healthcare.
The platform does not broker cross-clinic document sharing. Under HIPAA, cross-provider sharing requires formal written patient authorization (45 CFR § 164.508) — a higher bar than the consent gate used for profile sharing. Under GDPR, it constitutes a cross-controller data transfer. Facilitating this would classify RestartiX as a Health Information Exchange (HIE), bringing significantly heavier regulation.
Org-specific custom fields
Beyond the portable profile, clinics can define their own patient fields — referral source, preferred training surface, VIP status, etc. These are org-scoped and only visible within that clinic. See Custom Fields →.
Technical Reference
Everything below is intended for developers.
Data model
users (auth identity — managed by Clerk)
└── patient_persons (portable profile — patient-owned, no org_id)
├── patient_person_managers (who can manage this person)
└── patients (org-patient link — one per org per person)
└── organization_id (tenant isolation)patient_persons — portable profile columns
| Column | Type | Notes |
|---|---|---|
id | bigserial | Internal identity ID |
user_id | bigint, nullable, unique | Link to users table — NULL if no account |
name | text | Full name |
date_of_birth | date | |
sex | text | Male / Female / Other / Prefer not to say |
phone_encrypted | bytea | AES-256-GCM encrypted |
occupation | text | |
residence | text | |
blood_type | text | A+, A-, B+, B-, O+, O-, AB+, AB- |
allergies | text[] | Array of allergy names |
chronic_conditions | text[] | Array of condition names |
emergency_contact_name | text | |
emergency_contact_phone_encrypted | bytea | AES-256-GCM encrypted |
insurance_entries | jsonb | Array of {provider, number, type} objects |
patients — org-patient link columns
| Column | Type | Notes |
|---|---|---|
id | bigserial | Internal patient ID |
organization_id | bigint | Tenant isolation |
patient_person_id | bigint | Link to patient_persons |
profile_shared | boolean | false = org sees name only; true = full profile visible |
consumer_id | text | External system identifier (legacy migration) |
deleted_at | timestamptz | Soft delete — records are never hard-deleted (HIPAA) |
patient_person_managers
| Column | Type | Notes |
|---|---|---|
patient_person_id | bigint | The person being managed |
manager_user_id | bigint | The user managing them |
relationship | text | self / parent / child / spouse / sibling / caregiver / other |
Access control (RLS policies)
patient_persons (no org_id — user-based RLS):
- Superadmins — all records
- The person themselves or anyone managing them — their own record via
current_user_patient_person_ids() - Org staff — can read
patient_personsfor anyone registered at their org (joined throughpatients)
patients (org-scoped):
- Superadmins — all patients across all orgs
- Admin / Specialist / Customer Support — all patients in their org
- Patient / manager — their own and managed records via
current_user_patient_person_ids()
RLS helper function:
-- Returns all patient_person IDs the current user can act on behalf of:
-- their own identity + any dependents they manage
current_user_patient_person_ids() → SETOF BIGINTThis function is used in RLS on patients, appointments, forms, and any table that references patient_person_id.
Org-specific profile data
For data that is genuinely org-specific (not part of the universal profile), clinics use custom fields:
custom_field: {id: 7, entity_type: "patient", key: "referral_source", org_id: X}
└── custom_field_value: {entity_type: "patient", entity_id: patients.id, custom_field_id: 7, value: "Physiotherapist"}These values are org-scoped and not shared with other clinics. See Custom Fields →.
What each clinic sees
When a patient registers at a new clinic:
| Data | Before consent | After consent | Notes |
|---|---|---|---|
| Name | ✅ | ✅ | Always visible (minimum for scheduling) |
| DOB, sex, phone, occupation, residence | ❌ | ✅ | Requires profile_shared = true |
| Blood type, allergies, chronic conditions | ❌ | ✅ | Requires profile_shared = true |
| Emergency contact, insurance | ❌ | ✅ | Requires profile_shared = true |
| Appointments at other clinics | ❌ | ❌ | Org-scoped, never shared |
| Forms filled at other clinics | ❌ | ❌ | Org-scoped, never shared |
| Files uploaded at other clinics | ❌ | ❌ | Org-scoped, never shared |
| Treatment plans from other clinics | ❌ | ❌ | Org-scoped, never shared |
| Custom field values from other clinics | ❌ | ❌ | Org-scoped, never shared |
Segments
Patients can be grouped by rules across three data sources:
- Portable profile fields (
patient_personscolumns) - Org-specific custom field values
- Form responses
- Appointment history
Example: patients where blood_type = 'A+' AND forms.values.pain_level = 'high' AND appointments.count >= 2.
See Segments → for implementation details.