139 lines
5.5 KiB
Markdown
139 lines
5.5 KiB
Markdown
# Data Model & State Transitions: Classificação e Roteamento de Notícias de Mídia
|
|
|
|
**Feature**: `007-media-article-routing`
|
|
**Date**: 2026-08-24
|
|
**Status**: Completed
|
|
|
|
---
|
|
|
|
## 1. Diagrama Entidade-Relacionamento e Fluxo de Dados
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
Input[InputArticle JSON] --> Crawler[Foxcape Crawler]
|
|
Crawler --> LoadedPage[DOM Carregada: html, title, status]
|
|
LoadedPage --> Gate[DOM Media Gate: detecção estrutural]
|
|
|
|
Gate -- "Sem mídia candidata relevante" --> TextPipeline[Multimotor Textual: Trafilatura + Newspaper4k + Readability]
|
|
|
|
Gate -- "Com mídia candidata relevante" --> CompactBuilder[Montagem de Payload Compacto]
|
|
CompactBuilder --> Classifier[Cadeia Sequencial LLM: Ollama -> Groq -> OmniRoute]
|
|
|
|
Classifier -- "content_type = text" --> TextPipeline
|
|
TextPipeline --> ExtractedRecord[ExtractedArticle: Sucesso Textual]
|
|
ExtractedRecord --> MainReport[ExtractionBatchReport -> *_extracted.json]
|
|
|
|
Classifier -- "content_type = media" --> MediaRecord[MediaArticle: Registro de Mídia]
|
|
MediaRecord --> MediaEnvelope[MediaOutputFile -> *_media.json]
|
|
|
|
Classifier -- "Falha total dos 3 provedores" --> FailureRecord[Registro de Falha de Classificação]
|
|
FailureRecord --> MainReport
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Modelos de Dados (Dataclasses e Estruturas de Runtime)
|
|
|
|
### 2.1 `InputArticle`
|
|
Representa a notícia original carregada do JSON de entrada.
|
|
- `titulo: str` (Obrigatório)
|
|
- `url: str` (Obrigatório)
|
|
- `subtitulo: str | None` (Opcional, preservado conforme entrada)
|
|
- `quando_publicado: str | None` (Opcional, preservado conforme entrada)
|
|
- `pagina: int | None` (Opcional, preservado conforme entrada)
|
|
- Campos adicionais da entrada são preservados no dicionário `input_meta`.
|
|
|
|
### 2.2 `MediaCandidateInfo` (Estrutura Interna do Gate)
|
|
Resultado da análise estrutural da DOM:
|
|
- `has_candidate_media: bool`
|
|
- `has_video: bool`
|
|
- `image_count: int`
|
|
- `has_embed: bool`
|
|
|
|
### 2.3 `MediaClassification` (Estrutura Interna de Saída do LLM)
|
|
Resultado estrito retornado pelo classificador LLM:
|
|
- `content_type: Literal["text", "media"]`
|
|
- `media_type: Literal["video", "image", "images", "embed", "mixed"] | None`
|
|
- Regra semântica: `content_type == "text"` $\iff$ `media_type is None`.
|
|
|
|
### 2.4 `MediaArticle` (Persistência em `*_media.json`)
|
|
Registro consolidado de publicação classificada como mídia:
|
|
- `input_meta: dict[str, Any]` (Preserva todos os metadados recebidos da entrada)
|
|
- `crawled_url: str`
|
|
- `page_title: str | None`
|
|
- `http_status: int | None`
|
|
- `content_type: Literal["media"]`
|
|
- `media_type: Literal["video", "image", "images", "embed", "mixed"]`
|
|
|
|
### 2.5 `ExtractedArticle` (Persistência Textual em `*_extracted.json`)
|
|
Representa exclusivamente artigos textuais que efetivamente passaram pelo multimotor (`extract_all_engines`):
|
|
- `input_meta: InputArticle`
|
|
- `extraction_status: Literal["success", "failed"]`
|
|
- `error_message: str | None`
|
|
- `crawled_url: str`
|
|
- `page_title: str | None`
|
|
- `http_status: int | None`
|
|
- `trafilatura: TrafilaturaData | None`
|
|
- `newspaper4k: NewspaperData | None`
|
|
- `readability: ReadabilityData | None`
|
|
|
|
### 2.6 Registro de Falha de Classificação (Persistência Inline em `*_extracted.json`)
|
|
Para artigos que sofram falha operacional dos três provedores LLM, o registro é persistido inline no array `articles` do JSON principal sem ter executado os extratores textuais:
|
|
- `input_meta: dict[str, Any]`
|
|
- `crawled_url: str`
|
|
- `page_title: str | None`
|
|
- `http_status: int | None`
|
|
- `classification_status: Literal["failed"]`
|
|
- `error_message: str`
|
|
- `trafilatura: None`
|
|
- `newspaper4k: None`
|
|
- `readability: None`
|
|
|
|
### 2.7 `ExtractionBatchReport`
|
|
Envelope consolidado de saída gravado no arquivo principal (`*_extracted.json`):
|
|
- `source_file: str`
|
|
- `processed_at: str` (ISO 8601 UTC)
|
|
- `total_articles: int` (Total de registros presentes no array `articles` do JSON principal)
|
|
- `successful_articles: int` (Total de artigos textuais processados com sucesso)
|
|
- `failed_articles: int` (Total de registros de falha presentes, incluindo erros de crawl e de classificação)
|
|
- `articles: list[dict[str, Any] | ExtractedArticle]`
|
|
|
|
### 2.8 `MediaOutputFile`
|
|
Envelope mínimo gravado no arquivo de mídia (`*_media.json`):
|
|
- `articles: list[MediaArticle]`
|
|
|
|
---
|
|
|
|
## 3. Máquina de Estados do Processamento de Artigo
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> Crawling
|
|
Crawling --> CrawlFailed: Erro HTTP / Timeout Foxcape
|
|
CrawlFailed --> MainJSONRecord: Registra falha de crawl
|
|
|
|
Crawling --> StructuralInspection: DOM carregada com sucesso
|
|
StructuralInspection --> TextExtraction: Sem mídia candidata relevante
|
|
StructuralInspection --> LLMClassification: Mídia candidata relevante detectada
|
|
|
|
state LLMClassification {
|
|
[*] --> TryOllama
|
|
TryOllama --> ValidOutput: Resposta com schema válido
|
|
TryOllama --> TryGroq: Falha operacional Ollama
|
|
TryGroq --> ValidOutput: Resposta com schema válido
|
|
TryGroq --> TryOmniRoute: Falha operacional Groq
|
|
TryOmniRoute --> ValidOutput: Resposta com schema válido
|
|
TryOmniRoute --> AllProvidersFailed: Falha operacional OmniRoute
|
|
}
|
|
|
|
ValidOutput --> TextExtraction: content_type == text
|
|
ValidOutput --> MediaRouting: content_type == media
|
|
AllProvidersFailed --> MainJSONRecord: Registra classification_status = failed
|
|
|
|
TextExtraction --> MainJSONRecord: Executa Trafilatura + Newspaper + Readability
|
|
MediaRouting --> MediaJSONRecord: Grava em *_media.json
|
|
|
|
MainJSONRecord --> [*]
|
|
MediaJSONRecord --> [*]
|
|
```
|