Como gerenciar recursos FHIR com os pacotes FHIR

Veja nesta página como gerenciar recursos FHIR executando pacotes FHIR, que são um conjunto de recursos e operações FHIR a serem executados nesses recursos FHIR.

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

Pacotes FHIR

Um pacote FHIR contém uma matriz de entradas, cada uma representando uma operação, como criar, atualizar ou excluir, em um recurso, como uma observação ou um paciente. Veja as descrições detalhadas dos elementos no recurso pacote.

Quando você executa um pacote FHIR, o tipo de pacote determina como as operações são realizadas no pacote. Os seguintes tipos de pacotes estão disponíveis:

  • batch: executa as operações como várias solicitações independentes.
  • transaction: executa as operações como várias solicitações que dependem umas das outras.
  • history: insere as entradas no histórico de um recurso.

Por exemplo, suponha que um pacote de transações inclua a criação de um recurso Paciente e um recurso Observação. Se a solicitação de criação do recurso Paciente falhar, o recurso Observação não será criado.

Se uma operação falhar quando o tipo de pacote for batch, a API Cloud Healthcare executará as operações restantes no pacote. Se uma operação falhar quando o tipo de pacote for transaction, a API Cloud Healthcare interromperá a execução de operações e reverterá a transação.

Pacotes de história

Os pacotes de histórico são extensões personalizadas do padrão FHIR que oferecem suporte a casos de uso de backup e restauração, como sincronização. É possível usar pacotes de histórico para inserir ou substituir versões de recursos no histórico de um recurso do FHIR. Só é possível remover versões de recursos usando o 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 um carimbo de data/hora maior do que a versão mais recente na loja FHIR, a versão mais recente será atualizada de acordo. Se o pacote history for inserido com sucesso, uma resposta vazia será retornada. Caso contrário, um OperationOutcome será retornado descrevendo a falha.

O suporte para pacotes de histórico não está ativado por padrão. Um administrador do repositório FHIR precisa definir enableHistoryModifications como true na Configuração do repositório FHIR. Não é possível usar pacotes de histórico se disableResourceVersioning estiver definido como true na configuração do armazenamento FHIR.

Os pacotes de histórico são fornecidos no mesmo formato em que são retornados do método fhir.history. Para ser válida, cada entrada de pacote precisa de um ID de recurso, um carimbo de data/hora de modificação e um status. Além disso, todas as entradas precisam ter o mesmo ID de recurso. O ID do recurso é fornecido com o campo resource.id ou request.url. Se campos forem fornecidos, o ID do recurso fornecido será o mesmo. O carimbo de data/hora do recurso é fornecido com o campo meta.lastUpdated no recurso ou no campo response.lastModified.

Como conceder permissões para executar pacotes

Para executar pacotes é necessário o papel de permissão datasets.fhirStores.fhir.executeBundle. Para conceder essa permissão, use o papel healthcare.fhirResourceReader. Para as etapas para conceder essa permissão, consulte Como modificar uma política.

Para executar pacotes de histórico, também é necessário o papel de permissão datasets.fhirStores.fhir.import.

A API Cloud Healthcare verifica as permissões para cada operação no pacote. Com a permissão healthcare.fhirResources.create, mas sem a permissão healthcare.fhirResources.update, é possível executar pacotes contendo apenas operações healthcare.fhirResources.create.

Como executar um pacote

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

Nos exemplos a seguir, BUNDLE.Json é o caminho e o nome de arquivo para um pacote FHIR codificado em JSON. Também é possível incluir o pacote no corpo da solicitação.

O pacote de amostra a seguir cria um recurso de paciente e exclui outro recurso de 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"
      }
    }
  ]
}

Os exemplos a seguir mostram como executar um pacote.

curl

Para executar um pacote, faça uma solicitação POST e especifique as seguintes informações:

  • O nome e o local do conjunto de dados pai e do armazenamento de FHIR
  • O local do arquivo de pacote na máquina local
  • Um token de acesso

O exemplo a seguir mostra uma solicitação POST usando 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, depois de executar um pacote em lote, o servidor retorna uma representação codificada em JSON de um recurso Bundle do tipo batch-response. O recurso Bundle contém uma entrada para cada entrada na solicitação com o resultado do processamento da entrada, o que pode ser uma combinação de resultados de erro e sucesso.

