diff --git a/adaptors/fhir-4.md b/adaptors/fhir-4.md index 0a32eba4b5d..7d5e5db4145 100644 --- a/adaptors/fhir-4.md +++ b/adaptors/fhir-4.md @@ -65,7 +65,8 @@ written to the new resource ## Bundles -The FHIR-4 adaptor provides support for bundles. +The FHIR-4 adaptor provides support for creating, updating and uploading +bundles. You can create resources and add them to a bundle: @@ -76,25 +77,13 @@ addToBundle($.resource); Where `$.resource` is a FHIR resource, or an array of resources, on the state object. -This will add the resources to a bundle resource, under the `entry` key, on -state under a key called `bundle`. To save the bundle under a different key, -pass a string as the second argument: +`addToBundle` actually does two things: -```js -addToBundle($.resource, 'patients-bundle'); -``` +- First it creates a bundle object on state, setting some defaults +- Secondly it wraps your resource into an entry object, and add it to the + bundle. -Each item in the bundle will be given a `request` object with the `PUT` method -and a URL. For example: - -```js -addToBundle({ - id: 'x', - resourceType: 'Patient', -}); -``` - -Will create a state object like this: +The code `addToBundle($.resource)` will give you a state object like this: ```json { @@ -103,6 +92,7 @@ Will create a state object like this: "entry": [ { "resource": { + // This object is the resource you passed in "id": "x", "resourceType": "Patient" }, @@ -116,8 +106,24 @@ Will create a state object like this: } ``` +To save the bundle under a different key, pass a string key name as the second +argument: + +```js +addToBundle($.resource, 'patients-bundle'); +``` + +You can customize the bundle by calling the `createBundle()` function yourself. +This can be useful for setting the bundle type or other keys. + +``` +createBundle({ name: 'upload', type: 'batch' }) +``` + To send the Bundle to the FHIR server defined in `state.configuration.baseURL`, -call `uploadBundle()`. +call `uploadBundle()`. This operation will sort the bundle according to the +dependencies of its contents: so resources that reference other resources will +appear later in the bundle. You can pass the name of a key on state to upload a specific bundle: @@ -722,6 +728,10 @@ b.patient({ ### I've noticed a problem with this Adaptor, or something is out of date, what can I do? -Thanks for asking! We are a fully Open Source Digital Public Good, and we welcome contributions from our community. Check out our [Adaptors Wiki](https://github.com/OpenFn/adaptors/blob/main/wiki/index.md) for more information on how you can update Adaptors! +Thanks for asking! We are a fully Open Source Digital Public Good, and we +welcome contributions from our community. Check out our +[Adaptors Wiki](https://github.com/OpenFn/adaptors/blob/main/wiki/index.md) for +more information on how you can update Adaptors! -Or, you can always reach out to the Community through our [Community Forum here](https://community.openfn.org/). +Or, you can always reach out to the Community through our +[Community Forum here](https://community.openfn.org/). diff --git a/adaptors/fhir-eswatini.md b/adaptors/fhir-eswatini.md new file mode 100644 index 00000000000..f700e3855f6 --- /dev/null +++ b/adaptors/fhir-eswatini.md @@ -0,0 +1,418 @@ +--- +title: FHIR Eswatini Adaptor +--- + +Language support for the +[Eswatini National Health Information Exchange](https://hapifhir.eswatinihie.com) +FHIR Implementation Guide. + +This adaptor is generated from the Eswatini FHIR Implementation Guide (IG), +which means it understands Eswatini-specific profiles, extensions, and value +sets. It is designed to make writing FHIR-conformant job code as simple as +possible. + +## About FHIR + +[FHIR](https://www.hl7.org/fhir/overview.html) stands for Fast Healthcare +Interoperability Resources. It is a standard for representing and exchanging +healthcare data electronically. + +Learn more about FHIR and +[FHIR for heath data exchange](/documentation/get-started/standards#fhir-for-health-data-exchange). + +## Authentication + +See this adaptor's +[Configuration docs](/adaptors/packages/fhir-eswatini-configuration-schema) for +the required authentication parameters. + +See platform docs on +[managing credentials](/documentation/manage-projects/manage-credentials) for +how to configure a credential in OpenFn. If working locally or using a Raw JSON +credential type, your configuration will look something like this: + +```json +{ + "baseUrl": "https://hapifhir.eswatinihie.com", + "apiPath": "fhir" +} +``` + +## How This Adaptor Helps + +One of the major aims of the `fhir-eswatini` adaptor is to make it easier to +define FHIR resources. + +Writing conformant FHIR resources by hand is verbose. The raw JSON for basic +Patient looks something like this: + +```json +{ + "resourceType": "Patient", + "meta": { + "profile": [ + "https://hapifhir.eswatinihie.com/fhir/StructureDefinition/SzPatient" + ] + }, + "identifier": [ + { + "use": "official", + "type": { + "coding": [ + { + "system": "https://hapifhir.eswatinihie.com/fhir/CodeSystem/SzPersonIdentificationsCS", + "code": "PI", + "display": "Personal ID Number" + } + ] + }, + "system": "http://homeaffairs.sys", + "value": "1999001000000" + } + ], + "name": [{ "family": "Gule", "given": ["Jacob", "Samuel"] }], + "gender": "male", + "birthDate": "1990-01-01", + "extension": [ + { + "url": "https://hapifhir.eswatinihie.com/fhir/StructureDefinition/SzInkhundlaExtension", + "valueCodeableConcept": { + "coding": [ + { + "system": "https://hapifhir.eswatinihie.com/fhir/CodeSystem/SzTinkhundlaCS", + "code": "3", + "display": "LOBAMBA" + } + ], + "text": "LOBAMBA" + } + } + ] +} +``` + +The fhir-eswatini adaptor reduces this to: + +```js +b.patient({ + identifier: { + use: 'official', + type: 'PI', + system: 'http://homeaffairs.sys', + value: '1999001000000', + }, + name: [{ family: 'Gule', given: ['Jacob', 'Samuel'] }], + gender: 'male', + birthDate: '1990-01-01', + inkhundla: 'LOBAMBA', +}); +``` + +The "builder function" `b.patient()` handles the profile URL, the full +identifier type coding, and the extension structure automatically. It also +provides convenient facilities for mapping values from the incoming data +source - so sintead of something like this: + +``` +state.patient = { + // Use a custom map to translate source to target values + inkhundla: lookups.region2inkhundla[state.patient.region] +} +``` + +You can just do this: + +``` +state.patient = b.patient{ + inkhundla: state.patient.region +} +``` + +This means that workflow code which maps information into fhir resources is +simpler and cleaner, with less fiddly syntax and logic. It's easier to write, +easier to read, easier to reason about, and easier to modify. + +## User Guide + +### Builder functions + +All builder functions are available on the `b` (short for `builders`) namespac. + +There is a builder for every resource type in the Eswatini IG: `b.patient()`, +`b.encounter()`, `b.observation()`, `b.condition()` and so on. See the +[reference docs](/adaptors/packages/fhir-eswatini-docs) for the full list. + +Pass a plain object with the properties you want: + +```js +const patient = b.patient({ + gender: 'male', + birthDate: '1990-01-01', +}); +``` + +Builders are not operations themselves. Pass them as an argument to an operation +like `create()`, or use them inside a callback: + +```js +fn(state => { + state.patient = b.patient(state.data); + return state; +}); +``` + +### Bundling + +See the base `fhir-4` docs for details of the `createBundle`, `addToBundle` and +`uploadBundle` functions. + +The `fhir-eswatini` adaptor will generate correct URLs for each entry, and sort +by dependency order. + +```js +addToBundle( + b.patient({ + gender: 'male', + birthDate: '1990-01-01', + }) +); +uploadBundle(); +``` + +### Value mapping + +The adaptor knows the Eswatini IG's value sets, so short code values are +automatically expanded to full coded concepts. + +For example, an identifier's `type` accepts a short code from the Eswatini +Person Identifications code system: + +```js +b.patient({ + identifier: { + type: 'PI', // expands to a full CodeableConcept + system: 'http://homeaffairs.sys', + value: '1999001000000', + }, +}); +``` + +The same applies to other coded fields. For example, an encounter's `class` +accepts short codes like `OPD`, `IPD`, `CO`, and `SO`: + +```js +b.encounter({ + class: 'OPD', // expands to full coding with system and display +}); +``` + +You can pass mappings with the code or display values: + +```js +b.patient({ + inkundla: 'mbabane', // or code 264 +}); +``` + +To know the range of values allowed, follow the links in the docs to the value +set definition (the docs will link you straight into the Implementation Guide). +Any Code or Display value declared on that page is allowed. + +If a mapping fails, the value you passed in will be fed through the final FHIR +resource, unmapped and untouched. + +![Example listing of code values from the FHIR IG](/img/fhir-eswatini-valueset-example.png) + +You can also define your own value mappings. This is useful when mapping your +input data into FHIR. Just declare the mappings at the top of your job code with +the correct URL and whatever values you want. + +```js +b.setValues( + // get the value set URL from the docs + 'http://172.209.216.154:3447/fhir/ValueSet/SzTinkhundlaVS', + // Define your own mappings, where the key is the code value passed to + // to the builder, and the value is whatever should be mapped + // In this example, we want to map an inkhundla name to the code + { + mbabane: { + system: 'https://hapifhir.eswatinihie.com/fhir/CodeSystem/SzTinkhundlaCS', + code: '264', + display: 'MBABANE', + }, + } +); +``` + +Custom mappings like this do not override the default system values. In fact, +your custom mappings are expanded first and THEN the system mappings cut in - so +you can declare custom mappings which map right to codings, which will then be +expanded: + +```js +b.setValues( + 'http://172.209.216.154:3447/fhir/ValueSet/SzTinkhundlaVS', + // Map to the code, and let the default mapping expand the full extension + { + mbabane: '264', + } +); +``` + +Now your job code can do: + +```js +b.patient({ + inkundla: 'mbabane', +}); +``` + +If you declare mappings relative to the format your source data, you can declare +the value based on your data without complex mapping code + +```js +b.patient({ + inkundla: $.patient.district, // mapping from a different dataset +}); +``` + +### Extensions as properties + +Eswatini-specific extensions are exposed as simple named properties on the +relevant builders, instead of requiring you to construct extension objects +manually. + +For example, for the Patient resource, supported extension properties are: + +| Property | Extension | +| ------------------ | -------------------- | +| `inkhundla` | SzInkhundlaExtension | +| `chiefdom` | SzChiefdomExtension | +| `registrationDate` | SzRegistrationDate | + +Pass a code value and the builder constructs the full extension: + +```js +b.patient({ + inkhundla: '3', + chiefdom: '7', + registrationDate: '2025-06-01T10:00:00Z', +}); +``` + +```json +{ + "extension": [ + { + "url": "http://172.209.216.154:3447/fhir/StructureDefinition/SzInkhundlaExtension", + "valueCodeableConcept": { + "coding": [ + { + "system": "https://hapifhir.eswatinihie.com/fhir/CodeSystem/SzTinkhundlaCS", + "code": "3", + "display": "LOBAMBA" + } + ], + "text": "LOBAMBA" + } + }, + { + "url": "http://172.209.216.154:3447/fhir/StructureDefinition/SzChiefdomExtension", + "valueCodeableConcept": { + "coding": [ + { + "system": "https://hapifhir.eswatinihie.com/fhir/CodeSystem/SzChiefdomCS", + "code": "7", + "display": "Lobamba" + } + ], + "text": "Lobamba" + } + }, + { + "url": "http://172.209.216.154:3447/fhir/StructureDefinition/SzRegistrationDate", + "valueDateTime": "2025-06-01T10:00:00Z" + } + ] +} +``` + +### Multi-profile resources + +Some FHIR resource types have multiple Eswatini profiles. For these, pass the +profile name as the first argument to the builder. + +#### Observation + +The `observation` builder accepts one of `SzCauseOfDeath`, +`SzClinicalObservation`, `SzLabResult`, `SzMannerOfDeath`, or `SzVitalSigns`: + +```js +b.observation('SzVitalSigns', { + status: 'final', + subject: 'Patient/123', +}); +``` + +#### ServiceRequest + +The `serviceRequest` builder accepts one of `SzLabRequest` or `SzReferral`: + +```js +b.serviceRequest('SzReferral', { + status: 'active', + intent: 'order', + subject: 'Patient/123', +}); +``` + +### Finding More Examples + +The size and complexity of FHIR implementations can make it hard to document +effectively. + +The design of the builders is to be intuitive: pass any reasonable value to a +key and the builder should do the right thing with it. + +If you're not sure, and the API reference documentation isn't clear, the best +places to get help are: + +1. Look at the + [unit tests](https://github.com/OpenFn/adaptors/tree/main/packages/fhir-eswatini/test/resources) + for the resource you are building. These are hand written and have many + examples of mapping values (the unit tests are how we validate that the + different mappings work!) +1. Look at the + [source code](https://github.com/OpenFn/adaptors/tree/main/packages/fhir-eswatini/src/profiles) + of the builder function you are using. The builders are quite verbose and you + should be able to see how your input data is processed. Check that the key + you want is supported, and check how the value is processed (is it value + mapped? Is it sent to another builder?) + +## Adaptor Implementation + +The `fhir-eswatini` adaptor is automatically generated from an implementation +guide. + +All builder functions, reference documentation and typings are generated from +details in the specification according to specific usage patterns and templates. + +Some parts of the adaptor, like the main operations, core datatype builders, and +unit tests, are hand-written. + +The ability to generate adaptor code means that the adaptor can be updated to +changes in the specification within minutes. It also ensures that the generated +code is comprehensive and accurate. + +But bugs in the generation process can result in errors, and the sheer scale of +code generated means that test coverage can be lacking. + +## I've noticed a problem with this adaptor, or something is out of date, what can I do? + +Thanks for asking! We are a fully Open Source Digital Public Good, and we +welcome contributions from our community. Check out our +[Adaptors Wiki](https://github.com/OpenFn/adaptors/blob/main/wiki/index.md) for +more information on how you can update adaptors! + +Or, you can always reach out to the Community through our +[Community Forum here](https://community.openfn.org/). diff --git a/static/img/fhir-eswatini-valueset-example.png b/static/img/fhir-eswatini-valueset-example.png new file mode 100644 index 00000000000..757c84a5a58 Binary files /dev/null and b/static/img/fhir-eswatini-valueset-example.png differ