/ function that calculates the absolute diff between dates in milliseconds /
$dateDiffMillis := function($date1, $date2){($abs($toMillis($date1)-$toMillis($date2)))};
/ function that fetches resources above and below a requested date and returns them sorted by proximity /
$getClosestResources := function($resourceType, $date, $searchParam, $elementName) {(
/ get pages from above and below the date /
$geBundle := $search($resourceType, {$searchParam: 'ge' & $date, '_sort': $searchParam, '_count': $pageSize, '_elements': $elementName});
$leBundle := $search($resourceType, {$searchParam: 'le' & $date, '_sort': '-' & $searchParam, '_count': $pageSize, '_elements': $elementName});
/ sort the results by absolute diff from the selected date /
[$geBundle.entry.resource, $leBundle.entry.resource]^($dateDiffMillis($date, $lookup($, $elementName)));
)};
/ recursive function that adds a single new resource to an accumulating list /
/ educational note: /
/ note that the self-call is at the final step of the function. /
/ this is called "tail recursion" and it helps prevent stack overflow. /
/ see: https://docs.jsonata.org/programming#tail-call-optimization-tail-recursion/
$addResource := function($resourceType, $searchParam, $elementName, $accumulating, $iterationCounter){(
/ reset counter to 1 if not stated otherwise /
$iterationCounter := $exists($iterationCounter) ? $iterationCounter : 1;
/ initialize an empty list if accumulator not passed /
$accumulating := $exists($accumulating) ? $accumulating : [];
/ generate a new random date /
$randomDate := $newRandomDate($minDate, $maxDate);
/ get list of patients closest to the random date by birthday, sorted by proximity /
$searchResults := $getClosestResources($resourceType, $randomDate, $searchParam, $elementName);
/ get the closest one not collected yet /
$selection := $searchResults[$not(id in $accumulating)][0];
/ check the results /
$exists($selection) ? (
/ found a new patient - add its id to the list and return the updated list /
[$accumulating, $selection.id]
) : (
/ no new patient found for this date /
/ if we have reached max iterations - stop looking /
$iterationCounter >= $maxRetriesForNewResource ? (
/ return accumulated list as-is /
$accumulating
) : (
/ not reached max iterations - try again with a new random date /
$addResource($resourceType, $searchParam, $elementName, $accumulating, $iterationCounter + 1)
/ ^ this is the "tail call" /
)
)
)};
/ wrapper function that does the collection of the list of resources /
$collectResources := function($resourceType, $amountToCollect, $minDate, $maxDate, $searchParam, $elementName){(
/ iteration function that adds resources recursively, as long as: /
/ 1. the previous step succeeded in finding a new resource /
/ 2. the requested amount hasn't been collected yet /
$iterator := function($currentList, $previousList){(
$exists($currentList) and ($count($currentList) = $count($previousList) or $count($currentList) >= $amountToCollect) ? (
/ the stop condition is met - return list and stop iterating /
$currentList
) : (
/ try to add one patient and continue /
$updatedList := $addResource($resourceType, $searchParam, $elementName, $currentList);
/ tail call /
$iterator($updatedList, $currentList)
)
)};
/ call the iterator with empty initial lists /
$ids := $iterator();
/* transform the list of resource id's to a list of references (ready for $resolve) */
$ids.($join([$resourceType, $], '/'))
)};
/call the wrapper function with relevant keys, values will come from input/
/$collectResources(resourceType, amountToCollect, "2000-01-01", "2020-01-01", searchParam, elementName)/
The below map and input wont work as expected. Also, try, try.dev and rambam servers react diffrently
fume map
`( / global parameters / $pageSize := 10; $maxRetriesForNewResource := 10; /*****/
/ date generator function / $newRandomDate := function($min, $max){( $minMs := $toMillis($min); $maxMs := $toMillis($max); $diffMs := $maxMs - $minMs; $randomMs := $random() * $diffMs; $fromMillis(($minMs + $randomMs),'[Y0001]-[M01]-[D01]'); )};
/ function that calculates the absolute diff between dates in milliseconds / $dateDiffMillis := function($date1, $date2){($abs($toMillis($date1)-$toMillis($date2)))};
/ function that fetches resources above and below a requested date and returns them sorted by proximity / $getClosestResources := function($resourceType, $date, $searchParam, $elementName) {( / get pages from above and below the date / $geBundle := $search($resourceType, {$searchParam: 'ge' & $date, '_sort': $searchParam, '_count': $pageSize, '_elements': $elementName}); $leBundle := $search($resourceType, {$searchParam: 'le' & $date, '_sort': '-' & $searchParam, '_count': $pageSize, '_elements': $elementName}); / sort the results by absolute diff from the selected date / [$geBundle.entry.resource, $leBundle.entry.resource]^($dateDiffMillis($date, $lookup($, $elementName))); )};
/ recursive function that adds a single new resource to an accumulating list / / educational note: / / note that the self-call is at the final step of the function. / / this is called "tail recursion" and it helps prevent stack overflow. / / see: https://docs.jsonata.org/programming#tail-call-optimization-tail-recursion / $addResource := function($resourceType, $searchParam, $elementName, $accumulating, $iterationCounter){( / reset counter to 1 if not stated otherwise / $iterationCounter := $exists($iterationCounter) ? $iterationCounter : 1; / initialize an empty list if accumulator not passed / $accumulating := $exists($accumulating) ? $accumulating : []; / generate a new random date / $randomDate := $newRandomDate($minDate, $maxDate); / get list of patients closest to the random date by birthday, sorted by proximity / $searchResults := $getClosestResources($resourceType, $randomDate, $searchParam, $elementName); / get the closest one not collected yet / $selection := $searchResults[$not(id in $accumulating)][0]; / check the results / $exists($selection) ? ( / found a new patient - add its id to the list and return the updated list / [$accumulating, $selection.id] ) : ( / no new patient found for this date / / if we have reached max iterations - stop looking / $iterationCounter >= $maxRetriesForNewResource ? ( / return accumulated list as-is / $accumulating ) : ( / not reached max iterations - try again with a new random date / $addResource($resourceType, $searchParam, $elementName, $accumulating, $iterationCounter + 1) / ^ this is the "tail call" / ) ) )};
/ wrapper function that does the collection of the list of resources / $collectResources := function($resourceType, $amountToCollect, $minDate, $maxDate, $searchParam, $elementName){( / iteration function that adds resources recursively, as long as: / / 1. the previous step succeeded in finding a new resource / / 2. the requested amount hasn't been collected yet / $iterator := function($currentList, $previousList){( $exists($currentList) and ($count($currentList) = $count($previousList) or $count($currentList) >= $amountToCollect) ? ( / the stop condition is met - return list and stop iterating / $currentList ) : ( / try to add one patient and continue / $updatedList := $addResource($resourceType, $searchParam, $elementName, $currentList); / tail call / $iterator($updatedList, $currentList) ) )}; / call the iterator with empty initial lists / $ids := $iterator();
)};
/call the wrapper function with relevant keys, values will come from input/ /$collectResources(resourceType, amountToCollect, "2000-01-01", "2020-01-01", searchParam, elementName)/
/ testing the function: / $minDate := '1950-01-01'; $maxDate := $now(); $collectResources('Patient', 7, $minDate, $maxDate, 'birthdate', 'birthDate'); )`
fume input
{ "resourceType": "Patient", "amountToCollect": 7, "minDate": "2000-01-01", "maxDate": "2020-01-01", "searchParam": "birthdate", "elementName": "birthDate" }
fume error
{ "__isFumeError": true, "message": "Request failed with status code 500", "code": "ERR_BAD_RESPONSE", "name": "AxiosError", "token": "search", "cause": "", "position": 4489 }
console error
_header: 'GET /csp/healthshare/rambam/fhir/r4/Patient?_count=10&birthdate=ge1953-10-01&_sort=birthdate&_elements=birthDate HTTP/1.1\r\n' + 'Accept: application/fhir+json;fhirVersion=4.0\r\n' + 'Content-Type: application/fhir+json;fhirVersion=4.0\r\n' + 'User-Agent: axios/1.6.7\r\n' + 'Accept-Encoding: gzip, compress, deflate, br\r\n' + 'Host: ec2-3-124-79-139.eu-central-1.compute.amazonaws.com:52773\r\n' + 'Connection: keep-alive\r\n' + '\r\n', _keepAliveTimeout: 0, _onPendingData: [Function: nop], agent: [Agent], socketPath: undefined, method: 'GET', maxHeaderSize: undefined, insecureHTTPParser: undefined, joinDuplicateHeaders: undefined, path: '/csp/healthshare/rambam/fhir/r4/Patient?_count=10&birthdate=ge1953-10-01&_sort=birthdate&_elements=birthDate',