On this page — 6 sections
Emulators & the non-PHI version of the system
The EMR emulators were created to test and demonstrate this software. Registering with fifteen vendors takes weeks each, and sandboxes are not always obtainable at all (MEDITECH has no self-service sandbox). The emulators are synthetic stand-ins for every profiled vendor, so the entire integration — auth, bulk export, writes, in-context launch — can be exercised and shown end to end without a real EMR and without a single byte of PHI. The same machinery is how you run a complete non-PHI version of the platform for evaluation, demonstration, training and CI.
1. What they are
One server per vendor (emulators/ in the repository; runbooks/RUNBOOK_EMULATORS.md is the operational reference). All data is synthetic — one set of fake patients, imported from a single source (scripts/mock_epic_server.py) rather than copied, so there is exactly one synthetic dataset in the repository.
| Vendor emulator | Port | Auth it accepts | $export | Creatable |
|---|---|---|---|---|
| Epic | 9101 | JWT assertion (RS384) | yes | DocumentReference |
| Oracle Health (Cerner) | 9102 | JWT assertion (RS384) or client secret (Basic header or form); explicit scopes required; wildcard scopes refused | yes | DocumentReference, Condition, Observation |
| athenahealth | 9103 | client secret only — a JWT assertion is refused | yes | DocumentReference |
| eClinicalWorks | 9104 | JWT assertion (RS384) | yes | nothing |
| NextGen Healthcare | 9105 | JWT assertion (RS384) | no — refuses with an OperationOutcome | DocumentReference |
| MEDITECH Expanse | 9106 | JWT assertion (RS384) | yes | nothing |
| ModMed | 9107 | JWT assertion (ES384 only — an RS384 assertion is refused as invalid_client); explicit scopes required | yes | nothing |
| Altera Digital Health | 9108 | JWT assertion (RS384 — the default for a point Altera leaves undocumented); explicit scopes required | yes | nothing |
| Greenway Health | 9109 | JWT assertion (ES384 only — an RS384 assertion is refused as invalid_client); explicit scopes required | yes | nothing |
| Veradigm | 9110 | JWT assertion (RS384 or RS256 — RSA only) | yes | nothing |
| Practice Fusion | 9111 | JWT assertion (RS384 or ES384); explicit scopes required | yes | nothing |
| TruBridge | 9112 | JWT assertion (RS256, RS384, ES256 or ES384) or client secret; explicit scopes required | yes | nothing |
| MEDHOST | 9113 | JWT assertion (RS384 or ES384) | yes | nothing |
| Netsmart | 9114 | JWT assertion (RS384 — the default for a point Netsmart leaves undocumented) or client secret; explicit scopes required | yes | DocumentReference, DiagnosticReport |
| Nextech | 9115 | JWT assertion (RS384 or ES384); explicit scopes required | yes | DocumentReference |
2. Usage
Start them all, or one vendor:
python -m emulators # all fifteen (ports 9101–9115) python -m emulators --vendor cerner # one vendor
Point the platform at an emulator exactly as at a real EMR — that is the point. Set the FHIR base URL to the emulator (http://127.0.0.1:9101/api/FHIR/R4 for Epic), select the vendor profile, and run the bulk import: kickoff, a genuinely asynchronous status poll (the first poll returns 202, as every real implementation does), NDJSON download, encrypt-store-index. Plain http is accepted only for loopback (127.0.0.1, ::1, localhost) — the same carve-out RFC 8252 makes — and a remote http issuer is refused outright.
In-context launch, end to end: open http://127.0.0.1:9101/emulator/launch — a stand-in for a clinician viewing a chart — and it sends a SMART EHR launch at the platform (python -m emulators --record-url http://127.0.0.1:8080/smart/launch). The launch is honest: the id_token is really RSA-signed with a key published at /.well-known/jwks.json, PKCE is enforced, and authorization codes are single-use — an emulator handing out unsigned tokens would let a client that skips signature verification pass its tests, which is precisely the defect worth catching. Register an emulator in the SMART issuer allowlist like any EMR, and mark it record_source: true if the deployment ingests from it (the loader matches that key literally; a misspelling is silently forward-compatible, so confirm by launching, not by re-reading the YAML).
3. They reproduce the seams, not just the happy path
Testing against one generic FHIR server proves very little — integrations break on the vendor seams, and each emulator reproduces its vendor's honestly, including the unhelpful behaviors:
- athenahealth rejects a JWT assertion with invalid_client, as it would live; a client secret sent to any JWT-only vendor gets the mirror-image refusal.
- Cerner demands explicit system scopes at the token endpoint — a scope-less request (Epic's normal shape) or a wildcard gets invalid_scope, exactly as Oracle Health documents — and it also accepts the Basic-header secret mode.
- NextGen refuses $export with an OperationOutcome, not an empty result — "unsupported" must never be mistakable for "no data".
- Epic advertises create for almost nothing; MEDITECH and eClinicalWorks for nothing at all — so the delivery capability check has something real to refuse.
- Only Cerner honors If-None-Exist; the others return 412, which is what makes duplicate-prevention testable.
- Pagination is two per page regardless of _count, so a mishandled next link fails here, not against a large practice.
4. What a green run proves — and does not
It proves the client handles the shapes these servers produce: the auth flows, pagination, the async bulk-export handshake, capability-gated writes, conditional create, and in-context launch with a verified id_token — the majority of integration defects. It is not certification. A particular health system's build may behave differently; every vendor profile still requires confirmation against the instance's own CapabilityStatement, and the emulators never override that.
5. Setting up the non-PHI version of the system
A complete deployment of the real software in which no real person's data exists anywhere — the configuration for evaluation, demonstrations, training and CI, and the configuration this platform's own development runs on:
- Deploy the platform normally — the full stack from Setup & deployment (dev tfvars are fine), or a local scripts/run_web_dev.sh instance. Nothing about the non-PHI configuration is a different codebase.
- Generate the synthetic population: scripts/generate_corpus.py builds a Synthea-generated corpus — invented people with clinically plausible longitudinal records. Synthea is directionally, not quantitatively, calibrated: it supplies volume and structure, never an epidemiological claim.
- Load it through the real pipeline — either directly (scripts/load_local_corpus.py, which runs reference normalization, sensitivity classification, encrypt-store-index) or the full dress rehearsal: serve the corpus from the Epic emulator and run the bulk import manager against it, so kickoff → poll → NDJSON → store is exercised end to end.
- Use development personas (PHI_AI_WEB_DEV_PERSONAS) for the role-switching walkthrough — fabricated identity, every action still audited against the persona shown, and never enabled in production.
- Enable the assistant against the synthetic corpus — the full RAG loop, citations, withholding and refusals all demonstrate with zero PHI exposure.
- Exercise every seam: the emulators for all fifteen vendors, the streaming and export managers' seeded specimens, the consent gate, the Part 2 refusal, the audit chain verification.
- Say what it is. A non-PHI deployment should label itself synthetic on every page — a fixed banner stating no real patients exist in it — because the honest label is what lets you show the system to anyone without a BAA in sight.
What the non-PHI version gives you: every feature of the platform — the gates, the assistant, the integration managers, the audit trail — behaving exactly as production does, demonstrable to any audience, testable in CI, with no real patient in the blast radius of a mistake.
6. This demonstration site's synthetic environment
Everything below is demonstration data — it documents this public site's synthetic environment, and none of it is a property of the platform. The system documentation deliberately describes the system without it.
6.1 The demonstration cast
The header's persona switcher signs you in as one of six synthetic profiles. Every one is fictional; the System Administrator carries the author's name by design.
| Persona | Username | Roles | What it models |
|---|---|---|---|
| Becky Crumpler, MD | b.crumpler | Clinician | A treating physician: charts in context, the assistant, drafting and signing. No claims detail, no audit access, no psychotherapy notes. |
| Rose Okafor, RHIA | r.okafor | Health Information Management, Auditor | The records professional: release of information, claims, integration monitors, audit trail reads. |
| Levi Isaac, MBA | l.isaac | Analyst | Population health and research on the de-identified plane — no record-level identifier ever renders. |
| Dr. Carla Jung | c.jung | Clinician, Psychotherapy | The behavioral-health clinician: ordinary chart access plus the psychotherapy store. |
| Sofia Reyes, MBA | s.reyes | Administrator, Auditor, Disposition | Operations and governance. Cannot open a chart — operations does not need one. |
| Ryan Gomez | r.gomez | System Administrator | Every permission plus the Control panel. Every switch is audited under this name. |
6.2 The synthetic dataset
- 33,311 synthetic patients with longitudinal records — conditions, medications, allergies, encounters, labs and vitals, documents, immunizations, procedures, claims with line items. A generated national population: a realistic age pyramid, multicultural names, age-appropriate payer mix, and prevalence- weighted conditions across all fifty states. Deterministic generator (scripts/gen_population.py); no real person's data appears anywhere on this site.
- Psychotherapy notes are hand-authored synthetic specimens, including 42 CFR Part 2 examples, so the segregation and redisclosure refusals can be demonstrated on real content shapes.
- Imaging is generated on this server — deterministic synthetic phantom frames per study, watermarked SYNTHETIC, across CT, radiographs, ultrasound and retinal modalities. Some studies carry a planted finding for the AI read to discover.
- Ambient encounters are two scripted, synthesized recordings — a Texas in-person diabetes follow-up for Rolf Marlin Grant and a California telehealth hypertension check for Rafaela Yolando Satterfield — transcribed by the self-hosted listener; the transcripts shown are its genuine output.
- The source EMR on the integration screens is a ten-patient synthetic roster that exists "at the vendor" until you import it — the day-one reality of a new patient between bulk runs.
6.3 The demonstration model and limits
- Claude Sonnet 5 is the demonstration model only. In a deployment you bring your own model under your own BAA with your AI provider; every AI feature degrades to an honest scripted responder when live calls are off.
- Per-visitor rate limits keep the public demo affordable: 20 assistant questions/hour, 15 documentation-assistant questions/hour, 8 fresh AI imaging reads/hour (studies already read stay available to everyone).
- No live microphone, ever. The ambient capture controls drive the bundled recordings; a public site must not collect real voices.
6.4 A worked example against this dataset
The API documentation uses placeholder values; against this site's seeded data, the same retrieval tool call looks like this:
{"name": "search_patients", "input": {"query": "rolf grant"}}
→ [{"id": 951, "mrn": "MRN-106857", "name": "Rolf Marlin Grant",
"birth_date": "1935-05-13", "gender": "male", "payer": "Humana"}]
Citations in assistant answers use the same synthetic identifiers, e.g. [MRN-106857 · chart].
Companions: Setup & deployment for the production path, and EMR connections for the vendor seams the emulators reproduce.