Friday, January 1, 2021

Google Cloud Healthcare - Clinic Simulation

Summary

In this post, I'll demonstrate the usage of the Google Cloud Healthcare API via a contrived scenario.  The simulation here is a clinic in Colorado that is taking on new patients.  Each patient brings with them a health history that is contained in electronic health records in an FHIR format.  These FHIR records are pushed into the Healthcare API and then available for search, analytics, etc.  I'll be using Synthea to generate artificial records. 

Scenario

Below is a depiction of the simulation at a logical level.



Architecture

Below is the overall Google Cloud architecture.  FHIR records are generated from Synthea and then uploaded to Cloud Storage.  Google Cloud Scheduler is used to simulate a stream of patients arriving at the clinic.  Cloud Scheduler triggers a Cloud Function via Pub/Sub.  That function pulls down a random number of FHIR patient transaction bundles and then executes them to load them into Cloud Healthcare.


Patient Record Generation

As mentioned previously, I'm using Synthea to generate FHIR records.  There are three (3) releases of the FHIR standard 'supported' by the Healthcare API.  Based on my experience here, Google is not supporting the latest release (R4) adequately.  There were enough issues with Synthea-generated R4 bundles with Google that I gave up on troubleshooting them and reverted to the previous FHIR rev - STU3.  Those work.

Below is the config I used for this simulation.  This yields STU3 FHIR resource bundles with all living patients.  The default Synthea config will generate a random number of deceased patients.  In the spirit of this simulation, it's not likely any deceased patients will be admitting themselves to the clinic.
generate.only_alive_patients = true

exporter.fhir.transaction_bundle = true
exporter.fhir.export = false
exporter.fhir_stu3.export = true
exporter.fhir_dstu2.export = false

exporter.hospital.fhir.export = false
exporter.hospital.fhir_stu3.export = true
exporter.hospital.fhir_dstu2.export = false

exporter.practitioner.fhir.export = false
exporter.practitioner.fhir_stu3.export = true
exporter.practitioner.fhir_dstu2.export = false
Below is the command line I used (Developer) and a snippet of the output.  I generated ten living patients in Colorado.

./run_synthea -p 10 Colorado -c ./config.txt

Population: 10
Seed: 1609530634098
Provider Seed:1609530634098
Reference Time: 1609530634098
Location: Colorado
Min Age: 0
Max Age: 140
5 -- Bradford382 Marks830 (1 y/o M) Security-Widefield, Colorado 
4 -- Laveta191 Becker968 (2 y/o F) Denver, Colorado 
2 -- Valene773 Shanahan202 (17 y/o F) Denver, Colorado 
6 -- Valrie435 Cruickshank494 (47 y/o F) Highlands Ranch, Colorado 
1 -- Chance908 Murray856 (56 y/o M) Pueblo, Colorado 
3 -- Lashawnda573 Hettinger594 (70 y/o F) Thornton, Colorado 
8 -- Shin962 Bahringer146 (69 y/o F) Denver, Colorado 
7 -- Johnie961 Padberg411 (68 y/o M) Thornton, Colorado 
10 -- Bridget51 Leffler128 (24 y/o F) Louisville, Colorado 
9 -- Carlos172 González124 (55 y/o M) Denver, Colorado 
Records: total=10, alive=10, dead=0

Google Cloud Configuration

Screenshots below of the various upfront configuration activities that were necessary for this simulation:

Cloud Storage



Cloud Healthcare





Cloud Pub/Sub



Cloud Scheduler


Cloud Function


Hospital and Practitioner Upload

Synthea generates three types of FHIR transaction bundles for a given synthetic load:  Patient, Hospital, and Practitioner.  Patient resources reference Hospital and Practitioner resources.  Practitioner resources reference Hospital resources.  Below are the CURL commands I used to upload the Hospital and Practitioner bundles.  Note that I'm using 'v1beta1' of the Healthcare API.  V1 doesn't handle conditional references properly.  The Synthea bundles have conditional references in them.

curl -X POST \
    -H "Content-Type: application/fhir+json; charset=utf-8" \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    --data @hospitalInformation1609530634625.json \
    "https://healthcare.googleapis.com/v1beta1/projects/clinic-simulation/locations/us-central1/datasets/clinicset/fhirStores/clinicSTU3/fhir"


curl -X POST \
    -H "Content-Type: application/fhir+json; charset=utf-8" \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    --data @practitionerInformation1609530634625.json \
    "https://healthcare.googleapis.com/v1beta1/projects/clinic-simulation/locations/us-central1/datasets/clinicset/fhirStores/clinicSTU3/fhir"

Code Snippets

Cloud Function for Executing Transaction Bundles


/**
 * Reads a FHIR bundle from GCS and then loads it to a Google Healthcare FHIR store
 * @param {File} file - GCS File object
 * @return {Promise} 
 * @throws {Error} propagates exceptions
 */
async function loadBundle(file) {   
    const auth = await google.auth.getClient({
        scopes: ['https://www.googleapis.com/auth/cloud-platform'],
    });
    google.options({
        auth, 
        headers: {
            'Content-Type': 'application/fhir+json'
        }
    });

    let bundle = '';
    return new Promise((resolve, reject) => {
        file.createReadStream()
        .on('data', (chunk) => {
            bundle += chunk;
        })
        .on('end', async () => {
            const request = { 
                parent: BASE_URL,
                type: 'Bundle',
                requestBody: JSON.parse(bundle),
            };
            let response;

            try {
                response = await healthcare.projects.locations.datasets.fhirStores.fhir.executeBundle(request);
                resolve(response.statusText);
            }
            catch(err) {
                reject(err);
            }
        })
        .on('error', (err) => {
            reject(err);
        });
    });
}

Search Function

Code below searches the FHIR store for male patients over age 60.

async function search() { 
    const auth = await google.auth.getClient({
        scopes: ['https://www.googleapis.com/auth/cloud-platform'],
    });
    google.options({
        auth, 
        params: {
            gender:'male',
            birthdate: 'lt1961-01-01'
        }
    });
  
    const request = { 
        parent: BASE_URL, 
        resourceType : 'Patient'
    };
    const response = await healthcare.projects.locations.datasets.fhirStores.fhir.search(request);
    
    return response.data.entry;
}

Execution

Log snippet below for the Generator function executing patient transaction bundles from Cloud Storage.


The abbreviated output below from the search function mentioned previously.  Recall there was only 1 male patient over 60 in the ten patients that were loaded.

     "name": [
        {
          "family": "Padberg411",
          "given": [
            "Johnie961"
          ],
          "prefix": [
            "Mr."
          ],
          "use": "official"
        }
      ],
      "resourceType": "Patient",


Result count: 1

Source


Copyright ©1993-2024 Joey E Whelan, All rights reserved.