This page provides a high-level architectural overview. For detailed schema documentation, see the Data model page. For specific subsystems, see the dedicated pages for Milestones, Scoring, and Flags.
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 |
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)
Thecases 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. Thesync_soft_delete_columns() function enforces bidirectional sync:
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 |
| 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_idmatches their assigned facility - Global admins can access rows across all facilities
- Auth is validated via Supabase Auth tokens in middleware
Application structure
Data fetching pattern
All data fetching uses theuseSupabaseQuery hook:
Critical patterns
- Facility scoping — every query filters by
facility_id - Soft deletes — always filter
is_active = true - Milestone v2.0 — use
facility_milestone_idas FK - No
anytypes — TypeScript strict mode enforced - Structured logging — use the logger module, not
console.log
Key architectural decisions
Why Supabase instead of a custom API?
Why Supabase instead of a custom API?
Supabase provides PostgreSQL, Auth, RLS, Realtime, and Edge Functions in one platform. The PostgREST layer eliminates the need for a custom REST API for most operations, and RLS enforces security at the database level rather than in application code.
Why client-side scoring instead of server-side?
Why client-side scoring instead of server-side?
The ORbit Score engine runs client-side in
orbitScoreEngine.ts and caches results to surgeon_scorecards. This allows the scoring algorithm to be iterated quickly without database migrations. Cached results are refreshed nightly via pg_cron.Why denormalized stats in case_completion_stats?
Why denormalized stats in case_completion_stats?
The 41-column denormalized table avoids expensive multi-join queries for analytics. When a case is validated, the trigger pre-computes everything once, and analytics reads are simple SELECTs. The tradeoff is storage and trigger complexity.
Next steps
Dev setup
Set up your local development environment.
Data model
Full reference for all 70+ tables.