Skip to main content
The milestone system (v2.0) is the backbone of ORbit’s surgical workflow tracking. It uses a template-based model where milestone definitions are grouped into phases and assigned via a cascade resolution.
This is the most architecturally complex subsystem in ORbit. The critical invariant: case_milestones references facility_milestone_id, never milestone_type_id. Violating this causes data integrity issues.

Table hierarchy

milestone_types (global, read-only)
    ↓ source_milestone_type_id
facility_milestones (per-facility instances)
    ↓ facility_milestone_id
milestone_template_items (template contents)
    ↓ milestone_template_id
milestone_templates (template containers)
    ↓ milestone_template_id
cases (stamped at creation)
    ↓ case_id
case_milestones (actual timestamps)
Critical rule: facility_milestone_id is the foreign key everywhere in case_milestones. Never use milestone_type_id directly.

Cascade resolution

When creating a case, the system resolves which template to use via a 3-tier cascade:
  1. Surgeon override — check surgeon_template_overrides for this surgeon + procedure
  2. Procedure template — check procedure_types.milestone_template_id
  3. Facility default — use the milestone_templates record where is_default = true
The resolved template ID is stamped on the case at creation time (cases.milestone_template_id), so subsequent template changes don’t affect existing cases.

Key function: resolveTemplateForCase

Located in lib/dal/phase-resolver.ts, this TypeScript function implements the cascade:
resolveTemplateForCase(supabase, {
  surgeonId,
  procedureTypeId,
  facilityId
}) → milestone_template_id
The same cascade is implemented in SQL within create_case_with_milestones() and get_milestone_interval_medians().

Required milestones

Every new template must include 4 phases and 7 unique milestones (8 placements including shared boundaries):
PhaseRequired milestones
Pre-Oppatient_in
Surgicalprep_drape_start, prep_drape_complete, incision
Closingclosing (shared), closing_complete (shared)
Post-Oppatient_out
Required items are defined in lib/template-defaults.ts and enforced in both useTemplateBuilder and useAdminTemplateBuilder hooks. Grandfather logic: enforcement only applies to templates containing all 8 required milestones. Legacy templates are not retroactively enforced.

Phase boundary resolution

Phase boundaries are derived from template items, not stored separately. The SQL function resolve_template_phase_boundaries() reads milestone_template_items and returns phase boundaries. The TypeScript adapter resolvePhaseDefsFromTemplate() returns PhaseDefinitionWithMilestones[], preserving the interface expected by analytics components.

Key functions in lib/dal/phase-resolver.ts

FunctionDescription
resolvePhaseDefsFromTemplate()Returns phase definitions with milestone arrays
resolveDefaultPhaseDefsForFacility()Resolves the facility’s default template
resolveTemplateForCase()Implements the 3-tier cascade

Case creation flow

When create_case_with_milestones() is called:
  1. Resolve the template via the cascade
  2. Stamp milestone_template_id on the case
  3. Read milestone_template_items for the resolved template
  4. Create one case_milestones row per item with recorded_at = NULL
  5. Set case status to Scheduled

Analytics integration

  • Surgeon analytics — phase breakdowns use each case’s stamped template
  • Milestone comparisonget_milestone_interval_medians() uses the 3-tier cascade for baselines
  • Phase mediansget_phase_medians() accepts a milestone_template_id parameter
The analytics pipeline uses the per-case stamped template for phase boundary resolution. This means even if a template is later modified, historical cases keep their original phase boundaries, ensuring analytics remain consistent over time.

Common pitfalls

This is the most common mistake. Always join through facility_milestones to get global type information: case_milestonesfacility_milestones.source_milestone_type_idmilestone_types.
Required milestone enforcement only applies to templates that already contain all 8 required milestones. Legacy templates with fewer milestones are exempt. This prevents breaking existing workflows when the required set was introduced.
Phase boundaries are derived from milestone_template_items, not stored in a separate phase_definitions table (which was removed). Use resolve_template_phase_boundaries() to get boundaries.

Next steps