- Added scripts/extract_article_contents.py for batch scraping with stealth Foxcape and triple extraction (Trafilatura, Newspaper4k, Readability) - Created unit, integration, and E2E test suite in tests/test_extract_article_contents.py (90/90 passing) - Updated specs/003-article-content-extractor and README.md with usage documentation and CLI contracts - Passed ruff linting/formatting and mypy type checking cleanly
101 lines
3.7 KiB
Python
101 lines
3.7 KiB
Python
"""Unit tests for deterministic classification decision logic."""
|
|
|
|
import pytest
|
|
|
|
from src.classifier import InherenceClassifier
|
|
from src.models import DecisionCategory, ECPSnapshot, RelatedEntity
|
|
|
|
|
|
@pytest.fixture
|
|
def petrobras_ecp():
|
|
return ECPSnapshot(
|
|
target_entity_id="ent_petrobras",
|
|
target_name="Petrobras",
|
|
aliases=["Petróleo Brasileiro S.A.", "Petrobras", "Petrobrás"],
|
|
domain="Oil & Gas",
|
|
anchors=["pré-sal", "refinaria", "combustíveis", "petróleo", "exploração"],
|
|
negative_anchors=["posto de combustíveis pirata"],
|
|
graph_version="1.0.0",
|
|
related_entities=[
|
|
RelatedEntity(
|
|
entity_id="ent_transpetro",
|
|
name="Transpetro",
|
|
relation_type="SUBSIDIARY_OF",
|
|
weight=0.85,
|
|
aliases=[],
|
|
scope="logistics",
|
|
confidence=1.0,
|
|
)
|
|
],
|
|
)
|
|
|
|
|
|
def test_direct_inherent(petrobras_ecp):
|
|
classifier = InherenceClassifier()
|
|
content = (
|
|
"# Expansão da Produção Nacional\n\n"
|
|
"A Petrobras anunciou um aumento expressivo na produção de petróleo na camada pré-sal. "
|
|
"Os investimentos em novas plataformas devem acelerar a exploração offshore."
|
|
)
|
|
result = classifier.classify(petrobras_ecp, content)
|
|
assert result.decision == DecisionCategory.DIRECT_INHERENT
|
|
assert result.is_inherent is True
|
|
assert result.confidence >= 0.85
|
|
assert result.detected_language == "pt"
|
|
assert "Petrobras" in result.matched_anchors
|
|
assert len(result.evidence) > 0
|
|
|
|
|
|
def test_contextual_inherent(petrobras_ecp):
|
|
classifier = InherenceClassifier()
|
|
content = (
|
|
"# Logística de Combustíveis no Brasil\n\n"
|
|
"A Transpetro ampliou a sua frota de navios para o transporte de combustíveis e derivados "
|
|
"pelo litoral brasileiro, reforçando a infraestrutura energética."
|
|
)
|
|
result = classifier.classify(petrobras_ecp, content)
|
|
assert result.decision == DecisionCategory.CONTEXTUAL_INHERENT
|
|
assert result.is_inherent is True
|
|
assert result.confidence >= 0.70
|
|
assert len(result.graph_matches) == 1
|
|
assert result.graph_matches[0]["name"] == "Transpetro"
|
|
|
|
|
|
def test_tangential_inherent(petrobras_ecp):
|
|
classifier = InherenceClassifier()
|
|
content = (
|
|
"# Crônica de Viagem pelo Interior\n\n"
|
|
"Passamos perto de um prédio da Petrobras enquanto procurávamos um café na praça central. "
|
|
"A tarde estava quente e os pássaros cantavam nas árvores antigas."
|
|
)
|
|
result = classifier.classify(petrobras_ecp, content)
|
|
assert result.decision == DecisionCategory.TANGENTIAL
|
|
assert result.is_inherent is False
|
|
assert result.confidence < 0.60
|
|
assert len(result.warnings) > 0
|
|
|
|
|
|
def test_not_related(petrobras_ecp):
|
|
classifier = InherenceClassifier()
|
|
content = (
|
|
"# Como Fazer Bolo de Cenoura com Cobertura de Chocolate\n\n"
|
|
"Bata as cenouras raladas no liquidificador com os ovos e o óleo. "
|
|
"Acrescente a farinha de trigo e o açúcar aos poucos até obter uma massa homogênea."
|
|
)
|
|
result = classifier.classify(petrobras_ecp, content)
|
|
assert result.decision == DecisionCategory.NOT_RELATED
|
|
assert result.is_inherent is False
|
|
assert result.confidence >= 0.85
|
|
|
|
|
|
def test_negative_anchor_suppression(petrobras_ecp):
|
|
classifier = InherenceClassifier()
|
|
content = (
|
|
"# Operação Policial Fecha Estabelecimento\n\n"
|
|
"A polícia interditou um posto de combustíveis pirata na rodovia estadual por adulteração."
|
|
)
|
|
result = classifier.classify(petrobras_ecp, content)
|
|
assert result.decision == DecisionCategory.NOT_RELATED
|
|
assert result.is_inherent is False
|
|
assert len(result.negative_matches) > 0
|