Skip to main content

zero_operator_state/
snapshot.rs

1//! A renderer-friendly snapshot of operator state.
2//!
3//! Widgets do not read the [`crate::StateVector`] directly; they read
4//! a `Snapshot`. The snapshot includes the computed label, the
5//! derived friction level, and a monotonically increasing `version`
6//! so widgets can skip rendering when nothing has changed.
7
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10
11use crate::friction::{FrictionLevel, RiskContext};
12use crate::label::Label;
13use crate::vector::StateVector;
14
15/// Cheap, clone-safe summary consumed by the TUI.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Snapshot {
18    pub label: Label,
19    pub friction: FrictionLevel,
20    pub vector: StateVector,
21    /// When this snapshot was produced.
22    pub as_of: DateTime<Utc>,
23    /// Monotonic version number; widgets compare to their last seen
24    /// and skip render when equal.
25    pub version: u64,
26}
27
28impl Snapshot {
29    /// Construct a snapshot whose `friction` is derived from
30    /// `label` alone. Caps at L2 — call
31    /// [`Snapshot::new_with_risk`] from a caller with engine
32    /// access to reach L3/L4.
33    #[must_use]
34    pub fn new(label: Label, vector: StateVector, as_of: DateTime<Utc>, version: u64) -> Self {
35        let friction = FrictionLevel::from_label(label);
36        Self {
37            label,
38            friction,
39            vector,
40            as_of,
41            version,
42        }
43    }
44
45    /// Construct a snapshot whose `friction` is derived from both
46    /// `label` **and** the engine-reported `risk`.
47    ///
48    /// This is the M2 entrypoint. It uses
49    /// [`FrictionLevel::from_label_and_risk`] so the snapshot's
50    /// `.friction` field can reach L3 (TILT + guardrail proximity)
51    /// or L4 (TILT + halt). Callers without engine context (tests,
52    /// pure replay, the classifier's default `classify` path) stay
53    /// on [`Self::new`] and retain the L2 cap.
54    #[must_use]
55    pub fn new_with_risk(
56        label: Label,
57        vector: StateVector,
58        as_of: DateTime<Utc>,
59        version: u64,
60        risk: RiskContext,
61    ) -> Self {
62        let friction = FrictionLevel::from_label_and_risk(label, risk);
63        Self {
64            label,
65            friction,
66            vector,
67            as_of,
68            version,
69        }
70    }
71}