Skip to content

graflo.architecture.evolution.aspects.state

The state aspect: how facts change over time, and what was measured (PROV-O, SOSA, QUDT).

Modules:

Name Description
plan

Planning the state aspect: validity intervals, observations, units, groundings.

vocabulary

The state aspect's types: how a fact changes over time, and what was measured.

Attributes

__all__ = ['StatePlan', 'observation_type_name', 'observation_vertex', 'plan_state', 'state_type_name', 'state_vertex'] module-attribute

Classes

StatePlan

Bases: ConfigBaseModel

What the state aspect planned, in the two halves a composed lift needs apart.

Attributes:

Name Type Description
ops list[ManifestOp]

The additive ops, in application order: grounding first, then structure, then the scaffolding.

scaffolded list[str]

Names of the <Type>State and <Type>Observation types the ops add, for an aspect that attaches to them.

retire list[ManifestOp]

The destructive half, to be applied last.

Source code in graflo/architecture/evolution/aspects/state/plan.py
class StatePlan(ConfigBaseModel):
    """What the state aspect planned, in the two halves a composed lift needs apart.

    Attributes:
        ops: The additive ops, in application order: grounding first, then
            structure, then the scaffolding.
        scaffolded: Names of the ``<Type>State`` and ``<Type>Observation``
            types the ops add, for an aspect that attaches to them.
        retire: The destructive half, to be applied last.
    """

    ops: list[ManifestOp] = PydanticField(default_factory=list)
    scaffolded: list[str] = PydanticField(default_factory=list)
    retire: list[ManifestOp] = PydanticField(default_factory=list)

Attributes

ops = PydanticField(default_factory=list) class-attribute instance-attribute
retire = PydanticField(default_factory=list) class-attribute instance-attribute
scaffolded = PydanticField(default_factory=list) class-attribute instance-attribute

Functions:

observation_type_name(vertex)

Machine -> MachineObservation.

Source code in graflo/architecture/evolution/aspects/state/vocabulary.py
def observation_type_name(vertex: str) -> str:
    """``Machine`` -> ``MachineObservation``."""
    return f"{vertex}{OBSERVATION_SUFFIX}"

observation_vertex(subject, key_fields)

The Observation type for one subject: a measurement at a time.

result_unit is a property rather than a contract-level declaration because this type is abstract: one measuring temperature and one measuring pressure are the same type here, so the unit has to travel with the row.

Source code in graflo/architecture/evolution/aspects/state/vocabulary.py
def observation_vertex(subject: str, key_fields: list[Field]) -> Vertex:
    """The ``Observation`` type for one subject: a measurement at a time.

    ``result_unit`` is a property rather than a contract-level declaration
    because this type is abstract: one measuring temperature and one measuring
    pressure are the same type here, so the unit has to travel with the row.
    """
    return Vertex(
        name=observation_type_name(subject),
        description=f"A measurement of `{subject}` at a time.",
        semantics=Semantics(iri=SOSA_OBSERVATION, exact_match=[SOSA_OBSERVATION]),
        properties=[
            *(field.model_copy(deep=True) for field in key_fields),
            Field(
                name=OBSERVED_PROPERTY,
                type=FieldType.STRING,
                semantics=FieldSemantics(
                    exact_match=[SOSA_OBSERVED_PROPERTY, SSN_PROPERTY]
                ),
            ),
            Field(
                name=RESULT_VALUE,
                type=FieldType.FLOAT,
                semantics=FieldSemantics(exact_match=[SOSA_HAS_SIMPLE_RESULT]),
            ),
            Field(
                name=RESULT_UNIT,
                type=FieldType.STRING,
                semantics=FieldSemantics(iri=QUDT_UCUM_CODE),
            ),
            Field(
                name=RESULT_TIME,
                type=FieldType.DATETIME,
                semantics=FieldSemantics(iri=SOSA_RESULT_TIME),
            ),
        ],
        hash_identity_properties=[
            *(f.name for f in key_fields),
            OBSERVED_PROPERTY,
            RESULT_TIME,
        ],
    )

plan_state(manifest, spec, *, authored=None)

The ops that lift manifest into one that tracks state and measurements.

Parameters:

Name Type Description Default
manifest GraphManifest

the manifest to lift, already finish_init()-ed.

