Add <response> element validation for SPS 1.10 compliance#1138
Add <response> element validation for SPS 1.10 compliance#1138
Conversation
Implements 7 validation rules for <response> element: - Rule 1: @response-type presence (CRITICAL) - Rule 2: @response-type value must be "reply" (ERROR) - Rule 3: @xml:lang presence (CRITICAL) - Rule 4: @id presence (CRITICAL) - Rule 5: @id uniqueness (ERROR) - Rule 6: <front-stub> presence (WARNING) - Rule 7: <body> presence (WARNING) New files: - packtools/sps/validation/response.py - Validation class - packtools/sps/validation_rules/response_rules.json - Rules config - tests/sps/validation/test_response.py - 31 unit tests Modified files: - packtools/sps/validation/xml_validations.py - Import + wrapper function - packtools/sps/validation/xml_validator.py - Orchestrator integration Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com>
Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com>
Rossi-Luciano
left a comment
There was a problem hiding this comment.
Evidência de revisão — response.py
Data: 2026-04-25
Revisor: Luciano
Artefatos analisados:
response.py(validation) — módulo de validação (ResponseValidation)response_rules.json— parâmetros de configuraçãoresponse.xml— XML artificial gerado para cobertura de testesresponse-2026-25-04-145722-errors.csv— relatório de erros gerado pelo packtools
Metodologia
Análise cruzada entre o módulo de validação, o JSON de regras, o XML artificial
construído com o princípio do defeito isolado e o relatório CSV. O módulo é autocontido: _get_response_elements() extrai todos os campos diretamente do nó XML via XPath e .get(), sem delegar a nenhuma classe de model externa. Não foi necessário solicitar um módulo de model adicional.
O XML usa article-type="letter", que não consta em data_availability_required_article_types, evitando ruído do validador de disponibilidade de dados nas seções.
Regras validadas (7 métodos)
| # | Método | Nível configurado |
|---|---|---|
| R1 | validate_response_type_presence |
CRITICAL |
| R2 | validate_response_type_value |
ERROR |
| R3 | validate_xml_lang_presence |
CRITICAL |
| R4 | validate_id_presence |
CRITICAL |
| R5 | validate_id_uniqueness |
ERROR |
| R6 | validate_front_stub_presence |
WARNING |
| R7 | validate_body_presence |
WARNING |
Casos de teste no XML artificial (9 elementos <response>)
| Caso | Defeito introduzido | Nível esperado | Isolamento |
|---|---|---|---|
| P1 | Nenhum — caso ouro completo | OK (ausente do CSV) | todos os atributos e filhos válidos |
| C-R1 | <response> sem @response-type |
CRITICAL | R2 skipa (if not response_type: continue) — sem cascata |
| C-R2 | @response-type="comment" (valor inválido) |
ERROR | R1 passa (bool("comment")=True) |
| C-R3 | <response> sem @xml:lang |
CRITICAL | demais atributos válidos |
| C-R4 | <response> sem @id |
CRITICAL | R5 skipa (if not response_id: continue) — sem cascata |
| C-R5a | @id="resp-dup" (1ª ocorrência) |
ERROR | todos os outros atributos válidos para isolar R5 |
| C-R5b | @id="resp-dup" (2ª ocorrência) |
ERROR | validate_id_uniqueness percorre todos os contextos e gera uma entrada por instância duplicada |
| C-R6 | <response> sem <front-stub> |
WARNING | demais atributos e <body> válidos |
| C-R7 | <response> sem <body> |
WARNING | demais atributos e <front-stub> válidos |
Verificação cruzada com o CSV
Todas as 8 entradas esperadas do módulo response.py aparecem no CSV sob o
subject response, com os níveis de severidade corretos:
| Subject | Caso | Nível | Verificação |
|---|---|---|---|
| response | C-R1 | CRITICAL | PASS — response @response-type presence |
| response | C-R2 | ERROR | PASS — Replace @response-type with "reply" |
| response | C-R3 | CRITICAL | PASS — response @xml:lang presence |
| response | C-R4 | CRITICAL | PASS — response @id presence |
| response | C-R5a | ERROR | PASS — Replace duplicate @id="resp-dup" (1ª instância) |
| response | C-R5b | ERROR | PASS — Replace duplicate @id="resp-dup" (2ª instância) |
| response | C-R6 | WARNING | PASS — response front-stub presence |
| response | C-R7 | WARNING | PASS — response body presence |
O caso ouro P1 não gera nenhuma entrada. Não foram identificados falsos positivos nem falsos negativos originados do módulo.
Nota sobre R5: validate_id_uniqueness coleta todos os contextos em uma única passagem, identifica os ids duplicados e gera uma entrada por instância. O resultado de 2 entradas para @id="resp-dup" é o comportamento esperado e correto.
Ruído identificado (outros módulos) — 20 entradas
Ruído estrutural (XML artificial mínimo): history dates ausentes (5 entradas),
subject/subj-group heading ausente (3 entradas), disp-formula, inline-formula, table-wrap e app ausentes, bibliographic strip incompleto.
Ruído de ambiente: rendition PDF ausente, article dates incompleto, DOI não
registrado no Crossref.
Ruído de referência (id and rid ERROR, 1 entrada): <ref id="B01"> presente no
<ref-list> sem <xref ref-type="bibr" rid="B01"> correspondente no corpo do
artigo. Para eliminar esse ruído nos próximos XMLs artificiais de módulos que não validam referências, incluir ao menos um <xref ref-type="bibr" rid="B01"> no corpo.
Ruído de open science (CRITICAL, 1 entrada): o validador de open science disparou mesmo para article-type="letter". Verificar se letter deveria estar isento da exigência de declaração de disponibilidade de dados nesse módulo.
Conclusão
O módulo response.py está correto. Todas as 8 entradas esperadas dispararam com os níveis de severidade corretos, sem falsos positivos nem falsos negativos originados do módulo. O comportamento de skip de R2 para response_type=None (C-R1) e de skip de R5 para id=None (C-R4) foi confirmado pela ausência de cascatas no CSV.
There was a problem hiding this comment.
Pull request overview
Adds SPS 1.10 validations for the <response> element and integrates them into the XML validation orchestrator, with configurable severities and unit tests.
Changes:
- Introduces
ResponseValidationimplementing 7 SPS 1.10 rules for<response>(required attributes,@iduniqueness, and minimal structure). - Adds
response_rules.jsonto configure per-rule severity levels. - Wires the new validation group (
"response") into the orchestrator and adds a dedicated unit test suite.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
packtools/sps/validation/response.py |
New ResponseValidation class with the 7 rule checks and shared context extraction. |
packtools/sps/validation_rules/response_rules.json |
Default severity configuration for the new <response> rules. |
packtools/sps/validation/xml_validations.py |
Adds validate_response() and imports ResponseValidation. |
packtools/sps/validation/xml_validator.py |
Includes the new "response" group in the orchestrator output. |
tests/sps/validation/test_response.py |
New unit tests covering the 7 rules and custom error levels. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| element_name="response", | ||
| attribute_name="response-type", |
There was a problem hiding this comment.
In build_response(), format_advice() will auto-generate/override the provided advice when validation_type="exist" and any of element_name/attribute_name/sub_element_name are provided. As a result, this rule’s custom guidance (notably the required value reply) won’t appear in the output; you’ll get a generic ...="VALUE" suggestion instead. Consider either (a) omitting element_name/attribute_name for these exist validations so your custom advice is preserved, or (b) adjusting the utils helper to only auto-generate advice when advice is None (then keep the structured names).
| element_name="response", | |
| attribute_name="response-type", |
| def _get_response_elements(self): | ||
| """ | ||
| Yield context dicts for each <response> element found in the document. | ||
|
|
||
| Searches for <response> elements as children of <article> and | ||
| <sub-article>. | ||
| """ | ||
| root = self.xmltree.find(".") | ||
| if root is None: | ||
| return | ||
|
|
||
| for response_node in root.xpath(".//response"): | ||
| parent_node = response_node.getparent() | ||
| if parent_node is not None: | ||
| parent_tag = parent_node.tag | ||
| if parent_tag == "article": | ||
| parent_id = None | ||
| parent_article_type = parent_node.get("article-type") | ||
| parent_lang = parent_node.get(XML_LANG) | ||
| elif parent_tag == "sub-article": | ||
| parent_id = parent_node.get("id") | ||
| parent_article_type = parent_node.get("article-type") | ||
| parent_lang = parent_node.get(XML_LANG) | ||
| else: |
There was a problem hiding this comment.
_get_response_elements() docstring says it searches for as children of
/, but the XPath.//response matches any descendant. Since parent info is derived from the direct parent tag, a nested would be attributed to the wrong parent (or lose article-type/lang context). Either restrict the XPath to the intended locations (e.g., direct children of article/sub-article) or, if descendants are intended, resolve the nearest ancestor / for parent metadata and update the docstring accordingly.
| # 7 rules × 3 responses = 21, minus uniqueness (0 issues) = at least 18 | ||
| self.assertGreater(len(ok_results), 0) |
There was a problem hiding this comment.
This test is very permissive (assertGreater(len(ok_results), 0)) and doesn’t actually verify that all expected rule results were produced for the 3 valid elements. Since the expected count is deterministic with the current implementation (6 OK results per response when uniqueness yields nothing => 18 total), consider asserting the exact number of results and/or validating that each rule title appears 3 times with response=="OK" to prevent regressions.
| # 7 rules × 3 responses = 21, minus uniqueness (0 issues) = at least 18 | |
| self.assertGreater(len(ok_results), 0) | |
| # 6 OK results per response × 3 valid responses = 18 total results. | |
| self.assertEqual(len(results), 18) | |
| self.assertEqual(len(ok_results), 18) |
O que esse PR faz?
Implementa 7 regras de validação para o elemento
<response>conforme SPS 1.10, cobrindo atributos obrigatórios, unicidade de IDs e estrutura mínima esperada.@response-typepresença@response-typevalor ="reply"@xml:langpresença@idpresença@idunicidade<front-stub>presença<body>presençaOnde a revisão poderia começar?
packtools/sps/validation/response.py— contém a classeResponseValidationcom todos os 7 métodos de validação.Como este poderia ser testado manualmente?
Testar também via orchestrator:
31 testes unitários em
tests/sps/validation/test_response.pycobrem: atributos ausentes/vazios/whitespace, valores incorretos, IDs duplicados, respostas em<sub-article>, error levels customizáveis.Algum cenário de contexto que queira dar?
O
<response>identifica respostas a cartas/comentários publicadas no mesmo documento. Segue o mesmo padrão arquitetural dos validadores existentes (HistoryValidation,XMLPeerReviewValidation): classe de validação + rules JSON + integração no orchestrator viaxml_validations.py/xml_validator.py.Arquivos criados:
packtools/sps/validation/response.py—ResponseValidationpacktools/sps/validation_rules/response_rules.json— configuração de níveis de errotests/sps/validation/test_response.py— 31 testesArquivos modificados:
packtools/sps/validation/xml_validations.py— import +validate_response()packtools/sps/validation/xml_validator.py— yield do grupo"response"Screenshots
N/A — validação backend sem componente visual.
Quais são tickets relevantes?
Referências
<response>Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
api.crossref.orgfrom lxml import etree
from packtools.sps.validation.xml_validator import get_validation_results
from packtools.sps.validation.xml_validator_rules import get_default_rules
Test that response_rules loads from the JSON
rules = get_default_rules()
assert` (dns block)
Original prompt
This section details on the original issue you should resolve
<issue_title>Criar validações para o elemento </issue_title>
<issue_description>## Objetivo
Implementar validações para o elemento
<response>conforme a especificação SPS 1.10, aumentando a conformidade de X% para 70% (7 de 10 regras).Nota: Algumas validações para
<response>podem já estar parcialmente implementadas no repositório. Este Issue visa reavaliar, complementar e garantir cobertura completa das regras SPS 1.10.Contexto
O elemento
<response>identifica um conjunto de respostas referente a uma carta ou comentário, obrigatoriamente publicadas juntamente com a carta/comentário. É usado quando há múltiplas respostas no mesmo documento. Validações corretas garantem presença de atributos obrigatórios, unicidade de IDs, e estrutura mínima necessária.Conformidade atual: X de 10 regras implementadas (X%)
Meta após implementação: 7 de 10 regras (70%)
Documentação SPS
Referência oficial: https://docs.google.com/document/d/1GTv4Inc2LS_AXY-ToHT3HmO66UT0VAHWJNOIqzBNSgA/edit?tab=t.0#heading=h.response
Regras principais conforme SPS 1.10:
Ocorrência:
<response>pode aparecer zero ou mais vezes em<article>e<sub-article>Contexto de uso:
<sub-article article-type="reply"><article article-type="reply">Atributos obrigatórios:
@response-type="reply"(obrigatório)@xml:lang(obrigatório)@id(obrigatório)Unicidade de IDs:
@iddiferente para cada<response>Estrutura esperada:
<front-stub>- Metadados da resposta<body>- Conteúdo da resposta<back>- Seção final (opcional, pode conter<ref-list>)Regras a Implementar
P0 – Críticas (implementar obrigatoriamente)
@response-type@response-typeé obrigatório em<response>@response-type@response-typedeve ser"reply"@xml:lang@xml:langé obrigatório em<response>@id@idé obrigatório em<response>@id<response>deve ter um@idúnico (não pode repetir valores)P1 – Importantes (implementar se possível)
<front-stub><response>contenha<front-stub>com metadados da resposta<body><response>contenha<body>com o conteúdo da respostaP2 – Futuras (fora do escopo deste Issue)
Arquivos a Criar/Modificar
Avaliar existentes (podem ter validações parciais):
packtools/sps/models/response.pyou similar – Verificar se modelo existepacktools/sps/validation/response.py– Verificar validações existentespacktools/sps/validation/rules/response_rules.jsonou similar – Verificar configuraçãoCriar (se não existirem):
packtools/sps/models/response.py– Modelo de extração de dadospacktools/sps/validation/response.py– Validaçõespacktools/sps/validation/rules/response_rules.json– Configuração de níveis de errotests/sps/validation/test_response.py– Testes unitáriosReferenciar (implementações similares):
packtools/sps/validation/sub_article.py– Validação de estrutura similarpacktools/sps/validation/utils.py– Funções auxiliares (build_response)Exemplos de XML
XML Válido (deve passar sem erros):