Segment Examples
Simple Examples
Example 1: Form Response Filter
Use case: All patients who reported "Big pain" in intake survey.
{
"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:
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.
{
"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:
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.
{
"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:
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.
{
"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:
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"
{
"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:
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.
{
"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):
{
"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".
{
"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:
{
"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).
{
"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:
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).
{
"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.
{
"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.
{
"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):
{
"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.
{
"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.
{
"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).
{
"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.
{
"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.
{
"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.
{
"name": "Diabetes Patients",
"match_mode": "all",
"rules": [
{
"source": "profile",
"custom_field_id": 16,
"op": "contains",
"value": "diabetes"
}
]
}Case-insensitive: Matches "diabetes", "Diabetes", "DIABETES".
Example 18: Symptom Search
Use case: Patients reporting headaches in their symptoms form.
{
"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).
{
"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.
{
"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.
{
"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).
{
"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.
{
"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.
{
"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.
{
"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:
{
"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:
{
"source": "appointments",
"metric": "last_date",
"op": "gte",
"value": "2025-01-01"
}Problem: Segment becomes outdated. Needs manual updates.
Solution: Use relative dates:
{
"source": "appointments",
"metric": "last_date",
"op": "gte",
"value": "now-30d"
}Anti-Pattern 3: Deep Nesting
Bad:
{
"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
# 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
- Start simple, iterate: Begin with 1-2 rules, add complexity as needed
- Use
infor lists: More efficient than multipleeqrules with OR - Limit
containsusage: Slower than exact matches - Leverage indexes: Profile fields and form values are indexed
- Monitor eval times: Check
/metricsendpoint for segment eval duration - 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.