Skip to content

Relatório de validação agrupado por gravidade/categoria + CSV objetivo (#34)#35

Open
Rossi-Luciano wants to merge 11 commits into
scieloorg:mainfrom
Rossi-Luciano:feature/issue-34-relatorio-legivel
Open

Relatório de validação agrupado por gravidade/categoria + CSV objetivo (#34)#35
Rossi-Luciano wants to merge 11 commits into
scieloorg:mainfrom
Rossi-Luciano:feature/issue-34-relatorio-legivel

Conversation

@Rossi-Luciano

@Rossi-Luciano Rossi-Luciano commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

O que esse PR faz?

Resolve a issue #34: substitui o CSV bruto (que misturava texto de diagnóstico técnico com a ação de correção numa coluna só, chamada data) por um relatório HTML navegável, agrupado por gravidade e categoria, com um checklist de progresso.

  • Separa message (diagnóstico) de advise (ação de correção) nas rows de validação; remove a coluna técnica data do CSV e localiza seus cabeçalhos (pt/en/es).
  • Nova página /validation/<id>/report.html: ocorrências agrupadas por gravidade (CRITICAL/ERROR/WARNING) → categoria (alfabética) → mensagem de problema idêntica (evita repetir o mesmo texto uma vez por ocorrência — ex.: uma categoria com 59 ocorrências e só 11 mensagens distintas, testado com pacote real).
  • Checklist "Corrigido" por ocorrência (e "Marcar todas" por grupo), com progresso salvo no localStorage do navegador — pensado como controle momentâneo de quem está corrigindo o XML, sem persistir no banco.
  • Nova coluna "Relatório" no histórico, CSV renomeado para deixar claro que é o artefato técnico secundário.
  • Corrige de passagem um bug pré-existente na inicialização da janela desktop nativa (parâmetro icon que o pywebview não aceita mais em create_window()) e liga localStorage/cookies nela.

Onde a revisão poderia começar?

spsvalidator/src/spsvalidator/domain/report.py (lógica pura de agrupamento, sem dependência de Flask) → spsvalidator/src/spsvalidator/web/routes.py (rota view_report) → spsvalidator/src/spsvalidator/web/templates/report.html (template + checklist).

Como este poderia ser testado manualmente?

  1. pip install -e . e spsvalidator --browser (ou a janela nativa).
  2. Validar um pacote .zip SPS.
  3. Na tabela de histórico, clicar em "Relatório" (abre em nova aba).
  4. Expandir uma categoria com várias ocorrências repetidas e conferir se elas ficaram agrupadas por mensagem, com contagem.
  5. Marcar alguns itens como "Corrigido" (individual e via "Marcar todas" de um grupo) e conferir os contadores (total, por gravidade, por categoria) atualizando ao vivo.
  6. Recarregar a página e confirmar que as marcações continuam lá; clicar em "Limpar marcações" e confirmar que tudo volta a zero.
  7. Baixar o CSV e conferir as novas colunas/cabeçalhos (Pacote, Gravidade, Categoria, Problema, Ação de correção), sem a coluna data.

Algum cenário de contexto que queira dar?

Motivado por feedback de usabilidade da equipe de Publicação sobre a versão anterior do SPS Validator: o CSV era a única forma de ver o detalhamento de uma validação, misturava informação de depuração com o que realmente importa, e exigia mais esforço para localizar um problema do que para de fato analisá-lo. O desenho do agrupamento (fechado por padrão, mensagens repetidas consolidadas) foi ajustado depois de testar com pacotes reais, não só dados sintéticos — os primeiros testes revelaram, por exemplo, que categorias abertas por padrão recriavam a mesma "parede de texto" do problema original.

Screenshots

Interface modificada com relatório em HTML:

image

Relatório HTML com agrupamento:

image image image image

Visão geral do relatório HTML:

image

Relatório CSV com colunar modificadas:

image

HTML do artigo:

image

PDF do pacote:

image

Quais são os tickets relevantes?

Closes #34

Referências

N/A


Segurança da informação (NSI.04)

Este PR manipula dados sensíveis ou pessoais (LGPD)?

  • Não — o relatório reexibe os mesmos dados que já apareciam no CSV (metadados bibliográficos do próprio XML, como nomes de autores já públicos no pacote), sem coletar, armazenar ou processar novos dados pessoais.

Este PR altera autenticação, autorização, controle de acesso ou gerenciamento de sessão?

  • Não

Este PR introduz, atualiza ou remove dependências de terceiros?

  • Não — nenhuma dependência nova; domain/report.py usa só a biblioteca padrão (hashlib).

Este PR foi validado pelo pipeline de segurança (SonarQube / Trivy)?

  • Sim — link do job: (a preencher após a abertura do PR e execução automática do pipeline)
  • Não aplicável a este PR

Este PR concatena, monta ou executa comandos SQL, HTML ou JavaScript a partir de entrada externa?

  • Sim — o relatório monta HTML a partir de texto derivado do XML validado (via packtools), mas exclusivamente através de variáveis Jinja2 ({{ }}), com autoescape padrão do Flask ativo e sem nenhum uso de |safe em nenhum template do repositório (conferido). Testado na prática: texto de validação contendo <role> foi renderizado como entidade escapada (&lt;role&gt;), não como HTML. Os dois valores injetados em <script> inline usam o filtro |tojson, forma segura de serializar para JS. Nenhuma consulta SQL é concatenada nesta mudança.

Este PR expõe novos endpoints, telas ou serviços?

  • Sim — GET /validation/<history_id>/report.html. HTTPS não se aplica: o app é uma ferramenta desktop/local, o servidor Flask escuta só em 127.0.0.1 (loopback), sem exposição de rede — mesmo padrão dos endpoints já existentes (/validation/<id>/report.csv, previews HTML/PDF), sem modelo de autenticação porque é uso local de um único usuário por vez.

Algum segredo, senha, chave ou token está sendo adicionado ao código-fonte?

  • Não, nenhum segredo foi commitado

Rossi-Luciano and others added 11 commits July 24, 2026 17:32
…lidacao

- Adiciona o campo "advise" as rows, preenchido com o resultado bruto
  "advice" do packtools (a acao de correcao) - antes esse texto ia,
  sem renomear, para o campo "message".
- Passa a preencher "message" com o campo "message" que o proprio
  packtools ja calcula (o diagnostico automatico "Got X, expected Y"),
  antes descartado dentro do dicionario bruto "data".
- Fixa "subject" como "Renditions" e "Assets" nas duas rows que o
  spsvalidator monta manualmente (PDF de idioma ausente, asset
  referenciado no XML mas ausente do pacote), no lugar do valor por
  instancia (idioma ou nome de arquivo) usado antes.
- Escreve manualmente o texto de "advise" para essas duas rows, ja
  que elas nao passam por build_response() do packtools e nao tinham
  uma acao de correcao calculada separadamente do diagnostico.

Porque: primeira etapa da issue scieloorg#34, aberta a partir de feedback de
usabilidade da equipe de Publicacao. O CSV/relatorio de validacao
misturava, numa coluna so chamada "message", o texto de acao
corretiva do packtools - sem nunca expor o diagnostico separado.
Alem disso, cada asset ausente virava uma categoria propria (o nome
do arquivo), fragmentando o agrupamento por categoria em vez de
juntar ocorrencias do mesmo tipo de problema.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZA68J1nQ47EsvTN63wgwt
- Troca as duas asserções que checavam "subject == fig1.jpg" (nome do
  arquivo) por "subject == 'Assets'", validando o nome do arquivo
  dentro do texto de "message" em vez de na categoria.

Porque: consequência direta da mudança em domain/validation.py (commit
anterior) - "subject" das rows de asset ausente deixou de ser o nome
do arquivo e passou a ser a categoria fixa "Assets". Sem este ajuste
os dois testes quebrariam.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZA68J1nQ47EsvTN63wgwt
…s customizados

- Troca "data" (dump bruto de depuracao do resultado de validacao) por
  "advise" (acao de correcao) em VALIDATION_CSV_COLUMNS.
- build_validation_csv() ganha o parametro opcional "headers", com
  fallback para os nomes tecnicos das colunas (DEFAULT_CSV_HEADERS)
  quando nenhum cabecalho customizado e informado.

Porque: segunda etapa da issue scieloorg#34. O feedback da equipe de Publicacao
apontava que o CSV reunia informacao tecnica e de depuracao numa
coluna so, dificultando localizar a acao necessaria. O parametro
"headers" evita acoplar esta camada de dominio (sem dependencia do
Flask) ao flask_babel - a traducao dos cabecalhos fica por conta de
quem chama a funcao (ex.: a rota web, que ja tem acesso a gettext).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZA68J1nQ47EsvTN63wgwt
… atual

- Substitui o teste antigo, que ja verificava colunas (group, response,
  advice) que nao existem mais em VALIDATION_CSV_COLUMNS ha tempos - o
  teste falhava mesmo antes desta sessao, sem relacao com a mudanca
  atual (confirmado rodando a suite completa antes de qualquer edicao).
- Novo teste cobre o formato de row usado hoje (package, status,
  subject, message, advise) e confirma que "data" nao aparece no CSV.
- Adiciona teste para o parametro "headers" customizado introduzido no
  commit anterior.

Porque: aproveitado o commit em domain/export.py para corrigir um
teste ja desatualizado no mesmo arquivo, e cobrir a funcionalidade
nova (headers) que ele nao testava.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZA68J1nQ47EsvTN63wgwt
…desktop

- Troca a asserção "group,title" (ja desatualizada antes desta sessao,
  nao batia com nenhuma versao recente de VALIDATION_CSV_COLUMNS) por
  "package,status,subject,message,advise", refletindo as colunas
  atuais.

Porque: consequência da mudanca em domain/export.py. A API desktop
(save_validation_csv) chama build_validation_csv() sem parametro de
"headers" customizado, entao usa o fallback tecnico em ingles - o que
e esperado, ja que essa chamada roda fora do contexto de requisicao do
Flask e nao tem acesso a gettext.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZA68J1nQ47EsvTN63wgwt
…a e mensagem

- build_grouped_report(rows): agrupa por gravidade (ordem fixa
  CRITICAL, ERROR, WARNING), depois por categoria (ordem alfabetica),
  depois por mensagem de problema identica dentro da categoria (mais
  frequente primeiro) - em vez de listar cada ocorrencia solta.
- Cada ocorrencia recebe um subconjunto legivel de detalhes tecnicos
  (item, sub_item, validation_type, expected_value, got_value),
  descartando os campos de infraestrutura de i18n do packtools
  (msg_text, msg_params, adv_text, adv_params) e os campos internos de
  posicao (parent*, title), que nao agregam valor pro usuario final.
- Cada ocorrencia recebe uma chave estavel ("key"), gerada por hash de
  gravidade+categoria+mensagem+acao+indice - usada pelo relatorio HTML
  (proximo commit) para lembrar, no navegador, quais itens ja foram
  marcados como corrigidos.

Porque: nucleo da issue scieloorg#34. Testando com um pacote real (254
ocorrencias, 24 categorias), a categoria "contrib" sozinha tinha 59
ocorrencias mas so 11 mensagens de problema distintas - sem esse
agrupamento por mensagem, o relatorio recriaria o mesmo problema do
CSV bruto (parede de texto repetido), so que em HTML.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZA68J1nQ47EsvTN63wgwt
- Ordenacao de gravidade (CRITICAL/ERROR/WARNING primeiro, extras
  depois) e de categoria (alfabetica) dentro de cada gravidade.
- Contagem de ocorrencias por gravidade e por categoria.
- Agrupamento de mensagens repetidas dentro de uma categoria,
  incluindo ordenacao por frequencia (mais comum primeiro) e
  desempate alfabetico.
- Selecao dos campos tecnicos expostos (e omissao dos vazios/None).
- Estabilidade e unicidade das chaves ("key") entre duas execucoes com
  o mesmo input, e caso de lista vazia.

Porque: cobertura da logica pura de domain/report.py, testavel isolada
de Flask/banco/packtools.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZA68J1nQ47EsvTN63wgwt
- download_csv(): monta os cabecalhos do CSV via gettext (Pacote,
  Gravidade, Categoria, Problema, Acao de correcao), seguindo o idioma
  da UI (pt/en/es), e passa para build_validation_csv(headers=...).
- Nova rota GET /validation/<history_id>/report.html (view_report):
  busca os detalhes da validacao, agrupa via
  domain.report.build_grouped_report() e renderiza um template novo.

Porque: liga a camada de dominio (domain/export.py, domain/report.py)
a interface web. Os cabecalhos localizados mantem o CSV consistente
com o resto do app, que ja suporta pt/en/es; a nova rota e a etapa
central da issue scieloorg#34, expondo o agrupamento por gravidade/categoria
como uma pagina navegavel.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZA68J1nQ47EsvTN63wgwt
… de correcao

- Pagina standalone com secoes por gravidade (cor de destaque na
  borda), categorias e mensagens agrupadas em <details> fechados por
  padrao (nada expandido ao carregar a pagina).
- Checkbox "Corrigido" por ocorrencia e "Marcar todas como corrigidas"
  por grupo de mensagem repetida, com estado guardado no localStorage
  do navegador (chave por history_id) e restaurado ao recarregar a
  pagina; ocorrencias marcadas ganham estilo visual (opacidade
  reduzida, texto riscado).
- Contadores de ocorrencias corrigidas em tres niveis (total da
  pagina, por gravidade, por categoria), recalculados ao vivo a cada
  marcacao.
- Botao "Limpar marcacoes", com confirmacao, que zera o localStorage e
  a interface.
- Detalhes tecnicos de cada ocorrencia escondidos atras de uma segunda
  expansao opcional, separados do texto de problema/acao de correcao.

Porque: entrega visual da issue scieloorg#34. O padrao fechado por default e o
agrupamento de mensagens repetidas foram ajustados depois de testar
com um pacote real - categorias abertas por padrao e ocorrencias
repetidas sem agrupar recriavam o problema original (parede de texto,
maior esforco pra localizar do que pra analisar). O checklist de
correcao foi pedido em seguida, pensado como controle momentaneo de
quem esta corrigindo o XML durante uma sessao de trabalho, sem
persistir no banco - por isso localStorage, nao uma tabela nova.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZA68J1nQ47EsvTN63wgwt
- Nova coluna "Relatório", com link para a rota /report.html do
  pacote (nova aba).
- Renomeia a coluna que baixa o CSV, de "Validação" (cabecalho) /
  "Relatório" (texto do link) para "CSV" nos dois lugares.

Porque: da acesso ao novo relatorio HTML a partir do historico (ultima
etapa da issue scieloorg#34). A renomeacao evita a colisao de nomes entre os
dois links (antes "Relatório" era o texto do botao que baixava o CSV)
e segue o espirito da issue de tornar o CSV um artefato tecnico
secundario, nao mais a interface principal de consulta dos resultados.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZA68J1nQ47EsvTN63wgwt
…nativa

- Move "icon" de window_kwargs (repassado a webview.create_window())
  para webview.start(icon=...), unico lugar onde esse parametro existe
  na versao instalada do pywebview (6.2.1) - create_window() nao aceita
  mais "icon", o que fazia a janela desktop falhar ao abrir com
  TypeError.
- webview.start() passa a receber private_mode=False e storage_path
  apontando para ~/.spsvalidator/webview_data, ligando cookies e
  localStorage na janela desktop nativa. Antes, o padrao do pywebview
  (private_mode=True) desligava o localStorage por completo no backend
  GTK/WebKit2GTK usado no Linux (nao so "nao persiste entre aberturas"
  - a propria API ficava indisponivel).

Porque: o bug do "icon" e pre-existente (nao introduzido nesta sessao)
e so apareceu ao tentar abrir a janela desktop de verdade pela primeira
vez. O ajuste de private_mode foi pedido para que o checklist de
correcao do relatorio (commit anterior) se comporte de forma
consistente entre "--browser" e a janela nativa - ainda que, na
pratica, o relatorio sempre abra no navegador do sistema em ambos os
casos (o pywebview abre links com target="_blank" externamente por
padrao), esse ajuste evita que qualquer outra parte do app que dependa
de armazenamento local dentro da propria janela fique inconsistente
entre os dois modos.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZA68J1nQ47EsvTN63wgwt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Relatório de validação mais legível: CSV objetivo (problema/ação de correção) + novo relatório HTML agrupado por gravidade e categoria

2 participants