138 lines
5.4 KiB
Markdown
138 lines
5.4 KiB
Markdown
# Data Model: Deterministic Content Selection
|
|
|
|
**Branch**: `004-deterministic-content-selection` | **Date**: 2026-08-20 | **Spec**: [spec.md](spec.md)
|
|
|
|
---
|
|
|
|
## 1. Domain Entities & Value Types
|
|
|
|
```mermaid
|
|
classDiagram
|
|
class ExtractorName {
|
|
<<enumeration>>
|
|
TRAFILATURA = "trafilatura"
|
|
NEWSPAPER4K = "newspaper4k"
|
|
READABILITY = "readability"
|
|
}
|
|
|
|
class CandidateStatus {
|
|
<<enumeration>>
|
|
USABLE
|
|
DEGRADED
|
|
UNAVAILABLE
|
|
}
|
|
|
|
class ExtractorCandidate {
|
|
+ExtractorName name
|
|
+str raw_text
|
|
+str error
|
|
+CandidateStatus status
|
|
+List~str~ tokens
|
|
+Set~Tuple~ shingles
|
|
+int shingle_count
|
|
+float coverage
|
|
+float support
|
|
+float score
|
|
}
|
|
|
|
class ArticleSelectionResult {
|
|
+int article_index
|
|
+ExtractorName selected_extractor
|
|
+str selection_reason
|
|
+int active_candidates_count
|
|
+int consensus_shingles_count
|
|
+Dict~ExtractorName, ExtractorCandidate~ candidates
|
|
}
|
|
|
|
class BatchProcessingResult {
|
|
+int total_articles
|
|
+int processed_count
|
|
+Dict~str, int~ selection_distribution
|
|
+str input_file
|
|
+str output_file
|
|
}
|
|
|
|
ExtractorCandidate --> ExtractorName
|
|
ExtractorCandidate --> CandidateStatus
|
|
ArticleSelectionResult --> ExtractorName
|
|
ArticleSelectionResult --> ExtractorCandidate
|
|
BatchProcessingResult --> ArticleSelectionResult
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Entity Descriptions & Fields
|
|
|
|
### `ExtractorName` (Enum / Literal)
|
|
Enumeração estrita com os três motores de extração suportados:
|
|
- `"trafilatura"`
|
|
- `"newspaper4k"`
|
|
- `"readability"`
|
|
|
|
### `CandidateStatus` (Enum)
|
|
Classificação do estado de cada extrator em um dado artigo:
|
|
- `USABLE`: Campo de texto contém string não-vazia após normalização e campo `error` é nulo/vazio.
|
|
- `DEGRADED`: Campo de texto contém string não-vazia após normalização, porém campo `error` não é nulo.
|
|
- `UNAVAILABLE`: Campo de texto é ausente, nulo, tipo diferente de string ou vazio após normalização.
|
|
|
|
### `ExtractorCandidate` (Dataclass)
|
|
Representação estruturada de um candidato durante o cálculo:
|
|
|
|
| Campo | Tipo | Descrição |
|
|
|---|---|---|
|
|
| `name` | `ExtractorName` | Identificador do motor de extração (`trafilatura`, `newspaper4k`, `readability`). |
|
|
| `raw_text` | `str \| None` | Texto bruto obtido do campo correspondente no JSON (`trafilatura.text`, `newspaper4k.text`, `readability.cleaned_text`). |
|
|
| `error` | `str \| None` | Mensagem de erro do motor, se houver (`trafilatura.error`, etc.). |
|
|
| `status` | `CandidateStatus` | Estado de viabilidade do candidato (`USABLE`, `DEGRADED`, `UNAVAILABLE`). |
|
|
| `tokens` | `list[str]` | Sequência ordenada de tokens alfanuméricos minúsculos após normalização NFKC. |
|
|
| `shingles` | `set[tuple[str, ...]]` | Conjunto de n-grams consecutivos de 5 tokens (ou 1 n-gram se $1 \le \text{tokens} \le 4$). |
|
|
| `shingle_count` | `int` | Quantidade total de shingles gerados (`len(shingles)`). |
|
|
| `coverage` | `float` | Proporção de shingles do consenso presentes no candidato ($[0.0, 1.0]$). |
|
|
| `support` | `float` | Proporção de shingles do candidato que pertencem ao consenso ($[0.0, 1.0]$). |
|
|
| `score` | `float` | Pontuação $F_1$ baseada em cobertura e suporte ($[0.0, 1.0]$). |
|
|
|
|
---
|
|
|
|
### `ArticleSelectionResult` (Dataclass)
|
|
Resultado detalhado da avaliação para um único artigo:
|
|
|
|
| Campo | Tipo | Descrição |
|
|
|---|---|---|
|
|
| `article_index` | `int` | Posição ordinal do artigo no array `articles` original (0-indexed). |
|
|
| `selected_extractor` | `ExtractorName` | Vencedor da seleção determinística (`trafilatura`, `newspaper4k`, `readability`). |
|
|
| `selection_reason` | `str` | Justificativa rastreável da escolha (ex: `"highest_score"`, `"technical_tie_smallest_shingles"`, `"no_consensus_median_shingles"`, `"single_usable_candidate"`, `"fallback_all_unavailable"`). |
|
|
| `active_candidates_count` | `int` | Número de candidatos que formaram o conjunto ativo avaliado. |
|
|
| `consensus_shingles_count` | `int` | Quantidade de shingles no conjunto de consenso. |
|
|
| `candidates` | `dict[ExtractorName, ExtractorCandidate]` | Dicionário com o detalhamento de cada um dos 3 motores. |
|
|
|
|
---
|
|
|
|
### `BatchProcessingResult` (Dataclass)
|
|
Sumário da execução do lote:
|
|
|
|
| Campo | Tipo | Descrição |
|
|
|---|---|---|
|
|
| `total_articles` | `int` | Total de artigos encontrados no arquivo de entrada. |
|
|
| `processed_count` | `int` | Total de artigos processados e enriquecidos com sucesso. |
|
|
| `selection_distribution` | `dict[str, int]` | Contagem de seleções por motor (`{"trafilatura": X, "newspaper4k": Y, "readability": Z}`). |
|
|
| `input_file` | `str` | Caminho do arquivo lido. |
|
|
| `output_file` | `str` | Caminho do arquivo gerado de forma atômica. |
|
|
|
|
---
|
|
|
|
## 3. JSON Schema Mapping
|
|
|
|
### Entrada
|
|
- Raiz: Objeto contendo chave `articles: list[dict]`.
|
|
- Cada item em `articles`:
|
|
- `trafilatura` (objeto opcional): `{ "text": str | null, "error": str | null, ... }`
|
|
- `newspaper4k` (objeto opcional): `{ "text": str | null, "error": str | null, ... }`
|
|
- `readability` (objeto opcional): `{ "cleaned_text": str | null, "error": str | null, ... }`
|
|
|
|
### Saída
|
|
- Mesma estrutura exata da entrada, preservando 100% dos dados anteriores e ordem da lista `articles`.
|
|
- Em cada item de `articles`, adição/atualização da chave:
|
|
```json
|
|
"selected_extractor": "newspaper4k" | "readability" | "trafilatura"
|
|
```
|