[Evolução do PR #1449] Atualiza o serviço de registro de XMLs do PID Provider com auditoria por documento e controle dinâmico no Wagtail - #1466
Merged
pitangainnovare merged 32 commits intoAug 7, 2026
Conversation
Propósito: disponibilizar os novos métodos do packtools utilizados pelas refatorações do pid_provider (get_article_data, get_complete_publication_date, deprecated_sps_pkg_name_list, get_body_fragment). Solução técnica: bump de versão fixado via git+https no requirements/base.txt.
Propósito: padronizar os valores possíveis do campo status do modelo XMLURL, substituindo texto livre por opções controladas. Solução técnica: cria XMLURL_STATUS_SUCCESS, XMLURL_STATUS_XML_FETCH_FAILED, XMLURL_STATUS_PID_PROVIDER_XML_FAILED e a tupla XMLURL_STATUS.
Propósito: permitir que is_updated() sinalize, via exceção, que o registro deve ser ignorado (equal ou já atualizado), em vez de retornar dados silenciosamente. Solução técnica: cria a classe SkipSavePidProviderXML(Exception).
…omparação por similaridade Propósito: substituir o cálculo de score aditivo por campo por comparação percentual baseada em similaridade textual, e simplificar o builder de queries centralizando o acesso aos dados do adaptador. Solução técnica: adiciona compare/compare_items/compare_lists usando how_similar; QueryBuilderPidProviderXML passa a usar adapter_data, compare_data e xml_with_pre_data como fonte única, com validate_input_data() e article_location_params novos; remove as antigas cached_property espelhando atributos do xml_adapter.
…e XML e cria auditoria por documento
Propósito: tornar o processo de identificação de documentos já registrados
mais preciso (comparação por similaridade em estágios) e garantir
rastreabilidade de toda tentativa de registro (sucesso, erro, conflito,
skip), substituindo o modelo de eventos XMLEvent.
Solução técnica:
- get_records/get_record/best_matches substituídos por select_records
(generator em estágios: ids, journal-issue-article, journal-article),
select_record e get_best_match, usando percentual_score.
- PidProviderXML.register() reescrito com try/except/finally, sempre
gravando o evento via PidProviderXMLRegistration.record().
- is_updated() passa a levantar SkipSavePidProviderXML em vez de retornar
registered.data.
- Novo PidProviderXMLManager aplicando select_related('current_version')
por padrão.
- Novo campo readable_data em PidProviderXML.
- add_collections extraído de _save(), com fallback via FieldError entre
scielojournal e journalproc (compatibilidade upload/core).
- Corrige bug em merge_records (other_pid.version, não
other_pid.current_version) e em mark_items_as_invalid (persiste o status
calculado via bulk_update).
- XMLURL ganha os campos detail e is_public e o classmethod record().
- Remove modelo XMLEvent e o método add_event().
…o de XMLEvent Propósito: aplicar no banco de dados as mudanças de modelo introduzidas na refatoração do pid_provider. Solução técnica: cria o modelo PidProviderXMLRegistration com seus índices (pid_provide_pkg_nam_2db0b2_idx, pid_provide_event_s_3c9ae7_idx, pid_provide_created_94fb08_idx, pid_provide_pid_pro_c9fb0e_idx); remove os campos creator, ppxml e updated_by de XMLEvent e em seguida exclui o modelo XMLEvent; adiciona readable_data em PidProviderXML; adiciona detail e is_public em XMLURL, altera o campo status (com choices) e cria o índice pid_provide_is_public_idx.
…onViewSet no Wagtail admin Propósito: permitir consulta, pela interface administrativa, das URLs com falha de processamento (XMLURL) e dos eventos de auditoria de registro de XML (PidProviderXMLRegistration). Solução técnica: cria XMLURLViewSet e PidProviderXMLRegistrationViewSet (list_display, list_filter, search_fields e select_related no get_queryset) e os inclui em PidProviderViewSetGroup.
…erXML Propósito: Reverter a remoção do model XMLEvent feita em refatoração anterior — o rastreio de eventos por documento (registration attempts, validation errors, etc.) via XMLEvent ainda é necessário e não deve ser substituído pelo novo PidProviderXMLRegistration, que serve a um propósito complementar (auditoria agregada de register()), não ao histórico de eventos por instância de PidProviderXML. Solução técnica: Reintroduzido o import de BaseEvent em 'from tracker.models import BaseEvent, UnexpectedEvent'. Restaurado o método PidProviderXML.add_event(name, proc_status, detail=None, errors=None, exceptions=None), que atualiza proc_status, salva a instância e delega o registro do evento a XMLEvent.register(). Restaurado o model XMLEvent(BaseEvent, CommonControlField), com o campo ppxml (ParentalKey para PidProviderXML, related_name='events') e o classmethod register(), que cria a instância, marca completed conforme presença de errors/exceptions e delega a finalização a BaseEvent.finish().
Propósito: Consistência com a reversão feita em models.py — como o model XMLEvent voltou a existir no código, a migration 0017 (que removia seus campos creator/ppxml/updated_by e por fim o próprio model via DeleteModel) deixou de refletir o estado real dos models e precisa ser descartada. Solução técnica: Excluído o arquivo pid_provider/migrations/0017_pidproviderxmlregistration_remove_xmlevent_creator_and_more.py. A criação de PidProviderXMLRegistration e os demais campos que essa migration também introduzia (readable_data, XMLURL.detail/is_public, choices de XMLURL.status, índices) precisam ser recriados em uma nova migration, desta vez sem a remoção de XMLEvent — rodar 'python manage.py makemigrations pid_provider' para gerá-la a partir do estado atual dos models.
…dência de artigos
Propósito:
A query anterior usava OR (|) entre z_surnames, z_collab, z_links e
z_partial_body, o que permitia que documentos totalmente diferentes
fossem retornados como correspondência só por coincidirem em
z_partial_body — campo que, no formato antigo, era o hash do primeiro
parágrafo e acabava coincidindo com títulos de seção repetidos entre
artigos distintos. Isso gerava falsos positivos no select_records.
Solução técnica:
- compare_items: usa 'input_data or ""' e 'registered or ""' antes de
chamar how_similar, evitando passar None para a função de similaridade.
- article_data_query: removida a lógica OR entre os campos textuais;
agora sempre retorna um Q com AND entre z_surnames, z_collab, z_links
e z_partial_body, exigindo que todos os campos disponíveis coincidam
simultaneamente, eliminando a ambiguidade do OR.
- Novo método get_article_data_query(issue), separando a combinação da
query de dados do artigo conforme o escopo da busca:
- issue=True: article_data_query & issue_params & article_location_params
(correspondência dentro de um fascículo específico).
- issue=False: article_data_query & filtro garantindo
volume/number/suppl/elocation_id/fpage/lpage nulos (correspondência
apenas por journal + dados do artigo, sem fascículo, evitando
confundir com artigos de fascículos diferentes).
…t_records
Propósito:
Migrar o fingerprint principal de correspondência de artigos de
z_partial_body para body_fragment_fingerprint, disponibilizar os dados
legíveis do artigo com fallback ao XML, e centralizar a lógica de query
de seleção de registros no QueryBuilderPidProviderXML.
Solução técnica:
- Adicionado comentário documentando a migração de z_partial_body (hash
do primeiro parágrafo, ambíguo) para body_fragment_fingerprint (hash de
300 caracteres); registros antigos mantêm o valor legado e a query de
match passa a cobrir ambos os formatos sem exigir backfill.
- Novo método get_readable_data(): retorna self.readable_data se já
existir; caso contrário, calcula via self.xml_with_pre.get_article_data();
retorna {} se não houver XML disponível.
- as_dict() passa a incluir os dados de get_readable_data() no dicionário
retornado.
- data_to_compare agora usa get_readable_data() em vez de acessar
readable_data diretamente, garantindo dados mesmo antes da persistência.
- select_records: substituídas as queries inline
(Q(**qbuilder.issue_params) & qbuilder.article_data_query e
qbuilder.article_data_query) pelas chamadas
qbuilder.get_article_data_query(issue=True) e
qbuilder.get_article_data_query(issue=False), centralizando a lógica de
filtro no QueryBuilder.
- z_partial_body passa a ser preenchido a partir de
xml_adapter.xml_with_pre.body_fragment_fingerprint, em vez de
xml_adapter.z_partial_body.
…_query
Propósito:
Registros gravados antes desta correção têm z_partial_body como hash do
primeiro parágrafo não vazio do corpo (formato legado, sujeito a
colisão entre artigos diferentes que compartilham o mesmo texto de
seção, ex.: rótulos genéricos como "ARTIGO DE REVISÃO"). A partir de
agora, o campo passa a ser preenchido com o fingerprint do corpo INTEIRO
do artigo (xml_with_pre.body_fragment_fingerprint), mais robusto. É
preciso que a query de correspondência aceite ambos os formatos
simultaneamente, sem exigir migração/backfill dos registros antigos.
Solução técnica:
- __init__: adiciona self.z_body_fragment, obtido diretamente de
xml_adapter.xml_with_pre.body_fragment_fingerprint, sem exigir
mudanças no packtools nem no PidProviderXMLAdapter.
- Novo property partial_body_query:
- Reúne em candidates os hashes não vazios entre
adapter_data['z_partial_body'] (formato legado) e
self.z_body_fragment (formato atual).
- Se houver candidatos, usa Q(z_partial_body__in=candidates) para
casar com registros gravados em qualquer um dos dois formatos.
- Se não houver nenhum candidato (ambos None), usa
Q(z_partial_body__isnull=True) explicitamente — evita usar
z_partial_body__in=(None, None), que em SQL nunca retorna resultados
porque IN é uma cadeia de igualdades e NULL = NULL é UNKNOWN, não
True. Isso preserva o comportamento equivalente ao antigo
Q(z_partial_body=None), que o Django traduz para IS NULL.
- article_data_query: remove o acesso direto a z_partial_body e a
comparação Q(z_partial_body=z_partial_body); passa a compor o Q final
com '& self.partial_body_query', delegando a lógica de correspondência
do campo ao novo property.
Propósito: Tornar o diretório de testes reconhecido como pacote Python, permitindo que os módulos de teste (test_query_params, test_get_best_match, test_select_record, test_select_records, test_register) sejam descobertos e importados corretamente pelo test runner. Solução técnica: - Arquivo vazio, apenas com a função de marcar o diretório como pacote.
Propósito: Cobrir com testes unitários a lógica de construção de queries em QueryBuilderPidProviderXML, incluindo article_data_query, partial_body_query e get_article_data_query, garantindo que as correções recentes de ambiguidade (OR entre campos textuais, suporte a dois formatos de hash em z_partial_body e tratamento de valores nulos) se comportem conforme esperado. Solução técnica: - Casos de teste cobrindo cenários com e sem candidatos de hash disponíveis (z_partial_body legado, body_fragment_fingerprint atual, ambos ausentes). - Verificação de que get_article_data_query(issue=True/False) monta corretamente a combinação com issue_params e article_location_params ou com o filtro de campos de fascículo nulos.
Propósito: Cobrir com testes unitários a lógica de seleção do melhor candidato dentre múltiplos registros retornados pela query de correspondência, validando o cálculo de similaridade e o critério de desempate. Solução técnica: - Casos de teste com mocks simulando diferentes níveis de similaridade entre o XML de entrada e os candidatos registrados. - Verificação do comportamento em cenários de match exato, parcial e ausência de candidatos.
Propósito: Cobrir com testes unitários o fluxo de seleção de um único registro correspondente ao XML de entrada, incluindo a integração com o resultado de get_best_match. Solução técnica: - Casos de teste simulando diferentes conjuntos de resultados retornados pela query de seleção, verificando o registro escolhido em cada cenário.
Propósito: Cobrir com testes unitários o pipeline de seleção de múltiplos candidatos (por journal-issue-article e journal-article), validando a integração com QueryBuilderPidProviderXML.get_article_data_query nos dois escopos (issue=True/False). Solução técnica: - Casos de teste com mocks para os diferentes yields do gerador de seleção de registros, cobrindo cenários com e sem dados de fascículo.
Propósito: Cobrir com testes unitários o fluxo completo de registro de um XML, incluindo a seleção de registros existentes, atualização ou criação de PidProviderXML e a gravação dos campos derivados (z_surnames, z_collab, z_links, z_partial_body via body_fragment_fingerprint). Solução técnica: - Casos de teste simulando cenários de criação, atualização e registros já existentes, com mocks para as dependências externas (xml_adapter, xml_with_pre). - Verificação de que os campos textuais e o novo fingerprint de corpo são persistidos corretamente conforme a lógica revisada em models.py e query_params.py.
Propósito: Atualizar a versão fixada do packtools no requirements/base.txt. Solução técnica: Bump de packtools de 4.16.8 para 4.16.11 via egg git+https://github.com/scieloorg/packtools.git@4.16.11#egg=packtools, versão que corrige o bug de XMLWithPre.body_fragment_fingerprint (que retornava incorretamente o fingerprint do corpo INTEIRO em vez do fragmento), e cria XMLWithPre.body_fingerprint que retorna fingerprint do corpo INTEIRO, pré-requisito para a correção em query_params.py.
…l_body_query Propósito: Corrigir falso-positivo de match em QueryBuilderPidProviderXML.partial_body_query causado pelo bug do XMLWithPre.body_fragment_fingerprint, que retornava o fingerprint do corpo INTEIRO do artigo em vez do fragmento, permitindo colisão entre artigos diferentes com rótulos de seção genéricos (ex.: 'ARTIGO DE REVISÃO'). Solução técnica: Adiciona leitura de xml_with_pre.body_fingerprint (novo atributo, fingerprint do corpo INTEIRO) em self.z_body, mantendo self.z_body_fragment como fingerprint do FRAGMENTO (agora corrigido no packtools 4.16.11). partial_body_query passa a compor candidates com os três hashes disponíveis (z_partial_body legado, z_body_fragment, z_body), preservando fallback para z_partial_body__isnull=True quando nenhum estiver presente. Remove também variável morta other_pids em identifier_queries.
…t em partial_body_query Propósito: Validar a correção de partial_body_query após o fix do bug em XMLWithPre.body_fragment_fingerprint, garantindo que o fingerprint do corpo INTEIRO (body_fingerprint) e o do fragmento (body_fragment_fingerprint) sejam tratados como candidatos distintos e corretos, evitando regressão do falso-positivo original. Solução técnica: Adiciona parâmetro body_fingerprint em make_xml_adapter, setando explicitamente adapter.xml_with_pre.body_fingerprint (default None) para evitar Mock truthy espúrio nas asserções. Ajusta testes existentes para usar 'hash-fragmento-do-corpo' em vez de 'hash-corpo-inteiro' onde o valor representa body_fragment_fingerprint, e para comparar z_partial_body__in com set (não list), refletindo a implementação em produção. Adiciona novos testes cobrindo os três hashes combinados (test_combines_textual_fields_with_all_three_body_hashes) e a regressão direta do incidente (test_two_articles_with_same_legacy_hash_but_different_body_fingerprint_differ).
… nulo Propósito: Prevenir exceções ao renderizar objetos XMLVersion sem relacionamento ativo no admin do Wagtail. Solução técnica: Adiciona verificação defensiva de valor nulo no método __str__ do modelo XMLVersion.
…ions Propósito: Adequar a interface do Wagtail Admin à estrutura atual do modelo PidProviderXML. Solução técnica: Remove o campo collections dos painéis de edição panel_a e panel_b em pid_provider/models.py.
… skipped Propósito: Alinhar os status leves de auditoria com a convenção de nomenclatura oficial do sistema. Solução técnica: Atualiza o conjunto LIGHTWEIGHT_STATUSES para utilizar 'skipped' em vez de 'skip_update'.
…ara controle dinâmico de auditoria Propósito: Permitir o controle em tempo de execução da gravação de auditoria em fluxos limpos via interface web sem necessidade de alterar variáveis de ambiente ou reiniciar a aplicação. Solução técnica: Cria o modelo PidProviderSetting (BaseGenericSetting) com o campo record_all_registration_events, adiciona a migração 0018_pidprovidersetting.py e atualiza o método PidProviderXML.register para consultar a configuração dinamicamente.
Propósito: Corrigir o campo utilizado para pesquisa de versões de arquivos XML no Wagtail Admin. Solução técnica: Altera a propriedade search_fields em XMLVersionViewSet no arquivo pid_provider/wagtail_hooks.py de 'file__path' para 'file'.
…ting no registro Propósito: Garantir cobertura de testes automatizados para o comportamento da nova configuração dinâmica de auditoria. Solução técnica: Atualiza a documentação do módulo de testes e adiciona a classe RecordAllEventsSettingTest realizando o mock de PidProviderSetting.load().
Propósito: Evitar falhas esporádicas nos testes provocadas por variações de ordenação de chaves em dicionários. Solução técnica: Utiliza assertDictEqual sobre as representações em dicionário das estruturas de query em pid_provider/tests/test_query_params.py.
Propósito: Adequar os comandos e atalhos do Makefile às versões atuais e recomendadas do Docker CLI. Solução técnica: Substitui os comandos hifenizados docker-compose por docker compose em todos os alvos do Makefile.
Propósito: Criar a tabela no banco de dados para armazenar as configurações dinâmicas do PID Provider no Wagtail Admin. Solução técnica: Adiciona o arquivo de migração 0018_pidprovidersetting.py gerado pelo Django.
8 tasks
robertatakenaka
approved these changes
Aug 6, 2026
Closed
18 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
O que esse PR faz?
Este PR consolida o novo serviço de registro e auditoria de documentos XML no PID Provider. Ele combina a reestruturação vinda do PR #1449 (rebaseada sobre a
mainatualizada com o PR #1460) com novas melhorias, correções de robustez e configurações dinâmicas desenvolvidas nesta branch.🆕 Novas Implementações e Ajustes Criados Nesta Branch:
PidProviderSetting):PidProviderSetting(BaseGenericSetting) e da migração0018_pidprovidersetting.py.created,updated,skipped) em tempo de execução via interface web (/admin/settings/pid_provider/pidprovidersetting/), sem necessidade de variáveis de ambiente ou reinicialização.XMLVersion:XMLVersion.__str__quando o relacionamentopid_provider_xmlestiver ausente.collectionsdos painéis de ediçãopanel_aepanel_bnoPidProviderXML.search_fieldsemXMLVersionViewSetdefile__pathparafile.LIGHTWEIGHT_STATUSESemPidProviderXMLRegistrationpara utilizar o termo oficial'skipped'.RecordAllEventsSettingTestemtest_register.pypara validar o comportamento da configuração dinâmica.partial_body_queryemtest_query_params.pyutilizandoassertDictEqualpara evitar falhas por ordenação de chaves.Makefilepara a sintaxe moderna do Docker Compose v2 (docker compose).📦 Estrutura Herdada e Rebaseada do PR #1449:
PidProviderXML.register).PidProviderXMLRegistration.QueryBuilderPidProviderXMLe suporte a hashesbody_fingerprintebody_fragment_fingerprint.packtoolspara4.16.11.Onde a revisão poderia começar?
pid_provider/models.py:PidProviderSetting(configuração no Wagtail Admin).PidProviderXML.register(verificação dinâmica de auditoria).XMLVersion.__str__(tratamento defensivo contra nulo).pid_provider/wagtail_hooks.py:search_fieldsemXMLVersionViewSet.pid_provider/tests/test_register.py:RecordAllEventsSettingTest.Como este poderia ser testado manualmente?
Configuração Dinâmica no Wagtail Admin:
http://127.0.0.1:8009/admin/settings/pid_provider/pidprovidersetting/.Testes de Envio via Shell (
make django_shell):Verificação no Admin:
/admin/snippets/pid_provider/pidproviderxmlregistration/).Algum cenário de contexto que queira dar?
Esta branch substitui a proposta do PR #1449, rebaseando o trabalho sobre a
mainatualizada (que incorporou o PR #1460) e adicionando novas melhorias e correções identificadas durante os testes.Por padrão, a auditoria em
PidProviderXMLRegistrationgrava apenas erros e ambiguidades para otimizar o banco em produção. No entanto, adicionamos o modeloPidProviderSettingno Wagtail Admin para permitir ativar a auditoria completa dinamicamente via interface web sem necessitar de novas variáveis de ambiente ou reinicialização do servidor.Screenshots
Quais são os tickets relevantes?
pid_provider#1449Referências
wagtail.contrib.settings)Segurança da informação (NSI.04)
Este PR manipula dados sensíveis ou pessoais (LGPD)?
Este PR altera autenticação, autorização, controle de acesso ou gerenciamento de sessão?
Este PR introduz, atualiza ou remove dependências de terceiros?
packtoolsatualizado para 4.16.11)Este PR foi validado pelo pipeline de segurança (SonarQube / Trivy)?
Este PR concatena, monta ou executa comandos SQL, HTML ou JavaScript a partir de entrada externa?
Este PR expõe novos endpoints, telas ou serviços?
Algum segredo, senha, chave ou token está sendo adicionado ao código-fonte?