Skip to content

Segment Examples

Simple Examples

Example 1: Form Response Filter

Use case: All patients who reported "Big pain" in intake survey.

json
{
  "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"
    }
  ]
}

SQL equivalent:

sql
SELECT DISTINCT p.id, pp.name
FROM patients p
JOIN patient_persons pp ON pp.id = p.patient_person_id
JOIN forms f ON f.patient_person_id = p.patient_person_id
WHERE p.organization_id = :org_id
  AND f.form_template_id = 5
  AND f.status IN ('completed', 'signed')
  AND f.values->>'field_11' = 'Big pain'
ORDER BY f.updated_at DESC;

Example 2: Profile Filter

Use case: All patients located in Bucharest.

json
{
  "name": "Bucharest Patients",
  "description": "All patients located in Bucharest",
  "match_mode": "all",
  "rules": [
    {
      "source": "profile",
      "custom_field_id": 10,
      "op": "eq",
      "value": "Bucharest"
    }
  ]
}

SQL equivalent:

sql
SELECT p.id, p.name
FROM patients p
JOIN custom_field_values cfv ON cfv.entity_id = p.id AND cfv.entity_type = 'patient'
WHERE p.organization_id = :org_id
  AND cfv.custom_field_id = 10
  AND cfv.value = 'Bucharest';

Example 3: Appointment Count Filter

Use case: Patients with at least 2 completed appointments.

json
{
  "name": "Active Patients",
  "description": "Patients with 2+ completed appointments",
  "match_mode": "all",
  "rules": [
    {
      "source": "appointments",
      "metric": "count",
      "op": "gte",
      "value": 2,
      "filters": {
        "status": "done"
      }
    }
  ]
}

SQL equivalent:

sql
SELECT p.id, pp.name
FROM patients p
JOIN patient_persons pp ON pp.id = p.patient_person_id
WHERE p.organization_id = :org_id
  AND (
    SELECT COUNT(*)
    FROM appointments a
    WHERE a.patient_person_id = p.patient_person_id
      AND a.organization_id = :org_id
      AND a.status = 'done'
  ) >= 2;

Multi-Source Examples

Example 4: Combined Form + Profile

Use case: High pain patients in Bucharest.

json
{
  "name": "Bucharest High Pain",
  "description": "Bucharest patients reporting big pain",
  "match_mode": "all",
  "rules": [
    {
      "source": "profile",
      "custom_field_id": 10,
      "op": "eq",
      "value": "Bucharest"
    },
    {
      "source": "form",
      "template_id": 5,
      "custom_field_id": 11,
      "op": "eq",
      "value": "Big pain"
    }
  ]
}

SQL equivalent:

sql
SELECT p.id, pp.name
FROM patients p
JOIN patient_persons pp ON pp.id = p.patient_person_id
WHERE p.organization_id = :org_id

  -- Profile rule: city (custom_field_id = 10)
  AND EXISTS (
    SELECT 1 FROM custom_field_values cfv
    WHERE cfv.entity_type = 'patient' AND cfv.entity_id = p.id
      AND cfv.custom_field_id = 10 AND cfv.value = 'Bucharest'
  )

  -- Form rule: pain_level (custom_field_id = 11)
  AND EXISTS (
    SELECT 1 FROM forms f
    WHERE f.patient_person_id = p.patient_person_id
      AND f.organization_id = :org_id
      AND f.form_template_id = 5
      AND f.status IN ('completed', 'signed')
      AND f.values->>'field_11' = 'Big pain'
    ORDER BY f.updated_at DESC
    LIMIT 1
  );

Example 5: The Complex Query

Use case: "Patients with blood type A, from Bucharest, with big pain, 50 years old, and at least 2 completed appointments"

json
{
  "name": "Target Group - Bucharest A-type High Pain Seniors",
  "description": "Complex target group for outreach campaign",
  "match_mode": "all",
  "rules": [
    {
      "source": "profile",
      "custom_field_id": 12,
      "op": "eq",
      "value": "A+"
    },
    {
      "source": "profile",
      "custom_field_id": 10,
      "op": "eq",
      "value": "Bucharest"
    },
    {
      "source": "profile",
      "custom_field_id": 13,
      "op": "eq",
      "value": "50"
    },
    {
      "source": "form",
      "template_id": 5,
      "custom_field_id": 11,
      "op": "eq",
      "value": "Big pain"
    },
    {
      "source": "appointments",
      "metric": "count",
      "op": "gte",
      "value": 2,
      "filters": {
        "status": "done"
      }
    }
  ]
}

