InfospaceConfig (topic, disciplines, schemas, competency questions, viability thresholds, pipeline) with YAML load/save and directory discovery. InfospaceState aggregates entities, evaluations, and viability checks for status reporting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
142 lines
4.3 KiB
Python
142 lines
4.3 KiB
Python
"""
|
|
Infospace runtime state.
|
|
|
|
Computed from the current entities, evaluations, and metrics on disk.
|
|
Provides the data behind ``markitect infospace status`` and
|
|
``markitect infospace viability``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from markitect.infospace.config import InfospaceConfig, ViabilityThreshold
|
|
from markitect.infospace.models import EntityMeta
|
|
from markitect.infospace.evaluation import EvaluationSnapshot
|
|
|
|
|
|
@dataclass
|
|
class ViabilityResult:
|
|
"""Result of checking a single viability threshold."""
|
|
|
|
metric: str
|
|
value: float
|
|
threshold: ViabilityThreshold
|
|
passed: bool
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
d: Dict[str, Any] = {
|
|
"metric": self.metric,
|
|
"value": self.value,
|
|
"passed": self.passed,
|
|
}
|
|
if self.threshold.min is not None:
|
|
d["min"] = self.threshold.min
|
|
if self.threshold.max is not None:
|
|
d["max"] = self.threshold.max
|
|
return d
|
|
|
|
|
|
@dataclass
|
|
class InfospaceState:
|
|
"""Current runtime state of an infospace.
|
|
|
|
Aggregates entity metadata, evaluation results, and viability
|
|
checks into a single queryable object.
|
|
"""
|
|
|
|
config: InfospaceConfig
|
|
entities: List[EntityMeta] = field(default_factory=list)
|
|
latest_snapshot: Optional[EvaluationSnapshot] = None
|
|
viability_results: List[ViabilityResult] = field(default_factory=list)
|
|
computed_at: datetime = field(default_factory=datetime.utcnow)
|
|
|
|
@property
|
|
def entity_count(self) -> int:
|
|
return len(self.entities)
|
|
|
|
@property
|
|
def topic_name(self) -> str:
|
|
return self.config.topic.name
|
|
|
|
@property
|
|
def is_viable(self) -> bool:
|
|
"""``True`` if all viability thresholds are met."""
|
|
if not self.viability_results:
|
|
return False
|
|
return all(r.passed for r in self.viability_results)
|
|
|
|
@property
|
|
def viability_pass_count(self) -> int:
|
|
return sum(1 for r in self.viability_results if r.passed)
|
|
|
|
@property
|
|
def viability_total_count(self) -> int:
|
|
return len(self.viability_results)
|
|
|
|
@property
|
|
def domains(self) -> List[str]:
|
|
"""Distinct domain values across all entities."""
|
|
return sorted({e.domain for e in self.entities if e.domain})
|
|
|
|
@property
|
|
def has_evaluations(self) -> bool:
|
|
return self.latest_snapshot is not None
|
|
|
|
def check_viability(self, metrics: Dict[str, float]) -> List[ViabilityResult]:
|
|
"""Check *metrics* against the configured viability thresholds.
|
|
|
|
Updates :attr:`viability_results` and returns the results.
|
|
"""
|
|
results: List[ViabilityResult] = []
|
|
for name, threshold in self.config.viability.items():
|
|
value = metrics.get(name, 0.0)
|
|
results.append(ViabilityResult(
|
|
metric=name,
|
|
value=value,
|
|
threshold=threshold,
|
|
passed=threshold.check(value),
|
|
))
|
|
self.viability_results = results
|
|
return results
|
|
|
|
def summary(self) -> Dict[str, Any]:
|
|
"""Return a summary dict suitable for display or serialisation."""
|
|
d: Dict[str, Any] = {
|
|
"topic": self.topic_name,
|
|
"entity_count": self.entity_count,
|
|
"domains": self.domains,
|
|
"has_evaluations": self.has_evaluations,
|
|
}
|
|
if self.viability_results:
|
|
d["viable"] = self.is_viable
|
|
d["viability_pass"] = self.viability_pass_count
|
|
d["viability_total"] = self.viability_total_count
|
|
if self.latest_snapshot:
|
|
d["last_evaluated"] = self.latest_snapshot.created_at.isoformat()
|
|
return d
|
|
|
|
|
|
def build_state(
|
|
config: InfospaceConfig,
|
|
entities: Optional[List[EntityMeta]] = None,
|
|
snapshot: Optional[EvaluationSnapshot] = None,
|
|
metrics: Optional[Dict[str, float]] = None,
|
|
) -> InfospaceState:
|
|
"""Build an :class:`InfospaceState` from available data.
|
|
|
|
This is a convenience function that assembles the state object
|
|
and optionally runs viability checks if *metrics* are provided.
|
|
"""
|
|
state = InfospaceState(
|
|
config=config,
|
|
entities=entities or [],
|
|
latest_snapshot=snapshot,
|
|
)
|
|
if metrics is not None:
|
|
state.check_viability(metrics)
|
|
return state
|