feat(runtime): implement single-article consolidation runtime and modularize codebase
This commit is contained in:
@@ -42,6 +42,12 @@
|
||||
- [Sanitização Editorial e Deduplicação](#sanitização-editorial-e-deduplicação)
|
||||
- [Argumentos e Flags CLI](#argumentos-e-flags-cli-2)
|
||||
- [Exemplos Práticos de Uso](#exemplos-práticos-de-uso-2)
|
||||
- [6. Runtime de Consolidação e Higienização de Artigos (006-article-consolidation-runtime)](#6--runtime-de-consolidação-e-higienização-de-artigos-006-article-consolidation-runtime)
|
||||
- [Visão Geral e Arquitetura](#visão-geral-e-arquitetura-do-runtime)
|
||||
- [Configuração de Ambiente (.env)](#configuração-de-ambiente-env)
|
||||
- [Comandos e Utilitários CLI](#comandos-e-utilitários-cli)
|
||||
- [O que Esperar do Resultado (Artefatos Gerados)](#o-que-esperar-do-resultado-artefatos-gerados)
|
||||
- [Contrato de Códigos de Saída (Exit Codes)](#contrato-de-códigos-de-saída-exit-codes)
|
||||
- [Estrutura do Projeto](#-estrutura-do-projeto)
|
||||
- [Testes e Qualidade de Código](#-testes-e-qualidade-de-código)
|
||||
- [Licença](#-licença)
|
||||
@@ -472,37 +478,205 @@ print(f"Markdown gerado em: {out_file}")
|
||||
|
||||
---
|
||||
|
||||
## 6. 🚀 Runtime de Consolidação e Higienização de Artigos (006-article-consolidation-runtime)
|
||||
|
||||
### Visão Geral e Arquitetura do Runtime
|
||||
|
||||
O **Runtime de Consolidação e Higienização Editorial de Artigos** é um pipeline de produção industrial de alta confiabilidade projetado para transformar a saída de extração tríplice de artigos em documentos editoriais finais em **Markdown limpo e estruturado com Manifesto JSON de auditoria completa**, operando estritamente sob **modelos baratos de IA (Groq / DeepSeek / OpenAI / Proxies)** e com **proibição total de expressões regulares (`zero-regex`)** em suas operações de texto.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
In[Artigo JSON + ECP Snapshot] --> Val[1. Validação Estrita de Schemas e Preflight]
|
||||
Val --> FP[2. Cálculo de Fingerprint Determinístico SHA-256]
|
||||
FP --> SQLClaim[3. Claim Atômico no SQLite WAL - BEGIN IMMEDIATE]
|
||||
SQLClaim --> Shingles[4. Parser de Candidatos e Mapeamento de Equivalências sem Regex]
|
||||
Shingles --> HygLLM[5. Higienização Extrativa por LLM - Grounding por IDs de Blocos]
|
||||
HygLLM --> RepVal[6. Validador de Reparos Textuais Restritos - Mojibake/Typos/Espaçamento]
|
||||
RepVal --> ECPGate{7. Gate Obrigatório de ECP}
|
||||
ECPGate -- Não Inerente / Tangencial --> RejManifest[Grava Manifesto rejected_ecp - Zero Markdown]
|
||||
ECPGate -- Inerente DIRECT/CONTEXTUAL --> EnrichLLM[8. Enriquecimento LLM - Sentimento e Tags]
|
||||
EnrichLLM --> AtomPersist[9. Persistência Atômica temp + os.replace]
|
||||
AtomPersist --> OutMD[10. Markdown com YAML Front-matter + Manifesto .result.json]
|
||||
OutMD --> SQLiteDone[11. Atualização de Estado completed_text no SQLite]
|
||||
```
|
||||
|
||||
### Configuração de Ambiente (`.env`)
|
||||
|
||||
Crie ou edite o arquivo `.env` na raiz do projeto com suas credenciais:
|
||||
|
||||
```bash
|
||||
# Provedor Padrão (OpenAI / Omniroute / Proxy Customizado)
|
||||
OPENAI_API_KEY="sk-..."
|
||||
OPENAI_BASE_URL="https://omniroute.app.andreferraro.com/v1"
|
||||
OPENAI_MODEL="cgpt-web/gpt-5.5"
|
||||
|
||||
# Ou Provedores Nativos Específicos
|
||||
GROQ_API_KEY="gsk_..."
|
||||
DEEPSEEK_API_KEY="sk-..."
|
||||
|
||||
# Observabilidade (Opcional - Degrada para fila SQLite local se offline)
|
||||
LANGFUSE_PUBLIC_KEY="pk-lf-..."
|
||||
LANGFUSE_SECRET_KEY="sk-lf-..."
|
||||
LANGFUSE_HOST="https://cloud.langfuse.com"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Comandos e Utilitários CLI
|
||||
|
||||
O runtime expõe **5 utilitários CLI normativos**:
|
||||
|
||||
#### 1. Consolidação de Artigo Único (`consolidate.py`)
|
||||
Executa a consolidação de ponta a ponta de um artigo contra um perfil de entidade (ECP):
|
||||
```bash
|
||||
python src/runtime/cli/consolidate.py \
|
||||
--config runtime_config.local.json \
|
||||
--article examples/sample_article_valid.json \
|
||||
--ecp examples/sample_ecp_snapshot.json
|
||||
```
|
||||
|
||||
#### 2. Certificação de Pré-Voo (`preflight.py`)
|
||||
Valida permissões de disco, banco de dados SQLite, prompts, hashes e integridade do ambiente antes do início das operações:
|
||||
```bash
|
||||
python src/runtime/cli/preflight.py --config runtime_config.local.json
|
||||
```
|
||||
|
||||
#### 3. Teste de Fumaça (`smoke.py`)
|
||||
Valida rapidamente o funcionamento do pipeline completo com dados de exemplo locais:
|
||||
```bash
|
||||
python src/runtime/cli/smoke.py --config runtime_config.local.json
|
||||
```
|
||||
|
||||
#### 4. Reconciliação e Recuperação de Quedas (`reconcile.py`)
|
||||
Detecta artefatos gravados em disco e reconcilia o estado do banco SQLite após reinicializações ou crashes do processo:
|
||||
```bash
|
||||
# Auditoria e sincronização de estados divergentes
|
||||
python src/runtime/cli/reconcile.py --config runtime_config.local.json
|
||||
|
||||
# Reconciliação com limpeza de arquivos temporários órfãos (.tmp_*)
|
||||
python src/runtime/cli/reconcile.py --config runtime_config.local.json --cleanup-orphans
|
||||
```
|
||||
|
||||
#### 5. Despejo de Telemetria Operacional (`telemetry_flush.py`)
|
||||
Efetua o flush em lote de eventos e métricas enfileirados localmente no SQLite quando a conexão com o Langfuse foi restabelecida:
|
||||
```bash
|
||||
python src/runtime/cli/telemetry_flush.py --config runtime_config.local.json --batch-size 50
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### O que Esperar do Resultado (Artefatos Gerados)
|
||||
|
||||
Para cada artigo processado, o runtime grava seus artefatos no diretório configurado (ex: `out/articles/`):
|
||||
|
||||
#### 1. Documento Markdown Higienizado (`<fingerprint>.md`)
|
||||
Quando o artigo é classificado como inerente (`DIRECT_INHERENT` ou `CONTEXTUAL_INHERENT`), um arquivo Markdown padronizado é gerado com **YAML Front-Matter** estrito e corpo textual higienizado:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: "Los puntajes de River vs. Independiente Santa Fe"
|
||||
fingerprint: "c987f362b35e76239e3fda0841c9e5f89c3d4193760738e6a2e9424ce45fde88"
|
||||
source_url: "https://www.tycsports.com/river-plate/los-puntajes-de-river-vs-independiente-santa-fe.html"
|
||||
sentiment: "positive"
|
||||
---
|
||||
|
||||
# Los puntajes de River vs. Independiente Santa Fe
|
||||
|
||||
River Plate empató sin goles ante Independiente Santa Fe en el estadio El Campín de Bogotá por la ida de los octavos de final de la Copa Sudamericana.
|
||||
|
||||
El equipo de Marcelo Gallardo resistió la presión del conjunto colombiano y definirá la serie la próxima semana en el estadio Monumental de Buenos Aires.
|
||||
```
|
||||
|
||||
#### 2. Manifesto de Auditoria e Resultado (`<fingerprint>.result.json`)
|
||||
Contém a auditoria completa de hashes, tokens, latência, custos, status ECP e metadados:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": "1.0.0",
|
||||
"fingerprint": "c987f362b35e76239e3fda0841c9e5f89c3d4193760738e6a2e9424ce45fde88",
|
||||
"source_url": "https://www.tycsports.com/river-plate/los-puntajes-de-river-vs-independiente-santa-fe.html",
|
||||
"selected_extractor": "trafilatura",
|
||||
"final_status": "completed_text",
|
||||
"generate_markdown": true,
|
||||
"markdown_path": "out/articles/c987f362b35e76239e3fda0841c9e5f89c3d4193760738e6a2e9424ce45fde88.md",
|
||||
"markdown_hash": "b6b21b19f10feab12525030016a3eeb4ed702cdec6d39c91fc42289b65091e0e",
|
||||
"ecp_classification": {
|
||||
"category": "DIRECT_INHERENT",
|
||||
"is_inherent": true,
|
||||
"confidence": 0.98,
|
||||
"rationale": "Direct match of target entity 'Club Atlético River Plate' with strong contextual anchor density (4 anchor(s) matched)."
|
||||
},
|
||||
"enrichment": {
|
||||
"sentiment": "positive",
|
||||
"tags": ["river plate", "copa sudamericana", "futebol"]
|
||||
},
|
||||
"config_version": "1.0.0",
|
||||
"error_codes": []
|
||||
}
|
||||
```
|
||||
|
||||
> **Nota sobre Artigos Não Inerentes**: Caso o artigo seja rejeitado pelo ECP (`TANGENTIAL` ou `NOT_RELATED`), o arquivo `.md` **não é gerado** (zero bytes de lixo editorial) e o manifesto `.result.json` é gravado com `final_status: "rejected_ecp"` e `generate_markdown: false`.
|
||||
|
||||
#### 3. Rastreamento e Estado no Banco SQLite (`out/runtime.db`)
|
||||
O banco SQLite opera em modo **WAL** com transações imediatas para prevenir concorrência e deadlocks, registrando o histórico de transições de status (`received` → `claimed` → `hygiene_running` → `ecp_evaluated` → `enrichment_running` → `completed_text` / `rejected_ecp`).
|
||||
|
||||
---
|
||||
|
||||
### Contrato de Códigos de Saída (Exit Codes)
|
||||
|
||||
O CLI segue estritamente os códigos de saída normativos:
|
||||
|
||||
| Código | Significado | Descrição |
|
||||
| :---: | :--- | :--- |
|
||||
| `0` | **Sucesso** | Processamento completado com sucesso (artigo consolidado ou rejeitado com manifesto válido). |
|
||||
| `1` | **Erro de Contrato / Input** | JSON de entrada inválido, schema corrompido ou argumentos ausentes. |
|
||||
| `2` | **Erro de Preflight / Config** | Arquivo de configuração ausente, chave de API inexistente ou modelo não certificado. |
|
||||
| `3` | **Erro de Gateway / Fallback** | Provedor primário e fallback falharam simultaneamente sem recuperação determinística. |
|
||||
| `4` | **Erro Fatal de I/O** | Disco inacessível, falha de integridade SHA-256 ou corrupção de persistência atômica. |
|
||||
|
||||
---
|
||||
|
||||
## 📁 Estrutura do Projeto
|
||||
|
||||
```text
|
||||
TextNLPClassifierApp/
|
||||
├── classify.py # CLI principal do Classificador de Inerência
|
||||
├── runtime_config.local.json # Configuração canônica do Runtime (roles, modelos, pricing)
|
||||
├── .env # Chaves de API e URLs locais (gitignored)
|
||||
├── classify.py # CLI legado do Classificador de Inerência
|
||||
├── scripts/
|
||||
│ ├── __init__.py # Pacote utilitário de scripts
|
||||
│ ├── ci_check.py # Pipeline unificado de validação estática e CI
|
||||
│ ├── extract_google_news.py # CLI de Extração de Manchetes do Google News
|
||||
│ ├── extract_article_contents.py # CLI de Extração e Parsing Multimotor de Artigos
|
||||
│ ├── select_article_extractor.py # CLI de Seleção Determinística de Extrator
|
||||
│ └── convert_article_to_markdown.py # CLI de Conversão de Artigo JSON para Markdown
|
||||
├── src/ # Módulos centrais do classificador
|
||||
│ ├── classifier.py # Orquestrador de classificação (Tier 1, 2, 3)
|
||||
│ ├── models.py # Modelos de dados e esquemas (ECPSnapshot, Decision)
|
||||
│ ├── preprocessor.py # Normalização de texto e detecção de idioma
|
||||
│ └── adapters/ # Adaptadores opcionais de Embeddings e LLM
|
||||
├── specs/ # Especificações e planos arquiteturais (Speckit)
|
||||
│ ├── 001-multilingual-entity-classifier/
|
||||
│ ├── 002-google-news-extractor/
|
||||
│ ├── 003-article-content-extractor/
|
||||
│ ├── 004-deterministic-content-selection/
|
||||
│ └── 005-convert-json-markdown/ # Specs da conversão JSON para Markdown
|
||||
├── tests/ # Suíte de testes automatizados
|
||||
│ ├── test_classifier.py
|
||||
│ ├── test_extract_google_news.py
|
||||
│ ├── test_extract_article_contents.py
|
||||
│ ├── test_select_article_extractor.py
|
||||
│ ├── test_convert_article_to_markdown.py # Testes da conversão para Markdown
|
||||
│ ├── test_llm_fallback.py # Testes do Tier 3 LLM Fallback
|
||||
│ ├── test_e2e_text_analysis_pipeline.py # Suíte E2E do Funil de Análise e Fallback
|
||||
│ └── test_classify_exhaustive_suite.py # Suíte Exaustiva de Casos Felizes/Infelizes (QA Sênior)
|
||||
├── src/
|
||||
│ ├── runtime/ # MÓDULOS CENTRAIS DO RUNTIME DE CONSOLIDAÇÃO
|
||||
│ │ ├── candidate/ # Parser de candidatos, shingles e normalização zero-regex
|
||||
│ │ ├── cli/ # CLIs: consolidate, preflight, smoke, reconcile, flush
|
||||
│ │ ├── core/ # Configs, contratos, fingerprint SHA-256 e limites
|
||||
│ │ ├── ecp/ # Adapter de decisão de inerência e schema ECP
|
||||
│ │ ├── enrichment/ # Harness de enriquecimento (sentimento e tags)
|
||||
│ │ ├── gateway/ # Gateway agnóstico (Groq, DeepSeek, OpenAI) com failover
|
||||
│ │ ├── hygiene/ # Harness de higienização LLM e grounding por IDs
|
||||
│ │ ├── observability/ # Logging estruturado e tracer Langfuse com fila offline
|
||||
│ │ ├── quality/ # Validador de reparos textuais restritos (mojibake/typos)
|
||||
│ │ └── storage/ # Persistência atômica (file_store) e SQLite WAL
|
||||
│ └── tools/ # Módulos e extratores legados (classifier, language, parser)
|
||||
├── specs/ # Especificações Speckit (001 a 006)
|
||||
│ └── 006-article-consolidation-runtime/ # Especificação técnica completa do runtime
|
||||
├── docs/structured_extraction/ # Documentação arquitetural (PRD, ADRs, Test Plan, Runbook)
|
||||
├── tests/
|
||||
│ ├── runtime/ # SUÍTE DO RUNTIME (72 testes)
|
||||
│ │ ├── contract/ # Validação de schemas e contratos
|
||||
│ │ ├── fault_injection/ # Falhas HTTP 429, 500, JSON corrompido
|
||||
│ │ ├── integration/ # Concorrência (8 workers), Reconciliação, CLI subprocess, E2E Real
|
||||
│ │ ├── load/ # Benchmark de sustentação de carga (100 art/h)
|
||||
│ │ ├── quality/ # Orçamento de custo, Golden Set 20, Zero Regex scanner
|
||||
│ │ ├── security/ # Injeção de prompt e redação de secrets
|
||||
│ │ └── unit/ # Testes unitários dos módulos internos
|
||||
│ ├── tools/ # SUÍTE DE TOOLS LEGADAS (247 testes)
|
||||
│ └── scripts/check_zero_regex.py # Auditor estático de AST garantindo Zero Regex
|
||||
├── requirements.txt # Dependências do projeto
|
||||
├── pyproject.toml # Configurações de ferramentas (pytest, ruff, mypy)
|
||||
└── README.md # Documentação principal
|
||||
@@ -512,38 +686,26 @@ TextNLPClassifierApp/
|
||||
|
||||
## 🧪 Testes e Qualidade de Código
|
||||
|
||||
O repositório possui **247 testes automatizados** com 100% de aprovação cobrindo testes unitários, de regressão, de integração, Golden Fixtures exatas, testes de sensibilidade de mutação, testes de fallback para LLM (Tier 3), validações de degradação graciosa, matriz multilíngue e testes End-to-End (E2E) via CLI subprocess:
|
||||
O repositório possui **319 testes automatizados** com **100% de aprovação**:
|
||||
|
||||
```bash
|
||||
# Executar toda a suíte de testes do projeto (247 testes)
|
||||
pytest -v
|
||||
# 1. Executar a Verificação Completa do CI (Validação Estática Zero-Regex + 319 Testes)
|
||||
python scripts/ci_check.py
|
||||
|
||||
# Executar a Suíte Exaustiva de Classificação e Fallback (38 testes)
|
||||
pytest tests/test_classify_exhaustive_suite.py -v
|
||||
# 2. Executar apenas a Suíte do Runtime (72 testes)
|
||||
pytest tests/runtime -v
|
||||
|
||||
# Executar a Suíte E2E do Funil de Análise de Texto e Fallback para LLM
|
||||
pytest tests/test_e2e_text_analysis_pipeline.py -v
|
||||
# 3. Executar o Teste E2E Real contra API ao vivo
|
||||
pytest tests/runtime/integration/test_live_e2e_real_api.py -v -s
|
||||
|
||||
# Executar os testes do Fallback para LLM (Tier 3)
|
||||
pytest tests/test_llm_fallback.py -v
|
||||
# 4. Executar o Teste de Concorrência de Alta Contenção (8 workers paralelos simultâneos)
|
||||
pytest tests/runtime/integration/test_concurrency_claims.py -v
|
||||
|
||||
# Executar os testes de Conversão de Artigo para Markdown (67 testes)
|
||||
pytest tests/test_convert_article_to_markdown.py -v
|
||||
# 5. Executar apenas a Suíte de Ferramentas Legadas (247 testes)
|
||||
pytest tests/tools -v
|
||||
|
||||
# Executar os testes do Seletor Determinístico
|
||||
pytest tests/test_select_article_extractor.py -v
|
||||
|
||||
# Executar os testes do Extrator de Conteúdo Multimotor
|
||||
pytest tests/test_extract_article_contents.py -v
|
||||
|
||||
# Executar os testes do Extrator do Google News
|
||||
pytest tests/test_extract_google_news.py -v
|
||||
|
||||
# Validação com Ruff
|
||||
ruff check .
|
||||
|
||||
# Verificação estática de tipos com Mypy
|
||||
mypy src/ scripts/ tests/
|
||||
# 6. Auditoria Estática de Proibição Absoluta de Regex
|
||||
python tests/scripts/check_zero_regex.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user