Assinar e corrigir o laudo — API
Torna o laudo oficial ao assiná-lo (padrão, complementar por médico adicional, ou pré-assinatura por residente), e permite corrigir o texto de um laudo já assinado sem reabrir o fluxo de assinatura.
Funcionamento
Uma única rota, POST /reports/:id/signatures, cobre os três tipos de assinatura via o campo
kind do corpo:
Sem kind (assinatura padrão, com propagação a conjugados).
- Busca o laudo mais recente do exame apontado (o "âncora") e a lista de exames "conjugados" (irmãos da mesma solicitação, quando houver). Sem conjugados, assina só o laudo âncora.
- Com conjugados, tenta assinar o laudo mais recente de cada exame conjugado (âncora incluída), na mesma sequência de validações abaixo; a falha em um conjugado é só logada — não interrompe os demais nem falha a chamada. Ao final, remove a marcação de "exame duplicado" do exame âncora, best-effort.
- Para cada laudo assinado individualmente:
- Laudo não encontrado → recusa.
- Laudo sem conteúdo nenhum (
htmlvazio e não é OIT/mamografia/ecocardiograma) → recusa — não é possível assinar um laudo vazio. - Laudo já
SIGNED→ recusa. - Laudo
PRE_SIGNEDpelo próprio ator que está tentando assinar agora → recusa — quem pré-assinou como residente não pode fazer a assinatura final do próprio pré-laudo. - Exame excluído (soft delete) → recusa distinta de "exame não encontrado".
- Confirma elegibilidade física do ator na unidade do exame e que os formulários de preparo do exame/laudo estão completos.
- Se já existe um radiologista atribuído ao exame e esse radiologista já atuou no laudo → recusa (mesma família de erro do caso "pré-assinante tentando assinar").
- Se o laudo é OIT, recalcula as iniciais do leitor a partir do nome do ator (ignorando títulos como "dr"/"dra") e grava nas extensões do formulário.
- Verifica se há assinatura digital disponível para o ator (token SafeID presente e a integração habilitada) — se sim, marca o laudo como digitalmente assinado.
- Persiste a assinatura. Atualiza o status do exame para
SIGNED(primeira assinatura) ouRESIGNED(reassinatura); se o exame estava em preparo pendente e vai mudar de status, refaz a checagem completa de preparo. Remove marca de duplicata se houver. Reatribui a responsabilidade de radiologista ao assinante. - Registra o laudo assinado para entrega em integração externa.
- Grava evento de auditoria de assinatura.
- Após o commit da transação, dispara best-effort a geração do PDF final (síncrona à assinatura, mas seu resultado não bloqueia nem reverte a assinatura em si).
kind: "COMPLEMENT" (assinatura complementar por médico adicional).
- Só é aceita sobre um laudo já
SIGNED. É recusada se o próprio assinante original tentar complementar o próprio laudo, ou se já existe um médico adicional registrado (bloqueio de complemento duplo). - Confirma elegibilidade física do ator na unidade.
- Registra o ator com o papel "médico adicional" no laudo — não muda
signerUserIdnem o status do laudo, só adiciona o papel.
kind: "RESIDENT" (pré-assinatura por residente).
- Recusada se o laudo já está
SIGNED. - Confirma elegibilidade física do residente na unidade.
- Move o laudo para o status
PRE_SIGNEDcom o residente registrado — não é a assinatura final; um médico precisa concluir depois com a assinatura padrão (e não pode ser o mesmo residente, conforme a regra de assinatura padrão acima).
POST /reports/:id/revisions (correção ortográfica pós-assinatura).
- Laudo precisa existir e estar
SIGNED— senão recusa. - O ator que corrige não pode ser o próprio assinante original — a correção exige um segundo par de olhos.
- Confirma que o ator tem a permissão de assinatura na unidade do laudo e elegibilidade física.
- Aplica a correção de texto (
html) — o status e osignerUserIddo laudo não mudam, só o conteúdo. - Se
addAsReviewer: true, registra o ator com o papel de "revisor ortográfico" (se ainda não tiver) e reivindica a responsabilidade de revisor no exame. - Grava evento de auditoria e, após o commit, dispara o mesmo processamento pós-assinatura (PDF) do fluxo de assinatura normal.
Endpoints
| Método | Rota | Descrição |
|---|---|---|
| POST | /v1/reports/:id/signatures | Assina o laudo (padrão / complementar / residente, via kind) |
| POST | /v1/reports/:id/revisions | Corrige o texto de um laudo já assinado |
Versão: v1
Swagger: POST /reports/{id}/signatures · Rota (Dev): http://localhost:3000/v1/reports/{id}/signatures
Lógica de decisão da assinatura padrão (sem kind), aplicada ao laudo âncora e a cada conjugado:
Permissões
| Rota | Guards | Permissão / escopo |
|---|---|---|
POST /reports/:id/signatures | JwtAuthenticationGuard, AuthorizationGuard, SensitiveRouteGuard, DiagnosisClinicalLicenseGuard | REPORT_SIGN, escopo resource:report |
POST /reports/:id/revisions | JwtAuthenticationGuard, AuthorizationGuard | REPORT_WRITE, escopo resource:report — mais a checagem interna de REPORT_SIGN na unidade |
SensitiveRouteGuard exige que o token traga assurance: "MFA" e que o step-up de MFA não seja
mais antigo que o limite configurado para a classe de ação OTHER_PRIVILEGED
(ApplicationPrivilegedAccessPolicy). Sem MFA → 403 INSUFFICIENT_ASSURANCE; MFA velho demais →
403 MFA_STEP_UP_REQUIRED; sem usuário autenticado → 401.
DiagnosisClinicalLicenseGuard exige licença profissional ativa (hasActiveProfessionalLicense)
— senão 403 CLINICAL_LICENSE_REQUIRED. Assinar um laudo exige MFA recente e licença
profissional ativa — os dois guards, além da permissão REPORT_SIGN.
Path parameters
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | UUID | Sim | Id do laudo (na assinatura padrão, funciona como âncora para propagação a conjugados) |
Body
POST /reports/:id/signatures — SignDiagnosisReportRequest:
json{ "kind": "COMPLEMENT" }
| Campo | Tipo | Obrigatório | Validação |
|---|---|---|---|
kind | "STANDARD" | "COMPLEMENT" | "RESIDENT" | Não | @IsIn; ausente = assinatura padrão |
POST /reports/:id/revisions — ReviseDiagnosisReportSpellingRequest:
json{ "html": "<p>texto corrigido</p>", "addAsReviewer": true }
| Campo | Tipo | Obrigatório | Validação |
|---|---|---|---|
html | string | Sim | @IsString @IsNotEmpty |
addAsReviewer | boolean | Não | @IsBoolean — registra o ator como revisor ortográfico do laudo/exame |
Response
200 — DiagnosisReportResponse (mesmo formato de
Criar, editar, consultar e excluir laudo), refletindo
statusId: "SIGNED"/"RESIGNED"/"PRE_SIGNED", signerUserId, signedAt e
isDigitallySigned conforme o tipo de assinatura.
Erros
| Classe de erro | errorCode | Status | Quando ocorre |
|---|---|---|---|
DiagnosisReportNotFoundError | REPORT_NOT_FOUND | 404 | laudo inexistente |
DiagnosisReportEmptyError | REPORT_EMPTY | 400 | assinar laudo sem conteúdo nenhum |
DiagnosisReportAlreadySignedError | REPORT_SIGNED | 400 | laudo já SIGNED |
DiagnosisPreSignedUserError | PRE_SIGNED_USER | 400 | quem pré-assinou tenta fazer a assinatura final do próprio pré-laudo, ou radiologista já atribuído já atuou no laudo |
DiagnosisExamExcludedError | EXAME_EXCLUIDO | 400 | exame do laudo foi excluído |
DiagnosisReportNotSignedError | REPORT_NOT_SIGNED | 400 | guarda defensiva pós-persistência (não deveria ocorrer em uso normal); também lançado por complementSign/reviseSpelling quando o laudo não está SIGNED |
DiagnosisReportTransitionNotAllowedError | REPORT_COULD_NOT_SIGN | 400 | complemento pelo próprio assinante ou com médico adicional já registrado; correção pelo próprio assinante |
ForbiddenAction (INSUFFICIENT_ASSURANCE/MFA_STEP_UP_REQUIRED) | — | 403 | SensitiveRouteGuard: sem MFA ou MFA expirado |
ForbiddenAction (CLINICAL_LICENSE_REQUIRED) | — | 403 | DiagnosisClinicalLicenseGuard: sem licença profissional ativa |
| (validação de preparo do exame) | PREPARATION_FIELDS_MISSING | 400 | formulários de preparo do exame incompletos (documentado no Swagger da rota; serviço não detalhado neste levantamento) |
Regras de negócio
| ID | Regra | Comportamento esperado |
|---|---|---|
| RN-01 | TOTP/MFA e licença profissional são pré-condição de qualquer assinatura | os dois guards rodam antes de qualquer lógica de negócio |
| RN-02 | Não se assina laudo vazio | REPORT_EMPTY, exceto quando é OIT/mamografia/ecocardiograma estruturado |
| RN-03 | Quem pré-assina como residente não conclui a própria assinatura | PRE_SIGNED_USER |
| RN-04 | Assinatura padrão propaga a conjugados, tolerando falhas parciais | falha num conjugado é logada, não interrompe os demais nem falha a chamada |
| RN-05 | Assinatura digital (SafeID) é decidida pelo token do ator, não por uma config global | isEnabled() && actor.hasSafeId && actor.hasSafeIdAccessToken |
| RN-06 | Assinatura complementar não pode ser feita pelo próprio assinante nem duplicada | REPORT_COULD_NOT_SIGN nos dois casos |
| RN-07 | Correção ortográfica exige um segundo médico | o próprio assinante não pode corrigir o próprio laudo |
| RN-08 | Correção não muda status nem assinante do laudo | só o html muda; o laudo continua SIGNED |
| RN-09 | Geração do PDF pós-assinatura é assíncrona e best-effort | falha na geração/assinatura digital do PDF não desfaz a assinatura do laudo — vira um item de reprocessamento (ver Geração e impressão de PDF) |
Compliance
| Órgão / norma | Exigência | Como a rota atende |
|---|---|---|
| CFM / ICP-Brasil | assinatura eletrônica com verificação forte de identidade | MFA recente obrigatório (SensitiveRouteGuard) + licença profissional ativa (DiagnosisClinicalLicenseGuard) antes de qualquer assinatura; assinatura digital ICP-Brasil via SafeID quando o ator está habilitado |
| LGPD / HIPAA | trilha de quem assinou/complementou/corrigiu, quando e com que resultado | eventos de auditoria diagnosis.report.signed, .complement-signed, .spelling-revised, .pre-signed-by-resident |
| ANVISA (indireto) | laudo assinado não é alterado silenciosamente | conteúdo de laudo assinado só muda por rota própria e auditada (esta página), nunca pelo PATCH genérico |
Variáveis de ambiente
| Variável | Uso | Obrigatória |
|---|---|---|
(SafeID / DiagnosisDigitalSignatureClient) | Habilita/desabilita a assinatura digital ICP-Brasil e a comunicação com o provedor externo | A confirmar — responsável: time de Diagnosis; data: 24/09/2026. (implementação do cliente não lida neste levantamento) |
Tempo médio de resposta
A confirmar — responsável: time de Diagnosis; data: 24/09/2026. Não há medição publicada; a
assinatura envolve, no mínimo, uma chamada síncrona ao guard de MFA e, quando SafeID está
habilitado, uma chamada de rede ao provedor externo — tempos não medidos neste levantamento.
Requisitos não funcionais
| Requisito | Definição |
|---|---|
| Idempotência | Não — assinar duas vezes o mesmo laudo falha na segunda (REPORT_SIGNED); corrigir duas vezes aplica duas mudanças |
| Auditoria | Sim — um evento por assinatura/complemento/correção |
| Efeito colateral assíncrono | Sim — geração de PDF best-effort após o commit, ver Geração e impressão de PDF |