EMR-Bear EHI Export Format

Public format documentation

EMR-Bear Single-Patient EHI Export Format

How to read the export file EMR-Bear produces for one patient’s complete electronic health information — its package layout, its data dictionary, its CSV conventions, and the limits it declares about itself. Published so that anyone receiving an export can process it without assistance from EMR-Bear.

Format version
1.0
Last updated
28 August 2026
Criterion
45 CFR § 170.315(b)(10)(i)
Package type
ZIP · CSV · JSON

This document describes the format of the export file produced by EMR-Bear’s single-patient electronic health information (EHI) export, certified under ONC/ASTP Health IT criterion 45 CFR § 170.315(b)(10)(i).

Under § 170.315(b)(10)(i)(E), the hyperlink to this document is included inside every export. Under § 170.315(b)(10)(iii), this document is kept up to date as the format changes.

01Scope of the export

A single export contains all of the electronic health information EMR-Bear stores for one patient, drawn from both of the system’s data stores and from file storage:

StoreContents
Relational (MySQL)Demographics, enrollment, scheduling, encounters, orders, billing, claims, consents, assessments, audit and workflow records
Document (MongoDB)Clinical document content — allergies, medications, problems, clinical notes, registries, referrals
Object storageThe patient’s scanned documents, images and other file attachments

The set of exported tables is discovered at run time by reflecting over the live database schema and the application’s association graph. It is not a hand-maintained list. A table is included whenever it can be linked to a single patient; the only exclusions are the small, explicitly enumerated set in § 7. A schema change that adds new patient-linked storage is therefore picked up automatically rather than silently omitted.

Completeness

Deleted and hidden records are included. The export reads tables without the application’s default scopes, so soft-deleted rows, superseded rows and hidden duplicate records are exported. They remain part of the patient’s health information.

02How an export is produced

Who can run it

Export is limited to a specific set of identified users, satisfying § 170.315(b)(10)(i)(C)(1). A user must hold one of the following roles:

  • sysadmin
  • human resources
  • biller manager
  • report

Users without one of these roles are not shown the export controls and cannot invoke the export endpoint.

How it is run

A permitted user starts an export from either of two places in the application, with no assistance from EMR-Bear staff or developers, satisfying § 170.315(b)(10)(i)(B):

  1. Patient chart → Admin tab → “Export Client Data” — exports the patient whose chart is open.
  2. Reports → “Client EHI Export (ONC)” — select a single patient by name.

The export runs as a background job. When it completes, the ZIP file is available for download from the Reports page. Exactly one patient must be selected: the export refuses to run with no patient selected, and refuses to run with more than one.

03Package structure

The export is a single ZIP archive:

ehi-export-client-{patient_id}-{YYYY-MM-DD}.zip

Its contents:

README.txt Plain-text orientation, including the hyperlink to this document dictionary.json Machine-readable description of everything in the package <table>.csv One file per relational table (people.csv, encounters.csv, …) mongo_<collection>.csv One file per document collection assets/<table>/<row_id>/<filename> The patient's documents and images
  • dictionary.json is the authoritative index. Read it first. It names every data file, where it came from, how it was linked to the patient, every column and its type, and how many rows were written. It also records what was excluded and why, and anything that failed.
  • A CSV containing only a header row is normal and is not an error. It means EMR-Bear stores nothing for this patient in that table. A typical patient has data in a few dozen of the several hundred exported tables. A file that is missing entirely while listed in dictionary.json is a defect.
  • The mongo_ prefix distinguishes document-store collections from relational tables and prevents a collection name from colliding with a table name.
  • assets/ is absent when the patient has no stored documents. dictionary.json always reports the document count, so an absent directory is never ambiguous.
  • Files are linked to their records by the id column of the corresponding CSV: a file at assets/documents/45231/scan.pdf belongs to the row with id 45231 in documents.csv.

04CSV conventions

All CSV files follow RFC 4180.

PropertyValue
Character encodingUTF-8
DelimiterComma (,)
QuotingFields containing a comma, quote or newline are double-quoted; embedded quotes are doubled
Line terminator\n
Header rowAlways present, always first, even when there are no data rows
Empty valueAn empty, unquoted field represents NULL / absent

Value representation

Stored typeRepresentation in CSV
NULLEmpty field
Booleantrue / false
TimestampISO 8601 in UTC — 2026-03-14T09:22:31Z
DateYYYY-MM-DD2026-03-14
Object or arrayJSON — {"key":"value"}
Binary / BLOBBase64, prefixed with the literal base64:base64:iVBORw0KGgo…
Everything elseThe stored value as text

Text handling

Clinical free text in a long-lived record can contain byte sequences that are not valid UTF-8, and NUL bytes. Because a corrupted CSV is worse than an absent one — it looks complete — the export normalises text rather than failing:

  • Invalid UTF-8 byte sequences are removed.
  • NUL bytes (0x00) are removed.
  • Newlines inside a value are preserved and the field is quoted.
Parsing

