Skip to content

Adiciona related_item para os relacionamentos hasReview, isCommentOn, isReplyTo, isReviewOf, hasPreprint, etc - #330

Open
samuelveigarangel wants to merge 9 commits into
scieloorg:masterfrom
samuelveigarangel:issue-329
Open

Adiciona related_item para os relacionamentos hasReview, isCommentOn, isReplyTo, isReviewOf, hasPreprint, etc#330
samuelveigarangel wants to merge 9 commits into
scieloorg:masterfrom
samuelveigarangel:issue-329

Conversation

@samuelveigarangel

@samuelveigarangel samuelveigarangel commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

O que esse PR faz?

Expande o XMLProgramRelatedItemPipe para exportar documentos relacionados no depósito Crossref, consumindo a property related_documents do xylose (campo legado v241).

As principais mudanças são:

  • Mapeamento de tipos SPS e article_type para relationship-type válidos em relations.xsd (hasReview, isCommentOn, isReplyTo, isReviewOf, hasPreprint, etc.).
  • Refatoração da criação dos nós <program>, <related_item>, <description> e relações inter_work/intra_work.
  • Manutenção das relações de tradução existentes (isTranslationOf / hasTranslation).
  • Novos testes com validação contra o XSD Crossref 4.4.0.

Tipos editoriais sem correspondência em relations.xsd (ex.: corrected-article, retracted-article) são ignorados.

Onde a revisão poderia começar?

Comece por articlemeta/export_crossref.py, na classe XMLProgramRelatedItemPipe — especialmente RELATION_TYPES, _transform_related_articles e os helpers _create_program, _get_or_create_program e _create_related_item.

Em seguida, veja os testes em tests/test_export_crossref.py:

  • test_related_articles_validating_against_schema
  • test_related_item_for_supported_related_articles
  • test_related_item_for_article_type_mappings