Se um pacote de transações for bem-sucedido, o servidor retornará uma representação codificada em JSON de um recurso Bundle do tipo transaction-response contendo uma entrada para cada entrada na solicitação 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 conterá um pacote. Em vez disso, ele conterá um recurso OperationOutcome codificado em JSON que descreverá o motivo do erro. As operações bem-sucedidas que foram revertidas não serão informadas na resposta.

O exemplo de pacote a seguir é a saída da execução bem-sucedida do exemplo acima. A primeira entrada indica o sucesso da operação para criar um paciente e inclui o código do novo recurso. A segunda entrada indica o sucesso da operação de exclusã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 uma solicitação POST e especifique as seguintes informações:

  • O nome e o local do conjunto de dados pai e do armazenamento de FHIR
  • O local do arquivo de pacote na máquina local
  • Um token de acesso

O exemplo a seguir mostra uma solicitação POST usando o 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, depois de executar um pacote em lote, o servidor retorna uma representação codificada em JSON de um recurso Bundle do tipo batch-response. O recurso Bundle contém uma entrada para cada entrada na solicitação com o resultado do processamento da entrada, o que pode ser uma combinação de resultados de erro e sucesso.

Se um pacote de transações for bem-sucedido, o servidor retornará uma representação codificada em JSON de um recurso Bundle do tipo transaction-response contendo uma entrada para cada entrada na solicitação 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 conterá um pacote. Em vez disso, ele conterá um recurso OperationOutcome codificado em JSON que descreverá o motivo do erro. As operações bem-sucedidas que foram revertidas não serão informadas na resposta.

O exemplo de pacote a seguir é a saída da execução bem-sucedida do exemplo acima. A primeira entrada indica o sucesso da operação para criar um paciente e inclui o código do novo recurso. A segunda entrada indica o sucesso da operação de exclusã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

Um arquivo de pacote de amostra está disponível no repositório do GitHub da 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

Um arquivo de pacote de amostra está disponível no repositório do GitHub da 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

Faça uma solicitação PATCH

É possível usar pacotes FHIR para fazer solicitações JSON PATCH em recursos FHIR. Consulte Como executar uma solicitação PATCH em um pacote do FHIR para mais informações.

Como resolver referências a recursos criados em um pacote

Os recursos em um 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 usando o campo entry.fullUrl. As referências que correspondem ao valor entry.fullUrl de outro recurso no pacote são regravadas no código do recurso correspondente no armazenamento. Isso será bem-sucedido, independentemente da ordem das operações no pacote.

A API Cloud Healthcare aceita 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 O uso desse formato não é recomendado porque fullUrl é um marcador local para o pacote. Isso pode criar confusão se um recurso no armazenamento 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 amostra a seguir cria um recurso paciente e um recurso observação que se refere ao 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"
        }
      }
    }
  ]
}

Os exemplos a seguir mostram como executar um pacote.

curl

Um arquivo de pacote de amostra está disponível no repositório do GitHub da exemplo de código.

Para executar um pacote, faça uma solicitação POST e especifique as seguintes informações:

  • O nome e o local do conjunto de dados pai e do armazenamento de FHIR
  • O local do arquivo do pacote no Cloud Storage
  • Um token de acesso

O exemplo a seguir mostra uma solicitação POST usando 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 exemplo de pacote a seguir é a saída da execução bem-sucedida do exemplo acima. A primeira entrada indica o sucesso da operação para criar um paciente e inclui o código do novo recurso. A segunda entrada indica o sucesso da operação de criação da observação e inclui o código 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

Um arquivo de pacote de amostra está disponível no repositório do GitHub da exemplo de código.

Para executar um pacote, faça uma solicitação POST e especifique as seguintes informações:

  • O nome e o local do conjunto de dados pai e do armazenamento de FHIR
  • O local do arquivo do pacote no Cloud Storage
  • Um token de acesso

O exemplo a seguir mostra uma solicitação POST usando o 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 exemplo de pacote a seguir é a saída da execução bem-sucedida do exemplo acima. A primeira entrada indica o sucesso da operação para criar um paciente e inclui o código do novo recurso. A segunda entrada indica o sucesso da operação de criação da observação e inclui o código 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"
}