Administrar recursos de FHIR con paquetes de FHIR

En esta página, se explica cómo administrar recursos de FHIR mediante la ejecución de paquetes de FHIR, que son una colección de recursos y operaciones de FHIR que se realizarán en esos recursos.

El método ExecuteBundle implementa la interacción por lotes/transacción estándar de FHIR (DSTU2, STU3 y R4).

Paquetes de FHIR

Un paquete de FHIR contiene un arreglo de entradas, cada una de las cuales representa una operación, como crear, actualizar o borrar dentro de un recurso, como una observación o un paciente. Consulta las descripciones detalladas de los elementos en el recurso Bundle.

Cuando ejecutas un paquete de FHIR, el tipo de paquete determina cómo se realizan las operaciones dentro de él. Los siguientes tipos de paquetes están disponibles:

  • batch: Ejecuta las operaciones como varias solicitudes independientes.
  • transaction: Ejecuta las operaciones como varias solicitudes que dependen unas de otras.

Por ejemplo, supongamos que un paquete de transacciones incluye la creación de un recurso de paciente y un recurso de observación. Si la solicitud de creación de recursos de pacientes falla, no se crea el recurso de observación.

Si una operación falla cuando el tipo de paquete es batch, la API de Cloud Healthcare ejecuta las operaciones restantes en el paquete. Si una operación falla cuando el tipo de paquete es transaction, la API de Cloud Healthcare deja de ejecutar las operaciones y revierte la transacción.

Otorga permisos para ejecutar paquetes

Se requiere la función de permiso datasets.fhirStores.fhir.executeBundle para ejecutar paquetes. Para otorgar este permiso, usa la función healthcare.fhirResourceReader. Si deseas conocer los pasos para otorgar este permiso, consulta Modifica una política.

La API de Cloud Healthcare verifica los permisos para cada operación del paquete. Si tienes el permiso healthcare.fhirResources.create, pero no el permiso healthcare.fhirResources.update, solo puedes ejecutar paquetes que contengan operaciones healthcare.fhirResources.create.

Ejecuta un paquete

Para ejecutar un paquete de FHIR, usa el método projects.locations.datasets.fhirStores.fhir.executeBundle.

En las siguientes muestras, BUNDLE.json es la ruta y el nombre de archivo de un paquete de FHIR con codificación JSON. También puedes incluir el paquete en el cuerpo de la solicitud.

En el siguiente paquete de muestra, se crea un recurso paciente y se borra otro recurso paciente:

{
  "resourceType": "Bundle",
  "id": "bundle-transaction",
  "meta": {
    "lastUpdated": "2018-03-11T11:22:16Z"
  },
  "type": "transaction",
  "entry": [
    {
      "resource": {
        "resourceType": "Patient",
        "name": [
          {
            "family": "Smith",
            "given": [
              "Darcy"
            ]
          }
        ],
        "gender": "female",
        "address": [
          {
            "line": [
              "123 Main St."
            ],
            "city": "Anycity",
            "state": "CA",
            "postalCode": "12345"
          }
        ]
      },
      "request": {
        "method": "POST",
    "url": "Patient"
      }
    },
    {
      "request": {
        "method": "DELETE",
        "url": "Patient/1234567890"
      }
    }
  ]
}

En los siguientes ejemplos, se muestra cómo ejecutar un paquete.

curl

Para ejecutar un paquete, realiza una solicitud POST y especifica la siguiente información:

  • El nombre y la ubicación del conjunto de datos superior y el almacén de FHIR
  • La ubicación del archivo Paquete en tu máquina local
  • Un token de acceso

En el siguiente ejemplo, se muestra una solicitud POST mediante curl.

curl -X POST \
    -H "Content-Type: application/fhir+json; charset=utf-8" \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    --data @BUNDLE_FILE.json \
    "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir"

Sin importar el resultado de las operaciones individuales, después de ejecutar un paquete de lotes, el servidor muestra una representación codificada en JSON de un recurso Bundle de tipo batch-response. El recurso Bundle contiene una entrada para cada entrada en la solicitud con el resultado del procesamiento de la entrada, que puede ser una combinación de resultados de éxito y error.

