Skip to content

feat(pubmed): adicionar Suffix e CollectiveName em AuthorList#1245

Open
Rossi-Luciano wants to merge 2 commits into
scieloorg:masterfrom
Rossi-Luciano:feat/1236-pubmed-suffix-collective-name
Open

feat(pubmed): adicionar Suffix e CollectiveName em AuthorList#1245
Rossi-Luciano wants to merge 2 commits into
scieloorg:masterfrom
Rossi-Luciano:feat/1236-pubmed-suffix-collective-name

Conversation

@Rossi-Luciano

Copy link
Copy Markdown
Contributor

O que esse PR faz?

Implementa <Suffix> e <CollectiveName> em <AuthorList> no conversor SPS → PubMed XML (packtools/sps/formats/pubmed.py), fechando 2 dos # TODO marcados no código original. Os dados já estavam disponíveis no model article_contribs.Contrib (contrib_name["suffix"] e collab), só faltava conectar.

A DTD do PubMed exige que <Author> tenha nome pessoal OU CollectiveName, nunca os dois juntos:

<!ELEMENT Author (((FirstName, MiddleName?, LastName, Suffix?, Initials?) | CollectiveName), %affiliation;, Identifier*)>

xml_pubmed_author_list agora respeita essa exclusividade: contribs que só têm <collab> (sem <name>) — o padrão SPS 1.10 para autoria em grupo — geram <Author><CollectiveName>...</CollectiveName></Author>; os demais continuam gerando FirstName/LastName/Suffix como antes.

Também corrige um bug em get_affiliations: contribs sem afiliação vinculada (comum justamente no caso de CollectiveName, cujo único xref costuma apontar para author-notes, não para aff) não tinham a chave "affs" no dicionário, e for item in author_reg.get("affs") lançava TypeError: 'NoneType' object is not iterable. Sem esse fix, qualquer artigo com autoria em grupo quebraria o pipeline inteiro ao tentar gerar CollectiveName.

Onde a revisão poderia começar?

packtools/sps/formats/pubmed.py: add_suffix, add_collective_name (funções novas), get_affiliations (fix de uma linha), e xml_pubmed_author_list (lógica de exclusividade FirstName/LastName vs. CollectiveName).

Como este poderia ser testado manualmente?

  1. Rodar a suíte: source .venv/bin/activate && python -m pytest tests/sps/formats/test_pubmed.py -v (48 passed: 46 originais + 2 novos).
  2. Reproduzir o bug do get_affiliations antes do fix (reverter só essa linha) e rodar:
    from lxml import etree as ET
    from packtools.sps.formats import pubmed
    
    xml_tree = ET.fromstring(
        '<article xmlns:mml="http://www.w3.org/1998/Math/MathML" '
        'xmlns:xlink="http://www.w3.org/1999/xlink" article-type="research-article" '
        'dtd-version="1.1" specific-use="sps-1.9" xml:lang="en">'
        '<front><article-meta>'
        '<contrib-group>'
        '<contrib contrib-type="author" id="collab">'
        '<collab>The SciELO Group</collab>'
        '<xref ref-type="author-notes" rid="fn1">1</xref>'
        '</contrib>'
        '</contrib-group>'
        '</article-meta></front></article>'
    )
    xml_pubmed = ET.fromstring('<Article/>')
    pubmed.xml_pubmed_author_list(xml_pubmed, xml_tree)
    print(ET.tostring(xml_pubmed, pretty_print=True).decode())
    Sem o fix: TypeError: 'NoneType' object is not iterable. Com o fix: <Author><CollectiveName>The SciELO Group</CollectiveName></Author>, sem FirstName/LastName vazios.
  3. Rodar contra um artigo real (tests/samples/0034-7094-rba-69-03-0227.xml, sem autoria em grupo) para confirmar que o comportamento de autores individuais não mudou.

Algum cenário de contexto que queira dar?

Parte da quebra da #1226 em sub-issues. O padrão SPS 1.10 para autoria em grupo (documentado no guia interno da SciELO) é:

<contrib-group>
   <contrib contrib-type="author" id="collab">
     <collab>The SciELO Group</collab>
   </contrib>
</contrib-group>
<contrib-group content-type="collab-list">
    <contrib contrib-type="author" rid="collab">
      <name>...</name>
    </contrib>
    ...
</contrib-group>

Este PR cobre apenas o <collab> do primeiro contrib-group (o "nome do grupo"). Os membros individuais do content-type="collab-list" continuam sendo listados como Author comuns em AuthorList (comportamento herdado, sem mudança) — mover esses membros para GroupList/IndividualName é escopo da #1237, que também depende deste PR (mesma função xml_pubmed_author_list).

Quais são tickets relevantes?

Closes #1236. Relacionado a #1226 (issue-mãe), #1237 (depende deste PR).

Referências

Implementa add_suffix e add_collective_name em
packtools/sps/formats/pubmed.py, usando dados já expostos pelo model
article_contribs.Contrib (contrib_name["suffix"] e collab).

A DTD do PubMed exige que <Author> tenha nome pessoal
(FirstName/MiddleName/LastName/Suffix) OU CollectiveName, nunca os
dois: (((FirstName, MiddleName?, LastName, Suffix?, Initials?) |
CollectiveName), affiliation, Identifier*). xml_pubmed_author_list
agora respeita essa exclusividade: contribs com <collab> (sem <name>)
geram só CollectiveName, os demais geram FirstName/LastName/Suffix
como antes.

