OpenSeva
Production-grade Electronic Medical Records system backed by a Rust FHIR R4 server, PostgreSQL, AI-driven dataset generation, and Claude AI integration via the Model Context Protocol.
Platform Overview
6 Services + HIE LayerOpenSeva is a monorepo of six integrated services. Each service has a clearly defined responsibility and communicates over HTTP using FHIR R4 and standard OAuth2/JWT. On top of these services, OpenSeva now simulates a Health Information Exchange — multiple EMRs feeding one shared record.
EMR Frontend
React/Vite clinical application for patient registration, encounters, appointments, prescriptions, and scheduling.
:3000 :3100 Docker
Auth Server
Node.js OAuth2 server. Issues RS256 JWTs. Exposes /.well-known/jwks.json for FHIR JWT validation. MCP PIN API built-in.
:8088 :8188 Docker
FHIR Server (HFS)
Rust-based FHIR R4 server. SQLite for local dev, PostgreSQL for production. Validates JWTs via JWKS in Docker mode.
:8080 :8180 Docker
MCP Server
Claude AI integration via Model Context Protocol (stdio transport). Role-gated tools for querying patients, encounters, medications, and appointments.
stdio
Dataset Worker
Server-side LLM caller. Generates synthetic FHIR bundles via OpenAI-compatible API, validates them, uploads to HFS with auth, and persists job history.
:8091 :8191 Docker
FHIR Admin UI
Static HTML admin portal. Tabs: Search, Create/Edit, Bulk Upload, Dataset Studio (AI generation), Job History. Admin-only in Docker mode.
:8090 :8181 Docker
HIE Simulation Layer New
Multiple simulated EMRs (Clinic, Hospital, Lab, Pharmacy) feed one shared FHIR store, each resource tagged by source. The foundation for cross-org patient matching and a unified longitudinal record.
seed-hie.sh Track E
Architecture Diagram
Both StacksLocal Stack (run.sh)
Docker Stack (Compose)
run.sh mode, FHIR accepts all requests (JWT auth explicitly disabled for dev convenience).
HIE Simulation Layer New
A data layer on top of either stack — there is no new server. ./seed-hie.sh loads four tenant EMRs into the same FHIR store, each resource tagged by its source org. Tenant scoping works today; the exchange / matching view is the Track E roadmap, not yet built.
portal/hie-exchange.html. Still roadmap: probabilistic/fuzzy MPI scoring, consent enforcement, and deduplication. Also: HFS does not index _tag as a filter — tenant scoping uses ?organization=.
Credentials & Service URLs
Same credentials, both stacks- Full EMR access
- FHIR Admin + Dataset Studio
- User management APIs
- MCP: all tools
FHIR: Organization/org-local
- Patient list & charts
- Encounter notes
- Prescriptions & observations
- MCP: patient/encounter tools
FHIR: Practitioner/pract-doctor
- Appointment scheduling
- Patient registration
- Check-in workflows
- MCP: scheduling tools
FHIR: Practitioner/pract-receptionist
- Own records only
- Medications & lab results
- Appointments
- MCP: self-service tools
FHIR: Patient/patient-local
./seed-hie.sh and query them with the tenant-scoped URLs below. You still sign in with the four accounts above.
Service URLs — Quick Reference
| Service | Local (run.sh) · SQLite | Docker · PostgreSQL |
|---|---|---|
| EMR Login | localhost:3000/signin | localhost:3100/signin |
| FHIR Admin UI | localhost:8090/fhir-admin.html | localhost:8181/ |
| Auth Health | localhost:8088/health | localhost:8188/health |
| Auth JWKS | localhost:8088/.well-known/jwks.json | localhost:8188/.well-known/jwks.json |
| FHIR Health | localhost:8080/health | localhost:8180/health |
| FHIR Metadata | localhost:8080/metadata | localhost:8180/metadata |
| Dataset Worker | localhost:8091/health | localhost:8191/health |
| MCP Connect | localhost:3000/mcp-connect | use local stack for MCP |
| Swagger UI | — | localhost:8182/ |
| Portal / Nav | localhost:8090/ | — |
| HIE — Hospital B patients | …8080/Patient?organization=…org-hospital-b | …8180/Patient?organization=…org-hospital-b |
| HIE — Priya's diabetes (Clinic A only) | …8080/Condition?subject=Patient/hie-a-priya | …8180/Condition?subject=Patient/hie-a-priya |
Step 1 — Verify Local Stack is Running
Local · run.shStart the local SQLite dev stack with a single command. All five services launch as background processes.
./run.sh start # start all 5 services ./run.sh status # verify health + PIDs
Expected status output — all five services healthy:
SERVER PORT PID HEALTH fhir 8080 12345 healthy auth 8088 12346 healthy emr 3000 12347 healthy portal 8090 12348 healthy dataset 8091 12349 healthy
Health Check Links
{"backend":"sqlite"} — this confirms you are on the local SQLite path, not Docker. Docker returns {"backend":"postgres"}.
Presenter Checklist
-
FHIR health at :8080 returns
{"backend":"sqlite"} -
Auth health at :8088 returns
{"status":"ok"} - EMR frontend loads at :3000 (may take 3–5 s on first load)
-
Dataset Worker health at :8091 returns
{"status":"ok"}
Step 2 — EMR Login as Doctor
Local · run.sh-
1
Open the EMR login page
Open EMR Login ↗ -
2
Enter Doctor credentials
doctor@emr.localdoctor123The EMR uses OAuth2 implicit flow — login redirects to Auth Server at :8088, which returns a JWT in the URL hash.
-
3
Navigate to Patients — click Patients in the left sidebar. You will see the seeded demo patients: Alice, Bob, Carol, David.
-
4
Open a patient chart — click any patient name to open their chart. Explore the Encounters, Observations, and Appointments tabs.
-
5
(Optional) Login as Admin to show the full admin dashboard. The admin account sees all patients and can access system configuration.
admin@emr.localadmin123
Presenter Checklist
- Logged in as Doctor — EMR home screen visible
- Patient list shows seeded patients (Alice, Bob, Carol, David)
- Patient chart opened — Encounters tab visible
- Observations or appointments visible on the chart
Step 3 — FHIR Admin: Search Resources
Local · run.sh-
1
Open the FHIR Admin UI — served by the Portal server on :8090
Open FHIR Admin ↗ -
2
Verify the connection dot is green — top of the page shows the FHIR base URL with a green "●" indicator. If amber, click the "Check" button.
-
3
Search Patients — the Search tab is active by default. Select Patient from the resource type dropdown, set count to 20, and click Search.
-
4
View raw FHIR JSON — click any row to expand and see the raw FHIR R4 JSON for that resource.
-
5
Try other resource types:
Encounter,Observation,Practitioner,Organization,PractitionerRole
http://localhost:8080 — no proxy, no auth token needed in local mode. In Docker, it proxies through the Dataset Worker's auth layer.
Presenter Checklist
- FHIR Admin UI loaded — connection indicator green
- Patient search returned seeded patient records
- Raw FHIR JSON visible in the row expander
- At least one other resource type searched successfully
Step 4 — Bulk Import a Clinical Bundle
Local · run.shThe FHIR Admin UI can import any FHIR R4 transaction bundle. Six pre-built clinical datasets are included.
Pre-built Demo Datasets
| File | Patient | Scenario |
|---|---|---|
01-breast-cancer.json | Emma Chen | Breast cancer — diagnosis, chemo, follow-up |
02-stemi.json | James Rodriguez | STEMI — emergency, PCI, rehab |
03-colorectal-ca.json | David Park | Colorectal cancer — staging, surgery, chemo |
04-heart-failure.json | Margaret O'Brien | Heart failure — chronic management, readmission |
05-healthy-male.json | Michael Torres | Annual wellness checks — 5-year longitudinal |
06-healthy-female.json | Sarah Kim | Annual wellness checks — 5-year longitudinal |
-
1
Click "Bulk Upload" tab in FHIR Admin UI (second tab from left)
-
2
In the "Demo Files" panel on the right, click one of the dataset buttons (e.g., "Emma Chen - Breast Cancer"). The bundle loads into the upload text area.
-
3
Click "Upload to FHIR" — the panel below shows a per-resource result table. Green rows = success, red rows = error.
-
4
Verify in Search tab — go back to Search, select Patient, and confirm the new patient (Emma Chen) now appears.
Presenter Checklist
- Bulk Upload tab opened
- Demo bundle loaded via the Demo Files panel
- Upload executed — green result rows visible
- New patient appears in Search tab
Step 5 — Dataset Studio: AI-Driven FHIR Generation
Local · run.shThe Dataset Studio connects to an OpenAI-compatible LLM to generate synthetic FHIR R4 bundles on demand. A template fallback works even without an LLM endpoint.
-
1
Click "Dataset Studio" tab in FHIR Admin UI (fourth tab)
-
2
Check the Dataset Worker URL — pre-filled as
http://localhost:8091. This is the server-side LLM caller that keeps API keys off the browser. -
3
Select provider mode:
- Template only — generates a deterministic FHIR bundle with no LLM call. Always works. Use this if no LLM is configured.
- OpenAI-compatible — calls any vLLM / Ollama / OpenAI-style server at the configured base URL.
- Legacy DGX1 — calls the university's DGX1 server at
/v1/text.
-
4
Click "Use Template" or "Generate Dataset" — the FHIR JSON textarea fills with a transaction bundle containing Patients, Encounters, Observations, and Practitioners.
-
5
Click "Upload to FHIR" — the Dataset Worker validates the bundle and PUTs each resource to HFS. A success/error row appears per resource.
-
6
Check Dataset Job History — click "Refresh Jobs" to see the persisted job record: type, status, timestamp, user, and entry count.
dataset-worker/server.js) is a zero-dependency Node.js HTTP server. It calls the LLM server-side (so API keys never reach the browser), validates that the response is a valid FHIR R4 transaction bundle, uploads each resource to HFS, and records a JSONL audit event in /app/audit/dataset-worker.jsonl.
Presenter Checklist
- Dataset Studio tab opened
- Template loaded — valid FHIR JSON visible in output area
- Upload executed — green result rows visible
- Dataset Job History shows the new upload job
Step 6 — MCP: Connect Claude AI to the EMR
Local · run.shThe MCP Server exposes role-gated EMR tools to Claude via the Model Context Protocol. Users authenticate with a one-time PIN.
PIN Authentication Flow
-
1
Open the MCP Connect page in the EMR:
Open MCP Connect ↗ -
2
Log in with your EMR credentials — the page generates a 6-digit PIN visible on screen. The PIN expires in 5 minutes.
-
3
Add the MCP server to Claude Desktop — edit
~/.config/claude/claude_desktop_config.json:{ "mcpServers": { "openseva-emr": { "command": "node", "args": ["/path/to/openseva/mcp-server/index.js"] } } } -
4
In Claude, call authenticate — first call with no arguments to get instructions, then with the PIN:
authenticate() # → Claude shows instructions and MCP connect link authenticate({"pin": "123456"}) # → Authenticated as doctor@emr.local (practitioner) # → Role-gated tools now available
Role-Gated MCP Tools
| Role | Available Tools |
|---|---|
| Admin | All tools: patient search, encounters, medications, appointments, system info, user listing |
| Doctor | search_patients, get_patient, list_encounters, get_encounter, list_medications, list_observations, get_patient_summary |
| Receptionist | search_patients, get_patient, list_appointments, check_in_patient, schedule_appointment |
| Patient | my_records, my_appointments, my_medications (own data only) |
- "List all patients with diabetes"
- "Show me Emma Chen's last 3 encounters"
- "What medications is James Rodriguez currently taking?"
- "Summarise David Park's clinical history"
Presenter Checklist
- MCP Connect page opened and PIN visible
- MCP server config shown (Claude Desktop JSON)
- PIN exchange flow explained to advisor
- Role-gated tool table shown
Step 7 — Switch to Docker (PostgreSQL)
Docker · PostgresThe Docker stack runs all services in containers with PostgreSQL, stable JWT keys, and enforced FHIR authentication. Both stacks can run simultaneously — they use different port ranges.
docker compose --profile admin up -d # start all services docker compose --profile admin ps # verify all containers healthy
Stack Comparison
| Aspect | Local (run.sh) | Docker (Postgres) |
|---|---|---|
| Storage | SQLite file | PostgreSQL 16 (Docker volume) |
| FHIR auth | Disabled (open) | Enabled — RS256 JWT required |
| JWT keys | Ephemeral (new each restart) | Stable (set via env vars) |
| User passwords | Hardcoded in JS | bcrypt (cost 12) in PostgreSQL |
| PractitionerRole search | Broken (SQLite) | Fixed (Postgres) |
| EMR port | 3000 | 3100 |
| FHIR port | 8080 | 8180 |
| Auth port | 8088 | 8188 |
| FHIR Admin port | 8090 | 8181 |
| Dataset Worker | 8091 | 8191 |
| Swagger UI | — | 8182 |
| Data persistence | Local file (dev only) | Docker volume (survives restart) |
Health Check Links (Docker)
Presenter Checklist
-
All Docker containers show "Up (healthy)" in
docker compose ps -
FHIR health at :8180 returns
{"backend":"postgres"} -
Auth health at :8188 returns
{"db":"connected"} - EMR login page loads at :3100
Step 8 — JWT Auth Enforced: No Token = 401
Docker · PostgresIn Docker mode, HFS validates every request against an RS256 JWT. Unauthenticated writes are rejected with 401 Unauthorized. This is the key security difference from local dev.
Demonstrate the 401
curl -s -o /dev/null -w "%{http_code}" \
http://localhost:8180/Patient
# Expected: 401
Authenticated Request (with admin token)
# 1. Login and capture token
TOKEN=$(curl -s -X POST http://localhost:8188/auth/api/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@emr.local","password":"admin123"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
# 2. FHIR Patient search with token
curl -s -H "Authorization: Bearer $TOKEN" \
"http://localhost:8180/Patient?_count=5"
# Expected: 200 + FHIR bundle JSON
Verify the JWKS Endpoint
curl http://localhost:8188/.well-known/jwks.json # Returns: RSA public key used by HFS to validate all JWTs
JWT_PRIVATE_KEY_PEM env var). HFS fetches the public key from http://auth:8088/.well-known/jwks.json (Docker internal network) and verifies every incoming JWT's RS256 signature, issuer (http://localhost:8188), and audience (openseva-fhir).
run.sh mode, HFS_AUTH_ENABLED is explicitly set to empty — FHIR accepts all requests. This is intentional for lightweight local development. The Docker Compose file sets HFS_AUTH_ENABLED=true and wires the JWKS URL.
Presenter Checklist
-
Curl without token to :8180 returns
401 - JWKS endpoint at :8188 returns RSA public key JSON
-
Curl with admin token returns
200+ FHIR bundle - JWKS / RS256 JWT flow explained to advisor
Step 9 — Docker FHIR Admin + Persisted Job History
Docker · Postgres-
1
Open Docker FHIR Admin:
Open Docker FHIR Admin ↗ -
2
Login via the Auth panel — at the top of the page, enter admin credentials and click "Login". The bearer token is stored in session storage and forwarded to all FHIR and Dataset Worker requests.
admin@emr.localadmin123 -
3
Search Patients — the Search tab uses PostgreSQL via HFS. Note that results are faster and the
totalfield is present (not available in SQLite). -
4
Dataset Studio → Refresh Jobs — click "Refresh Jobs" to load persisted job history from the Docker volume at
$DATA_DIR/audit/dataset-jobs.json(default./docker-data). Jobs survive container restarts. -
5
Open Swagger UI — view the auto-generated OpenAPI spec from HFS's CapabilityStatement:
Open Swagger UI ↗
${DATA_DIR}/audit/dataset-worker.jsonl and job history at ${DATA_DIR}/audit/dataset-jobs.json. Both are bind-mounted from the host — they survive docker compose down and docker compose up.
Presenter Checklist
- Docker FHIR Admin loaded — admin logged in via Auth panel
- Patient search returns results (Postgres-backed, faster)
- Dataset Job History shows persisted jobs from earlier uploads
- Swagger UI loaded and showing FHIR API endpoints
Architecture: RS256 / JWKS Authentication Flow
Deep DiveStep-by-step JWT flow
GET /auth/authorize → Auth Server returns login HTML pagePOST /auth/authorize (email + password)Auth Server:
bcrypt.verify() against PostgreSQL users tableRS256 token signed with RSA-2048 private key
iss: http://localhost:8188aud: openseva-fhirrole: practitionerfhirResourceId: Practitioner/pract-doctorRedirect to EMR with token in URL hash fragment
http://localhost:3100/#access_token=eyJ…Authorization: Bearer <JWT>All FHIR reads/writes include this header
Fetches RSA public key from JWKS endpoint
Verifies RS256 signature + issuer + audience
200 OK or 401 UnauthorizedJWT Payload
{
"sub": "fa2d6178-...",
"email":"doctor@emr.local",
"role": "practitioner",
"scope":"system/*.cruds",
"aud": "openseva-fhir",
"iss": "http://localhost:8188",
"iat": 1780077594,
"exp": 1780106394
}
JWKS Response
{
"keys": [{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"kid": "openseva-signing-key-1",
"n": "rGNgsb213taJ5bD9tU27...",
"e": "AQAB"
}]
}
POST /auth/mcp/generate (requires valid EMR JWT) → returns 6-digit PIN stored in memory → POST /auth/mcp/exchange by MCP server → user session with role + FHIR user ID.
Role Capability Map
All Stacks| Capability | Admin | Doctor | Receptionist | Patient |
|---|---|---|---|---|
| EMR login | ✅ | ✅ | ✅ | ✅ |
| Patient list (all) | ✅ | ✅ | ✅ | ❌ own only |
| Patient chart | ✅ | ✅ | ✅ read | ❌ own only |
| Encounters | ✅ | ✅ | ❌ | ❌ own only |
| Prescriptions | ✅ | ✅ write | ❌ | ❌ own only |
| Book appointment | ✅ any | ✅ own patients | ✅ any (front desk) | ✅ self only |
| FHIR Admin UI | ✅ | ❌ | ❌ | ❌ |
| Dataset Studio | ✅ | ❌ | ❌ | ❌ |
| User management API | ✅ | ❌ | ❌ | ❌ |
| MCP tools | All | Clinical | Scheduling | Self-service |
Known Limitations
Honest Assessment| Feature | Status | Notes |
|---|---|---|
| PractitionerRole search | Fixed in Docker | SQLite does not support the JOIN queries HFS uses for PractitionerRole. Postgres resolves this. |
| SDC $populate / $extract | Partial | These are Aidbox-specific FHIR operations. HFS does not implement them — inherited EMR screens may show errors. |
| Video calls (Jitsi integration) | Stubbed | The inherited EMR code includes a Jitsi button. It returns a placeholder — no real video implementation. |
| HTTPS / TLS | Disabled | All services run HTTP locally. Production deployment should add a reverse proxy (nginx/Traefik) with TLS. |
| AidboxQuery / $query | Not available | Aidbox proprietary FHIR extension. HFS is not Aidbox. These inherited API calls return errors. |
| Stable JWT keys (local) | Ephemeral | Without JWT_PRIVATE_KEY_PEM env var, keys are regenerated on each ./run.sh restart auth. Existing tokens become invalid. |
| Admin FHIR identity | Known gap | Admin user maps to Organization/org-local instead of a Person resource. Clinical screens designed for practitioners may behave oddly. |
_tag search on HFS |
Not indexed | HFS ignores ?_tag=… and returns all resources. The HIE simulation scopes tenants with ?organization=Organization/org-X instead; meta.tag is a provenance label only. |
| HIE patient matching (MPI) | Deterministic built | A working viewer (portal/hie-exchange.html) does deterministic matching (family + DOB + gender) and merges the cross-org record. Fuzzy/probabilistic scoring, consent enforcement, and dedup remain student capstone tickets. |
| Playwright regression tests | Pending | No automated browser tests yet. Core workflows have been manually smoke-tested against both stacks. |
What's Built So Far
Project StatusA snapshot of OpenSeva's current capabilities. Everything marked Working runs today on both stacks and is demoable. The newest additions turn OpenSeva into a hand-over-to-students teaching platform with a multi-EMR exchange.
| Capability | Status | Notes |
|---|---|---|
| Core EMR — patients, encounters, prescriptions, scheduling | Working | React/Vite clinical app against the HFS FHIR R4 backend. |
| Four roles + RS256/JWKS auth | Working | Admin · Doctor · Receptionist · Patient. JWT enforced on the Docker stack. |
| Two stacks — local (SQLite) & Docker (Postgres) | Working | Separate, configurable port ranges; run simultaneously without conflict. |
| FHIR data import — bulk bundles + Dataset Studio | Working | FHIR Admin UI; AI-generated synthetic bundles via the Dataset Worker. |
| Local-LLM AI bridge — summaries, magic search, form builder | Working | Dataset Worker /ai/* → OpenAI-compatible local LLM. Self-contained, no external API. |
| Native appointment booking | Working | Creates Appointment directly (no SDC $extract, which HFS 405s). |
| MCP / Claude AI integration | Working | PIN-based session; role-gated tools over FHIR + Auth. |
| Student build roadmap + contributor guide | New | ROADMAP.md (30+ tickets, 6 tracks) & CONTRIBUTING.md. Pick → branch → PR → merge. |
| HIE multi-EMR simulation — scaffold (E1) | New | 4 tenant EMRs + cross-org patient-overlap scenario. seed-hie.sh, hie/README.md. |
| HIE Exchange viewer — cross-org match + merged record | New | Deterministic MPI + unified-record UI: portal/hie-exchange.html. Navigable from the Portal; drills into Patient 360. |
| Revenue Cycle Simulation — coding → draft claim → billing → simulated submit | New | Provider-side claims lifecycle for teaching. Explainable code suggestions, audit trail, X12 837 preview, burden dashboard. See section. |
| Payer App + Payment Integrity — standalone services, real FHIR Claim/ClaimResponse + Coverage, downcoding/fraud/payable-amount, EMR live status | New | Separate payer app (8092) + payment-integrity API (8093). EMR submits FHIR Claims; payer adjudicates and writes ClaimResponse; 16 sample insurers. See section. |
| Fuzzy/probabilistic MPI · consent · dedup (E2+) | Planned | Student capstone tickets — the data + a deterministic baseline viewer are in place to build on. |
Revenue Cycle Simulation
EducationalMost EMR teaching stops at the visit. The Revenue Cycle Simulation lets students experience what happens after a patient appointment — where the bulk of US healthcare administrative cost lives. It is a process teaching tool, not a billing engine: there is no payer adjudication, clearinghouse, real X12/EDI, payment, or denial logic. All codes are clearly labelled educational sample codes, never claimed to be billing-valid, and a human must approve every step. Language stays neutral ("coding review", "claim readiness", "documentation support") — the simulation deliberately does not teach upcoding.
The workflow
Appointment → Check-in → Encounter → Signed Note → Coding Review → Draft Claim → Billing Review → Simulated Submission
- Sign & Start Coding Review — on a finished encounter the provider signs the note (records a FHIR
Provenanceattestation) and a coding-review task is created. - Coding Review — the helper suggests diagnosis/procedure codes. Each suggestion is explainable: it shows the exact note text that supports it, plus a confidence score and reason. The reviewer accepts, rejects, edits, or adds a code, and can add a documentation note.
- Draft Claim — after documentation validation, accepted codes are assembled into a draft claim.
- Billing Review (Admin acts as Billing Specialist) — readiness checklist, status timeline, and a conceptual X12 837-style preview. Send back to coding, mark ready, or Simulate Submit.
- Submit to Payer — status becomes Submitted and a real FHIR
Claimis sent to the standalone Payer App, which adjudicates it; the EMR Claim Status view then shows the live result.
Coding helper — two modes
- AI-assisted — when the local LLM is configured, the note is sent to the Dataset Worker
/ai/coding-suggestbridge with strict guardrails (education only, never optimize reimbursement, never invent documentation, only note-supported codes, warnings when documentation is thin). Runs fully on-device on Apple Silicon via theMLX/module (./MLX/start-llm.sh) — no external API. - Rule-based fallback — a transparent keyword matcher (diabetes, hypertension, annual wellness, chest pain, follow-up …) that extracts real evidence sentences from the note. Used automatically when the LLM is unavailable, so the workflow always works offline.
Roles
| Role | Can do |
|---|---|
| Doctor / Provider | Sign notes, run coding review, accept/edit codes, generate draft claims. Does not submit final claims. |
| Admin (= Billing Specialist) | All queues, billing review, mark ready, simulate submit, configure. A dedicated billing role is a one-line add later. |
| Receptionist | View claim status only — cannot edit clinical codes. |
| Patient | No access — coding/claim details are not shown to patients in this phase. |
Under the hood
- Data: each claim is a
SimulatedClaimpersisted as a FHIRBasicresource (stable idsimclaim-<encounterId>, full payload in an extension) — shared across roles, persistent, and designed to map to a real FHIRClaimlater. - Audit trail: every action (signed, suggested, accepted/rejected/edited, draft generated, billing review, simulated submit) is recorded on the claim and surfaced in the billing timeline.
- Burden dashboard: an "Educational administrative burden simulation" card shows queue counts, average human touches per claim, and warnings generated — a teaching aid, not financial truth.
Try it
- Seed the demo scenarios:
./run.sh seed(loads 5 claims incl. an incomplete documentation case). - Login as doctor → Revenue Cycle → Coding Review Queue; open a claim and accept codes; Generate Draft Claim.
- Login as admin → Billing Review; review the checklist + X12 preview; Mark Ready, then Simulate Submit.
- Open Learning View for the step-by-step diagram of who does what, which FHIR resource is involved, and why each step adds administrative cost.
Payer App + Payment Integrity (standalone services)
Educational · Phase 3Revenue Cycle stops when the provider clicks Submit. From there the claim leaves the EMR for a separate insurance-company app running on its own port — exactly like real life, where the payer is a different organisation with its own systems. It is educational only: there is no real payer, clearinghouse, X12 837/835/EDI, CPT/ICD validation, fee schedule, eligibility, or prior authorization. Every amount is a labelled sample. The framing is neutral: the provider wants accurate, documentation-backed payment; the payer wants policy- and documentation-backed payment; the friction between them is the administrative burden.
Two standalone services + the EMR
| Service | Port | Role |
|---|---|---|
| EMR (admin) | 3000 | Builds a FHIR Claim from the coded visit + the patient's Coverage, submits it to the Payer App, and shows live status. |
Payer App payer-app/ | 8092 | Receives the FHIR Claim, runs the pipeline, writes Claim + ClaimResponse to FHIR, and serves its own dashboard. |
Payment Integrity payment-integrity/ | 8093 | A separate API the Payer App calls for downcoding, fraud scoring, and the payable amount. |
The pipeline (shown in real time; batch in reality)
received → validate → eligibility / coverage → payment integrity (downcoding + fraud + payable amount) → adjudicate → write FHIR Claim + ClaimResponse
- Admin submits from Billing Review → Submit Claim to Payer. A modal shows the patient's insurance on file (from
Coverage) and a payer dropdown; a real FHIRClaimis POSTed to the Payer App. - Validate — missing patient / provider NPI placeholder / diagnosis / procedure / coverage → rejected.
- Eligibility / coverage — unknown plan, or a service the plan excludes → denied.
- Payment integrity — the Payer App calls the integrity API: it detects downcoding (high-complexity code vs a per-plan documentation bar), scores fraud/abuse (upcoding pattern, excessive units, dx/proc mismatch, thin doc), and computes the payable amount from the plan's coverage %, copay, and deductible.
- Adjudicate & persist — outcome is paid / downcoded / denied / rejected; the Payer App writes the
ClaimResponseto FHIR. The EMR's Claim Status view polls FHIR and updates live.
Outcomes & sample payment math (simulation only)
| Outcome | When |
|---|---|
| paid | Clean claim — payment = allowed × coverage% − copay − deductible. |
| downcoded | High-complexity code not supported by documentation; payer lowers the payable level and pays less. |
| denied | Service not covered by the plan, or blocked by the fraud screen. |
| rejected | Required claim data missing/invalid — fails before payment review. |
Numbers are obvious placeholders — no real fee schedule. Each of the 16 sample insurers (payer-app/data/payers.json) has its own rules, so the same visit pays differently per plan. Example (Aetna): allowed 180 − $50 deductible × 80% − $30 copay = $74; BlueShield: allowed 180 × 85% − $25 = $128.
FHIR resources (the shared DB is the system of record)
Coverage+ insuranceOrganizations model patient insurance (seed-insurance.shlinks 13 patients to plans).Claim(built by the EMR, idclaim-<encounterId>) carries the note + chosen payer inhttp://openseva.local/claims/*extensions.ClaimResponse(written by the Payer App, idclaimresponse-<encounterId>) holds the outcome, adjudication amounts, payment, process notes, and fraud/downcoding extensions. The EMR reads this back for real-time status.
Roles
| Role | Submit claim to payer | View claim status (EMR) | Payer App dashboard (8092) |
|---|---|---|---|
| Admin | ✓ | ✓ | ✓ (separate app) |
| Practitioner | — | ✓ | ✓ (separate app) |
| Receptionist | — | view only | ✓ (separate app) |
| Patient | — | — (Revenue Cycle blocked) | — |
The Payer App dashboard is a separate service with no EMR login — it is the insurance company's view, deliberately outside the EMR's role model.
Try it
- Start everything (incl. the two payer services) and seed insurance:
./run.sh startthen./run.sh seed. - Login as admin → Revenue Cycle → Billing Review on a ready claim → Submit Claim to Payer (the patient's plan is prefilled from Coverage).
- Watch Claim Status update live as the payer adjudicates, then open the Payer App dashboard to see the same claim from the insurer's side.
- Try a downcode (high-complexity code on a short note) and a rejection (a claim missing a diagnosis) to see the other outcomes.
HIE Simulation — Multiple EMRs, One Patient Record
New · Track EAn EMR is one clinic's chart. An EHR is a record designed to follow the patient. A Health Information Exchange (HIE) is the infrastructure that moves records between organisations and assembles one longitudinal record. OpenSeva now simulates an HIE: four EMR "tenants" feed a shared FHIR store, and the genuinely hard interoperability problems — patient matching, consent, deduplication, provenance — become concrete, gradeable student projects.
Topology (tag-based, single store)
The four tenants
| Tenant | Organization | Holds |
|---|---|---|
| Clinic A | org-clinic-a | Conditions, encounters |
| Hospital B | org-hospital-b | Inpatient admissions, conditions |
| Lab C | org-lab-c | Lab orders + results |
| Pharmacy D | org-pharmacy-d | Prescriptions + dispenses |
Scope a tenant with ?organization=Organization/org-X. (HFS does not index _tag — it's a provenance label, not a filter.)
The cross-org matching scenario (the teaching payoff)
The seed deliberately registers the same human at several orgs with slightly different demographics and a different MRN each — exactly what a Master Patient Index must resolve. No single EMR sees the whole picture.
| Real person | Registered at | The twist | What only the merged record shows |
|---|---|---|---|
| Priya Sharma | Clinic A · Hospital B · Pharmacy D | Middle name & phone differ across orgs | Diabetes (A) + Hypertension (B) + active Metformin (D) |
| John Doe | Clinic A · Lab C | Fuzzy: Lab C has given name "J", not "John" |
Abnormal glucose lab (C) alongside his clinic chart (A) |
| Grace Lee / Omar Farah | One org each | Single-org controls | Must not match anyone — guards against false positives |
Load the HIE demo
./run.sh start # stack up (or: docker compose up -d) ./run.sh seed # base single-clinic demo (optional) ./seed-hie.sh # the multi-EMR HIE layer (idempotent)
Verify a tenant-scoped view and confirm Priya's record is split across orgs:
# Hospital B's patients only (→ Priya + Omar) curl 'http://localhost:8080/Patient?organization=Organization/org-hospital-b' # Priya's diabetes lives only at Clinic A; her Metformin only at Pharmacy D curl 'http://localhost:8080/Condition?subject=Patient/hie-a-priya' curl 'http://localhost:8080/MedicationDispense?subject=Patient/hie-d-priya'
hie/README.md.
Student Roadmap & Contributing
Hand-overOpenSeva is built to be handed to students: they pick a ticket from ROADMAP.md, build it, and raise a PR; maintainers review and merge. Every ticket lists the FHIR resources, acceptance criteria, and exact files to touch — no reverse-engineering required. Setup and the PR workflow live in CONTRIBUTING.md.
| Track | Theme | Example tickets |
|---|---|---|
| A | Quick wins / known-gap fixes Starter | Admin User Management UI · admin→Person mapping · patient provenance |
| B | Clinical chart Intermediate | Conditions · Allergies · Immunizations · Vitals trends · Labs · SOAP notes |
| C | Workflow & operations Intermediate | Lab orders · e-Prescribing · native scheduling · AuditEvent · billing |
| D | Interoperability & standards Advanced | Bulk $export · FHIR Subscriptions · SMART-on-FHIR launch |
| E | HIE simulation Capstone | Patient matching (MPI) · unified record · cross-org consent · dedup |
| F | AI & data generation Mixed | AI record summary · suggest ICD-10/SNOMED · synthetic cohorts · CDS alerts |
Hand-over workflow
- 1Student picks a ticket and opens a GitHub issue to claim it.
- 2Branch:
feat/B1-conditions. - 3Build against the acceptance criteria; test across roles.
- 4Raise a PR with screenshots + how-to-test.
- 5Maintainer reviews & merges.
Good first tickets
All 🟢 Starter — well-scoped, few files, teach the codebase patterns:
- A1 — Admin User Management UI (the auth API already exists)
- A3 — Patient provenance ("recorded by Dr. X")
- B1 / B2 / B3 — Conditions · Allergies · Immunizations tabs
ROADMAP.md, CONTRIBUTING.md, and hie/README.md live at the repo root.