Skip to content

Adiciona endpoints de leitura e gravação em lote da sessão plenária - #3866

Open
zwinglio wants to merge 4 commits into
interlegis:3.1.xfrom
zwinglio:lote-sessao-plenaria
Open

zwinglio wants to merge 4 commits into
interlegis:3.1.xfrom
zwinglio:lote-sessao-plenaria

Conversation

@zwinglio

@zwinglio zwinglio commented Oct 13, 2026 •

Copy link
Copy Markdown

Descrição

Adiciona dois endpoints na sessão plenária para que painéis e integrações montem e sincronizem uma sessão com uma requisição no início e uma no fim. Hoje são centenas.

Requisições por sessão

Exemplo: Casa com 21 vereadores, sessão com 20 matérias votadas nominalmente e 10 matérias lidas no expediente.

Antes Depois
Montar o painel (início da sessão) ~20 GETs paginados, já usando expand e filtros __in 1 GET .../painel/
Sincronizar a sessão (fim) 522 requisições 1 POST .../lote/

As 522 requisições da sincronização:

O que é gravado Requisições
Presenças da sessão 21
Presenças da ordem do dia 21
Votações: 1 registro + 21 votos + 1 PATCH na ordem do dia, por matéria 20 × 23 = 460
Leituras: 1 registro + 1 PATCH no expediente, por matéria 10 × 2 = 20
Total 522

De modo geral, são 2 × P + M × (P + 2) + 2 × L requisições (P parlamentares, M matérias votadas nominalmente, L matérias lidas). Com este PR, a sincronização passa a ser 1 requisição ao final da sessão, apenas para sincronização.

GET /api/sessao/sessaoplenaria/{id}/painel/

Devolve tudo o que o painel precisa: sessão, tipo, casa, parlamentares, mandatos, filiações e partidos, mesa diretora, presenças da sessão e da ordem do dia, expedientes, matérias do expediente e da ordem do dia, matérias, autorias e autores, registros de votação, votos, leituras e oradores (expediente, ordem do dia e explicações pessoais).

  • Cada bloco usa o mesmo serializer do endpoint de listagem do model, com o mesmo formato de /api/<app>/<model>/.
  • Permissões: são as mesmas das listagens. Todos esses models são de leitura pública, então o endpoint dispensa login, como propõe a Painel eletrônico: endpoint /dados não expõe todas as informações da sessão (mesa diretora, presenças, expedientes, matérias, oradores) #3843. Se alguma regra deixar de ser pública, o endpoint responde 403 com os models que faltam.
  • Visibilidade: segue restringe_sessoes_visiveis. Sessões futuras ainda ocultas ao público respondem 404 para anônimos.
  • Desempenho: o número de consultas não cresce com o tamanho da sessão. Relacionamentos usados no __str__, campos many-to-many e metadata são carregados uma vez por bloco. Com ~620 registros, a resposta faz menos de 100 consultas (antes do ajuste eram 2.681). Para isso, SaplSerializerMixin.get_metadata passou a aceitar metadata pré-carregado no contexto do serializer. Sem esse contexto, o comportamento é o mesmo de antes.

Documentação: docs/api-sessao-painel.md.

POST /api/sessao/sessaoplenaria/{id}/lote/

Grava numa única requisição:

Bloco Equivale a
presencas_sessao POST/DELETE /api/sessao/sessaoplenariapresenca/
presencas_ordem_dia POST/DELETE /api/sessao/presencaordemdia/
votacoes POST /api/sessao/registrovotacao/ + um POST /api/sessao/votoparlamentar/ por voto
leituras POST/PATCH/DELETE /api/sessao/registroleitura/
ordens_dia PATCH /api/sessao/ordemdia/{id}/
expedientes_materia PATCH /api/sessao/expedientemateria/{id}/
  • Qualquer combinação de blocos. O que não for enviado não é alterado.
  • Itens independentes. Cada item roda na própria transação, então a falha de um não impede os demais. Uma votação (registro e votos) é um item só: é gravada inteira ou não é gravada.
  • Relatório por item. Cada item volta como criado, atualizado, removido, inalterado ou erro (com erros), além de um resumo. Assim o cliente reenvia só o que falhou.
  • Idempotente. Reenviar o mesmo lote devolve inalterado e não duplica presenças, registros, votos nem leituras.
  • Mesmas validações e permissões. Os dados passam pelos serializers do drfautoapi de cada model, e cada item exige as mesmas permissões sessao.add_*/change_*/delete_* do endpoint individual. A action usa IsAuthenticated porque o SaplModelPermissions padrão exigiria add_sessaoplenaria para um POST nesse viewset.
  • Escopo da sessão. Itens da pauta precisam pertencer à sessão da URL, e o lote não permite mover matéria de sessão.
  • Votação nominal: os totais são calculados a partir dos votos (Sim, Não, Abstenção, Não Votou), e a lista substitui os votos da matéria, como ao finalizar a votação nominal na tela. Simbólica/secreta: informe numero_votos_sim, numero_votos_nao e numero_abstencoes.
  • Leitura: lida: true cria ou atualiza a leitura; lida: false remove, como "retirar leitura" na tela.
  • Limite de 500 itens por bloco.

Documentação: docs/api-sessao-lote.md.

Os endpoints individuais seguem sem alteração.

Issue Relacionada

Resolve #3843 (dados completos da sessão via API pública para painel). Inclui também o caminho de escrita dessas integrações.

Motivação e Contexto

