Divide task dispatch articles em duas task dispatch articles e task harvest articles - #1460
Conversation
0830753 to
ac10d72
Compare
#### Propósito Deixar explícito o significado de cada status de PidProviderXML e oferecer agrupamentos reutilizáveis para os filtros do pipeline de artigos. #### Solução técnica - Adicionado comentário explicativo a cada constante PPXML_STATUS_*. - Criadas as listas PPXML_STATUS_TO_CREATE_OR_UPDATE_ARTICLE_SOURCE e PPXML_STATUS_TO_IGNORE, consumidas por ArticleIteratorBuilder.from_article_source em article/controller.py.
…ovider #### Propósito Remover a dependência de ArticleSource no modelo legado AMArticle e tornar o fluxo de obtenção de XML/PID mais direto, com menos logging redundante e menos chamadas save() implícitas. #### Solução técnica - Substituído o FK am_article por pid (CharField) e collection (FK para Collection) em ArticleSource. - create/create_or_update reescritos para usar pid/collection/detail; extraído o método update() que aplica mudanças incrementais e informa se algo mudou. - add_pid_provider reestruturado com try/except mais enxuto: retorna bool `changed`, delega erros a mark_as_xml_error/mark_as_url_error/ mark_as_error e evita salvar o objeto múltiplas vezes. - request_xml e request_pid simplificados; erros de PID agora levantam UnableToRegisterPIDError diretamente, sem lista `detail` acumulada manualmente. - Adicionados ArticleSource.get_pid_provider_xml_id() e a propriedade ContribPerson.data. - Import ajustado para `from pid_provider import choices as pid_provider_choices` e removidos logging.info/logging.exception de baixo valor ao longo do arquivo.
…eSource #### Propósito Persistir no banco de dados as alterações do modelo ArticleSource: remoção do FK am_article e inclusão dos campos pid e collection. #### Solução técnica Migração gerada automaticamente (makemigrations) refletindo as mudanças já aplicadas em article/models.py: RemoveField(am_article), AddField(pid), AddField(collection) com FK para Collection.
…ource #### Propósito Refletir no admin/Wagtail a remoção do FK am_article e a introdução dos campos pid/collection em ArticleSource. #### Solução técnica Substituído am_article por url/pid/collection em list_display, list_filter e search_fields de ArticleSourceSnippetViewSet.
…ryset vazio #### Propósito Uma consulta sem resultados não é necessariamente um erro; registrar UnexpectedEvent para esse caso gerava ruído desnecessário. #### Solução técnica - Removida a criação de UnexpectedEvent.create quando o queryset filtrado vem vazio em Journal.select_items. - Journal.get_ids ajustado para encadear select_items().values_list() diretamente, sem variável intermediária.
…radores
#### Propósito
O antigo __iter__ disparava todos os iteradores simultaneamente, permitindo
que o mesmo artigo/pp_xml_id fosse selecionado por mais de uma fonte na
mesma execução. É preciso que quem chama escolha explicitamente uma fonte.
#### Solução técnica
- Removido __iter__; __init__ mantém apenas os filtros comuns a mais de
uma fonte (usuário, coleção/periódico, datas/anos, force_update,
parâmetros de harvest).
- Métodos privados _iter_from_* convertidos em métodos públicos from_*,
cada um recebendo como parâmetro o filtro exclusivo da sua fonte
(proc_status_list, data_status_list, article_source_status_list).
- from_pid_provider reescrito para resolver ISSNs via SciELOJournal e
retornar apenas dicts prontos via values(pp_xml_id=F("id")).distinct(),
sem instanciar os objetos completos.
- from_article_source e from_harvest movidos para o builder (antes viviam
em ArticleSource/Collection), com a mesma otimização via values().
- Removidos os contadores internos (_iter_from_*_count) e logging.info
intermediário.
- export_article_to_articlemeta passa a levantar ValueError quando não há
legacy_keys, em vez de registrar UnexpectedEvent e retornar silenciosamente.
- Corrigido bug: uso de pub_year trocado por pub_date_year em
bulk_export_articles_to_articlemeta.
- Removido bloco de código comentado (get_pp_xml_ids/select_pp_xml).
#### Propósito Cobrir com testes o novo comportamento de ArticleIteratorBuilder após a remoção do __iter__ único e a introdução dos métodos from_pid_provider, from_article, from_article_source e from_harvest, garantindo que cada fonte seja testada isoladamente e não haja sobreposição de seleção. #### Solução técnica Suite baseada em unittest com mocks (unittest.mock) para isolar consultas ao banco/Journal/SciELOJournal/PidProviderXML/ArticleSource, cobrindo: - filtros aplicados por from_pid_provider (ISSNs, datas, status); - comportamento de from_article com/sem pp_xml existente; - filtros de status em from_article_source, incluindo o caso force_update; - montagem de kwargs em from_harvest via harvester mockado. Supressão de ruído de logging do OpenSearch configurada no setUp para manter a saída de testes limpa.
…orBuilder #### Propósito Separar o fluxo de harvest (coleta externa via OPAC/ArticleMeta) do fluxo de despacho das fontes internas pendentes (article_source, pid_provider, article), e ajustar as tasks ao novo builder com métodos from_* explícitos. #### Solução técnica - Nova task task_harvest_articles, isolando builder.from_harvest(). - task_dispatch_articles reescrita para iterar sequencialmente from_article_source, from_pid_provider e from_article; parâmetros de harvest (limit/timeout/opac_url/stop) removidos dessa task, pois agora pertencem só a task_harvest_articles. - task_process_article_pipeline ajustada: não depende mais de AMArticle.create_or_update; ArticleSource.create_or_update passa a receber collection/pid/detail; pp_xml_id obtido via article_source.get_pid_provider_xml_id(). - Removido parâmetro `item` indevido em UnexpectedEvent.create de task_export_article_to_articlemeta e diversos logging.info de baixo valor.
…correlatas #### Propósito Validar os três fluxos de entrada do pipeline (xml_url, article_source_id, pp_xml_id) após a reescrita de task_process_article_pipeline, e cobrir task_harvest_articles e a nova task_dispatch_articles sequencial. #### Solução técnica Testes com unittest.mock isolando ArticleSource.create_or_update, PidProviderXML.get_by_id, Article.get_or_create e as chamadas task_export_article_to_articlemeta.delay/task_process_article_pipeline.delay, verificando: - levantamento de ValueError quando faltam collection_acron/pid com xml_url; - obtenção correta de pp_xml_id via article_source.get_pid_provider_xml_id(); - que export_to_articlemeta só dispara quando article.is_classic_public e article.valid são verdadeiros; - que task_dispatch_articles percorre article_source, pid_provider e article sem duplicar despachos; - registro de UnexpectedEvent em caso de exceção nas tasks. Logging do OpenSearch suprimido no setUp para reduzir ruído nos testes.
…ticles #### Propósito Registrar o agendamento da nova task_harvest_articles e ajustar task_dispatch_articles ao novo conjunto de parâmetros, além de limpar tasks obsoletas do scheduler. #### Solução técnica - Adicionada schedule_task_harvest_articles (cron diário às 02:01). - schedule_task_dispatch_articles ajustada para os novos kwargs (article_source_status_list/proc_status_list/data_status_list, sem limit/timeout/opac_url) e horário movido para 02:16. - Incluídas na lista de tasks a remover: task_dispatch_articles antiga e as variantes por fonte (task_dispatch_articles_from_pid_provider, _from_article, _from_article_source), substituídas pelas duas tasks especializadas.
…or url
#### Propósito
1. Refletir no painel Wagtail de ArticleSource a remoção do FK am_article,
exibindo collection e pid em seu lugar.
2. Simplificar ArticleAvailability.get, já que url é único (unique=True)
e por si só já identifica o registro, tornando o parâmetro article
redundante.
#### Solução técnica
- panels de ArticleSource: substituído FieldPanel("am_article", read_only=True)
por FieldPanel("collection", read_only=True) e FieldPanel("pid", read_only=True).
- ArticleAvailability.get(cls, url) agora recebe apenas url; create() e
create_or_update() ajustados para chamar cls.get(url) sem o argumento
article, aproveitando o índice único já existente em url.
ac10d72 to
97b2303
Compare
There was a problem hiding this comment.
Atualizar documentação com as exceções possíveis no lugar de "False".
| kwargs=dict( | ||
| username=username, | ||
| user_id=None, | ||
| collection_acron_list=None, | ||
| from_date=None, | ||
| until_date=None, | ||
| force_update=False, | ||
| export_to_articlemeta=False, | ||
| auto_solve_pid_conflict=False, | ||
| limit=None, | ||
| timeout=None, | ||
| opac_url=None, | ||
| ), |
There was a problem hiding this comment.
Aqui não deveria ter todos os parâmetros de task_harvest_article?
| exc_type, exc_value, exc_traceback = sys.exc_info() | ||
| UnexpectedEvent.create( | ||
| action="task_harvest_articles", | ||
| item=item, |
There was a problem hiding this comment.
❌ esta referencia à variável item dá erro, pois a declaração dela está dentro do try
patymori
left a comment
There was a problem hiding this comment.
Execução de task_harvest_articles manualmente com coleção pequena
✔️ documentos coletados disparam task_process_article_pipeline
Execução de task_dispatch_articles
kwargs para execução:
{"username": "scielo", "user_id": null, "collection_acron_list": ["cic"], "journal_acron_list": null, "from_pub_year": "2026", "until_pub_year": "2026", "from_date": null, "until_date": null, "force_update": false, "export_to_articlemeta": false, "auto_solve_pid_conflict": false, "article_source_status_list": ["completed"], "proc_status_list": ["DONE"], "data_status_list": ["completed"]}
❌ A seguinte exceção ocorreu no log do conteiner:
INFO 2026-07-31 16:06:28,025 strategy 11 138039802255168 Task article.tasks.task_dispatch_articles[a4834b9d-8761-4c48-923c-c20a2dc50a13] received
ERROR 2026-07-31 16:06:28,082 models 63 138039802255168 Cannot resolve keyword 'pub_date_year' into field. Choices are: aop_pid, article, article_pub_year, articlesource, available_since, collections, created, creator, creator_id, current_version, current_version_id, elocation_id, events, fixpidv2, fpage, fpage_seq, id, issn_electronic, issn_print, lpage, main_doi, number, origin_date, other_pid, other_pid_count, pkg_name, proc_status, pub_year, registered_in_core, suppl, updated, updated_by, updated_by_id, v2, v3, volume, xmlversion, z_collab, z_links, z_partial_body, z_surnames
Traceback (most recent call last):
File "/app/article/tasks.py", line 842, in task_dispatch_articles
for item_kwargs in item_iterator:
File "/app/article/controller.py", line 426, in from_pid_provider
PidProviderXML.objects.filter(q, **filters)
File "/usr/local/lib/python3.11/site-packages/django/db/models/manager.py", line 87, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/db/models/query.py", line 1493, in filter
return self._filter_or_exclude(False, args, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/db/models/query.py", line 1511, in _filter_or_exclude
clone._filter_or_exclude_inplace(negate, args, kwargs)
File "/usr/local/lib/python3.11/site-packages/django/db/models/query.py", line 1518, in _filter_or_exclude_inplace
self._query.add_q(Q(*args, **kwargs))
File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/query.py", line 1646, in add_q
clause, _ = self._add_q(q_object, can_reuse)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/query.py", line 1678, in _add_q
child_clause, needed_inner = self.build_filter(
^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/query.py", line 1526, in build_filter
lookups, parts, reffed_expression = self.solve_lookup_type(arg, summarize)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/query.py", line 1333, in solve_lookup_type
_, field, _, lookup_parts = self.names_to_path(lookup_splitted, self.get_meta())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/query.py", line 1806, in names_to_path
raise FieldError(
django.core.exceptions.FieldError: Cannot resolve keyword 'pub_date_year' into field. Choices are: aop_pid, article, article_pub_year, articlesource, available_since, collections, created, creator, creator_id, current_version, current_version_id, elocation_id, events, fixpidv2, fpage, fpage_seq, id, issn_electronic, issn_print, lpage, main_doi, number, origin_date, other_pid, other_pid_count, pkg_name, proc_status, pub_year, registered_in_core, suppl, updated, updated_by, updated_by_id, v2, v3, volume, xmlversion, z_collab, z_links, z_partial_body, z_surnames
INFO 2026-07-31 16:06:28,140 base 63 138039750842112 POST http://opensearch:9200/core-logs-dev-2026.07.31/_doc [status:201 request:0.041s]
#### Propósito Evitar perda de dados na migração que desacopla ArticleSource de AMArticle, preenchendo os novos campos pid e collection antes da remoção do relacionamento legado. #### Solução técnica - Reordena as operações para adicionar pid e collection antes de remover am_article. - Adiciona uma operação RunPython que copia pid e collection do AMArticle associado. - Usa PidProviderXML.v2 como fallback de pid e sua única collection como fallback de collection quando os dados legados não estão disponíveis. - Processa o backfill em lotes de 1000 registros com bulk_update. - Adiciona a dependência da migração pid_provider.0012_pidproviderxml_collections. - Mantém a operação reversa como noop, pois o relacionamento am_article não existe mais no estado final da migração.
#### Propósito Corrigir falhas na seleção de artigos causadas pelo uso de campos de ano incompatíveis com cada modelo e por um relacionamento inexistente na consulta de ArticleSource. #### Solução técnica - Usa pub_year nos filtros aplicados a PidProviderXML. - Usa pub_date_year nos filtros aplicados a Article. - Remove o select_related de pid_provider da consulta de ArticleSource, pois esse relacionamento não existe no modelo. - Atualiza os testes do ArticleIteratorBuilder para refletir os campos e a cadeia de consulta corretos.
#### Propósito Evitar que falhas do pipeline sejam ocultadas ou substituídas por erros secundários e melhorar o contexto registrado nos eventos inesperados. #### Solução técnica - Inicializa item e params antes da obtenção do usuário nas tasks de coleta e distribuição, preservando a exceção original em caso de falha. - Inclui filtros de ano, processamento, dados e ArticleSource no contexto registrado por task_dispatch_articles. - Preenche action, item e detail no evento criado quando a exportação de um artigo para o ArticleMeta falha. - Relança as exceções de task_process_article_pipeline depois de registrá-las, permitindo que o Celery sinalize corretamente a falha. - Documenta os limites do tratamento de erros da exportação em lote. - Atualiza os testes para validar o registro e a propagação das exceções.
#### Propósito Manter a configuração da tarefa periódica alinhada à assinatura de task_harvest_articles e permitir o uso de todos os controles disponíveis na coleta. #### Solução técnica - Adiciona journal_acron_list aos argumentos configurados no agendador. - Adiciona stop para permitir limitar o ponto de interrupção do fluxo. - Mantém ambos com valor padrão nulo, preservando o comportamento atual quando não forem informados.
#### Propósito Evitar perda de registros legados e corrigir estados inconsistentes identificados durante a execução completa do dispatch de artigos. #### Solução técnica - Remove de BaseLegacyRecord.get a exclusão automática de registros sem url e data, preservando AMArticles gerados pelo pipeline. - Faz ArticleSource retornar de NOT_PUBLIC para PENDING quando a fonte volta a ser identificada como pública. - Chama mark_as_available quando o artigo já possui disponibilidades, garantindo a atualização de seu estado para PUBLIC. - Adiciona testes de regressão para a preservação de AMArticle e para as transições de estado de ArticleSource e Article.
|
Continuei a validação da PR #1460 executando o pipeline de artigos de ponta a ponta, desde a coleta até a atualização de disponibilidade. Além de atender aos comentários da revisão, essa execução revelou algumas regressões que não apareciam nos testes isolados. Contexto e relação com outras PRs A PR #1460 continua a consolidação do pipeline iniciada pela PR #1402, separando agora a coleta externa ( Ela também precisa preservar comportamentos introduzidos pela PR #1453, especialmente filtro por periódico, Alterações acrescentadas nesta continuação
A migração original removia
A análise em homologação encontrou 7.385
Isso cobre também o
Foram atendidos os três apontamentos formais:
O contexto registrado em
O teste integrado revelou três problemas adicionais:
Foram adicionados testes de regressão para os três casos. Validação Foi executada uma coleta limitada a três documentos. Depois de carregar o fascículo ausente, o dispatch concluiu o fluxo, preservou os três Os testes direcionados do iterator, das tasks, da exportação e dos modelos passaram localmente, incluindo o teste com banco para garantir que As alterações foram separadas em cinco commits:
Estado atual do CI Os checks do GitGuardian e Snyk passaram. O job |
#### Propósito Restaurar a execução dos testes no GitHub Actions, cujo runner não disponibiliza mais o comando legado docker-compose. #### Solução técnica Substitui as chamadas ao executável Docker Compose v1 pelo comando docker compose, fornecido pelo plugin Compose v2 disponível no runner.
#### Propósito Permitir que o usuário django do container inicialize as configurações da aplicação durante as migrações e os testes no GitHub Actions. #### Solução técnica Cria o diretório logs no checkout com UID e GID 1000, correspondentes ao usuário do container, antes de montar o repositório em /app.







O que esse PR faz?
Refatora o pipeline de seleção e processamento de artigos:
ArticleIteratorBuilder(article/controller.py), removendo o__iter__único e substituindo por métodos explícitosfrom_pid_provider,from_article,from_article_sourceefrom_harvest, cada um recebendoo filtro exclusivo da sua fonte.
ArticleSourcedo modelo legadoAMArticle: remove o FKam_articlee adiciona os campospidecollection(article/models.pyadd_pid_provider/request_xml/request_pid, reduzindologging redundante e centralizando o tratamento de erro.
task_dispatch_articlesem duas tasks:task_harvest_articles(coleta externa via OPAC/ArticleMeta) e
task_dispatch_articles(despacho sequencial de article_source → pid_provider → article).
article/wagtail_hooks.pyaos novos campos deArticleSource.bigbang/tasks_scheduler.pypara agendar as novas tasks eremover as obsoletas.
Journal.select_items(journal/models.py), removendo oregistro de
UnexpectedEventpara queryset vazio.PidProviderXMLempid_provider/choices.py.test_article_iterator_builder.pyetest_article_pipeline.py, com mocks (unittest.mock) e supressão delogging do OpenSearch.
Onde a revisão poderia começar?
Sugiro começar por
article/controller.py(oArticleIteratorBuilderreescrito), depois
article/models.py(mudanças emArticleSource), epor fim
article/tasks.pypara ver como as novas tasks consomem o builder.Os testes em
article/tests/ajudam a entender o comportamento esperadode cada método.
Como este poderia ser testado manualmente?
0050_remove_articlesource_am_article_and_moreemambiente de homologação e conferir que os dados existentes de
ArticleSourcenão foram perdidos (verificarpid/collectionpreenchidos onde aplicável).
task_harvest_articlesmanualmente com uma coleção pequena econfirmar que os documentos coletados disparam
task_process_article_pipelinecorretamente.task_dispatch_articlese verificar nos logs/Flower que cadaartigo é despachado uma única vez, mesmo quando elegível por mais de
uma fonte (article_source, pid_provider, article).
make django_bashpython manage.py test article.tests --keepdbArticleSourceSnippetViewSet) que ascolunas e filtros exibem
pid/collectionno lugar deam_article.Algum cenário de contexto que queira dar?
Esta é uma refatoração estrutural sem mudança de escopo funcional externo
esperado — o objetivo é eliminar duplicidade de processamento e reduzir
acoplamento com
AMArticle. A migração remove um FK, então é importantevalidar em homologação antes de subir para produção. Nenhuma nova
dependência externa foi introduzida.
Screenshots
N/A (mudanças de backend/pipeline, sem impacto visual além do admin
listado acima).
Quais são os tickets relevantes?
vinculada acima).
Referências
Segurança da informação (NSI.04)
Seção obrigatória, referência NSI.04 - Norma de Desenvolvimento Seguro.
Manipula dados sensíveis/pessoais (LGPD)?
[ ] Sim [x] Não
N/A — os campos alterados (
pid,collection) são identificadoresbibliográficos/institucionais, não dados pessoais.
Altera autenticação, autorização, controle de acesso ou sessão?
[ ] Sim [x] Não
Introduz/atualiza/remove dependências de terceiros?
[ ] Sim [x] Não
Nenhuma dependência de terceiros foi adicionada, atualizada ou removida.
Validado pelo pipeline de segurança (SonarQube/Trivy)?
[ ] Sim [ ] Não — justificar
Link do job: (preencher com o link do pipeline após execução)
Concatena/monta/executa comandos SQL, HTML ou JS a partir de entrada externa?
[ ] Sim [x] Não
Todas as consultas usam o ORM do Django (filtros via
Q/Fe kwargs),sem SQL bruto ou interpolação de entrada externa.
Expõe novos endpoints, telas ou serviços?
[ ] Sim [x] Não
Não há novos endpoints; apenas ajuste de colunas/filtros no admin
Wagtail existente (
ArticleSourceSnippetViewSet).Algum segredo/senha/chave/token adicionado ao código-fonte?
[ ] Sim [x] Não