225 lines
7.0 KiB
Python
225 lines
7.0 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from k1link.laboratory.vegetation_mission_policy import (
|
|
VegetationMissionPolicyError,
|
|
load_vegetation_mission_policy,
|
|
load_vegetation_provider_label_map,
|
|
map_provider_material,
|
|
resolve_terrain_policy,
|
|
)
|
|
|
|
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
|
POLICY_PATH = (
|
|
REPOSITORY_ROOT / "config" / "perception" / "lab-v1-vegetation-mission-policy-v1.json"
|
|
)
|
|
CANDIDATE_MANIFEST_PATH = (
|
|
REPOSITORY_ROOT
|
|
/ "config"
|
|
/ "perception"
|
|
/ "lab-v1-vegetation-candidate-manifest-v1.json"
|
|
)
|
|
PROVIDER_LABEL_MAP_PATH = (
|
|
REPOSITORY_ROOT
|
|
/ "config"
|
|
/ "perception"
|
|
/ "lab-v1-vegetation-provider-label-map-v1.json"
|
|
)
|
|
|
|
|
|
def _policy() -> dict[str, object]:
|
|
return load_vegetation_mission_policy(POLICY_PATH, repository_root=REPOSITORY_ROOT)
|
|
|
|
|
|
def _provider_label_map() -> dict[str, object]:
|
|
policy = _policy()
|
|
return load_vegetation_provider_label_map(PROVIDER_LABEL_MAP_PATH, policy=policy)
|
|
|
|
|
|
def test_presets_are_defaults_and_explicit_mission_rule_wins() -> None:
|
|
policy = _policy()
|
|
|
|
urban_default = resolve_terrain_policy(
|
|
policy,
|
|
preset_id="urban",
|
|
material_class="grass",
|
|
evidence_state="VEGETATION_POTENTIALLY_TRAVERSABLE",
|
|
)
|
|
assert urban_default.requested_action == "NO_GO"
|
|
assert urban_default.effective_action == "NO_GO"
|
|
assert urban_default.policy_source == "selected_preset_default"
|
|
|
|
explicit_allow = resolve_terrain_policy(
|
|
policy,
|
|
preset_id="urban",
|
|
material_class="grass",
|
|
evidence_state="VEGETATION_POTENTIALLY_TRAVERSABLE",
|
|
mission_rules={"grass": "ALLOW"},
|
|
)
|
|
assert explicit_allow.requested_action == "ALLOW"
|
|
assert explicit_allow.effective_action == "ALLOW"
|
|
assert explicit_allow.policy_source == "explicit_mission_rule"
|
|
assert explicit_allow.actuation_authority is False
|
|
|
|
|
|
def test_offroad_grass_is_high_cost_until_mission_changes_it() -> None:
|
|
decision = resolve_terrain_policy(
|
|
_policy(),
|
|
preset_id="offroad",
|
|
material_class="grass",
|
|
evidence_state="VEGETATION_POTENTIALLY_TRAVERSABLE",
|
|
)
|
|
assert decision.requested_action == "HIGH_COST"
|
|
assert decision.effective_action == "HIGH_COST"
|
|
assert decision.safety_reason is None
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"state",
|
|
[
|
|
"UNOBSERVED",
|
|
"NEGATIVE_OR_UNSUPPORTED",
|
|
"RIGID_OR_UNKNOWN_OBSTACLE",
|
|
"VEGETATION_UNKNOWN",
|
|
"VEGETATION_WITH_RIGID_GEOMETRY",
|
|
],
|
|
)
|
|
def test_mission_allow_never_clears_hard_safety_interlock(state: str) -> None:
|
|
decision = resolve_terrain_policy(
|
|
_policy(),
|
|
preset_id="offroad",
|
|
material_class="grass",
|
|
evidence_state=state,
|
|
mission_rules={"grass": "ALLOW"},
|
|
)
|
|
assert decision.requested_action == "ALLOW"
|
|
assert decision.effective_action == "NO_GO"
|
|
assert decision.safety_reason == f"hard_safety_interlock:{state}"
|
|
|
|
|
|
def test_missing_semantics_and_support_fail_closed() -> None:
|
|
decision = resolve_terrain_policy(
|
|
_policy(),
|
|
preset_id="rural",
|
|
material_class=None,
|
|
evidence_state=None,
|
|
)
|
|
assert decision.material_class == "vegetation_unknown"
|
|
assert decision.evidence_state == "UNOBSERVED"
|
|
assert decision.effective_action == "NO_GO"
|
|
|
|
|
|
def test_woody_material_cannot_be_enabled_by_mission() -> None:
|
|
with pytest.raises(
|
|
VegetationMissionPolicyError,
|
|
match="material cannot be overridden by a mission: tree_or_trunk",
|
|
):
|
|
resolve_terrain_policy(
|
|
_policy(),
|
|
preset_id="offroad",
|
|
material_class="tree_or_trunk",
|
|
evidence_state="SUPPORTED_GROUND",
|
|
mission_rules={"tree_or_trunk": "ALLOW"},
|
|
)
|
|
|
|
|
|
def test_unknown_material_is_treated_as_unknown_vegetation() -> None:
|
|
decision = resolve_terrain_policy(
|
|
_policy(),
|
|
preset_id="offroad",
|
|
material_class="provider_specific_leaf",
|
|
evidence_state="SUPPORTED_GROUND",
|
|
)
|
|
assert decision.material_class == "vegetation_unknown"
|
|
assert decision.effective_action == "NO_GO"
|
|
assert decision.safety_reason == "hard_safety_interlock:vegetation_unknown"
|
|
|
|
|
|
def test_candidate_manifest_freezes_unique_assets_sources_and_no_authority() -> None:
|
|
manifest = json.loads(CANDIDATE_MANIFEST_PATH.read_text(encoding="utf-8"))
|
|
assert manifest["schema_version"] == "missioncore.vegetation-candidate-manifest/v1"
|
|
assert manifest["worker"]["canonical_runtime_mutation_allowed"] is False
|
|
|
|
sources = manifest["upstream_sources"]
|
|
assert {source["source_id"] for source in sources} == {
|
|
"wildscenes",
|
|
"rellis-3d",
|
|
"orfd-offnet",
|
|
}
|
|
assert all(len(source["revision"]) == 40 for source in sources)
|
|
|
|
candidates = manifest["candidate_assets"]
|
|
candidate_ids = [candidate["candidate_id"] for candidate in candidates]
|
|
assert len(candidate_ids) == len(set(candidate_ids)) == 10
|
|
assert all(candidate["size_bytes"] > 0 for candidate in candidates)
|
|
assert all(len(candidate["sha256"]) == 64 for candidate in candidates)
|
|
assert manifest["invariants"]["missing_or_unknown_is_free"] is False
|
|
assert manifest["invariants"]["navigation_authority"] is False
|
|
assert manifest["invariants"]["actuation_authority"] is False
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("provider_id", "provider_label", "material"),
|
|
[
|
|
("goose-fine-64", "low_grass", "grass"),
|
|
("goose-fine-64", "high_grass", "herbaceous_vegetation"),
|
|
("goose-fine-64", "bush", "woody_shrub"),
|
|
("goose-fine-64", "tree_trunk", "tree_or_trunk"),
|
|
("wildscenes-19", "tree-trunk", "tree_or_trunk"),
|
|
("rellis-20", "tree", "tree_or_trunk"),
|
|
],
|
|
)
|
|
def test_fine_provider_labels_map_to_separate_material_claims(
|
|
provider_id: str,
|
|
provider_label: str,
|
|
material: str,
|
|
) -> None:
|
|
assert (
|
|
map_provider_material(
|
|
_provider_label_map(),
|
|
provider_id=provider_id,
|
|
provider_label=provider_label,
|
|
)
|
|
== material
|
|
)
|
|
|
|
|
|
def test_coarse_vegetation_never_becomes_grass_permission() -> None:
|
|
material = map_provider_material(
|
|
_provider_label_map(),
|
|
provider_id="goose-category-12",
|
|
provider_label="vegetation",
|
|
)
|
|
assert material == "vegetation_unknown"
|
|
decision = resolve_terrain_policy(
|
|
_policy(),
|
|
preset_id="offroad",
|
|
material_class=material,
|
|
evidence_state="SUPPORTED_GROUND",
|
|
)
|
|
assert decision.effective_action == "NO_GO"
|
|
|
|
|
|
def test_binary_freespace_and_unmapped_labels_make_no_material_claim() -> None:
|
|
label_map = _provider_label_map()
|
|
assert (
|
|
map_provider_material(
|
|
label_map,
|
|
provider_id="orfd-binary-freespace",
|
|
provider_label="freespace",
|
|
)
|
|
is None
|
|
)
|
|
assert (
|
|
map_provider_material(
|
|
label_map,
|
|
provider_id="goose-fine-64",
|
|
provider_label="person",
|
|
)
|
|
is None
|
|
)
|