Files
mcm-26-b/src/mcm26b/evaluation/__init__.py
T

82 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""评估与统计:从自身动作日志离线复算虚拟时刻与统计量。
"演练统计"验收要求:总时间与分项耗时可独立复算 [题面/计划 §6]。
recompute_virtual_time 是回放测试与本模块共用的唯一复算实现,
客户端日志格式为 SessionLog 中 accepted 事件的 {path, request, response}。
"""
from __future__ import annotations
from ..config import CostConfig
from ..protocol.rules import CLEAR_SUCCESS
from ..protocol.timing import clear_cost, measure_cost
def recompute_virtual_time(events: list[dict],
cost: CostConfig = CostConfig()) -> dict:
"""从动作日志复算逐步虚拟时刻。
events 每项形如 {path, request, response}(accepted=true)。
返回逐步表与最终时刻;任何一步与服务器响应偏差超过容差记入 mismatches。
"""
position = (0.0, 0.0)
channel = 1
virtual = 0.0
steps, mismatches = [], []
for index, event in enumerate(events):
path, request = event["path"], event["request"]
response = event["response"]
if response.get("accepted") is not True:
mismatches.append({"index": index, "issue": "响应未被接受"})
continue
if path == "/enter":
position, channel, virtual = (0.0, 0.0), 1, response["virtual_time_s"]
elif path == "/measure":
pos = (request["position"]["x"], request["position"]["y"])
predicted = measure_cost(position, pos, channel, request["channel"], cost)
position, channel = pos, request["channel"]
virtual += predicted.total_s
elif path == "/clear":
pos = (request["position"]["x"], request["position"]["y"])
success = response.get("clear_result") == CLEAR_SUCCESS
predicted = clear_cost(position, pos, success, cost)
position = pos
virtual += predicted.total_s
elif path == "/exit":
pass # /exit 不推进虚拟时钟 [题面]
else:
mismatches.append({"index": index, "issue": f"未知路径 {path}"})
continue
delta = response["virtual_time_s"] - virtual
if abs(delta) > 1e-6:
mismatches.append({"index": index, "path": path,
"recomputed_s": virtual,
"recorded_s": response["virtual_time_s"],
"gap_s": delta})
steps.append({"index": index, "path": path,
"predicted_s": round(virtual, 9),
"recorded_s": response["virtual_time_s"]})
return {"steps": steps, "final_virtual_time_s": virtual,
"mismatches": mismatches}
def summarize(events: list[dict]) -> dict:
"""动作计数摘要:检测/清除/移动距离等基础统计。"""
position = (0.0, 0.0)
measures, clears, cleared, distance_m = 0, 0, 0, 0.0
for event in events:
request = event["request"]
if event["path"] in ("/measure", "/clear"):
pos = (request["position"]["x"], request["position"]["y"])
distance_m += ((pos[0] - position[0]) ** 2
+ (pos[1] - position[1]) ** 2) ** 0.5
position = pos
if event["path"] == "/measure":
measures += 1
elif event["path"] == "/clear":
clears += 1
if event["response"].get("clear_result") == CLEAR_SUCCESS:
cleared += 1
return {"measures": measures, "clears": clears, "cleared": cleared,
"distance_m": distance_m}