Skip to content

ontocast.tool.facts_validation.terms

Catalog term inventory, namespace closure rules, and alias candidates.

What the catalog declares versus merely references decides which namespaces the term checks may treat as closed; ValidationPolicy carries the deployment-level exemptions every check honours.

ValidationPolicy

Bases: BaseModel

Deployment-level exemptions and vocabulary for deterministic validation.

One object instead of a parameter per concern: the namespaces a deployment shares across catalogs, the sanctioned quantity fallback vocabulary, and the code predicates — everything the term checks must never flag, because configuration explicitly blessed it.

Source code in ontocast/tool/facts_validation/terms.py
class ValidationPolicy(BaseModel):
    """Deployment-level exemptions and vocabulary for deterministic validation.

    One object instead of a parameter per concern: the namespaces a deployment
    shares across catalogs, the sanctioned quantity fallback vocabulary, and
    the code predicates — everything the term checks must never flag, because
    configuration explicitly blessed it.
    """

    additional_standard_namespaces: tuple[str, ...] = ()
    quantity_fallback_vocabulary: dict[str, str] | None = None
    code_predicates: tuple[str, ...] = ()

    def standard_namespaces(self) -> tuple[str, ...]:
        """Built-in meta-vocabulary namespaces plus the configured ones."""
        return (*_STANDARD_NAMESPACES, *self.additional_standard_namespaces)

    def exempt_terms(self, *graphs: RDFGraph | None) -> set[str]:
        """Exact IRIs configuration blessed: fallback vocabulary + code predicates."""
        terms = expand_vocabulary_terms(self.quantity_fallback_vocabulary, *graphs)
        terms.update(self.code_predicates)
        return terms

exempt_terms(*graphs)

Exact IRIs configuration blessed: fallback vocabulary + code predicates.

Source code in ontocast/tool/facts_validation/terms.py
def exempt_terms(self, *graphs: RDFGraph | None) -> set[str]:
    """Exact IRIs configuration blessed: fallback vocabulary + code predicates."""
    terms = expand_vocabulary_terms(self.quantity_fallback_vocabulary, *graphs)
    terms.update(self.code_predicates)
    return terms

standard_namespaces()

Built-in meta-vocabulary namespaces plus the configured ones.

Source code in ontocast/tool/facts_validation/terms.py
def standard_namespaces(self) -> tuple[str, ...]:
    """Built-in meta-vocabulary namespaces plus the configured ones."""
    return (*_STANDARD_NAMESPACES, *self.additional_standard_namespaces)

build_surface_index(ontology_graph, code_predicates=())

Map exact catalog surface forms to the IRIs declaring them.

Case-sensitive and exact: these are codes and names a model may have transcribed verbatim ("d", "meV", "CsPbBr3"), not free text to be fuzzy-matched. A form claimed by more than one IRI stays in the index and is rejected at lookup time — an ambiguous code is not a repairable one.

Parameters:

Name Type Description Default
ontology_graph RDFGraph | None

Merged ontology context to index.

required
code_predicates Sequence[str]

Extra code-bearing predicates (UCUM codes, symbols, notations) on top of the standard name predicates.

()

Returns:

Type Description
dict[str, set[str]]

Surface form -> set of IRIs declaring it.

Source code in ontocast/tool/facts_validation/terms.py
def build_surface_index(
    ontology_graph: RDFGraph | None,
    code_predicates: Sequence[str] = (),
) -> dict[str, set[str]]:
    """Map exact catalog surface forms to the IRIs declaring them.

    Case-sensitive and exact: these are codes and names a model may have
    transcribed verbatim (``"d"``, ``"meV"``, ``"CsPbBr3"``), not free text to
    be fuzzy-matched. A form claimed by more than one IRI stays in the index and
    is rejected at lookup time — an ambiguous code is not a repairable one.

    Args:
        ontology_graph: Merged ontology context to index.
        code_predicates: Extra code-bearing predicates (UCUM codes, symbols,
            notations) on top of the standard name predicates.

    Returns:
        Surface form -> set of IRIs declaring it.
    """
    index: dict[str, set[str]] = {}
    if ontology_graph is None:
        return index
    predicates: list[URIRef] = [RDFS.label, SKOS.prefLabel, SKOS.notation]
    predicates.extend(URIRef(predicate) for predicate in code_predicates)
    for predicate in predicates:
        for subject, value in ontology_graph.subject_objects(predicate):
            if not isinstance(subject, URIRef) or not isinstance(value, Literal):
                continue
            text = str(value).strip()
            if text:
                index.setdefault(text, set()).add(str(subject))
    return index

