estudo de caso · Rivio · 2026
API, RPA, agente, humano: uma cadeia de integração durável para o faturamento hospitalar
Hospitais e operadoras de saúde trocam cerca de mil operações por dia: elegibilidade, autorizações, faturamento. Cada operadora oferece uma combinação diferente de canais. Desenhei e construí a orquestração que tenta cada canal em ordem e só entrega o caso a uma pessoa quando não consegue provar que o trabalho foi feito.
- Papel
- Senior Software Engineer
- Escala
- ~1.000 operações por dia em 5 operadoras
- Resultado
- ~90% resolvidas sem uma pessoa
- Stack
- Temporal Cloud · Python · Google Cloud Pub/Sub · GCP · MongoDB
O problema
A Rivio cuida do ciclo de receita de hospitais. Boa parte desse ciclo é conversar com operadoras de saúde: verificar se o paciente tem cobertura, pedir autorizações, enviar guias de faturamento. Chamamos cada uma dessas operações de integração.
Quando uma integração falhava, alguém do time de operações abria o portal da operadora e fazia tudo à mão. Com cerca de mil operações por dia, cada integração que falhava aumentava essa fila.
A dificuldade não estava em uma operadora específica. Estava na mistura. Algumas operadoras têm API. Outras só têm portal. Algumas têm os dois, mas só para certas operações. Elegibilidade na Operadora A podia passar por todos os canais. Elegibilidade na Operadora B só funcionava pelo portal. Ou seja, a variação não era por operadora, era por operação.
As integrações já rodavam como serviços orientados a eventos com Pub/Sub. O que faltava era algo responsável pela vida inteira de uma requisição: quais canais ela já tinha tentado, o que aconteceu em cada um e o que fazer em seguida.
O que o sistema precisava garantir
Escrevi quatro regras antes de escrever código:
- Nenhuma requisição se perde. Toda operação termina em um estado conhecido, mesmo que um worker caia no meio do caminho.
- Nada que muda estado na operadora roda duas vezes. Uma guia de faturamento duplicada é pior do que uma guia lenta.
- Toda falha termina em um lugar onde uma pessoa consegue ver. Nada de mensagens perdidas em silêncio, nada de dead letters que ninguém lê.
- Uma nova integração nunca mexe no orquestrador. Ela ainda precisa do próprio código no tier que fala com a operadora, como um client de API ou um script de RPA. A cadeia em si só lê configuração.
A cadeia
Toda operação passa pelos mesmos quatro tiers, sempre na mesma ordem:
- API. A API da própria operadora, quando existe. Rápida e barata.
- RPA. Uma execução roteirizada pelo portal da operadora, construída sobre o Playwright.
- Agente. Um agente de navegador guiado por LLM que opera o portal a partir de uma descrição escrita da tarefa.
- Operações. Uma tarefa para o time de operações na nossa plataforma interna.
Cada tier é um serviço separado. Um workflow orquestrador no Temporal decide qual tier chamar, espera o resultado e desce na cadeia quando um tier falha.
↩ A execução do agente é salva como script Python. Um dev revisa, e na próxima vez ela roda como RPA.
- 00:00.000WorkflowStartedauthorization.insurer_c · req 48215
- 00:00.010TierStartedapi
- 00:01.204ActivityFailedapi.request_authorization · 503 · attempt 1/3
- 00:03.412ActivityFailedapi.request_authorization · 503 · attempt 2/3
- 00:07.905ActivityFailedapi.request_authorization · 503 · attempt 3/3
- 00:07.911TierStartedrpa
- 00:08.120ActivityFailedrpa.request_authorization · no script for this flow
- 00:08.126TierStartedagent
- 00:08.340ActivityStartedagent.run · prompt: authorization / insurer_c · browser: panda
- 02:31.077ActivityHeartbeatagent.run · step 7/14 · filling procedure codes
- 04:20.512ActivityCompletedagent.run · authorization submitted
- 04:20.530ScriptSavedgenerated script → mongodb · review job queued
- 04:20.538WorkflowCompletedresolved by agent
Por que Temporal
O Pub/Sub é ótimo para mover mensagens. Ele não lembra onde uma requisição está. Numa cadeia de até quatro tentativas, algumas levando minutos, essa memória é o problema inteiro.
O Temporal resolveu isso de saída:
- Estado durável. Se um worker morre no meio de uma execução de RPA, o workflow continua de onde parou, em vez de recomeçar ou sumir.
- Retries e timeouts como configuração. Cada activity declara a própria política, em vez de cada serviço escrever seu próprio loop de retry.
- Signals. Um workflow pode esperar horas até uma pessoa resolver um caso e depois seguir como se nada tivesse acontecido.
- Histórico. Todo workflow guarda o histórico completo de eventos, então “o que aconteceu com essa requisição?” sempre tem resposta.
Rodávamos no Temporal Cloud, com workers Python no GCP. As task queues do Temporal direcionam o trabalho para os workers de cada tier, e o Pub/Sub continua conectando as partes da plataforma que já eram orientadas a eventos.
Uma configuração por operação, não por operadora
A ordem dos tiers nunca muda. Cada operação só declara quais tiers pode usar e com que cuidado fazer retry:
# ilustrativo
eligibility.insurer_b:
tiers: [rpa, agent, human] # só portal
retry: read_only
eligibility.insurer_a:
tiers: [api, rpa, agent, human]
retry: read_only
billing.submit_guide:
tiers: [api, rpa, agent, human]
retry: state_changing
Isso manteve o orquestrador pequeno. Ele não sabe nada sobre a Operadora A ou a Operadora B. Ele lê a configuração e percorre a cadeia.
As credenciais dos portais seguem a mesma ideia. Toda integração usa uma activity compartilhada que busca as credenciais no nosso secret manager em tempo de execução. Workers e prompts nunca guardam credenciais.
Retries dependem de efeitos colaterais
Consultar elegibilidade duas vezes não causa dano. Enviar uma guia de faturamento duas vezes, sim.
Por isso as políticas de retry seguem o que a operação faz, não qual tier a executa. Operações de leitura fazem retry à vontade. Operações que criam estado na operadora têm políticas conservadoras, com poucas tentativas e nenhum retry depois de um timeout que pode ter dado certo.
O caso interessante é uma execução que termina em estado desconhecido: o RPA clicou em “enviar” e estourou o timeout antes de ler a confirmação. A guia foi enviada? O sistema não tem como saber. Tentar de novo pode enviar duas vezes.
Se o orquestrador não consegue provar que uma operação terminou, ele a entrega a uma pessoa em vez de tentar de novo.
O workflow para, cria uma tarefa na plataforma de operações e espera um signal. Alguém confere o portal, confirma o que aconteceu, e o workflow termina com o resultado certo. Demora mais, mas nunca gera uma duplicata.
O tier lento ensina o rápido
O tier do agente é a parte sobre a qual as pessoas mais perguntam.
Cada operação tem um template de prompt, escrito em linguagem natural: o que abrir, o que preencher, o que conta como sucesso. O agente opera um navegador de verdade pelo portal da operadora. Tínhamos duas opções: uma sessão comum do Chrome ou o Panda, um navegador feito para agentes.
O agente é o tier mais lento, levando de 1 a 7 minutos dependendo da operação, e por isso vem por último. Isso não é um problema, porque toda integração é um job assíncrono. Ninguém fica olhando para uma tela de carregamento esperando.
O que fez valer a pena foi o ciclo de feedback. Na primeira vez que o agente resolve um fluxo novo, a execução é salva como script Python no MongoDB. Um job assíncrono pega esse script e abre um pull request no serviço de RPA. Um dev testa e faz o merge. Daí em diante, esse fluxo roda como RPA: segundos em vez de minutos, sem modelo no caminho.
O agente cobria fluxos para os quais ainda não tínhamos script, e cada sucesso tornava o agente menos necessário para aquele fluxo.
O humano no loop
O último tier não é um modo de falha. Ele faz parte do desenho.
Quando um caso chega a operações, ele aparece como uma tarefa na plataforma interna com o contexto que o workflow reuniu: quais tiers rodaram, o que cada um retornou e por que a cadeia parou. Também construí APIs de backoffice (FastAPI e Node.js/TypeScript) para tratar essas exceções e intervenções manuais. Quando a pessoa resolve a tarefa, a plataforma envia um signal para o workflow que estava esperando, que fecha a requisição com o estado correto.
Enxergando o caminho inteiro
Uma única integração pode passar pelo orquestrador, pelo serviço de API, pelo serviço de RPA, pelo agente e pela plataforma de operações. Quando algo dá errado, a pergunta nunca é “qual serviço falhou?”, e sim “o que aconteceu com essa requisição?”.
Todos os serviços eram monitorados no Datadog, com logs correlacionados entre serviços. Dava para seguir uma integração desde a entrada na cadeia até o tier que a resolveu, passando por cada serviço no meio, em vez de ler cinco streams de log separados e tentar adivinhar como eles se encaixam.
Dados de pacientes nunca em texto puro
Esses workflows carregam dados de saúde e de faturamento, e o Temporal Cloud guarda as entradas e os resultados de todo workflow no histórico. Não queríamos nada disso legível fora da nossa infraestrutura.
Os workers criptografam todo payload antes de ele sair do nosso lado, então o Temporal Cloud só armazena dados cifrados. Um codec server, rodando no nosso próprio ambiente, descriptografa os payloads para quem tem acesso quando essa pessoa inspeciona um workflow na UI do Temporal. A conveniência do histórico continua, e os dados sensíveis ficam com a gente.
Resultados
- Cerca de 1.000 operações por dia em 5 operadoras passam pela cadeia.
- ~50% são resolvidas no tier de API.
- ~40% são resolvidas por RPA ou pelo agente.
- ~10% chegam a uma pessoa, e são os casos que realmente precisam de uma.
- Novas integrações passaram a levar no mínimo metade do tempo. Não medimos com precisão, mas descrever um fluxo em linguagem natural e deixar o agente executá-lo (e escrever o primeiro script de RPA) substituiu a maior parte do trabalho de construir cada integração à mão.
O que eu faria a seguir
Guardrails para o agente. O escopo do agente era limitado por templates de prompt que nós controlávamos, e na prática isso bastou. Não é uma resposta de longo prazo, principalmente porque o agente também executava operações que mudam estado nas operadoras. O próximo passo seria uma lista de ações permitidas por operação, uma etapa de confirmação antes de qualquer coisa que mude estado e a remoção de dados de pacientes dos logs do agente. Em saúde, com a LGPD, esse último ponto não é negociável.
Custo e latência por tier. Sabíamos que o agente era lento. Eu mediria custo e duração por tier e por operação, para que a decisão de escrever um script de RPA venha de números, não de intuição.
Idempotência na origem. Entregar estados desconhecidos a uma pessoa é seguro, mas continua sendo trabalho manual. Quando a operadora oferece uma forma de consultar um envio, o workflow poderia checar antes de decidir e mandar menos casos para operações.
Stack: Temporal Cloud · Python (Pydantic, MyPy, Pytest) · Google Cloud Pub/Sub · GCP · MongoDB · Playwright · LLM browser agents · Datadog · FastAPI · Node.js/TypeScript