Platform Architecture¶
Version: 4.0.0 (SOAP Adapter Live + Onboarding Redesign) Last Updated: 2026-02-15 Validated: Every fact verified against running production system Status: Production — SOAP adapter active, REST bridge deprecated
This document provides a complete visual representation of the VitaraVox platform architecture across all layers — infrastructure, voice AI, server middleware, EMR integration, and database.
System Overview — Validated Infrastructure¶
╔══════════════════════════════════════════════════════════════════════════════╗
║ VITARAVOX PLATFORM — VALIDATED ║
║ 2026-02-15 — All facts checked ║
╚══════════════════════════════════════════════════════════════════════════════╝
📱 Patient's Phone
│
│ Regular phone call
▼
┌─────────────────────┐
│ VAPI.AI │ Cloud Service
│ AI Voice Platform │ - Answers the phone
│ │ - Listens & speaks
│ +1 236-305-7446 │ - Thinks (GPT-4o)
│ │
│ ┌───────────────┐ │
│ │ SQUAD v3.0 │ │ ID: 13fdfd19-a2cd-4ca4-8e14-ad2275095e32
│ │ (9 agents) │ │
│ │ │ │
│ │ Router │ │ Detects language, routes caller
│ │ │ │ │
│ │ ┌────┴────┐ │ │
│ │ ▼ ▼ │ │
│ │ EN ZH │ │ Two parallel tracks
│ │ Track Track │ │
│ │ │ │ │ │
│ │ ├─Patient ├─Patient Find caller's medical file
│ │ │ ID │ ID │
│ │ ├─Booking ├─Booking Book new appointments
│ │ ├─Modify ├─Modify Reschedule or cancel
│ │ └─Register└─Register Sign up new patients
│ └───────────────┘ │
└──────────┬────────────┘
│
│ HTTPS webhook (tool-calls)
│ Authenticated with API key (x-api-key header)
▼
┌──────────────────────────────────────┐
│ VITARA PLATFORM SERVER │
│ Oracle Cloud (OCI) │ ca-toronto-1
│ VM.Standard.A1.Flex (ARM64) │ 2 OCPUs, 24GB RAM
│ instance-20251030-1238 │
│ Port 3002 — PM2 process: │
│ "vitara-admin-api" │
│ │
│ ┌──────────────────────────────────┐ │
│ │ Webhook Handler │ │ vapi-webhook.ts
│ │ Routes: /api/vapi (primary) │ │ /vapi-webhook (legacy)
│ │ Auth: x-api-key verified │ │
│ └──────────┬───────────────────────┘ │
│ │ │
│ ┌──────────▼───────────────────────┐ │
│ │ Booking Engine │ │ booking.service.ts
│ │ (The Brain) │ │
│ │ │ │ Checks in order:
│ │ 1. Holiday check (from DB) │ │ ← clinic_holidays table
│ │ 2. Day-of-week open/closed │ │ ← clinic_hours table
│ │ 3. Max advance booking days │ │ ← clinic_config table
│ │ 4. Fetch schedule from OSCAR │ │ ← SOAP call
│ │ 5. Fetch existing appointments │ │ ← SOAP call
│ │ 6. Filter: hours, overlap, limit │ │ ← per-slot checks
│ │ │ │
│ │ Booking uses advisory locks │ │ PostgreSQL pg_try_advisory_lock
│ │ to prevent double-booking │ │
│ └──────────┬───────────────────────┘ │
│ │ │
│ ┌──────────▼───────────────────────┐ │
│ │ SOAP Adapter (ACTIVE) │ │ OscarSoapAdapter.ts
│ │ WS-Security: UsernameToken │ │ passwordType: PasswordText
│ │ (no Timestamp, no Nonce) │ │ hasTimeStamp: false
│ │ Circuit breaker: 10s timeout │ │ opossum library
│ │ │ │
│ │ Bridge Adapter (DEPRECATED) │ │ OscarBridgeAdapter.ts
│ │ REST/JSON + X-API-Key │ │ Dev/fallback only
│ └──────────┬───────────────────────┘ │
│ │ │
│ ┌──────────────────────────────────┐ │
│ │ PostgreSQL (LOCAL) │ │ localhost:5432
│ │ Database: vitara_platform │ │
│ │ Tables: clinic_config, │ │
│ │ clinic_hours, clinic_holidays, │ │
│ │ call_logs, waitlist, audit_log │ │
│ └──────────────────────────────────┘ │
└──────────┬────────────────────────────┘
│
│ SOAP/XML over HTTPS (port 8443)
│ WS-Security UsernameToken
│ Direct connection, no middleman
│
│ ← Crosses from OCI Toronto to AWS Montreal →
▼
┌──────────────────────────────────────┐
│ OSCAR EMR │
│ AWS ca-central-1 (Montreal) │ ec2-15-222-50-48
│ https://15.222.50.48:8443/oscar │
│ │
│ 3 SOAP Endpoints: │
│ ┌──────────────────────────────────┐ │
│ │ /ws/ScheduleService?wsdl │ │ Schedules & appointments
│ │ /ws/DemographicService?wsdl │ │ Patient records
│ │ /ws/ProviderService?wsdl │ │ Doctor list
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ MariaDB 10.5 │ │ The actual patient data
│ │ (oscar_mcmaster database) │ │
│ └──────────────────────────────────┘ │
└──────────────────────────────────────┘
Voice Call Data Flow — Validated Booking Sequence¶
Every method name, argument format, and check order verified against running code.
╔══════════════════════════════════════════════════════════════════════════════╗
║ WHAT HAPPENS WHEN A PATIENT CALLS TO BOOK ║
║ Every method name verified against running code ║
╚══════════════════════════════════════════════════════════════════════════════╝
Patient dials +1 236-305-7446
│
│ 1. "Hi, I'd like to book an appointment"
▼
┌─ VAPI ROUTER (assistant: router-v3) ──────────────────────────────────┐
│ Detects: English │
│ Calls: handoff_to_patient_id_en │
│ Hands off to: vitara-patient-id-en-v3 │
└────┬───────────────────────────────────────────────────────────────────┘
│
│ 2. "Let me pull up your file. Can I get your name?" → "John Smith"
▼
┌─ PATIENT-ID-EN (assistant: patient-id-en) ─────────────────────────────┐
│ │
│ Calls webhook: search_patient({ name: "Smith" }) │
│ │ │
│ ▼ │
│ ┌─ OCI SERVER (vapi-webhook.ts → booking.service.ts) ────────┐ │
│ │ │ │
│ │ SOAP Adapter → DemographicService │ │
│ │ Method: searchDemographicByNameAsync │ │
│ │ Args: { arg0: "Smith", arg1: 0, arg2: 20 } │ │
│ │ (search term, offset, limit) │ │
│ │ │ │ │
│ │ ▼ SOAP/XML over HTTPS to AWS Montreal │ │
│ │ │ │
│ │ OSCAR returns: { demographicNo: 11, │ │
│ │ firstName: "John", lastName: "Smith" } │ │
│ │ │ │ │
│ │ Returns: { found: true, patient: { id: "11", ... } } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ "I found John Smith. Is that you?" → "Yes!" │
│ "What can I help you with?" → "I need an appointment" │
│ │
│ Calls: handoff_to_booking_en │
│ Hands off to: vitara-booking-en-v3 │
└────┬───────────────────────────────────────────────────────────────────┘
│
│ 3. Silent handoff — patient doesn't notice
▼
┌─ BOOKING-EN (assistant: booking-en) ───────────────────────────────────┐
│ │
│ "When would you like to come in?" → "Next Tuesday morning" │
│ │
│ Calls webhook: get_available_slots │
│ { startDate: "2026-02-17", providerId: "999998" } │
│ │ │
│ ▼ │
│ ┌─ OCI SERVER (booking.service.ts → getTrueAvailability) ────┐ │
│ │ │ │
│ │ CHECK 1: Is Feb 17 a holiday? │ │
│ │ → Query clinic_holidays table (local PostgreSQL) │ │
│ │ → No match → Continue │ │
│ │ │ │
│ │ CHECK 2: Is Tuesday open? │ │
│ │ → Query clinic_hours table │ │
│ │ → Tuesday: open 09:00-14:00 → Continue │ │
│ │ │ │
│ │ CHECK 3: Is Feb 17 within max advance booking days? │ │
│ │ → Yes → Continue │ │
│ │ │ │
│ │ SOAP CALL 1: Get doctor's schedule template │ │
│ │ → ScheduleService │ │
│ │ → Method: getDayWorkScheduleAsync │ │
│ │ → Args: { arg0: "999998", arg1: "2026-02-17T00:00:00" } │ │
│ │ → Returns: time slots the doctor has that day │ │
│ │ │ │
│ │ SOAP CALL 2: Get already-booked appointments │ │
│ │ → ScheduleService │ │
│ │ → Method: getAppointmentsForDateRangeAndProvider2Async │ │
│ │ → Args: { arg0: "2026-02-17T00:00:00", │ │
│ │ arg1: "2026-02-17T23:59:59", │ │
│ │ arg2: "999998", arg3: false } │ │
│ │ → Returns: existing bookings │ │
│ │ │ │
│ │ FILTER: For each template slot: │ │
│ │ → Remove if outside clinic hours (09:00-14:00 Tue) │ │
│ │ → Remove if too soon (min advance hours) │ │
│ │ → Remove if overlaps with existing booking │ │
│ │ → Remove if slot hit its booking limit │ │
│ │ │ │
│ │ Returns: slots: ["9:30","9:45","10:00", ...] │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ "I have 9:30, 9:45, or 10:00 AM. What works?" → "9:30 please" │
│ │
│ Calls webhook: create_appointment │
│ { patientId:"11", providerId:"999998", │
│ appointmentTime:"2026-02-17 09:30", reason:"Checkup" } │
│ │ │
│ ▼ │
│ ┌─ OCI SERVER (booking.service.ts → bookAppointment) ────────┐ │
│ │ │ │
│ │ STEP 1: Acquire advisory lock │ │
│ │ → pg_try_advisory_lock("provider:999998:date: │ │
│ │ 2026-02-17:slot:09:30") │ │
│ │ → Lock acquired (no one else booking this slot) │ │
│ │ │ │
│ │ STEP 2: FRESH availability check │ │
│ │ → Calls getTrueAvailability() AGAIN │ │
│ │ → Confirms 9:30 is still free │ │
│ │ │ │
│ │ STEP 3: Book via SOAP │ │
│ │ → ScheduleService │ │
│ │ → Method: addAppointmentAsync │ │
│ │ → Args: { arg0: { │ │
│ │ providerNo: "999998", │ │
│ │ demographicNo: 11, │ │
│ │ appointmentStartDateTime: "2026-02-17T09:30:00", │ │
│ │ appointmentEndDateTime: "2026-02-17T09:45:00", │ │
│ │ reason: "Checkup", │ │
│ │ status: "t" ("t" = To Do) │ │
│ │ }} │ │
│ │ │ │
│ │ STEP 4: Release advisory lock (always, even on error) │ │
│ │ │ │
│ │ Returns: { success: true, appointmentId: 4527 } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ "You're all set! Tuesday Feb 17th at 9:30 AM." │
│ "Is there anything else?" → "No, thank you!" │
│ │
│ Calls webhook: log_call_metadata │
│ { action:"booking", outcome:"success", patientId:"11" } │
│ → Saved to local PostgreSQL (call_logs table) │
│ │
└────┬───────────────────────────────────────────────────────────────────┘
│
│ 4. Call ends
▼
┌────────────────────────────────────────────────────────────────────────┐
│ │
│ Appointment now in OSCAR's MariaDB (AWS Montreal) │
│ → Visible to clinic staff in OSCAR's web interface │
│ │
│ Call log saved in PostgreSQL (OCI Toronto) │
│ → Available for analytics in admin dashboard │
│ │
│ Patient talked to 3 AI agents (Router → Patient-ID → Booking) │
│ → Never knew — all silent handoffs │
│ │
│ Two servers involved: │
│ OCI Toronto (our code) <-> AWS Montreal (OSCAR EMR) │
│ Connected by direct SOAP/XML — no middleman │
│ │
└────────────────────────────────────────────────────────────────────────┘
Vapi Squad v3.0 — Agent Routing Map¶
Validated against /home/ubuntu/vitara-platform/vapi-gitops/resources/squads/vitaravox-v3.yml.
+1 236-305-7446
│
▼
┌─────────────────┐
│ ROUTER │ AssemblyAI Universal STT
│ (router-v3) │ "Hi, this is Vitara Clinic."
└────────┬────────┘
│
┌──────────────┴──────────────┐
│ │
handoff_to_patient_id_en handoff_to_patient_id_zh
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ PATIENT-ID-EN │ │ PATIENT-ID-ZH │
│ (patient-id-en) │ │ (patient-id-zh) │
│ Deepgram EN │ │ Deepgram ZH │
│ "Let me pull up │ │ "请稍等,我帮您 │
│ your file." │ │ 查一下信息。" │
└───────┬─────────┘ └───────┬─────────┘
│ │
┌────────────┼────────────┐ ┌────────────┼────────────┐
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌────────┐ ┌─────────┐ ┌──────────┐ ┌────────┐
│BOOKING │ │MODIFICA- │ │REGIS- │ │BOOKING │ │MODIFICA- │ │REGIS- │
│ EN │ │ TION EN │ │TRATION │ │ ZH │ │ TION ZH │ │TRATION │
│(booking │ │(modifica │ │ EN │ │(booking │ │(modifica │ │ ZH │
│ -en) │ │ tion-en) │ │(regis- │ │ -zh) │ │ tion-zh) │ │(regis- │
│ │ │ │ │tration │ │ │ │ │ │tration │
│Eleven- │ │ │ │ -en) │ │Azure │ │ │ │ -zh) │
│Labs TTS │ │ │ │ │ │ZH TTS │ │ │ │ │
└────┬────┘ └────┬─────┘ └───┬────┘ └────┬────┘ └────┬─────┘ └───┬────┘
│ │ │ │ │ │
└───────────┴───────────┘ └───────────┴───────────┘
│ │
Cross-handoffs available: Cross-handoffs available:
Booking ↔ Modification Booking ↔ Modification
Booking/Mod → Router Booking/Mod → Router
Registration → Booking Registration → Booking
All 9 agents share the same 19 webhook tools — the server handles logic regardless of which agent calls.
Server Architecture¶
Middleware Chain (execution order)¶
Request
│
▼
┌─────────────────┐
│ trust proxy = 1 │ Correct client IP behind NGINX/Caddy
└────────┬────────┘
▼
┌─────────────────┐
│ helmet() │ Security headers (X-Frame-Options, CSP, HSTS, etc.)
└────────┬────────┘
▼
┌─────────────────┐
│ pino-http │ Structured JSON request logging with x-request-id
└────────┬────────┘
▼
┌─────────────────┐
│ cors() │ Origin control for browser requests
└────────┬────────┘
▼
┌─────────────────┐
│ express.json() │ Body parsing (1MB limit)
└────────┬────────┘
▼
┌─────────────────┐
│ auditMiddleware │ Captures POST/PUT/DELETE → AuditLog table
└────────┬────────┘ (redacts passwords and secrets)
▼
┌─────────────────┐
│ rate-limit │ Per-IP throttling:
│ │ Auth: 5 req/min
│ │ Webhook: 300 req/min
│ │ API: 100 req/min
└────────┬────────┘
▼
Routes
Route Layer¶
┌─────────────────────────────────────────────────────────────────────┐
│ ROUTE STRUCTURE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ auth.ts (/api/auth/*) │
│ ├── POST /login → JWT access + refresh tokens │
│ ├── POST /refresh → Renew access token │
│ ├── GET /me → Current user profile │
│ └── POST /logout → Invalidate session │
│ │
│ vapi-webhook.ts (/api/vapi, /vapi-webhook) │
│ ├── vapiWebhookAuth → API Key verified (x-api-key header) │
│ └── 19 tool handlers → Patient, appointment, registration... │
│ │
│ api.ts (/api/*) — 87+ endpoints │
│ ├── PUBLIC: GET /oscar/health │
│ ├── CLINIC: /clinic/me, /stats, /calls, /settings, /analytics │
│ ├── ONBOARD: /clinic/onboarding/*, test-slots, test-patient-search │
│ ├── ADMIN: /admin/clinics, /stats, /health, /audit │
│ ├── PROVISION: /admin/clinics/:id/activate, pending-activation │
│ ├── OSCAR: /api/oscar/patients, providers, appointments │
│ └── NOTIFY: /api/notifications/list, read, read-all │
│ │
└─────────────────────────────────────────────────────────────────────┘
19 Webhook Tool Handlers¶
All verified working via E2E test (35/35 pass — 2026-02-14):
| Category | Tools | EMR Path |
|---|---|---|
| Patient | search_patient, search_patient_by_phone, get_patient |
SOAP → DemographicService |
| Provider | get_providers |
SOAP → ProviderService |
| Schedule | get_available_slots, find_earliest_appointment, check_appointments |
SOAP → ScheduleService |
| Booking | create_appointment, cancel_appointment, update_appointment |
SOAP → ScheduleService |
| Registration | register_new_patient |
Returns NOT_SUPPORTED (SOAP has no addDemographic) |
| Clinic | get_clinic_info, checkNewPatientAcceptance |
Local PostgreSQL only |
| Utility | transfer_call, log_call_metadata, add_to_waitlist |
Local PostgreSQL only |
| Error | Unknown tool names | Returns structured error |
EMR Adapter Layer¶
┌─────────────────────────────────────────────────────────────────────┐
│ EMR ADAPTER LAYER (v4.0) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ EmrAdapterFactory.getAdapter(clinicId) │
│ │ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ IEmrAdapter │ (common interface) │
│ │ healthCheck() │ │
│ │ searchPatients() │ │
│ │ getPatient() │ │
│ │ createPatient() │ │
│ │ getProviders() │ │
│ │ getScheduleSlots() │ ← New in v4.0 │
│ │ getAvailability() │ │
│ │ getAppointment() │ ← New in v4.0 │
│ │ bookAppointment() │ │
│ │ cancelAppointment() │ │
│ └───────────┬───────────┘ │
│ │ │
│ ┌─────────┼─────────┬───────────────┐ │
│ ▼ ▼ ▼ ▼ │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ OSCAR │ │ OSCAR │ │ Accuro │ │ Telus │ │
│ │ SOAP │ │ Bridge │ │(Future)│ │(Future)│ │
│ │ ACTIVE │ │DEPREC. │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ WS-Sec │ │REST/ │ │OAuth2.0│ │SAML/ │ │
│ │ SOAP │ │X-API- │ │ │ │OAuth │ │
│ │ │ │Key │ │ │ │ │ │
│ └────┬───┘ └────┬───┘ └────────┘ └────────┘ │
│ │ │ │
│ ▼ ▼ │
│ OSCAR SOAP OSCAR REST Bridge │
│ (Direct) (15.222.50.48:3000) │
│ Port 8443 Legacy, dev-only │
│ │
└─────────────────────────────────────────────────────────────────────┘
SOAP Adapter — Method Inventory¶
All method names verified against OSCAR WSDLs:
| Operation | SOAP Service | Method | Arguments |
|---|---|---|---|
| Search patient | DemographicService | searchDemographicByNameAsync |
arg0: name, arg1: offset, arg2: limit |
| Get patient | DemographicService | getDemographicAsync |
arg0: demographicNo |
| Create patient | — | NOT AVAILABLE | OSCAR SOAP has no addDemographic |
| Get providers | ProviderService | getProviders2Async |
arg0: activeOnly (boolean) |
| Get schedule | ScheduleService | getDayWorkScheduleAsync |
arg0: providerNo, arg1: dateTime (ISO) |
| Get appointments | ScheduleService | getAppointmentsForDateRangeAndProvider2Async |
arg0: startDate, arg1: endDate, arg2: providerNo, arg3: useGMT |
| Book appointment | ScheduleService | addAppointmentAsync |
arg0: |
| Cancel/update | ScheduleService | updateAppointmentAsync |
arg0: |
Critical SOAP notes:
- All methods use positional args (
arg0,arg1, etc.) — NOT named parameters - DateTime values must be ISO format:
2026-02-17T00:00:00 - WS-Security: UsernameToken with
passwordType: PasswordText,hasTimeStamp: false,hasNonce: false - OSCAR CXF WSS4J only validates UsernameToken — adding Timestamp causes SecurityError
Database Schema¶
┌─────────────────────────────────────────────────────────────────────┐
│ POSTGRESQL — 13 MODELS (Prisma ORM) │
│ localhost:5432/vitara_platform │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ CORE │
│ ┌─────────────────┐ │
│ │ Clinic │─────┬──────────┬──────────┬──────────┐ │
│ │ id, name, slug, │ │ │ │ │ │
│ │ status, timezone│ │ │ │ │ │
│ └────────┬────────┘ │ │ │ │ │
│ │ 1:1 │ 1:7 │ 1:N │ 1:N │ │
│ ▼ ▼ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────┐ ┌─────────┐ ┌────────┐ ┌────────┐│
│ │ ClinicConfig │ │ Clinic │ │ Clinic │ │Waitlist│ │CallLog ││
│ │ │ │ Hours │ │ Provider│ │ Entry │ │ ││
│ │ EMR type │ │ (7 days) │ │ │ │ │ │ ││
│ │ SOAP creds │ │ │ │ OSCAR │ │ Status │ │ Intent ││
│ │ Vapi IDs │ │ Open/ │ │ mapping │ │ mgmt │ │ Outcome││
│ │ Privacy │ │ close │ │ │ │ │ │ Transcrp││
│ │ BAA status │ │ times │ │ │ │ │ │ ││
│ │ Retention │ └──────────┘ └─────────┘ └────────┘ └────────┘│
│ │ Greeting │ │
│ │ Pharmacy │ │
│ └──────────────┘ │
│ │
│ ┌─────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ User │ │ ClinicHoliday │ │OnboardingProg.│ │
│ │ email, role, │ │ date, name │ │ 8 boolean │ │
│ │ clinicId │ │ (per clinic) │ │ flags + │ │
│ │ │ │ │ │ completedAt │ │
│ └─────────────┘ └───────────────┘ └───────────────┘ │
│ │
│ COMPLIANCE SUPPORT │
│ ┌──────────────────┐ ┌─────────────────────────────────┐ │
│ │ AuditLog │ │ SupportTicket + TicketMessage │ │
│ │ (append-only) │ │ + Notification │ │
│ └──────────────────┘ └─────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Security Architecture¶
┌─────────────────────────────────────────────────────────────────────┐
│ SECURITY CONTROLS (6 layers) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Layer 1: Network │
│ ├── NGINX reverse proxy with TLS (Let's Encrypt) │
│ ├── OCI Security Lists (firewall rules) │
│ └── trust proxy = 1 (correct IP for rate limiting) │
│ │
│ Layer 2: Transport │
│ ├── Helmet security headers (CSP, HSTS, X-Frame-Options) │
│ └── CORS origin control │
│ │
│ Layer 3: Rate Limiting │
│ ├── Auth endpoints: 5 req/min per IP │
│ ├── Webhook endpoints: 300 req/min per IP │
│ └── API endpoints: 100 req/min per IP │
│ │
│ Layer 4: Authentication │
│ ├── JWT access tokens (1h) + refresh tokens (7d) │
│ ├── bcrypt password hashing (cost factor 12) │
│ ├── Vapi webhook: API key (x-api-key header) │
│ ├── OSCAR SOAP: WS-Security UsernameToken (PasswordText) │
│ └── Password max-length 128 (bcrypt DoS prevention) │
│ │
│ Layer 5: Authorization │
│ ├── Role-based: vitara_admin, clinic_manager │
│ ├── requireRole() middleware for admin routes │
│ ├── requireClinicAccess middleware (tenant isolation) │
│ └── Advisory locks for concurrent booking protection │
│ │
│ Layer 6: Data Protection │
│ ├── AES-256-GCM encryption for EMR credentials │
│ ├── ENCRYPTION_KEY required in production (env validation) │
│ ├── Credential masking in API responses │
│ ├── Zod .strict() schema validation (reject unknown fields) │
│ ├── AuditLog on all mutations (PIPEDA 4.1.4 compliance) │
│ └── Data retention auto-purge (configurable per clinic) │
│ │
└─────────────────────────────────────────────────────────────────────┘
Connection Status (as of 2026-02-15)¶
| Component | Status | Details |
|---|---|---|
| PostgreSQL | Running | localhost:5432/vitara_platform (local, OCI Toronto) |
| Express Server | Running | PM2 vitara-admin-api, port 3002 |
| OSCAR SOAP | Active | https://15.222.50.48:8443/oscar (AWS Montreal) via WS-Security |
| OSCAR REST Bridge | Deprecated | 15.222.50.48:3000 — dev/fallback only, zero production calls |
| Vapi.ai | Deployed | v3.0 squad 13fdfd19, phone +1 236-305-7446 |
| E2E Tests | 35/35 pass | All 19 tools verified, auth enforced, SOAP confirmed |
Technology Stack¶
| Layer | Technology | Purpose |
|---|---|---|
| Hosting | Oracle Cloud (OCI) ca-toronto-1, ARM64 | Canadian data residency |
| Runtime | Node.js + Express 4.18 | HTTP server |
| Language | TypeScript | Type safety |
| ORM | Prisma 5.22 | Database access |
| Database | PostgreSQL 16 (local) | Primary data store |
| EMR Integration | node-soap + WS-Security | OSCAR SOAP adapter |
| Validation | Zod | Request + env validation |
| Logging | Pino | Structured JSON logs |
| Security | Helmet | HTTP security headers |
| Rate Limiting | express-rate-limit | Brute-force protection |
| Circuit Breaker | Opossum (10s timeout) | SOAP failure isolation |
| Scheduling | node-cron | Data retention jobs |
| Encryption | Node.js crypto | AES-256-GCM |
| Auth | jsonwebtoken + bcryptjs | JWT + password hashing |
| Voice AI | Vapi.ai | 9-agent bilingual squad |
| LLM | OpenAI GPT-4o | Intent understanding |
| STT | AssemblyAI Universal / Deepgram Nova-2 | Speech recognition |
| TTS | ElevenLabs (EN) / Azure Xiaoxiao (ZH) | Speech synthesis |
| OSCAR EMR | AWS ca-central-1 (Montreal) | Patient data, MariaDB 10.5 |
Document validated: 2026-02-14 — 35/35 E2E tests passing, all facts verified against running system