← Back to Portfolio
Full-Stack System · Healthcare · 2024–2025

E·OVR

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.

React 18 + FastAPI MongoDB CSFLE · 7 Encrypted Fields NLP · Free-text Analysis Automated Triage ML JWT + MFA (TOTP) 5-Tier RBAC Excel + Arabic PDF Export JCI Compliance
🏥

End-to-end incident lifecycle: anonymous patient submission via QR link → AI-assisted triage → controlled 6-state workflow → risk matrix → CAPA → JCI compliance → final report → export. Scope-filtered analytics served to 5 organisational tiers. 7 medical fields protected with client-side AES-256 field-level encryption.

✓ Session 1 Hardened ⏳ Session 2 — Frontend Completeness ⏳ Session 3 — Testing Suite 🔮 Vector Search (future)
5Organisational Tiers
6Workflow States
3AI Providers
50+REST API Endpoints
7CSFLE-Encrypted Fields
14Frontend Routes
Access Control

Five-Tier Role Architecture

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.

Tier 1–2
Staff Reporter
Own incidents · Submit · Read
Tier 2
Quality Admin
Facility · Workflow · AI review · MFA
Tier 3
Administration Mgr
Administration · Analytics trends
Tier 4
Governorate Mgr
Governorate · Comparison charts
Tier 5
Top Management
National · Provision · QR links · AI admin
Core Features

What E·OVR Does

🔄

6-State Controlled Workflow

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.

🤖

NLP + Automated Triage

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.

🔐

Multi-Field CSFLE Encryption

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.

📊

Scope-Gated Analytics

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.

🏥

JCI Compliance Tracking

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.

📁

Excel & Arabic PDF Exports

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.

🔗

Anonymous Patient Reporting + QR

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.

🛡️

TOTP MFA + Dual-Token Auth

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.

📧

SendGrid Email Notifications

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.

⚙️

Session 1 hardening applied (2026-05-04): UserInDB refactored — no plain-text password field. Startup validation with fail-fast env var checks. GET /health endpoint added. CORS parsing fixed. render.yaml and Dockerfile hardened. Duplicate AI feedback endpoint removed. requirements.txt fully pinned.

Incident Lifecycle

6-State Machine

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.

Created
In Progress
Evaluating
⇅ loop
More Info Needed
Action Taken
Completed
Risk Assessment

3×3 Risk Matrix

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.

Probability →
Severity ↑
2Low
4Med
6High
4Med
6High
9Critical
3Low
4Med
6Med
Classification Enums

Error Types & Event Categories

Medication Administrative Clinical Equipment InfectionControl Falls Documentation Behavior WorkplaceViolence Environmental
Event Types
IncidentEvent NearMiss AdverseEvent SignificantEvent SentinelEvent
AI & Machine Learning

Two Distinct AI Capabilities

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.

// Capability 01
🧠

Natural Language Processing

Reads and analyses the free-text narrative — extracting nuanced context, contributing factors, and edge cases that checkbox forms miss.

Populates → auto_classification · auto_event_type · signal_flags
// Capability 02

Automated Triage & Categorisation

ML algorithms compute a risk score at submission time — instantly flagging critical or high-risk events. Complements, never replaces, the human risk matrix.

Populates → ai_risk_score · classification_score · signal_flags · embedding_vector
Human-in-the-Loop

QA Review & Labelled Feedback

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.

OpenAI
Anthropic
Google Gemini
Disabled (none)
// ai_metadata schema
auto_classification · string, optional
auto_event_type · string, optional
classification_score · float [0.0–1.0]
ai_risk_score · int [1–9]
similar_incident_ids · string[]
signal_flags · string[]
embedding_vector · float[1536]
model_version · string
human_reviewed · bool
feedback.ai_suggested · string
feedback.human_chose · string

AI failure never blocks incident save — fields remain null and the form submits successfully.

Data & Analytics

Tier-Gated Analytics Dashboard

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.

EndpointMin. Tier StaffQuality AdminAdmin MgrGov. MgrTop Mgmt
GET /analytics/summary Tier 2
GET /analytics/trends Tier 3
GET /analytics/compare Tier 4
Status breakdown Severity pie chart 12-month trend line Facility comparison bar Recharts ?dimension=facility|governorate
Security

Defence in Depth

TOTP MFA Login Flow

POST /auth/login
requires_mfa: true + temp_token
POST /auth/mfa/verify
access_token + httpOnly cookie

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.

🔑
Dual-Token JWT

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.

🧬
CSFLE — 7 Encrypted Fields

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.

🛂
Role Gating + Scope Filter

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 Validation + Health Check

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.

CSFLE Field Map

Encrypted incident fields

  • DET medical_file_number — deterministic, searchable
  • RND involved_person
  • RND responsible_manager
  • RND disclosure_responsible
  • RND description
  • RND specific_error
  • RND notes

DET = AEAD-AES-256-CBC-HMAC-SHA-512 deterministic · RND = AEAD random

JWT Claims

Access token payload

user_id · str — user document ID
sub · str — same as user_id
role · str — UserRole enum
facility · str — scope filter
administration· str — scope filter
governorate · str — scope filter
tier · int — analytics gate
mfa_pending · bool — temp token only
Export Engine

Excel & Arabic PDF Reports

Both export endpoints are scoped to the requesting user's role — a facility user only exports their facility's incidents; top management exports everything.

📊

Excel Export

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.

openpyxlGET /exports/excelScoped
📄

Arabic PDF Report

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.

reportlabarabic-reshaperpython-bidiRTL
JCI Compliance

Joint Commission International Tracking

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.