Na Câmara Municipal de Parnamirim/RN mantemos um painel e outras integrações que consomem o SAPL via API autenticada. Com as regras de firewall adotadas por causa do volume de requisições de IA, essas integrações passaram a sofrer throttle e negação de serviço, mesmo enfileirando cerca de uma requisição por minuto. Cada requisição passou a levar de 2 a 3 segundos.

O problema de fundo é que a API só aceita um registro por requisição, tanto para ler quanto para gravar uma sessão. Com estes endpoints, a integração baixa a sessão uma vez, trabalha localmente e envia tudo de uma vez ao final. Se algo falhar, reenvia apenas os itens com erro.

Como Isso Foi Testado?

  • sapl/sessao/tests/test_sessao_painel_api.py (6 testes): acesso público, conteúdo completo de uma sessão com 21 parlamentares e 20 votações nominais (420 votos), mesmo formato da listagem, isolamento entre sessões, número de consultas limitado, sessão futura oculta ao público e sessão inexistente.
  • sapl/sessao/tests/test_sessao_lote.py (16 testes): autenticação, sessão inexistente, payload inválido, idempotência de presenças, remoção de presença, falha de um item sem afetar os demais, votação nominal (criação, reenvio e atualização), votação inválida sem gravação parcial, votação simbólica de expediente, alvo de outra sessão ou ambíguo, leituras (criação, reenvio, atualização, retirada e duplicadas), atualização e idempotência da ordem do dia, bloqueio de troca de sessão e permissões por bloco com usuário comum.
  • pytest sapl/sessao sapl/api sapl/painel sapl/materia sapl/base: os 22 testes novos passam. As falhas restantes (TestResumoView, test_numero_duplicado_sessao_plenaria_form e testes de formulário em sapl/base) são as mesmas da 3.1.x sem este PR.
  • manage.py spectacular: o schema é gerado com as novas rotas.
  • Ambiente: Python 3.12, Django 2.2.28, PostgreSQL 14.

Tipos de Mudanças

  • Bug fix (alteração que corrige uma issue e não altera funcionalidades já existentes)
  • Nova feature (alteração que adiciona uma funcionalidade e não altera funcionalidades já existentes)
  • Alteração disruptiva (Breaking change) (Correção ou funcionalidade que causa alteração nas funcionalidades existentes)

Checklist:

  • Eu li o documento de Contribuição (CONTRIBUTING).
  • Meu código segue o estilo de código deste projeto.
  • Minha alteração requer uma alteração na documentação.
  • Eu atualizei a documentação de acordo.
  • Eu adicionei testes para cobrir minhas mudanças.
  • Todos os testes novos e existentes passaram.

@zwinglio zwinglio changed the title Adiciona endpoint de gravação em lote da sessão plenária Adiciona endpoints de leitura e gravação em lote da sessão plenária Oct 14, 2026
@zwinglio
zwinglio force-pushed the lote-sessao-plenaria branch from 33d6a62 to 2498b9d Compare October 14, 2026 01:31
POST /api/sessao/sessaoplenaria/{id}/lote/ grava numa única requisição
presenças da sessão, presenças da ordem do dia, votações (registro e votos
nominais) e atualizações de matérias da ordem do dia e do expediente.

Cada item é gravado em sua própria transação e devolve um status
(criado, atualizado, removido, inalterado ou erro). O reenvio do mesmo
lote é idempotente. Validações e permissões são as mesmas dos endpoints
individuais, que seguem sem alteração.
GET /api/sessao/sessaoplenaria/{id}/painel/ devolve numa única requisição
sessão, mesa, parlamentares, presenças, expediente, ordem do dia, matérias,
autorias, votações, votos, leituras e oradores, no mesmo formato dos
endpoints de listagem.

Segue as permissões de leitura de cada model e a visibilidade pública das
sessões. O número de consultas ao banco não cresce com o tamanho da sessão.
@zwinglio
zwinglio force-pushed the lote-sessao-plenaria branch from 2498b9d to a80cd3d Compare October 14, 2026 01:40
@zwinglio

Copy link
Copy Markdown
Author

Fiz rebase do branch sobre a 3.1.x atualizada e um force push (--force-with-lease), por isso os hashes dos commits mudaram. O conteúdo dos três commits é o mesmo; só a base mudou.

Primeiro rebase (sobre #3849, #3855 e #3865): havia conflito em sapl/api/views_sessao.py. A 3.1.x passou a usar restringe_sessoes_visiveis e incluiu o _RestringeSessoesVisiveisMixin, nos mesmos trechos em que este PR adiciona as actions lote e painel. Na resolução mantive as duas mudanças: as actions continuam em _SessaoPlenariaViewSet, seguidas do mixin e do loop de registro vindos da 3.1.x.

Também conferi a compatibilidade com as novas constraints de unicidade:

  • VotoParlamentar (0071): o gravar_votos do lote remove duplicados antes de gravar, então cada parlamentar fica com um único voto por matéria.
  • votacao_aberta (0072): cada item do lote roda em transaction.atomic() próprio, então uma violação vira erro só daquele item, sem resultar em 500.

Segundo rebase (sobre #3846, sanitização de TextField): aplicou sem conflitos. A sanitização é feita em um receiver pre_save global, então o que o lote grava também passa por ela.

Quem tiver o branch localmente precisa atualizá-lo com git fetch && git reset --hard fork/lote-sessao-plenaria.

@zwinglio

Copy link
Copy Markdown
Author

@edwardoliveira @joaohortsenado tenho ticket no interlegis sobre isso de número 124060 e muitas camaras que desenvolvem integrações seriam beneficiadas por isso. Podemos conversar?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Painel eletrônico: endpoint /dados não expõe todas as informações da sessão (mesa diretora, presenças, expedientes, matérias, oradores)

1 participant