SQL equivalent:

sql
SELECT p.id, pp.name
FROM patients p
JOIN patient_persons pp ON pp.id = p.patient_person_id
WHERE p.organization_id = :org_id

  -- Rule 1: blood_type (custom_field_id = 12)
  AND EXISTS (
    SELECT 1 FROM custom_field_values cfv
    WHERE cfv.entity_type = 'patient' AND cfv.entity_id = p.id
      AND cfv.custom_field_id = 12 AND cfv.value = 'A+'
  )

  -- Rule 2: city (custom_field_id = 10)
  AND EXISTS (
    SELECT 1 FROM custom_field_values cfv
    WHERE cfv.entity_type = 'patient' AND cfv.entity_id = p.id
      AND cfv.custom_field_id = 10 AND cfv.value = 'Bucharest'
  )

  -- Rule 3: age (custom_field_id = 13)
  AND EXISTS (
    SELECT 1 FROM custom_field_values cfv
    WHERE cfv.entity_type = 'patient' AND cfv.entity_id = p.id
      AND cfv.custom_field_id = 13 AND cfv.value = '50'
  )

  -- Rule 4: pain_level (custom_field_id = 11, from form template 5)
  AND EXISTS (
    SELECT 1 FROM forms f
    WHERE f.patient_person_id = p.patient_person_id
      AND f.organization_id = :org_id
      AND f.form_template_id = 5
      AND f.status IN ('completed', 'signed')
      AND f.values->>'field_11' = 'Big pain'
    ORDER BY f.updated_at DESC
    LIMIT 1
  )

  -- Rule 5: at least 2 completed appointments
  AND (
    SELECT COUNT(*) FROM appointments a
    WHERE a.patient_person_id = p.patient_person_id
      AND a.organization_id = :org_id
      AND a.status = 'done'
  ) >= 2;

OR Logic Examples

Example 6: Any of Multiple Cities

Use case: Patients from Bucharest OR Cluj-Napoca.

json
{
  "name": "Major Cities",
  "description": "Patients from Bucharest or Cluj-Napoca",
  "match_mode": "any",
  "rules": [
    {
      "source": "profile",
      "custom_field_id": 10,
      "op": "eq",
      "value": "Bucharest"
    },
    {
      "source": "profile",
      "custom_field_id": 10,
      "op": "eq",
      "value": "Cluj-Napoca"
    }
  ]
}

Alternative using in operator (simpler):

json
{
  "name": "Major Cities",
  "match_mode": "all",
  "rules": [
    {
      "source": "profile",
      "custom_field_id": 10,
      "op": "in",
      "value": ["Bucharest", "Cluj-Napoca"]
    }
  ]
}

Example 7: Pain Levels (Any High Pain)

Use case: Patients reporting "Big pain" OR "Extreme pain".

json
{
  "name": "High Pain Levels",
  "match_mode": "any",
  "rules": [
    {
      "source": "form",
      "template_id": 5,
      "custom_field_id": 11,
      "op": "eq",
      "value": "Big pain"
    },
    {
      "source": "form",
      "template_id": 5,
      "custom_field_id": 11,
      "op": "eq",
      "value": "Extreme pain"
    }
  ]
}

Alternative using in:

json
{
  "name": "High Pain Levels",
  "match_mode": "all",
  "rules": [
    {
      "source": "form",
      "template_id": 5,
      "custom_field_id": 11,
      "op": "in",
      "value": ["Big pain", "Extreme pain"]
    }
  ]
}

Nested Group Examples

Example 8: AND with Nested OR

Use case: Bucharest patients with (Big pain OR Extreme pain).

json
{
  "name": "Bucharest High Pain",
  "description": "Bucharest patients with big or extreme pain",
  "match_mode": "all",
  "rules": [
    {
      "source": "profile",
      "custom_field_id": 10,
      "op": "eq",
      "value": "Bucharest"
    },
    {
      "group": true,
      "match_mode": "any",
      "rules": [
        {
          "source": "form",
          "template_id": 5,
          "custom_field_id": 11,
          "op": "eq",
          "value": "Big pain"
        },
        {
          "source": "form",
          "template_id": 5,
          "custom_field_id": 11,
          "op": "eq",
          "value": "Extreme pain"
        }
      ]
    }
  ]
}

