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
| Layer | Technology |
|---|
| Web frontend | Next.js (App Router), TypeScript, Tailwind CSS |
| iOS frontend | SwiftUI, MVVM + Repository pattern |
| UI components | shadcn/ui, Radix UI, lucide-react icons |
| Charts | Recharts |
| Drag and drop | @dnd-kit |
| Database | PostgreSQL via Supabase |
| Auth | Supabase Auth with RLS |
| Hosting | Vercel (web), App Store (iOS) |
Database overview
The database contains 70+ tables, 85 RPC functions, 25 triggers, and 3 materialized views.
Core tables
| Table | Purpose |
|---|
facilities | Multi-tenant facility definitions |
users | All platform users with facility assignment |
cases | Surgical cases — the central entity |
case_milestones | One row per milestone per case |
case_completion_stats | Denormalized stats cache (41 columns) |
or_rooms | Operating rooms per facility |
procedure_types | Procedure catalog with categories |
payers | Insurance/payer information |
Milestone system tables
| Table | Purpose |
|---|
milestone_types | Global milestone definitions (read-only reference) |
facility_milestones | Per-facility milestone definitions |
milestone_templates | Template definitions (name, facility, flags) |
milestone_template_items | Items within a template (milestone, phase, order) |
surgeon_template_overrides | Per-surgeon template assignments |
facility_phases | Phase 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
| Table | Purpose |
|---|
case_completion_stats | Per-case denormalized stats (timing, financial, sequencing) |
surgeon_scorecards | Cached ORbit Score results |
flag_rules | Configurable flag detection rules |
case_flags | Flag instances detected on cases |
financial_targets | Monthly 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.
| Trigger | Event | Action |
|---|
record_case_stats | After UPDATE (data_validated = true) | Writes 41 columns to case_completion_stats |
sync_soft_delete_columns | Before UPDATE | Keeps is_active and deleted_at in sync |
| Milestone triggers | After INSERT | Pre-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:
| Category | Column count | Examples |
|---|
| Identity/FK | 9 | case_id, facility_id, surgeon_id, procedure_type_id |
| Timing | 9 | total_duration, surgical_duration, schedule_variance |
| Sequencing | 5 | is_first_case_of_day_room, surgeon_case_sequence |
| Financial | 11 | reimbursement, profit, or_hourly_rate |
| Workflow | 7 | data_validated, is_excluded, exclusion_reason |
Three materialized views aggregate this data:
| View | Aggregation level |
|---|
surgeon_procedure_stats | Per surgeon, per procedure type |
facility_procedure_stats | Per facility, per procedure type |
surgeon_overall_stats | Per 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