Skip to content

Segments

Auto-updating patient groups based on rules—find "high pain patients" or "inactive 6+ months" without manual lists.

What this enables

Smart patient cohorts: Group patients by conditions—"Pain level = high", "From Bucharest", "Hasn't booked in 6 months"—without manually dragging names into folders.

Auto-updates: As patients fill forms or get appointments, segments refresh automatically. A patient's pain level goes from "low" to "high"? They move to the high-pain segment instantly.

Targeting campaigns: Send emails to "patients with active pain" or "inactive patients due for checkup" automatically.

Multi-rule cohorts: Combine conditions—"Age 65+, from Bucharest, AND less than 2 appointments this year" → complex cohorts without code.

Audit trail: Track when segments change, when rules are updated, keep historical records.

How it works

  1. Admin creates a segment: "High Pain Patients"
  2. Admin defines rules:
    • Form response: pain_level = "Big pain"
    • AND custom field: age >= 65
    • AND appointment metric: < 2 appointments in last year
  3. System evaluates: Automatically finds all patients matching all rules
  4. Patient updates: Patient fills form answering "Big pain" → automatically added to segment
  5. Patient changes: Pain level updates to "Low" → automatically removed from segment
  6. Use the segment: Send campaign to all patients in segment, run reports, bulk actions

Technical Reference

Overview

Segments are rules-based patient grouping that automatically evaluates membership based on form responses, profile data, and appointment history. They enable dynamic patient cohorts that stay up-to-date as data changes.

Core Concepts

Segment: A named group of patients defined by rules rather than manual assignment. Rules are evaluated automatically when data changes.

Rule: A condition that a patient must meet to belong to a segment. Rules can query:

  • Form responses (values from form instances)
  • Profile data (custom_field_values for patient entity)
  • Appointment metrics (count, dates, status)

Match Mode: How multiple rules combine:

  • all (AND) — patient must match ALL rules
  • any (OR) — patient must match ANY rule

Auto-Evaluation: Segment membership is updated automatically:

  • When forms are saved (real-time or async)
  • When profile data is updated
  • On-demand via API (manual refresh)

Use Cases

SegmentRules
High Pain PatientsForm response: pain_level = "Big pain"
Bucharest PatientsProfile: city = "Bucharest"
At-Risk SeniorsProfile: age >= 65 AND Appointments: count < 2 in last 6 months
Active PatientsAppointments: last_date >= now-30d
Complex CohortsMulti-source rules with nested groups (see operators.md)

Key Features

Real-Time Membership Updates

When a patient fills a form or updates their profile, segments referencing that data are automatically re-evaluated. No manual refresh needed for most use cases.

Multi-Source Rules

A single segment can combine:

  • Form responses: "answered X in survey Y"
  • Profile data: "blood type A+, from Bucharest"
  • Appointment history: "had 2+ appointments in last year"

Tiered Evaluation Strategy

  • Tier 1 (Inline): Simple segments (≤3 rules, single source) evaluate synchronously during form save
  • Tier 2 (Async): Complex segments (multi-source, >3 rules) evaluate via background worker
  • Tier 3 (Batch): Full rebuild on-demand via API

See performance.md for detailed evaluation strategy.

Queryable History

Segment membership changes are tracked via matched_at timestamp. Rule changes are versioned for audit and rollback.

Architecture

┌──────────────┐
│   Segments   │  Rules definition (JSONB)
└──────┬───────┘

       │  Evaluates against

┌─────────────────────────────────┐
│  Patient Data (3 sources)       │
│  1. forms.values (JSONB)        │
│  2. custom_field_values (TEXT)  │
│  3. appointments (aggregates)   │
└─────────────────────────────────┘

       │  Membership stored in

┌──────────────────┐
│ segment_members  │  patient_id + segment_id + matched_at
└──────────────────┘

Why This Design

ProblemSolution
Manual patient groups don't scaleRules auto-evaluate on data changes
Can't filter by form responsesForms store values as queryable JSONB
Need complex multi-condition filteringSupport nested rule groups with AND/OR logic
Real-time updates too expensive at scaleTiered evaluation: inline for simple, async for complex
Historical forms skew current stateMulti-instance resolution: latest completed form wins

Quick Start

Create a Simple Segment

bash
POST /v1/segments
{
  "name": "High Pain Patients",
  "description": "Patients reporting big pain in intake survey",
  "match_mode": "all",
  "rules": [
    {
      "source": "form",
      "template_id": 5,
      "custom_field_id": 11,
      "op": "eq",
      "value": "Big pain"
    }
  ]
}

Create a Multi-Source Segment

bash
POST /v1/segments
{
  "name": "Bucharest High Pain Seniors",
  "match_mode": "all",
  "rules": [
    {"source": "profile", "custom_field_id": 10, "op": "eq", "value": "Bucharest"},
    {"source": "profile", "custom_field_id": 12, "op": "gte", "value": 50},
    {"source": "form", "template_id": 5, "custom_field_id": 15, "op": "eq", "value": "Big pain"},
    {"source": "appointments", "metric": "count", "op": "gte", "value": 2, "filters": {"status": "done"}}
  ]
}

Note: Rules reference custom_field_id (from the field library) rather than arbitrary keys. This ensures:

  • Type-safe queries (field type is known)
  • Consistent lookups across forms and profiles
  • Compatibility with versioned field definitions

Query Segment Members

bash
GET /v1/segments/{id}/members
GET /v1/segments/{id}/members?fresh=true  # Force re-evaluation first
GET /v1/patients/{id}/segments            # Which segments does this patient belong to?

Manual Rebuild

bash
POST /v1/segments/{id}/evaluate  # Full rebuild from scratch

Documentation Structure

  • README.md (this file) — Overview and concepts
  • schema.sql — Tables, indexes, RLS policies
  • api.md — HTTP endpoints and request/response formats
  • rule-engine.md — Rule evaluation logic and multi-instance resolution
  • performance.md — Tiered evaluation, scaling, benchmarks
  • operators.md — All supported operators and rule sources
  • examples.md — Complex query examples and real-world use cases
  • Forms (../forms/) — Form instances provide response data (forms.values JSONB)
  • Custom Fields (../custom-fields/) — Versioned field library + profile data (custom_field_values)
  • Appointments (../appointments/) — Appointment history and metrics

Data Flow:

  1. Patient fills form → data saved to forms.values AND synced to custom_field_values
  2. Segments query BOTH sources:
    • source: "form" → queries forms.values (historical/all responses)
    • source: "profile" → queries custom_field_values (current patient state)
  3. Both sources reference custom_field_id for type-safe, consistent queries

Constraints

  • Max nesting depth: 3 levels for nested rule groups (prevents runaway query complexity)
  • Relative dates: Supported via now-30d, now-6M, now-1y syntax (see operators.md)
  • Form instance resolution: Latest completed/signed form wins when multiple instances exist
  • Evaluation SLOs: <50ms inline, <5s async, <30s full rebuild (10K patients)

Migration Notes

From legacy system: No direct equivalent exists. Segments replace manual filtering and ad-hoc SQL queries for patient cohorts. Existing workflows that use "find patients where X" should be migrated to segments for automatic updates.

Data requirements:

  • Forms must store values as plaintext JSONB (not encrypted) for queryability
  • Custom field values must be TEXT (not BYTEA) for profile queries
  • Appointments table must support status and date filtering

Version History

Segment rules are versioned. When rules change, the current version is archived to segment_versions table. This enables:

  • Audit trail of rule changes
  • Rollback to previous rule definitions
  • Understanding why membership changed over time

See schema.sql for version table structure.