Pular para o conteúdo principal

Integração de Lista de Trabalho

A API de Lista de Trabalho do TranscriMed permite integração bidirecional perfeita com sistemas RIS/PACS, possibilitando que prestadores de saúde otimizem seu fluxo de documentação médica.

Visão Geral

A integração de lista de trabalho do TranscriMed oferece:

  • Integração de Entrada: Receber itens de lista de trabalho do seu sistema RIS/PACS
  • Processamento: Médicos visualizam, selecionam e processam exames no TranscriMed
  • Integração de Saída: Enviar automaticamente documentos médicos completos de volta ao sistema de origem

Como Funciona

  1. Ingestão de Lista de Trabalho: Seu sistema RIS/PACS envia dados de exames para o TranscriMed
  2. Seleção do Médico: Profissionais médicos visualizam exames disponíveis e selecionam os relevantes
  3. Documentação: Médicos gravam áudio para os exames selecionados
  4. Processamento: TranscriMed gera documentos médicos estruturados
  5. Entrega: Documentos completos são enviados automaticamente de volta ao seu sistema

Formato de Dados

Utilizamos nomes de campos inspirados no DICOM em formato snake_case para compatibilidade com API REST:

Campos Principais

CampoTipoDescriçãoExemplo
accession_numberstringIdentificador único do exame"2024-001234"
study_uidstringUID da Instância do Estudo DICOM"1.2.826.0..."
patient_idstringIdentificador do paciente"PAT-456789"
patient_namestringNome completo do paciente"João Silva"
modalitystringModalidade do exame"CR", "CT", "MR"
exam_datetimestringData/hora do exame (ISO 8601)"2024-01-15T10:30:00Z"
exam_descriptionstringDescrição do procedimento"Raio-X de Tórax PA/Perfil"

Campos Opcionais

CampoTipoDescrição
patient_sexstringSexo do paciente (M/F/O/U)
patient_birth_datestringData de nascimento (ISO 8601)
patient_agestringIdade em formato livre
exam_roomstringSala onde o exame é realizado
referring_physicianstringMédico solicitante
hospital_namestringNome da instituição
locationstringDepartamento/localização dentro da instituição
procedure_idstringIdentificador do procedimento
metadataobjectDados adicionais específicos do sistema

Autenticação

1. Registrar Sua Aplicação

  1. Visite o Portal do Desenvolvedor
  2. Crie uma conta ou faça login
  3. Registre sua aplicação com:
    • Nome da aplicação
    • Nome da empresa/organização
    • URI de redirecionamento (para fluxo OAuth2)

2. Solicitar Escopos

Sua aplicação precisa destes escopos:

  • worklists:write - Enviar itens de lista de trabalho para o TranscriMed
  • worklists:manage - Atualizar status de item da lista de trabalho (opcional)

3. Obter Token de Acesso

Use o fluxo padrão de código de autorização OAuth2:

// Passo 1: Redirecionar usuário para URL de autorização
const authUrl = `https://api.transcrimed.com.br/api/oauth/authorize?` +
`client_id=${clientId}&` +
`response_type=code&` +
`scope=worklists:write&` +
`redirect_uri=${redirectUri}`;

// Passo 2: Trocar código por token de acesso
const response = await fetch('https://api.transcrimed.com.br/api/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: clientId,
client_secret: clientSecret,
code: authorizationCode,
redirect_uri: redirectUri
})
});

const { access_token } = await response.json();

Enviando Itens de Lista de Trabalho

Exemplo Básico

const worklistItems = [
{
accession_number: "2024-001234",
patient_id: "PAT-456789",
patient_name: "João Silva",
patient_sex: "M",
patient_birth_date: "1980-05-15",
modality: "CR",
exam_datetime: "2024-01-15T10:30:00Z",
exam_room: "Sala 1",
exam_description: "Raio-X de Tórax PA/Perfil",
referring_physician: "Dr. Maria Garcia",
hospital_name: "Hospital Central"
}
];

