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:
| Store | Contents |
|---|---|
| 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 storage | The 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.
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:
sysadminhuman resourcesbiller managerreport
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):
- Patient chart → Admin tab → “Export Client Data” — exports the patient whose chart is open.
- 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}.zipIts contents:
dictionary.jsonis 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.jsonis 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.jsonalways reports the document count, so an absent directory is never ambiguous.- Files are linked to their records by the
idcolumn of the corresponding CSV: a file atassets/documents/45231/scan.pdfbelongs to the row withid45231indocuments.csv.
04CSV conventions
All CSV files follow RFC 4180.
| Property | Value |
|---|---|
| Character encoding | UTF-8 |
| Delimiter | Comma (,) |
| Quoting | Fields containing a comma, quote or newline are double-quoted; embedded quotes are doubled |
| Line terminator | \n |
| Header row | Always present, always first, even when there are no data rows |
| Empty value | An empty, unquoted field represents NULL / absent |
Value representation
| Stored type | Representation in CSV |
|---|---|
| NULL | Empty field |
| Boolean | true / false |
| Timestamp | ISO 8601 in UTC — 2026-03-14T09:22:31Z |
| Date | YYYY-MM-DD — 2026-03-14 |
| Object or array | JSON — {"key":"value"} |
| Binary / BLOB | Base64, prefixed with the literal base64: — base64:iVBORw0KGgo… |
| Everything else | The 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.
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-
NULLvalue seen for that field — and are reported indictionary.jsonas lower-cased runtime type names such asstring,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
| Key | Type | Meaning |
|---|---|---|
| spec | string | The certification criterion, ONC 170.315(b)(10)(i) |
| client_id | integer | EMR-Bear’s internal identifier for the exported patient |
| generated_at | string | ISO 8601 UTC timestamp of the export |
| file_count | integer | Number of data files described in files |
| row_count | integer | Total data rows across all files |
| asset_count | integer | Documents and images copied into assets/ |
| assets_missing | integer | Files a record refers to that could not be retrieved from storage |
files[] — one entry per CSV written, sorted by filename
| Key | Type | Meaning |
|---|---|---|
| file | string | Filename within the package, e.g. encounters.csv |
| source | string | mysql or mongodb — see vocabularies below |
| table | string | Source table or collection name |
| tier | string | How the rows were linked to the patient — see vocabularies below |
| links | string[] | 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. |
| description | string | Plain-language description of what the table holds, or Not yet described. |
| row_count | integer | Data rows in the file, excluding the header |
| columns | object[] | {"name", "type"}, in file column order |
assets[] — one entry per table and attachment kind
| Key | Type | Meaning |
|---|---|---|
| table | string | The table whose rows own these files |
| attachment | string | The attachment field on that record, e.g. document |
| files | integer | Files successfully copied into assets/ |
| bytes | integer | Total size in bytes of those files |
| missing | integer | Files the record refers to that were not retrievable |
excluded[] and failures[]
| Array | Keys | Meaning |
|---|---|---|
| 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:
| Value | Meaning |
|---|---|
| mysql | Relational database table |
| mongodb | Document database collection |
tier takes exactly one of:
| Value | Meaning |
|---|---|
| 0-root | The patient’s own record (people.csv), matched on the primary key |
| 1-person_scoped | Linked by a person_id or patient_id column |
| 1-person_polymorphic | Linked by a polymorphic owner pair declared on the person record |
| 2-client_id | Linked by a client_id column, or a custom foreign key holding the patient’s id |
| 3-client_polymorphic | Linked by a polymorphic owner pair, e.g. contactable_id + contactable_type |
| 4-second_hop | Reached through an intermediate record, for tables with no direct patient column |
| 5-mongodb | Document 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.
- The patient’s own record. EMR-Bear stores patients in a shared
peopletable. The patient’s row is matched by primary key and written topeople.csv. - A direct identifier column. The table carries
client_id,person_idorpatient_idholding the patient’s identifier. - A polymorphic owner. The table carries an
<owner>_id/<owner>_typepair. - A second hop. For tables with no direct patient column, rows are reached through an intermediate record. Used only where no direct link exists.
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:
| Excluded | Reason |
|---|---|
| hl7_endpoints | Holds 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.csvmay contain more than one row. Where duplicate records for the same person have been merged or hidden, the related rows are linked byhidden_active_client_idand all of them are exported.versions.csvis not a complete audit trail. Only rows whoseitem_typeisPersonare patient-scoped and exported. Change history for related records carries otheritem_typevalues 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[].missingandexport.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.jsonare 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.
- Unzip the archive.
- Open
dictionary.json. Usefiles[]to find the tables relevant to your purpose — each carries a plain-languagedescription. - Load the CSVs with any RFC 4180 parser — spreadsheet software,
pandas.read_csv, R’sreadr, Ruby’sCSV. Parse them; do not count lines. - Join across files on the identifier columns, which retain EMR-Bear’s internal values consistently across the whole package.
- Find a record’s documents at
assets/<table>/<id>/, where<id>is the record’sidcolumn value. - 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.