Last updated: 2026-05-30
graph TB
subgraph Users["Users"]
PA[Patient — Mobile App]
CA[Clinical Staff — Web Dashboard]
HA[Hospital Admin — Web Dashboard]
GU[Guest — No Login]
end
subgraph Edge["Edge / Reverse Proxy"]
TR[Traefik v3.2<br/>TLS · Rate Limiting · VPN Gate]
end
subgraph Backend["Backend Services"]
API[NestJS API<br/>api.rivheal.com<br/>:8000]
ML[FastAPI ML Service<br/>ml-service:8000<br/>wait-time · no-show]
RASA[Rasa NLU 3.6<br/>rasa:5005<br/>health chatbot]
end
subgraph Auth["Authentication"]
KC[Keycloak 26<br/>auth.rivheal.com<br/>SSO · OIDC · JWT]
end
subgraph Data["Data Stores"]
PG[(PostgreSQL 16<br/>Primary store)]
MG[(MongoDB 7<br/>Documents / audit)]
RD[(Redis 7<br/>Cache · Bull queues)]
end
subgraph AI["AI Pipeline"]
ANT[Claude API<br/>Haiku 4.5<br/>Symptom triage LLM]
SKL[scikit-learn Models<br/>wait_time_model.pkl<br/>no_show_model.pkl]
end
subgraph External["External Integrations"]
PS[Paystack / Flutterwave<br/>Payments]
TM[Termii<br/>SMS OTP]
GM[Google Maps API<br/>Hospital geo-search]
CD[Cloudinary<br/>Image storage]
EX[Expo Push<br/>Notifications]
end
PA -->|HTTPS| TR
CA -->|HTTPS| TR
HA -->|HTTPS| TR
GU -->|HTTPS| TR
TR -->|route| API
TR -->|route| KC
API -->|JWT validate| KC
API -->|SQL| PG
API -->|documents| MG
API -->|queue / cache| RD
API -->|POST /predict/*| ML
API -->|POST /webhooks/rest| RASA
API -->|Claude API| ANT
API -->|payments| PS
API -->|SMS| TM
API -->|maps| GM
API -->|files| CD
API -->|push| EX
ML --> SKL
style Users fill:#dbeafe,stroke:#3b82f6
style Backend fill:#dcfce7,stroke:#16a34a
style Auth fill:#fef3c7,stroke:#d97706
style Data fill:#f3e8ff,stroke:#9333ea
style AI fill:#fce7f3,stroke:#db2777
style Edge fill:#f1f5f9,stroke:#64748b
style External fill:#fff7ed,stroke:#ea580c
rivheal-api)8000 (internal), exposed via Traefik at api.rivheal.com./api · Versioning: URI-based (/api/v1/...).hospitalId + branchId. The X-Hospital-Id and X-Branch-Id headers are required on admin endpoints.@Public() decorator for open endpoints, @Roles() for RBAC, Bull queues for async jobs, @Cron for scheduled tasks.rivheal-frontend)rivheal-mobile-app)guestSessionId in MMKV./queue namespace for live queue updates.rivheal-ml-service)8000 (internal only, not Traefik-routed in prod).models/*.pkl. Falls back to rule-based estimates when models are not yet trained.MlProxyService with 5-second timeout and graceful fallback.rasa-bot)5005 (Rasa) + 5055 (actions server).RasaService proxying patient messages to Rasa’s REST webhook.auth.rivheal.com)rivheal — pre-imported from rivheal-infra/keycloak/realm-export.json.api-server (confidential, for API), rivheal-web (public, for SPA), mobile-app (PKCE, for Expo).rivheal-infra)traefik/dynamic.yml defines rate-limit, security-header, and VPN-only middlewares.All AI features are controlled by two-level feature flags:
ENABLE_AI_FEATURES=true # set in .env / docker-compose
When false, all /predict/*, /patients/:id/health-score, and Claude LLM calls return fallback values or 403.
Column hospitals.ai_features_enabled (boolean, default false).
The FeatureFlagsService (global NestJS module) checks both:
const enabled = await featureFlagsService.isAiEnabled(hospitalId);
// false if ENABLE_AI_FEATURES=false OR hospital.ai_features_enabled=false
Enable per hospital via the admin API or direct DB update:
UPDATE hospitals SET ai_features_enabled = true WHERE id = '<hospital-uuid>';
See flows/appointment-booking.md for the full sequence diagram.