spinor-os / tests /test_workflow.py
luguog's picture
Deploy spinor-os as static HF Space receiver
bf3cbf1 verified
Raw
History Blame Contribute Delete
4.27 kB
"""Tests for the experiment-governance workflow state machine."""
from __future__ import annotations
import pytest
from spinor_os.config import AttributionMethod, Direction, EventType, LoopStage, MaturityStage
from spinor_os.models import (
AttributionClaim,
Event,
Experiment,
Hypothesis,
Mission,
PredictedEffect,
)
from spinor_os.workflow import (
ExperimentGovernanceWorkflow,
WorkflowContext,
WorkflowTransitionError,
)
def make_hypothesis() -> Hypothesis:
return Hypothesis(
statement="X increases Y",
causal_claim="X causes Y to increase.",
falsification_criteria=["Y does not increase"],
predicted_effect=PredictedEffect(
metric="y_rate",
direction=Direction.INCREASE,
magnitude=0.1,
unit="pp",
timing="7d",
confidence=0.9,
),
employee_owner="emp-001",
)
def make_context(stage: LoopStage, **overrides) -> WorkflowContext:
hypothesis = make_hypothesis()
experiment = Experiment(hypothesis_id=hypothesis.hypothesis_id)
experiment.set_stage(stage)
return WorkflowContext(
experiment=experiment,
hypothesis=hypothesis,
**overrides,
)
def test_allowed_next_from_research():
wf = ExperimentGovernanceWorkflow()
next_stages = wf.get_allowed_next(LoopStage.RESEARCH)
assert LoopStage.ALLOCATE in next_stages
assert LoopStage.DISASSEMBLE in next_stages
def test_invalid_transition_raises():
wf = ExperimentGovernanceWorkflow()
ctx = make_context(LoopStage.RESEARCH)
with pytest.raises(WorkflowTransitionError):
wf.advance(LoopStage.RESEARCH, LoopStage.SYSTEMIZE, ctx)
def test_gates_require_mission_before_execute():
wf = ExperimentGovernanceWorkflow()
ctx = make_context(LoopStage.ALLOCATE)
ok, reason = wf.can_advance(LoopStage.ALLOCATE, LoopStage.EXECUTE, ctx)
assert not ok
assert "mission" in reason.lower()
def test_gates_require_events_before_observe():
wf = ExperimentGovernanceWorkflow()
ctx = make_context(LoopStage.EXECUTE)
mission = Mission(
experiment_id=ctx.experiment.experiment_id,
hypothesis_id=ctx.hypothesis.hypothesis_id,
employee_id="emp-001",
modification="mod",
customer_segment="a",
territory="b",
timing="c",
)
ctx.mission = mission
ok, _ = wf.can_advance(LoopStage.EXECUTE, LoopStage.OBSERVE, ctx)
assert not ok
def test_falsify_to_replicate_requires_significant_claim():
wf = ExperimentGovernanceWorkflow()
hypothesis = make_hypothesis()
experiment = Experiment(hypothesis_id=hypothesis.hypothesis_id)
experiment.set_stage(LoopStage.ATTRIBUTE)
claim = AttributionClaim(
experiment_id=experiment.experiment_id,
hypothesis_id=hypothesis.hypothesis_id,
outcome_metric="y_rate",
outcome_value=0.05,
counterfactual_estimate=0.01,
method=AttributionMethod.RCT,
confidence=0.4,
falsification_survived=True,
)
ctx = WorkflowContext(experiment=experiment, hypothesis=hypothesis, claim=claim)
ok, _ = wf.can_advance(LoopStage.ATTRIBUTE, LoopStage.FALSIFY, ctx)
assert ok
# Advance to falsify first.
wf.advance(LoopStage.ATTRIBUTE, LoopStage.FALSIFY, ctx)
# Then try to replicate with a non-significant claim.
experiment.set_stage(LoopStage.FALSIFY)
with pytest.raises(WorkflowTransitionError):
wf.advance(LoopStage.FALSIFY, LoopStage.REPLICATE, ctx)
def test_suggest_next_falsify_significant():
wf = ExperimentGovernanceWorkflow()
hypothesis = make_hypothesis()
experiment = Experiment(hypothesis_id=hypothesis.hypothesis_id)
experiment.set_stage(LoopStage.FALSIFY)
claim = AttributionClaim(
experiment_id=experiment.experiment_id,
hypothesis_id=hypothesis.hypothesis_id,
outcome_metric="y_rate",
outcome_value=0.2,
counterfactual_estimate=0.05,
method=AttributionMethod.RCT,
confidence=0.95,
falsification_survived=True,
)
ctx = WorkflowContext(experiment=experiment, hypothesis=hypothesis, claim=claim)
assert wf.suggest_next(ctx) == LoopStage.REPLICATE