feat(perception): define semantic object understanding

This commit is contained in:
DCCONSTRUCTIONS
2026-08-25 16:43:47 +03:00
parent 1084984da2
commit 6276bbf324
28 changed files with 7028 additions and 0 deletions
@@ -0,0 +1,929 @@
"""Versioned semantic, state and advisory-risk projection for an obstacle.
The projection composes an immutable :class:`ObstacleObservation` instead of
changing the strict v1 geometry contract. Semantic identity, observed state,
class priors and advisory risk remain separate claims with explicit evidence.
None of them can create occupancy or acquire navigation, safety or actuation
authority.
"""
from __future__ import annotations
import json
import math
import re
from dataclasses import dataclass
from enum import StrEnum
from pathlib import Path
from typing import Final
from .contracts import FalseAuthority, MotionState, ObstacleObservation
OBJECT_UNDERSTANDING_SCHEMA: Final = "missioncore.object-understanding/v1"
OBJECT_SEMANTIC_VOCABULARY_SCHEMA: Final = "missioncore.object-semantic-vocabulary/v0"
MAX_SEMANTIC_HYPOTHESES: Final = 5
_IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/-]{0,159}$")
_NORMALIZED_LABEL = re.compile(r"^[a-z0-9][a-z0-9_]{0,79}$")
class ObjectUnderstandingError(ValueError):
"""An object-understanding document or vocabulary is incompatible."""
class EvidenceKind(StrEnum):
DETECTOR = "detector"
SEMANTIC_MASK = "semantic-mask"
HUMAN_REVIEW = "human-review"
GEOMETRY = "geometry"
TEMPORAL = "temporal"
POLICY = "policy"
class SemanticResolution(StrEnum):
UNRESOLVED = "unresolved"
SELECTED = "selected"
AMBIGUOUS = "ambiguous"
CONFLICT = "conflict"
class AgencyState(StrEnum):
UNKNOWN = "unknown"
INERT = "inert"
ANIMATE = "animate"
SELF_PROPELLED = "self-propelled"
class StateBasis(StrEnum):
UNKNOWN = "unknown"
OBSERVED = "observed"
CLASS_PRIOR = "class-prior"
FUSED = "fused"
class RiskLevel(StrEnum):
UNKNOWN = "unknown"
LOW = "low"
ELEVATED = "elevated"
HIGH = "high"
CRITICAL = "critical"
class RiskBasis(StrEnum):
UNKNOWN = "unknown"
SEMANTIC_PRIOR = "semantic-prior"
OBSERVED_STATE = "observed-state"
GEOMETRY = "geometry"
FUSED = "fused"
class AdvisoryResponse(StrEnum):
MONITOR = "monitor"
REDUCE_SPEED = "reduce-speed"
YIELD = "yield"
STOP = "stop"
ROUTE_AROUND = "route-around"
@dataclass(frozen=True, slots=True)
class EvidenceProvenance:
"""One source-bound evidence item used by semantic, state or risk claims."""
evidence_id: str
kind: EvidenceKind
source_id: str
frame_id: str
provider_id: str
model_id: str | None
model_revision: str | None
preprocess_id: str | None
prompt_set_id: str | None = None
def __post_init__(self) -> None:
if not isinstance(self.kind, EvidenceKind):
raise ObjectUnderstandingError("evidence kind is invalid")
for value, label in (
(self.evidence_id, "evidence id"),
(self.source_id, "evidence source id"),
(self.frame_id, "evidence frame id"),
(self.provider_id, "evidence provider id"),
):
_identifier(value, label)
for optional_value, label in (
(self.model_id, "evidence model id"),
(self.model_revision, "evidence model revision"),
(self.preprocess_id, "evidence preprocess id"),
(self.prompt_set_id, "evidence prompt-set id"),
):
_optional_identifier(optional_value, label)
if self.kind in {EvidenceKind.DETECTOR, EvidenceKind.SEMANTIC_MASK} and (
self.model_id is None or self.model_revision is None or self.preprocess_id is None
):
raise ObjectUnderstandingError(
"model evidence requires model, revision and preprocess identity"
)
def to_dict(self) -> dict[str, object]:
return {
"evidence_id": self.evidence_id,
"kind": self.kind.value,
"source_id": self.source_id,
"frame_id": self.frame_id,
"provider_id": self.provider_id,
"model_id": self.model_id,
"model_revision": self.model_revision,
"preprocess_id": self.preprocess_id,
"prompt_set_id": self.prompt_set_id,
}
@classmethod
def from_dict(cls, value: object) -> EvidenceProvenance:
document = _object(value, "evidence provenance")
_exact_keys(
document,
{
"evidence_id",
"kind",
"source_id",
"frame_id",
"provider_id",
"model_id",
"model_revision",
"preprocess_id",
"prompt_set_id",
},
"evidence provenance",
)
return cls(
evidence_id=_string(document, "evidence_id"),
kind=_enum(EvidenceKind, document.get("kind"), "evidence kind"),
source_id=_string(document, "source_id"),
frame_id=_string(document, "frame_id"),
provider_id=_string(document, "provider_id"),
model_id=_optional_string(document.get("model_id"), "model id"),
model_revision=_optional_string(document.get("model_revision"), "model revision"),
preprocess_id=_optional_string(document.get("preprocess_id"), "preprocess id"),
prompt_set_id=_optional_string(document.get("prompt_set_id"), "prompt-set id"),
)
@dataclass(frozen=True, slots=True)
class SemanticHypothesis:
"""One ranked canonical class hypothesis, never an occupancy identity."""
rank: int
class_id: str
raw_label: str
confidence: float
evidence_ids: tuple[str, ...]
def __post_init__(self) -> None:
_positive_integer(self.rank, "semantic rank")
_identifier(self.class_id, "semantic class id")
_label(self.raw_label, "raw semantic label")
_confidence(self.confidence, "semantic confidence")
_unique_identifiers(self.evidence_ids, "semantic evidence ids")
def to_dict(self) -> dict[str, object]:
return {
"rank": self.rank,
"class_id": self.class_id,
"raw_label": self.raw_label,
"confidence": self.confidence,
"evidence_ids": list(self.evidence_ids),
}
@classmethod
def from_dict(cls, value: object) -> SemanticHypothesis:
document = _object(value, "semantic hypothesis")
_exact_keys(
document,
{"rank", "class_id", "raw_label", "confidence", "evidence_ids"},
"semantic hypothesis",
)
return cls(
rank=_integer(document, "rank"),
class_id=_string(document, "class_id"),
raw_label=_string(document, "raw_label"),
confidence=_number(document, "confidence"),
evidence_ids=_string_tuple(document.get("evidence_ids"), "evidence ids"),
)
@dataclass(frozen=True, slots=True)
class SemanticDecision:
"""Resolution over ranked hypotheses; ambiguity remains first-class."""
resolution: SemanticResolution
selected_class_id: str | None
selected_confidence: float | None
reason_codes: tuple[str, ...]
def __post_init__(self) -> None:
if not isinstance(self.resolution, SemanticResolution):
raise ObjectUnderstandingError("semantic resolution is invalid")
_optional_identifier(self.selected_class_id, "selected semantic class id")
if self.selected_confidence is not None:
_confidence(self.selected_confidence, "selected semantic confidence")
_unique_identifiers(self.reason_codes, "semantic decision reasons")
has_selection = self.selected_class_id is not None and self.selected_confidence is not None
if self.resolution is SemanticResolution.SELECTED:
if not has_selection:
raise ObjectUnderstandingError("selected semantics require class and confidence")
elif self.selected_class_id is not None or self.selected_confidence is not None:
raise ObjectUnderstandingError("non-selected semantics cannot publish a selected class")
def to_dict(self) -> dict[str, object]:
return {
"resolution": self.resolution.value,
"selected_class_id": self.selected_class_id,
"selected_confidence": self.selected_confidence,
"reason_codes": list(self.reason_codes),
}
@classmethod
def from_dict(cls, value: object) -> SemanticDecision:
document = _object(value, "semantic decision")
_exact_keys(
document,
{
"resolution",
"selected_class_id",
"selected_confidence",
"reason_codes",
},
"semantic decision",
)
return cls(
resolution=_enum(
SemanticResolution,
document.get("resolution"),
"semantic resolution",
),
selected_class_id=_optional_string(
document.get("selected_class_id"), "selected class id"
),
selected_confidence=_optional_number(
document.get("selected_confidence"), "selected confidence"
),
reason_codes=_string_tuple(document.get("reason_codes"), "reason codes"),
)
@dataclass(frozen=True, slots=True)
class ObjectStateEstimate:
"""Observed motion and agency prior, with their bases kept explicit."""
motion: MotionState
motion_confidence: float
agency: AgencyState
agency_basis: StateBasis
evidence_ids: tuple[str, ...]
reason_codes: tuple[str, ...]
def __post_init__(self) -> None:
if not isinstance(self.motion, MotionState):
raise ObjectUnderstandingError("motion state is invalid")
if not isinstance(self.agency, AgencyState):
raise ObjectUnderstandingError("agency state is invalid")
if not isinstance(self.agency_basis, StateBasis):
raise ObjectUnderstandingError("agency basis is invalid")
_confidence(self.motion_confidence, "motion confidence")
_unique_identifiers(self.evidence_ids, "state evidence ids", allow_empty=True)
_unique_identifiers(self.reason_codes, "state reason codes")
if self.agency is AgencyState.UNKNOWN:
if self.agency_basis is not StateBasis.UNKNOWN:
raise ObjectUnderstandingError("unknown agency must retain unknown evidence basis")
elif self.agency_basis is StateBasis.UNKNOWN:
raise ObjectUnderstandingError("agency claim requires an explicit basis")
def to_dict(self) -> dict[str, object]:
return {
"motion": self.motion.value,
"motion_confidence": self.motion_confidence,
"agency": self.agency.value,
"agency_basis": self.agency_basis.value,
"evidence_ids": list(self.evidence_ids),
"reason_codes": list(self.reason_codes),
}
@classmethod
def from_dict(cls, value: object) -> ObjectStateEstimate:
document = _object(value, "object state")
_exact_keys(
document,
{
"motion",
"motion_confidence",
"agency",
"agency_basis",
"evidence_ids",
"reason_codes",
},
"object state",
)
return cls(
motion=_enum(MotionState, document.get("motion"), "motion state"),
motion_confidence=_number(document, "motion_confidence"),
agency=_enum(AgencyState, document.get("agency"), "agency state"),
agency_basis=_enum(StateBasis, document.get("agency_basis"), "agency basis"),
evidence_ids=_string_tuple(document.get("evidence_ids"), "evidence ids"),
reason_codes=_string_tuple(document.get("reason_codes"), "reason codes"),
)
@dataclass(frozen=True, slots=True)
class AdvisoryRiskAssessment:
"""Evidence-qualified risk hint that is never a planner command."""
policy_id: str
level: RiskLevel
confidence: float
basis: RiskBasis
responses: tuple[AdvisoryResponse, ...]
evidence_ids: tuple[str, ...]
reason_codes: tuple[str, ...]
def __post_init__(self) -> None:
if not isinstance(self.level, RiskLevel):
raise ObjectUnderstandingError("risk level is invalid")
if not isinstance(self.basis, RiskBasis):
raise ObjectUnderstandingError("risk basis is invalid")
_identifier(self.policy_id, "risk policy id")
_confidence(self.confidence, "risk confidence")
_unique_enum_values(self.responses, "advisory responses", allow_empty=True)
_unique_identifiers(self.evidence_ids, "risk evidence ids", allow_empty=True)
_unique_identifiers(self.reason_codes, "risk reason codes")
if self.level is RiskLevel.UNKNOWN:
if self.basis is not RiskBasis.UNKNOWN or self.confidence != 0.0:
raise ObjectUnderstandingError(
"unknown risk must retain unknown basis and zero confidence"
)
elif self.basis is RiskBasis.UNKNOWN:
raise ObjectUnderstandingError("risk claim requires an explicit basis")
def to_dict(self) -> dict[str, object]:
return {
"policy_id": self.policy_id,
"level": self.level.value,
"confidence": self.confidence,
"basis": self.basis.value,
"responses": [item.value for item in self.responses],
"evidence_ids": list(self.evidence_ids),
"reason_codes": list(self.reason_codes),
}
@classmethod
def from_dict(cls, value: object) -> AdvisoryRiskAssessment:
document = _object(value, "advisory risk")
_exact_keys(
document,
{
"policy_id",
"level",
"confidence",
"basis",
"responses",
"evidence_ids",
"reason_codes",
},
"advisory risk",
)
return cls(
policy_id=_string(document, "policy_id"),
level=_enum(RiskLevel, document.get("level"), "risk level"),
confidence=_number(document, "confidence"),
basis=_enum(RiskBasis, document.get("basis"), "risk basis"),
responses=tuple(
_enum(AdvisoryResponse, item, "advisory response")
for item in _array(document, "responses")
),
evidence_ids=_string_tuple(document.get("evidence_ids"), "evidence ids"),
reason_codes=_string_tuple(document.get("reason_codes"), "reason codes"),
)
@dataclass(frozen=True, slots=True)
class ObjectUnderstanding:
"""Complete machine projection: geometry, semantics, state, risk and lineage."""
understanding_id: str
vocabulary_id: str
generated_monotonic_ns: int
observation: ObstacleObservation
hypotheses: tuple[SemanticHypothesis, ...]
semantic: SemanticDecision
state: ObjectStateEstimate
risk: AdvisoryRiskAssessment
provenance: tuple[EvidenceProvenance, ...]
authority: FalseAuthority = FalseAuthority()
def __post_init__(self) -> None:
_identifier(self.understanding_id, "understanding id")
_identifier(self.vocabulary_id, "semantic vocabulary id")
_nonnegative_integer(self.generated_monotonic_ns, "generation time")
if not isinstance(self.observation, ObstacleObservation):
raise ObjectUnderstandingError("object geometry observation is invalid")
if (
not isinstance(self.hypotheses, tuple)
or any(not isinstance(item, SemanticHypothesis) for item in self.hypotheses)
or len(self.hypotheses) > MAX_SEMANTIC_HYPOTHESES
):
raise ObjectUnderstandingError("semantic hypothesis set is invalid")
if tuple(item.rank for item in self.hypotheses) != tuple(
range(1, len(self.hypotheses) + 1)
):
raise ObjectUnderstandingError("semantic hypotheses must have contiguous ranks")
class_ids = tuple(item.class_id for item in self.hypotheses)
if len(set(class_ids)) != len(class_ids):
raise ObjectUnderstandingError("semantic hypothesis classes must be unique")
if any(
self.hypotheses[index].confidence < self.hypotheses[index + 1].confidence
for index in range(len(self.hypotheses) - 1)
):
raise ObjectUnderstandingError("semantic hypotheses must be ordered by confidence")
if not isinstance(self.semantic, SemanticDecision):
raise ObjectUnderstandingError("semantic decision is invalid")
if not isinstance(self.state, ObjectStateEstimate):
raise ObjectUnderstandingError("object state is invalid")
if not isinstance(self.risk, AdvisoryRiskAssessment):
raise ObjectUnderstandingError("advisory risk is invalid")
if not isinstance(self.authority, FalseAuthority):
raise ObjectUnderstandingError("object understanding authority is invalid")
if not isinstance(self.provenance, tuple) or any(
not isinstance(item, EvidenceProvenance) for item in self.provenance
):
raise ObjectUnderstandingError("evidence provenance is invalid")
evidence_ids = tuple(item.evidence_id for item in self.provenance)
if len(set(evidence_ids)) != len(evidence_ids):
raise ObjectUnderstandingError("evidence provenance ids must be unique")
if any(
item.source_id != self.observation.source_id
or item.frame_id != self.observation.frame_id
for item in self.provenance
):
raise ObjectUnderstandingError("object evidence escaped its geometry source frame")
known_evidence = set(evidence_ids)
claimed_evidence = (
{evidence_id for item in self.hypotheses for evidence_id in item.evidence_ids}
| set(self.state.evidence_ids)
| set(self.risk.evidence_ids)
)
if claimed_evidence - known_evidence:
raise ObjectUnderstandingError("object claim references unknown evidence")
if (
self.semantic.resolution
in {
SemanticResolution.AMBIGUOUS,
SemanticResolution.CONFLICT,
}
and not self.hypotheses
):
raise ObjectUnderstandingError("ambiguous or conflicting semantics require hypotheses")
if self.semantic.resolution is SemanticResolution.CONFLICT and len(self.hypotheses) < 2:
raise ObjectUnderstandingError("semantic conflict requires two hypotheses")
if self.semantic.resolution is SemanticResolution.SELECTED:
selected = next(
(
item
for item in self.hypotheses
if item.class_id == self.semantic.selected_class_id
),
None,
)
if selected is None or selected.confidence != self.semantic.selected_confidence:
raise ObjectUnderstandingError(
"selected semantics must match one ranked hypothesis"
)
@property
def occupancy_identity(self) -> str:
"""Semantic or risk changes never replace the geometry-owned identity."""
return self.observation.occupancy_identity
def to_dict(self) -> dict[str, object]:
return {
"schema_version": OBJECT_UNDERSTANDING_SCHEMA,
"understanding_id": self.understanding_id,
"vocabulary_id": self.vocabulary_id,
"generated_monotonic_ns": self.generated_monotonic_ns,
"observation": self.observation.to_dict(),
"hypotheses": [item.to_dict() for item in self.hypotheses],
"semantic": self.semantic.to_dict(),
"state": self.state.to_dict(),
"risk": self.risk.to_dict(),
"provenance": [item.to_dict() for item in self.provenance],
"authority": self.authority.to_dict(),
}
@classmethod
def from_dict(cls, value: object) -> ObjectUnderstanding:
document = _contract(
value,
OBJECT_UNDERSTANDING_SCHEMA,
{
"understanding_id",
"vocabulary_id",
"generated_monotonic_ns",
"observation",
"hypotheses",
"semantic",
"state",
"risk",
"provenance",
"authority",
},
"object understanding",
)
return cls(
understanding_id=_string(document, "understanding_id"),
vocabulary_id=_string(document, "vocabulary_id"),
generated_monotonic_ns=_integer(document, "generated_monotonic_ns"),
observation=ObstacleObservation.from_dict(document.get("observation")),
hypotheses=tuple(
SemanticHypothesis.from_dict(item) for item in _array(document, "hypotheses")
),
semantic=SemanticDecision.from_dict(document.get("semantic")),
state=ObjectStateEstimate.from_dict(document.get("state")),
risk=AdvisoryRiskAssessment.from_dict(document.get("risk")),
provenance=tuple(
EvidenceProvenance.from_dict(item) for item in _array(document, "provenance")
),
authority=FalseAuthority.from_dict(document.get("authority")),
)
@dataclass(frozen=True, slots=True)
class CanonicalObjectClass:
"""One class in the bounded experimental object vocabulary."""
class_id: str
parent_id: str | None
aliases: tuple[str, ...]
agency_prior: AgencyState
risk_traits: tuple[str, ...]
def __post_init__(self) -> None:
if not isinstance(self.agency_prior, AgencyState):
raise ObjectUnderstandingError("canonical agency prior is invalid")
_identifier(self.class_id, "canonical class id")
_optional_identifier(self.parent_id, "canonical parent id")
if not self.aliases:
raise ObjectUnderstandingError("canonical class aliases must be nonempty")
normalized = tuple(normalize_raw_label(item) for item in self.aliases)
if len(set(normalized)) != len(normalized):
raise ObjectUnderstandingError("canonical class aliases must be unique")
_unique_identifiers(self.risk_traits, "class risk traits", allow_empty=True)
@dataclass(frozen=True, slots=True)
class ObjectSemanticVocabulary:
"""Loaded executable vocabulary profile; not a runtime ontology service."""
vocabulary_id: str
status: str
scope: str
classes: tuple[CanonicalObjectClass, ...]
max_hypotheses: int
def __post_init__(self) -> None:
_identifier(self.vocabulary_id, "vocabulary id")
if self.status != "experimental":
raise ObjectUnderstandingError("object vocabulary must remain experimental")
_identifier(self.scope, "vocabulary scope")
if not 1 <= self.max_hypotheses <= MAX_SEMANTIC_HYPOTHESES:
raise ObjectUnderstandingError("vocabulary top-k bound is invalid")
if not self.classes:
raise ObjectUnderstandingError("object vocabulary must declare classes")
by_id = {item.class_id: item for item in self.classes}
if len(by_id) != len(self.classes):
raise ObjectUnderstandingError("canonical class ids must be unique")
for item in self.classes:
if item.parent_id is not None and item.parent_id not in by_id:
raise ObjectUnderstandingError("canonical class parent is undeclared")
seen = {item.class_id}
parent_id = item.parent_id
while parent_id is not None:
if parent_id in seen:
raise ObjectUnderstandingError("canonical class hierarchy is cyclic")
seen.add(parent_id)
parent_id = by_id[parent_id].parent_id
aliases = [normalize_raw_label(alias) for item in self.classes for alias in item.aliases]
if len(set(aliases)) != len(aliases):
raise ObjectUnderstandingError("canonical aliases must be globally unique")
def class_definition(self, class_id: str) -> CanonicalObjectClass:
for item in self.classes:
if item.class_id == class_id:
return item
raise ObjectUnderstandingError("canonical class is undeclared")
def resolve_label(self, raw_label: str) -> str | None:
normalized = normalize_raw_label(raw_label)
for item in self.classes:
if normalized in {normalize_raw_label(alias) for alias in item.aliases}:
return item.class_id
return None
def ancestors(self, class_id: str) -> tuple[str, ...]:
by_id = {item.class_id: item for item in self.classes}
current = self.class_definition(class_id)
result: list[str] = []
while current.parent_id is not None:
result.append(current.parent_id)
current = by_id[current.parent_id]
return tuple(result)
def load_object_semantic_vocabulary(path: Path) -> ObjectSemanticVocabulary:
"""Load and fail-close an executable vocabulary profile."""
try:
document = json.loads(path.expanduser().resolve(strict=True).read_text("utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise ObjectUnderstandingError("object vocabulary cannot be read") from exc
root = _object(document, "object semantic vocabulary")
_exact_keys(
root,
{
"schema_version",
"vocabulary_id",
"status",
"scope",
"classes",
"policies",
},
"object semantic vocabulary",
)
if root.get("schema_version") != OBJECT_SEMANTIC_VOCABULARY_SCHEMA:
raise ObjectUnderstandingError("object vocabulary schema is incompatible")
policies = _object(root.get("policies"), "object vocabulary policies")
_exact_keys(
policies,
{
"occupancy_independent_of_semantics",
"unknown_preserves_obstacle",
"class_prior_is_not_observed_state",
"risk_is_advisory_only",
"planner_command_authority",
"max_hypotheses",
},
"object vocabulary policies",
)
required_true = (
"occupancy_independent_of_semantics",
"unknown_preserves_obstacle",
"class_prior_is_not_observed_state",
"risk_is_advisory_only",
)
if (
any(policies.get(key) is not True for key in required_true)
or policies.get("planner_command_authority") is not False
):
raise ObjectUnderstandingError("object vocabulary authority policy changed")
classes: list[CanonicalObjectClass] = []
for raw in _array(root, "classes"):
row = _object(raw, "canonical object class")
_exact_keys(
row,
{"class_id", "parent_id", "aliases", "agency_prior", "risk_traits"},
"canonical object class",
)
classes.append(
CanonicalObjectClass(
class_id=_string(row, "class_id"),
parent_id=_optional_string(row.get("parent_id"), "parent id"),
aliases=_string_tuple(row.get("aliases"), "aliases"),
agency_prior=_enum(AgencyState, row.get("agency_prior"), "agency prior"),
risk_traits=_string_tuple(row.get("risk_traits"), "risk traits"),
)
)
return ObjectSemanticVocabulary(
vocabulary_id=_string(root, "vocabulary_id"),
status=_string(root, "status"),
scope=_string(root, "scope"),
classes=tuple(classes),
max_hypotheses=_integer(policies, "max_hypotheses"),
)
def validate_object_understanding(
value: ObjectUnderstanding,
vocabulary: ObjectSemanticVocabulary,
) -> None:
"""Validate canonical class references without changing the document."""
if not isinstance(value, ObjectUnderstanding):
raise ObjectUnderstandingError("object understanding is invalid")
if value.vocabulary_id != vocabulary.vocabulary_id:
raise ObjectUnderstandingError("object understanding vocabulary changed")
declared = {item.class_id for item in vocabulary.classes}
referenced = {item.class_id for item in value.hypotheses}
if value.semantic.selected_class_id is not None:
referenced.add(value.semantic.selected_class_id)
if referenced - declared:
raise ObjectUnderstandingError("object understanding uses undeclared classes")
if len(value.hypotheses) > vocabulary.max_hypotheses:
raise ObjectUnderstandingError("object understanding exceeds vocabulary top-k")
def normalize_raw_label(value: str) -> str:
"""Normalize a provider label only for alias lookup, never as class truth."""
_label(value, "raw semantic label")
normalized = re.sub(r"[_\s-]+", "_", value.strip().lower())
if _NORMALIZED_LABEL.fullmatch(normalized) is None:
raise ObjectUnderstandingError("raw semantic label cannot be normalized")
return normalized
def _contract(
value: object,
schema: str,
fields: set[str],
label: str,
) -> dict[str, object]:
document = _object(value, label)
_exact_keys(document, {"schema_version", *fields}, label)
if document.get("schema_version") != schema:
raise ObjectUnderstandingError(f"{label} schema is incompatible")
return document
def _object(value: object, label: str) -> dict[str, object]:
if not isinstance(value, dict) or any(not isinstance(key, str) for key in value):
raise ObjectUnderstandingError(f"{label} must be an object")
return value
def _exact_keys(document: dict[str, object], expected: set[str], label: str) -> None:
if set(document) != expected:
raise ObjectUnderstandingError(f"{label} fields are incompatible")
def _array(document: dict[str, object], key: str) -> list[object]:
value = document.get(key)
if not isinstance(value, list):
raise ObjectUnderstandingError(f"{key} must be an array")
return value
def _string(document: dict[str, object], key: str) -> str:
return _string_value(document.get(key), key)
def _string_value(value: object, label: str) -> str:
if not isinstance(value, str) or not value:
raise ObjectUnderstandingError(f"{label} must be a nonempty string")
return value
def _optional_string(value: object, label: str) -> str | None:
if value is None:
return None
return _string_value(value, label)
def _integer(document: dict[str, object], key: str) -> int:
value = document.get(key)
if not isinstance(value, int) or isinstance(value, bool):
raise ObjectUnderstandingError(f"{key} must be an integer")
return value
def _number(document: dict[str, object], key: str) -> float:
value = document.get(key)
if (
not isinstance(value, (int, float))
or isinstance(value, bool)
or not math.isfinite(float(value))
):
raise ObjectUnderstandingError(f"{key} must be finite")
return float(value)
def _optional_number(value: object, label: str) -> float | None:
if value is None:
return None
if (
not isinstance(value, (int, float))
or isinstance(value, bool)
or not math.isfinite(float(value))
):
raise ObjectUnderstandingError(f"{label} must be finite")
return float(value)
def _identifier(value: str, label: str) -> None:
if not isinstance(value, str) or _IDENTIFIER.fullmatch(value) is None:
raise ObjectUnderstandingError(f"{label} is not a safe identifier")
def _optional_identifier(value: str | None, label: str) -> None:
if value is not None:
_identifier(value, label)
def _label(value: str, label: str) -> None:
if (
not isinstance(value, str)
or not value
or value != value.strip()
or len(value) > 120
or any(ord(character) < 32 for character in value)
):
raise ObjectUnderstandingError(f"{label} is invalid")
def _nonnegative_integer(value: object, label: str) -> int:
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
raise ObjectUnderstandingError(f"{label} must be a nonnegative integer")
return value
def _positive_integer(value: object, label: str) -> int:
result = _nonnegative_integer(value, label)
if result == 0:
raise ObjectUnderstandingError(f"{label} must be positive")
return result
def _confidence(value: object, label: str) -> float:
if (
not isinstance(value, (int, float))
or isinstance(value, bool)
or not math.isfinite(float(value))
or not 0.0 <= float(value) <= 1.0
):
raise ObjectUnderstandingError(f"{label} must be within [0, 1]")
return float(value)
def _unique_identifiers(
values: tuple[str, ...],
label: str,
*,
allow_empty: bool = False,
) -> None:
if (not values and not allow_empty) or len(set(values)) != len(values):
raise ObjectUnderstandingError(f"{label} must be unique")
for value in values:
_identifier(value, label)
def _unique_enum_values(
values: tuple[AdvisoryResponse, ...],
label: str,
*,
allow_empty: bool,
) -> None:
if (not values and not allow_empty) or len(set(values)) != len(values):
raise ObjectUnderstandingError(f"{label} must be unique")
if any(not isinstance(value, AdvisoryResponse) for value in values):
raise ObjectUnderstandingError(f"{label} are invalid")
def _string_tuple(value: object, label: str) -> tuple[str, ...]:
if not isinstance(value, list):
raise ObjectUnderstandingError(f"{label} must be an array")
return tuple(_string_value(item, label) for item in value)
def _enum[ENUM: StrEnum](
enum_type: type[ENUM],
value: object,
label: str,
) -> ENUM:
if not isinstance(value, str):
raise ObjectUnderstandingError(f"{label} must be a string")
try:
return enum_type(value)
except ValueError as exc:
raise ObjectUnderstandingError(f"{label} is incompatible") from exc
__all__ = [
"MAX_SEMANTIC_HYPOTHESES",
"OBJECT_SEMANTIC_VOCABULARY_SCHEMA",
"OBJECT_UNDERSTANDING_SCHEMA",
"AdvisoryResponse",
"AdvisoryRiskAssessment",
"AgencyState",
"CanonicalObjectClass",
"EvidenceKind",
"EvidenceProvenance",
"ObjectSemanticVocabulary",
"ObjectStateEstimate",
"ObjectUnderstanding",
"ObjectUnderstandingError",
"RiskBasis",
"RiskLevel",
"SemanticDecision",
"SemanticHypothesis",
"SemanticResolution",
"StateBasis",
"load_object_semantic_vocabulary",
"normalize_raw_label",
"validate_object_understanding",
]