Skip to content

Identificação e inserção automática de xref no pipeline DOCX → SPS XML#109

Open
Rossi-Luciano wants to merge 3 commits into
scieloorg:mainfrom
Rossi-Luciano:add-xref-linking
Open

Identificação e inserção automática de xref no pipeline DOCX → SPS XML#109
Rossi-Luciano wants to merge 3 commits into
scieloorg:mainfrom
Rossi-Luciano:add-xref-linking

Conversation

@Rossi-Luciano
Copy link
Copy Markdown
Contributor

Contexto

O padrão SPS/JATS exige que toda citação bibliográfica no corpo do artigo esteja marcada com <xref ref-type="bibr" rid="Bn">, ligando-a à entrada correspondente na lista de referências. Anteriormente, a geração dessas tags dependia exclusivamente de um LLM para parsear as referências e identificar citações — mecanismo com cobertura baixa e imprevisível: sem o modelo disponível, nenhum <xref> era gerado.

Esta branch implementa um pipeline determinístico e independente de LLM para essa tarefa, usando o próprio DOCX como fonte de verdade. O LLM passa de mecanismo primário a camada de fallback.


Fluxo do pipeline

DOCX original
    │
    ▼
① mark_references(doc)                          [xref.py]
    ├─ Detecta estilo de citação (ABNT / Vancouver bracket / superscript)
    ├─ Adiciona bookmarks xref_B1…xref_Bn em cada entrada da lista de referências
    └─ Insere hyperlinks internos do Word: citação → bookmark correspondente
    │
    ▼
② validate_marks(doc)                           [xref.py]
    ├─ Verifica consistência: hyperlinks sem bookmark (erro) / bookmarks sem citação (aviso)
    └─ Grava resultado em xref_status (JSONField no modelo)
    │
    ▼
③ DOCX marcado salvo como marked_file           [tasks.py]
    └─ Disponível para download e revisão humana na interface Wagtail
    │
    ▼
④ read_marks(doc) → xref_map                   [tasks.py]
    └─ Extrai mapeamento {texto de citação → rid} dos hyperlinks do DOCX
       Ranges expandidos: "[26-27]" → rid="B26 B27"
    │
    ▼
⑤ Pré-processamento de stream_data_body        [tasks.py]
    ├─ Passe 1: xref_map — substituição exata pelos hyperlinks do DOCX
    └─ Passe 2: text_xref_fn — matching regex de Author (year) narrativo/parentético
    │
    ▼
⑥ get_xml() — geração do XML SPS               [xml.py]
    ├─ Passe 1: xref_map (segmentado — não reprocessa xrefs já inseridos)
    ├─ Passe 2: text_xref_fn (já segmentado internamente)
    └─ Passe 3: proccess_labeled_text (LLM, fallback, segmentado)
    │
    ▼
XML com <xref ref-type="bibr" rid="Bn"> em todas as citações identificadas

Os passes ⑤ e ⑥ operam por segmento: o parágrafo é dividido nas fronteiras de <xref> já existentes e cada camada processa apenas o texto puro, eliminando risco de double-wrapping e garantindo que citações num parágrafo parcialmente marcado não sejam ignoradas.


Cobertura por estilo de citação

Fixture Estilo Refs Links DOCX Cobertura DOCX Cobertura XML estimada
bn-2025-1870 ABNT narrativo 52 23 44% ~81%
bn-2025-1828 ABNT parentético 95 42 44% ~65–70%
ean-2025-0109-pt Vancouver superscript 31 27 87% ~87%
mr-2025-0709 Vancouver bracket 33 28 85% ~91%*

*B27 e B30 cobertos via expansão de range [26-27] / [29-30] → sem hyperlink direto no DOCX, mas presentes no rid do XML.

Por que não é 100%:

  • ABNT: citações em grupo temporal ("Yano 1981, ..., 2018") geram um único hyperlink para a primeira entrada; ambiguidades autor+ano sem co-autor explícito são conservadoramente não linkadas; refs institucionais sem padrão autor+ano (GBIF.org, SpeciesLink) são insolúveis por regex.
  • Vancouver: refs sem hyperlink são genuinamente não citadas no corpo.

Comparação com a versão anterior (apenas LLM via proccess_labeled_text):

Estilo Antes Depois
ABNT ~10–20% ~65–81%
Vancouver bracket ~30–50% ~91%
Vancouver superscript ~0–5% ~87%

