From 1dc084305358d5a6155b37e98ae07ca73feca0b7 Mon Sep 17 00:00:00 2001 From: Edward Oliveira Date: Mon, 17 Nov 2025 16:20:30 -0300 Subject: [PATCH] WIP --- sapl/painel/README.md | 54 ++++ sapl/painel/consumers.py | 6 +- sapl/painel/views.py | 6 +- .../migrations/0070_views_sessao_plenaria.py | 255 ++++++++++++++++++ 4 files changed, 315 insertions(+), 6 deletions(-) create mode 100644 sapl/painel/README.md create mode 100644 sapl/sessao/migrations/0070_views_sessao_plenaria.py diff --git a/sapl/painel/README.md b/sapl/painel/README.md new file mode 100644 index 000000000..81f801879 --- /dev/null +++ b/sapl/painel/README.md @@ -0,0 +1,54 @@ + +# Websockets + +Rodar o container antes de iniciar o SAPL: + +```commandline + sudo docker run --rm -p 6379:6379 redis:7-alpine redis-server --save "" --appendonly no +``` + +Executar o SAPL + +Instalar dependências do Websockets e Redis: + +```commandline + pip install -r requirements/dev-requirements.txt +``` + +Abrir um terminal e rodar `yarn` para servir as páginas VueJS: + +```commandline + yarn serve +``` + +Executar o SAPL (duas formas): + +DAPHNE: + +Em outro terminal, no diretório raiz, execute como Daphne abaixo: +```commandline + daphne -b 127.0.0.1 -p 8000 sapl.asgi:application +``` + +Daphne é excelente para debugar a parte de WebSockets, pois contém melhores mensagens de erro e log. +O runserver geralmente só vai dar crash ou falhar ao enviar as mensagens via WebSocket. + +**MAS atenção: Daphne não faz reload automático após mudanças na página!** + +Para isso é que parar e reiniciar o Daphne ou usar `.manage.py runserver` + + +RUNSERVER: +Em outro terminal, no diretório raiz, execute como Daphne abaixo para debugar os websockets (melhores mensagens de log) +```commandline + ./manage.py runserver +``` + +Logar no SAPL e acessar a página `http://localhost:8000/painel/v2` + + +Ferramentas: + +`wscat`: permite fazer chamadas a websockets via CLI, mas para acessar o WS endpoint do painel precisa estar autenticado. + +**Redis Insight:** GUI que é tipo um pgAdmin para o Redis; diff --git a/sapl/painel/consumers.py b/sapl/painel/consumers.py index 71a62b13f..09a4b186d 100644 --- a/sapl/painel/consumers.py +++ b/sapl/painel/consumers.py @@ -30,19 +30,19 @@ def get_dados_painel(pk: int) -> dict: # Painel - presentes = SessaoPresencaView.objects.filter(sessao_plenaria_id=4984, + presentes = SessaoPresencaView.objects.filter(sessao_plenaria_id=pk, etapa_sessao='expediente').values_list('parlamentar_id', 'nome_parlamentar', 'filiacao', ) presentes = [dict(zip(['parlamentar_id', 'nome_parlamentar', 'filiacao'], p)) for p in presentes] - oradores = SessaoOradorView.objects.filter(sessao_plenaria_id=4983, + oradores = SessaoOradorView.objects.filter(sessao_plenaria_id=pk, etapa_sessao='expediente').values_list('ordem_pronunciamento', 'nome_parlamentar', ) oradores = [dict(zip(['ordem_pronunciamento', 'nome_parlamentar'], o)) for o in oradores] - votos = SessaoMateriaVotacaoView.objects.get(sessao_plenaria_id=4984, etapa_sessao='ordemdia', materia_id=4148) + votos = SessaoMateriaVotacaoView.objects.get(sessao_plenaria_id=pk, etapa_sessao='expediente', materia_id=31919) # TODO: recover stopwatch state from DB/Cache stopwatch = { diff --git a/sapl/painel/views.py b/sapl/painel/views.py index 5c628fbb2..7ebd7bbe8 100644 --- a/sapl/painel/views.py +++ b/sapl/painel/views.py @@ -639,10 +639,10 @@ def websocket_view(request): utc_offset = now.utcoffset().total_seconds() / 60 # controller_id == session_id context = {'head_title': str(_('Painel Plenário')), - 'sessao_id': 4984, # TODO: recover from template call + 'sessao_id': 2546, # TODO: recover from template call 'utc_offset': utc_offset, 'enable_live_ws': True, - 'controller_id': 4984, # TODO: unify with sessao_id + 'controller_id': 2546, # TODO: unify with sessao_id } return render(request, "painel/painel_v2.html", context) @@ -658,7 +658,7 @@ def painel_controller_view(request): if command: layer = get_channel_layer() - controller_id = 4984 # TODO: recover from template call + controller_id = 2393 # TODO: recover from template call group = f"controller_{controller_id}" print(group) diff --git a/sapl/sessao/migrations/0070_views_sessao_plenaria.py b/sapl/sessao/migrations/0070_views_sessao_plenaria.py new file mode 100644 index 000000000..12595f606 --- /dev/null +++ b/sapl/sessao/migrations/0070_views_sessao_plenaria.py @@ -0,0 +1,255 @@ +# Generated by Django 2.2.28 on 2025-11-17 17:55 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0069_auto_20220919_1705'), + ] + + operations = [ + migrations.RunSQL( + """ + CREATE OR REPLACE VIEW sessao_presenca_view AS + -- + -- PRESENCAS DE PARLAMENTARES COM FILIACAO (SE HOUVER) + -- MANDATO DEVE ESTAR ATIVO NA LEGISLATIURA + -- PARLAMENTAR DEVE ESTAR ATIVO + -- + -- EXPEDIENTE + SELECT + p.id, + presenca.sessao_plenaria_id, + 'expediente' AS etapa_sessao, + p.id as parlamentar_id, + p.nome_parlamentar as nome_parlamentar, + COALESCE(af.sigla, 'SEM PARTIDO') AS filiacao + FROM sessao_sessaoplenariapresenca AS presenca + JOIN sessao_sessaoplenaria AS sp + ON presenca.sessao_plenaria_id = sp.id + JOIN parlamentares_parlamentar AS p + ON presenca.parlamentar_id = p.id + JOIN parlamentares_mandato AS m + ON p.id = m.parlamentar_id + AND m.legislatura_id = sp.legislatura_id + AND m.data_inicio_mandato <= sp.data_inicio + AND (m.data_fim_mandato IS NULL OR m.data_fim_mandato >= sp.data_inicio) + -- recupera uma filiacao partidaria, se existir + LEFT JOIN LATERAL ( + SELECT pa.sigla + FROM parlamentares_filiacao f + JOIN parlamentares_partido pa ON pa.id = f.partido_id + WHERE f.parlamentar_id = p.id + AND f.data <= sp.data_inicio + AND (f.data_desfiliacao IS NULL OR f.data_desfiliacao >= sp.data_inicio) + ORDER BY f.data DESC + LIMIT 1 + ) AS af ON TRUE + WHERE p.ativo = TRUE + + UNION ALL + + -- ORDEM DO DIA + SELECT + p.id, + presenca.sessao_plenaria_id, + 'ordemdia' AS etapa_sessao, + p.id as parlamentar_id, + p.nome_parlamentar as nome_parlamentar, + COALESCE(af.sigla, 'SEM PARTIDO') AS filiacao + FROM sessao_presencaordemdia AS presenca + JOIN sessao_sessaoplenaria AS sp + ON presenca.sessao_plenaria_id = sp.id + JOIN parlamentares_parlamentar AS p + ON presenca.parlamentar_id = p.id + JOIN parlamentares_mandato AS m + ON p.id = m.parlamentar_id + AND m.legislatura_id = sp.legislatura_id + AND m.data_inicio_mandato <= sp.data_inicio + AND (m.data_fim_mandato IS NULL OR m.data_fim_mandato >= sp.data_inicio) + -- recupera uma filiacao partidaria, se existir + LEFT JOIN LATERAL ( + SELECT pa.sigla + FROM parlamentares_filiacao f + JOIN parlamentares_partido pa ON pa.id = f.partido_id + WHERE f.parlamentar_id = p.id + AND f.data <= sp.data_inicio + AND (f.data_desfiliacao IS NULL OR f.data_desfiliacao >= sp.data_inicio) + ORDER BY f.data DESC + LIMIT 1 + ) AS af ON TRUE + WHERE p.ativo = TRUE + ORDER BY sessao_plenaria_id, etapa_sessao, nome_parlamentar + """ + ), + migrations.RunSQL( + """ + CREATE OR REPLACE VIEW sessao_orador_view AS + -- + -- ORADORES DE ORDEMDIA/EXPEDIENTE + -- * PARLAMENTARES ATIVOS + -- + SELECT od.id, + sessao_plenaria_id, + 'ordemdia' as etapa_sessao, + numero_ordem as ordem_pronunciamento, + parlamentar_id, + nome_parlamentar, + COALESCE(af.sigla, 'SEM PARTIDO') AS filiacao + FROM sessao_oradorordemdia od + JOIN sessao_sessaoplenaria sp ON (od.sessao_plenaria_id = sp.id) + JOIN parlamentares_parlamentar p ON (od.parlamentar_id = p.id) + -- recupera uma filiacao partidaria, se existir + LEFT JOIN LATERAL ( + SELECT pa.sigla + FROM parlamentares_filiacao f + JOIN parlamentares_partido pa ON pa.id = f.partido_id + WHERE f.parlamentar_id = p.id + AND f.data <= sp.data_inicio + AND (f.data_desfiliacao IS NULL OR f.data_desfiliacao >= sp.data_inicio) + ORDER BY f.data DESC + LIMIT 1 + ) AS af ON TRUE + AND p.ativo = TRUE + UNION ALL + SELECT ex.id, + sessao_plenaria_id, + 'expediente' as etapa_sessao, + numero_ordem as ordem_pronunciamento, + parlamentar_id, + nome_parlamentar, + COALESCE(af.sigla, 'SEM PARTIDO') AS filiacao + FROM sessao_oradorexpediente ex + JOIN sessao_sessaoplenaria sp ON (ex.sessao_plenaria_id = sp.id) + JOIN parlamentares_parlamentar p on (ex.parlamentar_id = p.id) + -- recupera uma filiacao partidaria, se existir + LEFT JOIN LATERAL ( + SELECT pa.sigla + FROM parlamentares_filiacao f + JOIN parlamentares_partido pa ON pa.id = f.partido_id + WHERE f.parlamentar_id = p.id + AND f.data <= sp.data_inicio + AND (f.data_desfiliacao IS NULL OR f.data_desfiliacao >= sp.data_inicio) + ORDER BY f.data DESC + LIMIT 1 + ) AS af ON TRUE + AND p.ativo = TRUE + ORDER BY sessao_plenaria_id, etapa_sessao, ordem_pronunciamento + """ + ), + migrations.RunSQL( + """ + CREATE OR REPLACE VIEW sessao_materias_votacoes_view AS + -- + -- Votacao/Leitura de Materias + -- + + -- EXPEDIENTE + WITH votacao_materias AS ( + SELECT em.sessao_plenaria_id, + em.id id, + 'expediente' etapa_sessao, + em.numero_ordem, + em.materia_id, + ml.ementa, + tipo_votacao, + CASE tipo_votacao + WHEN 1 THEN 'Simbólia' + WHEN 2 THEN 'Nominal' + WHEN 3 THEN 'Secreta' + WHEN 4 THEN 'Leitura' + ELSE '' + END as tipo_votacao_descricao, + resultado_votacao, + em.resultado, + total_votos, + votos_parlamentares, + votacao_aberta + FROM sessao_expedientemateria em + JOIN materia_materialegislativa ml ON (em.materia_id = ml.id) + LEFT JOIN sessao_registroleitura rl on (rl.expediente_id = em.id) + LEFT JOIN LATERAL ( + SELECT jsonb_build_object( + 'sim', coalesce(rv.numero_votos_sim, 0), + 'não', coalesce(rv.numero_votos_nao, 0), + 'abstencoes', coalesce(rv.numero_abstencoes, 0) + ) as total_votos, + trv.nome resultado_votacao + FROM sessao_registrovotacao rv + JOIN sessao_tiporesultadovotacao trv on (rv.tipo_resultado_votacao_id = trv.id) + WHERE rv.expediente_id = em.id AND tipo_votacao != 4) rv ON TRUE + LEFT JOIN LATERAL ( + SELECT em.sessao_plenaria_id, + em.numero_ordem, + jsonb_agg(jsonb_build_object( + 'materia_id', em.materia_id, + 'parlamentar_id', vp.parlamentar_id, + 'parlamentar_nome', p.nome_parlamentar, + 'voto', vp.voto + ) ORDER BY em.materia_id) as votos_parlamentares + FROM sessao_votoparlamentar vp + JOIN parlamentares_parlamentar p ON (vp.parlamentar_id = p.id) + WHERE vp.expediente_id = em.id AND em.tipo_votacao != 4 + GROUP BY em.sessao_plenaria_id, em.numero_ordem + ORDER BY em.sessao_plenaria_id, em.numero_ordem + ) vp ON TRUE + + UNION ALL + + -- ORDEM DIA + SELECT od.sessao_plenaria_id, + od.id id, + 'ordemdia' etapa_sessao, + od.numero_ordem, + od.materia_id, + ml.ementa, + tipo_votacao, + CASE tipo_votacao + WHEN 1 THEN 'Simbólia' + WHEN 2 THEN 'Nominal' + WHEN 3 THEN 'Secreta' + WHEN 4 THEN 'Leitura' + ELSE '' + END as tipo_votacao_descricao, + resultado_votacao, + od.resultado, + total_votos, + votos_parlamentares, + votacao_aberta + FROM sessao_ordemdia od + JOIN materia_materialegislativa ml ON (od.materia_id = ml.id) + LEFT JOIN sessao_registroleitura rl on (od.id = rl.expediente_id) + LEFT JOIN LATERAL ( + SELECT jsonb_build_object( + 'sim', coalesce(rv.numero_votos_sim, 0), + 'não', coalesce(rv.numero_votos_nao, 0), + 'abstencoes', coalesce(rv.numero_abstencoes, 0) + ) as total_votos, + trv.nome resultado_votacao + FROM sessao_registrovotacao rv + JOIN sessao_tiporesultadovotacao trv on (rv.tipo_resultado_votacao_id = trv.id) + WHERE rv.expediente_id = od.id AND tipo_votacao != 4) rv ON TRUE + LEFT JOIN LATERAL ( + SELECT od.sessao_plenaria_id, + od.numero_ordem, + jsonb_agg(jsonb_build_object( + 'materia_id', od.materia_id, + 'parlamentar_id', vp.parlamentar_id, + 'parlamentar_nome', p.nome_parlamentar, + 'voto', vp.voto + ) ORDER BY od.materia_id) as votos_parlamentares + FROM sessao_votoparlamentar vp + JOIN parlamentares_parlamentar p ON (vp.parlamentar_id = p.id) + WHERE vp.expediente_id = od.id AND od.tipo_votacao != 4 + GROUP BY od.sessao_plenaria_id, od.numero_ordem + ORDER BY od.sessao_plenaria_id, od.numero_ordem + ) vp ON TRUE + ) + SELECT * + FROM votacao_materias + ORDER BY sessao_plenaria_id, etapa_sessao, numero_ordem + """ + ) + ]