This commit is contained in:
Cyber MacGeddon 2026-01-27 00:26:43 +00:00
parent 7b34ea7a9b
commit 6b7b710fe5
8 changed files with 310 additions and 302 deletions

View file

@ -12,7 +12,7 @@ import json
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock, patch
from trustgraph.extract.kg.agent.extract import Processor as AgentKgExtractor from trustgraph.extract.kg.agent.extract import Processor as AgentKgExtractor
from trustgraph.schema import Chunk, Triple, Triples, Metadata, Value, Error from trustgraph.schema import Chunk, Triple, Triples, Metadata, Term, Error, IRI, LITERAL
from trustgraph.schema import EntityContext, EntityContexts, AgentRequest, AgentResponse from trustgraph.schema import EntityContext, EntityContexts, AgentRequest, AgentResponse
from trustgraph.rdf import TRUSTGRAPH_ENTITIES, DEFINITION, RDF_LABEL, SUBJECT_OF from trustgraph.rdf import TRUSTGRAPH_ENTITIES, DEFINITION, RDF_LABEL, SUBJECT_OF
from trustgraph.template.prompt_manager import PromptManager from trustgraph.template.prompt_manager import PromptManager
@ -78,9 +78,9 @@ class TestAgentKgExtractionIntegration:
id="doc123", id="doc123",
metadata=[ metadata=[
Triple( Triple(
s=Value(value="doc123", is_uri=True), s=Term(type=IRI, iri="doc123"),
p=Value(value="http://example.org/type", is_uri=True), p=Term(type=IRI, iri="http://example.org/type"),
o=Value(value="document", is_uri=False) o=Term(type=LITERAL, value="document")
) )
] ]
) )
@ -178,15 +178,15 @@ class TestAgentKgExtractionIntegration:
assert len(sent_triples.triples) > 0 assert len(sent_triples.triples) > 0
# Check that we have definition triples # Check that we have definition triples
definition_triples = [t for t in sent_triples.triples if t.p.value == DEFINITION] definition_triples = [t for t in sent_triples.triples if t.p.iri == DEFINITION]
assert len(definition_triples) >= 2 # Should have definitions for ML and Neural Networks assert len(definition_triples) >= 2 # Should have definitions for ML and Neural Networks
# Check that we have label triples # Check that we have label triples
label_triples = [t for t in sent_triples.triples if t.p.value == RDF_LABEL] label_triples = [t for t in sent_triples.triples if t.p.iri == RDF_LABEL]
assert len(label_triples) >= 2 # Should have labels for entities assert len(label_triples) >= 2 # Should have labels for entities
# Check subject-of relationships # Check subject-of relationships
subject_of_triples = [t for t in sent_triples.triples if t.p.value == SUBJECT_OF] subject_of_triples = [t for t in sent_triples.triples if t.p.iri == SUBJECT_OF]
assert len(subject_of_triples) >= 2 # Entities should be linked to document assert len(subject_of_triples) >= 2 # Entities should be linked to document
# Verify entity contexts were emitted # Verify entity contexts were emitted
@ -198,7 +198,7 @@ class TestAgentKgExtractionIntegration:
assert len(sent_contexts.entities) >= 2 # Should have contexts for both entities assert len(sent_contexts.entities) >= 2 # Should have contexts for both entities
# Verify entity URIs are properly formed # Verify entity URIs are properly formed
entity_uris = [ec.entity.value for ec in sent_contexts.entities] entity_uris = [ec.entity.iri for ec in sent_contexts.entities]
assert f"{TRUSTGRAPH_ENTITIES}Machine%20Learning" in entity_uris assert f"{TRUSTGRAPH_ENTITIES}Machine%20Learning" in entity_uris
assert f"{TRUSTGRAPH_ENTITIES}Neural%20Networks" in entity_uris assert f"{TRUSTGRAPH_ENTITIES}Neural%20Networks" in entity_uris
@ -401,7 +401,7 @@ class TestAgentKgExtractionIntegration:
sent_triples = triples_publisher.send.call_args[0][0] sent_triples = triples_publisher.send.call_args[0][0]
# Check that unicode entity was properly processed # Check that unicode entity was properly processed
entity_labels = [t for t in sent_triples.triples if t.p.value == RDF_LABEL and t.o.value == "機械学習"] entity_labels = [t for t in sent_triples.triples if t.p.iri == RDF_LABEL and t.o.value == "機械学習"]
assert len(entity_labels) > 0 assert len(entity_labels) > 0
@pytest.mark.asyncio @pytest.mark.asyncio

View file