O que foi implementado

markup_doc/xref.py — novo módulo

Core da feature. Contém toda a lógica de identificação de citações no DOCX:

  • Detecção de estilo de citação (ABNT, Vancouver bracket, Vancouver superscript)
  • mark_references(), validate_marks(), read_marks(), build_text_xref_replacer()
  • Detecção robusta de fim da seção de referências: lista de headings conhecidos + heurística ALL-CAPS + estilo Word Heading N

markup_doc/tasks.py

Integração do pipeline xref antes do processamento principal. Expansão de ranges Vancouver no xref_map. Pré-processamento de stream_data_body com os dois primeiros passes.

markup_doc/xml.py

Substituição do guard coarse if 'xref' not in paragraph por processamento por segmento via _apply_to_segments(), _apply_xref_map() e _apply_proccess_labeled_text(). Todos os parágrafos recebem os três passes; xrefs pré-existentes são preservados.

markup_doc/models.py

Campos marked_file (FileField) e xref_status (JSONField) em ArticleDocxMarkup. Widgets DownloadMarkedFileWidget, XrefStatusWidget e painel ReprocessButtonPanel. Proxy model ProcessedDocx com view dedicada no admin.

markup_doc/views.py + markup_doc/wagtail_hooks.py

Views download_marked_docx e reprocess. ProcessedDocxViewSet registrado no grupo de marcação editorial.

markup_doc/static/js/xref-button.js

Botão "Reprocessar" injetado na barra de ações do cabeçalho Wagtail nas views de ProcessedDocx e MarkupXML, com mensagem de confirmação contextual.

Correções de bugs colaterais

markup_doc/labeling_utils.py:

  • Regex de detecção da seção de referências mais precisa
  • resp_json = {} inicializado antes do bloco condicional (prevenia UnboundLocalError)
  • < literal escapado em append_fragment (prevenia quebra do parser XML)
  • Guarda de None em proccess_special_content

markuplib/function_docx.py:

  • is_numPr inicializado antes do loop (tabelas após listas eram descartadas silenciosamente)
  • Parágrafos adicionados a content independente de tabelas adjacentes

Migrações

  • 0003_articledocxmarkup_marked_file
  • 0004_articledocxmarkup_xref_status

🤖 Generated with Claude Code

Rossi-Luciano and others added 3 commits May 31, 2026 18:52
…OCX → SPS XML)

Adiciona módulo xref.py com detecção de estilo de citação (ABNT narrativo,
ABNT parentético, Vancouver bracket, Vancouver superscript), marcação de
bookmarks e hyperlinks no DOCX, validação de consistência e extração do
mapeamento citação→rid.

Integra o pipeline em tasks.py: o DOCX é marcado antes do processamento
principal, o xref_map é aplicado em stream_data_body e repassado a get_xml().
Ranges Vancouver são expandidos para rid multi-valor (ex: "[26-27]" → "B26 B27").

Em xml.py, substitui o guard coarse `if 'xref' not in paragraph` por
processamento por segmento via _apply_to_segments(), eliminando risco de
double-wrapping e garantindo que citações em parágrafos parcialmente marcados
não sejam ignoradas. O LLM (proccess_labeled_text) passa a atuar como fallback.

Adiciona campos marked_file e xref_status ao modelo ArticleDocxMarkup, com
widgets visuais, proxy model ProcessedDocx e views download_marked_docx e
reprocess para suporte à revisão humana via interface Wagtail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
labeling_utils.py:
- Regex de detecção da seção de referências mais precisa (refer[eê]nci|references?)
- resp_json inicializado antes do bloco condicional (evitava UnboundLocalError)
- Escapa '<' literal em append_fragment para não quebrar o parser XML
- Guarda None em proccess_special_content para search_special_id retornando None

function_docx.py:
- is_numPr inicializado antes do loop (tabelas após listas eram descartadas
  silenciosamente por flag herdada da iteração anterior)
- Parágrafos adicionados a content independente de tabelas adjacentes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Insere botão na barra de ações do cabeçalho nas views de edição de
ProcessedDocx e MarkupXML, com mensagem de confirmação contextual.
Usa MutationObserver como fallback para injeção no DOM quando o
cabeçalho ainda não está renderizado.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@gitnnolabs
Copy link
Copy Markdown
Contributor

Revisando....

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.

2 participants