Corrige também um bug em get_affiliations: contribs sem afiliação
vinculada (comum no caso de CollectiveName, cujo único xref costuma
ser para author-notes) não tinham a chave "affs" no dict, causando
TypeError ao iterar author_reg.get("affs"). Agora usa
`or []` como fallback.

Refs scieloorg#1226, scieloorg#1236
@Rossi-Luciano

Copy link
Copy Markdown
Contributor Author

Validado localmente contra a DTD oficial do PubMed (https://dtd.nlm.nih.gov/ncbi/pubmed/in/PubMed.dtd, via lxml.etree.DTD), combinando esta branch com o #1243 (#1234, necessário porque a DTD exige a raiz ArticleSet):

(O validador linkado na issue-mãe #1226 não serve para este formato — ver comentário em #1226.)

A DTD do PubMed exige FirstName e LastName sempre juntos em Author
(nenhum dos dois é opcional isoladamente):
  Author (((FirstName, MiddleName?, LastName, Suffix?, Initials?) | CollectiveName), ...)

Encontrado ao validar a saída da CLI em lote (scieloorg#1241) contra a DTD real
usando tests/samples/example.xml: um contrib tem <given-names> mas não
tem <surname>, gerando <Author><FirstName>...</FirstName>...</Author>
sem <LastName> — inválido segundo a DTD (confirmado empiricamente:
Author só com FirstName ou só com LastName sempre falha a validação).

add_first_name e add_last_name agora caem para o outro campo quando o
esperado está ausente, duplicando o único nome disponível em ambos —
preserva a identificação do autor em vez de descartá-lo ou gerar XML
inválido.

Refs scieloorg#1226, scieloorg#1236
@Rossi-Luciano

Copy link
Copy Markdown
Contributor Author

Adicionado mais um commit a este PR. Achado ao validar a #1241 (CLI em lote) contra a DTD real usando `tests/samples/example.xml`: a DTD do PubMed exige `FirstName` e `LastName` sempre juntos em `Author` (nenhum dos dois é opcional isoladamente), mas há um contrib nesse artigo real com `` e sem ``, gerando `...` sem `LastName` — inválido segundo a DTD (confirmei empiricamente: `Author` só com `FirstName` ou só com `LastName` sempre falha).

`add_first_name`/`add_last_name` agora caem para o outro campo quando o esperado está ausente, duplicando o único nome disponível em ambos. Teste de regressão adicionado.

Suíte: 49/49 passando.

Rossi-Luciano added a commit to Rossi-Luciano/packtools that referenced this pull request Jul 10, 2026
…perdida

Encontrados ao validar as 26 amostras reais de tests/samples/ contra a
DTD real do PubMed em vez de só 1-2 arquivos escolhidos a dedo.

1. get_date() só olhava article_date (epub), ignorando collection_date
   (pub-type="epub-ppub"/date-type="collection") -- o padrão mais comum
   em artigos SciELO mais antigos. Isso descartava 21 de 26 (81%) das
   amostras reais do lote inteiro por "falta de PubDate", quando na
   verdade tinham data. references.py e oai_dc.py já fazem esse fallback
   manualmente; só pubmed.py não fazia.

2. xml_pubmed_citations quebrava com AttributeError quando <ref-list>
   não tem <title> (opcional no JATS), pois presumia que
   xml_pubmed_title_reference_list já tinha criado <ReferenceList>.
   Agora cria o elemento se necessário -- válido pela DTD
   (ReferenceList (Title?, Reference*, ReferenceList*)).

3. get_first_page só olhava elocation-id, nunca fpage; LastPage não
   existia. Artigos com paginação tradicional (fpage/lpage) perdiam
   essa informação por completo. Agora FirstPage prefere fpage e cai
   para elocation-id só quando não há paginação (fluxo de Publicação
   Contínua); LastPage foi implementado.

Validado: 24/26 amostras reais passam na DTD real (as 2 restantes
dependem do fix de Author FirstName/LastName de scieloorg#1236/scieloorg#1245, ainda não
mesclado nesta branch -- não são regressão destes fixes).
Rossi-Luciano added a commit to Rossi-Luciano/packtools that referenced this pull request Jul 10, 2026
…ra a DTD

Diferente de test_pubmed.py (uma pipe por vez, XML sintético mínimo),
este teste roda o pipeline completo (build_pubmed_article +
build_article_set_xml) em cada amostra real de tests/samples/*.xml e
valida o resultado contra a DTD real do PubMed.

A DTD foi vendorizada em tests/fixtures/pubmed/PubMed.dtd para o teste
não depender de rede (CI sem acesso à internet, ou instabilidade do
servidor da NLM não devem quebrar a suíte).

Essa é exatamente a lacuna que deixou passar despercebidos os 3 bugs
corrigidos no commit anterior (PubDate/collection_date, crash em
citations, FirstPage/LastPage) por 8 PRs já abertos.

Duas amostras (example.xml, 0034-8910-rsp-48-2-0249.xml) ainda
dependem de fixes que vivem em scieloorg#1236/PR scieloorg#1245 (Author FirstName/
LastName pairing e fallback `or []` em get_affiliations), ainda não
mesclados nesta branch -- marcadas em KNOWN_FAILURES com asserção
inversa, para que a suíte avise (ao invés de silenciosamente continuar
verde) quando alguém puder remover a entrada.
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.

Pubmed XML: adicionar Suffix e CollectiveName em AuthorList

2 participants