Sistema de integração e sincronização transacional de dados laboratoriais entre unidades sanitárias e a base de dados central nacional.
A aplicação opera em dois modos mutuamente exclusivos:
| Modo | Descrição Funcional |
|---|---|
local |
Nó local instalado na unidade sanitária. Processa pedidos do EMR, faz a ponte operacional com o SISLAB, gera números de rastreio nacionais e enfileira eventos transacionais na outbox para envio ao nó nacional. |
national |
Nó central nacional. Mantém a autoridade sobre o catálogo de dados mestre (dicionário nacional), agrega o fluxo transacional de todos os nós locais e orquestra o encaminhamento de amostras referidas. |
O modo de operação é parametrizado exclusivamente pela variável de ambiente SISLAB_SYNC_MODE. A aplicação aborta a inicialização caso a variável não esteja definida. As rotas, jobs e tarefas específicas de cada modo são carregadas condicionalmente.
O ambiente de execução padrão e suportado é baseado em contentores Docker:
- Docker Engine (versão 24.0 ou superior)
- Docker Compose (versão 2.20 ou superior)
Para execução nativa fora de contentores, consulte a secção 7. Para implantação em produção, consulte a secção 8.
A stack do Docker Compose constrói o estágio development da imagem, monta o código a partir do sistema de ficheiros anfitrião e opera em RAILS_ENV=development. Para implantação em produção, consulte a secção 8.
Selecione o modelo de configuração correspondente ao modo de operação pretendido:
cp .env.local.example .env # Para implantação de nó local (unidade sanitária)
# ou
cp .env.national.example .env # Para implantação do nó nacional centralNo modo local, configure obrigatoriamente a variável SISLAB_SYNC_FACILITY_CODE com o código da unidade sanitária no registo nacional (facilities). É a única identidade que um nó tem de receber: um nó é uma unidade sanitária, e o nome, o distrito e a província são lidos da entrada correspondente do registo, replicada do nó nacional. As chaves emitidas no nó herdam a unidade. Enquanto o registo não chegar, o próprio código serve de prefixo aos números de rastreio.
Os laboratórios ficam por baixo. Uma instância do mLab serve vários, cada um com o seu código, e todos chegam à rede por este nó — ver secção 6.4.
SISLAB_SYNC_NODE_CODE é lida como sinónimo: significa o código por que o nó responde, que é o da unidade. SISLAB_SYNC_LAB_CODE nomeia um laboratório e não serve de identidade a um nó — uma instalação que a tenha configurada não arranca, e deve passar a SISLAB_SYNC_FACILITY_CODE com o código da unidade.
Execute o provisionamento dos contentores e dependências:
docker compose up -dNa primeira inicialização, o contentor de aplicação executa automaticamente a compilação, criação da base de dados, execução das migrações do ActiveRecord e, em ambiente development, a inserção de dados de demonstração.
Valide a inicialização através do endpoint de diagnóstico:
curl -s http://localhost:3000/api/v3/health | jqResposta estruturada:
{
"data": {
"mode": "local",
"node_code": "HCM",
"version": "4.0.0",
"time": "2026-08-18T11:40:00Z"
},
"meta": {},
"errors": []
}Em ambiente development, as credenciais administrativas geradas automaticamente são registadas no ficheiro temporário:
cat tmp/demo_credentials.txtAceda a http://localhost:3000 e autentique-se com o utilizador admin.
Em ambiente de produção (production), crie a conta administrativa inicial via CLI:
docker compose exec app bin/rails "users:create[Nome Utilizador,email@instituicao.gov.mz,admin]"A palavra-passe temporária é emitida uma única vez no stdout do comando. O sistema armazena apenas o hash criptográfico (bcrypt).
A emissão de credenciais de integração (para sistemas EMR ou SISLAB) pode ser realizada via interface web (Clientes e chaves → Novo cliente → Emitir chave) ou via linha de comandos:
docker compose exec app bin/rails api_client:create \
NAME="EMR Unidade Central" KIND=emr
docker compose exec app bin/rails api_key:issue \
CLIENT="EMR Unidade Central" SCOPES="orders:write,orders:read,results:read,dictionary:read"Não é preciso indicar códigos. Um nó local é uma unidade sanitária e sabe qual: a chave herda o código da unidade (SISLAB_SYNC_FACILITY_CODE). Um pedido que declare outros códigos não é recusado — o nó usa o seu.
A chave não carrega laboratório nenhum. Uma instância do mLab fala por todos os laboratórios da unidade com a mesma chave, e qual deles está a falar segue em cada pedido (lab.code na criação, lab_code no feed e na reclamação).
| Serviço | Descrição Técnica |
|---|---|
app |
Servidor de aplicação HTTP Puma na porta APP_PORT (padrão: 3000). Executa a rotina de verificação e migração de base de dados na inicialização. |
sidekiq |
Processador de tarefas assíncronas e agendadas em segundo plano (sincronização de outbox, pooling de dicionário e encaminhamentos). |
css |
Compilador do Tailwind CSS em modo de monitorização contínua (watch). Utilizado em ambiente de desenvolvimento. |
mysql |
Sistema de Gestão de Base de Dados Relacional MySQL 8.4 LTS. |
redis |
Servidor Redis 7 para gestão de filas do Sidekiq e armazenamento temporário de rate limiting. |
Comandos operacionais frequentes:
docker compose up -d mysql redis # Inicialização exclusiva da infraestrutura de dados
docker compose logs -f app # Acompanhamento contínuo de logs da aplicação
docker compose exec app bin/rails c # Acesso à consola interativa do Rails
docker compose down # Encerramento dos contentores
docker compose down -v # Encerramento com eliminação persistente dos volumes de dadosPara testes e validação de fluxos de sincronização em ambiente de desenvolvimento, execute duas instâncias isoladas com namespaces de projeto e portas distintas:
# Instância do Nó Nacional
COMPOSE_PROJECT_NAME=sislab_national APP_PORT=3100 docker compose up -d
# Instância do Nó Local (configurada no .env correspondente)
SISLAB_SYNC_NATIONAL_URL=http://host.docker.internal:3100
SISLAB_SYNC_NATIONAL_API_KEY=<token_emitido_no_nacional_com_escopos_sync>A chave utilizada pelo nó local deve ser previamente emitida no nó nacional para um cliente de tipo node com os âmbitos sync:push, sync:pull e dictionary:read.
| Variável | Obrigatoriedade | Descrição |
|---|---|---|
SISLAB_SYNC_MODE |
Obrigatória | Define o modo de operação: local ou national. |
SISLAB_SYNC_FACILITY_CODE |
Obrigatória em modo local | Código da unidade sanitária no registo nacional (facilities). Identifica o nó e prefixa os números de rastreio. |
SISLAB_SYNC_NATIONAL_URL |
Obrigatória em modo local | URL base de comunicação com o nó nacional central. |
SISLAB_SYNC_NATIONAL_API_KEY |
Obrigatória em modo local | Token de autenticação Bearer para comunicação com o nó nacional. |
SISLAB_SYNC_PUSH_BATCH |
Opcional | Quantidade máxima de eventos por lote de envio da outbox (padrão: 100). |
SISLAB_SYNC_TLS_TERMINATED |
Opcional | false num nó de produção servido em HTTP simples, sem proxy inverso a terminar TLS (padrão: true). Ver secção 8.5. |
| Variável | Descrição |
|---|---|
DATABASE_HOST, DATABASE_PORT, DATABASE_USER, DATABASE_PASSWORD |
Parâmetros de ligação TCP ao servidor MySQL. |
DATABASE_NAME |
Nome da base de dados relacional da aplicação. |
TEST_DATABASE_NAME |
Nome da base de dados dedicada à execução de testes automatizados (padrão: sislab_sync_test). |
REDIS_URL |
URI de ligação ao servidor Redis. |
| Variável | Valor Padrão (Cron) | Descrição do Job |
|---|---|---|
SYNC_PUSH_CRON |
* * * * * |
Despacho periódico da outbox de eventos e sinal de heartbeat. |
SYNC_PULL_CRON |
* * * * * |
Consulta e receção de amostras referidas e resultados concluídos. |
DICTIONARY_PULL_CRON |
*/5 * * * * |
Sincronização incremental de atualizações do catálogo nacional. |
| Variável | Descrição |
|---|---|
API_RATE_LIMIT_PER_MINUTE |
Limite de requisições por minuto por chave de API. |
MLAB_DB_* |
Configurações de conexão para importação do legado mLab (exclusivo do nó nacional). |
MLAB_API_URL, MLAB_API_TOKEN, MLAB_API_USER, MLAB_API_PASSWORD |
Endereço e credenciais da API do mLab, alternativa à ligação directa à base de dados na importação do catálogo. O token é opcional: na sua ausência a importação autentica-se em /api/v1/auth/application_login. |
LOINC_CSV |
Caminho no sistema de ficheiros para o ficheiro Loinc.csv oficial utilizado na curadoria. |
WEB_CONCURRENCY, RAILS_MAX_THREADS, SIDEKIQ_CONCURRENCY |
Parâmetros de paralelismo de processos e threads do servidor de aplicação e workers. |
| Executável | Finalidade Técnica |
|---|---|
bin/rails |
Executável padrão do framework Rails. |
bin/setup |
Script de inicialização: instalação de dependências, migração de esquema e inicialização de serviços. |
bin/dev |
Inicializador de desenvolvimento com processos concorrentes via Foreman. |
bin/ci |
Execução integrada de análises estáticas (RuboCop, Bundler Audit, Brakeman). |
bin/rubocop, bin/brakeman, bin/bundler-audit |
Execução modular de ferramentas de análise estática e auditoria de vulnerabilidades. |
bin/rails "users:create[Nome Completo,email@dominio.mz,admin]" # Papéis disponíveis: admin, operator
bin/rails "users:reset_password[email@dominio.mz]" # Invalidação de sessões e redefinição de credencial
bin/rails users:list # Listagem de contas registadasbin/rails api_client:create NAME="EMR Local" KIND=emr
bin/rails api_client:list
bin/rails api_key:issue CLIENT="EMR Local" SCOPES="orders:write,results:read" [EXPIRES_AT=2027-01-01]
bin/rails api_key:list
bin/rails api_key:revoke KEY=<uuid_ou_prefixo>Âmbitos estruturados: orders:read, orders:write, results:read, results:write, referrals:write, dictionary:read, dictionary:write, sync:push, sync:pull.
O nó nacional conhece todas as unidades sanitárias e todos os laboratórios do país, e publica as duas listas pelo mesmo canal do dicionário — facilities e labs são entidades do catálogo como qualquer outra, com a mesma sequência de revisões, o mesmo cursor e os mesmos ecrãs. A unidade sanitária carrega o distrito, a província e um contacto; o laboratório aponta para a unidade onde está e guarda o código por que o seu LIS o conhece.
Um nó é uma unidade sanitária, e os laboratórios ficam por baixo dele. Uma instância do mLab serve vários laboratórios da mesma unidade, cada um com o seu código, e todos chegam à rede por este nó.
Um laboratório novo não é escrito à mão em lado nenhum. Entra por onde trabalha:
- O mLab envia a primeira amostra e identifica-se no bloco
labdo pedido: o seu código (code), o nome e, se quiser, o telefone. - O nó local regista-o na mesma transacção em que cria a amostra — com o código do LIS em
source_code, a unidade sanitária deste nó, e sem código nacional, porque só a capital os atribui. Nada é recusado: a amostra é aceite na mesma. - O registo sobe ao nó nacional como o evento
lab.registered, pela mesma outbox e pelas mesmas retentativas que os factos clínicos. Se a ligação à capital estiver em baixo, espera na fila. - A capital aceita e publica sempre, atribui o código nacional (
MOZ-LAB-nnnn) e devolve a entrada pelo feed do dicionário. O nó local reconhece-a como sua — pelo uuid, ou pelo par (unidade, código do LIS) — e passa a ter o laboratório com código nacional.
Até esse regresso o laboratório trabalha na mesma; só não pode receber amostras referidas de outra unidade, porque para isso é preciso um código com que o país concorde.
- o pedido de encaminhamento indica apenas
to_lab_code; a unidade sanitária de destino é lida do registo; - um código que o registo não conheça é recusado, porque uma encomenda endereçada a uma gralha nunca chega;
- entre dois laboratórios da mesma unidade o código do LIS basta, e o encaminhamento resolve-se dentro do nó, sem passar pela capital;
- as chaves emitidas num nó herdam a unidade sanitária do próprio nó — nunca um laboratório, porque uma chave do mLab fala por todos eles;
- um nó que ainda não tenha recebido o registo continua a funcionar: usa o seu próprio código como prefixo dos números de rastreio e aceita qualquer destino.
O mLab lê os laboratórios para os quais pode referenciar em GET /api/v3/dictionary/labs (âmbito dictionary:read), ou pelo feed incremental GET /api/v3/dictionary/changes?entities=facilities,labs.
As entradas mantêm-se no nó nacional, pelos ecrãs do dicionário (Dicionário → Unidades sanitárias e Laboratórios), e chegam aos nós locais na sincronização seguinte.
Um pedido vindo de um EMR chega à unidade sanitária e não a um laboratório: o clínico pede um exame à unidade, e qual bancada o executa decide-se na bancada. O pedido fica em receiving_facility_code sem receiving_lab_code, aparece no feed de todos os laboratórios da unidade, e ganha laboratório quando um deles o reclama (POST /api/v3/lab/orders/{tracking_number}/claim?lab_code=…). Um pedido vindo do mLab já traz o seu laboratório no bloco lab.
O catálogo é uma referência, não uma barreira. Um exame, painel, tipo de amostra, indicador ou motivo de rejeição pode ser indicado por national_code, por uuid ou por nome, e um termo que o nó não reconheça é aceite e guardado tal como chegou — com o nome e o código com que veio, sem entrada de dicionário associada. A ligação faz-se mais tarde, quando o catálogo alcançar o termo, sem reintroduzir a leitura. O mesmo vale na sincronização: o nó nacional não bloqueia a fila de um nó por um código que ainda não tenha.
A única recusa que resta é a de um termo que não indique nada — um teste sem exame, uma leitura sem indicador —, porque nesse caso ninguém saberia o que foi pedido ou medido.
A autoridade de escrita sobre o catálogo é restrita ao nó nacional:
# Executado no Nó Nacional:
bin/rails dictionary:seed # Carga inicial: catálogo distribuído com a release, já publicado
bin/rails dictionary:snapshot # Recolha de um novo catálogo a partir do mLab (SOURCE=api|database)
bin/rails dictionary:import_from_mlab # Importação directa a partir da base legada mLab (estado draft)
bin/rails dictionary:import_from_mlab_api # Importação equivalente através da API do mLab (estado draft)
bin/rails dictionary:quality # Geração de relatório de conformidade → tmp/dictionary_quality.csv
ACTOR="Administrador" SKIP_BLOCKED=1 \
bin/rails dictionary:promote # Publicação e ativação estruturada de rascunhos válidos
bin/rails dictionary:status # Diagnóstico do estado quantitativo do catálogo
# Executado no Nó Local:
bin/rails dictionary:pull # Sincronização manual imediata com o nó nacionalO ficheiro db/dictionary/mlab_catalog.json acompanha a release e contém o catálogo do mLab tal como
estava no momento da recolha — departamentos, espécimes, fármacos, organismos, indicadores com os
respectivos intervalos de referência, exames, painéis e todas as ligações entre eles. dictionary:seed
importa-o, acrescenta os motivos de rejeição (o mLab não tem tabela para eles) e publica tudo,
registando o acto contra ACTOR. É idempotente: a identidade vem do id do mLab através de
ExternalMapping, pelo que uma segunda execução não duplica nem altera nada.
Esta é a carga inicial. A partir daí o catálogo é propriedade do nó nacional e os exames novos são
criados à mão na interface; dictionary:snapshot só volta a ser necessário para distribuir um catálogo
mais recente do mLab numa nova release.
| Entidade | Entradas na recolha de 2026-08-30 |
|---|---|
| Departamentos | 13 |
| Tipos de espécime | 28 |
| Fármacos | 30 |
| Organismos | 224 |
| Indicadores | 527 (3 recusados por não terem nome) |
| Intervalos de referência | 1061 |
| Exames | 115 |
| Painéis | 9 |
| Motivos de rejeição | 10 (deste repositório, não do mLab) |
O relatório de qualidade é gerado no fim da carga e não a bloqueia: a recolha de 2026-08-30 acusa 279
nomes duplicados e 199 indicadores sem exame associado, herdados do mLab. SKIP_BLOCKED=1 retém os
exames sem indicadores ou sem espécime em rascunho, para serem corrigidos antes de chegarem aos
laboratórios.
As duas importações são intercambiáveis — partilham o mesmo importador, a mesma identidade por
ExternalMapping e a mesma idempotência — e diferem apenas na origem das linhas. A via API é a
indicada quando apenas o HTTP do mLab está acessível, com três limitações que decorrem da própria
API e que a tarefa comunica no final da execução:
- Exames e painéis pediátricos ou oncológicos são omitidos pelas listagens da API; uma importação por esta via retira os que uma importação anterior pela base de dados tenha criado.
- Um indicador chega através do exame que o utiliza, pelo que indicadores sem exame associado não são importados.
- Um exame cujo departamento esteja retirado no mLab não pode ser lido em detalhe (a API responde 404), pelo que é importado sem indicadores nem organismos e é nomeado nos avisos da fonte.
bin/rails dictionary:loinc:coverage # Métricas de cobertura de mapeamentos por entidade
LOINC_CSV=tmp/Loinc.csv \
bin/rails dictionary:loinc:worksheet # Extração de planilha de trabalho com candidatos → tmp/loinc_worksheet.csv
# Aplicação transacional do mapeamento validado:
LOINC_CSV=tmp/Loinc.csv \
bin/rails "dictionary:loinc:apply[tmp/loinc_worksheet.csv]" # Modo de simulação (dry-run)
LOINC_CSV=tmp/Loinc.csv ACTOR="Responsável Técnico" APPLY=1 \
bin/rails "dictionary:loinc:apply[tmp/loinc_worksheet.csv]" # Execução efetiva com persistênciadocker compose run --rm -e RAILS_ENV=test app bundle exec rspec
docker compose run --rm app bundle exec rubocop
docker compose run --rm app bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error
docker compose run --rm app bin/bundler-auditExecução segmentada por modo operacional:
# Execução da suite em modo Local:
docker compose run --rm -e RAILS_ENV=test \
-e SISLAB_SYNC_MODE=local -e SISLAB_SYNC_FACILITY_CODE=LOCAL01 app bundle exec rspec
# Execução da suite em modo Nacional:
docker compose run --rm -e RAILS_ENV=test \
-e SISLAB_SYNC_MODE=national -e SISLAB_SYNC_FACILITY_CODE=NATIONAL app bundle exec rspecRequisitos de runtime: Ruby 3.3.8, MySQL 8.4 e Redis 7.
bundle install
export SISLAB_SYNC_MODE=local SISLAB_SYNC_FACILITY_CODE=DEV
export DATABASE_HOST=127.0.0.1 DATABASE_USER=root DATABASE_PASSWORD=...
bin/rails db:prepare
bin/devNota técnica: O sistema requer isolamento de transação READ-COMMITTED configurado no MySQL (config/database.yml).
Em produção utiliza-se a imagem final do Dockerfile, distinta do estágio development consumido pela stack do Docker Compose: executa sob utilizador não privilegiado, incorpora os assets pré-compilados e não monta código do sistema anfitrião.
docker build -t sislab_sync:4.0.0 .A mesma imagem serve ambos os tipos de nó. O modo de operação é lido do ambiente em tempo de execução e não é fixado na construção.
Em ambiente production a aplicação requer um segredo, fornecido por uma das seguintes variáveis:
| Variável | Utilização |
|---|---|
RAILS_MASTER_KEY |
Conteúdo de config/master.key, para decifragem de config/credentials.yml.enc. |
SECRET_KEY_BASE |
Segredo fornecido diretamente, quando as credenciais cifradas não são utilizadas. |
config/master.key está excluído do controlo de versões e deve ser distribuído pelo mecanismo de gestão de segredos da infraestrutura.
| Variável | Exemplo | Observação |
|---|---|---|
RAILS_ENV |
production |
|
SISLAB_SYNC_MODE |
local | national |
|
SISLAB_SYNC_FACILITY_CODE |
HCM |
Obrigatória em modo local; identifica a unidade sanitária no registo nacional e prefixa os números de rastreio. |
SISLAB_SYNC_TLS_TERMINATED |
true |
false se o nó for servido em HTTP simples (ver 8.5). |
DATABASE_HOST, DATABASE_USER, DATABASE_PASSWORD, DATABASE_NAME |
MySQL 8.4 com transaction_isolation=READ-COMMITTED. |
|
REDIS_URL |
redis://redis:6379/0 |
Cache, limitação de taxa e filas Sidekiq. |
SISLAB_SYNC_NATIONAL_URL, SISLAB_SYNC_NATIONAL_API_KEY |
Modo local, para replicação com o nó nacional. |
A especificação completa consta da secção 5.
docker run -d --name sislab_sync \
-e RAILS_ENV=production \
-e RAILS_MASTER_KEY="$(cat config/master.key)" \
-e SISLAB_SYNC_MODE=local -e SISLAB_SYNC_FACILITY_CODE=HCM \
-e DATABASE_HOST=... -e DATABASE_USER=... -e DATABASE_PASSWORD=... \
-e DATABASE_NAME=sislab_sync_production \
-e REDIS_URL=redis://... \
-p 127.0.0.1:3000:3000 sislab_sync:4.0.0Em modo local, um processo Sidekiq adicional executa o despacho da outbox, a receção de amostras referidas e a sincronização do dicionário (ver secção 5.3). Utiliza a mesma imagem e o mesmo conjunto de variáveis:
docker run -d --name sislab_sync_worker <mesmas variáveis> sislab_sync:4.0.0 bundle exec sidekiqPor omissão, config/environments/production.rb define force_ssl e assume_ssl: a terminação TLS é realizada por um proxy reverso à frente do nó, que encaminha o cabeçalho X-Forwarded-Proto. O processo Puma serve exclusivamente HTTP simples na porta 3000, que não deve ser exposta fora da máquina — publique-a apenas no interface de loopback (-p 127.0.0.1:3000:3000) e coloque o proxy à frente.
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}Nó sem proxy TLS. Um nó servido diretamente em HTTP simples tem de declarar SISLAB_SYNC_TLS_TERMINATED=false. Mantido no valor por omissão, assume_ssl marca todos os pedidos como seguros antes do redirecionamento de force_ssl ser avaliado — pelo que nenhum pedido é redirecionado e o nó apenas passa a declarar um esquema que não pratica:
| Efeito | Consequência observada |
|---|---|
Cookie de sessão marcado Secure |
O navegador aceita o cookie e nunca o devolve: a autenticação na interface web não persiste. |
request.base_url resolve para https://<host>:3000 |
Os fullUrl e os link dos Bundles FHIR apontam para uma porta sem TLS. Um cliente que siga a paginação abre uma ligação TLS contra o Puma, que regista Puma::HttpParserError: Invalid HTTP format. |
Cabeçalho Strict-Transport-Security emitido em HTTP simples |
Ignorado por navegadores conformes (RFC 6797 §8.1); o comportamento de outros clientes não é uniforme. |
A variável não desativa proteção alguma que o transporte já possua: alinha a configuração da aplicação com o transporte efetivamente em uso. Num nó em HTTP simples, as credenciais e os resultados clínicos circulam em claro na rede, pelo que a colocação de um proxy com TLS permanece o destino correto.
bin/docker-entrypoint executa db:prepare quando o comando do contentor inclui rails server:
| Contentor | Comportamento |
|---|---|
| Aplicação | Migra a base de dados na inicialização. |
| Sidekiq | Não migra, evitando execução concorrente de migrações. |
| Efémero (manutenção) | Não migra; requer invocação explícita. |
docker run --rm <variáveis> sislab_sync:4.0.0 bin/rails db:prepare# Verificação de estado operacional
curl -s https://<host>/api/v3/health | jq
# Conta administrativa inicial (palavra-passe emitida uma única vez no stdout)
docker exec sislab_sync bin/rails "users:create[Nome Utilizador,email@instituicao.gov.mz,admin]"
# Credenciais de integração para o sistema clínico
docker exec sislab_sync bin/rails api_client:create NAME="EMR Unidade Central" KIND=emr FACILITY_CODE=HCM
docker exec sislab_sync bin/rails api_key:issue CLIENT=HCM SCOPES="orders:write,orders:read,results:read,dictionary:read"
# Carga inicial do catálogo — apenas no nó nacional, uma única vez (ver 6.4)
docker exec sislab_sync bin/rails dictionary:seed ACTOR="Nome de quem aceita o catálogo"Nota técnica: a inserção de dados de demonstração e a geração automática de credenciais administrativas (tmp/demo_credentials.txt) são exclusivas do ambiente development.
Interface web integrada para operadores e administradores, com autenticação por sessão:
| Módulo | Finalidade Operacional | Modos Suportados |
|---|---|---|
| Painel | Monitorização do estado da sincronização, outbox, cursores de revisão e latência de rede. | local, national |
| Pedidos | Consulta e rastreamento de pedidos por número de rastreio, NID do paciente ou nome. | local, national |
| Amostras Referidas | Gestão de amostras em trânsito, receções e rejeições de encaminhamento. | local, national |
| Dicionário | Navegação pelo catálogo ativo (leitura universal; edição e ativação restrita ao nacional). | local, national |
| Fila de Sincronização | Inspeção de eventos de outbox com falha e reprocessamento sob demanda. | local |
| Nós | Monitorização de nós locais conectados, versão de software e atraso de cursor. | national |
| Clientes e Chaves | Emissão, rotação e revogação de chaves de API com controlo de acessos baseado em âmbitos. | local, national (admin) |
| Auditoria | Histórico estruturado de requisições de API e tentativas de acesso negadas. | local, national (admin) |
A especificação formal da API é disponibilizada diretamente pelo nó em /api-docs:
| Endpoint | Formato | Descrição |
|---|---|---|
GET /api-docs |
HTML | Interface interativa de referência e consola de testes da API baseada em Scalar. |
GET /api-docs.json |
JSON | Especificação OpenAPI 3.1 filtrada dinamicamente para os endpoints ativos do nó. |
GET /api-docs.yaml |
YAML | Especificação OpenAPI 3.1 em formato YAML. |
O contrato global completo do sistema encontra-se no ficheiro docs/sislab-sync/openapi.yaml.
A interface interativa é gerida via gem scalar_ruby e configurada em config/initializers/scalar.rb:
Scalar.setup do |config|
config.page_title = "SISLAB Sync · Referência da API"
config.configuration = {
url: "/api-docs.json",
theme: "saturn",
layout: "modern",
showSidebar: true,
searchHotKey: "k"
}
endNa renderização da rota /api-docs, o Scalar::UI injecta o script de cliente autónomo que consome a especificação do nó:
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
<script>
Scalar.createApiReference(
'#app',
{
"url": "/api-docs.json",
"theme": "saturn",
"layout": "modern",
"showSidebar": true,
"searchHotKey": "k"
}
)
</script>A conformidade da especificação OpenAPI é validada em pipeline de CI contra:
- As rotas ativas do framework em cada modo operacional.
- Os enums e constantes de domínio (
OrderStatus,TestStatus,Priority,ErrorCode,SyncEventType). - Os esquemas de validação JSON Schema draft 2020-12 aplicados sobre todas as respostas da suite de testes.
Coleção Postman para testes integrados manuais de ponta a ponta:
docs/sislab-sync/sislab-sync.postman_collection.json.
Os pedidos, amostras e leituras servidos pela API REST nativa são igualmente expostos em HL7 FHIR R4 sob o prefixo /fhir/r4, em modo local. Cada recurso é projetado a partir dos modelos transacionais; a admissão de pedidos reutiliza o mesmo componente OrderRequest da API nativa, incluindo a validação de códigos de dicionário e a expansão de painéis.
| Endpoint | Âmbito | Descrição |
|---|---|---|
GET /fhir/r4/metadata |
— | CapabilityStatement do nó. Não requer autenticação. |
POST /fhir/r4/ServiceRequest |
orders:write |
Cria um pedido com a análise indicada. Um código de painel é expandido nas análises constituintes e a resposta passa a ser um Bundle de tipo collection. |
POST /fhir/r4 |
orders:write |
Bundle de tipo transaction: várias análises sobre uma amostra, agrupadas por requisition. Operação atómica. |
GET /fhir/r4/ServiceRequest/{id} |
orders:read |
Uma análise pedida, identificada pelo uuid. |
GET /fhir/r4/ServiceRequest |
orders:read |
Pesquisa por requisition, identifier, patient, patient.identifier, status, _count. |
GET /fhir/r4/DiagnosticReport/{id} |
results:read |
Relatório de uma análise, com as leituras atuais em contained. |
GET /fhir/r4/DiagnosticReport |
results:read |
Pesquisa por identifier, patient, patient.identifier, status, _count. |
GET /fhir/r4/Observation/{id} |
results:read |
Uma leitura. |
GET /fhir/r4/Observation |
results:read |
Feed de resultados por cursor _since, com paginação por link[next]. |
POST /fhir/r4/Observation/{id}/$acknowledge |
results:read |
Regista a confirmação de arquivamento da leitura pelo sistema clínico. |
GET /fhir/r4/Patient/{id}, GET /fhir/r4/Patient |
orders:read |
Doentes com amostras da unidade sanitária associada à chave. A pesquisa requer identifier. |
GET /fhir/r4/Specimen/{id} |
orders:read |
A amostra, identificada pelo uuid do pedido. |
Autenticação, âmbitos, restrição por unidade sanitária, limitação de taxa, idempotência e trilho de auditoria são comuns à API REST nativa e regem-se pelas regras descritas nas secções anteriores. A fachada difere apenas no formato das respostas: recursos FHIR em application/fhir+json, e OperationOutcome em substituição do envelope data/meta/errors.
Cada CodeableConcept emitido transporta dois coding, pela seguinte ordem:
| Ordem | Sistema | Presença |
|---|---|---|
| 1 | Código nacional (https://sislab.misau.gov.mz/fhir/CodeSystem/test-types e equivalentes) |
Sempre presente. |
| 2 | LOINC (http://loinc.org) |
Presente nas entradas de dicionário com mapeamento curado (ver secção 6.5). |
Na admissão, o código da análise é aceite em qualquer um dos dois sistemas, ou como código nacional com o prefixo MOZ- sem sistema declarado. A cobertura LOINC do catálogo aumenta à medida que a curadoria progride, sem alteração dos clientes.
O vocabulário de estados do FHIR é mais restrito que o do sistema: uma amostra recusada pelo laboratório e um pedido cancelado na unidade sanitária correspondem ambos a revoked. O estado nativo é publicado em extensão:
| Extensão | Conteúdo |
|---|---|
.../StructureDefinition/order-status |
Estado nativo do pedido (requested, accepted, rejected, cancelled, …). |
.../StructureDefinition/test-status |
Estado nativo da análise (pending, in_progress, completed, …). |
.../StructureDefinition/test-panel |
Painel de origem da análise, quando resultante de expansão. |
.../StructureDefinition/revision |
Revisão da leitura, para posicionamento do cursor. |
.../StructureDefinition/replaced-by |
Uuid da leitura que substituiu esta. |
_since é uma revisão sequencial atribuída por contador com bloqueio, e não um timestamp: uma leitura cuja transação demore a confirmar não fica atrás de um cursor já avançado. O cliente segue link[next] até este deixar de existir e retém a última revisão para a consulta seguinte.
As correções não reescrevem a leitura anterior. A leitura substituída volta a mover-se no feed e é emitida como Observation com status: "entered-in-error" e a extensão replaced-by; o DiagnosticReport correspondente passa ao estado corrected, sem reabertura da análise. A operação $acknowledge sobre uma leitura substituída é recusada com 409, identificando a substituta.
curl -X POST http://localhost:3000/fhir/r4/ServiceRequest \
-H "Authorization: Bearer ssk_dev_..." \
-H "Content-Type: application/fhir+json" \
-d '{
"resourceType": "ServiceRequest",
"identifier": [{ "system": "http://emr.local/orders", "value": "EMR-000123" }],
"status": "active", "intent": "order", "priority": "urgent",
"code": { "coding": [{ "system": "http://loinc.org", "code": "718-7" }] },
"subject": { "reference": "#doente" },
"specimen": [{ "reference": "#amostra" }],
"performer": [{ "identifier": { "value": "HCM-LAB" } }],
"contained": [
{ "resourceType": "Patient", "id": "doente",
"identifier": [{ "value": "110100234567A" }],
"name": [{ "text": "Ana Macuácua" }], "gender": "female", "birthDate": "1991-04-12" },
{ "resourceType": "Specimen", "id": "amostra",
"type": { "coding": [{ "code": "MOZ-SP-0001" }] },
"collection": { "collectedDateTime": "2026-09-14T08:20:00+02:00" } }
]
}'Nota técnica: na ausência do cabeçalho Idempotency-Key, o campo identifier do pedido é utilizado como chave de idempotência. Uma repetição com o mesmo identificador e conteúdo devolve a resposta original, sinalizada pelo cabeçalho Idempotent-Replay.
A fachada é descrita em dois documentos complementares, ambos validados em CI:
| Documento | Âmbito | Garantia |
|---|---|---|
docs/sislab-sync/openapi.yaml |
Operações: rotas, parâmetros de pesquisa, âmbitos, códigos de estado e respostas de erro. | Verificação cruzada contra as rotas da aplicação em ambas as direções, em spec/contracts/openapi_spec.rb: uma rota não documentada e uma operação sem rota correspondente produzem falha. As respostas geradas pela suite são validadas contra os esquemas declarados. |
GET /fhir/r4/metadata |
CapabilityStatement, gerado a partir das constantes utilizadas pelos controladores. |
Os parâmetros de pesquisa declarados correspondem aos efetivamente lidos, e os recursos declarados são comparados com os efetivamente roteados. |
Os esquemas dos recursos no openapi.yaml são parciais: declaram o subconjunto que o nó garante emitir e aceitar, mantêm additionalProperties aberto e remetem para a especificação HL7 quanto à definição completa dos recursos.
Cobertura de testes em spec/requests/fhir/ e spec/serializers/fhir/.
Desenvolvimento ativo no ramo v2. Etapas concluídas (S1 a S11): arquitetura dual, gestão de credenciais e âmbitos, motor de dados mestre de dicionário, feed transacional de alterações, API REST para EMR e SISLAB, pipeline assíncrono de outbox, orquestração de referências e interface web de gestão.
Fase atual: S12 — Especificação de Contrato, SDK e Entrega. Concluídos a especificação OpenAPI 3.1 com interface Scalar, validação de esquemas em CI, coleções de referência e a fachada FHIR R4 para sistemas EMR (secção 11). Em desenvolvimento: gem de cliente sislab_sync_client e documentação técnica de integração.
A cobertura LOINC do dicionário permanece por curar: a fachada FHIR opera com códigos nacionais e emite LOINC apenas onde este já foi mapeado.