orchestrata_ultimate.domaingen.ttl

Reference › orchestrata_ultimate › domaingen › ttl

ttl renders a DomainModel back to a Turtle ontology (SPEC-105 FR-002 and FR-003). render_ontology writes the prefix header and then, in sorted order and without timestamps, each entity as an OWL class block (_render_class, including required restrictions) followed by the deduplicated datatype properties (_render_datatype_properties) and object properties (_render_object_properties); _esc escapes text. Python types are mapped back to XSD types. Because output is deterministic, parse_ontology from reader applied to the rendered text round-trips the IR structurally (REQ-105-004 and REQ-105-026). The package and constraints import it.

At a glance

Source src/orchestrata_ultimate/domaingen/ttl.py
Lines of code 118
Classes 0
Functions 5
Imports (internal) orchestrata_ultimate.core.traceability, orchestrata_ultimate.domaingen.models
Imported by orchestrata_ultimate.domaingen, orchestrata_ultimate.domaingen.constraints
Tests that import it none

Class diagram

No classes; see Functions.

Dependency diagram

Dependency diagram orchestrata_ultimate.domaingen.ttl

API

Functions

Function Signature Summary
_esc traces _esc(text: str) -> str
_render_class traces _render_class(ent: Entity) -> str Render a single entity as an owl:Class block with required restrictions.
_render_datatype_properties traces _render_datatype_properties(entities: list[Entity]) -> list[str] Render the deduplicated, sorted owl:DatatypeProperty declarations.
_render_object_properties traces _render_object_properties(entities: list[Entity]) -> list[str] Render the deduplicated, sorted owl:ObjectProperty declarations.
render_ontology traces render_ontology(model: DomainModel) -> str Render a DomainModel to a deterministic Turtle ontology.

Source

Permalink: https://gitlab.com/MSamy/orchestrata-ultimate-architecture/-/blob/a4cbc2bdf/src/orchestrata_ultimate/domaingen/ttl.py

"""DomainModel IR -> Turtle ontology (SPEC-105 FR-002/003).

Deterministic: classes and properties are emitted in sorted order with stable
prefixes and no timestamps, so ``parse_ontology(render_ontology(ir))`` round-trips
the IR structurally (REQ-105-004/026).
"""

from __future__ import annotations

from orchestrata_ultimate.core.traceability import traces
from orchestrata_ultimate.domaingen.models import DomainModel, Entity

_PY_TO_XSD = {
    "str": "xsd:string",
    "int": "xsd:integer",
    "float": "xsd:decimal",
    "bool": "xsd:boolean",
    "datetime": "xsd:dateTime",
}

_PREFIX_HEADER = (
    "@prefix owl: <http://www.w3.org/2002/07/owl#> .",
    "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .",
    "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .",
)


@traces(spec="105", story="US-105-001", reqs=["REQ-105-003"])
def _esc(text: str) -> str:
    return text.replace("\\", "\\\\").replace('"', '\\"').replace("\n", " ")


@traces(spec="105", story="US-105-001", reqs=["REQ-105-003", "REQ-105-026"])
def _render_class(ent: Entity) -> str:
    """Render a single entity as an owl:Class block with required restrictions."""
    clauses = [f":{ent.name} a owl:Class"]
    if ent.doc:
        clauses.append(f'    rdfs:comment "{_esc(ent.doc)}"')
    if ent.parent:
        clauses.append(f"    rdfs:subClassOf :{ent.parent}")
    for member in sorted(ent.fields, key=lambda f: f.name):
        if member.required:
            clauses.append(
                f"    rdfs:subClassOf [ a owl:Restriction ; "
                f"owl:onProperty :{member.name} ; owl:minCardinality 1 ]"
            )
    for rel in sorted(ent.relationships, key=lambda r: r.name):
        if rel.required:
            clauses.append(
                f"    rdfs:subClassOf [ a owl:Restriction ; "
                f"owl:onProperty :{rel.name} ; owl:minCardinality 1 ]"
            )
    return " ;\n".join(clauses) + " ."


@traces(spec="105", story="US-105-001", reqs=["REQ-105-003", "REQ-105-026"])
def _render_datatype_properties(entities: list[Entity]) -> list[str]:
    """Render the deduplicated, sorted owl:DatatypeProperty declarations."""
    dprops = sorted(
        {
            (f.name, _PY_TO_XSD.get(f.type, "xsd:string"), e.name)
            for e in entities
            for f in e.fields
        }
    )
    return [
        f":{name} a owl:DatatypeProperty ;\n"
        f"    rdfs:domain :{domain} ;\n"
        f"    rdfs:range {xsd_type} ."
        for name, xsd_type, domain in dprops
    ]


@traces(spec="105", story="US-105-001", reqs=["REQ-105-003", "REQ-105-026"])
def _render_object_properties(entities: list[Entity]) -> list[str]:
    """Render the deduplicated, sorted owl:ObjectProperty declarations."""
    oprops = sorted(
        {
            (r.name, e.name, r.target, r.many, r.inverse)
            for e in entities
            for r in e.relationships
        }
    )
    blocks: list[str] = []
    for name, domain, target, many, inverse in oprops:
        decl = f":{name} a owl:ObjectProperty"
        if not many:
            decl += ", owl:FunctionalProperty"
        clauses = [decl, f"    rdfs:domain :{domain}", f"    rdfs:range :{target}"]
        if inverse:
            clauses.append(f"    owl:inverseOf :{inverse}")
        blocks.append(" ;\n".join(clauses) + " .")
    return blocks


@traces(
    spec="105",
    story="US-105-001",
    reqs=["REQ-105-003", "REQ-105-004", "REQ-105-011", "REQ-105-026"],
)
def render_ontology(model: DomainModel) -> str:
    """Render a DomainModel to a deterministic Turtle ontology."""
    out = [f"@prefix : <{model.namespace}> .", *_PREFIX_HEADER, ""]
    entities = sorted(model.all_entities(), key=lambda e: e.name)

    for ent in entities:
        out.append(_render_class(ent))
        out.append("")

    for block in _render_datatype_properties(entities):
        out.append(block)
        out.append("")

    for block in _render_object_properties(entities):
        out.append(block)
        out.append("")

    return "\n".join(out).rstrip() + "\n"