Si un paquete de transacciones es exitoso, el servidor muestra una representación codificada en JSON de un recurso Bundle de tipo transaction-response que contiene una entrada por cada entrada en la solicitud con el resultado correcto de la operación.

Si se produce un error mientras se ejecuta un paquete de transacciones, el cuerpo de la respuesta no contiene un paquete. En su lugar, contiene un recurso OperationOutcome codificado en JSON que describe el motivo del error. Las operaciones exitosas que se revirtieron no se informan en la respuesta.

El siguiente paquete de muestra es el resultado de la ejecución correcta del ejemplo anterior. La primera entrada indica el éxito de la operación para crear un Paciente e incluye el ID del nuevo recurso. La segunda entrada indica el éxito de la operación de eliminación.

{
  "entry": [
    {
      "response": {
        "location": projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir/RESOURCE/RESOURCE_ID,
        "status": "201 Created"
      }
    },
    {
      "response": {
        "status": "200 OK"
      }
    }
  ],
  "resourceType": "Bundle",
  "type": "transaction-response"
}

PowerShell

Para ejecutar un paquete, realiza una solicitud POST y especifica la siguiente información:

  • El nombre y la ubicación del conjunto de datos superior y el almacén de FHIR
  • La ubicación del archivo Paquete en tu máquina local
  • Un token de acceso

En el siguiente ejemplo, se muestra una solicitud POST mediante Windows PowerShell.

$cred = gcloud auth application-default print-access-token
$headers = @{ Authorization = "Bearer $cred" }