Parse these files with a real CSV parser. Do not count lines to count records — clinical notes contain newlines.

The [unrepresentable: …] sentinel

If a single stored value cannot be rendered at all, the cell is written as the literal marker:

[unrepresentable: Encoding::UndefinedConversionError]

This is a marker, not patient content. It exists so that one bad value cannot cost the rest of the table. It is rare; where you encounter one, the underlying value is still held in EMR-Bear and can be retrieved on request.

Document-collection (mongo_*.csv) specifics

A document store has no fixed schema, so:

  • The header row is the union of the field names actually present across this patient’s documents in that collection, in first-seen order. Fields no document uses do not appear.
  • A document lacking a field that appears in the header has an empty cell for it.
  • Nested objects and arrays are written as JSON.
  • Column types are inferred from the data — the type of the first non-NULL value seen for that field — and are reported in dictionary.json as lower-cased runtime type names such as string, integer, time, bson::objectid. They are not declared SQL types.

05dictionary.json

The machine-readable description of the package. It is generated while the export is being written, so it describes what actually happened rather than reconstructing it afterwards.

Top-level structure

{
  "export":   { … },   // object  — summary of this export
  "notes":    [ … ],   // strings — conventions and stated limits, in prose
  "files":    [ … ],   // objects — one per data file written
  "assets":   [ … ],   // objects — one per attachment type copied
  "excluded": [ … ],   // objects — what was deliberately not exported, and why
  "failures": [ … ]    // objects — what could not be read (empty in a clean export)
}

export

KeyTypeMeaning
specstringThe certification criterion, ONC 170.315(b)(10)(i)
client_idintegerEMR-Bear’s internal identifier for the exported patient
generated_atstringISO 8601 UTC timestamp of the export
file_countintegerNumber of data files described in files
row_countintegerTotal data rows across all files
asset_countintegerDocuments and images copied into assets/
assets_missingintegerFiles a record refers to that could not be retrieved from storage

files[] — one entry per CSV written, sorted by filename

KeyTypeMeaning
filestringFilename within the package, e.g. encounters.csv
sourcestringmysql or mongodb — see vocabularies below
tablestringSource table or collection name
tierstringHow the rows were linked to the patient — see vocabularies below
linksstring[]Human-readable description of the link(s) used to filter rows. Not a machine-parseable column list — values include plain column names ("client_id"), polymorphic pairs ("contactable_id + contactable_type"), indirect hops ("through crew_teams") and, for people.csv, "id (type = 'Client')". Treat it as provenance for a reader, not as a filter to re-execute.
descriptionstringPlain-language description of what the table holds, or Not yet described.
row_countintegerData rows in the file, excluding the header
columnsobject[]{"name", "type"}, in file column order

assets[] — one entry per table and attachment kind

KeyTypeMeaning
tablestringThe table whose rows own these files
attachmentstringThe attachment field on that record, e.g. document
filesintegerFiles successfully copied into assets/
bytesintegerTotal size in bytes of those files
missingintegerFiles the record refers to that were not retrievable

excluded[] and failures[]

ArrayKeysMeaning
excluded table, reason A table or collection deliberately not exported, with the reason. See § 7.
failures table, error, message A table that could not be read and was not exported at all; its absence is declared rather than silent. message is truncated to 500 characters. Present and empty in a clean export.

Controlled vocabularies

source takes exactly one of:

ValueMeaning
mysqlRelational database table
mongodbDocument database collection

tier takes exactly one of:

ValueMeaning
0-rootThe patient’s own record (people.csv), matched on the primary key
1-person_scopedLinked by a person_id or patient_id column
1-person_polymorphicLinked by a polymorphic owner pair declared on the person record
2-client_idLinked by a client_id column, or a custom foreign key holding the patient’s id
3-client_polymorphicLinked by a polymorphic owner pair, e.g. contactable_id + contactable_type
4-second_hopReached through an intermediate record, for tables with no direct patient column
5-mongodbDocument collection, linked by the client_id field

Worked example

An abbreviated dictionary.json. All values are synthetic.

{
  "export": {
    "spec": "ONC 170.315(b)(10)(i)",
    "client_id": 100042,
    "generated_at": "2026-08-28T14:03:11Z",
    "file_count": 312,
    "row_count": 4871,
    "asset_count": 26,
    "assets_missing": 0
  },
  "notes": [
    "One CSV per table. A file with only a header row means nothing is stored for this
     client in that table; a table listed under \"failures\" was not exported at all."
  ],
  "files": [
    {
      "file": "encounters.csv",
      "source": "mysql",
      "table": "encounters",
      "tier": "2-client_id",
      "links": ["client_id"],
      "description": "Clinical encounters — a visit or contact that clinical
                      documentation hangs from.",
      "row_count": 143,
      "columns": [
        { "name": "id",         "type": "integer"  },
        { "name": "client_id",  "type": "integer"  },
        { "name": "start_time", "type": "datetime" },
        { "name": "note",       "type": "text"     }
      ]
    },
    {
      "file": "mongo_med_allergies.csv",
      "source": "mongodb",
      "table": "med_allergies",
      "tier": "5-mongodb",
      "links": ["client_id"],
      "description": "Allergies recorded on the client's clinical record.",
      "row_count": 3,
      "columns": [
        { "name": "_id",       "type": "bson::objectid" },
        { "name": "client_id", "type": "integer"        },
        { "name": "substance", "type": "string"         },
        { "name": "reaction",  "type": "string"         }
      ]
    }
  ],
  "assets": [
    { "table": "documents", "attachment": "document",
      "files": 26, "bytes": 8134902, "missing": 0 }
  ],
  "excluded": [
    { "table": "hl7_endpoints",
      "reason": "Holds TLS certificates and private keys — must never be exported" }
  ],
  "failures": []
}