Logical expression: city = "Bucharest" AND (pain_level = "Big pain" OR pain_level = "Extreme pain")

SQL equivalent:

sql
SELECT p.id
FROM patients p
WHERE p.organization_id = :org_id
  AND EXISTS (
    SELECT 1 FROM custom_field_values cfv
    WHERE cfv.entity_type = 'patient' AND cfv.entity_id = p.id
      AND cfv.custom_field_id = 10 AND cfv.value = 'Bucharest'
  )
  AND (
    EXISTS (
      SELECT 1 FROM forms f
      WHERE f.patient_person_id = p.patient_person_id
        AND f.form_template_id = 5
        AND f.values->>'field_11' = 'Big pain'
      ORDER BY f.updated_at DESC LIMIT 1
    )
    OR
    EXISTS (
      SELECT 1 FROM forms f
      WHERE f.patient_person_id = p.patient_person_id
        AND f.form_template_id = 5
        AND f.values->>'field_11' = 'Extreme pain'
      ORDER BY f.updated_at DESC LIMIT 1
    )
  );

Example 9: Complex Nested Groups

Use case: (Bucharest OR Cluj) AND (High pain OR Recent appointment).

json
{
  "name": "Active High-Value Patients",
  "match_mode": "all",
  "rules": [
    {
      "group": true,
      "match_mode": "any",
      "rules": [
        {"source": "profile", "custom_field_id": 10, "op": "eq", "value": "Bucharest"},
        {"source": "profile", "custom_field_id": 10, "op": "eq", "value": "Cluj-Napoca"}
      ]
    },
    {
      "group": true,
      "match_mode": "any",
      "rules": [
        {"source": "form", "template_id": 5, "custom_field_id": 11, "op": "eq", "value": "Big pain"},
        {"source": "appointments", "metric": "last_date", "op": "gte", "value": "now-30d"}
      ]
    }
  ]
}

Logical expression:

(city = "Bucharest" OR city = "Cluj-Napoca")
AND
(pain_level = "Big pain" OR last_appointment >= now-30d)

Relative Date Examples

Example 10: Recent Appointments

Use case: Patients with an appointment in the last 30 days.

json
{
  "name": "Recently Active",
  "match_mode": "all",
  "rules": [
    {
      "source": "appointments",
      "metric": "last_date",
      "op": "gte",
      "value": "now-30d"
    }
  ]
}

Auto-updating: This segment automatically includes patients whose last appointment was within the last 30 days from today (not from when the segment was created).


Example 11: Inactive Patients

Use case: Patients with no appointments in the last 6 months.

json
{
  "name": "Inactive Patients",
  "description": "No appointments in last 6 months",
  "match_mode": "all",
  "rules": [
    {
      "source": "appointments",
      "metric": "last_date",
      "op": "lt",
      "value": "now-6M"
    }
  ]
}

Alternative (count-based):

json
{
  "name": "Inactive Patients",
  "match_mode": "all",
  "rules": [
    {
      "source": "appointments",
      "metric": "count",
      "op": "eq",
      "value": 0,
      "filters": {
        "after": "now-6M"
      }
    }
  ]
}

Example 12: New Patient Registrations

Use case: Patients who registered in the last 90 days.

json
{
  "name": "New Patients",
  "match_mode": "all",
  "rules": [
    {
      "source": "profile",
      "custom_field_id": 14,
      "op": "gte",
      "value": "now-90d"
    }
  ]
}

Assumption: Organization has a custom field with ID 14 (e.g., registration_date) with field_type = "date".


Range Examples

Example 13: Age Range

Use case: Patients between 18 and 65 years old.

json
{
  "name": "Working Age Adults",
  "match_mode": "all",
  "rules": [
    {
      "source": "profile",
      "custom_field_id": 13,
      "op": "gte",
      "value": 18
    },
    {
      "source": "profile",
      "custom_field_id": 13,
      "op": "lte",
      "value": 65
    }
  ]
}

