Skip to content

@fc/rmi-api ​

API REST de cálculo de RMI (Renda Mensal Inicial) para benefícios do RGPS. Expõe OpenAPI em /_openapi.json e deixa /_swagger disponível apenas em desenvolvimento local.

Esta aplicação é um deployment separado por razões operacionais (cálculo de RMI é intensivo em CPU/memória). Do ponto de vista do contrato público, todos os endpoints vivem sob o namespace /rgps/api/v1/* e compartilham o módulo rgps com a prev-api.

Todas as respostas JSON seguem o envelope PDPJ-Br:

json
{
  "status": "ok",
  "code": "200",
  "messages": [],
  "result": {
    /* ... */
  }
}

Geração do spec ​

Para regenerar o spec OpenAPI localmente:

bash
pnpm --filter @fc/rmi-api spec:gen

O comando usa as anotações @swagger dos arquivos em server/routes/** e os tipos declarados em nuxt.config.ts.

O contrato público canônico é /_openapi.json; principal.json é a fonte gerada no build. Ao servir o documento, o runtime acrescenta a origem atual, a proteção por API key e os schemas do envelope de erro, exclui rotas internas e impede cache compartilhado com Cache-Control: no-store. O antigo public/openapi-spec.json não é consumido pela aplicação e está excluído do contexto Docker.

API key estrutural ​

A proteção por x-api-key é declarativa por path, sem anotação nos handlers, e fica ligada por padrão. O nuxt.config.ts fixa o glob /rgps/api/v1/calculos-rmi/**; assim, a rota-base e todas as suas subrotas exigem a chave sem depender de variáveis de ambiente de deploy.

Os GETs de dados de referência /rgps/api/v1/fundamentos e /rgps/api/v1/marcos-temporais não pertencem a esse glob e permanecem públicos.

A chave é validada pela plataforma contra o cadastro central, com cache curto. Em desenvolvimento local, o assistente pode desligar essa proteção para facilitar testes; fora de dev e test, esse override interrompe o boot e o caminho protegido continua fazendo parte da configuração estrutural da app.

Fail-fast no boot: se o glob não casar com nenhuma rota registrada, a subida da app falha com mensagem clara.

Ver @fc/base-api/README.md para a mecânica completa.

Telemetria de uso ​

A rmi-api registra telemetria best effort no fim da resposta HTTP. O envio não participa do fluxo principal; falhas no backoffice entram apenas no log operacional. Quando a rota estiver protegida por API key, o evento reaproveita event.context.apiKeyValidada e preserva o idTokenApiKey sem revalidar a chave.

Eventos emitidos:

AçãoEndpoints
rgps.rmi.apicálculo RMI JSON principal
rgps.rmi.api.streamcálculo RMI via SSE
rgps.rmi.api.asynccriação de job assíncrono JSON e conclusão/erro do job
rgps.rmi.api.arquivo-calccálculo RMI a partir de .calc
rgps.rmi.api.arquivo-calc.streamcálculo RMI via SSE a partir de .calc
rgps.rmi.api.arquivo-calc.asynccriação e conclusão/erro de job assíncrono a partir de .calc
rgps.rmi.api.desvinculadacálculo desvinculado JSON ou .calc
rgps.rmi.api.por-fundamentocálculo por fundamento
rgps.rmi.api.job-statusconsulta do estado de um job
rgps.rmi.api.job-resultadoobtenção do resultado de um job
rgps.rmi.api.job-cancelarcancelamento de um job
rgps.rmi.api.fundamentoslistagem de fundamentos disponíveis
rgps.rmi.api.marcos-temporaislistagem de marcos temporais disponíveis
rgps.documentos.api.arquivo-calcleitura isolada de .calc, vinculada ou desvinculada

O dicionário de @fc/comum é a fonte canônica dos nomes. Um teste de contrato compara o mapa com as rotas físicas nas duas direções, valida cada ação para a origem rmi-api e impede que uma rota nova fique silenciosa. Eventos de conclusão de job carregam apenas fase e endpoint; payload, conteúdo e nome do documento não entram na telemetria. Nos logs operacionais, os prefixos rmi.job.* e arquivo-calc.rmi.job.* distinguem a origem sem depender do nome do arquivo.

Benchmark de latência ​

Há um benchmark reproduzível para comparar os endpoints JSON e SSE equivalentes:

bash
pnpm --filter @fc/rmi-api bench:latency -- --cenario implementacao-postergados --runs 8 --warmup 2

Parâmetros úteis:

  • --listar-cenarios: lista os cenários disponíveis
  • --base-url http://127.0.0.1:8003: aponta para uma instância já em execução
  • --execucao fundamento --fundamento EC_103_ART_20: executa o cálculo por-fundamento para o fundamento informado
  • --marco-temporal NA_DIB: fixa o cálculo em um único marco temporal (valor fora do catálogo de GET /marcos-temporais é rejeitado)
  • --output ./tmp/benchmark-rmi.json: salva o relatório em JSON

Métricas coletadas:

  • TTFB: tempo até os headers da resposta
  • TTLB: tempo até o fim da resposta JSON
  • TTFE: tempo até o primeiro evento útil do SSE
  • Primeiro parcial: tempo até o primeiro fundamento_calculado ou bloco_finalizado
  • TTLE: tempo até o evento final fim

O benchmark usa as amostras de test/amostras/*.json e as converte para CalculoRmiApi com lerArquivoTc, mantendo o payload o mais próximo possível dos casos reais do projeto.

Dependência externa ​

A rmi-api consome a API pública de índices (endpoints /indices/api/v1/*) para obter a tabela de indexadores utilizada nos cálculos. O endereço efetivo é fornecido pela implantação.

Se a API de índices estiver indisponível, os endpoints de cálculo retornam 503 com o seguinte corpo:

json
{
  "error": true,
  "statusCode": 503,
  "statusMessage": "API de índices indisponível",
  "message": "API de índices indisponível"
}

A retenção e a capacidade dos jobs assíncronos são limites operacionais configurados pela implantação. Ao atingir o teto de registros, a API descarta primeiro o resultado terminal mais antigo; jobs ativos não são expulsos. Os cálculos são executados fora do fluxo HTTP principal, mantendo o health check responsivo durante operações longas.

Endpoints ​

Todos os caminhos são servidos sob o namespace /rgps/api/v1/*.

Cálculo principal (JSON) ​

MétodoCaminhoDescrição
POST/rgps/api/v1/calculos-rmiCalcula a RMI para todos os benefícios aplicáveis
POST/rgps/api/v1/calculos-rmi/asyncCria job assíncrono de cálculo de RMI (retorna 202 + jobId)
POST/rgps/api/v1/calculos-rmi/streamCalcula a RMI com streaming SSE por bloco e fundamento
POST/rgps/api/v1/calculos-rmi/por-fundamentoCalcula a RMI apenas para o fundamento informado
POST/rgps/api/v1/calculos-rmi/desvinculadaCalcula RMI desvinculada com parâmetros fornecidos pelo usuário

Fonte externa: arquivo .calc ​

MétodoCaminhoDescrição
POST/rgps/api/v1/calculos-rmi/arquivo-calc/lerLê arquivo .calc de RMI canônica (períodos) e devolve o JSON pronto para o cálculo canônico; arquivo de RMI desvinculada responde 422 orientando ler-desvinculada
POST/rgps/api/v1/calculos-rmi/arquivo-calc/ler-desvinculadaLê arquivo .calc de RMI desvinculada (coeficiente e tempo informados) e devolve o JSON pronto para o cálculo desvinculado; arquivo de RMI canônica responde 422 orientando ler
POST/rgps/api/v1/calculos-rmi/arquivo-calc/calcularLeitura + cálculo em uma única chamada
POST/rgps/api/v1/calculos-rmi/arquivo-calc/calcular/asyncLeitura + cálculo em job assíncrono (retorna 202 + jobId)
POST/rgps/api/v1/calculos-rmi/arquivo-calc/calcular/streamLeitura + cálculo com streaming SSE
POST/rgps/api/v1/calculos-rmi/arquivo-calc/calcular-desvinculadaLeitura + cálculo desvinculado em uma única chamada

Como escolher o leitor. O arquivo .calc de Benefícios RGPS guarda um de dois tipos de RMI: a canônica, derivada dos períodos e das regras de tempo, e a desvinculada (salário-pura), em que o usuário informou coeficiente e tempo. arquivo-calc/ler → POST /rgps/api/v1/calculos-rmi; arquivo-calc/ler-desvinculada → POST /rgps/api/v1/calculos-rmi/desvinculada. Cada leitor recusa (422) o arquivo do outro tipo, em vez de devolver um payload que recalcularia a RMI como zero; arquivo-calc/calcular despacha sozinho e não exige essa escolha.

Jobs (operações assíncronas) ​

MétodoCaminhoDescrição
GET/rgps/api/v1/calculos-rmi/jobs/{jobId}Consulta status, progresso e resultado eventual de job assíncrono
GET/rgps/api/v1/calculos-rmi/jobs/{jobId}/resultadoObtém apenas o resultado final de job assíncrono concluído
DELETE/rgps/api/v1/calculos-rmi/jobs/{jobId}Cancela job pendente ou em processamento da mesma chave

Metadados ​

MétodoCaminhoDescrição
GET/rgps/api/v1/fundamentosLista os fundamentos aceitos nos endpoints filtrados
GET/rgps/api/v1/marcos-temporaisLista os marcos temporais aceitos em marcoTemporal

Os endpoints de upload de arquivo aceitam multipart/form-data com o arquivo no campo file. Os demais aceitam application/json, com exceção dos endpoints SSE, que recebem JSON (ou multipart/form-data para os que têm .calc) e respondem com text/event-stream.

A query opcional numeroMaximoIteracoes limita a quantidade de iterações de descarte nos cálculos canônicos JSON e .calc, inclusive nos modos stream e async, e no cálculo por fundamento. Quando omitida, a API usa 120 como padrão para evitar payloads excessivamente grandes em casos com muitos salários descartáveis.

As rotas síncronas que efetivamente calculam aceitam também permalink=true (ou 1). Quando solicitada, a API persiste um snapshot imutável e reabrível no prev-tc e acrescenta permalink — uma URL pública completa — no topo do envelope, sem alterar result. Omitir a query, ou usar false/0, preserva integralmente o contrato anterior. Os modos async e stream não oferecem esse campo. O snapshot segue a retenção longa da plataforma, atualmente dez anos; o opt-in não amplia os limites de entrada de 10 MiB para JSON e 5 MiB para .calc. Valor diferente dos quatro aceitos responde 400, indisponibilidade da capacidade responde 503 e uma resposta interna sem URL válida responde 502.

As mesmas cinco rotas síncronas aceitam também a query opcional demonstrativo=true (ou 1): o envelope ganha demonstrativoPdf — o demonstrativo do cálculo em PDF, na identidade visual da calculadora, com conteúdo em base64, nome de arquivo sugerido, quantidade de páginas e tamanho em bytes (schema DemonstrativoPdfApi no OpenAPI). As rotas de RMI canônica, por fundamento e .calc devolvem ainda demonstrativoDescartesPdf, o demonstrativo dos cálculos a cada descarte, quando alguma hipótese descartou competências; a RMI desvinculada rende um PDF só. O PDF sempre embute um permalink do cálculo no card de reprodução — por isso a geração cria um snapshot como o do permalink=true, e as duas queries podem ser combinadas sem criação duplicada; a query permalink decide apenas se a URL também sai como campo separado. Cálculo interrompido pelo limite de iterações não vira PDF: com a query, a rota responde 422. Os mesmos códigos valem para a capacidade de permalink (503/502), e falha na geração do PDF responde 500.

Para cálculos longos com upload .calc, prefira:

  • POST /rgps/api/v1/calculos-rmi/arquivo-calc/calcular/stream quando o cliente puder manter a conexão aberta e quiser progresso em tempo real.
  • POST /rgps/api/v1/calculos-rmi/arquivo-calc/calcular/async quando o cliente precisar apenas receber um jobId e consultar o resultado depois via polling.

Limitação conhecida da trilha assíncrona: os jobs ficam no storage local do processo da rmi-api. Em ambientes com múltiplas instâncias, é necessário afinidade de sessão, ou futuramente migrar essa trilha para storage/fila distribuídos.

Cada job pertence à identidade estável (idToken) da API key que o criou. Consulta, resultado e cancelamento exigem a mesma identidade; outra chave recebe o mesmo 404 de um identificador inexistente, sem confirmar a existência do job. A resposta pública expõe somente estado, datas, progresso, erro e resultado eventual — o payload interno de criação não faz parte do contrato público.

As rotas async aceitam o header opcional Idempotency-Key (até 128 caracteres). Repetir a mesma requisição com a mesma chave devolve o jobId original; reutilizá-la com conteúdo diferente responde 409. Quando a capacidade estiver inteiramente ocupada por jobs ativos, a fila de cálculos ou a cota de jobs ativos da chave estiver cheia, a API responde 429; nos limites de jobs, Retry-After: 5 orienta uma nova tentativa curta.

Fechar uma conexão SSE cancela o cálculo correspondente. DELETE /jobs/{jobId} faz o mesmo para jobs pendentes ou em processamento; repetir o cancelamento é seguro, enquanto jobs já concluídos ou encerrados com erro respondem 409.

Semântica HTTP dos jobs ​

RotaSituaçãoHTTPstatus
POST /calculos-rmi/asyncjob criado202in-progress
POST /calculos-rmi/arquivo-calc/.../asyncjob criado202in-progress
GET /jobs/{jobId}pendente / processando200in-progress
GET /jobs/{jobId}concluído200ok
GET /jobs/{jobId}cancelado200ok
GET /jobs/{jobId}inexistente ou de outra chave404error
GET /jobs/{jobId}/resultadoconcluído200ok
GET /jobs/{jobId}/resultadopendente / processando / falhou / cancelado409error
GET /jobs/{jobId}/resultadoinexistente ou de outra chave404error
DELETE /jobs/{jobId}pendente / processando / já cancelado200ok
DELETE /jobs/{jobId}concluído / falhou409error

Resultado de job assíncrono só é entregue em /resultado quando o job está concluído; nos demais estados a resposta é 409 Conflict com mensagem descritiva (alinhada ao Swagger). Para polling genérico — que precisa distinguir "ainda processando" de "já terminou" — use GET /jobs/{jobId} e observe status.

Sinalização de conclusão e truncamento ​

Cada bloco calculado inclui conclusao.completo. Se o limite interromper o descarte antes do fim, o bloco informa:

json
{
  "conclusao": {
    "completo": false,
    "motivoInterrupcao": "LIMITE_ITERACOES",
    "limitesAtingidos": { "numeroMaximoIteracoes": 120 }
  }
}

O evento final fim do SSE e o status do job assíncrono expõem a mesma conclusao, agregada para todos os blocos. Um resultado completo contém apenas { "completo": true }.

Por compatibilidade, respostas HTTP síncronas truncadas também incluem:

x-fc-rmi-iteracoes-limitadas: true
x-fc-rmi-max-iteracoes: <valor aplicado>

O log de auditoria registra iteracoesLimitadas: true no evento rmi.calculado. Clientes devem preferir o campo do corpo, que também sobrevive a fachadas como o MCP, para decidir se reexecutam com um limite maior ou apresentam ressalva ao usuário.

Validação de entrada e contrato de erro ​

Rotas JSON e multipart validam o payload com schemas Zod (apps/rmi-api/server/utils/esquemas.ts) e uploads com validarUploadUnico. Em falha, a resposta é:

  • JSON malformado, Zod inválido, ErroDominio, exceção conhecida do core → 400 + envelope PDPJ com messages descritivas (caminho: mensagem para Zod);
  • upload acima do teto de 5 MiB → 413;
  • arquivo .calc legível, mas incompatível com o endpoint → 422;
  • exceção não classificada → 500 + envelope genérico (erro registrado via logOperacional.error);
  • sempre via tratarErroRota(event, erro) de @base-api/server/utils/erros — nunca createError cru.

Os uploads multipart aceitam tanto content-length quanto transfer-encoding: chunked, sem alterar o teto nem a resposta 413.

Espécie do benefício. opcoesContagem.especie é obrigatória em todas as entradas JSON (canônica, por fundamento, desvinculada e PrevJud): sem ela a resposta é 400 com a mensagem que diz onde informá-la e os códigos aceitos — a API não presume espécie alguma. Aceita todas as espécies do dicionário (aposentadorias 41, 42, 46 e 57; incapacidade 31, 32, 91 e 92; auxílio-acidente 36, 94 e 95; pensões 21 e 93; auxílio-reclusão 25; salário-maternidade 80; amparos 87 e 88), e cada uma segue as próprias regras de RMI. Na RMI desvinculada isso vale igual: com especie: 94, o auxílio-acidente sai com coeficiente de 50%, piso proporcional e sem fator previdenciário; os benefícios não programados aceitam fatoGerador (DII, óbito) quando ele difere da DER. Num arquivo .calc, o campo é parametrosGerais.especie, e o arquivo sem ele também responde 400.

O campo opcional criterioSalarioMaternidade aceita UM_DOZE_AVOS ou MEDIA_SIMPLES_DOZE_ULTIMOS e mantém a escolha presente em um Arquivo .calc. A origem dos períodos usa valores nominais (CALCULO_INSS, CNIS, USUARIO, NENHUMA, PREVJUD ou MULTIPLA); valores divergentes retornam 400. Só CNIS, PREVJUD e CALCULO_INSS têm coluna própria no salário; USUARIO, MULTIPLA e a origem omitida entram como valor digitado pelo usuário, e o resultado os rotula como USUARIO — o número não muda. Nos resultados, cada benefício traz fundamento (a citação legal legível, EC 103, art. 20) e chaveFundamento (a chave, EC_103_ART_20) — a mesma aceita em POST /calculos-rmi/por-fundamento e listada em valor por GET /fundamentos, que devolve a citação em rotulo. As chaves de diferencas nos descartes usam a citação.

Convenções ​

Princípio central: um endpoint = um contrato (input único, output único).

  • Recurso (plural hifenizado): calculos-rmi.
  • Variantes de cálculo (sub-path): por-fundamento, desvinculada.
  • Fonte externa (sub-recurso aninhado): arquivo-calc/ler, arquivo-calc/calcular.
  • Modo de entrega (sub-path): async (202 + jobId), stream (SSE). Apenas calculos-rmi canônico e arquivo-calc/calcular canônico têm modos async/stream; as variantes são exclusivamente síncronas.

Implementação interna ​

Os fluxos de cálculo desta app usam a superfície central do core para manter a mesma normalização entre web, endpoints JSON e leitura de .calc:

  • entradas JSON usam DadosCalculo.deApiRmi(...) e DadosCalculo.deApiRmiDesvinculada(...)
  • entradas .calc usam DadosCalculo.deArquivoTc(...)
  • saídas serializadas usam CalculoRmi.paraApiBloco(...) e CalculoRmi.paraApiDesvinculada(...)

Os módulos legados em @fc/calculos-previdenciarios/utilidades-apis/* seguem exportados como compatibilidade transitória, mas novas integrações internas devem preferir essa superfície centralizada.

Logs ​

A aplicação emite logs estruturados em JSON via a classe Logger de @fc/utils. Todos os logs são escritos em stdout, prontos para ingestão por qualquer coletor (Loki, Elasticsearch, etc.).

Tipos de log ​

TagDescrição
rmi-apiLog operacional — registra toda requisição recebida
rmi-api, auditoriaLog de auditoria — registra cada operação de cálculo/leitura concluída

Os loggers vêm de @base/server/utils/loggers e usam como primeira tag o valor de nomeApp declarado em app/app.config.ts (aqui: rmi-api):

  • logOperacional — usado pelo middleware de log da layer @fc/base-api para registrar todas as requisições com a URL acessada.
  • logAuditoria — usado em cada endpoint para registrar a operação realizada.

Filtragem ​

Para isolar logs de auditoria em um coletor centralizado, filtre pela presença da tag auditoria:

json
{ "tags": ["rmi-api", "auditoria"], ... }