mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-19 16:22:08 +08:00
I did the backstroke in college
This commit is contained in:
@@ -28,11 +28,11 @@ AUTO_HOLD_VOLT_CARS = {
|
||||
CAR.CHEVROLET_VOLT_ASCM,
|
||||
CAR.CHEVROLET_VOLT_CAMERA,
|
||||
}
|
||||
AUTO_HOLD_DRIVE_GEARS = {
|
||||
AUTO_HOLD_DRIVE_GEARS = (
|
||||
GearShifter.drive,
|
||||
GearShifter.low,
|
||||
GearShifter.manumatic,
|
||||
}
|
||||
)
|
||||
AUTO_HOLD_MIN_BRAKE = 80
|
||||
AUTO_HOLD_MAX_BRAKE = 240
|
||||
AUTO_HOLD_MIN_DRIVE_TIME_S = 3.0
|
||||
|
||||
@@ -2,6 +2,8 @@ import sys
|
||||
import types
|
||||
from types import SimpleNamespace
|
||||
|
||||
from opendbc.car import structs
|
||||
|
||||
fake_interfaces = types.ModuleType("opendbc.car.interfaces")
|
||||
|
||||
|
||||
@@ -33,6 +35,7 @@ fake_testing_grounds.testing_ground = SimpleNamespace(use_1=False)
|
||||
sys.modules.setdefault("openpilot.starpilot.common.testing_grounds", fake_testing_grounds)
|
||||
|
||||
from opendbc.car.gm.carcontroller import (
|
||||
AUTO_HOLD_DRIVE_GEARS,
|
||||
CarController,
|
||||
estimate_auto_hold_brake,
|
||||
get_adas_keepalive_step,
|
||||
@@ -220,6 +223,13 @@ def test_auto_hold_brake_estimate_uses_driver_or_op_brake_and_clamps():
|
||||
assert estimate_auto_hold_brake(100.0, 400.0) == 240
|
||||
|
||||
|
||||
def test_auto_hold_drive_gears_accept_capnp_dynamic_enum_membership():
|
||||
msg = structs.CarState.new_message()
|
||||
msg.gearShifter = structs.CarState.GearShifter.drive
|
||||
|
||||
assert msg.gearShifter in AUTO_HOLD_DRIVE_GEARS
|
||||
|
||||
|
||||
def test_auto_hold_activation_allows_direct_entry_from_stopped_brake_press():
|
||||
assert should_activate_auto_hold(
|
||||
True,
|
||||
|
||||
@@ -35,10 +35,18 @@ def make_cem(*, model_length: float, model_stopped: bool = False, tracking_lead:
|
||||
model_length=model_length,
|
||||
model_stopped=model_stopped,
|
||||
tracking_lead=tracking_lead,
|
||||
starpilot_vcruise=SimpleNamespace(stop_sign_confirmed=stop_sign_confirmed, forcing_stop=forcing_stop),
|
||||
starpilot_vcruise=SimpleNamespace(
|
||||
stop_sign_confirmed=stop_sign_confirmed,
|
||||
forcing_stop=forcing_stop,
|
||||
slc=SimpleNamespace(experimental_mode=False),
|
||||
),
|
||||
starpilot_following=SimpleNamespace(slower_lead=False, following_lead=False),
|
||||
lead_one=SimpleNamespace(status=lead_status, dRel=lead_d_rel, vLead=lead_v_lead,
|
||||
modelProb=lead_model_prob, radar=lead_radar),
|
||||
road_curvature_detected=False,
|
||||
driving_in_curve=False,
|
||||
lane_width_left=0.0,
|
||||
lane_width_right=0.0,
|
||||
)
|
||||
return ConditionalExperimentalMode(planner)
|
||||
|
||||
@@ -368,6 +376,35 @@ def test_standstill_update_can_activate_exp_from_red_light_detection(monkeypatch
|
||||
assert cem.status_value == conditional_experimental_mode_module.CEStatus["STOP_LIGHT"]
|
||||
|
||||
|
||||
def test_first_post_standstill_pullaway_frame_does_not_blip_into_exp(monkeypatch):
|
||||
cem = make_cem(
|
||||
model_length=70.0,
|
||||
tracking_lead=True,
|
||||
lead_status=True,
|
||||
lead_d_rel=8.4,
|
||||
lead_v_lead=2.7,
|
||||
lead_model_prob=0.999,
|
||||
)
|
||||
toggles = make_update_toggles()
|
||||
toggles.conditional_lead = True
|
||||
toggles.conditional_slower_lead = True
|
||||
toggles.conditional_stopped_lead = True
|
||||
standstill_sm = make_update_sm(standstill=True)
|
||||
moving_sm = make_update_sm(standstill=False)
|
||||
|
||||
now = [100.0]
|
||||
monkeypatch.setattr(conditional_experimental_mode_module.time, "monotonic", lambda: now[0])
|
||||
|
||||
cem.update(0.0, standstill_sm, toggles)
|
||||
assert not cem.experimental_mode
|
||||
|
||||
now[0] = 100.1
|
||||
cem.update(0.12, moving_sm, toggles)
|
||||
|
||||
assert not cem.experimental_mode
|
||||
assert cem.status_value == conditional_experimental_mode_module.CEStatus["OFF"]
|
||||
|
||||
|
||||
def test_standstill_update_can_activate_exp_from_dashboard_stop_sign(monkeypatch):
|
||||
cem = make_cem(model_length=80.0, model_stopped=False)
|
||||
toggles = make_update_toggles()
|
||||
|
||||
@@ -120,8 +120,10 @@ class ConditionalExperimentalMode:
|
||||
if triggered:
|
||||
self.mode_hold_until = now + self.CEM_TRANSITION_GUARD_TIME
|
||||
self.mode_false_since = 0.0
|
||||
elif self.mode_false_since == 0.0:
|
||||
elif self.prev_experimental_mode and self.mode_false_since == 0.0:
|
||||
self.mode_false_since = now
|
||||
elif not self.prev_experimental_mode:
|
||||
self.mode_false_since = 0.0
|
||||
|
||||
hold_active = now < self.mode_hold_until
|
||||
transition_buffer_active = self.mode_false_since != 0.0 and (now - self.mode_false_since) < self.CEM_TRANSITION_BUFFER_TIME
|
||||
|
||||
@@ -403,7 +403,7 @@
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: minmax(170px, 1.3fr) minmax(230px, 1fr) minmax(210px, 1fr) minmax(180px, 0.9fr);
|
||||
grid-template-columns: minmax(170px, 1.35fr) minmax(170px, 0.8fr) minmax(210px, 1fr) minmax(180px, 0.9fr);
|
||||
min-width: 0;
|
||||
padding: 0.9rem 0;
|
||||
}
|
||||
@@ -412,6 +412,10 @@
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.dashboard-drive-row.is-pending {
|
||||
color: var(--dashboard-muted);
|
||||
}
|
||||
|
||||
.dashboard-drive-main,
|
||||
.dashboard-drive-details,
|
||||
.dashboard-attention,
|
||||
|
||||
@@ -110,6 +110,7 @@ function fallbackDashboard(data, unit) {
|
||||
segmentCount: 0,
|
||||
distractedMoments: 0,
|
||||
unresponsiveMoments: 0,
|
||||
attentionKnown: true,
|
||||
distanceUnit: unit,
|
||||
speedUnit: unit === "kilometers" ? "kph" : "mph",
|
||||
},
|
||||
@@ -144,21 +145,28 @@ function fallbackDashboard(data, unit) {
|
||||
};
|
||||
}
|
||||
|
||||
function driveStatsReady(drive) {
|
||||
return drive?.attentionKnown !== false;
|
||||
}
|
||||
|
||||
function renderLastDrive(drive) {
|
||||
const ready = driveStatsReady(drive);
|
||||
return `
|
||||
<section class="dashboard-card dashboard-last-drive">
|
||||
<div class="dashboard-card-kicker"><span></span>Last drive</div>
|
||||
<div class="dashboard-drive-date">${escapeHtml(formatDate(drive.date))}</div>
|
||||
<div class="dashboard-drive-metrics">
|
||||
<div><strong>${formatOneDecimal(drive.distance)}</strong><span>${escapeHtml(drive.distanceUnit || "miles")}</span></div>
|
||||
<div><strong>${ready ? formatOneDecimal(drive.distance) : "..."}</strong><span>${ready ? escapeHtml(drive.distanceUnit || "miles") : "analyzing"}</span></div>
|
||||
<div><strong>${formatDuration(drive.duration)}</strong><span>duration</span></div>
|
||||
<div><strong>${formatInt(drive.avgSpeed)}</strong><span>${escapeHtml(drive.speedUnit || "mph")} avg</span></div>
|
||||
<div><strong>${formatPercent(drive.engagedPercent)}</strong><span>engaged</span></div>
|
||||
<div><strong>${ready ? formatInt(drive.avgSpeed) : "..."}</strong><span>${ready ? `${escapeHtml(drive.speedUnit || "mph")} avg` : "speed"}</span></div>
|
||||
<div><strong>${ready ? formatPercent(drive.engagedPercent) : "..."}</strong><span>engaged</span></div>
|
||||
</div>
|
||||
<div class="dashboard-drive-footer">
|
||||
<span><i class="bi bi-cpu"></i>${escapeHtml(drive.model || "Unknown model")}</span>
|
||||
<span><i class="bi bi-eye"></i>${formatInt(drive.distractedMoments)} distracted</span>
|
||||
<span><i class="bi bi-exclamation-triangle"></i>${formatInt(drive.unresponsiveMoments)} unresponsive</span>
|
||||
${ready
|
||||
? `<span><i class="bi bi-eye"></i>${formatInt(drive.distractedMoments)} distracted</span>
|
||||
<span><i class="bi bi-exclamation-triangle"></i>${formatInt(drive.unresponsiveMoments)} unresponsive</span>`
|
||||
: `<span><i class="bi bi-hourglass-split"></i>Analyzing stats</span>`}
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
@@ -236,27 +244,30 @@ function renderRecentDrives(drives) {
|
||||
`;
|
||||
}
|
||||
|
||||
const rows = drives.map(drive => `
|
||||
<div class="dashboard-drive-row">
|
||||
const rows = drives.map(drive => {
|
||||
const ready = driveStatsReady(drive);
|
||||
return `
|
||||
<div class="dashboard-drive-row ${ready ? "" : "is-pending"}">
|
||||
<div class="dashboard-drive-main">
|
||||
<strong>${escapeHtml(formatDate(drive.date))}</strong>
|
||||
<span>${escapeHtml(drive.model || "Unknown model")}</span>
|
||||
</div>
|
||||
<div class="dashboard-drive-details">
|
||||
<span>${formatOneDecimal(drive.distance)} ${escapeHtml(drive.distanceUnit || "miles")}</span>
|
||||
<span>${ready ? `${formatOneDecimal(drive.distance)} ${escapeHtml(drive.distanceUnit || "miles")}` : "Analyzing stats"}</span>
|
||||
<span>${formatDuration(drive.duration)}</span>
|
||||
<span>${formatInt(drive.segmentCount)} segments</span>
|
||||
</div>
|
||||
<div class="dashboard-attention">
|
||||
<span>${formatInt(drive.distractedMoments)} distracted</span>
|
||||
<span>${formatInt(drive.unresponsiveMoments)} unresponsive</span>
|
||||
${ready
|
||||
? `<span>${formatInt(drive.distractedMoments)} distracted</span><span>${formatInt(drive.unresponsiveMoments)} unresponsive</span>`
|
||||
: `<span>Waiting for full route analysis</span>`}
|
||||
</div>
|
||||
<div class="dashboard-engaged-cell">
|
||||
<div class="dashboard-mini-bar"><span style="width:${Math.max(0, Math.min(100, numberValue(drive.engagedPercent)))}%"></span></div>
|
||||
<strong>${formatPercent(drive.engagedPercent)} engaged</strong>
|
||||
<div class="dashboard-mini-bar"><span style="width:${ready ? Math.max(0, Math.min(100, numberValue(drive.engagedPercent))) : 0}%"></span></div>
|
||||
<strong>${ready ? `${formatPercent(drive.engagedPercent)} engaged` : "Pending"}</strong>
|
||||
</div>
|
||||
</div>
|
||||
`).join("");
|
||||
`;
|
||||
}).join("");
|
||||
|
||||
return `
|
||||
<section class="dashboard-card dashboard-recent">
|
||||
|
||||
@@ -207,6 +207,11 @@ class FakeParams:
|
||||
self.values[key] = value
|
||||
|
||||
|
||||
class FailingPutParams(FakeParams):
|
||||
def put(self, key, value):
|
||||
raise RuntimeError("unknown key")
|
||||
|
||||
|
||||
class FakeMessage:
|
||||
def __init__(self, kind, log_mono_time, payload):
|
||||
self._kind = kind
|
||||
@@ -331,6 +336,20 @@ def test_route_sampling_bounds_analyzed_segments():
|
||||
assert [segment["num"] for segment in sampled["segments"]] == [0, 4, 9]
|
||||
|
||||
|
||||
def test_route_listing_prefers_segment_candidates_with_logs(tmp_path):
|
||||
hd_root = tmp_path / "realdata_HD"
|
||||
standard_root = tmp_path / "realdata"
|
||||
hd_segment = hd_root / "00000001--abcdef1234--0"
|
||||
standard_segment = standard_root / "00000001--abcdef1234--0"
|
||||
hd_segment.mkdir(parents=True)
|
||||
standard_segment.mkdir(parents=True)
|
||||
(standard_segment / "qlog.zst").write_bytes(b"")
|
||||
|
||||
routes = utilities._list_dashboard_routes([hd_root, standard_root])
|
||||
|
||||
assert routes[0]["segments"][0]["path"] == standard_segment
|
||||
|
||||
|
||||
def test_top_models_are_ranked_from_persisted_usage_not_favorites():
|
||||
params = FakeParams({
|
||||
"AvailableModels": "orion,vega,atlas,nova",
|
||||
@@ -439,6 +458,29 @@ def test_persistent_loader_accepts_decoded_param_dict():
|
||||
assert stats["routes"]["route-1"]["attentionKnown"] is False
|
||||
|
||||
|
||||
def test_dashboard_persistent_stats_fallback_to_file_when_param_put_fails(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(utilities, "DASHBOARD_PARAMS_DIR", tmp_path)
|
||||
params = FailingPutParams()
|
||||
drive = {
|
||||
"name": "route-1",
|
||||
"date": "2026-06-15T08:00:00",
|
||||
"distanceMeters": 1000.0,
|
||||
"duration": 60,
|
||||
"engagedSeconds": 30.0,
|
||||
"model": "Orion",
|
||||
"routeModifiedAt": 100,
|
||||
"attentionKnown": True,
|
||||
"analysisComplete": True,
|
||||
}
|
||||
|
||||
utilities._update_dashboard_persistent_stats(params, [drive], wall_now=1000)
|
||||
stats = utilities._load_dashboard_persistent_stats(params)
|
||||
|
||||
assert (tmp_path / utilities.DASHBOARD_PERSISTENT_STATS_PARAM).is_file()
|
||||
assert stats["routes"]["route-1"]["distanceMeters"] == 1000
|
||||
assert stats["routes"]["route-1"]["analysisComplete"] is True
|
||||
|
||||
|
||||
def test_lightweight_routes_surface_recent_drives_without_log_analysis(monkeypatch):
|
||||
utilities._invalidate_dashboard_cache()
|
||||
route_infos = [
|
||||
@@ -513,6 +555,38 @@ def test_unknown_attention_rows_do_not_reset_persisted_clean_records():
|
||||
assert records["cleanDriveStreak"]["value"] == "1 drive"
|
||||
|
||||
|
||||
def test_lightweight_route_update_preserves_parsed_duration():
|
||||
params = FakeParams()
|
||||
parsed_drive = {
|
||||
"name": "route-1",
|
||||
"date": "2026-06-15T08:00:00",
|
||||
"distanceMeters": 10000.0,
|
||||
"duration": 58,
|
||||
"engagedSeconds": 30.0,
|
||||
"model": "Orion",
|
||||
"routeModifiedAt": 100,
|
||||
"attentionKnown": True,
|
||||
"analysisComplete": True,
|
||||
}
|
||||
shell_drive = {
|
||||
"name": "route-1",
|
||||
"date": "2026-06-15T08:00:00",
|
||||
"distanceMeters": 0.0,
|
||||
"duration": 60,
|
||||
"engagedSeconds": 0.0,
|
||||
"model": "Orion",
|
||||
"routeModifiedAt": 100,
|
||||
"attentionKnown": False,
|
||||
"analysisComplete": False,
|
||||
}
|
||||
|
||||
utilities._update_dashboard_persistent_stats(params, [parsed_drive], wall_now=1000)
|
||||
stats = utilities._update_dashboard_persistent_stats(params, [shell_drive], wall_now=1000)
|
||||
|
||||
assert stats["routes"]["route-1"]["duration"] == 58
|
||||
assert stats["routes"]["route-1"]["analysisComplete"] is True
|
||||
|
||||
|
||||
def test_github_urls_accept_owner_repo_origin():
|
||||
assert utilities.get_github_changelog_url("owner/repo", "main") == "https://github.com/owner/repo/commits/main/"
|
||||
assert utilities.get_github_changelog_url("github.com/owner/repo", "main") == "https://github.com/owner/repo/commits/main/"
|
||||
|
||||
@@ -61,12 +61,13 @@ DASHBOARD_CACHE_TTL_SECONDS = 5.0
|
||||
DASHBOARD_ROUTE_SCAN_LIMIT = 24
|
||||
DASHBOARD_ROUTE_ANALYSIS_LIMIT = 0
|
||||
DASHBOARD_ANALYSIS_TIME_BUDGET_SECONDS = 0.0
|
||||
DASHBOARD_BACKGROUND_ROUTE_ANALYSIS_LIMIT = 24
|
||||
DASHBOARD_BACKGROUND_ROUTE_ANALYSIS_LIMIT = 5
|
||||
DASHBOARD_RECENT_DRIVE_LIMIT = 5
|
||||
DASHBOARD_ROUTE_SEGMENT_SAMPLE_LIMIT = 2
|
||||
DASHBOARD_PERSISTED_ROUTE_LIMIT = 5000
|
||||
DASHBOARD_PERSIST_MIN_ROUTE_AGE_SECONDS = 120
|
||||
DASHBOARD_PERSISTENT_STATS_PARAM = "GalaxyDashboardStats"
|
||||
DASHBOARD_PARAMS_DIR = Path("/data/params/d")
|
||||
DASHBOARD_ANALYZER_LOG_PATH = "/tmp/galaxy_dashboard_analyzer.log"
|
||||
DASHBOARD_TOP_MODEL_LIMIT = 3
|
||||
DASHBOARD_EVENT_DISTRACTED = "promptDriverDistracted"
|
||||
@@ -633,14 +634,44 @@ def _params_get_bool(params_obj, key):
|
||||
return value.strip().lower() in ("1", "true", "yes", "on")
|
||||
|
||||
|
||||
def _dashboard_param_file_path(key):
|
||||
if key != DASHBOARD_PERSISTENT_STATS_PARAM:
|
||||
return None
|
||||
return DASHBOARD_PARAMS_DIR / key
|
||||
|
||||
|
||||
def _read_dashboard_param_file(key):
|
||||
path = _dashboard_param_file_path(key)
|
||||
if path is None or not path.is_file():
|
||||
return None
|
||||
try:
|
||||
return path.read_text(encoding="utf-8")
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _write_dashboard_param_file(key, value):
|
||||
path = _dashboard_param_file_path(key)
|
||||
if path is None:
|
||||
return False
|
||||
try:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
tmp_path = path.with_name(f".{path.name}.{os.getpid()}.tmp")
|
||||
tmp_path.write_text(str(value), encoding="utf-8")
|
||||
os.replace(tmp_path, path)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _params_put_text(params_obj, key, value):
|
||||
if params_obj is None:
|
||||
return False
|
||||
return _write_dashboard_param_file(key, value)
|
||||
try:
|
||||
params_obj.put(key, value)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
return _write_dashboard_param_file(key, value)
|
||||
|
||||
|
||||
def _split_csv(value):
|
||||
@@ -691,6 +722,19 @@ def _segment_mtime(segment_path):
|
||||
return 0.0
|
||||
|
||||
|
||||
def _segment_has_dashboard_log(segment_path):
|
||||
path = Path(segment_path)
|
||||
if not path.is_dir():
|
||||
return False
|
||||
return any((path / candidate).is_file() for candidate in ROUTE_TIME_LOG_CANDIDATES)
|
||||
|
||||
|
||||
def _select_dashboard_segment_candidate(candidates):
|
||||
if not candidates:
|
||||
return None
|
||||
return next((candidate for candidate in candidates if _segment_has_dashboard_log(candidate)), candidates[0])
|
||||
|
||||
|
||||
def _list_dashboard_routes(footage_paths, limit=DASHBOARD_ROUTE_SCAN_LIMIT):
|
||||
routes = {}
|
||||
|
||||
@@ -726,7 +770,9 @@ def _list_dashboard_routes(footage_paths, limit=DASHBOARD_ROUTE_SCAN_LIMIT):
|
||||
for route in routes.values():
|
||||
segments = []
|
||||
for segment_num, candidates in sorted(route["segments_by_num"].items()):
|
||||
segments.append({"num": segment_num, "path": candidates[0]})
|
||||
selected = _select_dashboard_segment_candidate(candidates)
|
||||
if selected is not None:
|
||||
segments.append({"num": segment_num, "path": selected})
|
||||
|
||||
if not segments:
|
||||
continue
|
||||
@@ -993,6 +1039,7 @@ def _analyze_route_messages(messages, route_info, model_names, is_metric, deadli
|
||||
"unresponsiveMoments": unresponsive_moments,
|
||||
"routeModifiedAt": _safe_float(route_info.get("modifiedAt", 0.0), 0.0),
|
||||
"attentionKnown": True,
|
||||
"analysisComplete": analysis_segment_count >= segment_count,
|
||||
}
|
||||
|
||||
|
||||
@@ -1030,6 +1077,8 @@ def _empty_drive(is_metric):
|
||||
"segmentCount": 0,
|
||||
"distractedMoments": 0,
|
||||
"unresponsiveMoments": 0,
|
||||
"attentionKnown": True,
|
||||
"analysisComplete": False,
|
||||
"distanceUnit": "kilometers" if is_metric else "miles",
|
||||
"speedUnit": "kph" if is_metric else "mph",
|
||||
}
|
||||
@@ -1076,6 +1125,7 @@ def _route_shell_drive(route_info, params_obj, model_names, is_metric):
|
||||
"unresponsiveMoments": 0,
|
||||
"routeModifiedAt": _safe_float(route_info.get("modifiedAt", 0.0), 0.0),
|
||||
"attentionKnown": False,
|
||||
"analysisComplete": False,
|
||||
}
|
||||
|
||||
|
||||
@@ -1100,6 +1150,7 @@ def _drive_from_persistent_route(route_name, entry, is_metric):
|
||||
"unresponsiveMoments": max(0, _safe_int(entry.get("unresponsiveMoments", 0), 0)),
|
||||
"routeModifiedAt": _safe_float(entry.get("modifiedAt", 0.0), 0.0),
|
||||
"attentionKnown": bool(entry.get("attentionKnown", True)),
|
||||
"analysisComplete": bool(entry.get("analysisComplete", False)),
|
||||
}
|
||||
|
||||
|
||||
@@ -1147,7 +1198,7 @@ def _analysis_candidates(route_infos, persistent_stats):
|
||||
return True
|
||||
if _safe_float(entry.get("modifiedAt", 0.0), 0.0) < _safe_float(route_info.get("modifiedAt", 0.0), 0.0):
|
||||
return True
|
||||
return not bool(entry.get("attentionKnown", True))
|
||||
return not bool(entry.get("attentionKnown", True)) or not bool(entry.get("analysisComplete", False))
|
||||
|
||||
missing = [route_info for route_info in route_infos if needs_analysis(route_info)]
|
||||
return missing
|
||||
@@ -1180,9 +1231,10 @@ def warm_dashboard_stats(footage_paths=None):
|
||||
persistent_stats = _load_dashboard_persistent_stats(params_obj)
|
||||
candidates = _analysis_candidates(route_infos, persistent_stats)[:DASHBOARD_BACKGROUND_ROUTE_ANALYSIS_LIMIT]
|
||||
for route_info in candidates:
|
||||
sampled_route_info = _sample_route_info(route_info)
|
||||
messages = _iter_route_log_messages(sampled_route_info)
|
||||
drive = _analyze_route_messages(messages, sampled_route_info, model_names, is_metric)
|
||||
full_route_info = dict(route_info)
|
||||
full_route_info["analysisSegmentCount"] = max(0, _safe_int(route_info.get("segmentCount", 0), 0))
|
||||
messages = _iter_route_log_messages(full_route_info)
|
||||
drive = _analyze_route_messages(messages, full_route_info, model_names, is_metric)
|
||||
_update_dashboard_persistent_stats(params_obj, [drive], time.time())
|
||||
|
||||
|
||||
@@ -1541,12 +1593,16 @@ def _normalize_persistent_routes(raw_routes):
|
||||
"segmentCount": max(0, _safe_int(entry.get("segmentCount", 0), 0)),
|
||||
"modifiedAt": _safe_float(entry.get("modifiedAt", 0.0), 0.0),
|
||||
"attentionKnown": bool(entry.get("attentionKnown", True)),
|
||||
"analysisComplete": bool(entry.get("analysisComplete", False)),
|
||||
}
|
||||
return routes
|
||||
|
||||
|
||||
def _load_dashboard_persistent_stats(params_obj):
|
||||
data = _decode_json_param(_params_get_value(params_obj, DASHBOARD_PERSISTENT_STATS_PARAM, None), {})
|
||||
raw_data = _read_dashboard_param_file(DASHBOARD_PERSISTENT_STATS_PARAM)
|
||||
if raw_data is None:
|
||||
raw_data = _params_get_value(params_obj, DASHBOARD_PERSISTENT_STATS_PARAM, None)
|
||||
data = _decode_json_param(raw_data, {})
|
||||
if not isinstance(data, dict):
|
||||
data = {}
|
||||
data["version"] = 1
|
||||
@@ -1771,6 +1827,7 @@ def _update_dashboard_persistent_stats(params_obj, drives, wall_now):
|
||||
"segmentCount": max(0, _safe_int(drive.get("segmentCount", 0), 0)),
|
||||
"modifiedAt": _safe_float(drive.get("routeModifiedAt", 0.0), 0.0),
|
||||
"attentionKnown": attention_known,
|
||||
"analysisComplete": bool(drive.get("analysisComplete", False)),
|
||||
}
|
||||
existing_entry = routes.get(route_name)
|
||||
if isinstance(existing_entry, dict):
|
||||
@@ -1782,12 +1839,15 @@ def _update_dashboard_persistent_stats(params_obj, drives, wall_now):
|
||||
next_entry["clean"] = bool(existing_entry.get("clean", False))
|
||||
next_entry["undistracted"] = bool(existing_entry.get("undistracted", existing_entry.get("clean", False)))
|
||||
next_entry["attentionKnown"] = True
|
||||
next_entry["analysisComplete"] = bool(existing_entry.get("analysisComplete", False))
|
||||
if not attention_known and existing_current and existing_distance >= next_distance:
|
||||
next_entry["distanceMeters"] = existing_distance
|
||||
next_entry["duration"] = max(_safe_int(existing_entry.get("duration", 0), 0), next_entry["duration"])
|
||||
existing_duration = _safe_int(existing_entry.get("duration", 0), 0)
|
||||
next_entry["duration"] = existing_duration if existing_attention_known else max(existing_duration, next_entry["duration"])
|
||||
next_entry["engagedSeconds"] = max(0.0, _safe_float(existing_entry.get("engagedSeconds", 0.0), 0.0))
|
||||
next_entry["distractedMoments"] = max(0, _safe_int(existing_entry.get("distractedMoments", 0), 0))
|
||||
next_entry["unresponsiveMoments"] = max(0, _safe_int(existing_entry.get("unresponsiveMoments", 0), 0))
|
||||
next_entry["analysisComplete"] = bool(existing_entry.get("analysisComplete", False))
|
||||
if (not model_name or model_name == "Unknown model") and _clean_model_label(existing_entry.get("model", "")):
|
||||
next_entry["model"] = _clean_model_label(existing_entry.get("model", ""))
|
||||
next_entry["modelKey"] = canonical_model_key(existing_entry.get("modelKey", "")) or _model_usage_key(next_entry["model"])
|
||||
|
||||
Reference in New Issue
Block a user