5 · Resultado do exame (Bundle transaction)¶
Sistema: Lab+ (Laboratório) · Recursos: Observation ×4, DiagnosticReport, ServiceRequest (atualização)
O laudo do hemograma envolve seis operações: criar as 4 Observation
(uma por analito), criar o DiagnosticReport que as referencia e concluir o
ServiceRequest do pedido. O Lab+ envia tudo em um único POST com um
Bundle do tipo transaction: o servidor aplica as seis operações
atomicamente — ou tudo entra, ou nada entra.
Gravação — POST / (raiz) com Bundle transaction¶
curl -X POST 'http://localhost:8080/fhir' \
-H 'Content-Type: application/fhir+json' \
-d @resultado.json
Pontos-chave do bundle:
- Cada
Observationrecebe umfullUrltemporáriourn:uuid:…; - O
DiagnosticReport.resultreferencia essesurn:uuid— o servidor reescreve as referências para os ids definitivos (Observation/91, …); - O
basedOnaponta oServiceRequestjá existente, por referência literal; - A última entry é um
PUTque conclui o pedido (status: completed), preservando ameta.tagoriginal do iSUS APS (quem atualiza recurso alheio não troca a tag do criador).
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"fullUrl": "urn:uuid:c1f4a6d0-93b1-4f52-8a41-000000000001",
"resource": {
"resourceType": "Observation",
"status": "final",
"category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "Laboratory"}]}],
"code": {
"coding": [{"system": "http://loinc.org", "code": "718-7", "display": "Hemoglobina"}],
"text": "Hemoglobina"
},
"subject": {"reference": "Patient/42"},
"effectiveDateTime": "2026-09-10T10:12:00-03:00",
"valueQuantity": {"value": 14.2, "unit": "g/dL", "system": "http://unitsofmeasure.org", "code": "g/dL"},
"referenceRange": [{
"low": {"value": 13.2, "unit": "g/dL", "system": "http://unitsofmeasure.org", "code": "g/dL"},
"high": {"value": 16.6, "unit": "g/dL", "system": "http://unitsofmeasure.org", "code": "g/dL"}
}],
"meta": {"tag": [{"system": "https://demo-cbis.example/fhir/source-system", "code": "labmais", "display": "Lab+"}]}
},
"request": {"method": "POST", "url": "Observation"}
},
{
"fullUrl": "urn:uuid:c1f4a6d0-93b1-4f52-8a41-000000000002",
"resource": {
"resourceType": "Observation",
"status": "final",
"category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "Laboratory"}]}],
"code": {
"coding": [{"system": "http://loinc.org", "code": "4544-3", "display": "Hematócrito"}],
"text": "Hematócrito"
},
"subject": {"reference": "Patient/42"},
"effectiveDateTime": "2026-09-10T10:12:00-03:00",
"valueQuantity": {"value": 48.0, "unit": "%", "system": "http://unitsofmeasure.org", "code": "%"},
"referenceRange": [{
"low": {"value": 38.8, "unit": "%", "system": "http://unitsofmeasure.org", "code": "%"},
"high": {"value": 50.0, "unit": "%", "system": "http://unitsofmeasure.org", "code": "%"}
}],
"meta": {"tag": [{"system": "https://demo-cbis.example/fhir/source-system", "code": "labmais", "display": "Lab+"}]}
},
"request": {"method": "POST", "url": "Observation"}
},
{
"fullUrl": "urn:uuid:c1f4a6d0-93b1-4f52-8a41-000000000003",
"resource": {
"resourceType": "Observation",
"status": "final",
"category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "Laboratory"}]}],
"code": {
"coding": [{"system": "http://loinc.org", "code": "6690-2", "display": "Leucócitos"}],
"text": "Leucócitos"
},
"subject": {"reference": "Patient/42"},
"effectiveDateTime": "2026-09-10T10:12:00-03:00",
"valueQuantity": {"value": 2.1, "unit": "10*3/uL", "system": "http://unitsofmeasure.org", "code": "10*3/uL"},
"referenceRange": [{
"low": {"value": 4.0, "unit": "10*3/uL", "system": "http://unitsofmeasure.org", "code": "10*3/uL"},
"high": {"value": 11.0, "unit": "10*3/uL", "system": "http://unitsofmeasure.org", "code": "10*3/uL"}
}],
"interpretation": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", "code": "L", "display": "Low"}]}],
"meta": {"tag": [{"system": "https://demo-cbis.example/fhir/source-system", "code": "labmais", "display": "Lab+"}]}
},
"request": {"method": "POST", "url": "Observation"}
},
{
"fullUrl": "urn:uuid:c1f4a6d0-93b1-4f52-8a41-000000000004",
"resource": {
"resourceType": "Observation",
"status": "final",
"category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "Laboratory"}]}],
"code": {
"coding": [{"system": "http://loinc.org", "code": "777-3", "display": "Plaquetas"}],
"text": "Plaquetas"
},
"subject": {"reference": "Patient/42"},
"effectiveDateTime": "2026-09-10T10:12:00-03:00",
"valueQuantity": {"value": 42.0, "unit": "10*3/uL", "system": "http://unitsofmeasure.org", "code": "10*3/uL"},
"referenceRange": [{
"low": {"value": 150.0, "unit": "10*3/uL", "system": "http://unitsofmeasure.org", "code": "10*3/uL"},
"high": {"value": 450.0, "unit": "10*3/uL", "system": "http://unitsofmeasure.org", "code": "10*3/uL"}
}],
"interpretation": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", "code": "L", "display": "Low"}]}],
"meta": {"tag": [{"system": "https://demo-cbis.example/fhir/source-system", "code": "labmais", "display": "Lab+"}]}
},
"request": {"method": "POST", "url": "Observation"}
},
{
"fullUrl": "urn:uuid:c1f4a6d0-93b1-4f52-8a41-000000000005",
"resource": {
"resourceType": "DiagnosticReport",
"status": "final",
"category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/v2-0074", "code": "LAB", "display": "Laboratory"}]}],
"code": {
"coding": [{"system": "http://loinc.org", "code": "58410-2", "display": "Hemograma completo"}],
"text": "Hemograma completo"
},
"subject": {"reference": "Patient/42"},
"basedOn": [{"reference": "ServiceRequest/10"}],
"effectiveDateTime": "2026-09-10T10:12:00-03:00",
"issued": "2026-09-10T10:12:00-03:00",
"performer": [{"display": "Lab+ Análises Clínicas"}],
"result": [
{"reference": "urn:uuid:c1f4a6d0-93b1-4f52-8a41-000000000001", "display": "Hemoglobina"},
{"reference": "urn:uuid:c1f4a6d0-93b1-4f52-8a41-000000000002", "display": "Hematócrito"},
{"reference": "urn:uuid:c1f4a6d0-93b1-4f52-8a41-000000000003", "display": "Leucócitos"},
{"reference": "urn:uuid:c1f4a6d0-93b1-4f52-8a41-000000000004", "display": "Plaquetas"}
],
"conclusion": "Plaquetopenia acentuada, leucopenia e hemoconcentração — achados compatíveis com dengue grave.",
"meta": {"tag": [{"system": "https://demo-cbis.example/fhir/source-system", "code": "labmais", "display": "Lab+"}]}
},
"request": {"method": "POST", "url": "DiagnosticReport"}
},
{
"resource": {
"resourceType": "ServiceRequest",
"id": "10",
"status": "completed",
"intent": "order",
"category": [{"coding": [{"system": "https://demo-cbis.example/fhir/category", "code": "laboratorio", "display": "Exame laboratorial"}]}],
"code": {
"coding": [{"system": "http://loinc.org", "code": "58410-2", "display": "Hemograma completo"}],
"text": "Hemograma completo"
},
"subject": {"reference": "Patient/42"},
"encounter": {"reference": "Encounter/7"},
"authoredOn": "2026-09-10T09:35:00-03:00",
"requester": {"display": "UBS Vila Esperança — iSUS APS"},
"meta": {"tag": [{"system": "https://demo-cbis.example/fhir/source-system", "code": "isus-aps", "display": "iSUS APS"}]}
},
"request": {"method": "PUT", "url": "ServiceRequest/10"}
}
]
}
Resposta — Bundle transaction-response¶
O servidor responde uma entry por operação, na mesma ordem do envio
(exigência do FHIR R4), com o location de cada recurso:
{
"resourceType": "Bundle",
"type": "transaction-response",
"entry": [
{"response": {"status": "201 Created", "location": "Observation/91/_history/1"}},
{"response": {"status": "201 Created", "location": "Observation/92/_history/1"}},
{"response": {"status": "201 Created", "location": "Observation/93/_history/1"}},
{"response": {"status": "201 Created", "location": "Observation/94/_history/1"}},
{"response": {"status": "201 Created", "location": "DiagnosticReport/95/_history/1"}},
{"response": {"status": "200 OK", "location": "ServiceRequest/10/_history/2"}}
]
}
Ao gravar, o servidor reescreveu o result do laudo: quem ler
DiagnosticReport/95 encontrará {"reference": "Observation/91", "display":
"Hemoglobina"}, e assim por diante. Se qualquer entry falhar (validação,
conflito), nenhum recurso é gravado — o pedido segue active na worklist
e o laboratório pode tentar de novo.
Leitura — laudos emitidos¶
O painel "Laudos emitidos" do Lab+ filtra pela tag de origem:
curl 'http://localhost:8080/fhir/DiagnosticReport?_tag=https://demo-cbis.example/fhir/source-system|labmais&_include=DiagnosticReport:subject&_sort=-issued'
O iSUS APS enxerga o mesmo laudo pela busca por paciente (passo 3), casando
basedOn → ServiceRequest/10. A vigilância (CIMAN, passo 6) busca o laudo
mais recente do paciente.
Destino alternativo — barramento do Showcase SBIS/IPES¶
Além de gravar neste showcase, o Lab+ pode publicar o mesmo laudo
estruturado no barramento do Showcase de Interoperabilidade 2026
(SBIS/IPES) — um ambiente externo, mantido pela SBIS/IPES, que valida o
Bundle contra o perfil Laboratory do IG do IPES. O campo Destino do
laudo do formulário (as três opções ficam sempre habilitadas) decide o
que acontece:
| Destino | O que é gravado | Situação do pedido |
|---|---|---|
| Este showcase (padrão) | Bundle transaction no HAPI local, como nas seções acima |
Conclui (status: completed) |
| Barramento SBIS/IPES | Bundle transaction só no barramento SBIS/IPES — nada é gravado no HAPI local |
Continua active na worklist |
| Ambos | Envia ao SBIS/IPES e, confirmado, grava também neste showcase | Conclui se as duas gravações derem certo; se só o SBIS for confirmado, continua active |
O destino Este showcase nunca chama o barramento SBIS.
Antes de enviar, o Lab+ obtém um token OAuth2 e confirma que o barramento reconhece o paciente:
# 1. Token OAuth2 (client_credentials, Basic com clientId:clientSecret)
curl -X POST 'https://showcase-interop.sbis.org.br/token' \
-H 'Authorization: Basic <base64(clientId:clientSecret)>' \
-d 'grant_type=client_credentials'
# 2. Pré-checagem: o MPI do barramento conhece este CPF?
curl 'https://showcase-interop.sbis.org.br/mpi/fhir/1.0.1/Patient/2.16.840.1.113883.13.237-<cpf>' \
-H 'Authorization: Bearer <token>'
A pré-checagem é uma regra do próprio barramento, não do Lab+: o MPI do
SBIS recusa CPFs que não reconhece — 404 com OperationOutcome, o
caso do paciente de demonstração desta jornada (CPF 00000000000) — mas
cria na hora o Patient de qualquer CPF válido (200), mesmo que ele
nunca tenha sido cadastrado antes. Quando o MPI recusa, o Lab+ interrompe o
envio (nada é gravado) e pede para refazer com um paciente de CPF válido,
ou publicar só neste showcase.
Use somente o CPF de teste — nunca o de uma pessoa real
Para demonstrar este destino, cadastre no iSUS um paciente com o CPF de
teste do participante, 75124076100 (veja o README.md da raiz do
repositório). Nunca use o CPF de uma pessoa real: como o MPI
materializa qualquer CPF válido na hora, o laudo fictício ficaria
associado a essa pessoa real no ambiente do SBIS, com as credenciais da
SES-GO.
Confirmado o paciente, o Lab+ envia o Bundle:
curl -X POST 'https://showcase-interop.sbis.org.br/ehrposter/fhir/1.0.0' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/fhir+json' \
-d @resultado-sbis.json
Esse Bundle transaction, marcado com o perfil Laboratory do IG do IPES
(meta.profile), leva o mesmo conteúdo clínico do bundle gravado neste
showcase: os dois saem do mesmo Laudo (apps/labmais/backend/app/laudo.py),
montado uma vez por publicação. Valores, unidades, faixas de referência,
conclusão, a data do pedido (authoredOn) e a do resultado (effectiveDateTime
e issued, o mesmo instante nos dois lados) são idênticos — o teste
tests/test_pareamento.py compara os dois bundles campo a campo.
O que muda é só o envelope, por exigência de cada barramento:
| Aspecto | Este showcase (HAPI local) | Barramento SBIS/IPES |
|---|---|---|
ServiceRequest |
PUT no SR já existente, criado pelo iSUS APS (conclui o pedido) |
POST de um SR próprio do Lab+ — categoria SADT e código TUSS 403043018, exigidos pelo modelo validado |
| Motivo do pedido | fica no SR original | copiado do pedido original: o texto e, quando o solicitante informa, o CID-10 dele. Sem motivo no pedido, o campo não é enviado |
| Quem pediu | fica no SR original | requester copiado como veio — pelo nome ou pelo CNS, no formato dos pedidos que chegam do IPES |
| Código do exame | LOINC 58410-2 (Hemograma completo) |
TUSS 403043018, no SR e no DiagnosticReport |
| Paciente | Patient/{id} local |
Patient/2.16.840.1.113883.13.237-<cpf> — referência lógica; o recurso Patient não é enviado, só um identifier de CPF |
| Identificadores | nenhum além da meta.tag de origem |
identifier enviado com system urn:oid:<OID> e value <valor> (ex.: labmais-10-20260916103000) no DiagnosticReport, e <valor>-sr no ServiceRequest; o barramento atribui ao recurso o id <OID>.<valor> — é esse id que aparece no location da resposta e no toast do Lab+; sem meta.tag |
| Faixa de referência | referenceRange nas Observations |
igual — referenceRange nas Observations |
| Flag do resultado | interpretation (L/H) nas Observations |
sem flag: com interpretation o barramento responde HTTP 500 ao montar o laudo — "Exception evaluating OGNL expression: result.resource.interpretation.coding[0].display (template: diagnosticreport)". O template do IPES lê interpretation como objeto, mas no FHIR R4 é lista. Sondado em 21/09/2026 (integracao/respostas/20260921-14331[56]-labstrc-*.json); a faixa de referência supre a leitura |
| Origem do registro | meta.tag source-system = labmais |
sem meta.tag: lá a origem é o participante autenticado |
| Ordem da resposta | preserva a ordem das entries enviadas | não preserva a ordem — o laudo é localizado pelo tipo de recurso (DiagnosticReport) no location, não pela posição |
Por que o authoredOn importa
Sem ServiceRequest.authoredOn, a solicitação aparece como "Data não
informada" na linha do tempo do barramento SBIS. O Lab+ envia ali a data do
pedido (a que o iSUS APS gravou), não a hora em que o resultado saiu.
O perfil Laboratory descreve o laudo em PDF
O Laboratory do IG exige exatamente três entries (ServiceRequest,
DiagnosticReport e Binary), e o DiagnosticReportPresentedForm proíbe
result, conclusion e effective[x]; o ObservationBRIPES proíbe
referenceRange e interpretation. O modelo estruturado não tem perfil
publicado, e o barramento o aceita assim mesmo — a validação do ehrposter
checa o formato do identifier, não o StructureDefinition. O que vale aqui
é o que foi sondado e aceito; se o IPES apertar a validação, o caminho é pedir
o perfil do modelo estruturado ao helpdesk.
Quando o destino é Ambos, o Lab+ envia primeiro ao barramento SBIS
e só grava neste showcase depois de confirmado — a única ordem em que uma
falha parcial é recuperável: se este showcase fosse gravado primeiro, o
pedido sairia da worklist (completed) e não haveria mais como reenviar
ao SBIS.
Situação × HTTP × o que foi gravado¶
| Situação | HTTP | O que foi gravado |
|---|---|---|
| Entrada inválida (limites dos analitos, conclusão vazia ou acima de 1000 caracteres, destino desconhecido) | 422 | Nada |
| Pedido que não é hemograma completo | 422 | Nada |
| Paciente sem CPF, ou CPF não reconhecido pelo MPI do SBIS | 422 | Nada |
Recusa do SBIS (4xx com OperationOutcome) |
502 | Nada |
| SBIS inacessível, sem resposta, erro 5xx ou falha ao obter o token | 503 | Nada neste showcase — a mensagem inclui "confira no Portal Clínico" quando o envio pode ter chegado ao barramento |
| Destino Ambos, com o SBIS confirmado e este showcase indisponível | 200, com aviso | Só no SBIS; o pedido continua pendente nesta worklist |
| SBIS aceitou o envio mas não devolveu o id do laudo na resposta | 200, com aviso para conferir | Conforme o destino (SBIS e, se Ambos, também este showcase) |
Laudo só no SBIS não aparece neste showcase
Um laudo publicado apenas no destino Barramento SBIS/IPES não passa pelo HAPI local desta demonstração — por isso ele não aparece no painel "Laudos emitidos" do Lab+, na linha do tempo do Hub Saúde nem no histórico do Meu PEP (todos leem exclusivamente o HAPI local). Ele fica disponível no Portal Clínico do Showcase SBIS/IPES.