@synanetics/fhir-sort
v1.1.0
Published
A package for sorting fhir resources
Downloads
432
Maintainers
Keywords
Readme
@synanetics/fhir-sort
This package provides an implementation of in memory sorting of FHIR Resources.
Features
- FHIR STU3 and R4.
- Sorting FHIR Bundles and arrays of FHIR resources.
- Custom SearchParameters can be provided in addition to, or replacement of, default SearchParameters
- Custom normalisers and value sorters can be provided through FhirSorter overrides
- Legacy Connect normalisers are available for matching legacy Connect datatype normalisation behaviour
- Specialised sorting on most FHIR complex data types (e.g FHIR.HumanName)
- Chained sort terms, to a single depth
- Sort term validation
Limitations
- The sorter can only sort resources of the same resource type
- A bundles "matches" have the same resource type
- A resources array's values must have the same resource type
- Included resources, whether in a Bundle or includedResources array, can be of any type
- For Bundles, the sorter is designed to just support the searchset type
- The sorter does not follow resource orders that are prescribed in Bundle types
- E.G The message Bundle type which requires MessageHeader in entry[0]
- Note: that such Bundles likely have different resource types, and thus are not supported at all
Example Usage
const {
FhirSorter
} = require('@synanetics/fhir-sort');
// or
import {
FhirSorter
} from '@synanetics/fhir-sort';
const r4Sorter = new FhirSorter('r4');
const stu3Sorter = new FhirSorter('stu3');
const unsortedBundle: Bundle = {
resourceType: 'Bundle',
type: 'searchset',
total: 3,
entry: [
{
resource: {
resourceType: 'Patient',
name: [{
given: ['Mark'],
family: 'South',
}],
},
},
{
resource: {
resourceType: 'Patient',
name: [{
given: ['Rex'],
family: 'Ford',
}],
},
},
{
resource: {
resourceType: 'Patient',
name: [{
given: ['Adam'],
family: 'Ford',
}],
},
},
]
}
const sortedBundle: Bundle = r4Sorter.sortBundle(unsortedBundle, ['name'], 'Patient');
// Expected output
const sortedBundle: Bundle = {
resourceType: 'Bundle',
type: 'searchset',
total: 3,
entry: [
{
resource: {
resourceType: 'Patient',
name: [{
given: ['Adam'],
family: 'Ford',
}],
},
},
{
resource: {
resourceType: 'Patient',
name: [{
given: ['Rex'],
family: 'Ford',
}],
},
},
{
resource: {
resourceType: 'Patient',
name: [{
given: ['Mark'],
family: 'South',
}],
},
},
]
}FhirSorter Constructor
const sorter = new FhirSorter(fhirVersion, customSearchParameters, fhirModel, overrides);Arguments
fhirVersion
- String, either 'stu3 or 'r4'
- Used to determine default SearchParameters and FHIR model
customSearchParameters
- This is an optional argument if it is not provided the default SearchParameters for the selected FHIR version will be used
- Object containing fields mode and searchParameters
- mode: String, either 'replace' or 'concat'
- searchParameters: an array of FHIR SearchParameter resources
- When mode = replace, the provided SearchParameters will be used instead of the default SearchParameters
- When mode = concat, the provided SearchParameters will be used in addition to the SearchParameters
fhirModel
- FhirModels are defined in the fhirpath npm package
- They have an exported type 'Model'
- See: https://www.npmjs.com/package/fhirpath
- It is utilised by the package to determine the type of an evaluated value
- Example Patient.name = FHIR.HumanName
- This is an optional argument if it is not provided the default FhirModel for the selected FHIR version will be used
- This package supports custom FhirModels, but does not support combining custom and default FhirModels
- It is intended that future versions of this package will not rely on user knowledge of this third party interface
overrides
- This is an optional argument that allows callers to customise how evaluated FHIR values are normalised and compared
- Object containing optional fields normalisers and valueSorter
- normalisers: a partial map of datatype normalisers keyed by fhirpath type name, such as
FHIR.HumanName,FHIR.CodeableConcept,System.String, orfallback - valueSorter: a function with the shape
(direction) => (a, b) => number
- normalisers: a partial map of datatype normalisers keyed by fhirpath type name, such as
- Normaliser overrides are merged with the default normalisers, so only the datatypes that need different behaviour have to be supplied
- Each normaliser receives the evaluated value and sort details, then returns the primitive value used for sorting or
undefinedfor a missing sort value - Sort details include the sort direction, the resolved SearchParameter details, and the value sorter used when selecting a value from a multi-value field
- The valueSorter is used both to choose the most favourable value from multi-value fields and to compare the final normalised values
Override Example
import {
FhirSorter,
type SortOverrides,
} from '@synanetics/fhir-sort';
const overrides: SortOverrides = {
normalisers: {
'FHIR.HumanName': (value) => {
if (!value || typeof value !== 'object') {
return undefined;
}
return (value as { text?: string }).text?.toLowerCase();
},
},
};
const sorter = new FhirSorter('r4', undefined, undefined, overrides);Legacy Connect Normalisers
legacyConnectNormalisers can be used when fhir-sort needs to reproduce legacy Connect normalisation rules.
import {
FhirSorter,
legacyConnectNormalisers,
legacyConnectValueSort,
} from '@synanetics/fhir-sort';
const sorter = new FhirSorter(
'stu3',
undefined,
undefined,
{
normalisers: legacyConnectNormalisers,
valueSorter: legacyConnectValueSort,
},
);The legacy Connect normalisers use the legacy field choices for complex datatypes, for example Address.text, CodeableConcept.text, Coding.code, HumanName.text, Identifier.value, Period.start, Quantity.value, and Reference.reference.
Date SearchParameter strings are expanded to the earliest instant in their precision, then returned as lower-case ISO strings. Datatypes that legacy Connect did not support return undefined, which causes them to be treated as missing sort values.
Use legacyConnectValueSort with legacyConnectNormalisers when legacy Connect missing-value placement is also required.
FhirSorter.sortArray
const sorted = sorter.sortArray(resources, sortTerms, resourceType, allowChainedSort, includedResources);Arguments
resources
- An unsorted array of FHIR resources to be sorted
sortTerms
- An array of strings where each string is SearchParameter code
- e.g ['name', '_lastUpdated']
- By default the sort order is ascending
- A sort term can be prefixed with - to make the sort order descending
- e.g ['-name', '-_lastUpdated']
- If a SearchParameter related to a sort term cannot be found, an error will be thrown
resourceType
- A string which is the resourceType that is to be sorted
- e.g 'Patient'
allowChainedSort
- allowChainedSort is a boolean that defaults to false
- If it is true then sorting on chained sort terms is allowed
includedResources
- This is an optional argument that is only required for chained sorts
- includedResources is an array of FHIR resources that would be 'included' in the Bundle response of a FHIR search query
Return value
- This function returns a sorted array of FHIR resources
FhirSorter.sortBundle
const sorted = sorter.sortBundle(bundle, sortTerms, resourceType, allowChainedSort);Arguments
bundle
- An unsorted FHIR Bundle resources
- The entries to be sorted are expected to have search.mode = match
sortTerms
- An array of strings where each string is SearchParameter code
- e.g ['name', '_lastUpdated']
- By default the sort order is ascending
- A sort term can be prefixed with - to make the sort order descending
- e.g ['-name', '-_lastUpdated']
- If a SearchParameter related to a sor term cannot be found, an error will be thrown
resourceType
- A string which is the resourceType that is to be sorted
- e.g 'Patient'
allowChainedSort
- allowChainedSort is a boolean that defaults to false
- If it is true then sorting on chained sort terms is allowed
Return value
- This function the input bundle with its matched entries sorted
Chained Sorting
- Chained sorting is not part of the default FHIR specification however it is supported here to a single depth
- To perform a chained sort, pass in a chained sort term, and set allowChainedSort to true
- e.g chained sort term for Patient resource: 'general-practitioner.name'
- With this example sort term, the sorter would sort Patients based on their general practitioner's name
- Note that a general practitioner could be a Practitioner, Organization or PractitionerRole
- A sort term that is valid for one may not be valid for others. E.G 'family' is only a valid sort term for Practitioner
- The sorter will allow any chained sort term as long as it is valid at least one of of the possible resource types
- The sorter will attempt to resolve the sort using resources "included" in the input Bundle for sortBundle, or the includedResources array for sortArray
- For the sorter to find the appropriate included resource, it must be referenced in the base resource with a relative reference
- e.g 'Practitioner/12345'
- If a chained sort term is passed in, and the allowChainedSort argument is false, an error will be thrown
- If a chained sort term with depth > 1 is passed in an error will be thrown
- e.g 'general-practitioner.partof.name'
How Values Are Sorted
defaultValueSort is used unless an override is provided. Both defaultValueSort and legacyConnectValueSort are exported from the package. A value sorter is used in two places:
- To choose the most favourable normalised value when a field evaluates to multiple values
- To compare the final normalised values between resources
Multiple Values
- If a given field has multiple possible values, such as Patient.family, the most 'favourable' value is taken
- e.g when sorting if a patient had three names with family values: 'Banana', 'Pear' and 'Apple':
- 'Apple' would be used in the final sort when sorting ascending.
- 'Pear' would be used in the final sort when sorting descending.
Default Value Sorting
defaultValueSortsorts values in ascending order by default, or descending order when the sort term is prefixed with-undefinedvalues always appear at the end of the sort regardless of sort order- This means missing values are placed after defined values for both
nameand-name
Legacy Connect Value Sorting
legacyConnectValueSortsorts defined values in the same ascending or descending direction asdefaultValueSortundefinedvalues are ordered according to the requested sort direction- In ascending sorts, missing values are placed after defined values
- In descending sorts, missing values are placed before defined values
- This can be used with
legacyConnectNormalisersto reproduce legacy Connect missing-value placement
import {
FhirSorter,
legacyConnectNormalisers,
legacyConnectValueSort,
} from '@synanetics/fhir-sort';
const sorter = new FhirSorter(
'stu3',
undefined,
undefined,
{
normalisers: legacyConnectNormalisers,
valueSorter: legacyConnectValueSort,
},
);FHIR Date SearchParameters
- SearchParameters with type
dateare normalised before sorting - Supported FHIR date/dateTime precisions include
YYYY,YYYY-MM,YYYY-MM-DD, andYYYY-MM-DDThh:mm:ss - Zulu and timezone offset values are sorted by their actual instant
- e.g
2024-01-01T00:30:00Zsorts after2024-01-01T02:00:00+02:00
- e.g
- Date/time values without a timezone are treated as UTC for sorting
- Missing precision components are treated as the earliest point in that precision
- e.g
2024-02is sorted as2024-02-01T00:00:00.000Z
- e.g
- DateTimes are normalised to a fractional second precision of three decimal places, either padded
with zeros or truncated to three decimal places e.g.
2024-02-01T00:00:00.1Zbecomes2024-02-01T00:00:00.100Zand2024-02-01T00:59:59.9999Zbecomes2024-02-01T00:59:59.999Zas rounding up would require altering the hour/minute/second and millisecond components.
Normalising DataTypes
All data types are normalised before sorting, and then sorted based upon their sort order
Default Normalisation
Legacy Connect Normalisation
When legacyConnectNormalisers are supplied through FhirSorter overrides, values are normalised using the legacy Connect rules below. Datatypes that legacy Connect did not support return undefined.
