Files
StarPilot/openpilot/starpilot/speed_limits/history.py
T
firestar5683 58427d61d9 SLC
Select qualified speed-limit sources, preserve acceptance and override decisions, and publish distinct control and display coordinates. Carry accepted and pending source identity with each displayed decision.

Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
2026-10-14 20:02:39 -05:00

59 lines
3.3 KiB
Python

"""Versioned accepted-limit persistence with explicit identity applicability."""
import json
import math
from openpilot.cereal import custom
from openpilot.starpilot.schema_cache import schema_fingerprint
from openpilot.starpilot.speed_limits import acceptance as acc
FORMAT = "starpilot-slc-accepted"
VERSION = 1
def encode(accepted: acc.AcceptedLimit, saved_wall_ns: int) -> str:
if not isinstance(accepted, acc.AcceptedLimit) or type(saved_wall_ns) is not int or saved_wall_ns <= 0:
raise ValueError("invalid accepted history")
candidate = accepted.candidate
identity = candidate.observation_identity
if not acc.observation_is_valid(acc.Observation(acc.ObservationKind.VALID, candidate), session_id=accepted.session_id):
raise ValueError("invalid accepted candidate")
return json.dumps({"format": FORMAT, "version": VERSION, "source_schema": schema_fingerprint(custom.SlcDashboardObservation.schema),
"saved_wall_ns": saved_wall_ns, "source": candidate.source, "speed_mps": candidate.speed_mps,
"identity_kind": identity.kind.value, "identity_value": identity.value,
"origin_session": accepted.session_id}, sort_keys=True, separators=(",", ":"))
def decode(raw: str | bytes | dict | None, *, session_id: str, current: acc.Observation,
now_wall_ns: int, max_age_ns: int) -> acc.AcceptedLimit | None:
"""Only durable geographic identity can be carried into a new drive.
The caller sets an applicability age from its source contract. Dashboard TSR
producer episodes are drive-local and cannot be restored across a new session.
"""
if raw is None or type(session_id) is not str or not session_id or type(now_wall_ns) is not int or type(max_age_ns) is not int:
return None
if max_age_ns < 0 or now_wall_ns <= 0:
return None
try:
value = raw if isinstance(raw, dict) else json.loads(raw)
if (type(value) is not dict or set(value) != {"format", "version", "source_schema", "saved_wall_ns", "source",
"speed_mps", "identity_kind", "identity_value", "origin_session"} or
value["format"] != FORMAT or type(value["version"]) is not int or value["version"] != VERSION or
value["source_schema"] != schema_fingerprint(custom.SlcDashboardObservation.schema) or
type(value["saved_wall_ns"]) is not int or value["saved_wall_ns"] > now_wall_ns or
now_wall_ns - value["saved_wall_ns"] > max_age_ns or
value["identity_kind"] != acc.IdentityKind.GEOGRAPHIC.value or
not isinstance(value["identity_value"], str) or not value["identity_value"] or
not isinstance(value["origin_session"], str) or not value["origin_session"] or
type(value["speed_mps"]) not in (int, float) or not math.isfinite(value["speed_mps"])):
return None
candidate = acc.Candidate(value["source"], acc.ObservationIdentity(acc.IdentityKind.GEOGRAPHIC, value["identity_value"]),
float(value["speed_mps"]))
if (not acc.observation_is_valid(acc.Observation(acc.ObservationKind.VALID, candidate), session_id=session_id) or
current != acc.Observation(acc.ObservationKind.VALID, candidate)):
return None
return acc.AcceptedLimit(candidate, session_id, 0)
except (TypeError, ValueError, OverflowError):
return None