Skip to content

Entity Profiles

Entity profiles are the aggregated view of all field values for a specific entity instance. They serve as the single source of truth for entity attributes and enable form auto-fill.

Patient profiles are split across two sources: the portable profile (patient_persons) and org-specific custom field values (custom_field_values). Other entity types (specialist, appointment, organization) use only custom field values.


Patient profiles

A patient's profile has two layers:

Patient #123 profile
  ├── Portable profile (patient_persons — shared across all orgs)
  │     ├── name:               "Ana Pop"
  │     ├── date_of_birth:      "1985-03-12"
  │     ├── sex:                "Female"
  │     ├── blood_type:         "A+"
  │     ├── allergies:          ["Penicillin", "Latex"]
  │     ├── chronic_conditions: ["Hypertension"]
  │     ├── occupation:         "Engineer"
  │     ├── residence:          "Bucharest"
  │     └── insurance_entries:  [{"provider": "AXA", "number": "AXA-123", "type": "private"}]

  └── Org-specific profile (custom_field_values — scoped to this org)
        ├── referral_source:    "Physiotherapist"
        └── vip_status:         "true"

API endpoint for the full combined patient profile:

GET /v1/patients/{id}/profile

Returns both portable profile fields and org-specific custom field values, merged into a single response.

Reading the portable profile

sql
SELECT pp.date_of_birth, pp.sex, pp.blood_type, pp.allergies, pp.chronic_conditions,
       pp.occupation, pp.residence, pp.insurance_entries
FROM patients p
JOIN patient_persons pp ON pp.id = p.patient_person_id
WHERE p.id = ? AND p.organization_id = current_app_org_id()

Reading org-specific profile values

sql
SELECT cf.key, cf.label, cfv.value, cf.field_type, cf.options, cf.is_private
FROM custom_field_values cfv
JOIN custom_fields cf ON cf.id = cfv.custom_field_id
WHERE cfv.entity_type = 'patient'
  AND cfv.entity_id = ?
  AND cfv.organization_id = current_app_org_id()
ORDER BY cf.sort_order;

Updating the patient profile

Updates to portable profile fields write to patient_persons. Updates to org-specific fields write to custom_field_values. The PUT /v1/patients/{id}/profile endpoint handles both in a single request by routing each key to the correct destination.

Example:

json
PUT /v1/patients/123/profile
{
  "date_of_birth": "1985-03-12",
  "blood_type": "A+",
  "referral_source": "Physiotherapist"
}
  • date_of_birth → writes to patient_persons.date_of_birth
  • blood_type → writes to patient_persons.blood_type
  • referral_source → upserts custom_field_values for the org's referral_source field

Profile data flow (forms)

When a patient submits a form:

Form field with profile_field_key: "date_of_birth"
  → writes to patient_persons.date_of_birth

Form field with custom_field_id: 7 (referral_source)
  → upserts custom_field_values (entity_type='patient', entity_id=patients.id, custom_field_id=7)

When a new form is created for the same patient at any org:

Form field with profile_field_key: "date_of_birth"
  → pre-filled from patient_persons.date_of_birth (same value, cross-org)

Form field with custom_field_id: 7 (referral_source)
  → pre-filled from custom_field_values (org-scoped — only pre-fills in the same org)

See Form Auto-Fill → for the full mechanics.


Specialist profiles

Purpose: Provider credentials, specializations, certifications — all org-scoped.

Common fields:

  • license_number — Medical license ID
  • specialization — Primary specialty
  • certifications — Additional certifications
  • languages — Spoken languages

API endpoints:

GET /v1/specialists/{id}/profile     → Read profile
PUT /v1/specialists/{id}/profile     → Update profile

Access control: Specialist can edit their own profile. Admin can edit all in org.

All specialist profile data lives in custom_field_values — there is no portable profile for specialists.


Appointment profiles

Purpose: Appointment-specific metadata, internal notes — all org-scoped.

Common fields:

  • referral_source — How patient found the clinic (if tracked per appointment)
  • internal_notes — Staff notes (private)
  • billing_code — Insurance billing code
  • room_number — Physical location

API endpoints:

GET /v1/appointments/{id}/profile    → Read profile
PUT /v1/appointments/{id}/profile    → Update profile

Access control: Staff only (specialists, admins, customer support).


Organization profiles

Purpose: Org-level settings and configuration not modelled in the core organizations table.

API endpoints:

GET /v1/organizations/{id}/profile   → Read profile
PUT /v1/organizations/{id}/profile   → Update profile

Access control: Admin only.


Profile querying for segments

Portable profile fields on patient_persons are queryable via direct column access:

sql
-- Find all patients with blood type A+
SELECT p.id AS patient_id, pp.name
FROM patients p
JOIN patient_persons pp ON pp.id = p.patient_person_id
WHERE p.organization_id = current_app_org_id()
  AND p.deleted_at IS NULL
  AND pp.blood_type = 'A+';

-- Find all patients with a Penicillin allergy
SELECT p.id AS patient_id, pp.name
FROM patients p
JOIN patient_persons pp ON pp.id = p.patient_person_id
WHERE p.organization_id = current_app_org_id()
  AND p.deleted_at IS NULL
  AND 'Penicillin' = ANY(pp.allergies);

Org-specific custom field values are queryable via custom_field_values:

sql
-- Find all patients with referral_source = "Physiotherapist"
SELECT cfv.entity_id AS patient_id, pp.name
FROM custom_field_values cfv
JOIN custom_fields cf ON cf.id = cfv.custom_field_id
JOIN patients p ON p.id = cfv.entity_id
JOIN patient_persons pp ON pp.id = p.patient_person_id
WHERE cf.organization_id = current_app_org_id()
  AND cf.entity_type = 'patient'
  AND cf.key = 'referral_source'
  AND cfv.value = 'Physiotherapist';

See Segments → for segment rule evaluation details.


Profile visibility and privacy

Fields with is_private = true on a custom field are:

  • Visible in profile API responses (staff can see them)
  • Visible during form filling (patients can fill them)
  • Hidden from patient-facing PDFs and reports

Portable profile fields on patient_persons do not have an is_private flag — they are always visible to both staff and the patient themselves. Sensitivity is handled by the PHI encryption layer (phone_encrypted, emergency_contact_phone_encrypted).