From 12437464ad2afec53c10e91dc5f14cc01335e4fc Mon Sep 17 00:00:00 2001 From: Rossi-Luciano Date: Fri, 10 Jul 2026 14:47:11 -0300 Subject: [PATCH] =?UTF-8?q?fix(pubmed):=20mapeia=20@article-type=20para=20?= =?UTF-8?q?o=20vocabul=C3=A1rio=20fechado=20do=20PublicationType?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fecha #1240. get_publication_type fazia só @article-type.replace("-", " ").title(), gerando valores fora do vocabulário fechado do PubMed (ex.: "Research Article" em vez de "Journal Article"), que a doc do PubMed (NBK3828) e a DTD não reconhecem. Usa a tabela de correlação direta proposta no comentário da issue (research-article, case-report, review-article, letter, editorial, retraction/partial-retraction, correction, data-article) mais expression-of-concern, que também tem equivalente óbvio no vocabulário. Para @article-type sem correlação clara (obituary, brief-report, book-review, etc.) omite em vez de forçar um default -- o elemento é opcional na DTD (PublicationType*), então omitir é mais seguro que arriscar uma classificação errada num vocabulário fechado. --- packtools/sps/formats/pubmed.py | 30 +++++++++-- tests/sps/formats/test_pubmed.py | 90 ++++++++++++++++++++++++++++++-- 2 files changed, 113 insertions(+), 7 deletions(-) diff --git a/packtools/sps/formats/pubmed.py b/packtools/sps/formats/pubmed.py index f99b2651d..e7f4935ac 100644 --- a/packtools/sps/formats/pubmed.py +++ b/packtools/sps/formats/pubmed.py @@ -401,12 +401,36 @@ def xml_pubmed_author_list(xml_pubmed, xml_tree): xml_pubmed.append(author_list_tag) +# Maps SPS `@article-type` to the PubMed closed PublicationType vocabulary +# (https://www.ncbi.nlm.nih.gov/books/NBK3828/). Only pairs with a clear, +# unambiguous equivalence are listed here -- see get_publication_type for +# what happens to the rest. +PUBLICATION_TYPE_MAPPING = { + "research-article": "Journal Article", + "case-report": "Case Reports", + "review-article": "Review", + "letter": "Letter", + "editorial": "Editorial", + "retraction": "Retraction Notice", + "partial-retraction": "Retraction Notice", + "correction": "Published Erratum", + "data-article": "Journal Article", + "expression-of-concern": "Expression of Concern", +} + + def get_publication_type(xml_tree): - publication_type = article_and_subarticles.ArticleAndSubArticles( + """ + `PublicationType` is optional in the PubMed DTD (`PublicationType*`), so + an `@article-type` with no safe mapping (e.g. "obituary", "book-review", + "other") is omitted rather than defaulted to "Journal Article" -- forcing + a value into this closed vocabulary would misrepresent the document type + to PubMed's indexing, which is worse than leaving it out. See issue #1240. + """ + article_type = article_and_subarticles.ArticleAndSubArticles( xml_tree ).main_article_type - if publication_type is not None: - return publication_type.replace("-", " ").title() + return PUBLICATION_TYPE_MAPPING.get(article_type) def xml_pubmed_publication_type(xml_pubmed, xml_tree): diff --git a/tests/sps/formats/test_pubmed.py b/tests/sps/formats/test_pubmed.py index a8afad598..6bfc471d5 100644 --- a/tests/sps/formats/test_pubmed.py +++ b/tests/sps/formats/test_pubmed.py @@ -1013,12 +1013,9 @@ def test_xml_pubmed_author_list_without_author(self): self.assertEqual(obtained, expected) def test_xml_pubmed_publication_type(self): - # TODO - # Originalmente, espera-se que o valor da tag seja Journal Article - # Nos arquivos de exemplo há somente a referencia a Research Article, o qual foi utilizado expected = ( '
' - 'Research Article' + 'Journal Article' '
' ) xml_pubmed = ET.fromstring( @@ -1037,6 +1034,91 @@ def test_xml_pubmed_publication_type(self): self.assertEqual(obtained, expected) + def test_xml_pubmed_publication_type_maps_case_report(self): + expected = ( + '
' + 'Case Reports' + '
' + ) + xml_pubmed = ET.fromstring( + '
' + ) + xml_tree = ET.fromstring( + '
' + '
' + ) + + xml_pubmed_publication_type(xml_pubmed, xml_tree) + + obtained = ET.tostring(xml_pubmed, encoding="utf-8").decode("utf-8") + + self.assertEqual(obtained, expected) + + def test_xml_pubmed_publication_type_maps_retraction_and_partial_retraction(self): + for article_type in ("retraction", "partial-retraction"): + with self.subTest(article_type=article_type): + xml_pubmed = ET.fromstring('
') + xml_tree = ET.fromstring( + '
' + '
' + ) + + xml_pubmed_publication_type(xml_pubmed, xml_tree) + + obtained = ET.tostring(xml_pubmed, encoding="utf-8").decode("utf-8") + + self.assertEqual( + obtained, + '
Retraction Notice
', + ) + + def test_xml_pubmed_publication_type_maps_expression_of_concern(self): + expected = ( + '
' + 'Expression of Concern' + '
' + ) + xml_pubmed = ET.fromstring( + '
' + ) + xml_tree = ET.fromstring( + '
' + '
' + ) + + xml_pubmed_publication_type(xml_pubmed, xml_tree) + + obtained = ET.tostring(xml_pubmed, encoding="utf-8").decode("utf-8") + + self.assertEqual(obtained, expected) + + def test_xml_pubmed_publication_type_omits_article_type_without_mapping(self): + # "obituary" has no unambiguous PubMed PublicationType equivalent; + # PublicationType is optional in the DTD, so it's omitted rather + # than defaulted to a possibly-wrong value (see issue #1240). + expected = '
' + xml_pubmed = ET.fromstring( + '
' + ) + xml_tree = ET.fromstring( + '
' + '
' + ) + + xml_pubmed_publication_type(xml_pubmed, xml_tree) + + obtained = ET.tostring(xml_pubmed, encoding="utf-8").decode("utf-8") + + self.assertEqual(obtained, expected) + def test_xml_pubmed_publication_type_without_type(self): expected = ( '
'