Invoke-RestMethod `
  -Method Post `
  -Headers $headers `
  -ContentType: "application/fhir+json" `
  -InFile BUNDLE_FILE.json `
  -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir" | ConvertTo-Json

Sin importar el resultado de las operaciones individuales, después de ejecutar un paquete de lotes, el servidor muestra una representación codificada en JSON de un recurso Bundle de tipo batch-response. El recurso Bundle contiene una entrada para cada entrada en la solicitud con el resultado del procesamiento de la entrada, que puede ser una combinación de resultados de éxito y error.

Si un paquete de transacciones es exitoso, el servidor muestra una representación codificada en JSON de un recurso Bundle de tipo transaction-response que contiene una entrada por cada entrada en la solicitud con el resultado correcto de la operación.

Si se produce un error mientras se ejecuta un paquete de transacciones, el cuerpo de la respuesta no contiene un paquete. En su lugar, contiene un recurso OperationOutcome codificado en JSON que describe el motivo del error. Las operaciones exitosas que se revirtieron no se informan en la respuesta.

El siguiente paquete de muestra es el resultado de la ejecución correcta del ejemplo anterior. La primera entrada indica el éxito de la operación para crear un Paciente e incluye el ID del nuevo recurso. La segunda entrada indica el éxito de la operación de eliminación.

{
  "entry": [
    {
      "response": {
        "etag": "ETAG",
        "lastModified": "2020-08-03T04:12:47.312669+00:00",
        "location": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir/RESOURCE/RESOURCE_ID",
        "status": "201 Created"
      }
    },
    {
      "response": {
        "etag": "ETAG",
        "lastModified": "2020-08-03T04:12:47.312669+00:00",
        "status": "200 OK"
      }
    }
  ],
  "resourceType": "Bundle",
  "type": "transaction-response"
}

Go

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"

	healthcare "google.golang.org/api/healthcare/v1"
)

// fhirExecuteBundle executes an FHIR bundle.
func fhirExecuteBundle(w io.Writer, projectID, location, datasetID, fhirStoreID string) error {
	ctx := context.Background()

	healthcareService, err := healthcare.NewService(ctx)
	if err != nil {
		return fmt.Errorf("healthcare.NewService: %v", err)
	}

	fhirService := healthcareService.Projects.Locations.Datasets.FhirStores.Fhir

	payload := map[string]interface{}{
		"resourceType": "Bundle",
		"type":         "transaction",
		"entry": []map[string]interface{}{
			{
				"resource": map[string]interface{}{
					"resourceType": "Patient",
					"active":       true,
				},
				"request": map[string]interface{}{
					"method": "POST",
					"url":    "Patient",
				},
			},
		},
	}
	jsonPayload, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("json.Encode: %v", err)
	}

	parent := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", projectID, location, datasetID, fhirStoreID)

	call := fhirService.ExecuteBundle(parent, bytes.NewReader(jsonPayload))
	call.Header().Set("Content-Type", "application/fhir+json;charset=utf-8")
	resp, err := call.Do()
	if err != nil {
		return fmt.Errorf("ExecuteBundle: %v", err)
	}
	defer resp.Body.Close()

	respBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("could not read response: %v", err)
	}

	if resp.StatusCode > 299 {
		return fmt.Errorf("Create: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
	}
	fmt.Fprintf(w, "%s", respBytes)

	return nil
}

Java

import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.healthcare.v1.CloudHealthcare;
import com.google.api.services.healthcare.v1.CloudHealthcareScopes;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Collections;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;

public class FhirStoreExecuteBundle {
  private static final String FHIR_NAME = "projects/%s/locations/%s/datasets/%s/fhirStores/%s";
  private static final JsonFactory JSON_FACTORY = new JacksonFactory();
  private static final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport();

  public static void fhirStoreExecuteBundle(String fhirStoreName, String data)
      throws IOException, URISyntaxException {
    // String fhirStoreName =
    //    String.format(
    //        FHIR_NAME, "your-project-id", "your-region-id", "your-dataset-id", "your-fhir-id");
    // String data = "{\"resourceType\": \"Bundle\",\"type\": \"batch\",\"entry\": []}"

    // Initialize the client, which will be used to interact with the service.
    CloudHealthcare client = createClient();
    HttpClient httpClient = HttpClients.createDefault();
    String baseUri = String.format("%sv1/%s/fhir", client.getRootUrl(), fhirStoreName);
    URIBuilder uriBuilder = new URIBuilder(baseUri).setParameter("access_token", getAccessToken());
    StringEntity requestEntity = new StringEntity(data);

    HttpUriRequest request =
        RequestBuilder.post()
            .setUri(uriBuilder.build())
            .setEntity(requestEntity)
            .addHeader("Content-Type", "application/fhir+json")
            .addHeader("Accept-Charset", "utf-8")
            .addHeader("Accept", "application/fhir+json; charset=utf-8")
            .build();

    // Execute the request and process the results.
    HttpResponse response = httpClient.execute(request);
    HttpEntity responseEntity = response.getEntity();
    if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
      System.err.print(
          String.format(
              "Exception executing FHIR bundle: %s\n", response.getStatusLine().toString()));
      responseEntity.writeTo(System.err);
      throw new RuntimeException();
    }
    System.out.print("FHIR bundle executed: ");
    responseEntity.writeTo(System.out);
  }

  private static CloudHealthcare createClient() throws IOException {
    // Use Application Default Credentials (ADC) to authenticate the requests
    // For more information see https://cloud.google.com/docs/authentication/production
    GoogleCredentials credential =
        GoogleCredentials.getApplicationDefault()
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));

    // Create a HttpRequestInitializer, which will provide a baseline configuration to all requests.
    HttpRequestInitializer requestInitializer =
        request -> {
          new HttpCredentialsAdapter(credential).initialize(request);
          request.setConnectTimeout(60000); // 1 minute connect timeout
          request.setReadTimeout(60000); // 1 minute read timeout
        };

    // Build the client for interacting with the service.
    return new CloudHealthcare.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer)
        .setApplicationName("your-application-name")
        .build();
  }

  private static String getAccessToken() throws IOException {
    GoogleCredentials credential =
        GoogleCredentials.getApplicationDefault()
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));

    return credential.refreshAccessToken().getTokenValue();
  }
}

Node.js

Hay un archivo de paquete de muestra disponible en el repositorio de GitHub de muestra de código.

const google = require('@googleapis/healthcare');
const healthcare = google.healthcare({
  version: 'v1',
  auth: new google.auth.GoogleAuth({
    scopes: ['https://www.googleapis.com/auth/cloud-platform'],
  }),
  headers: {'Content-Type': 'application/fhir+json'},
});
const fs = require('fs');

async function executeFhirBundle() {
  // TODO(developer): uncomment these lines before running the sample
  // const cloudRegion = 'us-central1';
  // const projectId = 'adjective-noun-123';
  // const datasetId = 'my-dataset';
  // const fhirStoreId = 'my-fhir-store';
  // const bundleFile = 'bundle.json';
  const parent = `projects/${projectId}/locations/${cloudRegion}/datasets/${datasetId}/fhirStores/${fhirStoreId}`;

  const bundle = JSON.parse(fs.readFileSync(bundleFile));

  const request = {parent, requestBody: bundle};
  const resource =
    await healthcare.projects.locations.datasets.fhirStores.fhir.executeBundle(
      request
    );
  console.log('FHIR bundle executed');
  console.log(resource.data);
}

executeFhirBundle();

Python

Hay un archivo de paquete de muestra disponible en el repositorio de GitHub de muestra de código.

def execute_bundle(
    project_id,
    location,
    dataset_id,
    fhir_store_id,
    bundle,
):
    """Executes the operations in the given bundle.

    See https://github.com/GoogleCloudPlatform/python-docs-samples/tree/master/healthcare/api-client/v1/fhir
    before running the sample."""
    # Imports Python's built-in "os" module
    import os

    # Imports the google.auth.transport.requests transport
    from google.auth.transport import requests

    # Imports a module to allow authentication using a service account
    from google.oauth2 import service_account

    # Gets credentials from the environment.
    credentials = service_account.Credentials.from_service_account_file(
        os.environ["GOOGLE_APPLICATION_CREDENTIALS"]
    )
    scoped_credentials = credentials.with_scopes(
        ["https://www.googleapis.com/auth/cloud-platform"]
    )
    # Creates a requests Session object with the credentials.
    session = requests.AuthorizedSession(scoped_credentials)

    # URL to the Cloud Healthcare API endpoint and version
    base_url = "https://healthcare.googleapis.com/v1"

    # TODO(developer): Uncomment these lines and replace with your values.
    # project_id = 'my-project'  # replace with your GCP project ID
    # location = 'us-central1'  # replace with the parent dataset's location
    # dataset_id = 'my-dataset'  # replace with the parent dataset's ID
    # fhir_store_id = 'my-fhir-store' # replace with the FHIR store ID
    # bundle = 'bundle.json'  # replace with the bundle file
    url = "{}/projects/{}/locations/{}".format(base_url, project_id, location)

    resource_path = "{}/datasets/{}/fhirStores/{}/fhir".format(
        url, dataset_id, fhir_store_id
    )

    headers = {"Content-Type": "application/fhir+json;charset=utf-8"}

    with open(bundle, "r") as bundle_file:
        bundle_file_content = bundle_file.read()

    response = session.post(resource_path, headers=headers, data=bundle_file_content)
    response.raise_for_status()

    resource = response.json()

    print("Executed bundle from file: {}".format(bundle))
    print(json.dumps(resource, indent=2))

    return resource

Resuelve referencias a recursos creados en un paquete

Los recursos de un paquete de transacciones pueden contener referencias a recursos que no existen en el sistema de destino, pero que se crean durante la ejecución del paquete. La API de Cloud Healthcare resuelve la asociación entre los recursos mediante el campo entry.fullUrl. Las referencias que coinciden con el valor entry.fullUrl de otro recurso del paquete se vuelven a escribir con el ID del recurso correspondiente en el almacén. Esto tiene éxito sin importar el orden de las operaciones en el paquete.

La API de Cloud Healthcare acepta la fullUrl en los siguientes formatos:

  • urn:uuid:UUID
  • urn:oid:OID
  • cualquier URL
  • un nombre de recurso en el formato RESOURCE_TYPE/RESOURCE_ID, como Patient/123 No se recomienda usar este formato porque el fullUrl es un marcador de posición local del paquete. Esto puede crear confusión si un recurso del almacén tiene el mismo nombre, pero el recurso del paquete obtiene un nombre diferente como resultado de una operación de creación.

En el siguiente conjunto de muestras, se crea un recurso de paciente y un recurso de observación que hace referencia al recurso de paciente.

{
  "resourceType": "Bundle",
  "type": "transaction",
  "entry":[
    {
      "request": {
        "method":"POST",
        "url":"Patient"
      },
      "fullUrl": "urn:uuid:05efabf0-4be2-4561-91ce-51548425acb9",
      "resource": {
        "resourceType":"Patient",
        "gender":"male"
      }
    },
    {
      "request": {
        "method":"POST",
        "url":"Observation"
      },
      "resource": {
        "resourceType":"Observation",
        "subject": {
          "reference": "urn:uuid:05efabf0-4be2-4561-91ce-51548425acb9"
        },
        "status":"preliminary",
        "code": {
          "text":"heart rate"
        }
      }
    }
  ]
}

En los siguientes ejemplos, se muestra cómo ejecutar un paquete.

curl

Hay un archivo de paquete de muestra disponible en el repositorio de GitHub de muestra de código.

Para ejecutar un paquete, realiza una solicitud POST y especifica la siguiente información:

  • El nombre y la ubicación del conjunto de datos superior y el almacén de FHIR
  • La ubicación del archivo Paquete en Cloud Storage
  • Un token de acceso

En el siguiente ejemplo, se muestra una solicitud POST mediante curl.

curl -X POST \
    -H "Content-Type: application/fhir+json; charset=utf-8" \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    --data @BUNDLE_FILE.json \
    "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir"

El siguiente paquete de muestra es el resultado de la ejecución correcta del ejemplo anterior. La primera entrada indica el éxito de la operación para crear un Paciente e incluye el ID del nuevo recurso. La segunda entrada indica el éxito de la operación para crear la observación y, además, incluye el ID del recurso nuevo.

{
  "entry": [
    {
      "response": {
        "etag": "ETAG1",
        "lastModified": "2020-08-04T16:14:14.273976+00:00",
        "location": "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/REGION/datasets/REGION/fhirStores/FHIR_STORE_ID/fhir/Patient/PATIENT_ID/_history/HISTORY_ID",
        "status": "201 Created"
      }
    },
    {
      "response": {
        "etag": "ETAG",
        "lastModified": "2020-08-04T16:14:14.273976+00:00",
        "location": "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/REGION/datasets/REGION/fhirStores/FHIR_STORE_ID/fhir/Observation/OBSERVATION_ID/_history/HISTORY_ID",
        "status": "201 Created"
      }
    }
  ],
  "resourceType": "Bundle",
  "type": "transaction-response"
}

PowerShell

Hay un archivo de paquete de muestra disponible en el repositorio de GitHub de muestra de código.

Para ejecutar un paquete, realiza una solicitud POST y especifica la siguiente información:

  • El nombre y la ubicación del conjunto de datos superior y el almacén de FHIR
  • La ubicación del archivo Paquete en Cloud Storage
  • Un token de acceso

En el siguiente ejemplo, se muestra una solicitud POST mediante Windows PowerShell.

$cred = gcloud auth application-default print-access-token
$headers = @{ Authorization = "Bearer $cred" }

Invoke-RestMethod `
  -Method Post `
  -Headers $headers `
  -ContentType: "application/fhir+json" `
  -InFile BUNDLE_FILE.json `
  -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir" | ConvertTo-Json

El siguiente paquete de muestra es el resultado de la ejecución correcta del ejemplo anterior. La primera entrada indica el éxito de la operación para crear un Paciente e incluye el ID del nuevo recurso. La segunda entrada indica el éxito de la operación para crear la observación y, además, incluye el ID del recurso nuevo.

{
  "entry": [
    {
      "response": {
        "etag": "ETAG1",
        "lastModified": "2020-08-04T16:14:14.273976+00:00",
        "location": "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/REGION/datasets/REGION/fhirStores/FHIR_STORE_ID/fhir/Patient/PATIENT_ID/_history/HISTORY_ID",
        "status": "201 Created"
      }
    },
    {
      "response": {
        "etag": "ETAG",
        "lastModified": "2020-08-04T16:14:14.273976+00:00",
        "location": "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/REGION/datasets/REGION/fhirStores/FHIR_STORE_ID/fhir/Observation/OBSERVATION_ID/_history/HISTORY_ID",
        "status": "201 Created"
      }
    }
  ],
  "resourceType": "Bundle",
  "type": "transaction-response"
}