Gerir recursos FHIR através de pacotes FHIR

Esta página explica como gerir recursos FHIR executando pacotes FHIR, que são uma coleção de recursos FHIR e operações a realizar nesses recursos FHIR.

O método ExecuteBundle implementa a interação de lote/transação padrão FHIR (DSTU2, STU3, R4, e R5) e as operações de histórico.

Pacotes FHIR

Um pacote FHIR contém uma matriz de entradas, cada uma das quais representa uma operação, como criar, atualizar ou eliminar, num recurso, como uma observação ou um paciente. Consulte as descrições detalhadas dos elementos no recurso Bundle.

Quando executa um pacote FHIR, o tipo de pacote determina a forma como as operações no pacote são realizadas. Estão disponíveis os seguintes tipos de pacotes:

  • batch: executa as operações como vários pedidos independentes.
  • transaction: executa as operações como vários pedidos que dependem uns dos outros.
  • history: insere as entradas no histórico de um recurso.

Por exemplo, suponhamos que um pacote de transações inclui a criação de um recurso Patient e um recurso Observation. Se o pedido de criação do recurso Patient falhar, o recurso Observation não é criado.

Se uma operação falhar quando o tipo de pacote é batch, a API Cloud Healthcare executa as operações restantes no pacote. Se uma operação falhar quando o tipo de pacote for transaction, a Cloud Healthcare API para de executar operações e reverte a transação.

Pacotes de histórico

Os pacotes de histórico são extensões personalizadas à norma FHIR que suportam exemplos de utilização de cópia de segurança e restauro, como a sincronização. Pode usar history bundles para inserir ou substituir versões de recursos no histórico de um recurso FHIR. Só pode remover versões de recursos através do método Resource-purge. O pacote history é executado como uma única transação com um limite de 100 entradas por pacote. Se uma versão de recurso no pacote history tiver uma data/hora superior à versão mais recente na loja FHIR, a versão mais recente é atualizada em conformidade. Se o pacote history for inserido com êxito, é devolvida uma resposta vazia. Caso contrário, é devolvido um OperationOutcome a descrever a falha.

A compatibilidade com pacotes de histórico não está ativada por predefinição. Um administrador da FHIR store tem de definir enableHistoryModifications como true na configuração da FHIR store. Não pode usar pacotes de histórico se disableResourceVersioning estiver definido como true na configuração da loja FHIR.

Os pacotes de histórico são fornecidos no mesmo formato em que são devolvidos pelo método fhir.history. Para ser válida, cada entrada do pacote requer um ID do recurso, uma indicação de tempo de modificação e um estado. Além disso, todas as entradas têm de ter o mesmo ID do recurso. O ID do recurso é fornecido com o campo resource.id ou o campo request.url. Se forem fornecidos campos, o ID do recurso indicado tem de ser o mesmo. A data/hora do recurso é fornecida com o campo meta.lastUpdated no recurso ou o campo response.lastModified.

Conceder autorizações para executar pacotes

A função de autorização datasets.fhirStores.fhir.executeBundle é necessária para executar pacotes. Para conceder esta autorização, use a função healthcare.fhirResourceReader. Para ver os passos para conceder esta autorização, consulte o artigo Modificar uma política.

Para executar pacotes de histórico, também é necessária a função de autorização datasets.fhirStores.fhir.import.

A API Cloud Healthcare verifica as autorizações de cada operação no pacote. Se tiver a autorização healthcare.fhirResources.create, mas não a autorização healthcare.fhirResources.update, só pode executar pacotes que contenham operações healthcare.fhirResources.create.

Executar um pacote

Para executar um pacote FHIR, use o método projects.locations.datasets.fhirStores.fhir.executeBundle.

Nos exemplos seguintes, BUNDLE.json é o caminho e o nome do ficheiro de um pacote FHIR codificado em JSON. Também pode incluir o pacote no corpo do pedido.

O exemplo de pacote seguinte cria um recurso Patient e elimina outro recurso Patient:

{
  "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"
      }
    }
  ]
}

Os exemplos seguintes mostram como executar um pacote.

curl

Para executar um pacote, faça um pedido POST e especifique as seguintes informações:

  • O nome e a localização do conjunto de dados principal e da loja FHIR
  • A localização do ficheiro do pacote na sua máquina local
  • Uma chave de acesso

O exemplo seguinte mostra um pedido POST através do 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"

Independentemente do resultado das operações individuais, após a execução de um pacote em lote, o servidor devolve uma representação codificada em JSON de um recurso do tipo batch-response.Bundle O recurso Bundle contém uma entrada para cada entrada no pedido com o resultado do processamento da entrada, que pode ser uma combinação de resultados de êxito e de erro.

Se um pacote de transações for bem-sucedido, o servidor devolve uma representação codificada em JSON de um recurso Bundle do tipo transaction-response que contém uma entrada para cada entrada no pedido com o resultado bem-sucedido da operação.

Se ocorrer um erro durante a execução de um pacote de transações, o corpo da resposta não contém um pacote. Em alternativa, contém um recurso codificado em JSON que descreve o motivo do erro.OperationOutcome As operações bem-sucedidas que foram revertidas não são comunicadas na resposta.

