feat: tolerate recoverable seat intent inputs
This commit is contained in:
@@ -0,0 +1,823 @@
|
|||||||
|
{
|
||||||
|
"suite": "t2g-seat-frontend-comprehensive-hil",
|
||||||
|
"started_at": "2026-08-01T22:06:10.662623Z",
|
||||||
|
"base_url": "http://127.0.0.1:8000",
|
||||||
|
"vehicle_id": "denza_n7",
|
||||||
|
"conversation_id": "conv-hil-full-20260801220610",
|
||||||
|
"runtime": {
|
||||||
|
"hardware_execution_allowed": true,
|
||||||
|
"firmware_profiles": [
|
||||||
|
"combined"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": "MOTION-M0-ABS-DECIMAL-ROUND",
|
||||||
|
"group": "motion",
|
||||||
|
"utterance": "副驾座椅水平位置调到50.5%",
|
||||||
|
"started_at": "2026-08-01T22:06:33.819391Z",
|
||||||
|
"status": "passed",
|
||||||
|
"errors": [],
|
||||||
|
"http_status": 201,
|
||||||
|
"request_id": "req-20260801220633-35a9ee",
|
||||||
|
"provider_used": "local_rules",
|
||||||
|
"matched_modes": [],
|
||||||
|
"state_http_status": 200,
|
||||||
|
"physical_state": {
|
||||||
|
"position": {
|
||||||
|
"backrest_recline": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 0,
|
||||||
|
"motor_result": 3,
|
||||||
|
"motor_selector": 2,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "02 30 18 0B 00 00 00 00",
|
||||||
|
"timestamp": 52437.943305
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"legrest_height": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 0,
|
||||||
|
"motor_result": 3,
|
||||||
|
"motor_selector": 3,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "03 30 18 0B 00 00 00 00",
|
||||||
|
"timestamp": 52437.968795
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"longitudinal": {
|
||||||
|
"position_pct": 51,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 0,
|
||||||
|
"value_x100": 5110,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "00 26 08 0C 00 F6 13 00",
|
||||||
|
"timestamp": 52437.995098
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cushion_tilt": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 0,
|
||||||
|
"motor_result": 3,
|
||||||
|
"motor_selector": 1,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "01 30 18 0B 00 00 00 00",
|
||||||
|
"timestamp": 52438.021395
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ventilation": {
|
||||||
|
"state": "off",
|
||||||
|
"level": 0,
|
||||||
|
"feedback_value": 1,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x4FA",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "09 00 00 00 00 00 00 00",
|
||||||
|
"timestamp": 52438.031724
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heating": {
|
||||||
|
"state": "off",
|
||||||
|
"level": 0,
|
||||||
|
"feedback_value": 1,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x4FA",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "09 00 00 00 00 00 00 00",
|
||||||
|
"timestamp": 52438.031724
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"captured_at": "2026-08-01T22:06:42.683267Z",
|
||||||
|
"source": "physical_can",
|
||||||
|
"target": {
|
||||||
|
"device": "seat",
|
||||||
|
"zone": "front_right",
|
||||||
|
"instance": "passenger"
|
||||||
|
},
|
||||||
|
"complete": true,
|
||||||
|
"missing": []
|
||||||
|
},
|
||||||
|
"finished_at": "2026-08-01T22:06:42.768028Z",
|
||||||
|
"duration_ms": 8949
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MOTION-M0-ABS-NEGATIVE-CLAMP",
|
||||||
|
"group": "motion",
|
||||||
|
"utterance": "副驾座椅水平位置调到-10%",
|
||||||
|
"started_at": "2026-08-01T22:06:42.768083Z",
|
||||||
|
"status": "passed",
|
||||||
|
"errors": [],
|
||||||
|
"http_status": 201,
|
||||||
|
"request_id": "req-20260801220642-ffc18b",
|
||||||
|
"provider_used": "local_rules",
|
||||||
|
"matched_modes": [],
|
||||||
|
"state_http_status": 200,
|
||||||
|
"physical_state": {
|
||||||
|
"position": {
|
||||||
|
"cushion_tilt": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 0,
|
||||||
|
"motor_result": 3,
|
||||||
|
"motor_selector": 1,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "01 30 18 0D 00 00 00 00",
|
||||||
|
"timestamp": 52448.169066
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"backrest_recline": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 0,
|
||||||
|
"motor_result": 3,
|
||||||
|
"motor_selector": 2,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "02 30 18 0D 00 00 00 00",
|
||||||
|
"timestamp": 52448.196
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"legrest_height": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 0,
|
||||||
|
"motor_result": 3,
|
||||||
|
"motor_selector": 3,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "03 30 18 0D 00 00 00 00",
|
||||||
|
"timestamp": 52448.221821
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"longitudinal": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 0,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "00 26 18 0E 00 00 00 00",
|
||||||
|
"timestamp": 52448.247656
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ventilation": {
|
||||||
|
"state": "off",
|
||||||
|
"level": 0,
|
||||||
|
"feedback_value": 1,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x4FA",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "09 00 00 00 00 00 00 00",
|
||||||
|
"timestamp": 52448.255615
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heating": {
|
||||||
|
"state": "off",
|
||||||
|
"level": 0,
|
||||||
|
"feedback_value": 1,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x4FA",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "09 00 00 00 00 00 00 00",
|
||||||
|
"timestamp": 52448.255615
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"captured_at": "2026-08-01T22:06:52.906777Z",
|
||||||
|
"source": "physical_can",
|
||||||
|
"target": {
|
||||||
|
"device": "seat",
|
||||||
|
"zone": "front_right",
|
||||||
|
"instance": "passenger"
|
||||||
|
},
|
||||||
|
"complete": true,
|
||||||
|
"missing": []
|
||||||
|
},
|
||||||
|
"finished_at": "2026-08-01T22:06:52.966157Z",
|
||||||
|
"duration_ms": 10198
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MOTION-M2-TIMED-ZERO-MIN-CLAMP",
|
||||||
|
"group": "motion",
|
||||||
|
"utterance": "副驾靠背向后移动0秒",
|
||||||
|
"started_at": "2026-08-01T22:06:52.966239Z",
|
||||||
|
"status": "passed",
|
||||||
|
"errors": [],
|
||||||
|
"http_status": 201,
|
||||||
|
"request_id": "req-20260801220652-ebe458",
|
||||||
|
"provider_used": "local_rules",
|
||||||
|
"matched_modes": [],
|
||||||
|
"state_http_status": 200,
|
||||||
|
"physical_state": {
|
||||||
|
"position": {
|
||||||
|
"longitudinal": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 0,
|
||||||
|
"motor_result": 3,
|
||||||
|
"motor_selector": 0,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "00 30 18 0F 00 00 00 00",
|
||||||
|
"timestamp": 52449.295516
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cushion_tilt": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 0,
|
||||||
|
"motor_result": 3,
|
||||||
|
"motor_selector": 1,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "01 30 18 0F 00 00 00 00",
|
||||||
|
"timestamp": 52449.321809
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"backrest_recline": {
|
||||||
|
"position_pct": 1,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 2,
|
||||||
|
"value_x100": 120,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "02 26 08 10 00 78 00 00",
|
||||||
|
"timestamp": 52449.347017
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"legrest_height": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 0,
|
||||||
|
"motor_result": 3,
|
||||||
|
"motor_selector": 3,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "03 30 18 0F 00 00 00 00",
|
||||||
|
"timestamp": 52449.37364
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ventilation": {
|
||||||
|
"state": "off",
|
||||||
|
"level": 0,
|
||||||
|
"feedback_value": 1,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x4FA",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "09 00 00 00 00 00 00 00",
|
||||||
|
"timestamp": 52449.382777
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heating": {
|
||||||
|
"state": "off",
|
||||||
|
"level": 0,
|
||||||
|
"feedback_value": 1,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x4FA",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "09 00 00 00 00 00 00 00",
|
||||||
|
"timestamp": 52449.382777
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"captured_at": "2026-08-01T22:06:54.033862Z",
|
||||||
|
"source": "physical_can",
|
||||||
|
"target": {
|
||||||
|
"device": "seat",
|
||||||
|
"zone": "front_right",
|
||||||
|
"instance": "passenger"
|
||||||
|
},
|
||||||
|
"complete": true,
|
||||||
|
"missing": []
|
||||||
|
},
|
||||||
|
"finished_at": "2026-08-01T22:06:54.110745Z",
|
||||||
|
"duration_ms": 1145
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MODEL-FUZZY-COMFORT-ONLY",
|
||||||
|
"group": "model",
|
||||||
|
"utterance": "把座椅调舒服一点",
|
||||||
|
"started_at": "2026-08-01T22:06:54.110804Z",
|
||||||
|
"status": "passed",
|
||||||
|
"errors": [],
|
||||||
|
"http_status": 201,
|
||||||
|
"request_id": "req-20260801220654-5f0af4",
|
||||||
|
"provider_used": "openai:gpt-5.6-sol",
|
||||||
|
"matched_modes": [],
|
||||||
|
"state_http_status": 200,
|
||||||
|
"physical_state": {
|
||||||
|
"position": {
|
||||||
|
"legrest_height": {
|
||||||
|
"position_pct": 10,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 3,
|
||||||
|
"value_x100": 1005,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "03 26 08 15 00 ED 03 00",
|
||||||
|
"timestamp": 52491.95849
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"longitudinal": {
|
||||||
|
"position_pct": 80,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 0,
|
||||||
|
"value_x100": 8016,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "00 26 08 12 00 50 1F 00",
|
||||||
|
"timestamp": 52491.984306
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cushion_tilt": {
|
||||||
|
"position_pct": 30,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 1,
|
||||||
|
"value_x100": 2995,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "01 26 08 13 00 B3 0B 00",
|
||||||
|
"timestamp": 52492.010881
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"backrest_recline": {
|
||||||
|
"position_pct": 35,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 2,
|
||||||
|
"value_x100": 3501,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "02 26 08 14 00 AD 0D 00",
|
||||||
|
"timestamp": 52492.036223
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ventilation": {
|
||||||
|
"state": "off",
|
||||||
|
"level": 0,
|
||||||
|
"feedback_value": 1,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x4FA",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "09 00 00 00 00 00 00 00",
|
||||||
|
"timestamp": 52492.046648
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heating": {
|
||||||
|
"state": "off",
|
||||||
|
"level": 0,
|
||||||
|
"feedback_value": 1,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x4FA",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "09 00 00 00 00 00 00 00",
|
||||||
|
"timestamp": 52492.046648
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"captured_at": "2026-08-01T22:07:36.697054Z",
|
||||||
|
"source": "physical_can",
|
||||||
|
"target": {
|
||||||
|
"device": "seat",
|
||||||
|
"zone": "front_right",
|
||||||
|
"instance": "passenger"
|
||||||
|
},
|
||||||
|
"complete": true,
|
||||||
|
"missing": []
|
||||||
|
},
|
||||||
|
"finished_at": "2026-08-01T22:07:36.773665Z",
|
||||||
|
"duration_ms": 42663
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MODEL-UNKNOWN-BOSS-MODE",
|
||||||
|
"group": "model",
|
||||||
|
"utterance": "打开老板模式",
|
||||||
|
"started_at": "2026-08-01T22:07:36.773730Z",
|
||||||
|
"status": "passed",
|
||||||
|
"errors": [],
|
||||||
|
"http_status": 201,
|
||||||
|
"request_id": "req-20260801220736-be8392",
|
||||||
|
"provider_used": "openai:gpt-5.6-sol",
|
||||||
|
"matched_modes": [],
|
||||||
|
"state_http_status": 200,
|
||||||
|
"physical_state": {
|
||||||
|
"position": {
|
||||||
|
"legrest_height": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 3,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "03 26 18 1A 00 00 00 00",
|
||||||
|
"timestamp": 52526.819003
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"longitudinal": {
|
||||||
|
"position_pct": 20,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 0,
|
||||||
|
"value_x100": 2004,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "00 26 08 17 00 D4 07 00",
|
||||||
|
"timestamp": 52526.845094
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cushion_tilt": {
|
||||||
|
"position_pct": 30,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 1,
|
||||||
|
"value_x100": 2995,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "01 26 08 18 00 B3 0B 00",
|
||||||
|
"timestamp": 52526.87121
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"backrest_recline": {
|
||||||
|
"position_pct": 10,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 2,
|
||||||
|
"value_x100": 1003,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "02 26 08 19 00 EB 03 00",
|
||||||
|
"timestamp": 52526.897575
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ventilation": {
|
||||||
|
"state": "off",
|
||||||
|
"level": 0,
|
||||||
|
"feedback_value": 1,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x4FA",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "09 00 00 00 00 00 00 00",
|
||||||
|
"timestamp": 52526.90671
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heating": {
|
||||||
|
"state": "off",
|
||||||
|
"level": 0,
|
||||||
|
"feedback_value": 1,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x4FA",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "09 00 00 00 00 00 00 00",
|
||||||
|
"timestamp": 52526.90671
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"captured_at": "2026-08-01T22:08:11.556208Z",
|
||||||
|
"source": "physical_can",
|
||||||
|
"target": {
|
||||||
|
"device": "seat",
|
||||||
|
"zone": "front_right",
|
||||||
|
"instance": "passenger"
|
||||||
|
},
|
||||||
|
"complete": true,
|
||||||
|
"missing": []
|
||||||
|
},
|
||||||
|
"finished_at": "2026-08-01T22:08:11.634298Z",
|
||||||
|
"duration_ms": 34861
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"position_memory_sync": {
|
||||||
|
"id": "PREFLIGHT-POSITION-MEMORY-SYNC",
|
||||||
|
"group": "preflight",
|
||||||
|
"utterance": "关闭副驾座椅加热和通风,并把水平、坐盆、靠背、腿托四轴归零",
|
||||||
|
"started_at": "2026-08-01T22:06:10.662651Z",
|
||||||
|
"status": "passed",
|
||||||
|
"errors": [],
|
||||||
|
"http_status": 201,
|
||||||
|
"request_id": "req-20260801220610-fe1cde",
|
||||||
|
"provider_used": "local_rules",
|
||||||
|
"matched_modes": [],
|
||||||
|
"state_http_status": 200,
|
||||||
|
"physical_state": {
|
||||||
|
"position": {
|
||||||
|
"legrest_height": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 3,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "03 26 18 0A 00 00 00 00",
|
||||||
|
"timestamp": 52428.988603
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"longitudinal": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 0,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "00 26 18 07 00 00 00 00",
|
||||||
|
"timestamp": 52429.015565
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cushion_tilt": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 1,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "01 26 18 08 00 00 00 00",
|
||||||
|
"timestamp": 52429.041376
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"backrest_recline": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 2,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "02 26 18 09 00 00 00 00",
|
||||||
|
"timestamp": 52429.06754
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ventilation": {
|
||||||
|
"state": "off",
|
||||||
|
"level": 0,
|
||||||
|
"feedback_value": 1,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x4FA",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "09 00 00 00 00 00 00 00",
|
||||||
|
"timestamp": 52429.075411
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heating": {
|
||||||
|
"state": "off",
|
||||||
|
"level": 0,
|
||||||
|
"feedback_value": 1,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x4FA",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "09 00 00 00 00 00 00 00",
|
||||||
|
"timestamp": 52429.075411
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"captured_at": "2026-08-01T22:06:33.726921Z",
|
||||||
|
"source": "physical_can",
|
||||||
|
"target": {
|
||||||
|
"device": "seat",
|
||||||
|
"zone": "front_right",
|
||||||
|
"instance": "passenger"
|
||||||
|
},
|
||||||
|
"complete": true,
|
||||||
|
"missing": []
|
||||||
|
},
|
||||||
|
"finished_at": "2026-08-01T22:06:33.819288Z",
|
||||||
|
"duration_ms": 23157
|
||||||
|
},
|
||||||
|
"final_state": {
|
||||||
|
"id": "FINAL-THERMAL-OFF",
|
||||||
|
"group": "cleanup",
|
||||||
|
"utterance": "关闭副驾座椅加热和通风,并把水平、坐盆、靠背、腿托四轴归零",
|
||||||
|
"started_at": "2026-08-01T22:08:11.634351Z",
|
||||||
|
"status": "passed",
|
||||||
|
"errors": [],
|
||||||
|
"http_status": 201,
|
||||||
|
"request_id": "req-20260801220811-9d1330",
|
||||||
|
"provider_used": "local_rules",
|
||||||
|
"matched_modes": [],
|
||||||
|
"state_http_status": 200,
|
||||||
|
"physical_state": {
|
||||||
|
"position": {
|
||||||
|
"cushion_tilt": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 1,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "01 26 18 1D 00 00 00 00",
|
||||||
|
"timestamp": 52552.285415
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"backrest_recline": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 2,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "02 26 18 1E 00 00 00 00",
|
||||||
|
"timestamp": 52552.311577
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"legrest_height": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 3,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "03 26 18 1F 00 00 00 00",
|
||||||
|
"timestamp": 52552.337346
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"longitudinal": {
|
||||||
|
"position_pct": 0,
|
||||||
|
"position_valid": true,
|
||||||
|
"motion_state": "stopped",
|
||||||
|
"fault": false,
|
||||||
|
"task_state": 6,
|
||||||
|
"motor_result": 2,
|
||||||
|
"motor_selector": 0,
|
||||||
|
"value_x100": 0,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x302",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "00 26 18 1C 00 00 00 00",
|
||||||
|
"timestamp": 52552.364354
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ventilation": {
|
||||||
|
"state": "off",
|
||||||
|
"level": 0,
|
||||||
|
"feedback_value": 1,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x4FA",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "09 00 00 00 00 00 00 00",
|
||||||
|
"timestamp": 52552.372264
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heating": {
|
||||||
|
"state": "off",
|
||||||
|
"level": 0,
|
||||||
|
"feedback_value": 1,
|
||||||
|
"feedback_frame": {
|
||||||
|
"frame_id": "0x4FA",
|
||||||
|
"payload_size": 8,
|
||||||
|
"data_hex": "09 00 00 00 00 00 00 00",
|
||||||
|
"timestamp": 52552.372264
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"captured_at": "2026-08-01T22:08:37.021239Z",
|
||||||
|
"source": "physical_can",
|
||||||
|
"target": {
|
||||||
|
"device": "seat",
|
||||||
|
"zone": "front_right",
|
||||||
|
"instance": "passenger"
|
||||||
|
},
|
||||||
|
"complete": true,
|
||||||
|
"missing": []
|
||||||
|
},
|
||||||
|
"finished_at": "2026-08-01T22:08:37.100192Z",
|
||||||
|
"duration_ms": 25466
|
||||||
|
},
|
||||||
|
"finished_at": "2026-08-01T22:08:37.100232Z",
|
||||||
|
"summary": {
|
||||||
|
"total": 5,
|
||||||
|
"passed": 5,
|
||||||
|
"failed": 0,
|
||||||
|
"safety_blocked": 0,
|
||||||
|
"executed": 5,
|
||||||
|
"position_memory_sync_passed": true,
|
||||||
|
"final_state_passed": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"requested_delta_angle_deg": {"type": "number", "minimum": 0},
|
"requested_delta_angle_deg": {"type": "number", "minimum": 0},
|
||||||
"requested_delta_position_pct": {"type": "integer", "minimum": 0},
|
"requested_delta_position_pct": {"type": "integer", "minimum": 0},
|
||||||
"range_clamped": {"type": "boolean"},
|
"range_clamped": {"type": "boolean"},
|
||||||
"requested_duration_ms": {"type": "integer", "minimum": 1},
|
"requested_duration_ms": {"type": "integer", "minimum": 0},
|
||||||
"applied_duration_ms": {"type": "integer", "minimum": 1},
|
"applied_duration_ms": {"type": "integer", "minimum": 1},
|
||||||
"max_duration_ms": {"type": "integer", "minimum": 1},
|
"max_duration_ms": {"type": "integer", "minimum": 1},
|
||||||
"min_duration_ms": {"type": "integer", "minimum": 1},
|
"min_duration_ms": {"type": "integer", "minimum": 1},
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ python .\tools\run_seat_frontend_hil_suite.py `
|
|||||||
--output .\artifacts\hil\seat_frontend_hil_latest.json
|
--output .\artifacts\hil\seat_frontend_hil_latest.json
|
||||||
```
|
```
|
||||||
|
|
||||||
可用 `--only-group thermal|motion|combined|mode|model|safety` 只执行一组,用 `--start-at CASE-ID` 从指定用例续跑,或重复传入 `--case CASE-ID` 精确复测一个有序子集。每次运行先强制执行四轴物理归零,重建霍尔位置记忆;失败时停止后续位置测试。当前清单共 186 条:129 条热管理/移动/组合输入、33 条模式输入、4 条 GPT-5.6-sol 模糊意图输入和 20 条安全拒绝输入。新增覆盖包括取暖/吹风口语、正常秒数/半秒/毫秒与最小时长钳制、四轴独立端点表达、坐垫与腿托超量程、靠背角度下溢,以及后排/左前/全部座椅、小数/负位置和零时长安全拒绝。每条用例均核对:所有座椅操作为 `passenger/front_right`、物理状态来源为 `physical_can`、目标位置误差不超过 2%、联合孪生值与物理状态相同;模型用例还会防止静默降级为本地规则,时长和超量程用例会核对界面可见的时长上限、实际执行时长和到达端点提示,拒绝用例会比较动作前后物理指纹。调试环境设置 `COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN_REQUIRED=false`,执行器和前端均不要求逐次输入硬件令牌。
|
可用 `--only-group thermal|motion|combined|mode|model|safety` 只执行一组,用 `--start-at CASE-ID` 从指定用例续跑,或重复传入 `--case CASE-ID` 精确复测一个有序子集。每次运行先强制执行四轴物理归零,重建霍尔位置记忆;失败时停止后续位置测试。当前清单共 186 条:132 条热管理/移动/组合输入、33 条模式输入、6 条 GPT-5.6-sol 模糊/未知模式输入和 15 条安全拒绝输入。体验容错覆盖取暖/吹风口语、正常秒数/半秒/毫秒、零时长按最小安全时长执行、小数位置四舍五入、负位置钳制到近端、四轴独立端点、超量程备用端点,以及模糊舒适和未保存老板模式的 GPT 姿态合成。只有后排/左前/全部座椅等不受支持目标、未知物理能力或真实语义冲突保留安全拒绝。每条用例均核对:所有座椅操作为 `passenger/front_right`、物理状态来源为 `physical_can`、目标位置误差不超过 2%、联合孪生值与物理状态相同;模型用例还会防止静默降级为本地规则,时长、取整和超量程用例会核对界面可见的实际执行提示,拒绝用例会比较动作前后物理指纹。调试环境设置 `COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN_REQUIRED=false`,执行器和前端均不要求逐次输入硬件令牌。
|
||||||
|
|
||||||
## 真实台架执行顺序
|
## 真实台架执行顺序
|
||||||
|
|
||||||
@@ -64,3 +64,5 @@ python .\tools\run_seat_frontend_hil_suite.py `
|
|||||||
2026-08-01 的 M0/M2 无 Hall 与 M1 高电流无 Hall 记录最终定位为水平、坐盆和靠背 Hall 地线断开。2026-08-02 地线恢复、四轴手工归零后,revision 6 的完整自动 HIL 结果为 33/33 通过、0 失败、0 `safety_blocked`:覆盖加热/通风全部档位、M0~M3 双向中间位置、单轴及四轴归零、组合指令和全部 11 个预设模式。临时轴向安全阻塞已从测试清单移除;报告保存于 `artifacts/hil/seat_frontend_hil_20260802_full_retest_run1.json`。
|
2026-08-01 的 M0/M2 无 Hall 与 M1 高电流无 Hall 记录最终定位为水平、坐盆和靠背 Hall 地线断开。2026-08-02 地线恢复、四轴手工归零后,revision 6 的完整自动 HIL 结果为 33/33 通过、0 失败、0 `safety_blocked`:覆盖加热/通风全部档位、M0~M3 双向中间位置、单轴及四轴归零、组合指令和全部 11 个预设模式。临时轴向安全阻塞已从测试清单移除;报告保存于 `artifacts/hil/seat_frontend_hil_20260802_full_retest_run1.json`。
|
||||||
|
|
||||||
2026-08-02 补齐时长和超量程容错后,扩充后的完整 HIL 为 161/161 通过、0 失败、0 `safety_blocked`,最终清理通过;独立 CAN 复核确认加热/通风均为 0 档、M0~M3 均为 0% 且四轴 `Position_Valid=1`。其中“靠背向后移动70度”、四轴双向超长时长、水平101%、靠背200度以及“打开零重力模式”均在完整序列中通过。完整报告为 `artifacts/hil/seat_frontend_hil_20260802_full_timed_overflow.json`,新增规则的定向 14/14 报告为 `artifacts/hil/seat_frontend_hil_20260802_timed_overflow_targeted.json`。
|
2026-08-02 补齐时长和超量程容错后,扩充后的完整 HIL 为 161/161 通过、0 失败、0 `safety_blocked`,最终清理通过;独立 CAN 复核确认加热/通风均为 0 档、M0~M3 均为 0% 且四轴 `Position_Valid=1`。其中“靠背向后移动70度”、四轴双向超长时长、水平101%、靠背200度以及“打开零重力模式”均在完整序列中通过。完整报告为 `artifacts/hil/seat_frontend_hil_20260802_full_timed_overflow.json`,新增规则的定向 14/14 报告为 `artifacts/hil/seat_frontend_hil_20260802_timed_overflow_targeted.json`。
|
||||||
|
|
||||||
|
2026-08-02 调整体验容错边界后,软件完整回归为 158/158 通过并包含 248 个参数化子用例;真实台架定向回归为 5/5 通过。定向用例验证了 50.5% 取整为 51%、-10% 钳制到 0% 机械近端、0 秒按 300 ms 最小时长执行,以及 GPT-5.6-sol 将“把座椅调舒服一点”和未保存的“老板模式”分别合成为 80/30/35/10 与 20/30/10/0 四轴目标。预检位置记忆同步和最终清理均通过,独立物理 CAN 复核确认加热/通风为 0 档、M0~M3 为 0%、四轴 `Position_Valid=1`;报告为 `artifacts/hil/seat_frontend_hil_20260802_experience_tolerance_targeted.json`。
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import math
|
||||||
import re
|
import re
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
@@ -20,7 +21,7 @@ STATE_PATTERN = re.compile(
|
|||||||
LEVEL_PATTERN = re.compile(r"(最高|最低|[1-5一二三四五高中低])\s*档")
|
LEVEL_PATTERN = re.compile(r"(最高|最低|[1-5一二三四五高中低])\s*档")
|
||||||
POSITION_PATTERN_TEMPLATE = (
|
POSITION_PATTERN_TEMPLATE = (
|
||||||
r"{alias}(?:位置)?(?:调到|移动到|移到|设为|设置为|调至|移动至|到|为)?"
|
r"{alias}(?:位置)?(?:调到|移动到|移到|设为|设置为|调至|移动至|到|为)?"
|
||||||
r"(?P<position>\d{{1,3}})%"
|
r"(?P<position>[+-]?\d{{1,3}}(?:\.\d+)?)%"
|
||||||
)
|
)
|
||||||
ANGLE_PATTERN_TEMPLATE = (
|
ANGLE_PATTERN_TEMPLATE = (
|
||||||
r"{alias}(?:夹角|角度)?(?:调到|放倒到|抬到|移动到|移到|设为|设置为|"
|
r"{alias}(?:夹角|角度)?(?:调到|放倒到|抬到|移动到|移到|设为|设置为|"
|
||||||
@@ -300,6 +301,16 @@ def _format_seconds(duration_ms: int) -> str:
|
|||||||
return f"{seconds:g}"
|
return f"{seconds:g}"
|
||||||
|
|
||||||
|
|
||||||
|
def _round_half_away_from_zero(value: float) -> int:
|
||||||
|
"""Use the integer a user normally expects from a .5 position request."""
|
||||||
|
|
||||||
|
return math.floor(value + 0.5) if value >= 0 else math.ceil(value - 0.5)
|
||||||
|
|
||||||
|
|
||||||
|
def _format_numeric_request(value: float) -> str:
|
||||||
|
return f"{value:g}"
|
||||||
|
|
||||||
|
|
||||||
def _timed_motion_action(
|
def _timed_motion_action(
|
||||||
axis: str,
|
axis: str,
|
||||||
direction_token: str,
|
direction_token: str,
|
||||||
@@ -309,8 +320,8 @@ def _timed_motion_action(
|
|||||||
operation = _relative_direction(axis, direction_token)
|
operation = _relative_direction(axis, direction_token)
|
||||||
if operation is None:
|
if operation is None:
|
||||||
return None, f"{direction_token}不适用于该座椅轴,请确认移动方向。"
|
return None, f"{direction_token}不适用于该座椅轴,请确认移动方向。"
|
||||||
if requested_duration_ms <= 0:
|
if requested_duration_ms < 0:
|
||||||
return None, "座椅按时长移动的时长必须大于0。"
|
return None, "座椅按时长移动的时长不能为负数。"
|
||||||
state = _context_motion_state(context, axis)
|
state = _context_motion_state(context, axis)
|
||||||
if state is None:
|
if state is None:
|
||||||
return None, "缺少当前座椅位置,无法安全计算按时长移动的方向和备用端点。"
|
return None, "缺少当前座椅位置,无法安全计算按时长移动的方向和备用端点。"
|
||||||
@@ -894,12 +905,18 @@ def parse_local_intent(
|
|||||||
for axis, aliases in MOTION_AXIS_ALIASES.items()
|
for axis, aliases in MOTION_AXIS_ALIASES.items()
|
||||||
if any(alias in clause for alias in aliases)
|
if any(alias in clause for alias in aliases)
|
||||||
]
|
]
|
||||||
percentages = list(re.finditer(r"(?P<position>\d{1,3})%", clause))
|
percentages = list(
|
||||||
|
re.finditer(r"(?P<position>[+-]?\d{1,3}(?:\.\d+)?)%", clause)
|
||||||
|
)
|
||||||
shared_position = None
|
shared_position = None
|
||||||
if len(clause_axes) > 1 and len(percentages) == 1 and re.search(
|
if len(clause_axes) > 1 and len(percentages) == 1 and re.search(
|
||||||
r"都|全部|均", clause
|
r"都|全部|均", clause
|
||||||
):
|
):
|
||||||
shared_position = int(percentages[0].group("position"))
|
requested_shared_value = float(percentages[0].group("position"))
|
||||||
|
rounded_shared_position = _round_half_away_from_zero(
|
||||||
|
requested_shared_value
|
||||||
|
)
|
||||||
|
shared_position = max(0, min(100, rounded_shared_position))
|
||||||
target_zh = (
|
target_zh = (
|
||||||
_nearest_preceding_target(clause, percentages[0].start()) or "副驾"
|
_nearest_preceding_target(clause, percentages[0].start()) or "副驾"
|
||||||
)
|
)
|
||||||
@@ -908,8 +925,6 @@ def parse_local_intent(
|
|||||||
{"field": "target", "question": PASSENGER_ONLY_QUESTION}
|
{"field": "target", "question": PASSENGER_ONLY_QUESTION}
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
requested_shared_position = shared_position
|
|
||||||
shared_position = min(100, shared_position)
|
|
||||||
motion_actions.extend(
|
motion_actions.extend(
|
||||||
{
|
{
|
||||||
"target": "passenger",
|
"target": "passenger",
|
||||||
@@ -920,11 +935,22 @@ def parse_local_intent(
|
|||||||
{
|
{
|
||||||
"user_notice": (
|
"user_notice": (
|
||||||
f"{MOTION_AXIS_LABELS[axis]}请求位置"
|
f"{MOTION_AXIS_LABELS[axis]}请求位置"
|
||||||
f"{requested_shared_position}%超过最大行程;"
|
f"{_format_numeric_request(requested_shared_value)}%;"
|
||||||
"已采用备用目标并移动到对应机械末端。"
|
+ (
|
||||||
|
f"已取整为{rounded_shared_position}%。"
|
||||||
|
if requested_shared_value
|
||||||
|
!= rounded_shared_position
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
+ (
|
||||||
|
f" 取整后超过0到100%行程,已采用"
|
||||||
|
f"{shared_position}%备用目标并移动到对应机械末端。"
|
||||||
|
if rounded_shared_position != shared_position
|
||||||
|
else ""
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if requested_shared_position > 100
|
if requested_shared_value != shared_position
|
||||||
else {}
|
else {}
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -1104,8 +1130,13 @@ def parse_local_intent(
|
|||||||
break
|
break
|
||||||
absolute_angle = None
|
absolute_angle = None
|
||||||
if position_match is not None:
|
if position_match is not None:
|
||||||
requested_position_pct = int(position_match.group("position"))
|
requested_position_value = float(
|
||||||
position_pct = min(100, requested_position_pct)
|
position_match.group("position")
|
||||||
|
)
|
||||||
|
requested_position_pct = _round_half_away_from_zero(
|
||||||
|
requested_position_value
|
||||||
|
)
|
||||||
|
position_pct = max(0, min(100, requested_position_pct))
|
||||||
else:
|
else:
|
||||||
requested_angle = int(angle_match.group("angle"))
|
requested_angle = int(angle_match.group("angle"))
|
||||||
minimum, maximum = MOTION_ANGLE_RANGES[axis]
|
minimum, maximum = MOTION_ANGLE_RANGES[axis]
|
||||||
@@ -1133,11 +1164,21 @@ def parse_local_intent(
|
|||||||
f"{minimum}到{maximum}度行程;已采用备用目标"
|
f"{minimum}到{maximum}度行程;已采用备用目标"
|
||||||
f"{absolute_angle}度并移动到{endpoint_label}。"
|
f"{absolute_angle}度并移动到{endpoint_label}。"
|
||||||
)
|
)
|
||||||
elif requested_position_pct != position_pct:
|
else:
|
||||||
motion_action["user_notice"] = (
|
notices: list[str] = []
|
||||||
f"{MOTION_AXIS_LABELS[axis]}请求位置{requested_position_pct}%超过"
|
if requested_position_value != requested_position_pct:
|
||||||
"0到100%行程;已采用100%的备用目标并移动到机械末端。"
|
notices.append(
|
||||||
)
|
f"{MOTION_AXIS_LABELS[axis]}请求位置"
|
||||||
|
f"{_format_numeric_request(requested_position_value)}%;"
|
||||||
|
f"已取整为{requested_position_pct}%。"
|
||||||
|
)
|
||||||
|
if requested_position_pct != position_pct:
|
||||||
|
notices.append(
|
||||||
|
f"取整后超过0到100%行程;已采用{position_pct}%的备用目标"
|
||||||
|
"并移动到对应机械末端。"
|
||||||
|
)
|
||||||
|
if notices:
|
||||||
|
motion_action["user_notice"] = " ".join(notices)
|
||||||
motion_actions.append(motion_action)
|
motion_actions.append(motion_action)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
@@ -301,6 +301,29 @@ class LocalIntentParserTests(unittest.TestCase):
|
|||||||
self.assertTrue(minimum["actions"][0]["motion_intent"]["duration_clamped"])
|
self.assertTrue(minimum["actions"][0]["motion_intent"]["duration_clamped"])
|
||||||
self.assertIn("最小允许时长为0.3秒", minimum["actions"][0]["user_notice"])
|
self.assertIn("最小允许时长为0.3秒", minimum["actions"][0]["user_notice"])
|
||||||
|
|
||||||
|
def test_rounds_decimal_and_clamps_negative_absolute_positions(self) -> None:
|
||||||
|
decimal = parse_local_intent("副驾座椅水平位置调到50.5%")
|
||||||
|
negative = parse_local_intent("副驾座椅水平位置调到-10%")
|
||||||
|
|
||||||
|
self.assertEqual(decimal["ambiguities"], [])
|
||||||
|
self.assertEqual(decimal["actions"][0]["position_pct"], 51)
|
||||||
|
self.assertIn("取整为51%", decimal["actions"][0]["user_notice"])
|
||||||
|
self.assertEqual(negative["ambiguities"], [])
|
||||||
|
self.assertEqual(negative["actions"][0]["position_pct"], 0)
|
||||||
|
self.assertIn("0%的备用目标", negative["actions"][0]["user_notice"])
|
||||||
|
|
||||||
|
def test_zero_duration_uses_minimum_safe_motion_instead_of_rejecting(self) -> None:
|
||||||
|
result = parse_local_intent(
|
||||||
|
"副驾靠背向后移动0秒", self.motion_context()
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(result["ambiguities"], [])
|
||||||
|
motion = result["actions"][0]["motion_intent"]
|
||||||
|
self.assertEqual(motion["requested_duration_ms"], 0)
|
||||||
|
self.assertEqual(motion["applied_duration_ms"], 300)
|
||||||
|
self.assertTrue(motion["duration_clamped"])
|
||||||
|
self.assertIn("已按0.3秒", result["actions"][0]["user_notice"])
|
||||||
|
|
||||||
def test_rejects_unsupported_level_and_conflicting_state(self) -> None:
|
def test_rejects_unsupported_level_and_conflicting_state(self) -> None:
|
||||||
unsupported = parse_local_intent("副驾加热开到4档")
|
unsupported = parse_local_intent("副驾加热开到4档")
|
||||||
conflicting = parse_local_intent("打开副驾加热然后关闭副驾加热")
|
conflicting = parse_local_intent("打开副驾加热然后关闭副驾加热")
|
||||||
|
|||||||
@@ -139,6 +139,11 @@ with this conservative deterministic baseline: longitudinal 80%, cushion tilt 30
|
|||||||
remaining upright and reversible. State this inferred ergonomic baseline in decision_summary. Apply
|
remaining upright and reversible. State this inferred ergonomic baseline in decision_summary. Apply
|
||||||
the same causal reasoning to paraphrases such as insufficient legroom, feeling squeezed, wanting more
|
the same causal reasoning to paraphrases such as insufficient legroom, feeling squeezed, wanting more
|
||||||
space, or asking for a comfortable posture; do not require the user to name a motor or percentage.
|
space, or asking for a comfortable posture; do not require the user to name a motor or percentage.
|
||||||
|
When a user simply asks to make the seat more comfortable without numeric targets, use that same
|
||||||
|
80/30/35/10 ergonomic baseline. For an unsaved "boss", "executive", or equivalent front-seat
|
||||||
|
clearance mode, infer the conventional intent of moving this controllable front passenger seat out
|
||||||
|
of the way and use this deterministic baseline: longitudinal 20%, cushion tilt 30%, backrest recline
|
||||||
|
10%, and legrest height 0%. State the inferred mode meaning and targets in decision_summary.
|
||||||
|
|
||||||
When execution_policy.planning_environment is dry_run_candidate_synthesis, a named preset is not
|
When execution_policy.planning_environment is dry_run_candidate_synthesis, a named preset is not
|
||||||
required. For a commonly understood mode, activity, or comfort goal, decompose the goal into the
|
required. For a commonly understood mode, activity, or comfort goal, decompose the goal into the
|
||||||
@@ -156,8 +161,10 @@ capabilities as usual.
|
|||||||
|
|
||||||
Return every understood goal in requests using an exact capability name from the registry. Use
|
Return every understood goal in requests using an exact capability name from the registry. Use
|
||||||
target_scope and operation to preserve the user's intent. Put concrete capability parameters in the
|
target_scope and operation to preserve the user's intent. Put concrete capability parameters in the
|
||||||
parameters list when they are known or defensibly inferred. Do not drop catalog_only requests; the
|
parameters list when they are known or defensibly inferred. Preserve a catalog_only request when the
|
||||||
runtime router, not the model, decides which domain adapter can execute them.
|
user explicitly asks for that exact feature, but do not invent catalog_only capabilities while
|
||||||
|
synthesizing a vague comfort goal or an unknown named mode. Inferred seat postures must use only the
|
||||||
|
registered executable seat motion capabilities so the plan can safely reach hardware.
|
||||||
For executable capabilities, resolve follow-up and relative intent against runtime state and return
|
For executable capabilities, resolve follow-up and relative intent against runtime state and return
|
||||||
absolute target parameter values that the adapter can execute. Do not delegate that semantic decision
|
absolute target parameter values that the adapter can execute. Do not delegate that semantic decision
|
||||||
to downstream code. Catalog-only requests may remain relative when their current state is unavailable.
|
to downstream code. Catalog-only requests may remain relative when their current state is unavailable.
|
||||||
|
|||||||
+21
-9
@@ -14,8 +14,8 @@
|
|||||||
"coverage_dimensions": {
|
"coverage_dimensions": {
|
||||||
"targets": ["副驾", "副驾驶", "副驾位", "右前座", "乘客座", "省略座位", "后排/左前/全部座椅安全拒绝"],
|
"targets": ["副驾", "副驾驶", "副驾位", "右前座", "乘客座", "省略座位", "后排/左前/全部座椅安全拒绝"],
|
||||||
"verbs": ["打开", "开启", "启动", "调到", "设置为", "进入", "切换到", "执行", "关闭", "停止", "归零", "复位"],
|
"verbs": ["打开", "开启", "启动", "调到", "设置为", "进入", "切换到", "执行", "关闭", "停止", "归零", "复位"],
|
||||||
"values": ["阿拉伯数字", "中文数字", "全角百分号", "百分之表达", "高/中/低档", "正常秒数/半秒/毫秒", "时长上下限钳制", "绝对角度", "相对角度", "相对百分比", "逐轴机械端点", "超量程备用端点", "小数/负数安全拒绝"],
|
"values": ["阿拉伯数字", "中文数字", "全角百分号", "百分之表达", "高/中/低档", "正常秒数/半秒/毫秒", "时长上下限钳制", "零时长最小动作", "绝对角度", "相对角度", "相对百分比", "逐轴机械端点", "超量程备用端点", "小数取整/负数端点容错"],
|
||||||
"functions": ["加热/取暖", "通风/吹风", "水平", "坐盆", "靠背", "腿托", "四轴组合", "相对移动", "按时长移动", "预设模式", "GPT模糊舒适意图"],
|
"functions": ["加热/取暖", "通风/吹风", "水平", "坐盆", "靠背", "腿托", "四轴组合", "相对移动", "按时长移动", "预设模式", "GPT模糊舒适意图", "GPT未知模式姿态合成"],
|
||||||
"safety": ["主驾", "前排范围", "越界容错", "缺少状态", "缺少当前位置", "矛盾指令", "未知能力", "多模式冲突", "非端点堵转不容错"]
|
"safety": ["主驾", "前排范围", "越界容错", "缺少状态", "缺少当前位置", "矛盾指令", "未知能力", "多模式冲突", "非端点堵转不容错"]
|
||||||
},
|
},
|
||||||
"cases": [
|
"cases": [
|
||||||
@@ -315,7 +315,10 @@
|
|||||||
|
|
||||||
{"id": "MOTION-M1-ABS-OVERFLOW", "group": "motion", "utterance": "坐垫位置调到150%", "expected": {"cushion_tilt": 100}, "expected_notice_contains": ["备用目标", "机械末端"]},
|
{"id": "MOTION-M1-ABS-OVERFLOW", "group": "motion", "utterance": "坐垫位置调到150%", "expected": {"cushion_tilt": 100}, "expected_notice_contains": ["备用目标", "机械末端"]},
|
||||||
{"id": "MOTION-M2-ABS-ANGLE-UNDERFLOW", "group": "motion", "utterance": "副驾靠背调到80度", "expected": {"backrest_recline": 0}, "expected_notice_contains": ["备用目标", "95度", "最前方"]},
|
{"id": "MOTION-M2-ABS-ANGLE-UNDERFLOW", "group": "motion", "utterance": "副驾靠背调到80度", "expected": {"backrest_recline": 0}, "expected_notice_contains": ["备用目标", "95度", "最前方"]},
|
||||||
{"id": "MOTION-M3-ABS-ANGLE-OVERFLOW", "group": "motion", "utterance": "腿托调到200度", "expected": {"legrest_height": 100}, "expected_notice_contains": ["备用目标", "167度", "最上方"]}
|
{"id": "MOTION-M3-ABS-ANGLE-OVERFLOW", "group": "motion", "utterance": "腿托调到200度", "expected": {"legrest_height": 100}, "expected_notice_contains": ["备用目标", "167度", "最上方"]},
|
||||||
|
{"id": "MOTION-M0-ABS-DECIMAL-ROUND", "group": "motion", "utterance": "副驾座椅水平位置调到50.5%", "expected": {"longitudinal": 51}, "expected_notice_contains": ["取整为51%"]},
|
||||||
|
{"id": "MOTION-M0-ABS-NEGATIVE-CLAMP", "group": "motion", "utterance": "副驾座椅水平位置调到-10%", "expected": {"longitudinal": 0}, "expected_notice_contains": ["备用目标", "0%", "机械末端"]},
|
||||||
|
{"id": "MOTION-M2-TIMED-ZERO-MIN-CLAMP", "group": "motion", "utterance": "副驾靠背向后移动0秒", "expected": {}, "expected_motion": {"axis": "backrest_recline", "mode": "timed", "direction": "increase", "requested_duration_ms": 0, "applied_duration_ms": 300, "max_duration_ms": 30000, "min_duration_ms": 300, "duration_clamped": true}, "expected_notice_contains": ["最小允许时长为0.3秒", "已按0.3秒"]}
|
||||||
],
|
],
|
||||||
"mode_cases": [
|
"mode_cases": [
|
||||||
{"mode_id": "easy_entry_exit", "expected": {"longitudinal": 100, "cushion_tilt": 0, "backrest_recline": 0, "legrest_height": 0}, "variants": [
|
{"mode_id": "easy_entry_exit", "expected": {"longitudinal": 100, "cushion_tilt": 0, "backrest_recline": 0, "legrest_height": 0}, "variants": [
|
||||||
@@ -385,6 +388,20 @@
|
|||||||
"provider": "openai",
|
"provider": "openai",
|
||||||
"expected_provider_prefix": "openai:gpt-5.6-sol",
|
"expected_provider_prefix": "openai:gpt-5.6-sol",
|
||||||
"expected": {"longitudinal": 80, "cushion_tilt": 30, "backrest_recline": 35, "legrest_height": 10}
|
"expected": {"longitudinal": 80, "cushion_tilt": 30, "backrest_recline": 35, "legrest_height": 10}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MODEL-FUZZY-COMFORT-ONLY",
|
||||||
|
"utterance": "把座椅调舒服一点",
|
||||||
|
"provider": "openai",
|
||||||
|
"expected_provider_prefix": "openai:gpt-5.6-sol",
|
||||||
|
"expected": {"longitudinal": 80, "cushion_tilt": 30, "backrest_recline": 35, "legrest_height": 10}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MODEL-UNKNOWN-BOSS-MODE",
|
||||||
|
"utterance": "打开老板模式",
|
||||||
|
"provider": "openai",
|
||||||
|
"expected_provider_prefix": "openai:gpt-5.6-sol",
|
||||||
|
"expected": {"longitudinal": 20, "cushion_tilt": 30, "backrest_recline": 10, "legrest_height": 0}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"rejection_cases": [
|
"rejection_cases": [
|
||||||
@@ -396,18 +413,13 @@
|
|||||||
{"id": "SAFE-MISSING-STATE", "utterance": "副驾座椅通风", "expected_status": "needs_confirmation"},
|
{"id": "SAFE-MISSING-STATE", "utterance": "副驾座椅通风", "expected_status": "needs_confirmation"},
|
||||||
{"id": "SAFE-CONTRADICTORY", "utterance": "打开副驾加热然后关闭副驾加热", "expected_status": "needs_confirmation"},
|
{"id": "SAFE-CONTRADICTORY", "utterance": "打开副驾加热然后关闭副驾加热", "expected_status": "needs_confirmation"},
|
||||||
{"id": "SAFE-UNKNOWN-CAPABILITY", "utterance": "把副驾座椅按摩打开", "expected_status": "needs_confirmation"},
|
{"id": "SAFE-UNKNOWN-CAPABILITY", "utterance": "把副驾座椅按摩打开", "expected_status": "needs_confirmation"},
|
||||||
{"id": "SAFE-UNKNOWN-MODE", "utterance": "打开老板模式", "expected_status": "needs_confirmation"},
|
|
||||||
{"id": "SAFE-MULTIPLE-MODES", "utterance": "进入办公模式和零重力模式", "expected_status": "needs_confirmation"},
|
{"id": "SAFE-MULTIPLE-MODES", "utterance": "进入办公模式和零重力模式", "expected_status": "needs_confirmation"},
|
||||||
{"id": "SAFE-LEGREST-EXTENSION", "utterance": "把副驾腿托伸长到50%", "expected_status": "needs_confirmation"},
|
{"id": "SAFE-LEGREST-EXTENSION", "utterance": "把副驾腿托伸长到50%", "expected_status": "needs_confirmation"},
|
||||||
{"id": "SAFE-RELATIVE-WRONG-UNIT", "utterance": "坐垫向前移动10度", "expected_status": "needs_confirmation"},
|
{"id": "SAFE-RELATIVE-WRONG-UNIT", "utterance": "坐垫向前移动10度", "expected_status": "needs_confirmation"},
|
||||||
{"id": "SAFE-RELATIVE-MISSING-AXIS", "utterance": "向前移动10度", "expected_status": "needs_confirmation"},
|
{"id": "SAFE-RELATIVE-MISSING-AXIS", "utterance": "向前移动10度", "expected_status": "needs_confirmation"},
|
||||||
{"id": "SAFE-VAGUE", "utterance": "把座椅调舒服一点", "expected_status": "needs_confirmation"},
|
|
||||||
{"id": "SAFE-UNSUPPORTED-REAR-ROW", "utterance": "打开后排座椅加热", "expected_status": "needs_confirmation"},
|
{"id": "SAFE-UNSUPPORTED-REAR-ROW", "utterance": "打开后排座椅加热", "expected_status": "needs_confirmation"},
|
||||||
{"id": "SAFE-UNSUPPORTED-FRONT-LEFT", "utterance": "打开左前座椅通风", "expected_status": "needs_confirmation"},
|
{"id": "SAFE-UNSUPPORTED-FRONT-LEFT", "utterance": "打开左前座椅通风", "expected_status": "needs_confirmation"},
|
||||||
{"id": "SAFE-UNSUPPORTED-ALL-SEATS", "utterance": "打开所有座椅加热", "expected_status": "needs_confirmation"},
|
{"id": "SAFE-UNSUPPORTED-ALL-SEATS", "utterance": "打开所有座椅加热", "expected_status": "needs_confirmation"}
|
||||||
{"id": "SAFE-DECIMAL-POSITION", "utterance": "副驾座椅水平位置调到50.5%", "expected_status": "needs_confirmation"},
|
|
||||||
{"id": "SAFE-NEGATIVE-POSITION", "utterance": "副驾座椅水平位置调到-10%", "expected_status": "needs_confirmation"},
|
|
||||||
{"id": "SAFE-ZERO-DURATION", "utterance": "副驾靠背向后移动0秒", "expected_status": "needs_confirmation"}
|
|
||||||
],
|
],
|
||||||
"final_state": {
|
"final_state": {
|
||||||
"utterance": "关闭副驾座椅加热和通风,并把水平、坐盆、靠背、腿托四轴归零",
|
"utterance": "关闭副驾座椅加热和通风,并把水平、坐盆、靠背、腿托四轴归零",
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class SeatFrontendInputCoverageTests(unittest.TestCase):
|
|||||||
def test_suite_has_broad_equivalence_and_safety_coverage(self) -> None:
|
def test_suite_has_broad_equivalence_and_safety_coverage(self) -> None:
|
||||||
self.assertGreaterEqual(len(self.positive_cases), 160)
|
self.assertGreaterEqual(len(self.positive_cases), 160)
|
||||||
self.assertGreaterEqual(len(self.model_cases), 4)
|
self.assertGreaterEqual(len(self.model_cases), 4)
|
||||||
self.assertGreaterEqual(len(self.rejection_cases), 20)
|
self.assertGreaterEqual(len(self.rejection_cases), 15)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
{case["group"] for case in self.positive_cases},
|
{case["group"] for case in self.positive_cases},
|
||||||
{"thermal", "motion", "combined", "mode"},
|
{"thermal", "motion", "combined", "mode"},
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ class OpenAIResponsesTests(unittest.TestCase):
|
|||||||
self.assertIn("longitudinal 80%", normalized)
|
self.assertIn("longitudinal 80%", normalized)
|
||||||
self.assertIn("backrest recline 35%", normalized)
|
self.assertIn("backrest recline 35%", normalized)
|
||||||
self.assertIn("do not manufacture a driver-seat target", normalized)
|
self.assertIn("do not manufacture a driver-seat target", normalized)
|
||||||
|
self.assertIn("boss", normalized)
|
||||||
|
self.assertIn("longitudinal 20%", normalized)
|
||||||
|
self.assertIn("do not invent catalog_only capabilities", normalized)
|
||||||
|
|
||||||
def test_non_streaming_structured_output(self) -> None:
|
def test_non_streaming_structured_output(self) -> None:
|
||||||
response_body = json.dumps({"output_text": json.dumps(INTENT)})
|
response_body = json.dumps({"output_text": json.dumps(INTENT)})
|
||||||
|
|||||||
Reference in New Issue
Block a user