@ -16,7 +16,7 @@ from .cassandra_test_helper import cassandra_container
from trustgraph.direct.cassandra_kg import KnowledgeGraph from trustgraph.direct.cassandra_kg import KnowledgeGraph
from trustgraph.storage.triples.cassandra.write import Processor as StorageProcessor from trustgraph.storage.triples.cassandra.write import Processor as StorageProcessor
from trustgraph.query.triples.cassandra.service import Processor as QueryProcessor from trustgraph.query.triples.cassandra.service import Processor as QueryProcessor
from trustgraph.schema import Triple, Value, Metadata, Triples, TriplesQueryRequest from trustgraph.schema import Triple, Term, Metadata, Triples, TriplesQueryRequest, IRI, LITERAL
@pytest.mark.integration @pytest.mark.integration
@ -118,19 +118,19 @@ class TestCassandraIntegration:
metadata=Metadata(user="testuser", collection="testcol"), metadata=Metadata(user="testuser", collection="testcol"),
triples=[ triples=[
Triple( Triple(
s=Value(value="http://example.org/person1", is_uri=True), s=Term(type=IRI, iri="http://example.org/person1"),
p=Value(value="http://example.org/name", is_uri=True), p=Term(type=IRI, iri="http://example.org/name"),
o=Value(value="Alice Smith", is_uri=False) o=Term(type=LITERAL, value="Alice Smith")
), ),
Triple( Triple(
s=Value(value="http://example.org/person1", is_uri=True), s=Term(type=IRI, iri="http://example.org/person1"),
p=Value(value="http://example.org/age", is_uri=True), p=Term(type=IRI, iri="http://example.org/age"),
o=Value(value="25", is_uri=False) o=Term(type=LITERAL, value="25")
), ),
Triple( Triple(
s=Value(value="http://example.org/person1", is_uri=True), s=Term(type=IRI, iri="http://example.org/person1"),
p=Value(value="http://example.org/department", is_uri=True), p=Term(type=IRI, iri="http://example.org/department"),
o=Value(value="Engineering", is_uri=False) o=Term(type=LITERAL, value="Engineering")
) )
] ]
) )
@ -181,19 +181,19 @@ class TestCassandraIntegration:
metadata=Metadata(user="testuser", collection="testcol"), metadata=Metadata(user="testuser", collection="testcol"),
triples=[ triples=[
Triple( Triple(
s=Value(value="http://example.org/alice", is_uri=True), s=Term(type=IRI, iri="http://example.org/alice"),
p=Value(value="http://example.org/knows", is_uri=True), p=Term(type=IRI, iri="http://example.org/knows"),
o=Value(value="http://example.org/bob", is_uri=True) o=Term(type=IRI, iri="http://example.org/bob")
), ),
Triple( Triple(
s=Value(value="http://example.org/alice", is_uri=True), s=Term(type=IRI, iri="http://example.org/alice"),
p=Value(value="http://example.org/age", is_uri=True), p=Term(type=IRI, iri="http://example.org/age"),
o=Value(value="30", is_uri=False) o=Term(type=LITERAL, value="30")
), ),
Triple( Triple(
s=Value(value="http://example.org/bob", is_uri=True), s=Term(type=IRI, iri="http://example.org/bob"),
p=Value(value="http://example.org/knows", is_uri=True), p=Term(type=IRI, iri="http://example.org/knows"),
o=Value(value="http://example.org/charlie", is_uri=True) o=Term(type=IRI, iri="http://example.org/charlie")
) )
] ]
) )
@ -208,7 +208,7 @@ class TestCassandraIntegration:
# Test S query (find all relationships for Alice) # Test S query (find all relationships for Alice)
s_query = TriplesQueryRequest( s_query = TriplesQueryRequest(
s=Value(value="http://example.org/alice", is_uri=True), s=Term(type=IRI, iri="http://example.org/alice"),
p=None, # None for wildcard p=None, # None for wildcard
o=None, # None for wildcard o=None, # None for wildcard
limit=10, limit=10,
@ -218,10 +218,10 @@ class TestCassandraIntegration:
s_results = await query_processor.query_triples(s_query) s_results = await query_processor.query_triples(s_query)
print(f"Query processor results: {len(s_results)}") print(f"Query processor results: {len(s_results)}")
for result in s_results: for result in s_results:
print(f" S={result.s.value}, P={result.p.value}, O={result.o.value}") print(f" S={result.s.iri}, P={result.p.iri}, O={result.o.iri if result.o.type == IRI else result.o.value}")
assert len(s_results) == 2 assert len(s_results) == 2
s_predicates = [t.p.value for t in s_results] s_predicates = [t.p.iri for t in s_results]
assert "http://example.org/knows" in s_predicates assert "http://example.org/knows" in s_predicates
assert "http://example.org/age" in s_predicates assert "http://example.org/age" in s_predicates
print("✓ Subject queries via processor working") print("✓ Subject queries via processor working")
@ -229,7 +229,7 @@ class TestCassandraIntegration:
# Test P query (find all "knows" relationships) # Test P query (find all "knows" relationships)
p_query = TriplesQueryRequest( p_query = TriplesQueryRequest(
s=None, # None for wildcard s=None, # None for wildcard
p=Value(value="http://example.org/knows", is_uri=True), p=Term(type=IRI, iri="http://example.org/knows"),
o=None, # None for wildcard o=None, # None for wildcard
limit=10, limit=10,
user="testuser", user="testuser",
@ -239,7 +239,7 @@ class TestCassandraIntegration:
print(p_results) print(p_results)
assert len(p_results) == 2 # Alice knows Bob, Bob knows Charlie assert len(p_results) == 2 # Alice knows Bob, Bob knows Charlie
p_subjects = [t.s.value for t in p_results] p_subjects = [t.s.iri for t in p_results]
assert "http://example.org/alice" in p_subjects assert "http://example.org/alice" in p_subjects
assert "http://example.org/bob" in p_subjects assert "http://example.org/bob" in p_subjects
print("✓ Predicate queries via processor working") print("✓ Predicate queries via processor working")
@ -262,19 +262,19 @@ class TestCassandraIntegration:
metadata=Metadata(user="concurrent_test", collection="people"), metadata=Metadata(user="concurrent_test", collection="people"),
triples=[ triples=[
Triple( Triple(
s=Value(value=f"http://example.org/{person_id}", is_uri=True), s=Term(type=IRI, iri=f"http://example.org/{person_id}"),
p=Value(value="http://example.org/name", is_uri=True), p=Term(type=IRI, iri="http://example.org/name"),
o=Value(value=name, is_uri=False) o=Term(type=LITERAL, value=name)
), ),
Triple( Triple(
s=Value(value=f"http://example.org/{person_id}", is_uri=True), s=Term(type=IRI, iri=f"http://example.org/{person_id}"),
p=Value(value="http://example.org/age", is_uri=True), p=Term(type=IRI, iri="http://example.org/age"),
o=Value(value=str(age), is_uri=False) o=Term(type=LITERAL, value=str(age))
), ),
Triple( Triple(
s=Value(value=f"http://example.org/{person_id}", is_uri=True), s=Term(type=IRI, iri=f"http://example.org/{person_id}"),
p=Value(value="http://example.org/department", is_uri=True), p=Term(type=IRI, iri="http://example.org/department"),
o=Value(value=department, is_uri=False) o=Term(type=LITERAL, value=department)
) )
] ]
) )
@ -333,36 +333,36 @@ class TestCassandraIntegration:
triples=[ triples=[
# People and their types # People and their types
Triple( Triple(
s=Value(value="http://company.org/alice", is_uri=True), s=Term(type=IRI, iri="http://company.org/alice"),
p=Value(value="http://www.w3.org/1999/02/22-rdf-syntax-ns#type", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
o=Value(value="http://company.org/Employee", is_uri=True) o=Term(type=IRI, iri="http://company.org/Employee")
), ),
Triple( Triple(
s=Value(value="http://company.org/bob", is_uri=True), s=Term(type=IRI, iri="http://company.org/bob"),
p=Value(value="http://www.w3.org/1999/02/22-rdf-syntax-ns#type", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
o=Value(value="http://company.org/Employee", is_uri=True) o=Term(type=IRI, iri="http://company.org/Employee")
), ),
# Relationships # Relationships
Triple( Triple(
s=Value(value="http://company.org/alice", is_uri=True), s=Term(type=IRI, iri="http://company.org/alice"),
p=Value(value="http://company.org/reportsTo", is_uri=True), p=Term(type=IRI, iri="http://company.org/reportsTo"),
o=Value(value="http://company.org/bob", is_uri=True) o=Term(type=IRI, iri="http://company.org/bob")
), ),
Triple( Triple(
s=Value(value="http://company.org/alice", is_uri=True), s=Term(type=IRI, iri="http://company.org/alice"),
p=Value(value="http://company.org/worksIn", is_uri=True), p=Term(type=IRI, iri="http://company.org/worksIn"),
o=Value(value="http://company.org/engineering", is_uri=True) o=Term(type=IRI, iri="http://company.org/engineering")
), ),
# Personal info # Personal info
Triple( Triple(
s=Value(value="http://company.org/alice", is_uri=True), s=Term(type=IRI, iri="http://company.org/alice"),
p=Value(value="http://company.org/fullName", is_uri=True), p=Term(type=IRI, iri="http://company.org/fullName"),
o=Value(value="Alice Johnson", is_uri=False) o=Term(type=LITERAL, value="Alice Johnson")
), ),
Triple( Triple(
s=Value(value="http://company.org/alice", is_uri=True), s=Term(type=IRI, iri="http://company.org/alice"),
p=Value(value="http://company.org/email", is_uri=True), p=Term(type=IRI, iri="http://company.org/email"),
o=Value(value="alice@company.org", is_uri=False) o=Term(type=LITERAL, value="alice@company.org")
), ),
] ]
) )

View file

@ -15,7 +15,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
from trustgraph.extract.kg.definitions.extract import Processor as DefinitionsProcessor from trustgraph.extract.kg.definitions.extract import Processor as DefinitionsProcessor
from trustgraph.extract.kg.relationships.extract import Processor as RelationshipsProcessor from trustgraph.extract.kg.relationships.extract import Processor as RelationshipsProcessor
from trustgraph.storage.knowledge.store import Processor as KnowledgeStoreProcessor from trustgraph.storage.knowledge.store import Processor as KnowledgeStoreProcessor
from trustgraph.schema import Chunk, Triple, Triples, Metadata, Value, Error from trustgraph.schema import Chunk, Triple, Triples, Metadata, Term, Error, IRI, LITERAL
from trustgraph.schema import EntityContext, EntityContexts, GraphEmbeddings from trustgraph.schema import EntityContext, EntityContexts, GraphEmbeddings
from trustgraph.rdf import TRUSTGRAPH_ENTITIES, DEFINITION, RDF_LABEL, SUBJECT_OF from trustgraph.rdf import TRUSTGRAPH_ENTITIES, DEFINITION, RDF_LABEL, SUBJECT_OF
@ -253,24 +253,24 @@ class TestKnowledgeGraphPipelineIntegration:
if s and o: if s and o:
s_uri = definitions_processor.to_uri(s) s_uri = definitions_processor.to_uri(s)
s_value = Value(value=str(s_uri), is_uri=True) s_term = Term(type=IRI, iri=str(s_uri))
o_value = Value(value=str(o), is_uri=False) o_term = Term(type=LITERAL, value=str(o))
# Generate triples as the processor would # Generate triples as the processor would
triples.append(Triple( triples.append(Triple(
s=s_value, s=s_term,
p=Value(value=RDF_LABEL, is_uri=True), p=Term(type=IRI, iri=RDF_LABEL),
o=Value(value=s, is_uri=False) o=Term(type=LITERAL, value=s)
)) ))
triples.append(Triple( triples.append(Triple(
s=s_value, s=s_term,
p=Value(value=DEFINITION, is_uri=True), p=Term(type=IRI, iri=DEFINITION),
o=o_value o=o_term
)) ))
entities.append(EntityContext( entities.append(EntityContext(
entity=s_value, entity=s_term,
context=defn["definition"] context=defn["definition"]
)) ))
@ -279,16 +279,16 @@ class TestKnowledgeGraphPipelineIntegration:
assert len(entities) == 3 # 1 entity context per entity assert len(entities) == 3 # 1 entity context per entity
# Verify triple structure # Verify triple structure
label_triples = [t for t in triples if t.p.value == RDF_LABEL] label_triples = [t for t in triples if t.p.iri == RDF_LABEL]
definition_triples = [t for t in triples if t.p.value == DEFINITION] definition_triples = [t for t in triples if t.p.iri == DEFINITION]
assert len(label_triples) == 3 assert len(label_triples) == 3
assert len(definition_triples) == 3 assert len(definition_triples) == 3
# Verify entity contexts # Verify entity contexts
for entity in entities: for entity in entities:
assert entity.entity.is_uri is True assert entity.entity.type == IRI
assert entity.entity.value.startswith(TRUSTGRAPH_ENTITIES) assert entity.entity.iri.startswith(TRUSTGRAPH_ENTITIES)
assert len(entity.context) > 0 assert len(entity.context) > 0
@pytest.mark.asyncio @pytest.mark.asyncio
@ -312,49 +312,49 @@ class TestKnowledgeGraphPipelineIntegration:
if s and p and o: if s and p and o:
s_uri = relationships_processor.to_uri(s) s_uri = relationships_processor.to_uri(s)
s_value = Value(value=str(s_uri), is_uri=True) s_term = Term(type=IRI, iri=str(s_uri))
p_uri = relationships_processor.to_uri(p) p_uri = relationships_processor.to_uri(p)
p_value = Value(value=str(p_uri), is_uri=True) p_term = Term(type=IRI, iri=str(p_uri))
if rel["object-entity"]: if rel["object-entity"]:
o_uri = relationships_processor.to_uri(o) o_uri = relationships_processor.to_uri(o)
o_value = Value(value=str(o_uri), is_uri=True) o_term = Term(type=IRI, iri=str(o_uri))
else: else:
o_value = Value(value=str(o), is_uri=False) o_term = Term(type=LITERAL, value=str(o))
# Main relationship triple # Main relationship triple
triples.append(Triple(s=s_value, p=p_value, o=o_value)) triples.append(Triple(s=s_term, p=p_term, o=o_term))
# Label triples # Label triples
triples.append(Triple( triples.append(Triple(
s=s_value, s=s_term,
p=Value(value=RDF_LABEL, is_uri=True), p=Term(type=IRI, iri=RDF_LABEL),
o=Value(value=str(s), is_uri=False) o=Term(type=LITERAL, value=str(s))
)) ))
triples.append(Triple( triples.append(Triple(
s=p_value, s=p_term,
p=Value(value=RDF_LABEL, is_uri=True), p=Term(type=IRI, iri=RDF_LABEL),
o=Value(value=str(p), is_uri=False) o=Term(type=LITERAL, value=str(p))
)) ))
if rel["object-entity"]: if rel["object-entity"]:
triples.append(Triple( triples.append(Triple(
s=o_value, s=o_term,
p=Value(value=RDF_LABEL, is_uri=True), p=Term(type=IRI, iri=RDF_LABEL),
o=Value(value=str(o), is_uri=False) o=Term(type=LITERAL, value=str(o))
)) ))
# Assert # Assert
assert len(triples) > 0 assert len(triples) > 0
# Verify relationship triples exist # Verify relationship triples exist
relationship_triples = [t for t in triples if t.p.value.endswith("is_subset_of") or t.p.value.endswith("is_used_in")] relationship_triples = [t for t in triples if t.p.iri.endswith("is_subset_of") or t.p.iri.endswith("is_used_in")]
assert len(relationship_triples) >= 2 assert len(relationship_triples) >= 2
# Verify label triples # Verify label triples
label_triples = [t for t in triples if t.p.value == RDF_LABEL] label_triples = [t for t in triples if t.p.iri == RDF_LABEL]
assert len(label_triples) > 0 assert len(label_triples) > 0
@pytest.mark.asyncio @pytest.mark.asyncio
@ -374,9 +374,9 @@ class TestKnowledgeGraphPipelineIntegration:
), ),
triples=[ triples=[
Triple( Triple(
s=Value(value="http://trustgraph.ai/e/machine-learning", is_uri=True), s=Term(type=IRI, iri="http://trustgraph.ai/e/machine-learning"),
p=Value(value=DEFINITION, is_uri=True), p=Term(type=IRI, iri=DEFINITION),
o=Value(value="A subset of AI", is_uri=False) o=Term(type=LITERAL, value="A subset of AI")
) )
] ]
) )
@ -602,9 +602,9 @@ class TestKnowledgeGraphPipelineIntegration:
collection="test_collection", collection="test_collection",
metadata=[ metadata=[
Triple( Triple(
s=Value(value="doc:test", is_uri=True), s=Term(type=IRI, iri="doc:test"),
p=Value(value="dc:title", is_uri=True), p=Term(type=IRI, iri="dc:title"),
o=Value(value="Test Document", is_uri=False) o=Term(type=LITERAL, value="Test Document")
) )
] ]
) )

