fix(export): normalize entity name to label on all RDF paths

convert_kg_to_rdf() maps an entity's 'name' to 'label'/'text' but was
never invoked from export_to_rdf(), so graphs produced by GraphBuilder
(which emit 'name') exported with an empty semantica:text on every RDF
format (turtle, ntriples, rdfxml, jsonld). Call convert_kg_to_rdf() at
the export boundary before validation/serialization so all formats
benefit from a single normalization step.

Add regression tests asserting a name-only entity exports a non-empty
label across all four serializers and the file-writing entry point,
plus that an explicit 'text' is not clobbered and an id tail is used
as a fallback label.

Closes #1097
This commit is contained in:
江俊杰
2026-08-20 10:19:52 +08:00
committed by cxzg007
parent 2a303cf4da
commit 9e2f349221
2 changed files with 77 additions and 1 deletions
+6
View File
@@ -1054,6 +1054,12 @@ class RDFExporter:
self.logger.debug(f"Exporting to RDF format: {format}") self.logger.debug(f"Exporting to RDF format: {format}")
# Normalize the graph before serialization so every format benefits
# from field normalization (e.g. mapping 'name' -> 'label'/'text').
# Without this, graphs produced by GraphBuilder (which emit 'name')
# export with an empty semantica:text on all RDF paths. See #1097.
data = self.serializer.convert_kg_to_rdf(data)
self.progress_tracker.update_tracking( self.progress_tracker.update_tracking(
tracking_id, message="Validating RDF data..." tracking_id, message="Validating RDF data..."
) )
+71 -1
View File
@@ -3,7 +3,6 @@
import pytest import pytest
from semantica.export import RDFExporter from semantica.export import RDFExporter
RDF_DATA = { RDF_DATA = {
"entities": [ "entities": [
{"id": "e1", "text": "Apple Inc.", "type": "ORG", "confidence": 0.95}, {"id": "e1", "text": "Apple Inc.", "type": "ORG", "confidence": 0.95},
@@ -89,3 +88,74 @@ def test_validate_rdf_returns_overall_valid_key(exporter):
result = exporter.validate_rdf(RDF_DATA) result = exporter.validate_rdf(RDF_DATA)
assert "overall_valid" in result assert "overall_valid" in result
assert isinstance(result["overall_valid"], bool) assert isinstance(result["overall_valid"], bool)
# --- Regression tests for #1097 -------------------------------------------
# convert_kg_to_rdf() normalizes an entity's 'name' into 'label'/'text' but was
# never called from the export path, so GraphBuilder graphs (which emit 'name')
# exported with an empty semantica:text on every RDF format. These tests assert
# the human-readable label survives export on all four serializers.
NAME_ONLY_DATA = {
"entities": [
{
"id": "https://example.org/acme",
"name": "Acme Corp",
"type": "https://example.org/Org",
"confidence": 0.91,
},
],
"relationships": [],
}
@pytest.mark.parametrize("fmt", ["turtle", "ntriples", "rdfxml", "jsonld"])
def test_name_only_entity_exports_nonempty_label(exporter, fmt):
"""A GraphBuilder-style 'name'-only entity must export a non-empty label.
Regression for #1097: previously every RDF path dropped the label because
convert_kg_to_rdf() was never invoked from export_to_rdf().
"""
result = exporter.export_to_rdf(NAME_ONLY_DATA, format=fmt)
assert "Acme Corp" in result
# The empty-text pattern that the bug produced must not appear.
assert 'semantica:text ""' not in result
def test_name_only_export_to_file_contains_label(exporter, tmp_path):
"""The file-writing entry point must also normalize name -> label (#1097)."""
out = tmp_path / "acme.ttl"
exporter.export(NAME_ONLY_DATA, str(out), format="turtle")
content = out.read_text()
assert "Acme Corp" in content
assert 'semantica:text ""' not in content
def test_existing_text_not_overwritten_by_name(exporter):
"""An entity that already has 'text' must keep it, not be clobbered by 'name' (#1097)."""
data = {
"entities": [
{
"id": "https://example.org/acme",
"name": "Acme Corp",
"text": "Explicit Text",
"type": "https://example.org/Org",
}
],
"relationships": [],
}
result = exporter.export_to_rdf(data, format="turtle")
assert "Explicit Text" in result
assert "Acme Corp" not in result
def test_id_fallback_label_when_no_name(exporter):
"""With neither 'name' nor 'text', the id tail is used as the label (#1097)."""
data = {
"entities": [
{"id": "https://example.org/acme", "type": "https://example.org/Org"}
],
"relationships": [],
}
result = exporter.export_to_rdf(data, format="turtle")
assert 'semantica:text ""' not in result