Future enhancement: Add between operator for cleaner syntax.


Example 14: Appointment Count Range

Use case: Patients with 2-5 appointments (not too new, not too frequent).

json
{
  "name": "Moderate Frequency",
  "match_mode": "all",
  "rules": [
    {
      "source": "appointments",
      "metric": "count",
      "op": "gte",
      "value": 2
    },
    {
      "source": "appointments",
      "metric": "count",
      "op": "lte",
      "value": 5
    }
  ]
}

Existence Examples

Example 15: Patients with Email

Use case: Patients who have an email address on file.

json
{
  "name": "Emailable Patients",
  "match_mode": "all",
  "rules": [
    {
      "source": "profile",
      "custom_field_id": 15,
      "op": "exists"
    }
  ]
}

Example 16: Patients Missing Critical Data

Use case: Patients without a blood type on file.

json
{
  "name": "Missing Blood Type",
  "description": "Patients needing profile completion",
  "match_mode": "all",
  "rules": [
    {
      "source": "profile",
      "custom_field_id": 12,
      "op": "empty"
    }
  ]
}

String Search Examples

Example 17: Notes Containing Keyword

Use case: Patients with "diabetes" mentioned in their notes.

json
{
  "name": "Diabetes Patients",
  "match_mode": "all",
  "rules": [
    {
      "source": "profile",
      "custom_field_id": 16,
      "op": "contains",
      "value": "diabetes"
    }
  ]
}

Case-insensitive: Matches "diabetes", "Diabetes", "DIABETES".


Use case: Patients reporting headaches in their symptoms form.

json
{
  "name": "Headache Patients",
  "match_mode": "all",
  "rules": [
    {
      "source": "form",
      "template_id": 5,
      "custom_field_id": 17,
      "op": "contains",
      "value": "headache"
    }
  ]
}

Appointment Filter Examples

Example 19: Specific Appointment Type

Use case: Patients with at least 1 follow-up appointment (template 10).

json
{
  "name": "Follow-Up Patients",
  "match_mode": "all",
  "rules": [
    {
      "source": "appointments",
      "metric": "count",
      "op": "gte",
      "value": 1,
      "filters": {
        "template_id": 10
      }
    }
  ]
}

Example 20: Upcoming Appointments

Use case: Patients with at least 1 upcoming appointment.

json
{
  "name": "Scheduled Patients",
  "match_mode": "all",
  "rules": [
    {
      "source": "appointments",
      "metric": "count",
      "op": "gte",
      "value": 1,
      "filters": {
        "status": "upcoming"
      }
    }
  ]
}

Example 21: Date Range Filter

Use case: Patients with appointments between Jan 1 and Mar 31, 2025.

json
{
  "name": "Q1 2025 Patients",
  "match_mode": "all",
  "rules": [
    {
      "source": "appointments",
      "metric": "count",
      "op": "gte",
      "value": 1,
      "filters": {
        "after": "2025-01-01",
        "before": "2025-03-31"
      }
    }
  ]
}

Real-World Use Cases

Example 22: At-Risk Patient Alerts

Use case: Seniors (65+) with chronic pain and no recent appointments (potential churn risk).

json
{
  "name": "At-Risk Seniors",
  "description": "Seniors with chronic pain, no appointments in 90 days",
  "match_mode": "all",
  "rules": [
    {
      "source": "profile",
      "custom_field_id": 13,
      "op": "gte",
      "value": 65
    },
    {
      "source": "form",
      "template_id": 5,
      "custom_field_id": 11,
      "op": "in",
      "value": ["Big pain", "Extreme pain"]
    },
    {
      "source": "appointments",
      "metric": "last_date",
      "op": "lt",
      "value": "now-90d"
    }
  ]
}

Action: Automated outreach email to schedule follow-up.


Example 23: VIP Patients

Use case: Patients with 10+ appointments who gave positive feedback.

json
{
  "name": "VIP Patients",
  "description": "Highly engaged patients with positive feedback",
  "match_mode": "all",
  "rules": [
    {
      "source": "appointments",
      "metric": "count",
      "op": "gte",
      "value": 10
    },
    {
      "source": "form",
      "template_id": 20,
      "custom_field_id": 18,
      "op": "in",
      "value": ["Very satisfied", "Extremely satisfied"]
    }
  ]
}

