Skip to content

feat: DAG verify_news_integrity para verificação de integridade#104

Open
nitaibezerra wants to merge 1 commit intomainfrom
feature/verify-news-integrity
Open

feat: DAG verify_news_integrity para verificação de integridade#104
nitaibezerra wants to merge 1 commit intomainfrom
feature/verify-news-integrity

Conversation

@nitaibezerra
Copy link
Contributor

Summary

  • Nova DAG verify_news_integrity (a cada 30 min) que orquestra verificação de integridade
  • jobs/integrity/priority.py: priorização SQL por idade (5 tiers, artigos recentes verificados mais frequentemente)
  • jobs/integrity/results.py: upsert em news_features.features.integrity + sync campo image_broken no Typesense
  • Campo image_broken adicionado ao schema Typesense

Contexto

Notícias editadas nos sites gov.br podem ter imagens removidas/alteradas, quebrando o portal. A DAG busca batch priorizado no PostgreSQL, delega verificação HTTP para o Scraper Cloud Run (POST /verify/integrity), persiste resultados e sincroniza status com Typesense.

Depende de: destaquesgovbr/scraper PR (endpoint /verify/integrity)

Ref: #68

Test plan

  • 17 testes unitários passando (priority, results)
  • Validar parse da DAG no Airflow
  • Configurar scraper_api_url e integrity_batch_size como Variables no Airflow
  • Testar execução manual da DAG no Composer

Nova DAG que verifica periodicamente imagens e conteúdo das notícias:
- jobs/integrity/priority.py: priorização por idade do artigo (5 tiers)
- jobs/integrity/results.py: upsert em news_features + sync Typesense
- DAG orquestra chamando POST /verify/integrity no Scraper Cloud Run
- Adiciona campo image_broken ao schema Typesense

Ref: #68
@nitaibezerra nitaibezerra enabled auto-merge (squash) March 5, 2026 18:00
@nitaibezerra nitaibezerra requested review from ODenteAzul and removed request for LucaasMa March 5, 2026 18:06
Copy link
Contributor

@mauriciomendonca mauriciomendonca left a comment

Choose a reason for hiding this comment

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

Revisão — 2 issues bloqueantes

Dois bugs de correção em results.py que devem ser resolvidos antes do merge. Ambos afetam a integridade dos dados persistidos.

Detalhes nos comentários inline abaixo.

Copy link
Contributor

@mauriciomendonca mauriciomendonca left a comment

Choose a reason for hiding this comment

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

Revisão — 2 issues bloqueantes em results.py

Dois bugs de correção que devem ser resolvidos antes do merge. Ambos afetam a integridade dos dados persistidos. Detalhes nos comentários inline.

ON CONFLICT (unique_id) DO UPDATE
SET features = news_features.features || :features,
updated_at = NOW()
""")
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 BUG: UPSERT JSONB shallow merge apaga campos de conteúdo

O operador || no top-level substitui a chave integrity inteira:

SET features = news_features.features || :features

Onde :features é {"integrity": {...}}. Como 75% dos artigos têm check_content=False, os campos content_status, content_hash, content_checked_at e source_etag não entram no dict integrity (linhas 44-55) e serão perdidos a cada run de verificação de imagem.

Impacto em produção: Artigos que passaram por check_content=True terão seus dados de conteúdo silenciosamente apagados na próxima run de imagem.

Fix sugerido — usar jsonb_set com merge no sub-objeto:

INSERT INTO news_features (unique_id, features)
VALUES (:uid, jsonb_build_object('integrity', :integrity_fields))
ON CONFLICT (unique_id) DO UPDATE
SET features = jsonb_set(
    COALESCE(news_features.features, '{}'),
    '{integrity}',
    COALESCE(news_features.features -> 'integrity', '{}') || :integrity_fields
),
    updated_at = NOW()

E passar :integrity_fields como o sub-objeto integrity diretamente (sem o wrapper {"integrity": ...}).

f"Upsert de integridade: {count} artigos, "
f"{len(broken_ids)} quebrados, {len(fixed_ids)} restaurados"
)
finally:
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 BUG: Race condition — fixed_ids é sempre vazio

O upsert na linha 68 já atualizou image_status para "ok" no banco. Quando _was_previously_broken é chamado aqui, ela lê o estado pós-update — que já é "ok", não "broken". Resultado: retorna sempre False, e fixed_ids fica permanentemente vazio.

Impacto em produção: O Typesense nunca recebe image_broken: False para artigos cuja imagem foi restaurada. Uma vez marcado como broken, o artigo fica broken para sempre no portal.

Fix sugerido — buscar o status anterior antes do upsert:

for r in results:
    uid = r.get("unique_id")
    if not uid:
        continue

    # Ler estado ANTES do upsert
    previous_status = _get_previous_image_status(conn, uid)

    # ... montar integrity e executar upsert ...

    if r.get("image_status") == "broken":
        broken_ids.append(uid)
    elif r.get("image_status") == "ok" and previous_status == "broken":
        fixed_ids.append(uid)

Isso também elimina a função _was_previously_broken e pode ser combinado com o pré-carregamento em batch para resolver o N+1 (carregar check_count + image_status de todos os UIDs numa única query antes do loop).

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