O seguinte pacote de exemplo é o resultado da execução bem-sucedida do exemplo acima. A primeira entrada indica o êxito da operação para criar um paciente e inclui o ID do novo recurso. A segunda entrada indica o êxito da operação de eliminação.

{
  "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 executar um pacote, faça um pedido POST e especifique as seguintes informações:

  • O nome e a localização do conjunto de dados principal e da loja FHIR
  • A localização do ficheiro do pacote na sua máquina local
  • Uma chave de acesso

O exemplo seguinte mostra um pedido POST através do 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

Independentemente do resultado das operações individuais, após a execução de um pacote em lote, o servidor devolve uma representação codificada em JSON de um recurso do tipo batch-response.Bundle O recurso Bundle contém uma entrada para cada entrada no pedido com o resultado do processamento da entrada, que pode ser uma combinação de resultados de êxito e de erro.

Se um pacote de transações for bem-sucedido, o servidor devolve uma representação codificada em JSON de um recurso Bundle do tipo transaction-response que contém uma entrada para cada entrada no pedido com o resultado bem-sucedido da operação.

Se ocorrer um erro durante a execução de um pacote de transações, o corpo da resposta não contém um pacote. Em alternativa, contém um recurso codificado em JSON que descreve o motivo do erro.OperationOutcome As operações bem-sucedidas que foram revertidas não são comunicadas na resposta.

O seguinte pacote de exemplo é o resultado da execução bem-sucedida do exemplo acima. A primeira entrada indica o êxito da operação para criar um paciente e inclui o ID do novo recurso. A segunda entrada indica o êxito da operação de eliminação.

{
  "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"

	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: %w", 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: %w", 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: %w", err)
	}
	defer resp.Body.Close()

	respBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("could not read response: %w", 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.gson.GsonFactory;
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 GsonFactory();
  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

Está disponível um ficheiro de pacote de exemplo no repositório do GitHub do exemplo 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

Está disponível um ficheiro de pacote de exemplo no repositório do GitHub do exemplo 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/main/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 = f"{base_url}/projects/{project_id}/locations/{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) 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(f"Executed bundle from file: {bundle}")
    print(json.dumps(resource, indent=2))

    return resource

Fazer um PATCH pedido

Pode usar pacotes FHIR para fazer pedidos PATCH JSON em recursos FHIR. Consulte o artigo Executar um pedido PATCH num pacote FHIR para mais informações.

Resolver referências a recursos criados num pacote

Os recursos num pacote de transações podem conter referências a recursos que não existem no sistema de destino, mas são criados durante a execução do pacote. A API Cloud Healthcare resolve a associação entre os recursos através do campo entry.fullUrl. As referências que correspondem ao valor entry.fullUrl de outro recurso no pacote são reescritas para o ID do recurso correspondente na loja. Esta ação é bem-sucedida independentemente da ordem das operações no pacote.

A Cloud Healthcare API aceita o fullUrl nos seguintes formatos:

  • urn:uuid:UUID
  • urn:oid:OID
  • qualquer URL
  • um nome de recurso no formato RESOURCE_TYPE/RESOURCE_ID, como Patient/123. A utilização deste formato não é recomendada porque fullUrl é um marcador de posição local para o pacote. Isto pode criar confusão se um recurso na loja tiver o mesmo nome, mas o recurso no pacote for resolvido para um nome diferente como resultado de uma operação de criação.

O pacote de exemplo seguinte cria um recurso Patient e um recurso Observation que faz referência ao recurso Patient.

{
  "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"
        }
      }
    }
  ]
}

Os exemplos seguintes mostram como executar um pacote.

curl

Está disponível um ficheiro de pacote de exemplo no repositório do GitHub do exemplo de código.

Para executar um pacote, faça um pedido POST e especifique as seguintes informações:

  • O nome e a localização do conjunto de dados principal e da loja FHIR
  • A localização do ficheiro do pacote no Cloud Storage
  • Uma chave de acesso

O exemplo seguinte mostra um pedido POST através do 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"

O seguinte pacote de exemplo é o resultado da execução bem-sucedida do exemplo acima. A primeira entrada indica o êxito da operação para criar um paciente e inclui o ID do novo recurso. A segunda entrada indica o êxito da operação para criar a Observation e inclui o ID do novo recurso.

{
  "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

Está disponível um ficheiro de pacote de exemplo no repositório do GitHub do exemplo de código.

Para executar um pacote, faça um pedido POST e especifique as seguintes informações:

  • O nome e a localização do conjunto de dados principal e da loja FHIR
  • A localização do ficheiro do pacote no Cloud Storage
  • Uma chave de acesso

O exemplo seguinte mostra um pedido POST através do 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

O seguinte pacote de exemplo é o resultado da execução bem-sucedida do exemplo acima. A primeira entrada indica o êxito da operação para criar um paciente e inclui o ID do novo recurso. A segunda entrada indica o êxito da operação para criar a Observation e inclui o ID do novo recurso.

{
  "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"
}