mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
222 lines
9.3 KiB
Python
222 lines
9.3 KiB
Python
import unittest
|
|
import sys
|
|
import os
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
|
|
|
from semantica.semantic_extract.ner_extractor import NERExtractor, Entity
|
|
from semantica.semantic_extract.named_entity_recognizer import (
|
|
NamedEntityRecognizer,
|
|
EntityClassifier,
|
|
EntityConfidenceScorer,
|
|
CustomEntityDetector
|
|
)
|
|
from semantica.semantic_extract.relation_extractor import RelationExtractor, Relation
|
|
from semantica.semantic_extract.triplet_extractor import (
|
|
TripletExtractor,
|
|
TripletValidator,
|
|
TripletQualityChecker,
|
|
RDFSerializer,
|
|
Triplet
|
|
)
|
|
from semantica.semantic_extract.methods import get_entity_method, get_relation_method
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
class TestSemanticExtractDeepDive(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.text = "Apple Inc. was founded by Steve Jobs in Cupertino. Tim Cook is the CEO."
|
|
self.entities = [
|
|
Entity(text="Apple Inc.", label="ORG", start_char=0, end_char=10, confidence=0.9),
|
|
Entity(text="Steve Jobs", label="PERSON", start_char=26, end_char=36, confidence=0.95),
|
|
Entity(text="Cupertino", label="GPE", start_char=40, end_char=49, confidence=0.8),
|
|
Entity(text="Tim Cook", label="PERSON", start_char=51, end_char=59, confidence=0.9),
|
|
Entity(text="CEO", label="TITLE", start_char=67, end_char=70, confidence=0.7)
|
|
]
|
|
self.relations = [
|
|
Relation(subject=self.entities[0], predicate="founded_by", object=self.entities[1], confidence=0.85),
|
|
Relation(subject=self.entities[3], predicate="works_for", object=self.entities[0], confidence=0.8)
|
|
]
|
|
|
|
# --- NER Tests ---
|
|
|
|
def test_ner_extractor_pattern(self):
|
|
"""Test NERExtractor with pattern method"""
|
|
extractor = NERExtractor(method="pattern")
|
|
# Using a text that matches the hardcoded patterns in methods.py
|
|
text = "Steve Jobs worked at Apple Inc. in New York City on 12/12/2023."
|
|
entities = extractor.extract_entities(text)
|
|
|
|
# Verify entities are extracted
|
|
texts = [e.text for e in entities]
|
|
labels = [e.label for e in entities]
|
|
|
|
# Note: Patterns in methods.py might be specific, let's verify if they match
|
|
# PERSON: \b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)\b -> "Steve Jobs" should match
|
|
# ORG: \b([A-Z][a-zA-Z]+(?:\s+[A-Z][a-zA-Z]+)*\s+(?:Inc|Corp|LLC|Ltd|Company))\b -> "Apple Inc." should match
|
|
|
|
self.assertIn("Steve Jobs", texts)
|
|
self.assertIn("Apple Inc", texts)
|
|
self.assertIn("PERSON", labels)
|
|
self.assertIn("ORG", labels)
|
|
|
|
def test_named_entity_recognizer_flow(self):
|
|
"""Test NamedEntityRecognizer with mocked method"""
|
|
# We mock the internal extraction to avoid dependency on models
|
|
with patch('semantica.semantic_extract.methods.get_entity_method') as mock_get:
|
|
mock_method = MagicMock()
|
|
mock_method.return_value = self.entities
|
|
mock_get.return_value = mock_method
|
|
|
|
ner = NamedEntityRecognizer(confidence_threshold=0.8)
|
|
extracted = ner.extract_entities(self.text)
|
|
|
|
# Should filter out CEO (conf 0.7)
|
|
self.assertEqual(len(extracted), 4)
|
|
self.assertNotIn("CEO", [e.text for e in extracted])
|
|
|
|
def test_entity_classifier(self):
|
|
"""Test EntityClassifier"""
|
|
classifier = EntityClassifier()
|
|
classified = classifier.classify_entities(self.entities)
|
|
|
|
self.assertIn("PERSON", classified)
|
|
self.assertIn("ORG", classified)
|
|
self.assertEqual(len(classified["PERSON"]), 2) # Steve Jobs, Tim Cook
|
|
self.assertEqual(len(classified["ORG"]), 1) # Apple Inc.
|
|
|
|
def test_entity_confidence_scorer(self):
|
|
"""Test EntityConfidenceScorer"""
|
|
scorer = EntityConfidenceScorer()
|
|
scored = scorer.score_entities(self.entities)
|
|
|
|
# Ensure confidence scores are preserved or modified correctly
|
|
for entity in scored:
|
|
self.assertTrue(0 <= entity.confidence <= 1.0)
|
|
|
|
def test_custom_entity_detector(self):
|
|
"""Test CustomEntityDetector"""
|
|
patterns = {"EMAIL": r"[\w\.-]+@[\w\.-]+"}
|
|
detector = CustomEntityDetector(patterns=patterns)
|
|
text = "Contact us at test@example.com"
|
|
|
|
entities = detector.detect_custom_entities(text, "EMAIL")
|
|
self.assertEqual(len(entities), 1)
|
|
self.assertEqual(entities[0].text, "test@example.com")
|
|
self.assertEqual(entities[0].label, "EMAIL")
|
|
|
|
# --- Relation Tests ---
|
|
|
|
def test_relation_extractor_pattern(self):
|
|
"""Test RelationExtractor with pattern method"""
|
|
extractor = RelationExtractor(method="pattern")
|
|
# Text matching "founded by" pattern
|
|
text = "Apple was founded by Steve"
|
|
|
|
# We need entities for relation extraction
|
|
entities = [
|
|
Entity(text="Apple", label="ORG", start_char=0, end_char=5),
|
|
Entity(text="Steve", label="PERSON", start_char=21, end_char=26)
|
|
]
|
|
|
|
relations = extractor.extract_relations(text, entities)
|
|
|
|
self.assertTrue(len(relations) > 0)
|
|
self.assertEqual(relations[0].predicate, "founded_by")
|
|
self.assertEqual(relations[0].subject.text, "Apple")
|
|
self.assertEqual(relations[0].object.text, "Steve")
|
|
|
|
def test_relation_extractor_cooccurrence(self):
|
|
"""Test RelationExtractor with cooccurrence method"""
|
|
# Set low confidence threshold because cooccurrence yields 0.5 confidence
|
|
extractor = RelationExtractor(method="cooccurrence", confidence_threshold=0.4)
|
|
# Entities close to each other
|
|
text = "Apple Inc. CEO Tim Cook announced..."
|
|
entities = [
|
|
Entity(text="Apple Inc.", label="ORG", start_char=0, end_char=10),
|
|
Entity(text="Tim Cook", label="PERSON", start_char=15, end_char=23)
|
|
]
|
|
|
|
relations = extractor.extract_relations(text, entities)
|
|
self.assertTrue(len(relations) > 0)
|
|
self.assertEqual(relations[0].predicate, "related_to")
|
|
|
|
# --- Triplet Tests ---
|
|
|
|
def test_triplet_extractor(self):
|
|
"""Test TripletExtractor"""
|
|
# Mocking get_triplet_method to return a simple extraction function
|
|
with patch('semantica.semantic_extract.methods.get_triplet_method') as mock_get:
|
|
def mock_extract(text, entities, relations, **kwargs):
|
|
triplets = []
|
|
for rel in relations:
|
|
triplets.append(Triplet(
|
|
subject=rel.subject.text,
|
|
predicate=rel.predicate,
|
|
object=rel.object.text,
|
|
confidence=rel.confidence
|
|
))
|
|
return triplets
|
|
|
|
mock_get.return_value = mock_extract
|
|
|
|
extractor = TripletExtractor()
|
|
triplets = extractor.extract_triplets(self.text, self.entities, self.relations)
|
|
|
|
self.assertEqual(len(triplets), 2)
|
|
self.assertEqual(triplets[0].subject, "Apple Inc.")
|
|
self.assertEqual(triplets[0].predicate, "founded_by")
|
|
self.assertEqual(triplets[0].object, "Steve Jobs")
|
|
|
|
def test_triplet_validator(self):
|
|
"""Test TripletValidator"""
|
|
validator = TripletValidator()
|
|
|
|
# Create a valid and invalid triplet
|
|
valid_triplet = Triplet(subject="S", predicate="P", object="O", confidence=0.9)
|
|
invalid_triplet = Triplet(subject="", predicate="P", object="O", confidence=0.9) # Empty subject
|
|
low_conf_triplet = Triplet(subject="S", predicate="P", object="O", confidence=0.2)
|
|
|
|
triplets = [valid_triplet, invalid_triplet, low_conf_triplet]
|
|
|
|
validated = validator.validate_triplets(triplets, min_confidence=0.5)
|
|
|
|
self.assertEqual(len(validated), 1)
|
|
self.assertEqual(validated[0], valid_triplet)
|
|
|
|
def test_rdf_serializer(self):
|
|
"""Test RDFSerializer"""
|
|
serializer = RDFSerializer()
|
|
triplet = Triplet(subject="Apple_Inc", predicate="founded_by", object="Steve_Jobs")
|
|
|
|
# Test N-Triples format
|
|
rdf_output = serializer.serialize_to_rdf([triplet], format="ntriples")
|
|
self.assertIsInstance(rdf_output, str)
|
|
# Check if basic components are in the output (format might vary slightly)
|
|
# N-Triples: <subject> <predicate> <object> .
|
|
# The serializer might handle URIs, let's just check non-empty
|
|
self.assertTrue(len(rdf_output) > 0)
|
|
|
|
def test_triplet_quality_checker(self):
|
|
"""Test TripletQualityChecker"""
|
|
checker = TripletQualityChecker()
|
|
triplets = [
|
|
Triplet(subject="Apple", predicate="founded", object="Jobs", confidence=0.9),
|
|
Triplet(subject="Apple", predicate="located", object="US", confidence=0.8)
|
|
]
|
|
|
|
scores = checker.calculate_quality_scores(triplets)
|
|
|
|
self.assertIn("average_score", scores)
|
|
self.assertAlmostEqual(scores["average_score"], 0.85)
|
|
# triplet_count is not returned by calculate_quality_scores
|
|
# self.assertIn("triplet_count", scores)
|
|
# self.assertEqual(scores["triplet_count"], 2)
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|