JCI Chapters (14)
IPSGACCPFR AOPCOPASC MMUPFEQPS PCIGLDFMS SQEMCI
Compliance Status
Met PartiallyMet NotMet NotApplicable
// Group A — read-only disclosure fields
disclosure_date · date
disclosure_method · verbal | written | not_yet
vulnerable_patient · bool
medication_error_merp_category · str
// Group B — editable JCI fields
jci_chapter · JCIChapter
jci_standard · str
jci_measurable_element · str
jci_compliance_status · JCIComplianceStatus
jci_evidence · str
jci_gap_analysis · str
jci_action_plan · str
Technical Architecture

Full-Stack Monorepo

// Frontend · Vercel

React 18 SPA

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.

React 18 · React Router 6 · React Query 5
Recharts · React Hook Form · qrcode.react · Vite
// Backend · Render

FastAPI + Motor

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.

FastAPI · Motor · Pydantic · python-jose
passlib · pyotp · openpyxl · reportlab · SendGrid · httpx
// Data Layer · Atlas

MongoDB Atlas

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.

MongoDB Atlas · CSFLE · Aggregation Pipelines
Motor async driver · counters collection
Deployment Topology
Frontend
Vercel SPA
Backend
Render · Starter
🍃
Database
MongoDB Atlas
🐳
Docker
python:3.11-slim

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

Frontend

14-Route Application Map

Protected routes require a valid access token. RoleRoutes further restrict by allowed roles. Session restore calls /auth/refresh then /auth/me on mount.

RouteComponentAccessNotes
/loginLoginFormPublicMFA routing from here
/mfa/verifyMFAVerifyPublicUses sessionStorage temp token
/report/:uuidPatientReportPublicAnonymous patient submission
/change-passwordChangePasswordPublicFirst-login password reset
/dashboardDashboardProtected (minTier 2)Analytics for tier ≥3
/incidentsReportsProtected⏳ Pagination — Session 2
/incidents/:idIncidentDetailProtected⏳ JCI panel, error feedback — Session 2
/newNewIncidentFormProtectedStaff + quality_admin only
/analyticsAnalyticsProtected (minTier 3)⏳ CompareView stub — Session 2
/mfa/setupMFASetupProtectedScans TOTP QR code
/workflowWorkflowPageRoleRoute⏳ Missing roles fix — Session 2
/admin/provisionAdminProvisiontop_managementUser provisioning + QR codes
*FallbackRedirectPublic→ /dashboard or /login
API Reference

7 Routers · 50+ Endpoints

// auth
POST/auth/loginIssue access + refresh or MFA temp_token
POST/auth/mfa/setupGenerate TOTP secret
POST/auth/mfa/verifyValidate TOTP → issue full access token
POST/auth/refreshSilent token refresh via httpOnly cookie
POST/auth/logoutDelete cookie + invalidate stored hash
GET/auth/meCurrent user profile
POST/auth/change-passwordClears must_change_password
// incidents
GET/incidents/Scoped list — skip + limit
POST/incidents/Create + AI hook + insert
GET/incidents/{id}Scoped single incident
PATCH/incidents/{id}/statusState machine transition
PATCH/incidents/{id}/assessmentRisk matrix save — computes risk_score
PATCH/incidents/{id}/actionsCAPA corrective/preventive
PATCH/incidents/{id}/jci-fieldsJCI compliance panel update
POST/incidents/{id}/finalFinalise → Completed (from ActionTaken only)
POST/incidents/{id}/ai-feedbackAccept / override AI suggestion
// analytics
GET/analytics/summaryStatus / severity / event_type (tier ≥2)
GET/analytics/trends12-month monthly series (tier ≥3)
GET/analytics/compareFacility/governorate comparison (tier ≥4)
// exports
GET/exports/excelScoped .xlsx file stream
GET/exports/pdf/{id}Per-incident Arabic PDF stream
// patients
POST/patients/submit/{uuid}Anonymous patient submission
GET/patients/token/{facility_id}Patient link UUID (top_management)
// facilities
GET/facilities/Public safe list (no UUID)
GET/facilities/cascadingNested dropdown data (public)
GET/facilities/fullFull list with UUIDs (top_management)
// users · ai · health
GET/usersUser list (top_management)
POST/users/provision/facility/{id}Create staff + quality_admin pair
POST/users/provision/tierCreate tier 3–5 users
PATCH/users/{id}/deactivateSet is_active=false
POST/ai/classify/batchBatch classify (top_management)
GET/ai/modelsModel registry
GET/healthDB / AI / SendGrid status · always 200
Roadmap

Gap Tracker & Pending Sessions

⏳ Session 2 — Frontend Completeness
1Pagination UI on IncidentListTodo
2JCI panel in IncidentDetailTodo
3Error feedback on all 5 mutationsTodo
4WorkflowView role access fixTodo
5Session expiry warning (13/15 min)Todo
6CompareView implementationTodo
7QRCodeView implementationTodo
8WorkflowStep componentTodo
⏳ Session 3 — Testing
1Frontend test runner (Vitest + MSW)Todo
2Auth integration tests (6 tests)Todo
3Incident lifecycle tests (5 tests)Todo
4Frontend unit tests (7 tests)Todo
✓ Session 1 — Hardening
UserInDB plain-text passwordDone
Startup env validationDone
Health check endpointDone
CORS parsing + dev fallbackDone
render.yaml + Dockerfile hardenedDone
requirements.txt pinnedDone
Full Tech Stack
React 18 React Router 6 React Query 5 React Hook Form Recharts qrcode.react date-fns Vite 5 FastAPI Motor (async MongoDB) Pydantic python-jose JWT passlib bcrypt pyotp (TOTP MFA) openpyxl reportlab arabic-reshaper python-bidi httpx MongoDB CSFLE NLP Free-text Analysis Automated Triage ML OpenAI / Anthropic / Google SendGrid Vercel Render Docker (python:3.11-slim) MongoDB Atlas