@onivoro/isomorphic-fhir 24.38.2 → 25.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -135
- package/package.json +2 -3
- package/src/lib/functions/normalize-fhir-bundle.function.js +215 -151
- package/src/lib/functions/normalize-fhir-bundle.function.js.map +1 -1
- package/src/lib/functions/fhir-test-config.d.ts +0 -127
- package/src/lib/functions/fhir-test-config.js +0 -276
- package/src/lib/functions/fhir-test-config.js.map +0 -1
- package/src/lib/functions/test-setup.d.ts +0 -13
- package/src/lib/functions/test-setup.js +0 -81
- package/src/lib/functions/test-setup.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,44 +1,53 @@
|
|
|
1
1
|
# @onivoro/isomorphic-fhir
|
|
2
2
|
|
|
3
|
-
A library for
|
|
3
|
+
A library for flattening FHIR (Fast Healthcare Interoperability Resources) R4 bundles into a set of relational-style tables (plain arrays of row objects), plus TypeScript interfaces for the supported FHIR resources and the generated tables.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @onivoro/isomorphic-fhir
|
|
9
|
+
```
|
|
4
10
|
|
|
5
11
|
## Overview
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
`normalizeFhirBundle` walks a bundle's entries and splits each supported resource into a main row plus child rows for its repeating and nested parts. Along the way it:
|
|
8
14
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
+
- Generates a new UUID (v4) `id` for every row and links child rows to their parent through that id (e.g. `patientNames[].patientId`, `codings[].codeableConceptId`)
|
|
16
|
+
- Keeps the original FHIR `id` in `resourceId` on main rows
|
|
17
|
+
- Stores shared data types (addresses, telecoms, CodeableConcepts and codings, notes, attachments, references) in generic tables keyed by `resourceType` + `resourceId` (+ `fieldName` where relevant)
|
|
18
|
+
- Copies FHIR reference strings (e.g. `"Patient/patient-123"`) as-is into `*Reference` columns and the `references` table; references are not resolved to generated ids
|
|
19
|
+
- Stamps rows with `createdAt` (and `updatedAt` on main rows) set to the time of normalization
|
|
20
|
+
|
|
21
|
+
It does not deduplicate rows (two identical addresses produce two `addresses` rows), validate resources, or talk to a database.
|
|
15
22
|
|
|
16
23
|
## Supported FHIR Resources
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- **Patient**:
|
|
21
|
-
- **Practitioner**:
|
|
22
|
-
- **Organization**:
|
|
23
|
-
- **Encounter**:
|
|
24
|
-
- **Condition**:
|
|
25
|
-
- **Observation**:
|
|
26
|
-
- **MedicationRequest**:
|
|
27
|
-
- **Procedure**:
|
|
28
|
-
- **Immunization**:
|
|
29
|
-
- **AllergyIntolerance**:
|
|
30
|
-
- **DiagnosticReport**:
|
|
31
|
-
- **Location**:
|
|
32
|
-
- **Appointment**:
|
|
33
|
-
- **CareTeam**:
|
|
34
|
-
- **CarePlan**:
|
|
25
|
+
Entries whose `resourceType` is one of the following are normalized; any other resource type only produces a `bundleEntries` row.
|
|
26
|
+
|
|
27
|
+
- **Patient**: demographics, identifiers, names, contacts (with their telecoms/address), addresses, telecoms, general practitioners, photos, marital status
|
|
28
|
+
- **Practitioner**: demographics, qualifications, identifiers, names, telecoms, addresses, photos, communication languages
|
|
29
|
+
- **Organization**: types, identifiers, telecoms, addresses, endpoints
|
|
30
|
+
- **Encounter**: class, status, period, participants, subject, identifiers, basedOn, appointments, reasons
|
|
31
|
+
- **Condition**: clinical/verification status, code, category, body site, notes
|
|
32
|
+
- **Observation**: code, value, components, category, interpretation, notes, performers, related references
|
|
33
|
+
- **MedicationRequest**: medication, dosage instructions (first dose/rate), category, reasons, notes, related references
|
|
34
|
+
- **Procedure**: performers, reasons, body site, complications, follow-up, notes, used items
|
|
35
|
+
- **Immunization**: vaccine, dose, site/route, reasons, subpotent reasons, program eligibility, notes
|
|
36
|
+
- **AllergyIntolerance**: reactions, manifestations, categories, notes
|
|
37
|
+
- **DiagnosticReport**: result references, category, performers, conclusion codes, presented forms
|
|
38
|
+
- **Location**: position, type, aliases, telecoms, address, endpoints
|
|
39
|
+
- **Appointment**: participants, reasons, slots, related references
|
|
40
|
+
- **CareTeam**: participants, category, reasons, managing organizations, telecoms, notes
|
|
41
|
+
- **CarePlan**: activities, category, goals, care teams and other references, notes
|
|
42
|
+
|
|
43
|
+
Sub-structures not listed (for example `Condition.evidence`, `Encounter.location`, `Immunization.reaction`/`protocolApplied`, `Patient.communication`) are typed but not normalized.
|
|
35
44
|
|
|
36
45
|
## Usage
|
|
37
46
|
|
|
38
47
|
```typescript
|
|
39
|
-
import { normalizeFhirBundle } from '@onivoro/isomorphic-fhir';
|
|
48
|
+
import { normalizeFhirBundle, IFhirBundle } from '@onivoro/isomorphic-fhir';
|
|
40
49
|
|
|
41
|
-
const fhirBundle = {
|
|
50
|
+
const fhirBundle: IFhirBundle = {
|
|
42
51
|
resourceType: 'Bundle',
|
|
43
52
|
type: 'searchset',
|
|
44
53
|
entry: [
|
|
@@ -48,8 +57,8 @@ const fhirBundle = {
|
|
|
48
57
|
id: 'patient-123',
|
|
49
58
|
name: [{ family: 'Doe', given: ['John'] }],
|
|
50
59
|
gender: 'male',
|
|
51
|
-
birthDate: '1985-03-15'
|
|
52
|
-
}
|
|
60
|
+
birthDate: '1985-03-15',
|
|
61
|
+
},
|
|
53
62
|
},
|
|
54
63
|
{
|
|
55
64
|
resource: {
|
|
@@ -58,132 +67,88 @@ const fhirBundle = {
|
|
|
58
67
|
status: 'final',
|
|
59
68
|
code: { coding: [{ code: '8867-4', display: 'Heart rate' }] },
|
|
60
69
|
subject: { reference: 'Patient/patient-123' },
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
]
|
|
70
|
+
value: { value: 72, unit: 'beats/min' },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
65
74
|
};
|
|
66
75
|
|
|
67
76
|
const normalizedData = normalizeFhirBundle(fhirBundle);
|
|
68
77
|
|
|
69
|
-
//
|
|
70
|
-
console.log(normalizedData.patients);
|
|
71
|
-
console.log(normalizedData.patientNames);
|
|
72
|
-
console.log(normalizedData.observations);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
console.log(normalizedData.bundles); // one row for the bundle
|
|
79
|
+
console.log(normalizedData.patients); // patient demographics
|
|
80
|
+
console.log(normalizedData.patientNames); // patient names, linked by patientId
|
|
81
|
+
console.log(normalizedData.observations); // valueType 'Quantity', valueNumber 72, valueString 'beats/min'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Choice-type (`[x]`) fields
|
|
85
|
+
|
|
86
|
+
The input interfaces model FHIR choice-type elements with a single un-suffixed property, and the normalizer only reads that property. Use `value`, `effective`, `onset`, `abatement`, `deceased`, `multipleBirth`, `medication`, `performed`, `occurrence`, `asNeeded` and `scheduled` rather than the wire-format names such as `valueQuantity`, `effectiveDateTime` or `medicationCodeableConcept`. Raw FHIR JSON from a server must be mapped to these names first, or those fields are left empty.
|
|
87
|
+
|
|
88
|
+
## API
|
|
89
|
+
|
|
90
|
+
### `normalizeFhirBundle(bundle: IFhirBundle): INormalizedFhirData`
|
|
91
|
+
|
|
92
|
+
Returns an `INormalizedFhirData` object with one array per table. Every array is always present (empty when there is nothing to put in it).
|
|
93
|
+
|
|
94
|
+
### Types
|
|
95
|
+
|
|
96
|
+
- **Input interfaces** (`fhir-resources.interface`): `IFhirBundle`, `IFhirBundleEntry`, the `IFhirResource` union, one interface per supported resource (`IFhirPatient`, `IFhirPractitioner`, `IFhirOrganization`, `IFhirEncounter`, `IFhirCondition`, `IFhirObservation`, `IFhirMedicationRequest`, `IFhirProcedure`, `IFhirImmunization`, `IFhirAllergyIntolerance`, `IFhirDiagnosticReport`, `IFhirLocation`, `IFhirAppointment`, `IFhirCareTeam`, `IFhirCarePlan`), common data types (`IFhirIdentifier`, `IFhirHumanName`, `IFhirContactPoint`, `IFhirAddress`, `IFhirCodeableConcept`, `IFhirCoding`, `IFhirReference`, `IFhirPeriod`, `IFhirQuantity`, `IFhirRange`, `IFhirAge`, `IFhirAttachment`, `IFhirAnnotation`, `IFhirDuration`, `IFhirDateTime`, `IFhirDosage`, `IFhirTiming`, `IFhirTimingRepeat`, `IFhirRatio`, `IFhirDosageDoseAndRate`) and backbone elements (e.g. `IFhirPatientContact`, `IFhirEncounterParticipant`, `IFhirObservationComponent`, `IFhirCarePlanActivityDetail`).
|
|
97
|
+
- **Output interfaces** (`fhir-normalized-tables.interface`): `INormalizedFhirData` and one row interface per table, named `I<Table>Table` (e.g. `IPatientTable`, `IObservationComponentTable`, `ICodingTable`, `IReferenceTable`, `IBundleEntryTable`).
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { INormalizedFhirData, IPatientTable } from '@onivoro/isomorphic-fhir';
|
|
101
|
+
|
|
102
|
+
function patientsBornBefore(data: INormalizedFhirData, isoDate: string): IPatientTable[] {
|
|
103
|
+
return data.patients.filter((p) => p.birthDate && p.birthDate < isoDate);
|
|
104
|
+
}
|
|
76
105
|
```
|
|
77
106
|
|
|
78
107
|
## Normalized Tables Generated
|
|
79
108
|
|
|
80
|
-
|
|
109
|
+
`INormalizedFhirData` has 39 tables.
|
|
81
110
|
|
|
82
111
|
### Core Resource Tables
|
|
83
|
-
|
|
84
|
-
- **
|
|
85
|
-
- **
|
|
86
|
-
- **practitioners**:
|
|
87
|
-
- **organizations**: Healthcare organizations
|
|
88
|
-
- **encounters**: Healthcare encounters
|
|
89
|
-
- **conditions**: Medical conditions and diagnoses
|
|
90
|
-
- **observations**: Clinical measurements and findings
|
|
91
|
-
- **medicationRequests**: Medication prescriptions
|
|
92
|
-
- **procedures**: Medical procedures performed
|
|
93
|
-
- **immunizations**: Vaccination records
|
|
94
|
-
- **allergyIntolerances**: Allergy and intolerance information
|
|
95
|
-
- **diagnosticReports**: Diagnostic test reports
|
|
96
|
-
- **locations**: Healthcare facility information
|
|
97
|
-
- **appointments**: Scheduled appointments
|
|
98
|
-
- **careTeams**: Care team compositions
|
|
99
|
-
- **carePlans**: Patient care plans
|
|
112
|
+
|
|
113
|
+
- **bundles**: bundle metadata (`type`, `timestamp`, `total`)
|
|
114
|
+
- **bundleEntries**: one row per entry (`fullUrl`, search mode/score, `resourceType`, `resourceId`)
|
|
115
|
+
- **patients**, **practitioners**, **organizations**, **encounters**, **conditions**, **observations**, **medicationRequests**, **procedures**, **immunizations**, **allergyIntolerances**, **diagnosticReports**, **locations**, **appointments**, **careTeams**, **carePlans**: one row per resource; coded fields are flattened to the first coding's code/display
|
|
100
116
|
|
|
101
117
|
### Relationship and Detail Tables
|
|
102
|
-
|
|
103
|
-
- **patientNames
|
|
104
|
-
- **
|
|
105
|
-
- **
|
|
106
|
-
- **
|
|
107
|
-
- **
|
|
108
|
-
- **
|
|
109
|
-
- **
|
|
110
|
-
- **
|
|
111
|
-
- **
|
|
112
|
-
- **
|
|
113
|
-
- **
|
|
114
|
-
- **
|
|
115
|
-
- **careTeamParticipants**: Care team member details
|
|
116
|
-
- **carePlanActivities**: Care plan activity details
|
|
118
|
+
|
|
119
|
+
- **patientIdentifiers**, **patientNames**, **patientContacts**
|
|
120
|
+
- **practitionerQualifications**
|
|
121
|
+
- **organizationTypes** (first coding of each type)
|
|
122
|
+
- **encounterParticipants**
|
|
123
|
+
- **observationComponents**
|
|
124
|
+
- **medicationDosages**
|
|
125
|
+
- **procedurePerformers**
|
|
126
|
+
- **allergyReactions**, **allergyManifestations**
|
|
127
|
+
- **diagnosticReportResults**
|
|
128
|
+
- **appointmentParticipants**
|
|
129
|
+
- **careTeamParticipants**
|
|
130
|
+
- **carePlanActivities**
|
|
117
131
|
|
|
118
132
|
### Common Data Type Tables
|
|
119
|
-
- **addresses**: Physical addresses (deduplicated)
|
|
120
|
-
- **telecoms**: Contact points (phone, email, etc.)
|
|
121
|
-
- **codeableConcepts**: Coded concepts with text
|
|
122
|
-
- **codings**: Individual coding systems
|
|
123
|
-
- **notes**: Annotations and comments
|
|
124
|
-
- **attachments**: Binary attachments and documents
|
|
125
|
-
- **references**: Resource references and relationships
|
|
126
|
-
|
|
127
|
-
## Key Features
|
|
128
|
-
|
|
129
|
-
### FHIR Compliance
|
|
130
|
-
- Full FHIR R4 resource support
|
|
131
|
-
- Proper handling of FHIR data types (CodeableConcept, Reference, etc.)
|
|
132
|
-
- Preservation of clinical terminology and coding systems
|
|
133
|
-
- Support for complex nested structures
|
|
134
|
-
|
|
135
|
-
### Data Integrity
|
|
136
|
-
- Referential integrity through normalized foreign keys
|
|
137
|
-
- Unique constraint handling for identifiers
|
|
138
|
-
- Proper temporal data handling (effective periods, timestamps)
|
|
139
|
-
- Clinical status and verification status preservation
|
|
140
|
-
|
|
141
|
-
### Healthcare-Specific Patterns
|
|
142
|
-
- Support for multiple identifier systems per resource
|
|
143
|
-
- Proper handling of clinical hierarchies (conditions, observations)
|
|
144
|
-
- Care team and care plan relationship modeling
|
|
145
|
-
- Medication dosing and administration patterns
|
|
146
|
-
- Allergy and adverse reaction tracking
|
|
147
|
-
|
|
148
|
-
### Performance Optimized
|
|
149
|
-
- Efficient deduplication of common entities (addresses, codings)
|
|
150
|
-
- Bulk insert-friendly table structures
|
|
151
|
-
- Indexed foreign key relationships
|
|
152
|
-
- Minimal data redundancy
|
|
153
133
|
|
|
154
|
-
|
|
134
|
+
- **addresses**: addresses of any resource (`line` joined with `\n`)
|
|
135
|
+
- **telecoms**: contact points
|
|
136
|
+
- **codeableConcepts**: one row per CodeableConcept, with `fieldName` (e.g. `category`, `reasonCode`)
|
|
137
|
+
- **codings**: codings of each CodeableConcept, linked by `codeableConceptId`
|
|
138
|
+
- **notes**: annotations
|
|
139
|
+
- **attachments**: photos and presented forms
|
|
140
|
+
- **references**: references, keyed by `fieldName` (e.g. `subject`, `basedOn`, `performer`)
|
|
141
|
+
|
|
142
|
+
Identifiers of resources other than Patient are stored in **references** with `fieldName: 'identifier'` (the identifier's `type` goes to **codeableConcepts** as `identifier.type`). Practitioner names are stored as text in **codeableConcepts** with `fieldName: 'name'`: the name's `text`, or else its prefix, given, family and suffix parts joined with spaces, skipping missing parts. Patient contact names follow the same rule (`text`, or else given and family). The generic tables' `resourceId` is the generated row id of the owning record, not the FHIR id.
|
|
155
143
|
|
|
156
|
-
|
|
157
|
-
- Complete FHIR bundle normalization
|
|
158
|
-
- Individual resource type processing
|
|
159
|
-
- Complex nested structure handling
|
|
160
|
-
- Reference relationship preservation
|
|
161
|
-
- Edge cases and error conditions
|
|
144
|
+
## Testing
|
|
162
145
|
|
|
163
|
-
Run tests with:
|
|
164
146
|
```bash
|
|
165
|
-
nx test lib-isomorphic-fhir
|
|
147
|
+
npx nx test lib-isomorphic-fhir
|
|
166
148
|
```
|
|
167
149
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
This library is designed to work with FHIR R4 resources and follows HL7 FHIR standards for:
|
|
171
|
-
- Resource structure and cardinality
|
|
172
|
-
- Data type handling and validation
|
|
173
|
-
- Reference resolution and integrity
|
|
174
|
-
- Clinical terminology bindings
|
|
175
|
-
- Healthcare workflow patterns
|
|
176
|
-
|
|
177
|
-
## Use Cases
|
|
178
|
-
|
|
179
|
-
Perfect for:
|
|
180
|
-
- **EHR Data Warehousing**: Normalize FHIR data for analytics
|
|
181
|
-
- **Clinical Data Lakes**: Store FHIR resources in relational format
|
|
182
|
-
- **Interoperability Solutions**: Transform FHIR for legacy systems
|
|
183
|
-
- **Healthcare Analytics**: Enable SQL-based analysis of clinical data
|
|
184
|
-
- **Compliance Reporting**: Structure data for regulatory requirements
|
|
185
|
-
- **Research Databases**: Prepare clinical data for research queries
|
|
150
|
+
The project also contains integration specs that call the public HAPI FHIR server; see `INTEGRATION_TESTS.md` in the repository.
|
|
186
151
|
|
|
187
152
|
## License
|
|
188
153
|
|
|
189
|
-
This library is licensed under the MIT License. See the LICENSE file in this package for details.
|
|
154
|
+
This library is licensed under the MIT License. See the LICENSE file in this package for details.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onivoro/isomorphic-fhir",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "25.0.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -10,9 +10,8 @@
|
|
|
10
10
|
"url": "https://github.com/onivoro/monorepo.git"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@nestjs/swagger": "*",
|
|
14
13
|
"tslib": "^2.3.0",
|
|
15
|
-
"uuid": "
|
|
14
|
+
"uuid": "^9.0.1"
|
|
16
15
|
},
|
|
17
16
|
"files": [
|
|
18
17
|
"**/*.js",
|