feat: harden seat intent and expand HIL coverage
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
|
||||
|
||||
|
||||
SUITE_PATH = REPOSITORY_ROOT / "tests" / "fixtures" / "seat_frontend_hil_suite.json"
|
||||
EXPECTED_CAPABILITIES = {
|
||||
"heating": "seat.set_heating",
|
||||
"ventilation": "seat.set_ventilation",
|
||||
"longitudinal": "seat.set_longitudinal_position",
|
||||
"cushion_tilt": "seat.set_cushion_tilt",
|
||||
"backrest_recline": "seat.set_backrest_recline",
|
||||
"legrest_height": "seat.set_legrest_height",
|
||||
}
|
||||
|
||||
|
||||
def expand_entries(entries: list[dict], group: str | None = None) -> list[dict]:
|
||||
expanded = []
|
||||
for entry in entries:
|
||||
base = {key: value for key, value in entry.items() if key != "variants"}
|
||||
if group is not None:
|
||||
base["group"] = group
|
||||
variants = entry.get("variants")
|
||||
if not variants:
|
||||
expanded.append(base)
|
||||
continue
|
||||
expanded.extend({**base, **variant} for variant in variants)
|
||||
return expanded
|
||||
|
||||
|
||||
class SeatFrontendInputCoverageTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.temporary_directory = tempfile.TemporaryDirectory()
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED": "false"},
|
||||
clear=False,
|
||||
):
|
||||
cls.service = SeatDemoService(REPOSITORY_ROOT)
|
||||
cls.service.artifact_root = Path(cls.temporary_directory.name)
|
||||
cls.suite = json.loads(SUITE_PATH.read_text(encoding="utf-8"))
|
||||
cls.positive_cases = expand_entries(cls.suite["cases"]) + expand_entries(
|
||||
cls.suite["mode_cases"], "mode"
|
||||
)
|
||||
cls.model_cases = expand_entries(cls.suite.get("model_cases", []), "model")
|
||||
cls.rejection_cases = expand_entries(cls.suite["rejection_cases"], "safety")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls) -> None:
|
||||
cls.temporary_directory.cleanup()
|
||||
|
||||
def test_suite_has_broad_equivalence_and_safety_coverage(self) -> None:
|
||||
self.assertGreaterEqual(len(self.positive_cases), 160)
|
||||
self.assertGreaterEqual(len(self.model_cases), 4)
|
||||
self.assertGreaterEqual(len(self.rejection_cases), 20)
|
||||
self.assertEqual(
|
||||
{case["group"] for case in self.positive_cases},
|
||||
{"thermal", "motion", "combined", "mode"},
|
||||
)
|
||||
self.assertEqual(
|
||||
{case["mode_id"] for case in self.positive_cases if case["group"] == "mode"},
|
||||
{
|
||||
"easy_entry_exit",
|
||||
"office",
|
||||
"queen_passenger_driving",
|
||||
"baby_nap",
|
||||
"cruise_sofa",
|
||||
"meditation_space",
|
||||
"leisure_lounger",
|
||||
"calf_decompression_float",
|
||||
"electric_beach_chair",
|
||||
"nap",
|
||||
"zero_gravity",
|
||||
},
|
||||
)
|
||||
|
||||
def test_all_expanded_case_ids_and_utterances_are_unique(self) -> None:
|
||||
cases = [*self.positive_cases, *self.model_cases, *self.rejection_cases]
|
||||
self.assertEqual(len({case["id"] for case in cases}), len(cases))
|
||||
self.assertEqual(len({case["utterance"] for case in cases}), len(cases))
|
||||
|
||||
def test_model_cases_require_gpt56_and_define_physical_targets(self) -> None:
|
||||
for case in self.model_cases:
|
||||
with self.subTest(case=case["id"]):
|
||||
self.assertEqual(case["provider"], "openai")
|
||||
self.assertEqual(
|
||||
case["expected_provider_prefix"], "openai:gpt-5.6-sol"
|
||||
)
|
||||
self.assertEqual(
|
||||
set(case["expected"]),
|
||||
{
|
||||
"longitudinal",
|
||||
"cushion_tilt",
|
||||
"backrest_recline",
|
||||
"legrest_height",
|
||||
},
|
||||
)
|
||||
def test_every_positive_utterance_generates_the_expected_dry_run(self) -> None:
|
||||
for case in self.positive_cases:
|
||||
with self.subTest(case=case["id"], utterance=case["utterance"]):
|
||||
result = self.service.process(
|
||||
case["utterance"],
|
||||
provider=case.get("provider", "local"),
|
||||
conversation_id="conv-coverage-positive-sequence",
|
||||
execution_mode="dry_run",
|
||||
vehicle_id=self.suite["vehicle_id"],
|
||||
)
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
if case.get("mode_id"):
|
||||
self.assertIn(
|
||||
case["mode_id"],
|
||||
{item["id"] for item in result["matched_modes"]},
|
||||
)
|
||||
seat_config = result["function_configs"].get("seat") or result[
|
||||
"function_config"
|
||||
]
|
||||
self.assertIsNotNone(seat_config)
|
||||
operations = seat_config["spec"]["operations"]
|
||||
by_capability = {item["capability"]: item for item in operations}
|
||||
passenger = result["actuator_state"]["passenger"]
|
||||
for field, expected in case["expected"].items():
|
||||
capability = EXPECTED_CAPABILITIES[field]
|
||||
self.assertIn(capability, by_capability)
|
||||
operation = by_capability[capability]
|
||||
self.assertEqual(operation["target"]["instance"], "passenger")
|
||||
self.assertEqual(operation["target"]["zone"], "front_right")
|
||||
if field in {"heating", "ventilation"}:
|
||||
if expected == 0:
|
||||
self.assertEqual(operation["parameters"]["state"], "off")
|
||||
else:
|
||||
self.assertEqual(passenger[field]["level"], expected)
|
||||
else:
|
||||
self.assertEqual(operation["parameters"]["position_pct"], expected)
|
||||
self.assertEqual(
|
||||
passenger["position"][field]["position_pct"], expected
|
||||
)
|
||||
expected_motion = case.get("expected_motion")
|
||||
if expected_motion:
|
||||
capability = EXPECTED_CAPABILITIES[expected_motion["axis"]]
|
||||
operation = by_capability[capability]
|
||||
self.assertIn("motion", operation)
|
||||
for field, expected in expected_motion.items():
|
||||
if field != "axis":
|
||||
self.assertEqual(operation["motion"][field], expected)
|
||||
|
||||
def test_every_rejection_utterance_stops_before_execution(self) -> None:
|
||||
for case in self.rejection_cases:
|
||||
with self.subTest(case=case["id"], utterance=case["utterance"]):
|
||||
result = self.service.process(
|
||||
case["utterance"],
|
||||
provider=case.get("provider", "local"),
|
||||
conversation_id=f"conv-coverage-{case['id'].lower()}",
|
||||
execution_mode="dry_run",
|
||||
vehicle_id=self.suite["vehicle_id"],
|
||||
)
|
||||
self.assertEqual(result["status"], case["expected_status"])
|
||||
self.assertIsNone(result["function_config"])
|
||||
self.assertEqual(result["function_configs"], {})
|
||||
self.assertIsNone(result["execution_report"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user