Skip to main content
ORbit is a multi-platform surgical analytics application built on Next.js (web) and SwiftUI (iOS) with a shared PostgreSQL database via Supabase.

Technology stack

LayerTechnology
Web frontendNext.js (App Router), TypeScript, Tailwind CSS
iOS frontendSwiftUI, MVVM + Repository pattern
UI componentsshadcn/ui, Radix UI, lucide-react icons
ChartsRecharts
Drag and drop@dnd-kit
DatabasePostgreSQL via Supabase
AuthSupabase Auth with RLS
HostingVercel (web), App Store (iOS)

Database overview

The database contains 70+ tables, 85 RPC functions, 25 triggers, and 3 materialized views.

Core tables

TablePurpose
facilitiesMulti-tenant facility definitions
usersAll platform users with facility assignment
casesSurgical cases — the central entity
case_milestonesOne row per milestone per case
case_completion_statsDenormalized stats cache (41 columns)
or_roomsOperating rooms per facility
procedure_typesProcedure catalog with categories
payersInsurance/payer information

Milestone system tables

TablePurpose
milestone_typesGlobal milestone definitions (read-only reference)
facility_milestonesPer-facility milestone definitions
milestone_templatesTemplate definitions (name, facility, flags)
milestone_template_itemsItems within a template (milestone, phase, order)
surgeon_template_overridesPer-surgeon template assignments
facility_phasesPhase definitions per facility
case_milestones references facility_milestone_id, never milestone_type_id. To get global type info, join through facility_milestones.source_milestone_type_id.

Analytics tables

TablePurpose
case_completion_statsPer-case denormalized stats (timing, financial, sequencing)
surgeon_scorecardsCached ORbit Score results
flag_rulesConfigurable flag detection rules
case_flagsFlag instances detected on cases
financial_targetsMonthly profit targets per facility

Trigger system

Cases table (8 triggers)

The cases table has the most triggers of any table. Be cautious modifying case INSERT/UPDATE logic.
TriggerEventAction
record_case_statsAfter UPDATE (data_validated = true)Writes 41 columns to case_completion_stats
sync_soft_delete_columnsBefore UPDATEKeeps is_active and deleted_at in sync
Milestone triggersAfter INSERTPre-creates case_milestones rows from the resolved template

Soft delete trigger

Applied to 20+ tables. The sync_soft_delete_columns() function enforces bidirectional sync:
-- Setting deleted_at also sets is_active = false
-- Setting is_active = false also sets deleted_at = NOW()
-- Clearing either one clears the other
Tables with soft deletes include: body_regions, cancellation_reasons, complexities, cost_categories, delay_types, facility_milestones, implant_companies, milestone_types, or_rooms, payers, procedure_types, users, and more.

Stats pipeline

When a case is validated (data_validated = true), the record_case_stats trigger fires and writes to case_completion_stats:
CategoryColumn countExamples
Identity/FK9case_id, facility_id, surgeon_id, procedure_type_id
Timing9total_duration, surgical_duration, schedule_variance
Sequencing5is_first_case_of_day_room, surgeon_case_sequence
Financial11reimbursement, profit, or_hourly_rate
Workflow7data_validated, is_excluded, exclusion_reason
Three materialized views aggregate this data:
ViewAggregation level
surgeon_procedure_statsPer surgeon, per procedure type
facility_procedure_statsPer facility, per procedure type
surgeon_overall_statsPer surgeon (all procedures)

Row-level security

Every table has RLS policies enforced. The key pattern:
  • Users can only access rows where facility_id matches their assigned facility
  • Global admins can access rows across all facilities
  • Auth is validated via Supabase Auth tokens in middleware

Application structure

apps/web/or-flow-app/
├── app/                    # Next.js App Router pages
├── components/
│   ├── ui/                 # shadcn base components
│   ├── layouts/            # DashboardLayout, Sidebar, Header
│   ├── cases/              # Case management components
│   ├── dashboard/          # Dashboard components
│   ├── analytics/          # Analytics page components
│   ├── data-quality/       # Data quality components
│   ├── settings/           # Settings page components
│   └── block-schedule/     # Block scheduling components
├── lib/
│   ├── dal/                # Data access layer
│   ├── hooks/              # React hooks (useSupabaseQuery, etc.)
│   ├── constants/          # Metrics catalog, status config
│   └── utils/              # Formatting, analytics utilities
├── types/                  # TypeScript type definitions
└── supabase/migrations/    # Database migration files

Data fetching pattern

All data fetching uses the useSupabaseQuery hook:
const { data, isLoading, error } = useSupabaseQuery(
  ['cases', facilityId],
  async (supabase) => {
    const { data, error } = await supabase
      .from('cases')
      .select('*')
      .eq('facility_id', facilityId)
      .eq('is_active', true);
    if (error) throw error;
    return data;
  }
);

Critical patterns

  • Facility scoping — every query filters by facility_id
  • Soft deletes — always filter is_active = true
  • Milestone v2.0 — use facility_milestone_id as FK
  • No any types — TypeScript strict mode enforced
  • Structured logging — use the logger module, not console.log

Next steps