Action: Send exclusive offers, priority scheduling.


Example 24: New Patient Onboarding

Use case: Patients registered in last 30 days who haven't completed their intake form.

json
{
  "name": "Incomplete Onboarding",
  "match_mode": "all",
  "rules": [
    {
      "source": "profile",
      "custom_field_id": 14,
      "op": "gte",
      "value": "now-30d"
    },
    {
      "group": true,
      "match_mode": "any",
      "rules": [
        {"source": "form", "template_id": 5, "custom_field_id": 19, "op": "empty"},
        {"source": "form", "template_id": 5, "custom_field_id": 19, "op": "eq", "value": "false"}
      ]
    }
  ]
}

Action: Send reminder email to complete intake form.


Example 25: Post-Treatment Follow-Up

Use case: Patients who had surgery (form template 30) in the last 2 weeks.

json
{
  "name": "Post-Surgery Follow-Up",
  "match_mode": "all",
  "rules": [
    {
      "source": "appointments",
      "metric": "last_date",
      "op": "gte",
      "value": "now-14d",
      "filters": {
        "template_id": 30
      }
    }
  ]
}

Action: Schedule follow-up call, send recovery instructions.


Anti-Patterns (What NOT to Do)

Anti-Pattern 1: Too Many Rules

Bad:

json
{
  "name": "Overly Specific",
  "match_mode": "all",
  "rules": [
    /* 20 rules here */
  ]
}

Problem: Performance degrades with > 10 rules. Difficult to maintain.

Solution: Split into multiple segments or use nested groups to organize.


Anti-Pattern 2: Hardcoded Dates

Bad:

json
{
  "source": "appointments",
  "metric": "last_date",
  "op": "gte",
  "value": "2025-01-01"
}

Problem: Segment becomes outdated. Needs manual updates.

Solution: Use relative dates:

json
{
  "source": "appointments",
  "metric": "last_date",
  "op": "gte",
  "value": "now-30d"
}

Anti-Pattern 3: Deep Nesting

Bad:

json
{
  "group": true,
  "match_mode": "all",
  "rules": [
    {
      "group": true,
      "rules": [
        {
          "group": true,
          "rules": [
            {
              "group": true,
              "rules": [/* ... */]
            }
          ]
        }
      ]
    }
  ]
}

Problem: Exceeds max nesting depth (3 levels). Unreadable, slow.

Solution: Flatten logic or split into multiple segments.


Testing Segments

Test Segment with Known Patient

bash
# Create test patient with known data
POST /v1/patients
{
  "name": "Test Patient"
}

# Update patient profile (custom field values)
PUT /v1/patients/{patientID}/profile
{
  "10": "Bucharest",    # city
  "13": "50",           # age
  "12": "A+"            # blood_type
}

# Fill test form
POST /v1/forms
{
  "template_id": 5,
  "user_id": <test_patient_user_id>,
  "values": {
    "field_11": "Big pain"   # pain_level (custom_field_id: 11)
  }
}

# Evaluate segment
POST /v1/segments/{id}/evaluate

# Check membership
GET /v1/segments/{id}/members
# Should include test patient

# Cleanup
DELETE /v1/patients/<test_patient_id>

Performance Tips

  1. Start simple, iterate: Begin with 1-2 rules, add complexity as needed
  2. Use in for lists: More efficient than multiple eq rules with OR
  3. Limit contains usage: Slower than exact matches
  4. Leverage indexes: Profile fields and form values are indexed
  5. Monitor eval times: Check /metrics endpoint for segment eval duration
  6. Prefer Tier 1 for critical segments: Keep high-priority segments simple (≤3 rules, single source)

Summary

Segments support:

  • ✅ Form responses (JSONB queries)
  • ✅ Profile data (custom field values)
  • ✅ Appointment metrics (count, dates)
  • ✅ Multi-source rules (forms + profile + appointments)
  • ✅ Nested groups (AND/OR logic)
  • ✅ Relative dates (auto-updating)
  • ✅ String search (contains)
  • ✅ Range filters (gt, lt, between-equivalent)
  • ✅ Existence checks (exists, empty)

Use these examples as templates for your own segments. Adapt field keys, template IDs, and values to match your organization's data model.