collect_catalog_terms(ontology_graph)

All IRIs appearing anywhere in the ontology context.

Source code in ontocast/tool/facts_validation/terms.py
def collect_catalog_terms(ontology_graph: RDFGraph | None) -> set[str]:
    """All IRIs appearing anywhere in the ontology context."""
    terms: set[str] = set()
    if ontology_graph is None:
        return terms
    for triple in ontology_graph:
        for term in triple:
            if isinstance(term, URIRef):
                terms.add(str(term))
    return terms

collect_declared_namespaces(ontology_graph)

Namespaces the catalog declares terms in (subject-position IRIs).

The UNKNOWN_TERM check treats a namespace as closed — flagging members the catalog does not list — only when the catalog actually declares terms there. A namespace the catalog merely references (qudt:QuantityValue in an rdfs:subClassOf, qudt:unit in an owl:onProperty) is an external vocabulary the catalog borrows from, and the catalog is not an authority on its membership. Treating referenced-only namespaces as closed produced mandatory findings against canonical external properties (qudt:numericValue), which repair renders then obeyed by deleting correct data.

Source code in ontocast/tool/facts_validation/terms.py
def collect_declared_namespaces(ontology_graph: RDFGraph | None) -> set[str]:
    """Namespaces the catalog *declares* terms in (subject-position IRIs).

    The UNKNOWN_TERM check treats a namespace as closed — flagging members the
    catalog does not list — only when the catalog actually declares terms
    there. A namespace the catalog merely *references* (``qudt:QuantityValue``
    in an ``rdfs:subClassOf``, ``qudt:unit`` in an ``owl:onProperty``) is an
    external vocabulary the catalog borrows from, and the catalog is not an
    authority on its membership. Treating referenced-only namespaces as closed
    produced mandatory findings against canonical external properties
    (``qudt:numericValue``), which repair renders then obeyed by deleting
    correct data.
    """
    namespaces: set[str] = set()
    if ontology_graph is None:
        return namespaces
    for subject in ontology_graph.subjects():
        if isinstance(subject, URIRef):
            namespaces.add(_namespace_of(str(subject)))
    return namespaces

expand_vocabulary_terms(vocabulary, *graphs)

Expand configured vocabulary terms (CURIEs or full IRIs) to IRI strings.

CURIEs are expanded against the prefix bindings of every graph given, in order; a CURIE whose prefix no graph binds is dropped rather than guessed.

Source code in ontocast/tool/facts_validation/terms.py
def expand_vocabulary_terms(
    vocabulary: dict[str, str] | None,
    *graphs: RDFGraph | None,
) -> set[str]:
    """Expand configured vocabulary terms (CURIEs or full IRIs) to IRI strings.

    CURIEs are expanded against the prefix bindings of every graph given, in
    order; a CURIE whose prefix no graph binds is dropped rather than guessed.
    """
    terms: set[str] = set()
    if not vocabulary:
        return terms
    bindings: dict[str, str] = {}
    for graph in graphs:
        if graph is None:
            continue
        for prefix, uri in graph.namespaces():
            if prefix:
                bindings.setdefault(prefix, str(uri))
    for term in vocabulary.values():
        if not term:
            continue
        if term.startswith("http://") or term.startswith("https://"):
            terms.add(term)
            continue
        prefix, separator, local = term.partition(":")
        if separator and local and prefix in bindings:
            terms.add(bindings[prefix] + local)
    return terms

resolve_unique_surface(index, text)

The single IRI declaring text as a surface form, if exactly one does.

Source code in ontocast/tool/facts_validation/terms.py
def resolve_unique_surface(index: dict[str, set[str]], text: str) -> URIRef | None:
    """The single IRI declaring ``text`` as a surface form, if exactly one does."""
    candidates = index.get(text.strip(), set())
    if len(candidates) != 1:
        return None
    return URIRef(next(iter(candidates)))