Files
TextNLPClassifierApp/specs/007-media-article-routing/data-model.md
T

5.5 KiB

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

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

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 --> [*]