Tag: ibm-fhir-server

  • Moving on…

    In 2019, I joined the IBM FHIR Server team. A team tasked with engineering an internal FHIR server (DSTU2) as an updated and upgrade open source HL7 FHIR R4 Server. The open sourced code, on GitHub IBM® FHIR® Server – IBM/FHIR is a product of many contributors since it’s inception in 2016 (the project history goes back to the DSTU2 days). I contributed over a 1000 commits over my time working on the project, authored over 300 issues, opened-updated-closed 600 plus Pull Requests, and triaged/reviewed and designed many more.

    Today I’m moving on to IBM Power Systems and working on OpenShift.

    Contributions to the Project – Automation, Search, Hard Erase, Performance, Data Storage, Bulk Data

  • Using the IBM FHIR Server and Implementation Guide as Java Modules

    The IBM FHIR Server is an extensible HL7 FHIR Server. The IBM FHIR server supports complicated ImplementationGuides (IGs), a set of rules of how a particular problem is solved using FHIR Resources. The implementation guides include a set of Profiles, ValueSets, CodeSystems and supporting resources (Examples, CapabilityStatements).

    The IBM FHIR Server supports the loading of NPM packages – stored in the (package.tgz). You see the package at the https://www.hl7.org/fhir/us/core/package.tgz (One just appends package.tgz to any IG site).

    The IBM FHIR Server includes a number of IGs built-tested-released with each tag.

    The IGs are Java modules which are specially formed to support the resources in the NPM packages. The Java modules use a ServiceLoader (activated at startup when the Java Module is in the classpath).

    The best way to start is to copy and existing fhir-ig, such as fhir-ig-us-core, and modify as needed (package rename and updated files).

    The service loader uses the META-INF/services/com.ibm.fhir.registry.spi.FHIRRegistryResourceProvider interface to activate the list of classes in the file.

    Each of the corresponding classes need to be in src/main/java under the corresponding package (com.ibm.fhir.ig.us.core as above).

    The PackageRegistryResourceProvider navigates the src/main/resources to find the folder hl7/fhir/us/core/311 and loads the files referenced in the NPM packages index (.index.json).

    You might not see the .index.json file by default in Eclipse, and you should unhide the .resource file at the Enterprise Explorer View > Filters and Customization, Select .*resources, Click OK

    When you open .index.json, you should see:

    These are the resources which will be loaded when the fhir-ig-us-core is added to the userlib folder.

    The US-Core and CarinBB support multiple versions of the same IG – v3.1.1 and v4.0.0 in the same Java module. To control this behavior, one needs to set the configuration in order to map to a default version (the default is always the latest or newest version).  With cross IG dependencies, they are updated to point to the correct one, or the latest one as the IG specifies.

    To make these files viewable, we do like to format the contents of these folders so they are pretty JSON. When the IGs are built and released, the JSON files are compressed and saves a good chunk of Memory. 

    We also like to remove Narrative texts:

    "text": {
         "status": "empty",
         "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">Redacted for size</div>"
    },

    There is an examples folder included with most package.tgz files.  You should copy this into the src/test/resources/JSON folder and update the index.txt so you have the latest examples in place, and these will be loaded the ProfileTest (ConformaceTest is used for usecase specific tests).  The below is an example of loading the 400/index.txt and failing when issues exceed a limit or a severity of error.

    public class ProfileTest {
    
        private static final String INDEX = "./src/test/resources/JSON/400/index.txt";
    
        private String path = null;
    
        public ProfileTest() {
            // No Operation
        }
    
        public ProfileTest(String path) {
            this.path = path;
        }
    
        @Test
        public void testUSCoreValidation() throws Exception {
            try (Reader r = Files.newBufferedReader(Paths.get(path))) {
                Resource resource = FHIRParser.parser(Format.JSON).parse(r);
                List<Issue> issues = FHIRValidator.validator().validate(resource);
                issues.forEach(item -> {
                    if (item.getSeverity().getValue().equals("error")) {
                        System.out.println(path + " " + item);
                    }
                });
                assertEquals(countErrors(issues), 0);
            } catch (Exception e) {
                System.out.println("Exception with " + path);
                fail(e.toString());
            }
        }
    
        @Factory
        public Object[] createInstances() {
            List<Object> result = new ArrayList<>();
    
            try (BufferedReader br = Files.newBufferedReader(Paths.get(INDEX))) {
                String line;
                while ((line = br.readLine()) != null) {
                    result.add(new ProfileTest(line));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return result.toArray();
        }
    
    }

    The ProviderTest checks the provider loads successfully, and that the number of resources returned is correct, and tests one exemplary resource is returned.

    @Test
        public void testRegistry() {
            StructureDefinition definition = FHIRRegistry.getInstance()
                    .getResource("http://hl7.org/fhir/us/core/StructureDefinition/pediatric-bmi-for-age", StructureDefinition.class);
            assertNotNull(definition);
            assertEquals(definition.getVersion().getValue(), "4.0.0");
        }
    
        @Test
        public void testUSCoreResourceProvider() {
            FHIRRegistryResourceProvider provider = new USCore400ResourceProvider();
            assertEquals(provider.getRegistryResources().size(), 148);
        }

    There is a very important test which is testConstraintGenerator any issue in the structure definition will be reported when the Constraints are compiled, and you’ll get some really good warnings.

        @Test
        public static void testConstraintGenerator() throws Exception {
            FHIRRegistryResourceProvider provider = new USCore400ResourceProvider();
            for (FHIRRegistryResource registryResource : provider.getRegistryResources()) {
                if (StructureDefinition.class.equals(registryResource.getResourceType())) {
                    assertEquals(registryResource.getVersion().toString(), "4.0.0");
                    String url = registryResource.getUrl() + "|4.0.0";
                    System.out.println(url);
                    Class<?> type = ModelSupport.isResourceType(registryResource.getType()) ? ModelSupport.getResourceType(registryResource.getType()) : Extension.class;
                    for (Constraint constraint : ProfileSupport.getConstraints(url, type)) {
                        System.out.println("    " + constraint);
                        if (!Constraint.LOCATION_BASE.equals(constraint.location())) {
                            compile(constraint.location());
                        }
                        compile(constraint.expression());
                    }
                }
            }
        }

    For example, you might get a pediatric BMI set of constraints:

    http://hl7.org/fhir/us/core/StructureDefinition/pediatric-bmi-for-age|4.0.0
        @com.ibm.fhir.model.annotation.Constraint(id="vs-1", level="Rule", location="Observation.effective", description="if Observation.effective[x] is dateTime and has a value then that value shall be precise to the day", expression="($this as dateTime).toString().length() >= 8", source="http://hl7.org/fhir/StructureDefinition/vitalsigns", modelChecked=false, generated=false, validatorClass=interface com.ibm.fhir.model.annotation.Constraint$FHIRPathConstraintValidator)
        @com.ibm.fhir.model.annotation.Constraint(id="vs-2", level="Rule", location="(base)", description="If there is no component or hasMember element then either a value[x] or a data absent reason must be present.", expression="(component.empty() and hasMember.empty()) implies (dataAbsentReason.exists() or value.exists())", source="http://hl7.org/fhir/StructureDefinition/vitalsigns", modelChecked=false, generated=false, validatorClass=interface com.ibm.fhir.model.annotation.Constraint$FHIRPathConstraintValidator)

    The above constraints can be tested using the FHIRPath expression against a failing test resource, and confirm the validity of the StructureDefinition.

    I hope this document help you build your own IGs for the IBM FHIR Server.

  • Connectathon 29: IBM FHIR Server and the Bulk Data Track

    I recently attended the HL7 FHIR Connectathon 29. For those that are not familiar with Connectathons, I think they are fairly unique events featuring standards enthusiasts, vendors and implementors doing hands-on standards development (FHIR) and testing. As an attendee I picked one of the tracksbulk data.

    The bulk data track tests the FHIR Bulk Data Access Implementation Guide (IG) – v2.0.0 STU2. For those unfamiliar with the standards process, STU refers to the level of maturity of the specification. This maturity aligns well with the associated ANSI certification process where the highest level is normative where the "content is considered to be stable and has been ‘locked’". Connectathons test interoperablity and the standards and make the normative/locked in version even more robust.

    This particular part of the spec (IG) provides for "efficient access large volumes of information on a group of individuals". Instead of making 100s of 1000s of individual requests, the IG defines an efficient asynchronous process for aggregating the relevant healthcare care data into flat files. These flat files are in the NDJSON format, such as:

    {"resourceType":"Patient","name":[{"family":"Doe","given":["John"]}],"birthDate":"1970-01-01"}
    {"resourceType":"Patient","name":[{"family":"Doe","given":["Jane"]}],"birthDate":"1960-01-01"}
    

    For the IBM FHIR Server team, I brought our own server to the Connectathon to test one scenario Scenario 2: Bulk data export with retrieval of referenced files on a protected endpoint. Our team stood up an IBM FHIR Server deployment using Kubernetes and Helm and configured with SMART Backend Services Authorization and IBM Cloud Object Storage, an S3 compatible service.

    This blog outlines the recipe to setup the IBM FHIR Server with SMART Backend Services Authorization with Bulk Data. The recipe shows how to side load data into the environment.

    1. Setup Prerequisites

    In order to complete this setup, you need to setup kubernetes and helm. For my case, I chose to install the ibmcloud tool as it hosts my Kubernetes deployment.

    1. Install the tools:

    2. Install the plugins for ibmcloud

    When deploying the IBM FHIR Server Edition, you’ll need a few additional plugins than the IBM Cloud default: cloud-object-storage, kubernetes-service, container-registry and the infrastructure-service.

    ibmcloud plugin repo-plugins -r "IBM Cloud"
    ibmcloud plugin install cloud-object-storage -f
    ibmcloud plugin install container-service
    ibmcloud plugin install container-registry -f
    ibmcloud plugin install infrastructure-service -f
    

    2. Setting up the S3 Bucket with HMAC

    The test environment uses bulk data with presigned urls to store the bulk exported data.

    1. Login with an API Key (much easier if you use SSO)
    API_KEY=$(cat cloudpak.json | jq -r .apiKey)
    ibmcloud login --apikey ${API_KEY} -r us-east
    
    1. Create a Cloud Object Storage Instance, if it does not exist.
    ibmcloud resource service-instance-create \
        my-bulk-data \
        cloud-object-storage standard global
    CRN=$(ibmcloud resource service-instance \
        my-bulk-data --output JSON | jq -r '.[].crn')
    ibmcloud cos config crn --crn "${CRN}"
    ibmcloud cos create-bucket --bucket \
        "fhir-bulk-data"
    ibmcloud resource service-key-create \
        test-user-hmac Writer --instance-id "${CRN}" \
        --parameters '{"HMAC":true}' --output JSON
    
    1. You’ll see a JSON output with cos_hmac_keys save this for later.
    "cos_hmac_keys": {
        "access_key_id": "abcdefgh",
        "secret_access_key": "xyzmnopq"
    |
    

    The details of the environment can be output:

    ibmcloud resource service-instance \
        my-bulk-data --output JSON
    
    1. Check the endpoints
    curl https://control.cloud-object-storage.cloud.ibm.com/v2/endpoints -o endpoints.json 
    
    1. In the endpoints.json, find the internalUrl (the private) and the externalUrl (the direct) for the location of your Cloud Object Storage, and record it along with the region. Note, I used a regional COS instance.
    ...
        "regional": {
          "us-south": {
            "public": {
              "us-south": "s3.us-south.cloud-object-storage.appdomain.cloud"
            },
            "private": {
              "us-south": "s3.private.us-south.cloud-object-storage.appdomain.cloud"
            },
            "direct": {
              "us-south": "s3.direct.us-south.cloud-object-storage.appdomain.cloud"
            }
          }
    ...
    
    1. Here is a table for your reference:
    Name Value
    bucketname fhir-bulk-data
    accessKey abcdefgh
    secretKey xyzmnopq
    region us-south
    internalUrl s3.private.us-south.cloud-object-storage.appdomain.cloud
    externalUrl s3.direct.us-south.cloud-object-storage.appdomain.cloud

    3. Create the Cluster

    VPC_ID=$(ibmcloud ks vpcs --provider vpc-gen2 --output json \
        | jq -r .[].id)
    SUBNET_ID=$(ibmcloud ks subnets --provider vpc-gen2 \
        --vpc-id ${VPC_ID} --zone us-east-1 --output json \
        | jq -r '.[].id')
    ibmcloud oc cluster create vpc-gen2 \
        --name demo --flavor bx2.4x16 \
            --version 1.23.3 \
            --cos-instance ${CRN} \
            --service-subnet 172.21.0.0/16 --pod-subnet 172.17.64.0/18 \
            --workers 3 --zone us-east-1 --vpc-id=${VPC_ID} \
            --subnet-id ${SUBNET_ID}
    

    The IBM Cloud Kubernetes Service has comprehensive documentation at link

    If you have questions about which version to check, you can refere to ibmcloud ks versions or the docs.

    Once your cluster is up and operation, where you can login to the Administration console, you are ready to target your deployment to the Cluster.

    4. Build and Push the latest from IBM FHIR Server main

    Since there are features in that impact Bulk Data support in main, it’s best to push the latest to a docker registry, and pull the latest into your enviornment.

    1. Clone the IBM FHIR Server repository and switch to the cloned repository.
    git clone https://github.com/IBM/FHIR.git && cd $(basename $_ .git)
    
    1. Setup the examples
    mvn clean install -f fhir-examples -DskipTests
    
    1. Build the fhir projects
    mvn clean install -f fhir-parent -DskipTests
    
    1. Build the IBM FHIR Server
    export BUILD_ID=4.11.0-SNAPSHOT
    nerdctl build fhir-install -t prb112/ibm-fhir-server:latest
    nerdctl login docker.io
    nerdctl push docker.io/prb112/ibm-fhir-server:latest
    

    Now you have the IBM FHIR Server with the latest deployed to a public registry, note, you can always update to work off a private registry using a custom pull secret.

    5. Use Helm to deploy the IBM FHIR Server Helm for Smart-on-FHIR access

    This helm chart is very comprehensive and includes – Postgres as a Subchart and keycloak with its own Postgres.

    1. Add the Helm Chart
    helm repo add alvearie https://alvearie.io/alvearie-helm
    
    1. Update the Helm Chart
    $ helm repo update alvearie
    Hang tight while we grab the latest from your chart repositories...
    ...Successfully got an update from the "alvearie" chart repository
    Update Complete. ⎈Happy Helming!⎈
    
    1. Create a Postgres Password, and save this locally.
    export POSTGRES_PASSWORD=$(openssl rand -hex 20)
    echo $POSTGRES_PASSWORD
    
    1. Configure your kubectl for the target cluster
    ibmcloud ks cluster config --cluster demo
    

    You see:

    OK
    The configuration for demo was downloaded successfully.
    
    Added context for m to the current kubeconfig file.
    You can now execute 'kubectl' commands against your cluster. For example, run 'kubectl get nodes'.
    If you are accessing the cluster for the first time, 'kubectl' commands might fail for a few seconds while RBAC synchronizes.
    
    1. Create a namespace for the target deployment.
    kubectl create namespace example
    namespace/example created
    
    1. Setup the TLS Secret on the ibm-provided domain docs
    ibmcloud ks cluster get --cluster demo --output JSON | jq .ingress
    

    The output is

    {
      "hostname": "demo-12345-0000.us-east.containers.appdomain.cloud",
      "secretName": "demo-1235-0000",
      "status": "healthy",
      "message": "All Ingress components are healthy"
    }
    

    Copy the secret from the default namespace to the new namespace example

    kubectl get secret -n default demo-1235-0000 -o yaml | sed 's/namespace: .*/namespace: example/' | kubectl apply -n example -f  -
    secret/demo-1235-0000 created
    

    Save the hostname, and secretName for later.

    1. Setup the Secret for the IBM Cloud container registry docs
    kubectl get secret -n default all-icr-io -o yaml | sed 's/namespace: .*/namespace: example/' | kubectl apply -n example -f -
    secret/all-icr-io created
    

    Note, this secret is provided in the ibmcloud registry.

    1. Create an overrides file – values-example.yml and update the following values:
    Value to Update Value to replace below Notes
    image.repository REPLACE_WITH_YOUR_REPO The location of the repository / image – docker.io/prb112/ibm-fhir-server or the recommended default for this case – quay.io/alvearie/fhir-data-access
    postgresql.postgresqlPassword REPLACE_WITH_YOUR_POSTGRES_PASSWORD The Postgres Password you generated
    keycloak.postgresql.postgresqlPassword REPLACE_WITH_YOUR_POSTGRES_PASSWORD The Postgres Password you generated
    keycloak.adminPassword REPLACE_WITH_YOUR_ADMIN_PASSWORD You can pick thisk password
    objectStorage.location REPLACE_WITH_COS_REGION This is the COS Bucket’s region
    objectStorage.endpointUrl REPLACE_WITH_COS_ENDPOINT_URL This is the COS endpointURL (the direct one) and is prefixed with https
    objectStorage.accessKey REPLACE_WITH_ACCESS_KEY The COS HMAC accessKey you created
    objectStorage.secretKey REPLACE_WITH_SECRET_KEY The COS HMAC secretKey you created
    objectStorage.bulkDataBucketName REPLACE_WITH_BUCKET_NAME The Bucket you previously created
    ingress.secretName REPLACE_SECRET_NAME_FOR_TLS The secret name for TLS demo-1235-0000 from above.
    ingress.hostname REPLACE_WITH_INGRESS_HOSTNAME The hostname recorded from above demo-12345-0000.us-east.containers.appdomain.cloud
    image:
      repository: REPLACE_WITH_YOUR_REPO
      tag: latest
      pullPolicy: Always
    ingress:
      hostname: "{{ $.Release.Namespace }}.REPLACE_WITH_INGRESS_HOSTNAME"
      tls:
        - secretName: REPLACE_SECRET_NAME_FOR_TLS
      annotations:
        nginx.ingress.kubernetes.io/backend-protocol: HTTPS
    traceSpec: >-
      com.ibm.fhir.smart.*=fine:com.ibm.fhir.server.*=fine
    postgresql:
      enabled: true
      postgresqlPassword: REPLACE_WITH_YOUR_POSTGRES_PASSWORD
      nameOverride: postgres
    security:
      jwtValidation:
        enabled: true
      oauth:
        enabled: true
        regUrl: "https://{{ tpl $.Values.ingress.hostname $ }}/auth/realms/test/clients-registrations/openid-connect"
        authUrl: "https://{{ tpl $.Values.ingress.hostname $ }}/auth/realms/test/protocol/openid-connect/auth"
        tokenUrl: "https://{{ tpl $.Values.ingress.hostname $ }}/auth/realms/test/protocol/openid-connect/token"
        smart:
          enabled: true
          resourceScopes:
            - "patient/*.read"
            - "patient/AllergyIntolerance.read"
            - "patient/CarePlan.read"
            - "patient/CareTeam.read"
            - "patient/Condition.read"
            - "patient/Device.read"
            - "patient/DiagnosticReport.read"
            - "patient/DocumentReference.read"
            - "patient/Encounter.read"
            - "patient/ExplanationOfBenefit.read"
            - "patient/Goal.read"
            - "patient/Immunization.read"
            - "patient/Location.read"
            - "patient/Medication.read"
            - "patient/MedicationRequest.read"
            - "patient/MedicationDispense.read"
            - "patient/Observation.read"
            - "patient/Organization.read"
            - "patient/Patient.read"
            - "patient/Practitioner.read"
            - "patient/PractitionerRole.read"
            - "patient/Procedure.read"
            - "patient/Provenance.read"
            - "patient/RelatedPerson.read"
            - "system/*.read"
            - "system/AllergyIntolerance.read"
            - "system/CarePlan.read"
            - "system/CareTeam.read"
            - "system/Condition.read"
            - "system/Device.read"
            - "system/DiagnosticReport.read"
            - "system/DocumentReference.read"
            - "system/Encounter.read"
            - "system/ExplanationOfBenefit.read"
            - "system/Goal.read"
            - "system/Immunization.read"
            - "system/Location.read"
            - "system/Medication.read"
            - "system/MedicationRequest.read"
            - "system/MedicationDispense.read"
            - "system/Observation.read"
            - "system/Organization.read"
            - "system/Patient.read"
            - "system/Practitioner.read"
            - "system/PractitionerRole.read"
            - "system/Procedure.read"
            - "system/Provenance.read"
            - "system/RelatedPerson.read"
    keycloak:
      enabled: true
      adminUsername: admin
      adminPassword: REPLACE_WITH_YOUR_ADMIN_PASSWORD
      config:
        enabled: true
        realms:
          test:
            clients:
              inferno:
                consentRequired: true
                publicClient: true
                redirectURIs:
                  - "http://localhost:4567/inferno/*"
                defaultScopes: []
                optionalScopes:
                  - "patient/*.read"
                  - "patient/AllergyIntolerance.read"
                  - "patient/CarePlan.read"
                  - "patient/CareTeam.read"
                  - "patient/Condition.read"
                  - "patient/Device.read"
                  - "patient/DiagnosticReport.read"
                  - "patient/DocumentReference.read"
                  - "patient/Encounter.read"
                  - "patient/ExplanationOfBenefit.read"
                  - "patient/Goal.read"
                  - "patient/Immunization.read"
                  - "patient/Location.read"
                  - "patient/Medication.read"
                  - "patient/MedicationRequest.read"
                  - "patient/MedicationDispense.read"
                  - "patient/Observation.read"
                  - "patient/Organization.read"
                  - "patient/Patient.read"
                  - "patient/Practitioner.read"
                  - "patient/PractitionerRole.read"
                  - "patient/Procedure.read"
                  - "patient/Provenance.read"
                  - "patient/RelatedPerson.read"
              infernoBulk:
                consentRequired: false
                publicClient: false
                standardFlowEnabled: false
                serviceAccountsEnabled: true
                clientAuthenticatorType: client-jwt
                defaultScopes: []
                optionalScopes:
                  - "system/*.read"
                  - "system/AllergyIntolerance.read"
                  - "system/CarePlan.read"
                  - "system/CareTeam.read"
                  - "system/Condition.read"
                  - "system/Device.read"
                  - "system/DiagnosticReport.read"
                  - "system/DocumentReference.read"
                  - "system/Encounter.read"
                  - "system/ExplanationOfBenefit.read"
                  - "system/Goal.read"
                  - "system/Immunization.read"
                  - "system/Location.read"
                  - "system/Medication.read"
                  - "system/MedicationDispense.read"
                  - "system/MedicationRequest.read"
                  - "system/Observation.read"
                  - "system/Organization.read"
                  - "system/Patient.read"
                  - "system/Practitioner.read"
                  - "system/PractitionerRole.read"
                  - "system/Procedure.read"
                  - "system/Provenance.read"
                  - "system/RelatedPerson.read"
      ingress:
        enabled: true
        rules:
          - host: "{{ $.Release.Namespace }}.REPLACE_WITH_INGRESS_HOSTNAME"
            paths:
              - path: /auth
                pathType: Prefix
        servicePort: https
        tls:
          - secretName: REPLACE_SECRET_NAME_FOR_TLS
        annotations:
          nginx.ingress.kubernetes.io/server-snippet: |
            add_header Strict-Transport-Security "max-age=86400; includeSubDomains";
          nginx.ingress.kubernetes.io/backend-protocol: HTTPS
          nginx.ingress.kubernetes.io/proxy-buffer-size: "64k"
          nginx.ingress.kubernetes.io/proxy-ssl-protocols: TLSv1.2 TLSv1.3
      postgresql:
        postgresqlPassword: REPLACE_WITH_YOUR_POSTGRES_PASSWORD
    objectStorage:
      enabled: true
      location: REPLACE_WITH_COS_REGION
      endpointUrl: https://REPLACE_WITH_COS_ENDPOINT_URL
      accessKey: REPLACE_WITH_ACCESS_KEY
      secretKey: REPLACE_WITH_SECRET_KEY
      bulkDataBucketName: REPLACE_WITH_BUCKET_NAME
      batchIdEncryptionKey:
    

    The above configuration enables READ only system scopes.

    1. Upgrade and install
    helm upgrade --install ibm-fhir-server alvearie/ibm-fhir-server -f values-pentest.yaml --namespace=example 
    

    Note, helm outputs the fhiruser password and ingress.hostname, save this for later.

    1. Watch the pods until the pods are up in the Running state.
    kubectl -n pentest get pods -w
    NAME                               READY   STATUS     RESTARTS   AGE
    ibm-fhir-server-7557689c57-mq7zr   0/1     Init:0/1   0          53s
    ibm-fhir-server-7557689c57-tfjcl   0/1     Init:0/1   0          54s
    ibm-fhir-server-postgres-0         0/1     Pending    0          54s
    ibm-fhir-server-schematool-g2wq5   0/1     Init:0/1   0          54s
    

    Then it looks like and wait for the ibm-fhir-server is Running.

    ibm-fhir-server-7557689c57-mq7zr   0/1     Init:0/1   0          53s
    ibm-fhir-server-7557689c57-tfjcl   0/1     Init:0/1   0          54s
    ibm-fhir-server-postgres-0         0/1     Pending    0          54s
    ibm-fhir-server-schematool-g2wq5   0/1     Init:0/1   0          54s
    ibm-fhir-server-postgres-0         0/1     Pending    0          73s
    ibm-fhir-server-postgres-0         0/1     ContainerCreating   0          73s
    ibm-fhir-server-postgres-0         0/1     ContainerCreating   0          2m19s
    ibm-fhir-server-postgres-0         0/1     Running             0          2m20s
    ibm-fhir-server-postgres-0         1/1     Running             0          2m33s
    ibm-fhir-server-7557689c57-mq7zr   0/1     PodInitializing     0          2m42s
    ibm-fhir-server-7557689c57-mq7zr   0/1     Running             0          2m43s
    ibm-fhir-server-7557689c57-tfjcl   0/1     PodInitializing     0          2m44s
    ibm-fhir-server-schematool-g2wq5   0/1     PodInitializing     0          2m44s
    ibm-fhir-server-7557689c57-tfjcl   0/1     Running             0          2m45s
    ibm-fhir-server-schematool-g2wq5   1/1     Running             0          2m45s
    ibm-fhir-server-schematool-g2wq5   0/1     Completed           0          3m48s
    ibm-fhir-server-schematool-g2wq5   0/1     Completed           0          3m49s
    ibm-fhir-server-7557689c57-mq7zr   1/1     Running             0          3m50s
    ibm-fhir-server-7557689c57-tfjcl   1/1     Running             0          3m51s
    
    1. Check $healthcheck
    curl -i -u 'fhiruser:REPLACE_WITH_PASSWORD' 'https://REPLACE_WITH_BASE_URL.containers.appdomain.cloud/fhir-server/api/v4/$healthcheck' -v
    
    < HTTP/2 200 
    HTTP/2 200 
    < date: Wed, 19 Jan 2022 16:25:27 GMT
    date: Wed, 19 Jan 2022 16:25:27 GMT
    < content-length: 0
    content-length: 0
    < content-language: en-US
    content-language: en-US
    < strict-transport-security: max-age=15724800; includeSubDomains
    strict-transport-security: max-age=15724800; includeSubDomains
    

    6. Login to Keycloak

    Keycloak provides the authentication and authorization service for IBM FHIR Server’s implementation of Smart-on-FHIR.

    1. Sign in to the Keycloak Console https://REPLACE_WITH_BASE_URL/auth/ using the keycloak.admin as the user and the keycloak.adminPassword for the password.

    2. You are in the Test Realm, Click Clients > infernoBulk

    3. Select Use JWKS, enter https://bulk-data.smarthealthit.org/keys/RS384.public.json – note this key is only for testing.

    {
        "keys": [
            {
                "kty": "RSA",
                "alg": "RS384",
                "n": "<<REDACTED>>",
                "e": "AQAB",
                "key_ops": [
                    "verify"
                ],
                "use": "sig",
                "ext": true,
                "kid": "6cf70879258f9c656bb7ccc65802d099"
            }
        ]
    }
    
    1. Click Import

    2. Click Client Scopes. Under Optional Client Scopes, if any are specified as system/, Add selected.

    system/*.read
    system/AllergyIntolerance.read
    system/CarePlan.read
    system/CareTeam.read
    system/Condition.read
    system/Device.read
    system/DiagnosticReport.read
    system/DocumentReference.read
    system/Encounter.read
    system/ExplanationOfBenefit.read
    system/Goal.read
    system/Immunization.read
    system/Location.read
    system/Medication.read
    system/MedicationDispense.read
    system/MedicationRequest.read
    system/Observation.read
    system/Organization.read
    system/Patient.read
    system/Practitioner.read
    system/PractitionerRole.read
    system/Procedure.read
    system/Provenance.read
    system/RelatedPerson.read
    
    1. Click Service Account. If this is blank, it should prompt you to create the Service Account user.

    2. For Service-account-infernobulk, Click Groups

    3. Search available groups for /fhirUser and add the /fhirUser to the GroupMembership

    You now have a Service Account for SMART Backend Services Authorization for BulkData usage.

    7. Side Loading Data

    To sideload data, you can use a custom datasource and fhir-server-config.json, and startup a new container from the ibmcom/ibm-fhir-server image with kubectl installed with ibmcloud tools.

    1. Start up the container
    nerdctl run -p 9443:9443 --name fhir -e BOOTSTRAP_DB=true ibmcom/ibm-fhir-server
    docker.io/ibmcom/ibm-fhir-server:latest
    
    1. You then port-forward to the Kubernetes cluster’s postgres from the container
    kubectl port-forward --namespace=example service/ibm-fhir-server-postgres 5432:5432
    
    <server>
        <!-- ============================================================== -->
        <!-- TENANT: default; DSID: default; TYPE: read-write               -->
        <!-- ============================================================== -->
        <dataSource id="fhirDefaultDefault" jndiName="jdbc/fhir_default_default" type="javax.sql.XADataSource" statementCacheSize="200" syncQueryTimeoutWithTransactionTimeout="true" validationTimeout="30s">
            <jdbcDriver javax.sql.XADataSource="org.postgresql.xa.PGXADataSource" libraryRef="sharedLibPostgres"/>
            <properties.postgresql
                 serverName="localhost"
                 portNumber="5432"
                 databaseName="fhir"
                 user="postgres"
                 password="REPLACE_WITH_YOUR_POSTGRES_PASSWORD"
                 currentSchema="fhirdata"
             />
            <connectionManager maxPoolSize="200" minPoolSize="40"/>
        </dataSource>
    </server>
    
    1. Download the Patient bundle
    curl -L https://raw.githubusercontent.com/IBM/FHIR/main/fhir-server-test/src/test/resources/testdata/everything-operation/Antonia30_Acosta403.json -o Antonia30_Acosta403.json
    
    1. Check the Patient
    curl -u 'fhiruser:change-password' 'https://localhost:9443/fhir-server/api/v4/Patient?_format=application/json&_page=1&_sort=-_lastUpdated'
    

    You should see a single _count is 1 where a patient is now loaded, and now ready for more comprehensive testing.

    A test using the RS384 Key from SMART Health IT and uses the bulk data client to test the environment.

    Summary

    You have learned more about Connecathon and SMART Health IT with Backend Authorization.

    Further information on testing is available at https://bastide.org/2022/01/14/bulk-data-using-the-smart-on-fhir-bulk-data-client-to-test-export/

    Trackers/Issues

    A lot of interesting points were raised at the Connectathon, and the IBM Team identified a number of issues:

    1. AccessTokens should not be set with Presigned URLs #3188
    2. Support BulkData with Expires Header #3185
    3. Scope warning message for $export is confusing #3182
    4. $import allows adding Resources of multiple types in the same ndjson which could include unsupported resources. #3180
    5. fhir-smart Patient/$export assumes no _type filtering leading #3179
    6. Support subsetting exported resources based on implied SMART-on-FHIR scopes #3177
    7. Support associating a serviceAccount user with a particular group #33

    And a few which we opened with the bulk data client team:

    1. Bearer token is expected to be capitalized Bearer. #1
    2. User-Agent string is awkward #3
    3. Output doesn’t give a lot of details on what resourceType was exported #5

    And one we’re watching:

    1. Provides token even if requiresAccessToken is false #2

    And a few which we’ve had on the plan for a while:

    1. BulkData 2.0.0: _type query parameter’s cardinality is relaxed #3081
    2. Bulk Data Export 2.0.0: Support the bulkdata patient parameter #1719

    We’re also monitoring this issue:

    1. Provides token even if requiresAccessToken is false #2

    I’m looking forward to the next Connectathon and working with you all.

    Links

    These links are handy for anyone starting out:

    Resource Link
    ArtifactHub: IBM FHIR Server Helm Chart https://bit.ly/3qUgHiH
    GitHub – Helm Chart https://bit.ly/3eSKQcC
    GitHub – IBM FHIR Server https://bit.ly/3G4iEj5
    GitHub – IBM FHIR Server Documentation https://bit.ly/3eW5tok
    DockerHub: ibmcom/ibm-fhir-server https://hub.docker.com/r/ibmcom/ibm-fhir-server
    DockerHub: ibmcom/ibm-fhir-schematool https://hub.docker.com/r/ibmcom/ibm-fhir-schematool
  • Tips for IBM Cloud and running IBM FHIR Server

    Here are my tips/setup for the IBM FHIR. I hope they help you as you setup your environment.

    1. Create a variable to prefix the environment resources and the resource-group name.

    The following generates a date that is 14 days in the future, and is in lower case, it’s best to lower case everything in the following case:

    EXPIRY_DATE=$(date -j -v +14d +%Y-%b-%d |tr '[:upper:]' '[:lower:]')
    echo ${EXPIRY_DATE}
    

    The output is like the following:

    2022-mar-07
    
    1. Install the plugins

    When deploying the IBM FHIR Server, you’ll need a few additional plugins than the IBM Cloud default: cloud-object-storage, kubernetes-service, container-registry, cloud-database, event-streams and the infrastructure-service.

    ibmcloud plugin repo-plugins -r "IBM Cloud"
    ibmcloud plugin install cloud-object-storage -f
    ibmcloud plugin install container-service -f
    ibmcloud plugin install container-registry -f
    ibmcloud plugin install cloud-databases -f
    ibmcloud plugin install event-streams -f
    ibmcloud plugin install infrastructure-service -f
    
    1. Login with an API Key (much easier if you use SSO)
    API_KEY=$(cat cloudpak.json | jq -r .apiKey)
    ibmcloud login --apikey ${API_KEY} -r us-east
    
    1. As a first step, you can check to see if there are any exisiting resources in the account:
    # List the Current Databases
    ibmcloud cdb ls --json
    
    # List the Open Shift Cluster
    ibmcloud oc cluster ls --json
    
    # List the Open Shift Cluster or the Event Streams
    ibmcloud resource service-instances
    
    1. Check to see if you have an existing resource-group, if no group exists, create one.
    if ! ibmcloud resource group cloudpak-testing-${EXPIRY_DATE}
    then
        ibmcloud resource group-create 'cloudpak-testing'-${EXPIRY_DATE}
    fi
    
    1. Create a Cloud Object Storage Instance, if it does not exist.
    if ! ibmcloud resource service-instance cloudpak-testing-cos-${EXPIRY_DATE}
    then
        ibmcloud resource service-instance-create \
            cloudpak-testing-cos-${EXPIRY_DATE} \
            cloud-object-storage standard global \
        -g 'cloudpak-testing'-${EXPIRY_DATE}
        CRN=$(ibmcloud resource service-instance \
            cloudpak-testing-cos-${EXPIRY_DATE} \
            --output JSON | jq -r '.[].crn')
        ibmcloud cos config crn --crn "${CRN}"
        ibmcloud cos create-bucket --bucket \
            "fhir-cloudpak-testing-${EXPIRY_DATE}"
        ibmcloud resource service-key-create \
            test-user-hmac Writer --instance-id "${CRN}" \
            --parameters '{"HMAC":true}'
        ibmcloud resource service-key-create test-user-iam Writer \
            --instance-id "${CRN}" --parameters '{"HMAC":false}'
    fi
    

    Note, this creates an IAM and HMAC login user. The IBM FHIR Server team prefers the HMAC as it enables the use of presigned urls.

    1. Create an Event Streams instance, if it does not exist.
    if ! ibmcloud resource service-instance cloudpak-testing-es-${EXPIRY_DATE}
    then
        ibmcloud resource service-instance-create \
            cloudpak-testing-es-${EXPIRY_DATE} messagehub standard \
            us-east -g 'cloudpak-testing'-${EXPIRY_DATE}
        ibmcloud resource service-key-create service_manager Manager \
            --instance-name cloudpak-testing-es-${EXPIRY_DATE}
        ibmcloud es init -i cloudpak-testing-es-${EXPIRY_DATE}
        ibmcloud es topic-create --name FHIR_AUDIT --partitions 3
        ibmcloud es topic-create --name FHIR_NOTIFICATIONS --partitions 3
    fi
    
    1. Create a Db2 Instance, if it does not exist.
    if ! ibmcloud resource service-instance cloudpak-testing-db2-${EXPIRY_DATE}
    then
        ibmcloud resource service-instance-create \
            cloudpak-testing-db2-${EXPIRY_DATE} \
            dashdb-for-transactions standard us-east \
            -g 'cloudpak-testing'-${EXPIRY_DATE} -p '{
                "datacenter": "us-south:washington d.c",
                "high_availability": "no",
                "key_protect_instance": "none",
                "key_protect_key": "none",
                "oracle_compatibility": "no",
                "service-endpoints": "public-and-private"
            }'
    fi
    

    Note, there are some manual steps to complete the db2 setup.

    1. Create a postgres instance
    if ! ibmcloud resource service-instance cloudpak-testing-postgres-${EXPIRY_DATE}
    then
        ibmcloud resource service-instance-create \
            cloudpak-testing-postgres-${EXPIRY_DATE} \
            databases-for-postgresql standard us-east \
            -g 'cloudpak-testing'-${EXPIRY_DATE} \
            -p '{"service-endpoints": "public-and-private"}'
    fi
    

    Note, there are some manual steps to complete the postgres setup.

    1. Create the OpenShift Cluster. The CRN is from the prior creation of the COS instance.
    if [ $(ibmcloud oc cluster ls --provider vpc-gen2 --output json \
            | jq -r .[].name | grep -c cloudpak-testing) = 0 ]
    then
        VPC_ID=$(ibmcloud ks vpcs --provider vpc-gen2 --output json \
                    | jq -r .[].id)
        SUBNET_ID=$(ibmcloud ks subnets --provider vpc-gen2 \
            --vpc-id ${VPC_ID} --zone us-east-1 --output json \
                | jq -r '.[].id')
        ibmcloud oc cluster create vpc-gen2 \
            --name cloudpak-${EXPIRY_DATE} --flavor bx2.4x16 \
            --version 4.6_openshift \
            --cos-instance ${CRN} \
            --service-subnet 172.21.0.0/16 --pod-subnet 172.17.64.0/18 \
            --workers 3 --zone us-east-1 --vpc-id=${VPC_ID} \
            --subnet-id ${SUBNET_ID}
    fi
    
    1. Once the postgres instance is up, you can create users – fhiradmin and fhirserver:
    PG_PASSWORD="$(openssl rand -base64 21| base64 | sed 's|=||g' )>"
    echo "Postgres: " ${PG_PASSWORD}
    ibmcloud cdb deployment-user-create \
        cloudpak-testing-postgres-${EXPIRY_DATE} fhiradmin 
    ibmcloud cdb deployment-user-create \
        cloudpak-testing-postgres-${EXPIRY_DATE} fhirserver
    ibmcloud resource service-key-create service_manager \
        --instance-name cloudpak-testing-postgres-${EXPIRY_DATE}
    ibmcloud resource service-keys \
        --instance-name cloudpak-testing-postgres-${EXPIRY_DATE} \
        --output json
    
    1. Using psql, create a fhirserver user for the db:
    psql "host=********.databases.appdomain.cloud port=30794 dbname=ibmclouddb user=admin sslmode=verify-full"
        PGPASSWORD=******
    

    Note, if you don’t have psql in your path, use brew install postgres to get it.

    1. Login with the password from the json PGPASSWORD

    2. Run the following SQL to create the fhirserver user.

    CREATE USER fhirserver WITH LOGIN encrypted password '*****CHANGE*******';
    GRANT CONNECT ON DATABASE ibmclouddb TO fhirserver;
    
    1. Check the postgres configuration, and save locally:
    ibmcloud cdb deployment-connections \
        cloudpak-testing-postgres-${EXPIRY_DATE} --json
    
    1. Setup the necessary max_connections and max_prepared_transactions for postgres
    ibmcloud cdb deployment-configuration \
        cloudpak-testing-postgres-${EXPIRY_DATE} \
        '{"max_connections": 150}'
    sleep 2m
    ibmcloud cdb deployment-configuration \
        cloudpak-testing-postgres-${EXPIRY_DATE} \ 
        '{"max_prepared_transactions": 150}'
    
    1. Create the db2 service-key
    ibmcloud resource service-key-create service_manager \
        Manager --instance-name cloudpak-testing-db2-${EXPIRY_DATE}
    
    1. Login and create fhirserver on the https://cloud.ibm.com

    Your environment is ready to run the IBM offering for IBM FHIR Server along with the supporting resources.

  • Recipe: Getting started with the IBM FHIR Server and Terminology

    The IBM FHIR Server Terminology module fhir-term provides a FHIR terminology service provider interface (SPI) and a default implementation that implements terminology services using CodeSystem, ValueSet, and ConceptMap resources that have been made available through the FHIR registry module fhir-registry.

    This document outlines a small test environment to setup Cassandra and ElasticSearch to run the Terminology and run a simple test.

    Recipe

    1. Clone the IBM FHIR Server repository and switch to the cloned repository.
    git clone https://github.com/IBM/FHIR.git && cd $(basename $_ .git)
    
    1. Setup the examples
    mvn clean install -f fhir-examples -DskipTests
    
    1. Edit line 68 – term/fhir-term-graph/pom.xml, change provided to compile
    <dependency>
                <groupId>org.janusgraph</groupId>
                <artifactId>janusgraph-cql</artifactId>
                <scope>compile</scope>
    
    1. Edit line 174 – term/fhir-term-graph/pom.xml, change provided to compile
            <dependency>
                <groupId>org.janusgraph</groupId>
                <artifactId>janusgraph-es</artifactId>
                <scope>compile</scope>
            </dependency>
    
    1. Setup the fhir projects
    mvn clean install -f fhir-parent -DskipTests
    
    1. Build the IBM FHIR Server
    export WORKSPACE=$(pwd)
    export BUILD_ID=4.11.0-SNAPSHOT
    docker logout
    nerdctl build fhir-install -t ibmcom/ibm-fhir-server:graph
    

    You’ll see:

    Successfully built 196dd54732a4
    Successfully tagged ibmcom/ibm-fhir-server:latest
    
    1. Build the Graph Terminology Loader
    pushd
    cd ${WORKSPACE}/fhir-install/src/main/docker/ibm-fhir-term-graph-loader/
    mkdir -p target/
    cp ${WORKSPACE}/term/fhir-term-graph-loader/target/fhir-term-graph-loader-*-cli.jar target/
    cp ${WORKSPACE}/LICENSE target/
    nerdctl build --build-arg FHIR_VERSION=${BUILD_ID} -t ibmcom/ibm-fhir-term-loader:latest .
    popd
    
    1. Download the janusgraph-cassandra-elasticsearch.properties
    curl -o janusgraph-cassandra-elasticsearch.properties -L \
        https://raw.githubusercontent.com/IBM/FHIR/main/term/fhir-term-graph-loader/src/test/resources/conf/janusgraph-cassandra-elasticsearch.properties
    
    1. Download the fhir-server-config.json
    curl -o fhir-server-config.json -L \
        https://gist.githubusercontent.com/prb112/c08613e6e21e77b92cfc0ea19c56f081/raw/a9e17bb2924b59de14bd04aa9fbbcc8ff38afb10/fhir-server-config-term.json
    
    1. Download the docker-compose.yml
    curl -o docker-compose.yml -L \
        https://gist.githubusercontent.com/prb112/1461e66d28767ba169843bded4b0aad8/raw/a4ed5b59fd8eb430745c173536dc5ab304186c7a/docker-compose.yml
    
    1. Startup the cassandra container
    nerdctl compose up cassandra
    
    1. Check the logs until you see:
    fhir-cassandra |INFO  [main] 2022-02-19 00:36:18,884 CassandraDaemon.java:780 - Startup complete
    
    1. Startup the elasticsearch container
    nerdctl compose up elasticsearch -d
    
    1. Check the logs for "Yellow to Green"
    Cluster health status changed from [YELLOW] to [GREEN]
    
    1. Start the IBM FHIR Server
    nerdctl compose up fhir-server -d
    
    1. Check the logs for smarter planet
    fhir-server_1 |[2/19/22, 0:40:09:547 UTC] 00000026 FeatureManage A   CWWKF0011I: The defaultServer server is ready to run a smarter planet. The defaultServer server started in 27.170 seconds.
    

    You should also see the schema created:

    fhir-server |[2/19/22, 1:20:11:487 UTC] 00000027 FHIRTermGraph I   Creating schema...
    fhir-server |[2/19/22, 1:20:13:840 UTC] 00000027 FHIRTermGraph I   
    fhir-server |------------------------------------------------------------------------------------------------
    fhir-server |Vertex Label Name              | Partitioned | Static                                             |
    fhir-server |---------------------------------------------------------------------------------------------------
    fhir-server |CodeSystem                     | false       | false                                              |
    fhir-server |Concept                        | false       | false                                              |
    fhir-server |Designation                    | false       | false                                              |
    fhir-server |Property_                      | false       | false                                              |
    fhir-server |---------------------------------------------------------------------------------------------------
    fhir-server |Edge Label Name                | Directed    | Unidirected | Multiplicity                         |
    fhir-server |---------------------------------------------------------------------------------------------------
    fhir-server |isa                            | true        | false       | MULTI                                |
    fhir-server |concept                        | true        | false       | MULTI                                |
    fhir-server |designation                    | true        | false       | MULTI                                |
    fhir-server |property_                      | true        | false       | MULTI                                |
    fhir-server |---------------------------------------------------------------------------------------------------
    fhir-server |Property Key Name              | Cardinality | Data Type                                          |
    fhir-server |---------------------------------------------------------------------------------------------------
    fhir-server |valueCode                      | SINGLE      | class java.lang.String                             |
    fhir-server |display                        | SINGLE      | class java.lang.String                             |
    fhir-server |version                        | SINGLE      | class java.lang.String                             |
    fhir-server |code                           | SINGLE      | class java.lang.String                             |
    fhir-server |codeLowerCase                  | SINGLE      | class java.lang.String                             |
    fhir-server |url                            | SINGLE      | class java.lang.String                             |
    fhir-server |value                          | SINGLE      | class java.lang.String                             |
    fhir-server |valueBoolean                   | SINGLE      | class java.lang.Boolean                            |
    fhir-server |valueDateTimeLong              | SINGLE      | class java.lang.Long                               |
    fhir-server |valueDecimal                   | SINGLE      | class java.lang.Double                             |
    fhir-server |valueInteger                   | SINGLE      | class java.lang.Integer                            |
    fhir-server |valueString                    | SINGLE      | class java.lang.String                             |
    fhir-server |count                          | SINGLE      | class java.lang.Integer                            |
    fhir-server |group                          | SINGLE      | class java.lang.String                             |
    fhir-server |language                       | SINGLE      | class java.lang.String                             |
    fhir-server |system                         | SINGLE      | class java.lang.String                             |
    fhir-server |use                            | SINGLE      | class java.lang.String                             |
    fhir-server |valueDateTime                  | SINGLE      | class java.lang.String                             |
    fhir-server |valueDecimalString             | SINGLE      | class java.lang.String                             |
    fhir-server |---------------------------------------------------------------------------------------------------
    fhir-server |Graph Index (Vertex)           | Type        | Unique    | Backing        | Key:           Status |
    fhir-server |---------------------------------------------------------------------------------------------------
    fhir-server |byUrl                          | Composite   | false     | internalindex  | url:          ENABLED |
    fhir-server |byCode                         | Composite   | false     | internalindex  | code:         ENABLED |
    fhir-server |byCodeLowerCase                | Composite   | false     | internalindex  | codeLowerCase:    ENABLED |
    fhir-server |byDisplay                      | Composite   | false     | internalindex  | display:      ENABLED |
    fhir-server |byUrlAndVersion                | Composite   | false     | internalindex  | url:          ENABLED |
    fhir-server |                               |             |           |                | version:      ENABLED |
    fhir-server |byValue                        | Composite   | false     | internalindex  | value:        ENABLED |
    fhir-server |byValueBoolean                 | Composite   | false     | internalindex  | valueBoolean:    ENABLED |
    fhir-server |byValueCode                    | Composite   | false     | internalindex  | valueCode:    ENABLED |
    fhir-server |byValueDateTimeLong            | Composite   | false     | internalindex  | valueDateTimeLong:    ENABLED |
    fhir-server |byValueDecimal                 | Composite   | false     | internalindex  | valueDecimal:    ENABLED |
    fhir-server |byValueInteger                 | Composite   | false     | internalindex  | valueInteger:    ENABLED |
    fhir-server |byValueString                  | Composite   | false     | internalindex  | valueString:    ENABLED |
    fhir-server |vertices                       | Mixed       | false     | search         | display:      ENABLED |
    fhir-server |                               |             |           |                | value:        ENABLED |
    fhir-server |                               |             |           |                | valueCode:    ENABLED |
    fhir-server |                               |             |           |                | valueString:    ENABLED |
    fhir-server |---------------------------------------------------------------------------------------------------
    fhir-server |Graph Index (Edge)             | Type        | Unique    | Backing        | Key:           Status |
    fhir-server |---------------------------------------------------------------------------------------------------
    fhir-server |---------------------------------------------------------------------------------------------------
    fhir-server |Relation Index (VCI)           | Type        | Direction | Sort Key       | Order    |     Status |
    fhir-server |---------------------------------------------------------------------------------------------------
    fhir-server |
    fhir-server |[2/19/22, 1:20:15:061 UTC] 00000071 Clock         I com.datastax.oss.driver.internal.core.time.Clock getInstance Using native clock for microsecond precision
    fhir-server |[2/19/22, 1:20:15:132 UTC] 00000027 ExecutorServi I org.janusgraph.diskstorage.configuration.ExecutorServiceBuilder buildFixedExecutorService Initiated fixed thread pool of size 10
    fhir-server |[2/19/22, 1:20:15:197 UTC] 00000027 UniqueInstanc I org.janusgraph.graphdb.idmanagement.UniqueInstanceIdRetriever getOrGenerateUniqueInstanceId Generated unique-instance-id=0a04031316-fhir2
    fhir-server |[2/19/22, 1:20:15:256 UTC] 00000084 Clock         I com.datastax.oss.driver.internal.core.time.Clock getInstance Using native clock for microsecond precision
    fhir-server |[2/19/22, 1:20:15:331 UTC] 00000027 ExecutorServi I org.janusgraph.diskstorage.configuration.ExecutorServiceBuilder buildFixedExecutorService Initiated fixed thread pool of size 10
    fhir-server |[2/19/22, 1:20:15:332 UTC] 00000027 Backend       I org.janusgraph.diskstorage.Backend getIndexes Configuring index [search]
    fhir-server |[2/19/22, 1:20:15:373 UTC] 00000027 ExecutorServi I org.janusgraph.diskstorage.configuration.ExecutorServiceBuilder buildFixedExecutorService Initiated fixed thread pool of size 4
    fhir-server |[2/19/22, 1:20:15:541 UTC] 00000027 KCVSLog       I org.janusgraph.diskstorage.log.kcvs.KCVSLog$MessagePuller initializeTimepoint Loaded unidentified ReadMarker start time 2022-02-19T01:20:15.541046Z into org.janusgraph.diskstorage.log.kcvs.KCVSLog$MessagePuller@2c491de4
    
    1. Check the Capabilities endpoint, it should respond with a wealth of input
    curl --request GET \
      --url https://localhost:9443/fhir-server/api/v4/metadata \
      --header 'Content-Type: application/fhir+json' \
      --header 'X-FHIR-TENANT-ID: default' \
      -k -o /dev/null -I
    
    1. Copy over the Jar file term/fhir-term-graph-loader/target/fhir-term-graph-loader-4.11.0-SNAPSHOT-cli.jar

    2. Loading the Data for your specific use-case.

    • CodeSystem
    java -jar fhir-term-graph-loader-4.11.0-SNAPSHOT-cli.jar -config ./janusgraph-cassandra-elasticsearch.properties -file CodeSystem.json
    

    Also -url

    • Snomed
    java -jar fhir-term-graph-loader-4.11.0-SNAPSHOT-cli.jar -config ./janusgraph-cassandra-elasticsearch.properties \
        -base snomed/ \
        -concept concept.cpt \
        -relation relation.rt \
        -desc desc.file \
        -lang lang.file
    
    • UMLS
    java -jar fhir-term-graph-loader-4.11.0-SNAPSHOT-cli.jar -config ./janusgraph-cassandra-elasticsearch.properties
    

    I don’t actually execute these commands in this blog as it is licensed content.

    1. Execute the queries for the terminology system.

    There are some ancillary tasks you should do when done:

    A. Shutdown the Containers when you are done.

    nerdctl compose down 
    

    B. List current moby images.

    nerdctl images
    
  • Recipe: Setting up IBM FHIR Server and Azure in Development

    The IBM FHIR Server has support for exporting and importing Bulk Data using extended operations for Bulk Data $import, $export and $bulkdata-status, which are implemented as Java Maven projects. The IBM FHIR Server uses JSR252 JavaBatch jobs running in the Open Liberty Java Batch Framework to enable access to Large Volumes of HL7 FHIR data.

    This blog is a follow on to Recipe: IBM FHIR Server – Using Bulk Data with the Azure Blob Service, and provides a docker-compose file that works with the Azure emulator called Azurite.

    Typically, you can run the container locally:

    docker run -p 10000:10000 -v /local/path/to/azurite:/data mcr.microsoft.com/azure-storage/azurite \
        azurite-blob --blobHost 0.0.0.0 --blobPort 10000

    Recipe

    1. Pull the image

    $docker pull mcr.microsoft.com/azure-storage/azurite
    
    Using default tag: latest
    latest: Pulling from azure-storage/azurite
    396c31837116: Pull complete 
    9e7b0c9574dd: Pull complete 
    ec07c04a8d4c: Pull complete 
    c1eb01e62785: Pull complete 
    2cbc599970e9: Pull complete 
    a0ee56369073: Pull complete 
    ad1956587082: Pull complete 
    29652032eab7: Pull complete 
    Digest: sha256:4d40e97bf9345c9e321f4b8cf722dc4615d5d6080fd2953844be288a13eadb59
    Status: Downloaded newer image for mcr.microsoft.com/azure-storage/azurite:latest
    mcr.microsoft.com/azure-storage/azurite:latest

    2. Download the docker-compose.yml and put it in a working folder

    3. Download the fhir-server-config.json and put it in the same working folder

    4. Create a folder azurite in the working folder.

    5. The file layout should look like the following:

    6. Startup the Docker Compose

    nerdctl --address /var/run/docker/containerd/containerd.sock compose up

    You can then upload data, and use the Azurite emulator, the key is:

    "storageProviders": {
         "default" : {
         "type": "azure-blob",
         "bucketName": "fhirbulkdata",
         "auth" : {
              "type": "connection",
              "connection": "DefaultEndpointsProtocol=http;AccountName=account1;AccountKey=key1;BlobEndpoint=http://azure-blob:10000/account1;"
         },
         "disableOperationOutcomes": true,
         "duplicationCheck": false, 
         "validateResources": false, 
         "create": false
         }
    }
  • Recipe: Streaming the FHIR Audit from the IBM FHIR Server with Go

    The IBM FHIR Server supports audit events for FHIR interactions (CREATE-READ-UPDATE-DELETE-SEARCH-EXTENDED_OPERATION) in Cloud Auditing Data Federation (CADF) and HL7 FHIR AuditEvent and pushing the events to an Apache Kafka backend. You can read more about it in another post I made.

    This recipe shows how to stream the data with Go-Kafka in a small lightweight library and decode the BASE64 content embedded in CADF.

    Let’s spin up an IBM FHIR Server with fhir-audit and how to stream and decode the important content.

    Recipe

    1. Log in to the IBM Cloud Console
    2. Click Create Resource
    Click Create Resource (on right)

    3. Search for Event Streams, and click on Event Streams

    Search and Click on Event Streams

    4. Choose your Location. I chose us-south. Pick the data center that is closest or co-located with your IBM FHIR Server.

    5. Select the Standard plan. A typical bundle which represents a patient history, such as Antonia30_Acosta403.json, include hundreds of resources which correspond to many messages sent over the topic when processed as a Bundle batch. For a Bundle transaction, you get only one notification message.

    6. Click I Accept

    7. Click Create. You are redirected to the EventStreams resource that is created.

    Your Service is Created

    8. Click Service Credentials

    9. Click New Credentials

    10. Click Add

    Create Credentials Dialog

    11. Copy the Service Credentials and paste it locally (on the right hand-side)

    Copy Credentials

    12. Click on Topics

    13. Click Create Topic

    14. Enter Topic Name – FHIR_AUDIT

    15. Click Next

    Enter Topic Name

    16. Select the default number of partitions

    Select Number of Partitions

    17. Click Next

    18. Select Message Retention – 1 Day

    19. Click Create Topic

    Create Topic

    With the IBM EventStreams Kafka Topic setup, it’s now time to connect the IBM FHIR Server and the FHIR Audit module to Kafka.

    20. Download the fhir-server-config.json

    curl -L https://raw.githubusercontent.com/IBM/FHIR/main/fhir-server-webapp/src/main/liberty/config/config/default/fhir-server-config-audit-environment.json -o fhir-server-config-audit-environment.json

    The file contains a setting for loading the properties from the Environment (e.g. Operation System variables) and some basic settings:

            "audit": {
                "serviceClassName" : "com.ibm.fhir.audit.impl.KafkaService",
                "serviceProperties" : {
                    "auditTopic": "FHIR_AUDIT",
                    "geoCity": "Dallas",
                    "geoState": "TX",
                    "geoCounty": "US"
                }
            }

    21. Download the docker image

    docker pull ibmcom/ibm-fhir-server:latest

    22. Convert the copied Service Credentials into a file – ibm-creds.json

    23. Make the JSON a single line

    cat ibm-creds.json | tr -d '\n'

    The output is:

    {  "api_key": "credentialapikey",  "apikey": "credentialapikey",  "iam_apikey_description": "Auto-generated for key 01ba3165-85d9-410b-ad1a-1111",  "iam_apikey_name": "Service credentials-1",  "iam_role_crn": "crn:v1:bluemix:public:iam::::serviceRole:Manager",  "iam_serviceid_crn": "crn:v1:bluemix:public:iam-identity::a/11111::serviceid:ServiceId-864b549f-ff90-4b24-84f2-1111",  "instance_id": "6b00cc8c-26ec-4c42-a1e8-f4da5b9f71e7",  "kafka_admin_url": "https://admin.eventstreams.cloud.ibm.com",  "kafka_brokers_sasl": [    "broker-3.eventstreams.cloud.ibm.com:9093",    "broker-5.eventstreams.cloud.ibm.com:9093",    "broker-1.eventstreams.cloud.ibm.com:9093",    "broker-2.eventstreams.cloud.ibm.com:9093",    "broker-4.eventstreams.cloud.ibm.com:9093",    "broker-0.eventstreams.cloud.ibm.com:9093"  ],  "kafka_http_url": "https://admin.eventstreams.cloud.ibm.com",  "password": "credentialapikey",  "user": "token"}
    

    24. Export ES_CONFIG

    export ES_CONFIG='{  "api_key": "credentialapikey",  "apikey": "credentialapikey",  "iam_apikey_description": "Auto-generated for key 01ba3165-85d9-410b-ad1a-1111",  "iam_apikey_name": "Service credentials-1",  "iam_role_crn": "crn:v1:bluemix:public:iam::::serviceRole:Manager",  "iam_serviceid_crn": "crn:v1:bluemix:public:iam-identity::a/11111::serviceid:ServiceId-864b549f-ff90-4b24-84f2-1111",  "instance_id": "6b00cc8c-26ec-4c42-a1e8-f4da5b9f71e7",  "kafka_admin_url": "https://admin.eventstreams.cloud.ibm.com",  "kafka_brokers_sasl": [    "broker-3.eventstreams.cloud.ibm.com:9093",    "broker-5.eventstreams.cloud.ibm.com:9093",    "broker-1.eventstreams.cloud.ibm.com:9093",    "broker-2.eventstreams.cloud.ibm.com:9093",    "broker-4.eventstreams.cloud.ibm.com:9093",    "broker-0.eventstreams.cloud.ibm.com:9093"  ],  "kafka_http_url": "https://admin.eventstreams.cloud.ibm.com",  "password": "credentialapikey",  "user": "token"}'
    

    Note the single quotes surround the above.

    25. Start up the IBM FHIR Server with the EventStreams credentials and fhir-server-config.json we just downloaded.

    docker run --rm -d -p 9443:9443 -e BOOTSTRAP_DB=true \
        -v $(pwd)/fhir-server-config-audit-environment.json:/config/config/default/fhir-server-config.json \
        -e EVENT_STREAMS_AUDIT_BINDING="${ES_CONFIG}" ibmcom/ibm-fhir-server
    

    You see the container id output 60a5f1cae6d677d80772f1736db1be74836a8a4845fcccc81286b7c557bc2d86.

    26. Check that the applications are started using the container id.

    $ docker logs 60a | grep -i started
    [4/21/21, 20:55:58:449 UTC] 00000001 FrameworkMana I   CWWKE0002I: The kernel started after 1.43 seconds
    [4/21/21, 20:55:58:464 UTC] 0000002a FeatureManage I   CWWKF0007I: Feature update started.
    [4/21/21, 20:56:01:328 UTC] 00000030 AppMessageHel A   CWWKZ0001I: Application fhir-openapi started in 1.588 seconds.
    [4/21/21, 20:56:03:141 UTC] 00000031 AppMessageHel A   CWWKZ0001I: Application fhir-bulkdata-webapp started in 3.402 seconds.
    [4/21/21, 20:56:07:824 UTC] 0000002d AppMessageHel A   CWWKZ0001I: Application fhir-server-webapp started in 7.871 seconds.
    [4/21/21, 20:56:07:868 UTC] 0000002a TCPPort       I   CWWKO0219I: TCP Channel defaultHttpEndpoint-ssl has been started and is now listening for requests on host *  (IPv4) port 9443.
    [4/21/21, 20:56:07:880 UTC] 0000002a FeatureManage A   CWWKF0011I: The defaultServer server is ready to run a smarter planet. The defaultServer server started in 10.885 seconds.

    27. Download the Sample Data

    curl -L https://raw.githubusercontent.com/IBM/FHIR/main/fhir-server-test/src/test/resources/testdata/everything-operation/Antonia30_Acosta403.json -o Antonia30_Acosta403.json

    28. Load the Sample Data bundle to the IBM FHIR Server

    curl -k --location --request POST 'https://localhost:9443/fhir-server/api/v4' \
    --header 'Content-Type: application/fhir+json' \
    -u "fhiruser:change-password" \
    --data-binary  "@Antonia30_Acosta403.json" -o response.json

    29. Scan the response.json for any status that is not "status": "201". For example, the status is in the family of User Request Error or Server Side Error.

    30. Clone the repository fhir-kafka-go

    31. Clone the repository

    git clone https://github.com/prb112/fhir-kafka-go.git

    32. Build the Go guild

    go build

    33. Run the fhir-kakfka-go library

    ./fhir-kafka-go $(cat ibm-creds.json | jq -r '.kafka_brokers_sasl | join(",")') "token" $(cat ibm-creds.json | jq -r '.password') FHIR_AUDIT

    You’ll see the following output:

    2022/02/18 12:53:02 The audit details are:  t${
        "request_unique_id": "8b7ebb8f-3b48-4633-92d3-4e1d423a3aa2",
        "action": "C",
        "start_time": "2022-02-18 17:52:22.444",
        "end_time": "2022-02-18 17:52:22.525",
        "api_parameters": {
            "request": "/fhir-server/api/v4/Patient",
            "request_status": 201
        },
        "data": {
            "resource_type": "Patient",
            "id": "17f0df6a5b6-1d8db4d7-5042-4742-a951-6c3a3d08ee34",
            "version_id": "1"
        },
        "event_type": "fhir-create",
        "description": "FHIR Create request",
        "location": "172.17.0.1/_gateway"
    }

    This tool uses Go-Kafka and a few extra tools to process Kafka Messages and help tail the messages.

    I hope this works for you, it’s great to work with Go and Kafka.

  • A Reliable CVE Dependency Check: How-To

    dependency-check is a standalone maven plugin which checks for vulnerable dependencies. It’s hosted on GitHub. I switched to it from the victims-db, which no longer looks like it is updated. I had to carefully analyze the output, it was very helpful finding one issue where we had an unintended include.

    Build

    export TAG=4.10.2
    git checkout ${TAG}
    mvn clean install -f fhir-examples -DskipTests
    mvn clean install -f fhir-parent -DskipTests
    mvn org.owasp:dependency-check-maven:check -f fhir-parent/ -l output-${TAG}.log
    grep -e 'vulnerabilities' -e CVE- output-${TAG}.log  > summary-${TAG}.log
    

    Output Note, this is some what cleaned up.

    fhir-term-graph:
    ant-1.7.0.jar (pkg:maven/org.apache.ant/ant@1.7.0, cpe:2.3:a:apache:ant:1.7.0:*:*:*:*:*:*:*) : CVE-2020-1945
    cassandra-driver-core-3.11.0.jar (pkg:maven/com.datastax.cassandra/cassandra-driver-core@3.11.0, cpe:2.3:a:apache:cassandra:3.11.0:*:*:*:*:*:*:*) : CVE-2018-8016, CVE-2020-13946, CVE-2020-17516
    gremlin-core-3.5.1.jar (pkg:maven/org.apache.tinkerpop/gremlin-core@3.5.1, cpe:2.3:a:apache:tinkerpop:3.5.1:*:*:*:*:*:*:*) : CVE-2021-37136, CVE-2021-37137
    
  • Bulk Data: Using the SMART-on-FHIR Bulk Data Client to test $export

    I recently attended the HL7 FHIR Connectathon 29. For those that are not familiar with Connectathons, I think they are fairly unique events featuring standards enthusiasts, vendors and implementors doing hands-on standards development (FHIR) and testing. As an attendee I picked one of the tracksbulk data.

    This blog is part of a series on Bulk Data Setup and Testing based on my experience at HL7 FHIR Connectathon 29.

    The SMART-on-FHIR team has built a node application to call Bulk Data $export and facilitate the calls to get a bearer token.

    1. Clone the Bulk Data Client repository
    /git/wffh/2023$ git clone -b main-pb --single-branch \
        https://github.com/prb112/bulk-data-client.git && \
        cd $(basename $_ .git)
    Cloning into 'bulk-data-client'...
    remote: Enumerating objects: 343, done.
    remote: Counting objects: 100% (343/343), done.
    remote: Compressing objects: 100% (188/188), done.
    remote: Total 343 (delta 220), reused 261 (delta 146), pack-reused 0
    Receiving objects: 100% (343/343), 407.93 KiB | 3.32 MiB/s, done.
    Resolving deltas: 100% (220/220), done.
    /git/wffh/2023/bulk-data-client$
    

    Note I added a changes to support the IBM FHIR Server.

    1. Install nvm
    $ brew install nvm
    Running `brew update --preinstall`...
    ==> Auto-updated Homebrew!
    Updated 3 taps (homebrew/core, homebrew/cask and minio/stable).
    ==> New Formulae
    cwb3                                                      erofs-utils                                               netmask                                                   scalingo                                                  zk
    ==> Updated Formulae
    Updated 390 formulae.
    
    ...
    
    nvm 0.35.3 is already installed but outdated (so it will be upgraded).
    ==> Downloading https://ghcr.io/v2/homebrew/core/nvm/manifests/0.39.1_1
    ######################################################################## 100.0%
    ==> Downloading https://ghcr.io/v2/homebrew/core/nvm/blobs/sha256:6e14c8a2bf94212545c1ebac9a722df168c318d0e8af2fc75b729a07fea54efe
    ==> Downloading from https://pkg-containers.githubusercontent.com/ghcr1/blobs/sha256:6e14c8a2bf94212545c1ebac9a722df168c318d0e8af2fc75b729a07fea54efe?se=2022-01-13T21%3A30%3A00Z&sig=52CYOAyuxabqLNYih9%2BjMQtHLFrN%2FVyHXf%2FgPuBgi6w%3D&sp=r&spr=https&sr=b&sv=2019-12-12
    ######################################################################## 100.0%
    ==> Upgrading nvm
      0.35.3 -> 0.39.1_1
    
    ==> Pouring nvm--0.39.1_1.all.bottle.tar.gz
    ...
    
    Bash completion has been installed to:
      /usr/local/etc/bash_completion.d
    ==> Summary
    🍺  /usr/local/Cellar/nvm/0.39.1_1: 9 files, 184.1KB
    ==> Running `brew cleanup nvm`...
    Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
    Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
    Removing: /usr/local/Cellar/nvm/0.35.3... (7 files, 149.9KB)
    
    1. Switch to Node 16
    $ nvm use 16
    Now using node v16.13.0 (npm v8.1.2)
    
    1. Install Node 16
    $ nvm install
    Found '/git/wffh/2023/bulk-data-client/.nvmrc' with version <16>
    Downloading and installing node v16.13.2...
    Downloading https://nodejs.org/dist/v16.13.2/node-v16.13.2-darwin-x64.tar.xz...
    ############################################################## 100.0%
    Computing checksum with sha256sum
    Checksums matched!
    Now using node v16.13.2 (npm v8.1.2)
    
    1. Create the binaries
    $ npm run clean && npm run build
    
    > bulk-data-client@1.0.0 clean
    > rm -rf ./built
    
    
    > bulk-data-client@1.0.0 build
    > tsc
    
    1. Create the config/ibm-fhir-server.js – update the fhirUrl, tokenUrl and privateKey. I used the RS384 Key.
    1. Run the application
    $ AUTO_RETRY_TRANSIENT_ERRORS=1 SHOW_ERRORS=1 node . --config config/ibm-fhir-server.js  --_type=Patient
    Kick-off started
    Got new access token
    Kick-off completed
    Bulk Data export started
    Bulk Data export completed in 10 seconds
    Export manifest:  {
      transactionTime: '2022-01-14T20:56:31.553Z',
      request: 'https://bulk.cluster1-blue-x123456.containers.appdomain.cloud/fhir-server/api/v4/Patient/$export?_type=Patient',
      requiresAccessToken: false,
      output: [
        {
          type: 'Patient',
          url: 'https://x123456.cloud-object-storage.appdomain.cloud/x123456/f1wSMD84-3eRVNTUlyIwctWtNxA33Odz14KiQctry-U/Patient_1.ndjson?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=x123456%2F20220114%2Fus-east%2Fs3%2Faws4_request&X-Amz-Date=20220114T205641Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=abcdefgh',
          count: 2
        }
      ],
      error: []
    }
    
    Downloading exported files: ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 100%
              Downloaded Files: 1 of 1
                FHIR Resources: 2
                   Attachments: 0
               Downloaded Size: 6.3 kB
    
    Download completed in 0 seconds
    Do you want to signal the server that this export can be removed? [Y/n]n
    

    If you need to debug, you’ll see all the outputs:

    NODE_DEBUG=* AUTO_RETRY_TRANSIENT_ERRORS=1 SHOW_ERRORS=1 node . \
       --config config/ibm-fhir-server.js
    
    1. The files are downloaded to a local directory, and you can check the data. cat downloads/1.Patient.ndjson
    {"resourceType":"Patient","id":"17e44643c84-7618d4fe-e1d3-48e5-8a66-923bd4780c17","meta":{"versionId":"1","lastUpdated":"2022-01-10T14:28:46.854836Z"},"text":{"status":"generated","div":"<div xmlns=\"http://www.w3.org/1999/xhtml\">Generated by <a href=\"https://github.com/synthetichealth/synthea\">Synthea</a>.Version identifier: master-branch-latest-2-g8e7e92c\n .   Person seed: 7105486291024734541  Population seed: 0</div>"},"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Graciela518 Badillo851"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Caguas","state":"Puerto Rico","country":"PR"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":1.6165934938317164},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":47.383406506168285},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"F"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"bd958c64-56b6-f206-b03d-8f4b8a417215"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"bd958c64-56b6-f206-b03d-8f4b8a417215"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-93-6090"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99913499"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X26583229X"}],"name":[{"use":"official","family":"Acosta403","given":["Antonia30"],"prefix":["Mrs."]},{"use":"maiden","family":"Armenta418","given":["Antonia30"],"prefix":["Mrs."]}],"telecom":[{"system":"phone","value":"555-457-3489","use":"home"}],"gender":"female","birthDate":"1970-09-06","address":[{"extension":[{"extension":[{"url":"latitude","valueDecimal":41.6497330022825},{"url":"longitude","valueDecimal":-71.17511064024423}],"url":"http://hl7.org/fhir/StructureDefinition/geolocation"}],"line":["635 Littel Esplanade Suite 65"],"city":"Fall River","state":"Massachusetts","postalCode":"02790","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"es","display":"Spanish"}],"text":"Spanish"}}]}
    {"resourceType":"Patient","id":"17e4f2f3dca-67724e2c-3991-4ac1-b6f9-51fc30209894","meta":{"versionId":"1","lastUpdated":"2022-01-12T16:46:43.149786Z"},"text":{"status":"generated","div":"<div xmlns=\"http://www.w3.org/1999/xhtml\">Generated by <a href=\"https://github.com/synthetichealth/synthea\">Synthea</a>.Version identifier: v2.4.0-404-ge7ce2295\n .   Person seed: -8292973307042192125  Population seed: 0</div>"},"extension":[{"extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2135-2","display":"Other"}},{"url":"text","valueString":"Other"}],"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"},{"extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}],"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Isabel214 Reséndez908"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"La Paz","state":"Baja California","country":"MX"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":0.13213216767824654},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":34.86786783232176}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"d171d808-1f31-4ad3-aba5-e03a2fa921c7"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"d171d808-1f31-4ad3-aba5-e03a2fa921c7"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-83-6585"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99914532"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X18544570X"}],"name":[{"use":"official","family":"Hurtado459","given":["Marco Antonio298"],"prefix":["Mr."]}],"telecom":[{"system":"phone","value":"555-185-5373","use":"home"}],"gender":"male","birthDate":"1983-04-03","address":[{"extension":[{"extension":[{"url":"latitude","valueDecimal":42.49384661818001},{"url":"longitude","valueDecimal":-70.92858466579901}],"url":"http://hl7.org/fhir/StructureDefinition/geolocation"}],"line":["552 Rippin Port"],"city":"Revere","state":"Massachusetts","postalCode":"02151","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"es","display":"Spanish"}],"text":"Spanish"}}]}
    

    As you can see the bulk-data-client is a handy tool to test Bulk Data with $export with SMART Backend Services Authorization.

  • IBM FHIR Server: Getting Started Links

    The IBM FHIR Server supports FHIR R4 and is at version 4.10.2.

    The conformance page outlines the features and capabilities of the main build link. The user’s guide outlines the features and configurations link. The module catalog outlines the features link.

    The IBM FHIR Server is delivered in three ways.

    1. A Modular Application
    • Jars published to Maven Central
    • Search for Jars at https://mvnrepository.com/artifact/com.ibm.fhir
    • Javadocs
    1. Docker
    1. A Helm Chart to Install a Working Environment

    If you have more specifics, we can work on diving into the features.