View file

@ -7,7 +7,7 @@ collecting labels and definitions for entity embedding and retrieval.
import pytest import pytest
from trustgraph.extract.kg.ontology.extract import Processor from trustgraph.extract.kg.ontology.extract import Processor
from trustgraph.schema.core.primitives import Triple, Value from trustgraph.schema.core.primitives import Triple, Term, IRI, LITERAL
from trustgraph.schema.knowledge.graph import EntityContext from trustgraph.schema.knowledge.graph import EntityContext
@ -25,9 +25,9 @@ class TestEntityContextBuilding:
"""Test that entity context is built from rdfs:label.""" """Test that entity context is built from rdfs:label."""
triples = [ triples = [
Triple( Triple(
s=Value(value="https://example.com/entity/cornish-pasty", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/cornish-pasty"),
p=Value(value="http://www.w3.org/2000/01/rdf-schema#label", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2000/01/rdf-schema#label"),
o=Value(value="Cornish Pasty", is_uri=False) o=Term(type=LITERAL, value="Cornish Pasty")
) )
] ]
@ -35,16 +35,16 @@ class TestEntityContextBuilding:
assert len(contexts) == 1, "Should create one entity context" assert len(contexts) == 1, "Should create one entity context"
assert isinstance(contexts[0], EntityContext) assert isinstance(contexts[0], EntityContext)
assert contexts[0].entity.value == "https://example.com/entity/cornish-pasty" assert contexts[0].entity.iri == "https://example.com/entity/cornish-pasty"
assert "Label: Cornish Pasty" in contexts[0].context assert "Label: Cornish Pasty" in contexts[0].context
def test_builds_context_from_definition(self, processor): def test_builds_context_from_definition(self, processor):
"""Test that entity context includes definitions.""" """Test that entity context includes definitions."""
triples = [ triples = [
Triple( Triple(
s=Value(value="https://example.com/entity/pasty", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/pasty"),
p=Value(value="http://www.w3.org/2004/02/skos/core#definition", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2004/02/skos/core#definition"),
o=Value(value="A baked pastry filled with savory ingredients", is_uri=False) o=Term(type=LITERAL, value="A baked pastry filled with savory ingredients")
) )
] ]
@ -57,14 +57,14 @@ class TestEntityContextBuilding:
"""Test that label and definition are combined in context.""" """Test that label and definition are combined in context."""
triples = [ triples = [
Triple( Triple(
s=Value(value="https://example.com/entity/recipe1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/recipe1"),
p=Value(value="http://www.w3.org/2000/01/rdf-schema#label", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2000/01/rdf-schema#label"),
o=Value(value="Pasty Recipe", is_uri=False) o=Term(type=LITERAL, value="Pasty Recipe")
), ),
Triple( Triple(
s=Value(value="https://example.com/entity/recipe1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/recipe1"),
p=Value(value="http://www.w3.org/2004/02/skos/core#definition", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2004/02/skos/core#definition"),
o=Value(value="Traditional Cornish pastry recipe", is_uri=False) o=Term(type=LITERAL, value="Traditional Cornish pastry recipe")
) )
] ]
@ -80,14 +80,14 @@ class TestEntityContextBuilding:
"""Test that only the first label is used in context.""" """Test that only the first label is used in context."""
triples = [ triples = [
Triple( Triple(
s=Value(value="https://example.com/entity/food1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/food1"),
p=Value(value="http://www.w3.org/2000/01/rdf-schema#label", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2000/01/rdf-schema#label"),
o=Value(value="First Label", is_uri=False) o=Term(type=LITERAL, value="First Label")
), ),
Triple( Triple(
s=Value(value="https://example.com/entity/food1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/food1"),
p=Value(value="http://www.w3.org/2000/01/rdf-schema#label", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2000/01/rdf-schema#label"),
o=Value(value="Second Label", is_uri=False) o=Term(type=LITERAL, value="Second Label")
) )
] ]
@ -101,14 +101,14 @@ class TestEntityContextBuilding:
"""Test that all definitions are included in context.""" """Test that all definitions are included in context."""
triples = [ triples = [
Triple( Triple(
s=Value(value="https://example.com/entity/food1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/food1"),
p=Value(value="http://www.w3.org/2004/02/skos/core#definition", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2004/02/skos/core#definition"),
o=Value(value="First definition", is_uri=False) o=Term(type=LITERAL, value="First definition")
), ),
Triple( Triple(
s=Value(value="https://example.com/entity/food1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/food1"),
p=Value(value="http://www.w3.org/2004/02/skos/core#definition", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2004/02/skos/core#definition"),
o=Value(value="Second definition", is_uri=False) o=Term(type=LITERAL, value="Second definition")
) )
] ]
@ -123,9 +123,9 @@ class TestEntityContextBuilding:
"""Test that schema.org description is treated as definition.""" """Test that schema.org description is treated as definition."""
triples = [ triples = [
Triple( Triple(
s=Value(value="https://example.com/entity/food1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/food1"),
p=Value(value="https://schema.org/description", is_uri=True), p=Term(type=IRI, iri="https://schema.org/description"),
o=Value(value="A delicious food item", is_uri=False) o=Term(type=LITERAL, value="A delicious food item")
) )
] ]
@ -138,26 +138,26 @@ class TestEntityContextBuilding:
"""Test that contexts are created for multiple entities.""" """Test that contexts are created for multiple entities."""
triples = [ triples = [
Triple( Triple(
s=Value(value="https://example.com/entity/entity1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/entity1"),
p=Value(value="http://www.w3.org/2000/01/rdf-schema#label", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2000/01/rdf-schema#label"),
o=Value(value="Entity One", is_uri=False) o=Term(type=LITERAL, value="Entity One")
), ),
Triple( Triple(
s=Value(value="https://example.com/entity/entity2", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/entity2"),
p=Value(value="http://www.w3.org/2000/01/rdf-schema#label", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2000/01/rdf-schema#label"),
o=Value(value="Entity Two", is_uri=False) o=Term(type=LITERAL, value="Entity Two")
), ),
Triple( Triple(
s=Value(value="https://example.com/entity/entity3", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/entity3"),
p=Value(value="http://www.w3.org/2000/01/rdf-schema#label", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2000/01/rdf-schema#label"),
o=Value(value="Entity Three", is_uri=False) o=Term(type=LITERAL, value="Entity Three")
) )
] ]
contexts = processor.build_entity_contexts(triples) contexts = processor.build_entity_contexts(triples)
assert len(contexts) == 3, "Should create context for each entity" assert len(contexts) == 3, "Should create context for each entity"
entity_uris = [ctx.entity.value for ctx in contexts] entity_uris = [ctx.entity.iri for ctx in contexts]
assert "https://example.com/entity/entity1" in entity_uris assert "https://example.com/entity/entity1" in entity_uris
assert "https://example.com/entity/entity2" in entity_uris assert "https://example.com/entity/entity2" in entity_uris
assert "https://example.com/entity/entity3" in entity_uris assert "https://example.com/entity/entity3" in entity_uris
@ -166,9 +166,9 @@ class TestEntityContextBuilding:
"""Test that URI objects are ignored (only literal labels/definitions).""" """Test that URI objects are ignored (only literal labels/definitions)."""
triples = [ triples = [
Triple( Triple(
s=Value(value="https://example.com/entity/food1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/food1"),
p=Value(value="http://www.w3.org/2000/01/rdf-schema#label", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2000/01/rdf-schema#label"),
o=Value(value="https://example.com/some/uri", is_uri=True) # URI, not literal o=Term(type=IRI, iri="https://example.com/some/uri") # URI, not literal
) )
] ]
@ -181,14 +181,14 @@ class TestEntityContextBuilding:
"""Test that other predicates are ignored.""" """Test that other predicates are ignored."""
triples = [ triples = [
Triple( Triple(
s=Value(value="https://example.com/entity/food1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/food1"),
p=Value(value="http://www.w3.org/1999/02/22-rdf-syntax-ns#type", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
o=Value(value="http://example.com/Food", is_uri=True) o=Term(type=IRI, iri="http://example.com/Food")
), ),
Triple( Triple(
s=Value(value="https://example.com/entity/food1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/food1"),
p=Value(value="http://example.com/produces", is_uri=True), p=Term(type=IRI, iri="http://example.com/produces"),
o=Value(value="https://example.com/entity/food2", is_uri=True) o=Term(type=IRI, iri="https://example.com/entity/food2")
) )
] ]
@ -205,29 +205,29 @@ class TestEntityContextBuilding:
assert len(contexts) == 0, "Empty triple list should return empty contexts" assert len(contexts) == 0, "Empty triple list should return empty contexts"
def test_entity_context_has_value_object(self, processor): def test_entity_context_has_term_object(self, processor):
"""Test that EntityContext.entity is a Value object.""" """Test that EntityContext.entity is a Term object."""
triples = [ triples = [
Triple( Triple(
s=Value(value="https://example.com/entity/test", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/test"),
p=Value(value="http://www.w3.org/2000/01/rdf-schema#label", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2000/01/rdf-schema#label"),
o=Value(value="Test Entity", is_uri=False) o=Term(type=LITERAL, value="Test Entity")
) )
] ]
contexts = processor.build_entity_contexts(triples) contexts = processor.build_entity_contexts(triples)
assert len(contexts) == 1 assert len(contexts) == 1
assert isinstance(contexts[0].entity, Value), "Entity should be Value object" assert isinstance(contexts[0].entity, Term), "Entity should be Term object"
assert contexts[0].entity.is_uri, "Entity should be marked as URI" assert contexts[0].entity.type == IRI, "Entity should be IRI type"
def test_entity_context_text_is_string(self, processor): def test_entity_context_text_is_string(self, processor):
"""Test that EntityContext.context is a string.""" """Test that EntityContext.context is a string."""
triples = [ triples = [
Triple( Triple(
s=Value(value="https://example.com/entity/test", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/test"),
p=Value(value="http://www.w3.org/2000/01/rdf-schema#label", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2000/01/rdf-schema#label"),
o=Value(value="Test Entity", is_uri=False) o=Term(type=LITERAL, value="Test Entity")
) )
] ]
@ -241,22 +241,22 @@ class TestEntityContextBuilding:
triples = [ triples = [
# Entity with label - should create context # Entity with label - should create context
Triple( Triple(
s=Value(value="https://example.com/entity/entity1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/entity1"),
p=Value(value="http://www.w3.org/2000/01/rdf-schema#label", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2000/01/rdf-schema#label"),
o=Value(value="Entity One", is_uri=False) o=Term(type=LITERAL, value="Entity One")
), ),
# Entity with only rdf:type - should NOT create context # Entity with only rdf:type - should NOT create context
Triple( Triple(
s=Value(value="https://example.com/entity/entity2", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/entity2"),
p=Value(value="http://www.w3.org/1999/02/22-rdf-syntax-ns#type", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
o=Value(value="http://example.com/Food", is_uri=True) o=Term(type=IRI, iri="http://example.com/Food")
) )
] ]
contexts = processor.build_entity_contexts(triples) contexts = processor.build_entity_contexts(triples)
assert len(contexts) == 1, "Should only create context for entity with label/definition" assert len(contexts) == 1, "Should only create context for entity with label/definition"
assert contexts[0].entity.value == "https://example.com/entity/entity1" assert contexts[0].entity.iri == "https://example.com/entity/entity1"
class TestEntityContextEdgeCases: class TestEntityContextEdgeCases:
@ -266,9 +266,9 @@ class TestEntityContextEdgeCases:
"""Test handling of unicode characters in labels.""" """Test handling of unicode characters in labels."""
triples = [ triples = [
Triple( Triple(
s=Value(value="https://example.com/entity/café", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/café"),
p=Value(value="http://www.w3.org/2000/01/rdf-schema#label", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2000/01/rdf-schema#label"),
o=Value(value="Café Spécial", is_uri=False) o=Term(type=LITERAL, value="Café Spécial")
) )
] ]
@ -282,9 +282,9 @@ class TestEntityContextEdgeCases:
long_def = "This is a very long definition " * 50 long_def = "This is a very long definition " * 50
triples = [ triples = [
Triple( Triple(
s=Value(value="https://example.com/entity/test", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/test"),
p=Value(value="http://www.w3.org/2004/02/skos/core#definition", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2004/02/skos/core#definition"),
o=Value(value=long_def, is_uri=False) o=Term(type=LITERAL, value=long_def)
) )
] ]
@ -297,9 +297,9 @@ class TestEntityContextEdgeCases:
"""Test handling of special characters in context text.""" """Test handling of special characters in context text."""
triples = [ triples = [
Triple( Triple(
s=Value(value="https://example.com/entity/test", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/test"),
p=Value(value="http://www.w3.org/2000/01/rdf-schema#label", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2000/01/rdf-schema#label"),
o=Value(value="Test & Entity <with> \"quotes\"", is_uri=False) o=Term(type=LITERAL, value="Test & Entity <with> \"quotes\"")
) )
] ]
@ -313,27 +313,27 @@ class TestEntityContextEdgeCases:
triples = [ triples = [
# Label - relevant # Label - relevant
Triple( Triple(
s=Value(value="https://example.com/entity/recipe1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/recipe1"),
p=Value(value="http://www.w3.org/2000/01/rdf-schema#label", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2000/01/rdf-schema#label"),
o=Value(value="Cornish Pasty Recipe", is_uri=False) o=Term(type=LITERAL, value="Cornish Pasty Recipe")
), ),
# Type - irrelevant # Type - irrelevant
Triple( Triple(
s=Value(value="https://example.com/entity/recipe1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/recipe1"),
p=Value(value="http://www.w3.org/1999/02/22-rdf-syntax-ns#type", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
o=Value(value="http://example.com/Recipe", is_uri=True) o=Term(type=IRI, iri="http://example.com/Recipe")
), ),
# Property - irrelevant # Property - irrelevant
Triple( Triple(
s=Value(value="https://example.com/entity/recipe1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/recipe1"),
p=Value(value="http://example.com/produces", is_uri=True), p=Term(type=IRI, iri="http://example.com/produces"),
o=Value(value="https://example.com/entity/pasty", is_uri=True) o=Term(type=IRI, iri="https://example.com/entity/pasty")
), ),
# Definition - relevant # Definition - relevant
Triple( Triple(
s=Value(value="https://example.com/entity/recipe1", is_uri=True), s=Term(type=IRI, iri="https://example.com/entity/recipe1"),
p=Value(value="http://www.w3.org/2004/02/skos/core#definition", is_uri=True), p=Term(type=IRI, iri="http://www.w3.org/2004/02/skos/core#definition"),
o=Value(value="Traditional British pastry recipe", is_uri=False) o=Term(type=LITERAL, value="Traditional British pastry recipe")
) )
] ]

View file

@ -9,7 +9,7 @@ the knowledge graph.
import pytest import pytest
from trustgraph.extract.kg.ontology.extract import Processor from trustgraph.extract.kg.ontology.extract import Processor
from trustgraph.extract.kg.ontology.ontology_selector import OntologySubset from trustgraph.extract.kg.ontology.ontology_selector import OntologySubset
from trustgraph.schema.core.primitives import Triple, Value from trustgraph.schema.core.primitives import Triple, Term, IRI, LITERAL
@pytest.fixture @pytest.fixture
@ -92,12 +92,12 @@ class TestOntologyTripleGeneration:
# Find type triples for Recipe class # Find type triples for Recipe class
recipe_type_triples = [ recipe_type_triples = [
t for t in triples t for t in triples
if t.s.value == "http://purl.org/ontology/fo/Recipe" if t.s.iri == "http://purl.org/ontology/fo/Recipe"
and t.p.value == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" and t.p.iri == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
] ]
assert len(recipe_type_triples) == 1, "Should generate exactly one type triple per class" assert len(recipe_type_triples) == 1, "Should generate exactly one type triple per class"
assert recipe_type_triples[0].o.value == "http://www.w3.org/2002/07/owl#Class", \ assert recipe_type_triples[0].o.iri == "http://www.w3.org/2002/07/owl#Class", \
"Class type should be owl:Class" "Class type should be owl:Class"
def test_generates_class_labels(self, extractor, sample_ontology_subset): def test_generates_class_labels(self, extractor, sample_ontology_subset):
@ -107,14 +107,14 @@ class TestOntologyTripleGeneration:
# Find label triples for Recipe class # Find label triples for Recipe class
recipe_label_triples = [ recipe_label_triples = [
t for t in triples t for t in triples
if t.s.value == "http://purl.org/ontology/fo/Recipe" if t.s.iri == "http://purl.org/ontology/fo/Recipe"
and t.p.value == "http://www.w3.org/2000/01/rdf-schema#label" and t.p.iri == "http://www.w3.org/2000/01/rdf-schema#label"
] ]
assert len(recipe_label_triples) == 1, "Should generate label triple for class" assert len(recipe_label_triples) == 1, "Should generate label triple for class"
assert recipe_label_triples[0].o.value == "Recipe", \ assert recipe_label_triples[0].o.value == "Recipe", \
"Label should match class label from ontology" "Label should match class label from ontology"
assert not recipe_label_triples[0].o.is_uri, \ assert recipe_label_triples[0].o.type == LITERAL, \
"Label should be a literal, not URI" "Label should be a literal, not URI"
def test_generates_class_comments(self, extractor, sample_ontology_subset): def test_generates_class_comments(self, extractor, sample_ontology_subset):
@ -124,8 +124,8 @@ class TestOntologyTripleGeneration:
# Find comment triples for Recipe class # Find comment triples for Recipe class
recipe_comment_triples = [ recipe_comment_triples = [
t for t in triples t for t in triples
if t.s.value == "http://purl.org/ontology/fo/Recipe" if t.s.iri == "http://purl.org/ontology/fo/Recipe"
and t.p.value == "http://www.w3.org/2000/01/rdf-schema#comment" and t.p.iri == "http://www.w3.org/2000/01/rdf-schema#comment"
] ]
assert len(recipe_comment_triples) == 1, "Should generate comment triple for class" assert len(recipe_comment_triples) == 1, "Should generate comment triple for class"
@ -139,13 +139,13 @@ class TestOntologyTripleGeneration:
# Find type triples for ingredients property # Find type triples for ingredients property
ingredients_type_triples = [ ingredients_type_triples = [
t for t in triples t for t in triples
if t.s.value == "http://purl.org/ontology/fo/ingredients" if t.s.iri == "http://purl.org/ontology/fo/ingredients"
and t.p.value == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" and t.p.iri == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
] ]
assert len(ingredients_type_triples) == 1, \ assert len(ingredients_type_triples) == 1, \
"Should generate exactly one type triple per object property" "Should generate exactly one type triple per object property"
assert ingredients_type_triples[0].o.value == "http://www.w3.org/2002/07/owl#ObjectProperty", \ assert ingredients_type_triples[0].o.iri == "http://www.w3.org/2002/07/owl#ObjectProperty", \
"Object property type should be owl:ObjectProperty" "Object property type should be owl:ObjectProperty"
def test_generates_object_property_labels(self, extractor, sample_ontology_subset): def test_generates_object_property_labels(self, extractor, sample_ontology_subset):
@ -155,8 +155,8 @@ class TestOntologyTripleGeneration:
# Find label triples for ingredients property # Find label triples for ingredients property
ingredients_label_triples = [ ingredients_label_triples = [
t for t in triples t for t in triples
if t.s.value == "http://purl.org/ontology/fo/ingredients" if t.s.iri == "http://purl.org/ontology/fo/ingredients"
and t.p.value == "http://www.w3.org/2000/01/rdf-schema#label" and t.p.iri == "http://www.w3.org/2000/01/rdf-schema#label"
] ]
assert len(ingredients_label_triples) == 1, \ assert len(ingredients_label_triples) == 1, \
@ -171,15 +171,15 @@ class TestOntologyTripleGeneration:
# Find domain triples for ingredients property # Find domain triples for ingredients property
ingredients_domain_triples = [ ingredients_domain_triples = [
t for t in triples t for t in triples
if t.s.value == "http://purl.org/ontology/fo/ingredients" if t.s.iri == "http://purl.org/ontology/fo/ingredients"
and t.p.value == "http://www.w3.org/2000/01/rdf-schema#domain" and t.p.iri == "http://www.w3.org/2000/01/rdf-schema#domain"
] ]
assert len(ingredients_domain_triples) == 1, \ assert len(ingredients_domain_triples) == 1, \
"Should generate domain triple for object property" "Should generate domain triple for object property"
assert ingredients_domain_triples[0].o.value == "http://purl.org/ontology/fo/Recipe", \ assert ingredients_domain_triples[0].o.iri == "http://purl.org/ontology/fo/Recipe", \
"Domain should be Recipe class URI" "Domain should be Recipe class URI"
assert ingredients_domain_triples[0].o.is_uri, \ assert ingredients_domain_triples[0].o.type == IRI, \
"Domain should be a URI reference" "Domain should be a URI reference"
def test_generates_object_property_range(self, extractor, sample_ontology_subset): def test_generates_object_property_range(self, extractor, sample_ontology_subset):
@ -189,13 +189,13 @@ class TestOntologyTripleGeneration:
# Find range triples for produces property # Find range triples for produces property
produces_range_triples = [ produces_range_triples = [
t for t in triples t for t in triples
if t.s.value == "http://purl.org/ontology/fo/produces" if t.s.iri == "http://purl.org/ontology/fo/produces"
and t.p.value == "http://www.w3.org/2000/01/rdf-schema#range" and t.p.iri == "http://www.w3.org/2000/01/rdf-schema#range"
] ]
assert len(produces_range_triples) == 1, \ assert len(produces_range_triples) == 1, \
"Should generate range triple for object property" "Should generate range triple for object property"
assert produces_range_triples[0].o.value == "http://purl.org/ontology/fo/Food", \ assert produces_range_triples[0].o.iri == "http://purl.org/ontology/fo/Food", \
"Range should be Food class URI" "Range should be Food class URI"
def test_generates_datatype_property_type_triples(self, extractor, sample_ontology_subset): def test_generates_datatype_property_type_triples(self, extractor, sample_ontology_subset):
@ -205,13 +205,13 @@ class TestOntologyTripleGeneration:
# Find type triples for serves property # Find type triples for serves property
serves_type_triples = [ serves_type_triples = [
t for t in triples t for t in triples
if t.s.value == "http://purl.org/ontology/fo/serves" if t.s.iri == "http://purl.org/ontology/fo/serves"
and t.p.value == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" and t.p.iri == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
] ]
assert len(serves_type_triples) == 1, \ assert len(serves_type_triples) == 1, \
"Should generate exactly one type triple per datatype property" "Should generate exactly one type triple per datatype property"
assert serves_type_triples[0].o.value == "http://www.w3.org/2002/07/owl#DatatypeProperty", \ assert serves_type_triples[0].o.iri == "http://www.w3.org/2002/07/owl#DatatypeProperty", \
"Datatype property type should be owl:DatatypeProperty" "Datatype property type should be owl:DatatypeProperty"
def test_generates_datatype_property_range(self, extractor, sample_ontology_subset): def test_generates_datatype_property_range(self, extractor, sample_ontology_subset):
@ -221,8 +221,8 @@ class TestOntologyTripleGeneration:
# Find range triples for serves property # Find range triples for serves property
serves_range_triples = [ serves_range_triples = [
t for t in triples t for t in triples
if t.s.value == "http://purl.org/ontology/fo/serves" if t.s.iri == "http://purl.org/ontology/fo/serves"
and t.p.value == "http://www.w3.org/2000/01/rdf-schema#range" and t.p.iri == "http://www.w3.org/2000/01/rdf-schema#range"
] ]
assert len(serves_range_triples) == 1, \ assert len(serves_range_triples) == 1, \
@ -236,8 +236,8 @@ class TestOntologyTripleGeneration:
# Count unique class subjects # Count unique class subjects
class_subjects = set( class_subjects = set(
t.s.value for t in triples t.s.iri for t in triples
if t.p.value == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" if t.p.iri == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
and t.o.value == "http://www.w3.org/2002/07/owl#Class" and t.o.value == "http://www.w3.org/2002/07/owl#Class"
) )
@ -250,8 +250,8 @@ class TestOntologyTripleGeneration:
# Count unique property subjects (object + datatype properties) # Count unique property subjects (object + datatype properties)
property_subjects = set( property_subjects = set(
t.s.value for t in triples t.s.iri for t in triples
if t.p.value == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" if t.p.iri == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
and ("ObjectProperty" in t.o.value or "DatatypeProperty" in t.o.value) and ("ObjectProperty" in t.o.value or "DatatypeProperty" in t.o.value)
) )
@ -276,7 +276,7 @@ class TestOntologyTripleGeneration:
# Should still generate proper RDF triples despite dict field names # Should still generate proper RDF triples despite dict field names
label_triples = [ label_triples = [
t for t in triples t for t in triples
if t.p.value == "http://www.w3.org/2000/01/rdf-schema#label" if t.p.iri == "http://www.w3.org/2000/01/rdf-schema#label"
] ]
assert len(label_triples) > 0, \ assert len(label_triples) > 0, \
"Should generate rdfs:label triples from dict 'labels' field" "Should generate rdfs:label triples from dict 'labels' field"

View file

@ -6,11 +6,21 @@ import pytest
from unittest.mock import Mock, AsyncMock from unittest.mock import Mock, AsyncMock
# Mock schema classes for testing # Mock schema classes for testing
class Value: # Term type constants
def __init__(self, value, is_uri, type): IRI = "i"
self.value = value LITERAL = "l"
self.is_uri = is_uri BLANK = "b"
TRIPLE = "t"
class Term:
def __init__(self, type, iri=None, value=None, id=None, datatype=None, language=None, triple=None):
self.type = type self.type = type
self.iri = iri
self.value = value
self.id = id
self.datatype = datatype
self.language = language
self.triple = triple
class Triple: class Triple:
def __init__(self, s, p, o): def __init__(self, s, p, o):
@ -66,32 +76,30 @@ def sample_relationships():
@pytest.fixture @pytest.fixture
def sample_value_uri(): def sample_term_uri():
"""Sample URI Value object""" """Sample URI Term object"""
return Value( return Term(
value="http://example.com/person/john-smith", type=IRI,
is_uri=True, iri="http://example.com/person/john-smith"
type=""
) )
@pytest.fixture @pytest.fixture
def sample_value_literal(): def sample_term_literal():
"""Sample literal Value object""" """Sample literal Term object"""
return Value( return Term(
value="John Smith", type=LITERAL,
is_uri=False, value="John Smith"
type="string"
) )
@pytest.fixture @pytest.fixture
def sample_triple(sample_value_uri, sample_value_literal): def sample_triple(sample_term_uri, sample_term_literal):
"""Sample Triple object""" """Sample Triple object"""
return Triple( return Triple(
s=sample_value_uri, s=sample_term_uri,
p=Value(value="http://schema.org/name", is_uri=True, type=""), p=Term(type=IRI, iri="http://schema.org/name"),
o=sample_value_literal o=sample_term_literal
) )

View file

@ -11,7 +11,7 @@ import json
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock, patch
from trustgraph.extract.kg.agent.extract import Processor as AgentKgExtractor from trustgraph.extract.kg.agent.extract import Processor as AgentKgExtractor
from trustgraph.schema import Chunk, Triple, Triples, Metadata, Value, Error from trustgraph.schema import Chunk, Triple, Triples, Metadata, Term, Error, IRI, LITERAL
from trustgraph.schema import EntityContext, EntityContexts from trustgraph.schema import EntityContext, EntityContexts
from trustgraph.rdf import TRUSTGRAPH_ENTITIES, DEFINITION, RDF_LABEL, SUBJECT_OF from trustgraph.rdf import TRUSTGRAPH_ENTITIES, DEFINITION, RDF_LABEL, SUBJECT_OF
from trustgraph.template.prompt_manager import PromptManager from trustgraph.template.prompt_manager import PromptManager
@ -53,9 +53,9 @@ class TestAgentKgExtractor:
id="doc123", id="doc123",
metadata=[ metadata=[
Triple( Triple(
s=Value(value="doc123", is_uri=True), s=Term(type=IRI, iri="doc123"),
p=Value(value="http://example.org/type", is_uri=True), p=Term(type=IRI, iri="http://example.org/type"),
o=Value(value="document", is_uri=False) o=Term(type=LITERAL, value="document")
) )
] ]
) )
@ -178,27 +178,27 @@ This is not JSON at all
triples, entity_contexts = agent_extractor.process_extraction_data(data, sample_metadata) triples, entity_contexts = agent_extractor.process_extraction_data(data, sample_metadata)
# Check entity label triple # Check entity label triple
label_triple = next((t for t in triples if t.p.value == RDF_LABEL and t.o.value == "Machine Learning"), None) label_triple = next((t for t in triples if t.p.iri == RDF_LABEL and t.o.value == "Machine Learning"), None)
assert label_triple is not None assert label_triple is not None
assert label_triple.s.value == f"{TRUSTGRAPH_ENTITIES}Machine%20Learning" assert label_triple.s.iri == f"{TRUSTGRAPH_ENTITIES}Machine%20Learning"
assert label_triple.s.is_uri == True assert label_triple.s.type == IRI
assert label_triple.o.is_uri == False assert label_triple.o.type == LITERAL
# Check definition triple # Check definition triple
def_triple = next((t for t in triples if t.p.value == DEFINITION), None) def_triple = next((t for t in triples if t.p.iri == DEFINITION), None)
assert def_triple is not None assert def_triple is not None
assert def_triple.s.value == f"{TRUSTGRAPH_ENTITIES}Machine%20Learning" assert def_triple.s.iri == f"{TRUSTGRAPH_ENTITIES}Machine%20Learning"
assert def_triple.o.value == "A subset of AI that enables learning from data." assert def_triple.o.value == "A subset of AI that enables learning from data."
# Check subject-of triple # Check subject-of triple
subject_of_triple = next((t for t in triples if t.p.value == SUBJECT_OF), None) subject_of_triple = next((t for t in triples if t.p.iri == SUBJECT_OF), None)
assert subject_of_triple is not None assert subject_of_triple is not None
assert subject_of_triple.s.value == f"{TRUSTGRAPH_ENTITIES}Machine%20Learning" assert subject_of_triple.s.iri == f"{TRUSTGRAPH_ENTITIES}Machine%20Learning"
assert subject_of_triple.o.value == "doc123" assert subject_of_triple.o.iri == "doc123"
# Check entity context # Check entity context
assert len(entity_contexts) == 1 assert len(entity_contexts) == 1
assert entity_contexts[0].entity.value == f"{TRUSTGRAPH_ENTITIES}Machine%20Learning" assert entity_contexts[0].entity.iri == f"{TRUSTGRAPH_ENTITIES}Machine%20Learning"
assert entity_contexts[0].context == "A subset of AI that enables learning from data." assert entity_contexts[0].context == "A subset of AI that enables learning from data."
def test_process_extraction_data_relationships(self, agent_extractor, sample_metadata): def test_process_extraction_data_relationships(self, agent_extractor, sample_metadata):
@ -220,23 +220,23 @@ This is not JSON at all
predicate_uri = f"{TRUSTGRAPH_ENTITIES}is_subset_of" predicate_uri = f"{TRUSTGRAPH_ENTITIES}is_subset_of"
# Find label triples # Find label triples
subject_label = next((t for t in triples if t.s.value == subject_uri and t.p.value == RDF_LABEL), None) subject_label = next((t for t in triples if t.s.iri == subject_uri and t.p.iri == RDF_LABEL), None)
assert subject_label is not None assert subject_label is not None
assert subject_label.o.value == "Machine Learning" assert subject_label.o.value == "Machine Learning"
predicate_label = next((t for t in triples if t.s.value == predicate_uri and t.p.value == RDF_LABEL), None) predicate_label = next((t for t in triples if t.s.iri == predicate_uri and t.p.iri == RDF_LABEL), None)
assert predicate_label is not None assert predicate_label is not None
assert predicate_label.o.value == "is_subset_of" assert predicate_label.o.value == "is_subset_of"
# Check main relationship triple # Check main relationship triple
object_uri = f"{TRUSTGRAPH_ENTITIES}Artificial%20Intelligence" object_uri = f"{TRUSTGRAPH_ENTITIES}Artificial%20Intelligence"
rel_triple = next((t for t in triples if t.s.value == subject_uri and t.p.value == predicate_uri), None) rel_triple = next((t for t in triples if t.s.iri == subject_uri and t.p.iri == predicate_uri), None)
assert rel_triple is not None assert rel_triple is not None
assert rel_triple.o.value == object_uri assert rel_triple.o.iri == object_uri
assert rel_triple.o.is_uri == True assert rel_triple.o.type == IRI
# Check subject-of relationships # Check subject-of relationships
subject_of_triples = [t for t in triples if t.p.value == SUBJECT_OF and t.o.value == "doc123"] subject_of_triples = [t for t in triples if t.p.iri == SUBJECT_OF and t.o.iri == "doc123"]
assert len(subject_of_triples) >= 2 # At least subject and predicate should have subject-of relations assert len(subject_of_triples) >= 2 # At least subject and predicate should have subject-of relations
def test_process_extraction_data_literal_object(self, agent_extractor, sample_metadata): def test_process_extraction_data_literal_object(self, agent_extractor, sample_metadata):
@ -254,7 +254,7 @@ This is not JSON at all
triples, entity_contexts = agent_extractor.process_extraction_data(data, sample_metadata) triples, entity_contexts = agent_extractor.process_extraction_data(data, sample_metadata)
# Check that object labels are not created for literal objects # Check that object labels are not created for literal objects
object_labels = [t for t in triples if t.p.value == RDF_LABEL and t.o.value == "95%"] object_labels = [t for t in triples if t.p.iri == RDF_LABEL and t.o.value == "95%"]
# Based on the code logic, it should not create object labels for non-entity objects # Based on the code logic, it should not create object labels for non-entity objects
# But there might be a bug in the original implementation # But there might be a bug in the original implementation
@ -263,12 +263,12 @@ This is not JSON at all
triples, entity_contexts = agent_extractor.process_extraction_data(sample_extraction_data, sample_metadata) triples, entity_contexts = agent_extractor.process_extraction_data(sample_extraction_data, sample_metadata)
# Check that we have both definition and relationship triples # Check that we have both definition and relationship triples
definition_triples = [t for t in triples if t.p.value == DEFINITION] definition_triples = [t for t in triples if t.p.iri == DEFINITION]
assert len(definition_triples) == 2 # Two definitions assert len(definition_triples) == 2 # Two definitions
# Check entity contexts are created for definitions # Check entity contexts are created for definitions
assert len(entity_contexts) == 2 assert len(entity_contexts) == 2
entity_uris = [ec.entity.value for ec in entity_contexts] entity_uris = [ec.entity.iri for ec in entity_contexts]
assert f"{TRUSTGRAPH_ENTITIES}Machine%20Learning" in entity_uris assert f"{TRUSTGRAPH_ENTITIES}Machine%20Learning" in entity_uris
assert f"{TRUSTGRAPH_ENTITIES}Neural%20Networks" in entity_uris assert f"{TRUSTGRAPH_ENTITIES}Neural%20Networks" in entity_uris
@ -282,7 +282,7 @@ This is not JSON at all
triples, entity_contexts = agent_extractor.process_extraction_data(data, metadata) triples, entity_contexts = agent_extractor.process_extraction_data(data, metadata)
# Should not create subject-of relationships when no metadata ID # Should not create subject-of relationships when no metadata ID
subject_of_triples = [t for t in triples if t.p.value == SUBJECT_OF] subject_of_triples = [t for t in triples if t.p.iri == SUBJECT_OF]
assert len(subject_of_triples) == 0 assert len(subject_of_triples) == 0
# Should still create entity contexts # Should still create entity contexts
@ -330,9 +330,9 @@ This is not JSON at all
test_triples = [ test_triples = [
Triple( Triple(
s=Value(value="test:subject", is_uri=True), s=Term(type=IRI, iri="test:subject"),
p=Value(value="test:predicate", is_uri=True), p=Term(type=IRI, iri="test:predicate"),
o=Value(value="test object", is_uri=False) o=Term(type=LITERAL, value="test object")
) )
] ]
@ -348,7 +348,7 @@ This is not JSON at all
# Note: metadata.metadata is now empty array in the new implementation # Note: metadata.metadata is now empty array in the new implementation
assert sent_triples.metadata.metadata == [] assert sent_triples.metadata.metadata == []
assert len(sent_triples.triples) == 1 assert len(sent_triples.triples) == 1
assert sent_triples.triples[0].s.value == "test:subject" assert sent_triples.triples[0].s.iri == "test:subject"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_emit_entity_contexts(self, agent_extractor, sample_metadata): async def test_emit_entity_contexts(self, agent_extractor, sample_metadata):
@ -357,7 +357,7 @@ This is not JSON at all
test_contexts = [ test_contexts = [
EntityContext( EntityContext(
entity=Value(value="test:entity", is_uri=True), entity=Term(type=IRI, iri="test:entity"),
context="Test context" context="Test context"
) )
] ]
@ -374,7 +374,7 @@ This is not JSON at all
# Note: metadata.metadata is now empty array in the new implementation # Note: metadata.metadata is now empty array in the new implementation
assert sent_contexts.metadata.metadata == [] assert sent_contexts.metadata.metadata == []
assert len(sent_contexts.entities) == 1 assert len(sent_contexts.entities) == 1
assert sent_contexts.entities[0].entity.value == "test:entity" assert sent_contexts.entities[0].entity.iri == "test:entity"
def test_agent_extractor_initialization_params(self): def test_agent_extractor_initialization_params(self):
"""Test agent extractor parameter validation""" """Test agent extractor parameter validation"""

View file

@ -11,7 +11,7 @@ import urllib.parse
from unittest.mock import AsyncMock, MagicMock from unittest.mock import AsyncMock, MagicMock
from trustgraph.extract.kg.agent.extract import Processor as AgentKgExtractor from trustgraph.extract.kg.agent.extract import Processor as AgentKgExtractor
from trustgraph.schema import Chunk, Triple, Triples, Metadata, Value from trustgraph.schema import Chunk, Triple, Triples, Metadata, Term, IRI, LITERAL
from trustgraph.schema import EntityContext, EntityContexts from trustgraph.schema import EntityContext, EntityContexts
from trustgraph.rdf import TRUSTGRAPH_ENTITIES, DEFINITION, RDF_LABEL, SUBJECT_OF from trustgraph.rdf import TRUSTGRAPH_ENTITIES, DEFINITION, RDF_LABEL, SUBJECT_OF
@ -188,7 +188,7 @@ class TestAgentKgExtractionEdgeCases:
triples, contexts = agent_extractor.process_extraction_data(data, metadata) triples, contexts = agent_extractor.process_extraction_data(data, metadata)
# Should not create subject-of triples when ID is empty string # Should not create subject-of triples when ID is empty string
subject_of_triples = [t for t in triples if t.p.value == SUBJECT_OF] subject_of_triples = [t for t in triples if t.p.iri == SUBJECT_OF]
assert len(subject_of_triples) == 0 assert len(subject_of_triples) == 0
def test_process_extraction_data_special_entity_names(self, agent_extractor): def test_process_extraction_data_special_entity_names(self, agent_extractor):
@ -221,7 +221,7 @@ class TestAgentKgExtractionEdgeCases:
# Verify URIs were properly encoded # Verify URIs were properly encoded
for i, entity in enumerate(special_entities): for i, entity in enumerate(special_entities):
expected_uri = f"{TRUSTGRAPH_ENTITIES}{urllib.parse.quote(entity)}" expected_uri = f"{TRUSTGRAPH_ENTITIES}{urllib.parse.quote(entity)}"
assert contexts[i].entity.value == expected_uri assert contexts[i].entity.iri == expected_uri
def test_process_extraction_data_very_long_definitions(self, agent_extractor): def test_process_extraction_data_very_long_definitions(self, agent_extractor):
"""Test processing with very long entity definitions""" """Test processing with very long entity definitions"""
@ -241,7 +241,7 @@ class TestAgentKgExtractionEdgeCases:
assert contexts[0].context == long_definition assert contexts[0].context == long_definition
# Find definition triple # Find definition triple
def_triple = next((t for t in triples if t.p.value == DEFINITION), None) def_triple = next((t for t in triples if t.p.iri == DEFINITION), None)
assert def_triple is not None assert def_triple is not None
assert def_triple.o.value == long_definition assert def_triple.o.value == long_definition
@ -262,7 +262,7 @@ class TestAgentKgExtractionEdgeCases:
assert len(contexts) == 4 assert len(contexts) == 4
# Check that both definitions for "Machine Learning" are present # Check that both definitions for "Machine Learning" are present
ml_contexts = [ec for ec in contexts if "Machine%20Learning" in ec.entity.value] ml_contexts = [ec for ec in contexts if "Machine%20Learning" in ec.entity.iri]
assert len(ml_contexts) == 2 assert len(ml_contexts) == 2
assert ml_contexts[0].context == "First definition" assert ml_contexts[0].context == "First definition"
assert ml_contexts[1].context == "Second definition" assert ml_contexts[1].context == "Second definition"
@ -286,7 +286,7 @@ class TestAgentKgExtractionEdgeCases:
assert len(contexts) == 3 assert len(contexts) == 3
# Empty entity should create empty URI after encoding # Empty entity should create empty URI after encoding
empty_entity_context = next((ec for ec in contexts if ec.entity.value == TRUSTGRAPH_ENTITIES), None) empty_entity_context = next((ec for ec in contexts if ec.entity.iri == TRUSTGRAPH_ENTITIES), None)
assert empty_entity_context is not None assert empty_entity_context is not None
def test_process_extraction_data_nested_json_in_strings(self, agent_extractor): def test_process_extraction_data_nested_json_in_strings(self, agent_extractor):
@ -338,7 +338,7 @@ class TestAgentKgExtractionEdgeCases:
# Should process all relationships # Should process all relationships
# Note: The current implementation has some logic issues that these tests document # Note: The current implementation has some logic issues that these tests document
assert len([t for t in triples if t.p.value != RDF_LABEL and t.p.value != SUBJECT_OF]) >= 7 assert len([t for t in triples if t.p.iri != RDF_LABEL and t.p.iri != SUBJECT_OF]) >= 7
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_emit_empty_collections(self, agent_extractor): async def test_emit_empty_collections(self, agent_extractor):