const response = await fetch('https://api.transcrimed.com.br/api/v1/worklists/ingest', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'id-unico-requisicao-12345'
},
body: JSON.stringify(worklistItems)
});

const result = await response.json();
console.log('Resultado da ingestão:', result);

Formato de Resposta

{
"success": true,
"data": {
"batch_id": "batch_12345-67890",
"inserted": 1,
"updated": 0,
"deduped": 0,
"errors": []
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2024-01-15T10:30:00Z"
}
}

Gerenciando Status da Lista de Trabalho

Você pode opcionalmente atualizar o status dos itens da lista de trabalho:

const response = await fetch(`https://api.transcrimed.com.br/api/v1/worklists/items/${itemId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
status: 'in_progress',
reason: 'Exame iniciado na Sala 1'
})
});

Valores de Status Disponíveis

  • released – Documento liberado (novo padrão)
  • cancelled – Exame cancelado

Compatibilidade: a API ainda aceita completed e o converte automaticamente em released.

Recebendo Documentos Completos

Quando um médico completa um documento médico para um item da lista de trabalho, o TranscriMed tentará automaticamente entregá-lo de volta ao seu sistema usando o endpoint webhook configurado.

Configurando Webhooks

  1. No Portal do Desenvolvedor, configure seu endpoint webhook
  2. Defina a URL do endpoint onde deseja receber documentos completos
  3. Gere e armazene com segurança seu segredo webhook para verificação de assinatura

Exemplo de Payload do Webhook

{
"event": "document.completed",
"worklist_item_id": "550e8400-e29b-41d4-a716-446655440000",
"accession_number": "2024-001234",
"document": {
"id": "doc-uuid-aqui",
"title": "Laudo de Raio-X de Tórax",
"content": "<html>Conteúdo completo do laudo médico...</html>",
"format": "html",
"created_at": "2024-01-15T11:30:00Z"
},
"patient": {
"id": "PAT-456789",
"name": "João Silva"
},
"metadata": {
"processing_duration_ms": 45000,
"template_used": "laudo-radiologia"
}
}

Segurança do Webhook

Todas as requisições webhook são assinadas com HMAC-SHA256:

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');

return signature === `sha256=${expectedSignature}`;
}

// No seu endpoint webhook
app.post('/webhooks/transcrimed', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const payload = JSON.stringify(req.body);

if (!verifyWebhookSignature(payload, signature, webhookSecret)) {
return res.status(401).send('Assinatura inválida');
}

// Processar o webhook
console.log('Documento completo:', req.body);
res.status(200).send('OK');
});

Melhores Práticas

1. Idempotência

Sempre inclua um cabeçalho Idempotency-Key para evitar processamento duplicado:

const idempotencyKey = `${systemId}-${timestamp}-${accessionNumber}`;

fetch('/api/v1/worklists/ingest', {
headers: {
'Idempotency-Key': idempotencyKey
}
// ... outras opções
});

2. Tratamento de Erros

Implemente tratamento adequado de erros e lógica de repetição:

async function ingestWorklistItems(items, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch('/api/v1/worklists/ingest', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'Idempotency-Key': generateIdempotencyKey()
},
body: JSON.stringify(items)
});

if (response.ok) {
return await response.json();
}

if (response.status === 401) {
// Renovar token de acesso
await refreshAccessToken();
continue;
}

throw new Error(`HTTP ${response.status}: ${response.statusText}`);

} catch (error) {
console.error(`Tentativa ${attempt} falhou:`, error);

if (attempt === maxRetries) {
throw error;
}

// Backoff exponencial
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);
}
}
}

3. Validação de Dados

Valide seus dados antes de enviar:

function validateWorklistItem(item) {
const errors = [];

// Pelo menos um identificador obrigatório
if (!item.accession_number && !item.study_uid) {
errors.push('accession_number ou study_uid é obrigatório');
}

// Validar modalidade
const validModalities = ['CR', 'CT', 'MR', 'US', 'XA', 'RF', 'DX', 'MG'];
if (item.modality && !validModalities.includes(item.modality)) {
errors.push(`Modalidade inválida: ${item.modality}`);
}

// Validar formato de data
if (item.exam_datetime && !isValidISO8601(item.exam_datetime)) {
errors.push('exam_datetime deve estar no formato ISO 8601');
}

return errors;
}

4. Limitação de Taxa

Respeite os limites de taxa e implemente estratégias de backoff:

class RateLimitedClient {
constructor(accessToken) {
this.accessToken = accessToken;
this.requestQueue = [];
this.processing = false;
}

async makeRequest(url, options) {
return new Promise((resolve, reject) => {
this.requestQueue.push({ url, options, resolve, reject });
this.processQueue();
});
}

async processQueue() {
if (this.processing || this.requestQueue.length === 0) return;

this.processing = true;

while (this.requestQueue.length > 0) {
const { url, options, resolve, reject } = this.requestQueue.shift();

try {
const response = await fetch(url, options);

if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
await new Promise(resolve =>
setTimeout(resolve, (retryAfter || 60) * 1000)
);
this.requestQueue.unshift({ url, options, resolve, reject });
continue;
}

resolve(await response.json());
} catch (error) {
reject(error);
}

// Pequeno atraso entre requisições
await new Promise(resolve => setTimeout(resolve, 100));
}

this.processing = false;
}
}

Testando

Modo de Teste

Use credenciais de teste para experimentar sem afetar a produção:

  1. Solicite credenciais de teste com prefixo test_tc_
  2. Use o mesmo endpoint de produção
  3. A API automaticamente retorna dados fictícios para clientes de teste
const testClient = {
client_id: 'test_tc_abc123',
client_secret: 'test_tcs_xyz789'
};

// Isso ativará automaticamente o modo de teste
const response = await fetch('/api/v1/worklists/ingest', {
headers: {
'Authorization': `Bearer ${testAccessToken}`
},
body: JSON.stringify(testWorklistItems)
});

Dados de Teste Exemplo

Use dados de teste realistas:

const testWorklistItems = [
{
accession_number: "TEST-2024-001",
patient_id: "TEST-PAT-001",
patient_name: "Paciente Teste",
patient_sex: "M",
patient_birth_date: "1980-01-01",
modality: "CR",
exam_datetime: "2024-01-15T10:00:00Z",
exam_description: "Raio-X de Tórax Teste",
referring_physician: "Dr. Médico Teste",
hospital_name: "Hospital Teste"
}
];

Solução de Problemas

Problemas Comuns

Erros de Autenticação (401)

  • Verifique se seu token de acesso é válido e não expirou
  • Confirme que você tem os escopos necessários (worklists:write)
  • Certifique-se de estar usando as credenciais de cliente corretas

Erros de Validação (400)

  • Verifique se todos os campos obrigatórios estão presentes
  • Confirme que os formatos de data são ISO 8601
  • Certifique-se de que os códigos de modalidade são válidos
  • Pelo menos um de accession_number ou study_uid deve ser fornecido

Limitação de Taxa (429)

  • Implemente backoff exponencial
  • Respeite o cabeçalho Retry-After
  • Considere reduzir a frequência de requisições

Problemas de Entrega de Webhook

  • Verifique se seu endpoint webhook está acessível
  • Confirme a implementação da verificação de assinatura
  • Certifique-se de que seu endpoint responde com status 200
  • Revise os logs de webhook no Portal do Desenvolvedor

Modo de Debug

Habilite log detalhado para solucionar problemas:

const DEBUG = process.env.NODE_ENV === 'development';

async function debugRequest(url, options) {
if (DEBUG) {
console.log('Requisição:', { url, options });
}

const response = await fetch(url, options);
const data = await response.json();

if (DEBUG) {
console.log('Resposta:', {
status: response.status,
headers: Object.fromEntries(response.headers),
data
});
}

return { response, data };
}

Precisa de Ajuda?

Pronto para integrar? Comece com nosso Guia de Início ou explore a Referência da API.