Files
TextNLPClassifierApp/docs/prd_deterministic_content_selection.md
T

15 KiB
Raw Blame History

PRD — Seleção determinística da biblioteca de extração de conteúdo

Versão: 1.0
Data: 20/08/2026
Status: Pronto para implementação

1. Contexto

Cada artigo é processado pelas bibliotecas Trafilatura, Newspaper4k e Readability a partir do mesmo HTML. O resultado é consolidado em um arquivo JSON que contém, para cada artigo, as saídas das três bibliotecas.

É necessário escolher deterministicamente uma única biblioteca por artigo. A escolha deve ocorrer mesmo quando todos os resultados forem ruins. O processamento não pode retornar estado ambíguo nem deixar um artigo sem seleção.

2. Objetivo

Receber um arquivo JSON no formato do arquivo de referência, selecionar a melhor saída de extração disponível para cada artigo e gerar um novo arquivo JSON com os mesmos dados, acrescentando somente a chave selected_extractor em cada item de articles.

3. Escopo

3.1 Incluído

  • Ler um arquivo JSON com uma coleção articles.
  • Comparar as saídas de Trafilatura, Newspaper4k e Readability de cada artigo.
  • Escolher obrigatoriamente uma das três bibliotecas.
  • Adicionar selected_extractor em cada artigo.
  • Gerar um novo arquivo JSON.
  • Preservar os dados e a ordem dos artigos recebidos.

3.2 Fora do escopo

  • Baixar ou renderizar páginas.
  • Verificar se as bibliotecas receberam o mesmo HTML.
  • Executar novamente as bibliotecas de extração.
  • Limpar, recortar, combinar ou reescrever o conteúdo extraído.
  • Gerar Markdown.
  • Usar LLM, embeddings ou regras específicas por domínio.
  • Alterar qualquer campo existente no JSON.
  • Adicionar métricas, justificativas ou outras chaves ao arquivo de saída.

4. Premissas

  • As três bibliotecas processaram exatamente o mesmo HTML.
  • A entrada segue a estrutura do JSON de referência.
  • A seleção é executada individualmente para cada artigo.
  • O algoritmo deve sempre produzir uma escolha, inclusive em situações sem concordância entre as bibliotecas.

5. Entrada

5.1 Arquivo

  • Formato: JSON válido.
  • A raiz deve conter articles como uma lista.
  • Cada item de articles representa um artigo.

5.2 Conteúdos comparados

Biblioteca Campo usado na comparação
Trafilatura trafilatura.text
Newspaper4k newspaper4k.text
Readability readability.cleaned_text

Os demais campos, incluindo título, descrição, resumo, palavras-chave, HTML estruturado e metadados, não participam da seleção.

5.3 Estado de um candidato

Para cada biblioteca, o candidato é classificado em um dos seguintes estados:

Estado Condição
Utilizável Campo de conteúdo é uma string não vazia após normalização e error é nulo
Degradado Campo de conteúdo é uma string não vazia após normalização, mas error não é nulo
Indisponível Campo ausente, nulo, de tipo diferente de string ou vazio após normalização

O campo de erro considerado é trafilatura.error, newspaper4k.error ou readability.error, conforme a biblioteca.

6. Saída

6.1 Arquivo

O arquivo de entrada nunca deve ser alterado. Deve ser criado um novo arquivo com o nome:

<nome_original_sem_extensão>_selected.json

Exemplo: river_plate_extracted(2).json gera river_plate_extracted(2)_selected.json.

6.2 Alteração permitida

Cada item de articles deve receber exatamente uma nova chave no mesmo nível de trafilatura, newspaper4k e readability:

selected_extractor

Valores permitidos:

  • trafilatura
  • newspaper4k
  • readability

Não são permitidos null, string vazia, ambiguous ou qualquer outro valor.

6.3 Preservação da entrada

  • Todas as chaves e valores existentes devem permanecer semanticamente idênticos.
  • A ordem dos itens de articles deve ser preservada.
  • Nenhum artigo pode ser adicionado ou removido.
  • Espaçamento, indentação e ordem textual das chaves do JSON não fazem parte do contrato, pois o arquivo pode ser serializado novamente.
  • Caso selected_extractor já exista, seu valor deve ser recalculado e substituído.

7. Algoritmo determinístico de seleção

7.1 Formar o conjunto ativo

