1240 lines
22 KiB
Markdown
1240 lines
22 KiB
Markdown
# 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
|
|
text
|
|
```
|
|
|
|
de:
|
|
|
|
```text
|
|
media
|
|
```
|
|
|
|
Quando o conteúdo for classificado como `media`, também deve ser identificado como:
|
|
|
|
```text
|
|
video
|
|
image
|
|
images
|
|
embed
|
|
mixed
|
|
```
|
|
|
|
O objetivo desta feature é exclusivamente:
|
|
|
|
1. identificar publicações predominantemente de mídia;
|
|
2. evitar processamento desnecessário pelo multimotor textual;
|
|
3. 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:
|
|
|
|
1. Production-ready.
|
|
2. Mínimo de código necessário para atender integralmente aos requisitos.
|
|
3. Sem overengineering.
|
|
4. Sem alteração desnecessária do crawler atual.
|
|
5. Sem alteração desnecessária dos motores existentes.
|
|
6. Sem novos serviços.
|
|
7. Sem filas.
|
|
8. Sem agentes.
|
|
9. Sem LangGraph.
|
|
10. Sem votação entre modelos.
|
|
11. Sem pipelines adicionais de NLP.
|
|
12. Sem análise visual da mídia.
|
|
13. Sem download de mídia.
|
|
14. Sem regex.
|
|
15. Independente de idioma.
|
|
16. Compatível com os até 10 idiomas processados pelo sistema.
|
|
17. Fallback de LLM exclusivamente por falha operacional.
|
|
|
|
---
|
|
|
|
# 5. Escopo funcional
|
|
|
|
## 5.1 Fluxo principal
|
|
|
|
O novo fluxo deve ser:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
Título
|
|
|
|
"Mira el increíble gol marcado durante el partido."
|
|
|
|
[VÍDEO]
|
|
```
|
|
|
|
Resultado:
|
|
|
|
```json
|
|
{
|
|
"content_type": "media",
|
|
"media_type": "video"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
Exemplo:
|
|
|
|
```text
|
|
Título
|
|
|
|
"Estas fueron las mejores imágenes de la celebración."
|
|
|
|
[IMAGEM]
|
|
[IMAGEM]
|
|
[IMAGEM]
|
|
[IMAGEM]
|
|
```
|
|
|
|
Resultado:
|
|
|
|
```json
|
|
{
|
|
"content_type": "media",
|
|
"media_type": "images"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
Exemplo:
|
|
|
|
```text
|
|
Título
|
|
|
|
"El jugador publicó el siguiente mensaje."
|
|
|
|
[INSTAGRAM EMBED]
|
|
```
|
|
|
|
Resultado:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"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.
|
|
|
|
```json
|
|
{
|
|
"content_type": "media",
|
|
"media_type": "video"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 9.2 `image`
|
|
|
|
Conteúdo cujo elemento informativo principal é uma única imagem.
|
|
|
|
```json
|
|
{
|
|
"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.
|
|
|
|
```json
|
|
{
|
|
"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.
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```text
|
|
texto curto
|
|
|
|
[VÍDEO]
|
|
|
|
[IMAGEM]
|
|
[IMAGEM]
|
|
```
|
|
|
|
Resultado:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{
|
|
"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
|
|
→ text
|
|
→ pipeline atual
|
|
```
|
|
|
|
Se existir mídia candidata:
|
|
|
|
```text
|
|
→ 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
|
|
text
|
|
```
|
|
|
|
ou:
|
|
|
|
```text
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"content_type": "text",
|
|
"media_type": null
|
|
}
|
|
```
|
|
|
|
ou:
|
|
|
|
```json
|
|
{
|
|
"content_type": "media",
|
|
"media_type": "video"
|
|
}
|
|
```
|
|
|
|
Schema conceitual:
|
|
|
|
```text
|
|
content_type:
|
|
text | media
|
|
|
|
media_type:
|
|
null | video | image | images | embed | mixed
|
|
```
|
|
|
|
Regras:
|
|
|
|
```text
|
|
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á:
|
|
|
|
```text
|
|
Qwen3.5 2B
|
|
```
|
|
|
|
executado localmente através de:
|
|
|
|
```text
|
|
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 é:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
Ollama
|
|
↓ falha
|
|
Groq
|
|
↓ falha
|
|
OmniRoute
|
|
↓ falha
|
|
```
|
|
|
|
o sistema não deve assumir:
|
|
|
|
```text
|
|
text
|
|
```
|
|
|
|
nem:
|
|
|
|
```text
|
|
media
|
|
```
|
|
|
|
O artigo deve receber:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{
|
|
"content_type": "media",
|
|
"media_type": "..."
|
|
}
|
|
```
|
|
|
|
o artigo:
|
|
|
|
1. não executa Trafilatura;
|
|
2. não executa Newspaper4k;
|
|
3. não executa Readability;
|
|
4. é removido do fluxo textual;
|
|
5. é registrado no JSON separado de mídia.
|
|
|
|
---
|
|
|
|
# 20. Arquivo de mídia
|
|
|
|
Para uma entrada:
|
|
|
|
```text
|
|
river_plate.json
|
|
```
|
|
|
|
o pipeline textual continua utilizando:
|
|
|
|
```text
|
|
river_plate_extracted.json
|
|
```
|
|
|
|
e os conteúdos de mídia devem ser separados em:
|
|
|
|
```text
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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
|
|
text
|
|
media
|
|
```
|
|
|
|
## FR-007
|
|
|
|
`media_type` deve aceitar apenas:
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
texto jornalístico
|
|
nenhuma mídia
|
|
```
|
|
|
|
Esperado:
|
|
|
|
```text
|
|
multimotor executado
|
|
```
|
|
|
|
---
|
|
|
|
## Cenário B — texto curto + vídeo
|
|
|
|
Esperado:
|
|
|
|
```text
|
|
media/video
|
|
multimotor não executado
|
|
```
|
|
|
|
---
|
|
|
|
## Cenário C — texto curto + imagem
|
|
|
|
Esperado:
|
|
|
|
```text
|
|
media/image
|
|
```
|
|
|
|
---
|
|
|
|
## Cenário D — texto curto + múltiplas imagens
|
|
|
|
Esperado:
|
|
|
|
```text
|
|
media/images
|
|
```
|
|
|
|
---
|
|
|
|
## Cenário E — texto curto + embed
|
|
|
|
Esperado:
|
|
|
|
```text
|
|
media/embed
|
|
```
|
|
|
|
---
|
|
|
|
## Cenário F — texto curto + vídeo + imagens
|
|
|
|
Esperado:
|
|
|
|
```text
|
|
media/mixed
|
|
```
|
|
|
|
---
|
|
|
|
## Cenário G — artigo textual longo + imagem
|
|
|
|
Esperado:
|
|
|
|
```text
|
|
text
|
|
multimotor executado
|
|
```
|
|
|
|
---
|
|
|
|
## Cenário H — artigo textual longo + vídeo
|
|
|
|
Esperado:
|
|
|
|
```text
|
|
text
|
|
multimotor executado
|
|
```
|
|
|
|
---
|
|
|
|
## Cenário I — Ollama indisponível
|
|
|
|
Esperado:
|
|
|
|
```text
|
|
Groq utilizado
|
|
```
|
|
|
|
---
|
|
|
|
## Cenário J — Ollama e Groq indisponíveis
|
|
|
|
Esperado:
|
|
|
|
```text
|
|
OmniRoute utilizado
|
|
```
|
|
|
|
---
|
|
|
|
## Cenário K — todos indisponíveis
|
|
|
|
Esperado:
|
|
|
|
```text
|
|
classification_status = failed
|
|
nenhum multimotor
|
|
nenhum registro em media.json
|
|
lote continua
|
|
```
|
|
|
|
---
|
|
|
|
## Cenário L — resposta fora do schema
|
|
|
|
Esperado:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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;
|
|
* [ ] `video` funciona;
|
|
* [ ] `image` funciona;
|
|
* [ ] `images` funciona;
|
|
* [ ] `embed` funciona;
|
|
* [ ] `mixed` funciona;
|
|
* [ ] 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.
|