129 lines
3.6 KiB
Python
129 lines
3.6 KiB
Python
|
|
from datetime import datetime, timezone
|
||
|
|
from enum import StrEnum
|
||
|
|
|
||
|
|
from pydantic import (
|
||
|
|
BaseModel,
|
||
|
|
ConfigDict,
|
||
|
|
Field,
|
||
|
|
PrivateAttr,
|
||
|
|
computed_field,
|
||
|
|
field_validator,
|
||
|
|
)
|
||
|
|
from rdflib import URIRef
|
||
|
|
|
||
|
|
from ontocast.onto.constants import DEFAULT_IRI
|
||
|
|
from ontocast.onto.rdfgraph import RDFGraph
|
||
|
|
from ontocast.util import iri2namespace, render_text_hash
|
||
|
|
|
||
|
|
|
||
|
|
class OutputType(StrEnum):
|
||
|
|
FACTS = "facts"
|
||
|
|
ONTOLOGIES = "ontologies"
|
||
|
|
|
||
|
|
|
||
|
|
class SourceUnit(BaseModel):
|
||
|
|
"""Immutable source unit identity and input text.
|
||
|
|
|
||
|
|
Attributes:
|
||
|
|
text: Source text content for this unit.
|
||
|
|
index: Position of this unit in the source document.
|
||
|
|
hid: A stable hash id derived from text.
|
||
|
|
doc_iri: IRI of parent document.
|
||
|
|
type: Type of content unit (facts or ontology).
|
||
|
|
"""
|
||
|
|
|
||
|
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||
|
|
|
||
|
|
text: str = Field(description="Source text content for this unit")
|
||
|
|
index: int = Field(description="Position of this unit in the source document")
|
||
|
|
doc_iri: URIRef = Field(description="IRI of parent doc")
|
||
|
|
type: OutputType = Field(
|
||
|
|
default=OutputType.FACTS, description="Type of content unit"
|
||
|
|
)
|
||
|
|
_hid: str = PrivateAttr(default="")
|
||
|
|
|
||
|
|
@field_validator("doc_iri", mode="before")
|
||
|
|
@classmethod
|
||
|
|
def _coerce_doc_iri(cls, value: URIRef | str) -> URIRef:
|
||
|
|
if isinstance(value, URIRef):
|
||
|
|
return value
|
||
|
|
return URIRef(value)
|
||
|
|
|
||
|
|
@computed_field(return_type=str)
|
||
|
|
@property
|
||
|
|
def hid(self) -> str:
|
||
|
|
"""Stable hash id generated from source text."""
|
||
|
|
rendered_hid = render_text_hash(self.text)
|
||
|
|
if self._hid != rendered_hid:
|
||
|
|
self._hid = rendered_hid
|
||
|
|
return self._hid
|
||
|
|
|
||
|
|
@property
|
||
|
|
def iri(self):
|
||
|
|
"""Get the base IRI for this unit.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
str: The base unit IRI.
|
||
|
|
"""
|
||
|
|
return DEFAULT_IRI
|
||
|
|
|
||
|
|
@property
|
||
|
|
def iri_absolute(self):
|
||
|
|
"""Get the absolute IRI for this unit.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
str: The unit IRI.
|
||
|
|
"""
|
||
|
|
return f"{self.doc_iri}/{self.hid}"
|
||
|
|
|
||
|
|
@property
|
||
|
|
def namespace(self):
|
||
|
|
"""Get the namespace for this unit.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
str: The unit namespace.
|
||
|
|
"""
|
||
|
|
return iri2namespace(self.iri, ontology=False)
|
||
|
|
|
||
|
|
def __len__(self):
|
||
|
|
return len(self.text)
|
||
|
|
|
||
|
|
|
||
|
|
class ContentUnit(SourceUnit):
|
||
|
|
"""A processing unit that extends source data with mutable output fields."""
|
||
|
|
|
||
|
|
graph: RDFGraph = Field(
|
||
|
|
description="RDF triples representing facts rendered from this source unit in turtle format "
|
||
|
|
"as a string in compact form: use prefixes for namespaces, do NOT add comments",
|
||
|
|
default_factory=RDFGraph,
|
||
|
|
)
|
||
|
|
|
||
|
|
_graph_absolute: RDFGraph | None = PrivateAttr(default=None)
|
||
|
|
|
||
|
|
processed: bool = Field(default=False, description="Was this unit processed?")
|
||
|
|
generated_at: datetime | None = Field(
|
||
|
|
default=None, description="generated timestamp"
|
||
|
|
)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def graph_absolute(self):
|
||
|
|
if self._graph_absolute is None:
|
||
|
|
self._graph_absolute = self.graph.copy()
|
||
|
|
self._graph_absolute.remap_namespaces(self.iri, self.iri_absolute)
|
||
|
|
return self._graph_absolute
|
||
|
|
|
||
|
|
@property
|
||
|
|
def generated_at_iso(self):
|
||
|
|
"""Get generated timestamp in ISO format.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
str: Timestamp in ISO format.
|
||
|
|
"""
|
||
|
|
if self.generated_at is None:
|
||
|
|
self.generated_at = datetime.now(timezone.utc)
|
||
|
|
return self.generated_at.isoformat()
|
||
|
|
|
||
|
|
def sanitize(self):
|
||
|
|
self.graph = self.graph.unbind_chunk_namespaces()
|
||
|
|
self.graph.sanitize_prefixes_namespaces()
|