/// Contract-first state: semantics live in the data
/// Fable's four-projection inversion — state carries meaning, derive narrative automatically
///
/// Three proved shapes across real apps:
/// - Stat: numeric measurement (battery, frame count, event count)
/// - Discrete: named state with per-variant narrative (connection, mode)
/// - Counted: aggregate with threshold (claims, devices, records)
///
/// Pattern: name + value + unit + significance threshold → derive prose that never rots
// ============================================================================
// STAT: NUMERIC STATE WITH UNIT AND SIGNIFICANCE
// ============================================================================
pub struct Stat {
name : String
value : Int
unit : String
warn_below : Int
critical_below : Int
}
///| `stat`: the warning band defaults to 2× the critical threshold — right for
/// %-style or count metrics (critical_below=20 → warning under 40).
pub fn stat(name : String, value : Int, unit : String, critical_below : Int) -> Stat {
Stat::{ name, value, unit, warn_below: critical_below * 2, critical_below }
}
///| `stat_banded`: explicit warn + critical lines — for a measurement whose healthy
/// range sits just above the line (e.g. LiPo battery in mV: warn 3900, critical 3850),
/// where a 2× warning band would be meaningless.
pub fn stat_banded(
name : String,
value : Int,
unit : String,
warn_below : Int,
critical_below : Int,
) -> Stat {
Stat::{ name, value, unit, warn_below, critical_below }
}
// TODO(hysteresis): bands are single-threshold, so a value flapping at an edge
// (battery 3849↔3851) makes stat_event emit low/ok/low/ok — duplicate noise in the
// event stream. Fix: a deadband with separate enter/exit thresholds (drop to "low"
// below critical, but don't return to "ok" until well above warn), so line-noise
// can't flap the stream. Same trick as the gesture-control EMG hysteresis.
pub fn stat_significance(s : Stat) -> String {
if s.value < s.critical_below {
"critical"
} else if s.value < s.warn_below {
"warning"
} else {
"ok"
}
}
pub fn stat_narrative(s: Stat) -> String {
let sig = stat_significance(s)
let state_word = match sig {
"critical" => "CRITICAL — "
"warning" => "warning, "
_ => ""
}
"\{s.name}: \{state_word}\{s.value}\{s.unit}"
}
// ============================================================================
// DISCRETE: NAMED STATE WITH PER-VARIANT NARRATIVES
// ============================================================================
pub struct DiscreteState {
name: String
current: String
narratives: Map[String, String]
}
pub fn discrete_state(
name: String,
current: String,
narratives: Map[String, String],
) -> DiscreteState {
DiscreteState::{ name, current, narratives }
}
pub fn discrete_state_narrative(s: DiscreteState) -> String {
match s.narratives.get(s.current) {
Some(prose) => prose
None => "\{s.name}: unknown state \{s.current}"
}
}
// ============================================================================
// COUNTED: AGGREGATE WITH THRESHOLD
// ============================================================================
pub struct CountedState {
name: String
count: Int
total: Int
critical_threshold: Int
}
pub fn counted_state(
name: String,
count: Int,
total: Int,
critical_threshold: Int,
) -> CountedState {
CountedState::{ name, count, total, critical_threshold }
}
pub fn counted_state_narrative(c: CountedState) -> String {
let pct = if c.total == 0 { 0 } else { c.count * 100 / c.total }
let critical = if c.count >= c.critical_threshold { " — CRITICAL" } else { "" }
"\{c.name}: \{c.count} (\{pct}% of \{c.total})\{critical}"
}
// ============================================================================
// ENGINE/DEVICE PATTERNS (FROM GESTURE LAB)
// ============================================================================
pub struct EngineState {
ready: Bool
devices: Int
frames: Int
}
pub fn engine_state(ready: Bool, devices: Int, frames: Int) -> EngineState {
EngineState::{ ready, devices, frames }
}
pub fn engine_state_narrative(e: EngineState) -> String {
if !e.ready {
"engine: waiting for connection"
} else {
"engine: ready · \{e.devices} device(s) · frame \{e.frames}"
}
}
pub struct DeviceState {
name: String
connected: Bool
battery_pct: Int
critical_threshold: Int
}
pub fn device_state(
name: String,
connected: Bool,
battery_pct: Int,
critical_threshold: Int,
) -> DeviceState {
DeviceState::{ name, connected, battery_pct, critical_threshold }
}
pub fn device_state_narrative(d: DeviceState) -> String {
if !d.connected {
"\{d.name}: offline"
} else {
let status = if d.battery_pct < d.critical_threshold {
"low"
} else {
"ok"
}
"\{d.name}: \{d.battery_pct}% (\{status})"
}
}