6.8 KiB
Research & Architectural Decisions: Deterministic Content Selection
Branch: 004-deterministic-content-selection | Date: 2026-08-20 | Spec: spec.md
1. Text Normalization Pipeline
Context
Cada extrator (Trafilatura, Newspaper4k e Readability) gera textos com diferentes resíduos de formatação (entidades HTML como & ou , links no formato markdown [texto](url) ou tags <a href="...">texto</a>, variações de quebras de linha e pontuações). A comparação textual para consenso exige uma normalização uniforme, determinística e de alta performance.
Decisions
- Decodificação de entidades HTML: Utilizar
html.unescape()da biblioteca padrão do Python. - Remoção de imagens Markdown: Expressão regular
re.compile(r'!\s*\[[^\]]*\]\([^)]*\)')substituindo imagens Markdown por espaços para descartar marcação de mídia não-textual e evitar falsos consensos com legendas. - Preservação de texto de links Markdown: Expressão regular
re.compile(r'\[([^\]]+)\]\([^)]+\)')substituindo links Markdown pelo texto âncora\1. - Remoção de tags HTML: Expressão regular
re.compile(r'<[^>]+>')substituindo tags por espaços para evitar fusão acidental de palavras vizinhas. - Normalização Unicode:
unicodedata.normalize('NFKC', text)para uniformizar caracteres compostos, ligaduras e variantes tipográficas. - Conversão para minúsculas:
.lower()após NFKC. - Colapso de espaços em branco:
re.sub(r'\s+', ' ', text).strip(). - Tokenização: Extração de sequências alfanuméricas com
re.findall(r'[\w]+', text, flags=re.UNICODE). Pontuações são descartadas naturalmente sem remoção semântica de palavras.
Rationale
- 100% implementável com módulos padrão do Python (
re,unicodedata,html), garantindo portabilidade em qualquer ambiente sem novas dependências externas. - Complexidade linear
O(N)no tamanho do texto, com execução em frações de milissegundo por artigo.
Alternatives Considered
BeautifulSouppara strip de tags: Rejeitado por ser mais lento e desnecessário para textos já extraídos.nltkouspacy: Rejeitados por adicionarem dependências pesadas, download de modelos e lentidão desnecessária para uma tarefa de tokenização alfanumérica pura.
2. 5-Token Shingles & Consensus Metrics
Context
O algoritmo compara a sobreposição textual entre os candidatos ativos através de janelas deslizantes consecutivas de 5 tokens (shingles).
Decisions
- Geração de Shingles:
- Para um candidato com
Ttokens ordenados[t_0, t_1, \dots, t_{T-1}]:- Se
T \ge 5: conjunto de tuplas de 5 tokens\{ (t_i, t_{i+1}, t_{i+2}, t_{i+3}, t_{i+4}) \mid 0 \le i \le T-5 \}. - Se
1 \le T \le 4: conjunto contendo uma única tupla com todos os tokens\{ (t_0, \dots, t_{T-1}) \}. - Se
T = 0: conjunto vazio\emptyset.
- Se
- Para um candidato com
- Construção do Consenso:
- Para cada shingle único observado nos candidatos ativos, conta-se em quantos candidatos distintos ele aparece.
\text{Consenso} = \{ s \mid \text{contagem}(s) \ge 2 \}.
- Métricas por Candidato Ativo $C$:
\text{cobertura}(C) = \frac{|C_{\text{shingles}} \cap \text{Consenso}|}{|\text{Consenso}|}\text{suporte}(C) = \frac{|C_{\text{shingles}} \cap \text{Consenso}|}{|C_{\text{shingles}}|}\text{score}(C) = \frac{2 \times \text{cobertura}(C) \times \text{suporte}(C)}{\text{cobertura}(C) + \text{suporte}(C)}(se denominador for zero,\text{score} = 0.0).
Rationale
- A métrica de pontuação
F_1penaliza tanto extratores que perderam conteúdo essencial (baixa cobertura) quanto extratores que trouxeram excesso de lixo/boilerplate do site (baixo suporte). - A representação por
setde tuplas em Python permite operações de intersecção (&) com complexidade ótima de tempoO(|C|).
3. Regras de Decisão, Empate Técnico e Desempate Hierárquico
Context
O sistema precisa garantir uma escolha única e determinística em todas as variações possíveis de entrada.
Decisions
- Formação do Conjunto Ativo:
- Classificação:
Usável:texté string não vazia após normalização eerroréNone/vazio.Degradado:texté string não vazia após normalização, maserrornão éNone.Indisponível:texté nulo, ausente, não-string ou vazio.
- Se houver
\ge 1Usável\toAtivos = Usáveis. - Senão, se houver
\ge 1Degradado\toAtivos = Degradados. - Senão
\toSelecionanewspaper4kdiretamente (Fallback Final). - Se
|\text{Ativos}| = 1 \toSeleciona o único candidato ativo imediatamente.
- Classificação:
- Seleção Com Consenso (
|\text{Consenso}| > 0):- Maior score
S_{\max} = \max_{C \in \text{Ativos}} \text{score}(C). - Grupo de empate técnico:
\{ C \in \text{Ativos} \mid S_{\max} - \text{score}(C) \le 0.03 + 10^{-9} \}. - Se grupo tiver 1 candidato
\toSeleciona ele. - Se grupo tiver
\ge 2candidatos\toSeleciona o candidato com menor|C_{\text{shingles}}|(menor conteúdo excedente). - Se ainda houver empate no número de shingles
\toDesempate por prioridade fixa:newspaper4k>readability>trafilatura.
- Maior score
- Seleção Sem Consenso (
|\text{Consenso}| = 0):- Se
|\text{Ativos}| = 3 \toSeleciona candidato com quantidade mediana de shingles. - Se
|\text{Ativos}| = 2 \toSeleciona candidato com maior quantidade de shingles. - Se
|\text{Ativos}| = 1 \toSeleciona o único candidato. - Empates na quantidade de shingles
\toPrioridade fixa:newspaper4k>readability>trafilatura.
- Se
Rationale
- Total aderência às seções 7.1 a 7.6 do PRD. A tolerância de
10^{-9}evita imprecisões de ponto flutuante em comparações<= 0.03.
4. Estratégia de I/O Não Destrutiva e Escrita Atômica
Context
O processamento em lote deve preservar a ordem dos artigos e todos os campos originais do JSON, gravando o resultado sem risco de corrupção de arquivos em caso de interrupção.
Decisions
- Entrada e Saída:
- Nome padrão de saída:
<nome_original_sem_extensão>_selected.json. - Suporte a argumento opcional de saída
--output / -o.
- Nome padrão de saída:
- Gravação Atômica:
- Gravar os dados em um arquivo temporário no mesmo diretório (
<saida>.tmp.<pid>). - Executar substituição atômica via
os.replace(temp_path, target_path).
- Gravar os dados em um arquivo temporário no mesmo diretório (
- Preservação de Conteúdo:
- Carregar o JSON original em estruturas nativas de dicionário/lista.
- Inserir a chave
selected_extractordiretamente em cada dicionário de artigo. - Se
selected_extractorjá existir na entrada, sobrescrever com o novo valor recalculado.
Rationale
- Garante integridade absoluta dos dados contra falhas de disco ou encerramentos abruptos.