feat: DAG verify_news_integrity para verificação de integridade#104
feat: DAG verify_news_integrity para verificação de integridade#104nitaibezerra wants to merge 1 commit intomainfrom
Conversation
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
mauriciomendonca
left a comment
There was a problem hiding this comment.
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.
mauriciomendonca
left a comment
There was a problem hiding this comment.
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() | ||
| """) |
There was a problem hiding this comment.
🔴 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 || :featuresOnde :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: |
There was a problem hiding this comment.
🔴 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).
Summary
verify_news_integrity(a cada 30 min) que orquestra verificação de integridadejobs/integrity/priority.py: priorização SQL por idade (5 tiers, artigos recentes verificados mais frequentemente)jobs/integrity/results.py: upsert emnews_features.features.integrity+ sync campoimage_brokenno Typesenseimage_brokenadicionado ao schema TypesenseContexto
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
scraper_api_urleintegrity_batch_sizecomo Variables no Airflow