Para cada artigo:

  1. Identificar os candidatos utilizáveis.
  2. Se existir pelo menos um utilizável, considerar somente os utilizáveis.
  3. Se não existir utilizável, considerar os candidatos degradados.
  4. Se não existir candidato utilizável nem degradado, selecionar newspaper4k pelo desempate final obrigatório.
  5. Se o conjunto ativo possuir somente um candidato, selecioná-lo imediatamente.

7.2 Normalizar os conteúdos

A normalização serve apenas para comparação e não modifica o JSON de saída.

Para cada candidato ativo:

  1. Decodificar entidades HTML.
  2. Remover marcação HTML e Markdown, preservando o texto visível.
  3. Em links, preservar o texto e remover o endereço.
  4. Aplicar normalização Unicode NFKC.
  5. Converter o texto para minúsculas.
  6. Substituir toda sequência de espaços, tabulações ou quebras de linha por um único espaço.
  7. Tokenizar mantendo letras e números Unicode.
  8. Desconsiderar pontuação.

Nenhuma palavra ou trecho pode ser removido por interpretação semântica.

7.3 Gerar shingles

  • Gerar a sequência ordenada de tokens de cada candidato.
  • Formar o conjunto de todas as janelas consecutivas de cinco tokens.
  • Quando o candidato possuir entre um e quatro tokens, usar a sequência completa como um único shingle.
  • Candidato sem token é indisponível e não participa do conjunto ativo.

7.4 Construir o consenso

O consenso é o conjunto de shingles presentes em pelo menos dois candidatos ativos.

Para cada candidato ativo, calcular:

Cobertura:

coverage = quantidade de shingles do consenso presentes no candidato / quantidade total de shingles do consenso

Suporte:

support = quantidade de shingles do candidato presentes no consenso / quantidade total de shingles do candidato

Pontuação:

score = 2 × coverage × support / (coverage + support)

Quando coverage + support for zero, a pontuação será zero.

7.5 Selecionar quando existe consenso

  1. Ordenar os candidatos por score, do maior para o menor.
  2. Identificar o maior score.
  3. Considerar empate técnico todo candidato cuja diferença para o maior score seja menor ou igual a 0,03.
  4. Se houver apenas um candidato no empate técnico, selecioná-lo.
  5. Se houver empate técnico, selecionar o candidato com a menor quantidade de shingles.
  6. Se a quantidade de shingles também empatar, aplicar a prioridade final:
    1. newspaper4k
    2. readability
    3. trafilatura

A preferência pelo menor candidato ocorre somente no empate técnico. Nesse cenário, os candidatos possuem qualidade de concordância equivalente, e a decisão favorece menor conteúdo excedente.

7.6 Selecionar quando não existe consenso

Quando nenhum shingle aparece em pelo menos dois candidatos ativos:

  • Com três candidatos ativos: selecionar o candidato com a quantidade mediana de shingles.
  • Com dois candidatos ativos: selecionar o candidato com a maior quantidade de shingles.
  • Com um candidato ativo: selecionar o único candidato.
  • Em empate de quantidade: aplicar a prioridade newspaper4k, readability, trafilatura.
  • Sem candidato ativo: selecionar newspaper4k.

Essas regras garantem uma escolha mesmo quando não existe concordância textual.

7.7 Gravar a escolha

Adicionar ou substituir selected_extractor no artigo com o identificador da biblioteca vencedora. Repetir o processo até que todos os itens de articles tenham sido processados.

8. Requisitos funcionais

ID Requisito
FR-001 O sistema deve aceitar um arquivo JSON como entrada.
FR-002 O sistema deve validar que a raiz é um objeto e que articles é uma lista.
FR-003 O sistema deve processar todos os artigos, preservando sua ordem.
FR-004 O sistema deve usar exclusivamente os três campos de conteúdo definidos no PRD para calcular a escolha.
FR-005 O sistema deve executar a normalização e a comparação conforme o algoritmo deste PRD.
FR-006 O sistema deve selecionar exatamente uma biblioteca por artigo.
FR-007 O sistema nunca deve produzir resultado ambíguo.
FR-008 O sistema deve adicionar somente selected_extractor em cada artigo.
FR-009 O valor de selected_extractor deve pertencer ao catálogo fechado de valores permitidos.
FR-010 O sistema deve preservar todas as chaves e valores recebidos.
FR-011 O sistema deve gerar um novo arquivo e manter o arquivo original inalterado.
FR-012 O sistema deve produzir a mesma seleção sempre que receber exatamente a mesma entrada.
FR-013 O sistema deve recalcular selected_extractor quando a chave já existir.