Como este poderia ser testado manualmente?

  1. Instale o xylose com a property related_documents (dependência do PR Add related_documents property and tests to Article xylose#207).
  2. Baixe um artigo com campo v241, por exemplo:
curl -s "https://articlemeta.scielo.org/api/v1/article/?collection=scl&code=S2237-96222025000100200" -o /tmp/article.json
  1. Gere o XML Crossref localmente:
python -c "
import json
from articlemeta.export import Export
article = json.load(open('/tmp/article.json'))
open('crossref.xml', 'wb').write(Export(article).pipeline_crossref())
"
  1. Verifique no XML gerado o bloco <program xmlns="http://www.crossref.org/relations.xsd"> com <inter_work_relation relationship-type="hasReview"> para pareceres (reviewer-report).
  2. Rode os testes automatizados:
python -m unittest tests.test_export_crossref

Algum cenário de contexto que queira dar?

Este PR depende da property related_documents no xylose, que abstrai o campo v241 e retorna dicionários com identifier, document_type, identifier_type e label.

A distinção entre inter_work_relation e intra_work_relation segue o relations.xsd: relações entre obras distintas (comentários, pareceres, resenhas) usam inter_work; variantes da mesma obra (traduções, preprints) usam intra_work.

Screenshots

N/A

Quais são tickets relevantes?

#329
scieloorg/xylose#207

Referências


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

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

  • Sim — descreva os controles de proteção aplicados (criptografia, mascaramento, anonimização, etc.):
  • Não

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

  • Sim — descreva o que mudou e por quê:
  • Não

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

  • Sim — as novas dependências foram verificadas no SBOM/Trivy sem vulnerabilidades críticas/altas em aberto?
    • Verificado e aprovado
    • Pendente / vulnerabilidade aceita com justificativa:
  • Não

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

  • Sim — link do job:
  • Não aplicável a este PR (justifique): alterações localizadas em código Python/Django e traduções; sem mudanças em infra/dependências.

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

  • Sim — confirme que há sanitização/parametrização (prepared statements, escaping, etc.):
  • Não

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

  • Sim — HTTPS obrigatório está garantido e o acesso segue o princípio de menor privilégio?
    • As novas telas são páginas do Wagtail ModelAdmin, acessíveis apenas a usuários autenticados e com permissões do Django.
  • Não

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

  • Não, nenhum segredo foi commitado
  • Sim (bloquear merge e corrigir antes de prosseguir)

samuelveigarangel and others added 5 commits July 14, 2026 14:12
Mapeia tipos SPS e article_type para relationship-type válidos em relations.xsd e inclui testes de validação XSD 4.4.0.

Co-authored-by: Cursor <cursoragent@cursor.com>
Usa PropertyMock para compatibilidade com a property related_documents do xylose.

Co-authored-by: Cursor <cursoragent@cursor.com>
Usa related_article_type do SciELO/JATS, desambigua commentary/letter pelo document_type do artigo corrente e ignora tipos sem equivalente no relations.xsd.

Co-authored-by: Cursor <cursoragent@cursor.com>
@robertatakenaka

Copy link
Copy Markdown
Member

@samuelveigarangel justificar o motivo de Tipos editoriais sem correspondência em relations.xsd (ex.: corrected-article, retracted-article) são ignorados. . Criar issue para tratar isso e adiciona o link aqui.

Comment thread articlemeta/export_crossref.py Outdated
Comment on lines +1131 to +1141
RELATION_TYPES = {
'commentary-article': ('inter_work_relation', 'isCommentOn'),
'reply': ('inter_work_relation', 'isReplyTo'),
'reviewed-article': ('inter_work_relation', 'isReviewOf'),
'reviewer-report': ('inter_work_relation', 'hasReview'),
'preprint': ('intra_work_relation', 'hasPreprint'),
'article-commentary': ('inter_work_relation', 'isCommentOn'),
'letter': ('inter_work_relation', 'isReplyTo'),
'book-review': ('inter_work_relation', 'isReviewOf'),
'editorial': ('inter_work_relation', 'isCommentOn'),
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samuelveigarangel adicionar comentário

Comment thread articlemeta/export_crossref.py Outdated
Comment on lines +1234 to +1236
related_articles = getattr(raw, 'related_documents', None)
if callable(related_articles):
related_articles = related_articles()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samuelveigarangel

try:
    related_articles = raw.related_documents()
except (AttributeError, TypeError):
     ....

Comment thread articlemeta/export_crossref.py Outdated
program_node = self._get_or_create_program(journal_article_node)

for related_article in related_articles:
document_type = related_article.get('document_type')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samuelveigarangel no xylose vc não usou o mesmo nome do SPS que é related_article_type ... ou este document_type é o article-type de article?

description_node = ET.Element('description')
description_node.text = raw.original_title()
related_item_node.append(description_node)
current_document_type = getattr(raw, 'document_type', None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samuelveigarangel é realemente necessário usar isso no lugar de raw.document_type?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Só pra evitar atributteError mas acredito que sempre vai existir document_type

@robertatakenaka robertatakenaka changed the title Adiciona exportação de documentos relacionados no Crossref Adiciona related_item para os relacionamentos hasReview, isCommentOn, isReplyTo, isReviewOf, hasPreprint, etc Jul 23, 2026
@pitangainnovare pitangainnovare self-assigned this Jul 23, 2026

@pitangainnovare pitangainnovare left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Encontrei um caso real que não está coberto pelos testes atuais. Faltou validar o outro lado da relação: os pareceres de revisão .a e .b, que devem apontar para o artigo avaliado por meio de isReviewOf.

Os registros usados na validação foram:

  • Parecer .a: S2237-96222025000100800
  • Parecer .b: S2237-96222025000100801

É possível reproduzir o problema com:

curl -fsS \
  "https://articlemeta.scielo.org/api/v1/article/?collection=scl&code=S2237-96222025000100800" \
  -o /tmp/article-a.json

curl -fsS \
  "https://articlemeta.scielo.org/api/v1/article/?collection=scl&code=S2237-96222025000100801" \
  -o /tmp/article-b.json

python - <<'PY'
import json
from pathlib import Path

from articlemeta.export import Export

for suffix in ("a", "b"):
    with open(f"/tmp/article-{suffix}.json", encoding="utf-8") as source:
        article = json.load(source)

    Path(f"crossref-{suffix}.xml").write_bytes(
        Export(article).pipeline_crossref()
    )
PY

Os XML são gerados com sucesso, mas ambos ficam com o programa de relações vazio:

<program xmlns="http://www.crossref.org/relations.xsd"/>

A causa é uma diferença entre o valor presente nos dados reais e o valor esperado pelo mapeamento do PR.

O ArticleMeta fornece a relação no campo legado v241:

"v241": [
  {
    "i": "10.1590/S2237-96222024v33e20240180.en",
    "t": "peer-reviewed-material",
    "_": "",
    "n": "doi"
  }
]

O xylose não cria, traduz ou normaliza o valor "peer-reviewed-material". A propriedade related_documents apenas repassa os subcampos de v241:

@property
def related_documents(self):
    if "v241" in self.data["article"]:
        related_documents = []

        for related_document in self.data["article"].get("v241"):
            item = {}

            if "i" in related_document:
                item["id"] = related_document["i"]

            if "t" in related_document:
                item["related_article_type"] = related_document["t"]

            if "n" in related_document:
                item["ext_link_type"] = related_document["n"]

            related_documents.append(item)

        if len(related_documents) > 0:
            return related_documents

Portanto, a linha:

item["related_article_type"] = related_document["t"]

repassa diretamente o valor de v241.t, resultando em:

{
    "id": "10.1590/S2237-96222024v33e20240180.en",
    "related_article_type": "peer-reviewed-material",
    "ext_link_type": "doi",
}

Isso também pode ser confirmado diretamente:

python - <<'PY'
import json
from xylose.scielodocument import Article

with open("/tmp/article-a.json", encoding="utf-8") as source:
    article = Article(json.load(source))

print(article.related_documents)
PY

Por outro lado, o mapeamento implementado no exportador reconhece somente:

"reviewed-article": ("inter_work_relation", "isReviewOf")

Como "peer-reviewed-material" não existe nesse mapeamento, _resolve_relation() retorna None. A relação é silenciosamente descartada e o elemento <program> permanece vazio.

Os testes atuais utilizam "reviewed-article" na estrutura simulada de related_documents. Por isso, passam sem cobrir o valor efetivamente recebido do ArticleMeta e repassado sem transformação pelo xylose.

Pelo que entendi, precisamos contemplar o valor encontrado nos dados reais:

"peer-reviewed-material": ("inter_work_relation", "isReviewOf")

Também é necessário adicionar pelo menos um teste com a estrutura real de related_documents, incluindo related_article_type="peer-reviewed-material".

O resultado esperado para cada parecer é uma relação deste formato:

<program xmlns="http://www.crossref.org/relations.xsd">
  <related_item>
    <inter_work_relation
      relationship-type="isReviewOf"
      identifier-type="doi">DOI_DO_ARTIGO_AVALIADO</inter_work_relation>
  </related_item>
</program>

@samuelveigarangel

Copy link
Copy Markdown
Contributor Author

Muito bom @pitangainnovare . No documento do sps não está previsto esse valor para related-article

samuelveigarangel and others added 2 commits July 27, 2026 17:49
Corrige hasTranslation/isTranslationOf no artigo original e nas traduções, expande o mapeamento de related-article-type e emite rel:program apenas para tipos mapeados.

Co-authored-by: Cursor <cursoragent@cursor.com>
@pitangainnovare

Copy link
Copy Markdown

@samuelveigarangel, seguem três pontos sobre as novas alterações:

  1. O caso real de peer-reviewed-material ficou correto, mas seria interessante adicionar um teste específico para esse valor e também para o outro lado da relação, reviewer-reporthasReview. Os testes atuais continuam usando reviewed-article, então não cobrem exatamente os valores recebidos do ArticleMeta.

  2. Seria importante revisar os fallbacks de commentary e letter. O PR já combina raw.document_type com v241.t, mas combinações não reconhecidas ainda podem gerar isReplyTo. Talvez seja mais seguro mapear somente as combinações conhecidas e ignorar as demais até termos casos reais documentados.

  3. Como <program> é criado antes de existir uma relação válida, documentos contendo apenas tipos não mapeados ainda podem gerar <program/> vazio. Mesmo que seja aceito pelo XSD, talvez seja melhor criar o elemento somente quando uma relação for efetivamente adicionada.

@samuelveigarangel

Copy link
Copy Markdown
Contributor Author

@rafaelpezzuto Para commentary e letter, há dois cenários possíveis. Quando related_item é commentary, o documento principal pode ser reply ou commentary-article. Porém, o Xylose não retorna o valor reply, apenas commentary-article para o documento principal. Entendo que isso ocorre porque os dados da SciELO ainda não estão normalizados. Vou especificar esse valor no dicionário, prevendo que futuramente esses dados sejam contemplados.

…vazio.

Adiciona cobertura para peer-reviewed-material/reviewer-report e reply como documento principal, criando <program> só quando há related_item.

Co-authored-by: Cursor <cursoragent@cursor.com>
@pitangainnovare
pitangainnovare self-requested a review July 28, 2026 15:40
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.

3 participants