feat: update CAN integration and T2G seat firmware
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -27,7 +27,7 @@ class SeatContractTests(unittest.TestCase):
|
||||
self.assertEqual(self.validator.validate(config), [])
|
||||
|
||||
def test_inferred_level_function_config_matches_both_schemas(self) -> None:
|
||||
parsed = parse_local_intent("主驾有点热,有点闷")
|
||||
parsed = parse_local_intent("副驾有点热,有点闷")
|
||||
config = build_function_config(parsed, "req-contract-002", "local_rules")
|
||||
|
||||
self.assertEqual(config["spec"]["operations"][0]["parameters"]["level"], 1)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -6,6 +6,7 @@ from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
|
||||
from cognitive_agent.infrastructure.llm.openai_responses import OpenAIIntentError
|
||||
|
||||
|
||||
class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
@@ -17,13 +18,13 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
|
||||
def test_supported_request_completes_full_dry_run(self) -> None:
|
||||
result = self.service.process(
|
||||
"打开主驾座椅通风,同时关闭副驾座椅加热", provider="local"
|
||||
"打开副驾座椅通风,同时关闭副驾座椅加热", provider="local"
|
||||
)
|
||||
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
self.assertEqual(len(result["function_config"]["spec"]["operations"]), 2)
|
||||
telemetry = result["execution_report"]["spec"]["results"][0]["telemetry"]
|
||||
self.assertEqual(telemetry["command_frame"]["data_hex"], "01 80")
|
||||
self.assertEqual(telemetry["command_frame"]["data_hex"], "00 90")
|
||||
self.assertTrue(Path(result["artifact_path"]).is_absolute())
|
||||
self.assertTrue(
|
||||
(self.service.artifact_root / result["request_id"] / "result.json").is_file()
|
||||
@@ -32,7 +33,7 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
self.assertEqual(self.service.contracts.validate(event), [])
|
||||
|
||||
def test_gear_request_is_preserved_and_simulated(self) -> None:
|
||||
result = self.service.process("主驾座椅加热开到三档", provider="local")
|
||||
result = self.service.process("副驾座椅加热开到三档", provider="local")
|
||||
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
operation = result["function_config"]["spec"]["operations"][0]
|
||||
@@ -48,21 +49,21 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
conversation_id = "conv-test-context"
|
||||
|
||||
first = self.service.process(
|
||||
"主驾有点热,有点闷",
|
||||
"副驾有点热,有点闷",
|
||||
provider="local",
|
||||
conversation_id=conversation_id,
|
||||
)
|
||||
second = self.service.process(
|
||||
"主驾还有点闷",
|
||||
"副驾还有点闷",
|
||||
provider="local",
|
||||
conversation_id=conversation_id,
|
||||
)
|
||||
|
||||
self.assertEqual(first["status"], "succeeded")
|
||||
self.assertEqual(first["actuator_state"]["driver"]["ventilation"]["level"], 1)
|
||||
self.assertEqual(first["actuator_state"]["passenger"]["ventilation"]["level"], 1)
|
||||
self.assertEqual(second["status"], "succeeded")
|
||||
self.assertEqual(second["conversation_turn"], 2)
|
||||
self.assertEqual(second["actuator_state"]["driver"]["ventilation"]["level"], 2)
|
||||
self.assertEqual(second["actuator_state"]["passenger"]["ventilation"]["level"], 2)
|
||||
operation = second["function_config"]["spec"]["operations"][0]
|
||||
self.assertEqual(operation["parameters"], {"state": "on", "level": 2})
|
||||
self.assertIn("提升到2档", "".join(second["decision_summary"]))
|
||||
@@ -94,12 +95,15 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
self.assertEqual(fourth_operation["target"]["instance"], "passenger")
|
||||
self.assertEqual(fourth_operation["capability"], "seat.set_ventilation")
|
||||
self.assertEqual(fourth_operation["parameters"], {"state": "off"})
|
||||
self.assertEqual(fourth["actuator_state"]["driver"]["heating"], {"state": "on", "level": 1})
|
||||
self.assertEqual(
|
||||
fourth["actuator_state"]["passenger"]["heating"],
|
||||
{"state": "on", "level": 1},
|
||||
)
|
||||
self.assertEqual(fourth["conversation_turn"], 4)
|
||||
|
||||
def test_compound_cold_and_stuffy_feedback_generates_balanced_dry_run(self) -> None:
|
||||
result = self.service.process(
|
||||
"主驾有点冷、有点闷",
|
||||
"副驾有点冷、有点闷",
|
||||
provider="local",
|
||||
conversation_id="conv-compound-comfort",
|
||||
)
|
||||
@@ -113,7 +117,10 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
command = result["command_plan"]["spec"]["commands"][0]
|
||||
self.assertEqual(
|
||||
command["signals"],
|
||||
{"Driver_Seat_Heating_S": 1, "Driver_Seat_Ventilation_S": 1},
|
||||
{
|
||||
"Front_Passenger_Seat_Heating_S": 1,
|
||||
"Front_Passenger_Seat_Ventilation_S": 1,
|
||||
},
|
||||
)
|
||||
self.assertIn(
|
||||
"COMBINED_THERMAL_SIMULATED",
|
||||
@@ -179,6 +186,30 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
"车上太闷了", context, self.service.capability_context
|
||||
)
|
||||
|
||||
def test_explicit_gpt_failure_falls_back_to_local_rules(self) -> None:
|
||||
self.service.openai.api_key = "test-key"
|
||||
|
||||
with patch.object(
|
||||
self.service.openai,
|
||||
"interpret",
|
||||
side_effect=OpenAIIntentError("invalid_api_key"),
|
||||
):
|
||||
result = self.service.process(
|
||||
"打开副驾座椅通风",
|
||||
"openai",
|
||||
"conv-explicit-gpt-fallback",
|
||||
)
|
||||
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
self.assertEqual(result["provider_used"], "local_rules_fallback")
|
||||
self.assertEqual(
|
||||
result["function_config"]["spec"]["operations"][0]["target"]["instance"],
|
||||
"passenger",
|
||||
)
|
||||
self.assertTrue(
|
||||
any("已降级到本地规则解析" in item for item in result["decision_summary"])
|
||||
)
|
||||
|
||||
def test_model_climate_request_completes_domain_loop(self) -> None:
|
||||
model_output = {
|
||||
"detected_domains": ["climate"],
|
||||
@@ -220,13 +251,13 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
{
|
||||
"domain": "seat",
|
||||
"capability": "seat.set_heating",
|
||||
"target_scope": "driver",
|
||||
"target_scope": "passenger",
|
||||
"operation": "activate",
|
||||
"parameters": [
|
||||
{"name": "state", "value": "on"},
|
||||
{"name": "level", "value": 1},
|
||||
],
|
||||
"reason": "Warm the driver seat.",
|
||||
"reason": "Warm the passenger seat.",
|
||||
},
|
||||
{
|
||||
"domain": "climate",
|
||||
@@ -245,14 +276,17 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
|
||||
with patch.object(self.service.openai, "interpret", return_value=model_output):
|
||||
result = self.service.process(
|
||||
"主驾有点冷、有点闷",
|
||||
"副驾有点冷、有点闷",
|
||||
"openai",
|
||||
"conv-model-partial",
|
||||
)
|
||||
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
self.assertIsNotNone(result["execution_report"])
|
||||
self.assertEqual(result["actuator_state"]["driver"]["heating"], {"state": "on", "level": 1})
|
||||
self.assertEqual(
|
||||
result["actuator_state"]["passenger"]["heating"],
|
||||
{"state": "on", "level": 1},
|
||||
)
|
||||
self.assertEqual(result["domain_state"]["climate"]["fan_level"], 2)
|
||||
self.assertEqual(set(result["function_configs"]), {"seat", "climate"})
|
||||
self.assertEqual(result["scene_config"]["kind"], "SceneConfig")
|
||||
@@ -264,18 +298,18 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
{
|
||||
"domain": "seat",
|
||||
"capability": "seat.set_longitudinal_position",
|
||||
"target_scope": "driver",
|
||||
"target_scope": "passenger",
|
||||
"operation": "set",
|
||||
"parameters": [{"name": "position_pct", "value": 35}],
|
||||
"reason": "Move the driver seat backward from its current midpoint.",
|
||||
"reason": "Move the passenger seat backward from its current midpoint.",
|
||||
},
|
||||
{
|
||||
"domain": "seat",
|
||||
"capability": "seat.set_backrest_recline",
|
||||
"target_scope": "driver",
|
||||
"target_scope": "passenger",
|
||||
"operation": "set",
|
||||
"parameters": [{"name": "position_pct", "value": 60}],
|
||||
"reason": "Recline the driver backrest.",
|
||||
"reason": "Recline the passenger backrest.",
|
||||
},
|
||||
{
|
||||
"domain": "seat",
|
||||
@@ -302,7 +336,7 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
|
||||
with patch.object(self.service.openai, "interpret", return_value=model_output):
|
||||
result = self.service.process(
|
||||
"主驾往后一点并把靠背躺下,副驾腿托升起并伸长",
|
||||
"副驾往后一点并把靠背躺下,腿托升起并伸长",
|
||||
"openai",
|
||||
"conv-model-motion",
|
||||
)
|
||||
@@ -311,20 +345,18 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
self.assertEqual(len(result["function_config"]["spec"]["operations"]), 4)
|
||||
self.assertEqual(len(result["command_plan"]["spec"]["commands"]), 4)
|
||||
self.assertEqual(len(result["execution_report"]["spec"]["results"]), 4)
|
||||
driver = result["actuator_state"]["driver"]["position"]
|
||||
passenger = result["actuator_state"]["passenger"]["position"]
|
||||
self.assertEqual(driver["longitudinal"]["position_pct"], 35)
|
||||
self.assertEqual(driver["backrest_recline"]["position_pct"], 60)
|
||||
self.assertEqual(passenger["longitudinal"]["position_pct"], 35)
|
||||
self.assertEqual(passenger["backrest_recline"]["position_pct"], 60)
|
||||
self.assertEqual(passenger["legrest_height"]["position_pct"], 65)
|
||||
self.assertEqual(passenger["legrest_extension"]["position_pct"], 70)
|
||||
telemetry = result["execution_report"]["spec"]["results"][0]["telemetry"]
|
||||
self.assertEqual(telemetry["start_position_pct"], 50)
|
||||
self.assertEqual(telemetry["effective_position_pct"], 35)
|
||||
self.assertEqual(telemetry["feedback_frame"]["frame_id"], "0x558")
|
||||
self.assertEqual(telemetry["feedback_frame"]["frame_id"], "0x559")
|
||||
self.assertEqual(telemetry["fault_mask"], 0)
|
||||
codes = {issue["code"] for issue in result["validation"]}
|
||||
self.assertIn("SYNTHETIC_MOTION_CAN", codes)
|
||||
self.assertIn("DRIVER_MOTION_INTERLOCK_NOT_IMPLEMENTED", codes)
|
||||
|
||||
def test_motion_follow_up_state_is_supplied_to_model(self) -> None:
|
||||
def output(position: int) -> dict[str, object]:
|
||||
@@ -334,7 +366,7 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
{
|
||||
"domain": "seat",
|
||||
"capability": "seat.set_longitudinal_position",
|
||||
"target_scope": "driver",
|
||||
"target_scope": "passenger",
|
||||
"operation": "increase",
|
||||
"parameters": [{"name": "position_pct", "value": position}],
|
||||
"reason": "Model resolved the relative request.",
|
||||
@@ -352,27 +384,29 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
side_effect=[output(60), output(70)],
|
||||
) as interpret:
|
||||
first = self.service.process(
|
||||
"主驾往后一点", "openai", "conv-motion-follow-up"
|
||||
"副驾往后一点", "openai", "conv-motion-follow-up"
|
||||
)
|
||||
second = self.service.process(
|
||||
"还是靠前,再往后一点", "openai", "conv-motion-follow-up"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
first["actuator_state"]["driver"]["position"]["longitudinal"][
|
||||
first["actuator_state"]["passenger"]["position"]["longitudinal"][
|
||||
"position_pct"
|
||||
],
|
||||
60,
|
||||
)
|
||||
self.assertEqual(
|
||||
second["actuator_state"]["driver"]["position"]["longitudinal"][
|
||||
second["actuator_state"]["passenger"]["position"]["longitudinal"][
|
||||
"position_pct"
|
||||
],
|
||||
70,
|
||||
)
|
||||
second_context = interpret.call_args_list[1].args[1]
|
||||
self.assertEqual(
|
||||
second_context["devices"]["driver"]["position"]["longitudinal"]["position_pct"],
|
||||
second_context["devices"]["passenger"]["position"]["longitudinal"][
|
||||
"position_pct"
|
||||
],
|
||||
60,
|
||||
)
|
||||
|
||||
@@ -383,7 +417,7 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
{
|
||||
"domain": "seat",
|
||||
"capability": "seat.set_longitudinal_position",
|
||||
"target_scope": "driver",
|
||||
"target_scope": "passenger",
|
||||
"operation": "decrease",
|
||||
"parameters": [{"name": "distance_mm", "value": 50}],
|
||||
"reason": "Move forward by a physical distance.",
|
||||
@@ -391,7 +425,7 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
{
|
||||
"domain": "seat",
|
||||
"capability": "seat.set_backrest_recline",
|
||||
"target_scope": "driver",
|
||||
"target_scope": "passenger",
|
||||
"operation": "increase",
|
||||
"parameters": [{"name": "angle_delta_deg", "value": 10}],
|
||||
"reason": "Recline by a relative physical angle.",
|
||||
@@ -405,7 +439,7 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
|
||||
with patch.object(self.service.openai, "interpret", return_value=model_output) as interpret:
|
||||
result = self.service.process(
|
||||
"主驾水平向前移动50毫米,靠背往后10度",
|
||||
"副驾水平向前移动50毫米,靠背往后10度",
|
||||
"openai",
|
||||
"conv-vehicle-profile-relative",
|
||||
vehicle_id="denza_n7",
|
||||
@@ -417,22 +451,22 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
self.assertEqual(operations[0]["parameters"], {"position_pct": 27})
|
||||
self.assertEqual(
|
||||
operations[1]["parameters"],
|
||||
{"position_pct": 38, "angle_deg": 116},
|
||||
{"position_pct": 35, "angle_deg": 118.6},
|
||||
)
|
||||
backrest_motion = operations[1]["motion"]
|
||||
self.assertEqual(backrest_motion["mode"], "relative")
|
||||
self.assertEqual(backrest_motion["direction"], "increase")
|
||||
self.assertEqual(backrest_motion["start_position_pct"], 20)
|
||||
self.assertEqual(backrest_motion["delta_position_pct"], 18)
|
||||
self.assertEqual(backrest_motion["start_angle_deg"], 106.0)
|
||||
self.assertEqual(backrest_motion["delta_position_pct"], 15)
|
||||
self.assertEqual(backrest_motion["start_angle_deg"], 108.6)
|
||||
self.assertEqual(backrest_motion["delta_angle_deg"], 10)
|
||||
context_sent_to_model = interpret.call_args.args[1]
|
||||
self.assertEqual(context_sent_to_model["vehicle"]["id"], "denza_n7")
|
||||
self.assertEqual(
|
||||
context_sent_to_model["devices"]["driver"]["position"][
|
||||
context_sent_to_model["devices"]["passenger"]["position"][
|
||||
"backrest_recline"
|
||||
]["angle_deg"],
|
||||
106.0,
|
||||
108.6,
|
||||
)
|
||||
|
||||
def test_vehicle_profile_angle_limits_are_accepted(self) -> None:
|
||||
@@ -452,7 +486,7 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
"capability": "seat.set_legrest_height",
|
||||
"target_scope": "passenger",
|
||||
"operation": "set",
|
||||
"parameters": [{"name": "angle_deg", "value": 173}],
|
||||
"parameters": [{"name": "angle_deg", "value": 167}],
|
||||
"reason": "Use the vehicle-specific maximum legrest angle.",
|
||||
},
|
||||
],
|
||||
@@ -464,7 +498,7 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
|
||||
with patch.object(self.service.openai, "interpret", return_value=model_output):
|
||||
result = self.service.process(
|
||||
"副驾靠背到163度,腿托到173度",
|
||||
"副驾靠背到163度,腿托到167度",
|
||||
"openai",
|
||||
"conv-vehicle-angle-limits",
|
||||
vehicle_id="denza_n7",
|
||||
@@ -473,7 +507,7 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
operations = result["function_config"]["spec"]["operations"]
|
||||
self.assertEqual(operations[0]["parameters"], {"position_pct": 100, "angle_deg": 163})
|
||||
self.assertEqual(operations[1]["parameters"], {"position_pct": 100, "angle_deg": 173})
|
||||
self.assertEqual(operations[1]["parameters"], {"position_pct": 100, "angle_deg": 167})
|
||||
|
||||
def test_motion_adapter_does_not_resolve_missing_absolute_target(self) -> None:
|
||||
model_output = {
|
||||
@@ -482,7 +516,7 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
{
|
||||
"domain": "seat",
|
||||
"capability": "seat.set_longitudinal_position",
|
||||
"target_scope": "driver",
|
||||
"target_scope": "passenger",
|
||||
"operation": "decrease",
|
||||
"parameters": [],
|
||||
"reason": "No absolute target was produced.",
|
||||
@@ -496,7 +530,7 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
|
||||
with patch.object(self.service.openai, "interpret", return_value=model_output):
|
||||
result = self.service.process(
|
||||
"主驾往后一点", "openai", "conv-motion-missing-target"
|
||||
"副驾往后一点", "openai", "conv-motion-missing-target"
|
||||
)
|
||||
|
||||
self.assertEqual(result["status"], "needs_confirmation")
|
||||
@@ -630,7 +664,7 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(
|
||||
first_operations[1]["parameters"],
|
||||
{"position_pct": 49, "angle_deg": 133},
|
||||
{"position_pct": 53, "angle_deg": 133},
|
||||
)
|
||||
self.assertEqual(
|
||||
first["actuator_state"]["passenger"]["position"]["backrest_recline"][
|
||||
@@ -698,11 +732,53 @@ class SeatServiceIntegrationTests(unittest.TestCase):
|
||||
apply_context = interpret.call_args_list[4].args[1]
|
||||
self.assertEqual(apply_context["presets"][0]["name"], "零重力模式")
|
||||
|
||||
def test_missing_target_requests_confirmation(self) -> None:
|
||||
def test_missing_target_defaults_to_passenger(self) -> None:
|
||||
result = self.service.process("打开座椅通风", provider="local")
|
||||
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
operation = result["function_config"]["spec"]["operations"][0]
|
||||
self.assertEqual(operation["target"]["instance"], "passenger")
|
||||
|
||||
def test_explicit_driver_target_is_rejected(self) -> None:
|
||||
result = self.service.process("打开主驾座椅通风", provider="local")
|
||||
|
||||
self.assertEqual(result["status"], "needs_confirmation")
|
||||
self.assertIsNone(result["function_config"])
|
||||
questions = [
|
||||
item["question"] for item in result["intent_ir"]["spec"]["ambiguities"]
|
||||
]
|
||||
self.assertTrue(any("仅控制副驾" in question for question in questions))
|
||||
|
||||
def test_model_driver_target_is_rejected(self) -> None:
|
||||
model_output = {
|
||||
"detected_domains": ["seat"],
|
||||
"requests": [
|
||||
{
|
||||
"domain": "seat",
|
||||
"capability": "seat.set_ventilation",
|
||||
"target_scope": "driver",
|
||||
"operation": "activate",
|
||||
"parameters": [{"name": "state", "value": "on"}],
|
||||
"reason": "Ventilate the explicitly requested driver seat.",
|
||||
}
|
||||
],
|
||||
"ambiguities": [],
|
||||
"decision_summary": ["Explicit driver-seat request."],
|
||||
"confidence": 0.95,
|
||||
}
|
||||
self.service.openai.api_key = "test-key"
|
||||
|
||||
with patch.object(self.service.openai, "interpret", return_value=model_output):
|
||||
result = self.service.process(
|
||||
"打开主驾座椅通风", "openai", "conv-driver-rejected"
|
||||
)
|
||||
|
||||
self.assertEqual(result["status"], "needs_confirmation")
|
||||
self.assertIsNone(result["function_config"])
|
||||
questions = [
|
||||
item["question"] for item in result["intent_ir"]["spec"]["ambiguities"]
|
||||
]
|
||||
self.assertTrue(any("仅控制副驾" in question for question in questions))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -9,27 +9,27 @@ class LocalIntentParserTests(unittest.TestCase):
|
||||
@staticmethod
|
||||
def context(level: int = 0) -> dict:
|
||||
return {
|
||||
"last_target": "driver" if level else None,
|
||||
"last_target": "passenger" if level else None,
|
||||
"devices": {
|
||||
"driver": {
|
||||
"ventilation": {"state": "on" if level else "off", "level": level},
|
||||
"ventilation": {"state": "off", "level": 0},
|
||||
"heating": {"state": "off", "level": 0},
|
||||
},
|
||||
"passenger": {
|
||||
"ventilation": {"state": "off", "level": 0},
|
||||
"ventilation": {"state": "on" if level else "off", "level": level},
|
||||
"heating": {"state": "off", "level": 0},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def test_parses_driver_ventilation_on(self) -> None:
|
||||
result = parse_local_intent("打开主驾座椅通风")
|
||||
def test_parses_passenger_ventilation_on(self) -> None:
|
||||
result = parse_local_intent("打开副驾座椅通风")
|
||||
|
||||
self.assertEqual(
|
||||
result["actions"],
|
||||
[
|
||||
{
|
||||
"target": "driver",
|
||||
"target": "passenger",
|
||||
"function": "ventilation",
|
||||
"state": "on",
|
||||
"requested_level": None,
|
||||
@@ -38,31 +38,65 @@ class LocalIntentParserTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(result["ambiguities"], [])
|
||||
|
||||
def test_expands_front_seats(self) -> None:
|
||||
def test_rejects_scope_that_includes_driver(self) -> None:
|
||||
result = parse_local_intent("主副驾座椅通风都打开")
|
||||
|
||||
self.assertEqual({item["target"] for item in result["actions"]}, {"driver", "passenger"})
|
||||
self.assertTrue(all(item["state"] == "on" for item in result["actions"]))
|
||||
|
||||
def test_keeps_requested_level_for_safety_validation(self) -> None:
|
||||
result = parse_local_intent("主驾座椅加热开到三档")
|
||||
|
||||
self.assertEqual(result["actions"][0]["requested_level"], 3)
|
||||
|
||||
def test_requires_target(self) -> None:
|
||||
result = parse_local_intent("打开座椅通风")
|
||||
|
||||
self.assertEqual(result["actions"], [])
|
||||
self.assertEqual(result["ambiguities"][0]["field"], "target")
|
||||
self.assertIn("仅控制副驾", result["ambiguities"][0]["question"])
|
||||
|
||||
def test_keeps_requested_level_for_safety_validation(self) -> None:
|
||||
result = parse_local_intent("副驾座椅加热开到三档")
|
||||
|
||||
self.assertEqual(result["actions"][0]["requested_level"], 3)
|
||||
|
||||
def test_defaults_missing_target_to_passenger(self) -> None:
|
||||
result = parse_local_intent("打开座椅通风")
|
||||
|
||||
self.assertEqual(result["actions"][0]["target"], "passenger")
|
||||
self.assertEqual(result["ambiguities"], [])
|
||||
|
||||
def test_parses_four_axis_zero_for_passenger(self) -> None:
|
||||
result = parse_local_intent("把水平、坐盆、靠背、腿托四轴归零")
|
||||
|
||||
self.assertEqual(result["ambiguities"], [])
|
||||
self.assertEqual(
|
||||
[(action["axis"], action["position_pct"]) for action in result["actions"]],
|
||||
[
|
||||
("longitudinal", 0),
|
||||
("cushion_tilt", 0),
|
||||
("backrest_recline", 0),
|
||||
("legrest_height", 0),
|
||||
],
|
||||
)
|
||||
self.assertTrue(
|
||||
all(action["target"] == "passenger" for action in result["actions"])
|
||||
)
|
||||
|
||||
def test_parses_explicit_motion_percentages(self) -> None:
|
||||
result = parse_local_intent("副驾水平移到63%,靠背调到54%")
|
||||
|
||||
self.assertEqual(result["ambiguities"], [])
|
||||
self.assertEqual(
|
||||
[(action["axis"], action["position_pct"]) for action in result["actions"]],
|
||||
[("longitudinal", 63), ("backrest_recline", 54)],
|
||||
)
|
||||
|
||||
def test_rejects_driver_four_axis_zero(self) -> None:
|
||||
result = parse_local_intent("把主驾座椅四轴归零")
|
||||
|
||||
self.assertEqual(result["actions"], [])
|
||||
self.assertEqual(result["ambiguities"][0]["field"], "target")
|
||||
self.assertIn("仅控制副驾", result["ambiguities"][0]["question"])
|
||||
|
||||
def test_maps_mild_hot_and_stuffy_feedback_to_level_one(self) -> None:
|
||||
result = parse_local_intent("主驾有点热,有点闷", self.context())
|
||||
result = parse_local_intent("副驾有点热,有点闷", self.context())
|
||||
|
||||
self.assertEqual(
|
||||
result["actions"],
|
||||
[
|
||||
{
|
||||
"target": "driver",
|
||||
"target": "passenger",
|
||||
"function": "ventilation",
|
||||
"state": "on",
|
||||
"requested_level": 1,
|
||||
@@ -75,7 +109,7 @@ class LocalIntentParserTests(unittest.TestCase):
|
||||
result = parse_local_intent("还有点闷", self.context(level=1))
|
||||
|
||||
self.assertEqual(result["actions"][0]["requested_level"], 2)
|
||||
self.assertEqual(result["actions"][0]["target"], "driver")
|
||||
self.assertEqual(result["actions"][0]["target"], "passenger")
|
||||
self.assertIn("提升到2档", "".join(result["decision_summary"]))
|
||||
|
||||
def test_cold_feedback_reduces_active_ventilation_before_heating(self) -> None:
|
||||
@@ -85,7 +119,7 @@ class LocalIntentParserTests(unittest.TestCase):
|
||||
result["actions"],
|
||||
[
|
||||
{
|
||||
"target": "driver",
|
||||
"target": "passenger",
|
||||
"function": "ventilation",
|
||||
"state": "off",
|
||||
"requested_level": None,
|
||||
@@ -114,7 +148,7 @@ class LocalIntentParserTests(unittest.TestCase):
|
||||
self.assertIn("致冷来源", "".join(result["decision_summary"]))
|
||||
|
||||
def test_cold_and_stuffy_are_independent_comfort_dimensions(self) -> None:
|
||||
result = parse_local_intent("主驾有点冷、有点闷", self.context())
|
||||
result = parse_local_intent("副驾有点冷、有点闷", self.context())
|
||||
|
||||
self.assertEqual(
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ class MatrixCompilerTests(unittest.TestCase):
|
||||
self.compiler = SeatCompiler(DOMAIN_ROOT / "can" / "signal-mapping.yaml")
|
||||
|
||||
def test_encodes_combined_control_bits(self) -> None:
|
||||
parsed = parse_local_intent("打开主驾座椅通风,同时关闭副驾座椅加热")
|
||||
parsed = parse_local_intent("打开副驾座椅通风,同时关闭副驾座椅加热")
|
||||
config = build_function_config(parsed, "req-test-001", "local_rules")
|
||||
plan = self.compiler.compile(config)
|
||||
command = plan["spec"]["commands"][0]
|
||||
@@ -32,7 +32,7 @@ class MatrixCompilerTests(unittest.TestCase):
|
||||
frame = self.matrix.encode(command["message"], command["signals"])
|
||||
|
||||
self.assertEqual(frame["frame_id"], "0x42A")
|
||||
self.assertEqual(frame["data_hex"], "01 80")
|
||||
self.assertEqual(frame["data_hex"], "00 90")
|
||||
|
||||
def test_compilation_is_deterministic(self) -> None:
|
||||
parsed = parse_local_intent("关闭副驾座椅通风")
|
||||
@@ -46,20 +46,20 @@ class MatrixCompilerTests(unittest.TestCase):
|
||||
"actions": [
|
||||
{
|
||||
"action_type": "position",
|
||||
"target": "driver",
|
||||
"target": "passenger",
|
||||
"axis": "longitudinal",
|
||||
"position_pct": 35,
|
||||
},
|
||||
{
|
||||
"action_type": "position",
|
||||
"target": "driver",
|
||||
"target": "passenger",
|
||||
"axis": "backrest_recline",
|
||||
"position_pct": 60,
|
||||
},
|
||||
],
|
||||
"deferred_requests": [],
|
||||
"ambiguities": [],
|
||||
"decision_summary": ["Move two driver-seat axes."],
|
||||
"decision_summary": ["Move two front-passenger-seat axes."],
|
||||
"confidence": 1.0,
|
||||
}
|
||||
config = build_function_config(parsed, "req-motion-001", "test")
|
||||
@@ -68,15 +68,15 @@ class MatrixCompilerTests(unittest.TestCase):
|
||||
|
||||
self.assertEqual(len(plan["spec"]["commands"]), 2)
|
||||
first, second = plan["spec"]["commands"]
|
||||
self.assertEqual(first["message"], "Driver_Seat_Motion_Command_550")
|
||||
self.assertEqual(first["message"], "Passenger_Seat_Motion_Command_551")
|
||||
self.assertEqual(first["signals"]["Longitudinal_Target_Pct"], 35)
|
||||
self.assertEqual(first["signals"]["Motion_Enable_Mask"], 1)
|
||||
self.assertEqual(first["completion"]["message"], "Driver_Seat_Motion_Status_558")
|
||||
self.assertEqual(first["completion"]["message"], "Passenger_Seat_Motion_Status_559")
|
||||
self.assertEqual(first["completion"]["signal"], "Longitudinal_Actual_Pct")
|
||||
self.assertEqual(second["signals"]["Backrest_Recline_Target_Pct"], 60)
|
||||
self.assertEqual(second["signals"]["Motion_Enable_Mask"], 4)
|
||||
frame = self.combined_matrix.encode(first["message"], first["signals"])
|
||||
self.assertEqual(frame["frame_id"], "0x550")
|
||||
self.assertEqual(frame["frame_id"], "0x551")
|
||||
self.assertEqual(frame["payload_size"], 8)
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user