9. Tratamento de erros

Situação Comportamento obrigatório
JSON inválido Encerrar o processamento e não gerar arquivo de saída
Raiz diferente de objeto Encerrar o processamento e não gerar arquivo de saída
articles ausente ou diferente de lista Encerrar o processamento e não gerar arquivo de saída
articles vazio Gerar arquivo com lista vazia e sem outras alterações
Estrutura de uma biblioteca ausente Tratar seu candidato como indisponível
Campo de conteúdo com tipo inválido Tratar seu candidato como indisponível
Todas as bibliotecas indisponíveis Selecionar newspaper4k
Falha ao gravar o arquivo Não deixar arquivo de saída parcialmente gravado

Um erro em um artigo não pode impedir a seleção dos demais artigos, desde que o JSON e a lista articles sejam válidos.

10. Requisitos não funcionais

ID Requisito
NFR-001 O processamento deve ser totalmente determinístico.
NFR-002 O processamento não deve realizar chamadas de rede.
NFR-003 O processamento não deve depender de LLM, embeddings ou serviços externos.
NFR-004 O processamento deve operar somente sobre os dados do arquivo recebido.
NFR-005 A gravação do arquivo deve ser atômica: sucesso completo ou ausência do arquivo de saída.

11. Critérios de aceite

  1. Dado o JSON de referência com 20 artigos, o arquivo de saída contém os mesmos 20 artigos na mesma ordem.
  2. Cada artigo contém exatamente um selected_extractor válido.
  3. Nenhum artigo contém selected_extractor nulo, vazio ou ambíguo.
  4. Todas as chaves e valores anteriores permanecem semanticamente idênticos.
  5. Nenhuma chave adicional, além de selected_extractor, é criada.
  6. O arquivo original permanece inalterado.
  7. Duas execuções sobre o mesmo arquivo produzem os mesmos valores de selected_extractor.
  8. A seleção usa somente trafilatura.text, newspaper4k.text e readability.cleaned_text.
  9. Quando todas as saídas estiverem vazias ou indisponíveis, selected_extractor recebe newspaper4k.
  10. Quando não houver consenso, o desempate segue exatamente as regras da seção 7.6.
  11. Quando houver empate técnico, o desempate segue exatamente as regras da seção 7.5.
  12. Um JSON inválido ou sem articles válido não produz arquivo parcial.

12. Casos obrigatórios de teste

Caso Condição Resultado esperado
CT-001 Três candidatos com consenso e um vencedor claro Selecionar o maior score
CT-002 Dois ou mais candidatos dentro de 0,03 do maior score Selecionar o de menor quantidade de shingles
CT-003 Empate técnico e mesma quantidade de shingles Aplicar prioridade final
CT-004 Três candidatos sem consenso Selecionar a quantidade mediana de shingles
CT-005 Dois candidatos sem consenso Selecionar a maior quantidade de shingles
CT-006 Somente um candidato utilizável Selecionar esse candidato
CT-007 Nenhum utilizável, mas existe candidato degradado Executar o algoritmo somente com os degradados
CT-008 Todos os candidatos indisponíveis Selecionar newspaper4k
CT-009 Readability retorna apenas um fragmento pequeno enquanto os outros concordam O fragmento perde por baixa cobertura do consenso
CT-010 Um candidato contém o conteúdo comum e muito conteúdo excedente O candidato perde suporte e reduz sua pontuação
CT-011 Um candidato contém somente parte do conteúdo comum O candidato perde cobertura e reduz sua pontuação
CT-012 A entrada já contém selected_extractor Recalcular e substituir somente essa chave
CT-013 articles está vazio Gerar saída válida com articles vazio
CT-014 JSON inválido Não gerar saída

13. Definition of Done

  • Todos os requisitos funcionais foram implementados.
  • Todos os casos obrigatórios de teste foram automatizados e aprovados.
  • O JSON de referência com 20 artigos é processado integralmente.
  • A saída contém somente a inclusão de selected_extractor em cada artigo.
  • O arquivo original permanece inalterado.
  • Execuções repetidas sobre a mesma entrada produzem as mesmas escolhas.
  • Não existe caminho de execução que produza ambiguous, null ou artigo sem seleção.