참고소스 수정본
This commit is contained in:
224
참고/ontocast-main/ontocast/tool/agg/promoter.py
Normal file
224
참고/ontocast-main/ontocast/tool/agg/promoter.py
Normal file
@@ -0,0 +1,224 @@
|
||||
"""URI promotion from chunk-level to document-level.
|
||||
|
||||
This module handles the promotion of chunk-level entity URIs to document-level URIs,
|
||||
while preserving ontology entity URIs unchanged.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from rdflib import URIRef
|
||||
|
||||
from ontocast.onto.constants import DEFAULT_IRI
|
||||
|
||||
from .normalizer import EntityRepresentation
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class URIPromoter:
|
||||
"""Promotes chunk-level URIs to document-level URIs.
|
||||
|
||||
This class is responsible for converting entities from chunk namespaces
|
||||
to the document namespace, while keeping ontology entities unchanged.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
doc_namespace: str,
|
||||
chunk_namespaces: set[str],
|
||||
facts_iri: str = DEFAULT_IRI,
|
||||
):
|
||||
"""Initialize the URI promoter.
|
||||
|
||||
Args:
|
||||
doc_namespace: Document namespace for promoted URIs
|
||||
chunk_namespaces: Set of chunk namespace URIs
|
||||
facts_iri: Base IRI for fact entities. Entities under this
|
||||
namespace are facts; all other entities are ontology
|
||||
entities and preserved as-is.
|
||||
"""
|
||||
self.doc_namespace = self._normalize_namespace(doc_namespace)
|
||||
self.chunk_namespaces = chunk_namespaces
|
||||
self.facts_iri = facts_iri.rstrip("/") + "/"
|
||||
self._used_uris: set[str] = set()
|
||||
|
||||
def _normalize_namespace(self, namespace: str) -> str:
|
||||
"""Ensure namespace ends with appropriate separator."""
|
||||
return namespace if namespace.endswith(("/", "#")) else namespace + "/"
|
||||
|
||||
def _clean_local_name(self, name: str) -> str:
|
||||
"""Clean a name for use as URI local part.
|
||||
|
||||
Args:
|
||||
name: Name to clean
|
||||
|
||||
Returns:
|
||||
Cleaned name suitable for URI
|
||||
"""
|
||||
# Replace invalid URI characters with underscores
|
||||
cleaned = re.sub(r"[^\w\-.]", "_", name)
|
||||
# Remove consecutive underscores
|
||||
cleaned = re.sub(r"_+", "_", cleaned)
|
||||
# Remove leading/trailing underscores
|
||||
cleaned = cleaned.strip("_")
|
||||
return cleaned or "entity"
|
||||
|
||||
def _ensure_unique_uri(self, uri: str) -> str:
|
||||
"""Ensure URI is unique by appending counter if needed.
|
||||
|
||||
Args:
|
||||
uri: Proposed URI
|
||||
|
||||
Returns:
|
||||
Unique URI
|
||||
"""
|
||||
if uri not in self._used_uris:
|
||||
self._used_uris.add(uri)
|
||||
return uri
|
||||
|
||||
# URI already used, append counter
|
||||
base_uri = uri
|
||||
counter = 1
|
||||
|
||||
while uri in self._used_uris:
|
||||
# Extract local name and add counter
|
||||
if "#" in base_uri:
|
||||
namespace, local = base_uri.rsplit("#", 1)
|
||||
uri = f"{namespace}#{local}_{counter}"
|
||||
else:
|
||||
namespace = base_uri.rstrip("/").rsplit("/", 1)[0]
|
||||
local = (
|
||||
base_uri.rstrip("/").rsplit("/", 1)[1]
|
||||
if "/" in base_uri.rstrip("/")
|
||||
else base_uri
|
||||
)
|
||||
uri = f"{namespace}/{local}_{counter}"
|
||||
counter += 1
|
||||
|
||||
self._used_uris.add(uri)
|
||||
return uri
|
||||
|
||||
def should_promote(self, entity: URIRef) -> bool:
|
||||
"""Check if an entity should be promoted to document namespace.
|
||||
|
||||
Args:
|
||||
entity: Entity URI to check
|
||||
|
||||
Returns:
|
||||
True if entity should be promoted (is from chunk namespace)
|
||||
"""
|
||||
entity_str = str(entity)
|
||||
|
||||
# Don't promote ontology entities (anything not under facts_iri)
|
||||
if not entity_str.startswith(self.facts_iri):
|
||||
return False
|
||||
|
||||
# Promote chunk entities
|
||||
if any(entity_str.startswith(ns) for ns in self.chunk_namespaces):
|
||||
return True
|
||||
|
||||
# Unknown namespace - be conservative, don't promote
|
||||
return False
|
||||
|
||||
def promote_entity(
|
||||
self, entity: URIRef, representation: EntityRepresentation
|
||||
) -> URIRef:
|
||||
"""Promote a chunk entity to document namespace.
|
||||
|
||||
Args:
|
||||
entity: Original entity URI
|
||||
representation: Entity representation with metadata
|
||||
|
||||
Returns:
|
||||
Promoted entity URI
|
||||
"""
|
||||
if not self.should_promote(entity):
|
||||
# Keep ontology entities unchanged
|
||||
return entity
|
||||
|
||||
# Create new URI in document namespace
|
||||
# Use normalized form as local name
|
||||
local_name = self._clean_local_name(representation.normal_form)
|
||||
|
||||
# Construct promoted URI
|
||||
promoted_uri_str = f"{self.doc_namespace}{local_name}"
|
||||
|
||||
# Ensure uniqueness
|
||||
unique_uri_str = self._ensure_unique_uri(promoted_uri_str)
|
||||
|
||||
return URIRef(unique_uri_str)
|
||||
|
||||
def create_promotion_mapping(
|
||||
self,
|
||||
entities: list[URIRef],
|
||||
representations: dict[URIRef, EntityRepresentation],
|
||||
) -> dict[URIRef, URIRef]:
|
||||
"""Create mapping from original to promoted URIs.
|
||||
|
||||
Args:
|
||||
entities: List of entity URIs to promote
|
||||
representations: Dictionary mapping entities to their representations
|
||||
|
||||
Returns:
|
||||
Dictionary mapping original URIs to promoted URIs (e -> promoted(e))
|
||||
"""
|
||||
mapping = {}
|
||||
promoted_count = 0
|
||||
preserved_count = 0
|
||||
|
||||
for entity in entities:
|
||||
representation = representations.get(entity)
|
||||
if representation is None:
|
||||
logger.warning(f"No representation found for entity {entity}")
|
||||
mapping[entity] = entity
|
||||
continue
|
||||
|
||||
promoted = self.promote_entity(entity, representation)
|
||||
mapping[entity] = promoted
|
||||
|
||||
if promoted != entity:
|
||||
promoted_count += 1
|
||||
else:
|
||||
preserved_count += 1
|
||||
|
||||
logger.info(
|
||||
f"Created promotion mapping: "
|
||||
f"{promoted_count} promoted, "
|
||||
f"{preserved_count} preserved (ontology entities)"
|
||||
)
|
||||
|
||||
return mapping
|
||||
|
||||
def compose_mappings(
|
||||
self,
|
||||
clustering_mapping: dict[URIRef, URIRef],
|
||||
promotion_mapping: dict[URIRef, URIRef],
|
||||
) -> dict[URIRef, URIRef]:
|
||||
"""Compose clustering and promotion mappings.
|
||||
|
||||
First, entities are mapped to their cluster representatives (clustering_mapping).
|
||||
Then, representatives are promoted to document namespace (promotion_mapping).
|
||||
|
||||
The composed mapping is: e -> promoted(representative(e))
|
||||
|
||||
Args:
|
||||
clustering_mapping: Map from entity to cluster representative (e -> e_rep)
|
||||
promotion_mapping: Map from representative to promoted URI (e_rep -> e')
|
||||
|
||||
Returns:
|
||||
Composed mapping (e -> e')
|
||||
"""
|
||||
composed = {}
|
||||
|
||||
for original, representative in clustering_mapping.items():
|
||||
# Look up the promoted version of the representative
|
||||
promoted = promotion_mapping.get(representative, representative)
|
||||
composed[original] = promoted
|
||||
|
||||
logger.info(
|
||||
f"Composed mapping: {len(composed)} entities mapped to "
|
||||
f"{len(set(composed.values()))} final URIs"
|
||||
)
|
||||
|
||||
return composed
|
||||
Reference in New Issue
Block a user