Electronic Occurrence & Variance Reporting — a production-grade healthcare incident reporting platform with 5-tier role architecture, NLP-powered narrative analysis, ML-driven automated triage, TOTP-based MFA, multi-field CSFLE encryption, scope-gated analytics, Excel & Arabic PDF exports, and JCI compliance tracking.
Every API endpoint and analytics query is gated by tier. Scope filters cascade automatically from JWT claims — a facility user only ever sees their own incidents; national management sees all.
Strict state machine: Created → InProgress → Evaluating ⇄ MoreInfoNeeded → ActionTaken → Completed. Every transition is validated server-side against _ALLOWED_TRANSITIONS and appended to an immutable audit trail with user ID, old status, and timestamp.
Two distinct AI capabilities: NLP reads free-text narratives to extract context beyond rigid drop-downs. Automated triage ML-scores and flags high-risk events at submission time. Provider-agnostic: OpenAI, Anthropic, or Google — hot-swappable via env var. AI failure never blocks submission.
MongoDB Client-Side Field Level Encryption protects 7 fields before they ever leave the application — deterministic AES-256 for searchable fields, random AEAD for all others. Works independently of server-side encryption; plaintext never transits the wire.
Aggregation pipelines serve status/severity breakdowns, 12-month trend series, and cross-facility comparison charts. Three analytics endpoints each enforce a minimum tier — tier 2 for summary, tier 3 for trends, tier 4 for comparison.
Dedicated JCI compliance panel covering chapter, standard, measurable element, compliance status (Met/PartiallyMet/NotMet/N/A), evidence, gap analysis, and action plan — all editable by quality admins and above via a dedicated PATCH /jci-fields endpoint.
Scoped incident export to Excel (openpyxl) and per-incident PDF reports (reportlab) with full Arabic RTL support via arabic-reshaper and python-bidi. Both streams are scoped by the requesting user's role claims.
Each facility has a UUID v4 patient link. Patients submit incidents without any login. Top management generates QR codes (qrcode.react) for facility reception desks. Incidents enter the same 6-state workflow tagged reporter_type=patient.
Opt-in TOTP MFA via pyotp. Login returns a temp_token when MFA is enabled; full access token issued only after TOTP verification. Short-lived access token in memory + httpOnly refresh cookie with 15-min idle timeout.
Automated email alerts on incident submission and status changes via SendGrid. Email service degrades gracefully — if SENDGRID_API_KEY is absent or placeholder, notifications are silently skipped; workflows continue unaffected.
Invalid transitions return HTTP 400. Every valid transition appends a timestamped audit entry — user ID, old status, new status. The MoreInfoNeeded loop allows quality admins to request clarification before evaluation continues.
Quality admins assign Severity × Probability to compute a risk_score server-side. The matrix is rendered as a clickable 3×3 grid (RiskMatrix.jsx). Human score and AI risk score are stored and displayed side-by-side.
E·OVR uses ML for two independent, named purposes. Each solves a different problem in the incident reporting lifecycle. AI is provider-agnostic and fully disabled until activation.
Reads and analyses the free-text narrative — extracting nuanced context, contributing factors, and edge cases that checkbox forms miss.
ML algorithms compute a risk score at submission time — instantly flagging critical or high-risk events. Complements, never replaces, the human risk matrix.
Quality admins review NLP and triage outputs via the AIBadge — accepting or overriding the classification. Every review stores the AI suggestion alongside the human choice, creating a labelled dataset for model refinement.
AI failure never blocks incident save — fields remain null and the form submits successfully.
MongoDB aggregation pipelines serve summary cards, 12-month trend series, and cross-facility comparison charts. Backend scope filters ensure each tier only sees data it is authorised to access. high_risk and pending_ai_review are derived on the frontend.
| Endpoint | Min. Tier | Staff | Quality Admin | Admin Mgr | Gov. Mgr | Top Mgmt |
|---|---|---|---|---|---|---|
GET /analytics/summary |
Tier 2 | — | ✓ | ✓ | ✓ | ✓ |
GET /analytics/trends |
Tier 3 | — | — | ✓ | ✓ | ✓ |
GET /analytics/compare |
Tier 4 | — | — | — | ✓ | ✓ |
TOTP secret generated via pyotp.random_base32(), stored on user document.
mfa_pending claims are rejected by get_current_user.
15-min idle timeout dispatches session expiry warning via CustomEvent.
15-min access token in memory + 30-day refresh token in httpOnly cookie (SameSite=Lax, Secure in prod). Refresh tokens are bcrypt-hashed in DB; up to 10 retained per user. Logout invalidates hash. Silent refresh on 401 via Axios interceptor.
medical_file_number — deterministic AES-256 (searchable). involved_person, responsible_manager, disclosure_responsible, description, specific_error, notes — random AEAD (non-searchable). Local KMS master key; plaintext never reaches the server wire.
require_role() FastAPI dependency at every router. get_scope_filter() builds MongoDB queries dynamically from JWT claims — facility / administration / governorate / all — ensuring data never leaks across tiers.
startup_checks.py runs before DB connection — fails hard on missing MONGO_URL, JWT_SECRET, or invalid CSFLE key. GET /health returns DB, AI, and SendGrid status; used as Render healthCheckPath.
medical_file_number — deterministic, searchableinvolved_personresponsible_managerdisclosure_responsibledescriptionspecific_errornotesDET = AEAD-AES-256-CBC-HMAC-SHA-512 deterministic · RND = AEAD random
Both export endpoints are scoped to the requesting user's role — a facility user only exports their facility's incidents; top management exports everything.
Scoped incident list exported as a formatted .xlsx file streamed directly from FastAPI. Built with openpyxl — full column formatting, auto-width, and header rows. Auth-scoped via get_scope_filter.
Per-incident PDF generated with reportlab. Full Arabic RTL support: text is reshaped via arabic-reshaper and bidi-reordered via python-bidi before rendering. Font path configurable via PDF_ARABIC_FONT_PATH env var.
Each incident carries an optional JCI compliance panel editable by quality admins and top management via PATCH /incidents/{id}/jci-fields. Covers all 14 JCI chapters with structured compliance evidence.
Role-driven sidebar routing, React Query (5-min staleTime) for server state, React Hook Form for incident submission, Recharts for analytics. Axios base URL /api proxied to backend in dev.
Async MongoDB driver (Motor) with FastAPI lifespan. 7 routers: auth, incidents, analytics, facilities, exports, users, ai, health. CSFLE auto-encryption applied at startup. Non-root Docker user, HEALTHCHECK configured.
5 collections: incidents, facilities, users, model_registry, counters. Collision-free incident IDs via atomic counter. CSFLE schema map on 7 fields. Aggregation pipelines for all analytics. 7 indexes created at startup.
Build: pip install -r requirements.txt && python scripts/seed_model_registry.py ·
Start: uvicorn app.main:app --host 0.0.0.0 --port $PORT ·
Health: GET /health
Protected routes require a valid access token. RoleRoutes further restrict by allowed roles. Session restore calls /auth/refresh then /auth/me on mount.
| Route | Component | Access | Notes |
|---|---|---|---|
| /login | LoginForm | Public | MFA routing from here |
| /mfa/verify | MFAVerify | Public | Uses sessionStorage temp token |
| /report/:uuid | PatientReport | Public | Anonymous patient submission |
| /change-password | ChangePassword | Public | First-login password reset |
| /dashboard | Dashboard | Protected (minTier 2) | Analytics for tier ≥3 |
| /incidents | Reports | Protected | ⏳ Pagination — Session 2 |
| /incidents/:id | IncidentDetail | Protected | ⏳ JCI panel, error feedback — Session 2 |
| /new | NewIncidentForm | Protected | Staff + quality_admin only |
| /analytics | Analytics | Protected (minTier 3) | ⏳ CompareView stub — Session 2 |
| /mfa/setup | MFASetup | Protected | Scans TOTP QR code |
| /workflow | WorkflowPage | RoleRoute | ⏳ Missing roles fix — Session 2 |
| /admin/provision | AdminProvision | top_management | User provisioning + QR codes |
| * | FallbackRedirect | Public | → /dashboard or /login |