required
spec LiftSpec

the semantic declarations the planner cannot infer.

required
authored dict[str, Any] | None

the manifest as written, when available. Only an authored document distinguishes "this vertex declared no identity" from "this vertex declared every property as its identity", so without it the planner cannot detect the fallback and does not try.

None

Returns:

Type Description
StatePlan

The additive ops, the scaffolded type names, and the removals kept

StatePlan

apart so they can run last. Ordering matters -- a property is moved

StatePlan

onto its State before it is removed from the entity.

Raises:

Type Description
LiftError

a declaration the planner needs is missing, or a name it would mint is already taken.

Source code in graflo/architecture/evolution/aspects/state/plan.py
def plan_state(
    manifest: GraphManifest,
    spec: LiftSpec,
    *,
    authored: dict[str, Any] | None = None,
) -> StatePlan:
    """The ops that lift *manifest* into one that tracks state and measurements.

    Args:
        manifest: the manifest to lift, already ``finish_init()``-ed.
        spec: the semantic declarations the planner cannot infer.
        authored: the manifest as written, when available. Only an authored
            document distinguishes "this vertex declared no identity" from
            "this vertex declared every property as its identity", so without
            it the planner cannot detect the fallback and does not try.

    Returns:
        The additive ops, the scaffolded type names, and the removals kept
        apart so they can run last. Ordering matters -- a property is moved
        onto its ``State`` before it is removed from the entity.

    Raises:
        LiftError: a declaration the planner needs is missing, or a name it
            would mint is already taken.
    """
    schema = manifest.graph_schema
    if schema is None:
        raise LiftError(
            "cannot lift a manifest with no schema block: there are no types to "
            "ground and nothing to attach state to"
        )

    core = schema.core_schema
    vertex_config = core.vertex_config
    existing = set(vertex_config.vertex_set)
    by_name = {vertex.name: vertex for vertex in vertex_config.vertices}

    unknown = sorted(
        (set(spec.grounding) | set(spec.stateful) | set(spec.observed)) - existing
    )
    if unknown:
        raise LiftError(f"spec names types the manifest does not have: {unknown}")

    ops: list[ManifestOp] = []
    ops += _identity_ops(spec, vertex_config, authored)
    ops += _grounding_ops(spec, core)
    ops += _directionality_ops(core)
    ops += _unit_ops(spec, by_name)
    scaffold_ops, scaffolded = _scaffold_ops(spec, by_name, core)
    ops += scaffold_ops
    return StatePlan(ops=ops, scaffolded=scaffolded, retire=_retire_ops(spec, by_name))

state_type_name(vertex)

Machine -> MachineState.

Source code in graflo/architecture/evolution/aspects/state/vocabulary.py
def state_type_name(vertex: str) -> str:
    """``Machine`` -> ``MachineState``."""
    return f"{vertex}{STATE_SUFFIX}"

state_vertex(subject, key_fields, moved)

The State type for one subject: its key, its mutable facts, a validity interval.

Keyed on the subject's own key plus valid_from: the same property of the same entity holds many values over time, and those are different facts rather than revisions of one. Closing valid_to instead of overwriting is what makes the history queryable at all.

Source code in graflo/architecture/evolution/aspects/state/vocabulary.py
def state_vertex(subject: str, key_fields: list[Field], moved: list[Field]) -> Vertex:
    """The ``State`` type for one subject: its key, its mutable facts, a validity interval.

    Keyed on the subject's own key *plus* ``valid_from``: the same property of
    the same entity holds many values over time, and those are different facts
    rather than revisions of one. Closing ``valid_to`` instead of overwriting is
    what makes the history queryable at all.
    """
    return Vertex(
        name=state_type_name(subject),
        description=(
            f"Mutable facts about `{subject}`, each holding over one interval. "
            "Closing `valid_to` rather than overwriting is what makes history "
            "queryable."
        ),
        semantics=Semantics(
            iri=PROV_ENTITY, exact_match=[PROV_ENTITY, TIME_PROPER_INTERVAL]
        ),
        properties=[
            *(field.model_copy(deep=True) for field in key_fields),
            *(field.model_copy(deep=True) for field in moved),
            *_validity_fields(),
        ],
        hash_identity_properties=[*(f.name for f in key_fields), VALID_FROM],
    )