06How rows are linked to the patient

Every exported row is tied to the patient by one of the mechanisms below. The mechanism used for each file is reported in that file’s tier and links values.

  1. The patient’s own record. EMR-Bear stores patients in a shared people table. The patient’s row is matched by primary key and written to people.csv.
  2. A direct identifier column. The table carries client_id, person_id or patient_id holding the patient’s identifier.
  3. A polymorphic owner. The table carries an <owner>_id / <owner>_type pair.
  4. A second hop. For tables with no direct patient column, rows are reached through an intermediate record. Used only where no direct link exists.
If you query these files directly

A polymorphic type column holds the value Person, never Client. Patients are stored as a subtype of person and the stored discriminator is the base type. Filtering on Client returns no rows.

Where a table can be reached in more than one way — for example a table carrying both a client_id column and a polymorphic owner — the export takes the union of the direct links, so rows set by either mechanism are included. Indirect second-hop links are not unioned with direct ones, because some intermediate records are shared between patients and unioning them would pull in another patient’s rows.

Attachments are copied only for rows that were actually written, so a document can never ship without the record it belongs to.

07What is not included, and why

Everything reachable from a patient is exported. The following are the only deliberate exclusions, and each is listed with its reason in the excluded array of every export:

ExcludedReason
hl7_endpointsHolds TLS certificates and private keys — must never be exported
client_imports
authorization_imports
superbill_imports
otp_order_imports
otp_verification_imports
Import staging areas, not patient records. The resulting patient records are exported.
billing_configurations
terms_conditions
fax_cover_sheets
education_materials
System configuration, not patient records
Collections with no client_id Cannot be scoped to a single patient; each such collection is listed by name with this reason

Nothing else is withheld. In particular, soft-deleted rows, superseded rows and hidden duplicate patient records are included.

08Stated limits of the export

These are properties a recipient could not infer from the files themselves. They are repeated in the notes array of dictionary.json.

  • people.csv may contain more than one row. Where duplicate records for the same person have been merged or hidden, the related rows are linked by hidden_active_client_id and all of them are exported.
  • versions.csv is not a complete audit trail. Only rows whose item_type is Person are patient-scoped and exported. Change history for related records carries other item_type values and is not included in this file.
  • A document a record references may be unretrievable. Where a record names a stored file that could not be retrieved from object storage, the record still ships and the file is counted in assets[].missing and export.assets_missing. A non-zero value there means the package is short by that many files — declared, rather than silent.
  • A table that could not be read is declared. It appears in failures[] with the error and is never silently omitted. failures[] is empty in a clean export.
  • Row counts in dictionary.json are authoritative. If a file’s row count disagrees with the dictionary, treat the package as defective and request a fresh export.

09Reading an export

The package is designed to be readable with ordinary tooling and no EMR-Bear software.

  1. Unzip the archive.
  2. Open dictionary.json. Use files[] to find the tables relevant to your purpose — each carries a plain-language description.
  3. Load the CSVs with any RFC 4180 parser — spreadsheet software, pandas.read_csv, R’s readr, Ruby’s CSV. Parse them; do not count lines.
  4. Join across files on the identifier columns, which retain EMR-Bear’s internal values consistently across the whole package.
  5. Find a record’s documents at assets/<table>/<id>/, where <id> is the record’s id column value.
  6. Decode any value beginning base64: by stripping that prefix and Base64-decoding the remainder.

10Format versioning and currency

This document describes format version 1.0.

The format is generated directly by the export code rather than transcribed from it: the column lists, types, row counts and table descriptions in dictionary.json are produced from the live schema at export time. As a result, the dictionary.json inside any given package is always an accurate description of that package, even if this document has not yet caught up with a very recent schema change.

Per § 170.315(b)(10)(iii), this document is kept up to date. It is maintained in the same repository as the export implementation and is reviewed whenever the export’s structure, conventions or exclusions change. Changes that would affect a consumer — a new or removed top-level dictionary.json key, a change to value encoding, a change to the package layout, or a new source or tier value — increment the version above.

11Contact

Questions about this format, or a request for data that appears to be missing from an export, should be directed to EMR-Bear support through your organisation’s usual support channel.