22 KiB
PRD — Classificação e Roteamento de Notícias Predominantemente de Mídia
1. Visão geral
O sistema atual processa URLs de notícias, obtém a página completamente carregada pelo crawler e encaminha o HTML para múltiplos motores de extração textual.
Esse comportamento funciona para notícias cujo conteúdo principal é textual.
Entretanto, alguns veículos publicam conteúdos jornalísticos cujo conteúdo principal é uma mídia, por exemplo:
- vídeo;
- uma única imagem;
- múltiplas imagens;
- conteúdo incorporado;
- combinação de mais de um tipo de mídia.
Nesses casos, o texto da publicação é curto e funciona essencialmente como uma introdução, descrição ou contextualização da mídia.
Essas publicações não devem passar pelo pipeline multimotor textual.
O sistema deve identificá-las imediatamente após o crawl e roteá-las para um arquivo JSON separado.
2. Objetivo
Adicionar ao pipeline atual uma etapa pequena de classificação capaz de distinguir:
text
de:
media
Quando o conteúdo for classificado como media, também deve ser identificado como:
video
image
images
embed
mixed
O objetivo desta feature é exclusivamente:
- identificar publicações predominantemente de mídia;
- evitar processamento desnecessário pelo multimotor textual;
- separar fisicamente essas publicações em um JSON próprio.
A feature não deve extrair, interpretar, baixar ou processar a mídia.
3. Motivação
O multimotor atual foi criado para extração de conteúdo textual.
Executar Trafilatura, Newspaper4k e Readability em uma página cujo conteúdo real é um vídeo, uma imagem ou uma coleção de imagens:
- não agrega valor;
- consome processamento desnecessariamente;
- pode produzir conteúdo textual irrelevante ou insuficiente;
- mistura publicações de natureza diferente no mesmo pipeline.
A separação deve acontecer antes do multimotor.
4. Princípios obrigatórios
A implementação deve seguir estes princípios:
- Production-ready.
- Mínimo de código necessário para atender integralmente aos requisitos.
- Sem overengineering.
- Sem alteração desnecessária do crawler atual.
- Sem alteração desnecessária dos motores existentes.
- Sem novos serviços.
- Sem filas.
- Sem agentes.
- Sem LangGraph.
- Sem votação entre modelos.
- Sem pipelines adicionais de NLP.
- Sem análise visual da mídia.
- Sem download de mídia.
- Sem regex.
- Independente de idioma.
- Compatível com os até 10 idiomas processados pelo sistema.
- Fallback de LLM exclusivamente por falha operacional.
5. Escopo funcional
5.1 Fluxo principal
O novo fluxo deve ser:
URL
↓
Crawler atual
↓
Página completamente carregada
↓
Detecção estrutural de presença de mídia
↓
├── nenhuma mídia candidata
│ ↓
│ pipeline textual atual
│
└── existe mídia candidata
↓
classificador de conteúdo
↓
├── text
│ ↓
│ pipeline textual atual
│
└── media
↓
*_media.json
O classificador deve executar antes de qualquer chamada a:
- Trafilatura;
- Newspaper4k;
- Readability.
6. Definição de conteúdo predominantemente de mídia
Uma publicação deve ser classificada como media quando:
O conteúdo informativo principal da publicação está em uma mídia e o texto existente funciona essencialmente como introdução, legenda, contextualização ou breve descrição dessa mídia.
Exemplos:
Título
"Mira el increíble gol marcado durante el partido."
[VÍDEO]
Resultado:
{
"content_type": "media",
"media_type": "video"
}
Exemplo:
Título
"Estas fueron las mejores imágenes de la celebración."
[IMAGEM]
[IMAGEM]
[IMAGEM]
[IMAGEM]
Resultado:
{
"content_type": "media",
"media_type": "images"
}
Exemplo:
Título
"El jugador publicó el siguiente mensaje."
[INSTAGRAM EMBED]
Resultado:
{
"content_type": "media",
"media_type": "embed"
}
7. Conteúdo textual normal
A presença de mídia não transforma automaticamente uma notícia em media.
Exemplo:
Título
Parágrafo 1
Parágrafo 2
Parágrafo 3
Parágrafo 4
Parágrafo 5
Parágrafo 6
[VÍDEO]
Parágrafo 7
Parágrafo 8
Resultado:
{
"content_type": "text"
}
Essa publicação deve continuar normalmente pelo multimotor.
8. Texto curto
A referência de negócio é um texto equivalente aproximadamente a três a cinco linhas de conteúdo editorial.
Essa referência NÃO deve ser implementada utilizando quantidade literal de linhas renderizadas.
Quantidade de linhas depende de:
- viewport;
- resolução;
- fonte;
- CSS;
- dispositivo;
- responsividade.
O modelo deve avaliar semanticamente se o texto possui conteúdo jornalístico substancial por si próprio ou se apenas introduz/descreve a mídia.
Não deve existir heurística linguística baseada em idioma.
9. Tipos de mídia
9.1 video
Conteúdo cujo elemento informativo principal é vídeo.
{
"content_type": "media",
"media_type": "video"
}
9.2 image
Conteúdo cujo elemento informativo principal é uma única imagem.
{
"content_type": "media",
"media_type": "image"
}
Não é necessário identificar se a imagem representa:
- fotografia;
- infográfico;
- gráfico;
- ilustração;
- charge;
- qualquer outra categoria visual.
9.3 images
Conteúdo cujo elemento informativo principal é composto por múltiplas imagens.
Pode ser:
- carousel;
- galeria;
- slideshow;
- imagens apresentadas sequencialmente;
- imagens simplesmente exibidas uma abaixo da outra.
O sistema não precisa identificar qual mecanismo visual o site utiliza.
{
"content_type": "media",
"media_type": "images"
}
9.4 embed
Conteúdo incorporado cuja informação principal esteja em um elemento externo incorporado à página.
Exemplos possíveis:
- Instagram;
- TikTok;
- X;
- outros embeds.
{
"content_type": "media",
"media_type": "embed"
}
9.5 mixed
Conteúdo predominantemente de mídia que possui mais de um tipo relevante de mídia.
Exemplo:
texto curto
[VÍDEO]
[IMAGEM]
[IMAGEM]
Resultado:
{
"content_type": "media",
"media_type": "mixed"
}
10. Notícia curta com uma imagem
Uma publicação com texto muito curto e uma única imagem pode ser classificada como:
{
"content_type": "media",
"media_type": "image"
}
Mesmo que tecnicamente pudesse ser considerada uma notícia textual curta.
Esse comportamento é intencional.
Conteúdo textual insuficiente não é útil para o pipeline textual atual.
11. Detecção estrutural prévia
Antes de chamar qualquer LLM, o sistema deve verificar estruturalmente se existe mídia candidata na DOM carregada.
Essa análise:
- deve utilizar parser de DOM;
- não pode utilizar regex;
- não deve interpretar semanticamente o conteúdo;
- não deve identificar palavras-chave;
- não deve depender do idioma.
Elementos estruturais relevantes podem incluir elementos HTML representando:
- imagens;
- vídeo;
- múltiplas imagens;
- iframe;
- embed;
- object;
- estruturas equivalentes já presentes na DOM carregada.
O objetivo dessa etapa é apenas evitar uma chamada ao classificador quando não existe mídia candidata.
Se não existir mídia candidata:
→ text
→ pipeline atual
Se existir mídia candidata:
→ executar classificação LLM
12. Entrada do classificador
O classificador não deve receber mídia binária.
Não deve:
- baixar imagens;
- baixar vídeos;
- executar OCR;
- executar visão computacional;
- assistir vídeo;
- transcrever áudio;
- navegar dentro do carousel;
- chamar APIs dos providers da mídia.
O classificador deve utilizar somente informações já disponíveis na página carregada.
A entrada deve ser compacta e conter apenas os dados necessários para decidir:
text
ou:
media
e, no segundo caso, o media_type.
A entrada pode conter:
- título;
- blocos textuais relevantes presentes na DOM;
- informação estrutural sobre mídia presente;
- quantidade/tipos estruturais de mídia encontrados.
Não deve ser enviado o HTML completo caso os mesmos dados possam ser representados de forma menor.
13. Saída do classificador
A saída deve utilizar structured output.
Contrato:
{
"content_type": "text",
"media_type": null
}
ou:
{
"content_type": "media",
"media_type": "video"
}
Schema conceitual:
content_type:
text | media
media_type:
null | video | image | images | embed | mixed
Regras:
content_type = text
→ media_type obrigatoriamente null
content_type = media
→ media_type obrigatoriamente diferente de null
Não adicionar:
- confidence;
- rationale;
- summary;
- keywords;
- descrição gerada;
- explicação do modelo.
Esses campos não são necessários para a feature.
14. Modelo primário
O classificador primário será:
Qwen3.5 2B
executado localmente através de:
Ollama
O modelo deve utilizar configuração determinística compatível com o provider e structured output.
Não utilizar reasoning desnecessário.
15. Fallback de providers
A ordem obrigatória é:
1. Qwen3.5 2B / Ollama
2. GPT-OSS 20B / Groq
3. cgpt-web/gpt-5.5 / OmniRoute
O fallback é estritamente sequencial.
16. Condição para fallback
Fallback somente ocorre quando o provider atual falha operacionalmente.
Exemplos:
- conexão indisponível;
- timeout;
- erro HTTP;
- provider indisponível;
- resposta impossível de consumir;
- resposta incompatível com o schema obrigatório.
Não executar fallback porque:
- a classificação foi
text; - a classificação foi
media; - o resultado parece estranho;
- o modelo parece inseguro;
- outro modelo poderia discordar.
Não existe votação ou comparação entre providers.
17. Falha de todos os providers
Se:
Ollama
↓ falha
Groq
↓ falha
OmniRoute
↓ falha
o sistema não deve assumir:
text
nem:
media
O artigo deve receber:
{
"classification_status": "failed",
"error_message": "media classification providers unavailable"
}
A mensagem concreta pode preservar o padrão de erro existente, desde que:
- indique falha de classificação;
- não exponha API keys;
- não exponha tokens;
- não exponha credenciais.
O artigo:
- não passa pelo multimotor;
- não é enviado ao JSON de mídia;
- permanece registrado no JSON principal de processamento como falha.
18. Roteamento de conteúdo textual
Se o resultado for:
{
"content_type": "text",
"media_type": null
}
o pipeline continua exatamente pelo fluxo textual atual.
A feature não deve modificar a lógica interna de:
- Trafilatura;
- Newspaper4k;
- Readability;
- seleção posterior dos resultados desses motores.
19. Roteamento de conteúdo de mídia
Se o resultado for:
{
"content_type": "media",
"media_type": "..."
}
o artigo:
- não executa Trafilatura;
- não executa Newspaper4k;
- não executa Readability;
- é removido do fluxo textual;
- é registrado no JSON separado de mídia.
20. Arquivo de mídia
Para uma entrada:
river_plate.json
o pipeline textual continua utilizando:
river_plate_extracted.json
e os conteúdos de mídia devem ser separados em:
river_plate_media.json
O arquivo deve ser JSON.
Não utilizar XML.
21. Dados mínimos no *_media.json
Cada item deve preservar os dados básicos necessários para identificação do conteúdo e rastreabilidade do crawl.
Estrutura mínima:
{
"input_meta": {
"titulo": "...",
"subtitulo": "...",
"quando_publicado": "...",
"url": "...",
"pagina": 1
},
"crawled_url": "...",
"page_title": "...",
"http_status": 200,
"content_type": "media",
"media_type": "video"
}
Campos opcionais existentes no input continuam opcionais.
Não adicionar dados de mídia extraídos.
22. O que NÃO deve estar no arquivo de mídia
O sistema não deve adicionar:
image_urls
video_urls
embed_urls
thumbnails
duration
captions
transcripts
OCR
alt-text gerado
descrição da imagem
provider da mídia
metadata da mídia
summary
keywords
sentiment
A extração da mídia pertence a outro sistema.
23. Compatibilidade com o JSON textual atual
O fluxo textual existente deve permanecer compatível com o contrato atual.
A nova feature não exige mudanças nos objetos resultantes de artigos textuais processados com sucesso.
Falhas de classificação devem reutilizar o arquivo principal de processamento e registrar explicitamente:
classification_status = failed
sem criar um terceiro arquivo físico exclusivo para falhas.
24. Requisitos funcionais
FR-001
O sistema deve analisar a DOM carregada antes de executar o multimotor textual.
FR-002
A análise estrutural não pode usar regex.
FR-003
Quando nenhuma mídia candidata estiver presente, o artigo deve seguir diretamente para o pipeline textual existente.
FR-004
Quando existir mídia candidata, o sistema deve executar o classificador.
FR-005
O classificador deve retornar somente content_type e media_type.
FR-006
content_type deve aceitar apenas:
text
media
FR-007
media_type deve aceitar apenas:
video
image
images
embed
mixed
ou null quando content_type = text.
FR-008
Artigo classificado como text deve seguir pelo pipeline multimotor atual.
FR-009
Artigo classificado como media não pode executar o multimotor.
FR-010
Artigo classificado como media deve ser persistido no *_media.json.
FR-011
O sistema não deve extrair mídia.
FR-012
O sistema não deve navegar em carousel ou galeria.
FR-013
O sistema não deve analisar arquivos de imagem, áudio ou vídeo.
FR-014
O sistema deve utilizar Qwen3.5 2B/Ollama como classificador primário.
FR-015
Falha operacional do Ollama deve acionar GPT-OSS 20B/Groq.
FR-016
Falha operacional do Groq deve acionar cgpt-web/gpt-5.5/OmniRoute.
FR-017
Fallback não pode ocorrer com base no conteúdo da classificação.
FR-018
Falha dos três providers deve gerar classification_status = failed.
FR-019
Falha dos três providers não pode gerar classificação presumida.
FR-020
Falha dos três providers deve permanecer registrada no JSON principal.
FR-021
O classificador deve funcionar independentemente do idioma do artigo.
25. Requisitos não funcionais
NFR-001 — Simplicidade
Implementar a feature com o menor número razoável de componentes.
NFR-002 — Isolamento
A nova lógica deve ficar isolada do funcionamento interno dos extratores existentes.
NFR-003 — Determinismo de contrato
Toda resposta de modelo deve ser validada contra schema conhecido.
NFR-004 — Configuração externa
Endpoints, credenciais e configuração dos providers devem ser externos ao código.
Segredos não podem ser hardcoded.
NFR-005 — Segurança
Logs e erros não podem registrar:
- API keys;
- tokens;
- credenciais.
NFR-006 — Compatibilidade
A feature não deve quebrar o formato atual dos artigos textuais processados com sucesso.
NFR-007 — Multilíngue
Nenhuma lógica de decisão pode depender de listas de palavras em idiomas específicos.
NFR-008 — Zero regex
A nova implementação de detecção e classificação de mídia não pode utilizar bibliotecas ou APIs de expressão regular.
NFR-009 — Observabilidade mínima
Devem existir logs suficientes para identificar:
- provider utilizado;
- fallback Ollama → Groq;
- fallback Groq → OmniRoute;
- classificação final;
- falha total da cadeia.
Sem registrar payload textual integral por padrão.
NFR-010 — Falha isolada
Falha na classificação de um artigo não pode abortar o processamento dos demais artigos do lote.
26. Caminho feliz — notícia textual
1. Receber URL.
2. Crawler carrega página completamente.
3. Sistema analisa estruturalmente a DOM.
4. Nenhuma mídia candidata é encontrada
OU o classificador determina que existe texto jornalístico substancial.
5. Artigo segue para extract_all_engines().
6. Pipeline atual continua sem alteração.
7. Resultado permanece no *_extracted.json.
27. Caminho feliz — notícia de mídia
1. Receber URL.
2. Crawler carrega página completamente.
3. Sistema encontra mídia candidata na DOM.
4. Qwen3.5 2B recebe representação compacta do conteúdo.
5. Modelo retorna:
content_type = media
media_type = images
6. Schema é validado.
7. Multimotor NÃO é executado.
8. Dados básicos do artigo são registrados no *_media.json.
9. Próximo artigo é processado.
28. Caminho de fallback
1. Existe mídia candidata.
2. Ollama é chamado.
3. Ollama apresenta falha operacional.
4. Groq é chamado.
5. Groq apresenta falha operacional.
6. OmniRoute é chamado.
7. OmniRoute responde com classificação válida.
8. Resultado é utilizado normalmente.
Não existe qualquer diferença funcional entre uma classificação válida retornada pelo provider primário ou por um fallback.
29. Caminho de falha total
1. Existe mídia candidata.
2. Ollama falha.
3. Groq falha.
4. OmniRoute falha.
5. classification_status = failed.
6. Artigo NÃO passa pelo multimotor.
7. Artigo NÃO entra no *_media.json.
8. Falha é registrada no JSON principal.
9. Lote continua.
30. Cenários mínimos de teste
Cenário A — texto sem mídia
texto jornalístico
nenhuma mídia
Esperado:
multimotor executado
Cenário B — texto curto + vídeo
Esperado:
media/video
multimotor não executado
Cenário C — texto curto + imagem
Esperado:
media/image
Cenário D — texto curto + múltiplas imagens
Esperado:
media/images
Cenário E — texto curto + embed
Esperado:
media/embed
Cenário F — texto curto + vídeo + imagens
Esperado:
media/mixed
Cenário G — artigo textual longo + imagem
Esperado:
text
multimotor executado
Cenário H — artigo textual longo + vídeo
Esperado:
text
multimotor executado
Cenário I — Ollama indisponível
Esperado:
Groq utilizado
Cenário J — Ollama e Groq indisponíveis
Esperado:
OmniRoute utilizado
Cenário K — todos indisponíveis
Esperado:
classification_status = failed
nenhum multimotor
nenhum registro em media.json
lote continua
Cenário L — resposta fora do schema
Esperado:
provider considerado incapaz de fornecer classificação válida
seguir cadeia de fallback
31. Testes multilíngues
O conjunto de testes/eval deve conter casos representativos em todos os idiomas efetivamente suportados pelo pipeline.
O objetivo é validar especialmente:
texto curto descritivo + mídia → media
texto jornalístico substancial + mídia → text
Não criar regras específicas por idioma para corrigir o modelo.
32. Métricas operacionais
Devem ser observáveis, no mínimo:
total de artigos avaliados
total roteado para text
total roteado para media
total media/video
total media/image
total media/images
total media/embed
total media/mixed
uso do fallback Groq
uso do fallback OmniRoute
falhas totais de classificação
Essas métricas podem utilizar o mecanismo de logging/relatório já existente.
A feature não justifica introduzir uma nova plataforma de observabilidade.
33. Fora de escopo
Explicitamente fora desta feature:
- extração das imagens;
- extração dos vídeos;
- download das mídias;
- OCR;
- visão computacional;
- transcrição de vídeo;
- transcrição de áudio;
- identificação de objetos nas imagens;
- identificação de infográfico;
- geração de descrição;
- geração de resumo;
- NLP adicional;
- interação com carousel;
- chamadas às APIs das plataformas incorporadas;
- classificação editorial da notícia;
- verificação de fatos;
- mudança no crawler;
- mudança nos três motores atuais;
- mudança no pipeline posterior de conteúdo textual;
- armazenamento em banco;
- filas;
- workers novos;
- agentes;
- LangChain;
- LangGraph;
- votação de modelos;
- scoring complexo;
- classificação baseada em regex;
- classificação baseada em palavras-chave por idioma.
34. Definition of Done
A feature está concluída quando:
- classificação acontece antes do multimotor;
- nenhuma regex é utilizada na nova lógica;
- artigos sem mídia seguem normalmente;
- artigos textuais com mídia continuam podendo ser classificados como
text; - artigos predominantemente de mídia são identificados;
videofunciona;imagefunciona;imagesfunciona;embedfunciona;mixedfunciona;- artigos de mídia não executam os três motores;
*_media.jsoné produzido corretamente;- dados básicos do crawl são preservados;
- nenhuma mídia é extraída;
- Qwen3.5 2B/Ollama funciona como provider primário;
- Groq funciona como primeiro fallback operacional;
- OmniRoute funciona como segundo fallback operacional;
- nenhum fallback ocorre com base em confiança ou discordância;
- falha dos três providers é registrada como
classification_failed; - falha de um artigo não interrompe o lote;
- contrato estruturado do modelo é validado;
- segredos não aparecem em logs;
- testes cobrem os idiomas suportados;
- comportamento atual do multimotor permanece compatível;
- não foram adicionados componentes fora do escopo.