use scema_policy::decide::{Abstention, Decision, Ranked};
use scema_policy::render;
use scema_sim::Projection;
use scema_world::{Coverage, Polarity, Provenance, Signal, Term, WorldState};
use crate::theme::Role;
pub const UNMEASURED: &str = "—";
pub fn cell(t: &Term) -> (String, Role) {
let text = render::cell(t);
let role = if t.measured { Role::Measured } else { Role::Unmeasured };
(text, role)
}
pub fn provenance_role(p: &Provenance) -> Role {
match p {
Provenance::Live { .. } => Role::Live,
Provenance::Stale { .. } => Role::Stale,
Provenance::Absent => Role::Absent,
Provenance::Simulated => Role::Simulated,
}
}
pub fn provenance_label(p: &Provenance) -> String {
match p {
Provenance::Live { age_secs } => format!("LIVE {}", short_age(*age_secs)),
Provenance::Stale { age_secs, budget_secs } => {
format!("STALE {} > {}", short_age(*age_secs), short_age(*budget_secs))
}
Provenance::Absent => "ABSENT".into(),
Provenance::Simulated => "SIMULATED".into(),
}
}
pub fn short_age(secs: u64) -> String {
match secs {
s if s < 60 => format!("{s}s"),
s if s < 3_600 => format!("{}m", s / 60),
s if s < 86_400 => format!("{}h", s / 3_600),
s => format!("{}d", s / 86_400),
}
}
pub fn signal_role(s: &Signal) -> Role {
if !s.measured {
return Role::Estimated;
}
match s.polarity {
Polarity::Risk => Role::Risk,
Polarity::Opportunity => Role::Opportunity,
}
}
pub fn signal_tag(s: &Signal) -> &'static str {
match (s.measured, s.polarity) {
(true, Polarity::Risk) => "RISK",
(true, Polarity::Opportunity) => "OPP ",
(false, _) => "EST?",
}
}
pub fn rank_role(decision: &Decision, r: &Ranked) -> Role {
if decision.chosen.as_ref() == Some(&r.hypothesis) {
Role::Chosen
} else {
Role::Runner
}
}
pub fn rank_marker(decision: &Decision, r: &Ranked) -> &'static str {
if decision.chosen.as_ref() == Some(&r.hypothesis) {
"▸"
} else {
" "
}
}
pub fn coverage_meter(c: Coverage) -> String {
if c.total == 0 {
return "∅".into();
}
let cap = 12usize;
if c.total <= cap {
let mut s = String::with_capacity(c.total);
for i in 0..c.total {
s.push(if i < c.measured { '▰' } else { '▱' });
}
s
} else {
c.label()
}
}
pub fn coverage_is_thin(c: Coverage, floor: f64) -> bool {
c.fraction() < floor
}
pub fn abstention_advice(a: &Abstention) -> &'static str {
match a {
Abstention::NoCandidates => {
"Nothing was proposed. Check that the world has counted signals — `scema observe` lists them."
}
Abstention::AllForbidden { .. } => {
"Every branch violates a constraint on the goal. The goal is unsatisfiable as stated; relax a --must-not or restate it."
}
Abstention::NoPositiveUtility { .. } => {
"Acting scores worse than not acting. Accept that, or lower the bar deliberately in policy — not by accident."
}
Abstention::TooLittleMeasured { .. } => {
"This is a statement about how little was observed, not about the branches. Go and observe more."
}
Abstention::Contested { .. } => {
"A specialist that IS qualified here disagrees with the top branch. Read its note before overriding it."
}
}
}
pub fn projection_for<'a>(projections: &'a [Projection], hypothesis: &str) -> Option<&'a Projection> {
projections.iter().find(|p| p.hypothesis == hypothesis)
}
pub fn extent_line(w: &WorldState) -> (String, Role) {
match w.extent.fraction() {
Some(f) => (
format!("{} of {} observed ({:.0}%) — {}", w.extent.observed, w.extent.total.unwrap_or(0), f * 100.0, w.extent.note),
Role::Measured,
),
None => (
format!("{} observed, EXTENT UNBOUNDED — {}", w.extent.observed, w.extent.note),
Role::Estimated,
),
}
}
pub fn truncate(s: &str, n: usize) -> String {
if s.chars().count() <= n {
return s.to_string();
}
if n == 0 {
return String::new();
}
let mut out: String = s.chars().take(n.saturating_sub(1)).collect();
out.push('…');
out
}
#[cfg(test)]
mod tests {
use super::*;
use scema_world::{Coverage, Term};
#[test]
fn an_unmeasured_term_is_an_em_dash_and_a_recessive_role() {
let (text, role) = cell(&Term::absent("R", "gain", 0.0, "nothing cited"));
assert_eq!(text.trim(), UNMEASURED);
assert!(!text.contains('0'));
assert_eq!(role, Role::Unmeasured);
}
#[test]
fn a_measured_zero_is_a_number_and_a_measured_role() {
let (text, role) = cell(&Term::measured("R", "gain", 0.0, "counted zero"));
assert!(text.contains("0.00"));
assert_eq!(role, Role::Measured);
}
#[test]
fn this_module_never_formats_a_term_itself() {
for t in [
Term::measured("A", "a", 0.125, "x"),
Term::absent("B", "b", 0.0, "y"),
Term::measured("C", "c", -1.5, "z"),
] {
assert_eq!(cell(&t).0, render::cell(&t));
}
}
#[test]
fn stale_never_borrows_the_live_role() {
assert_eq!(provenance_role(&Provenance::Live { age_secs: 1 }), Role::Live);
assert_eq!(
provenance_role(&Provenance::Stale { age_secs: 99, budget_secs: 10 }),
Role::Stale
);
assert_ne!(
provenance_role(&Provenance::Stale { age_secs: 99, budget_secs: 10 }),
provenance_role(&Provenance::Live { age_secs: 1 })
);
assert_eq!(provenance_role(&Provenance::Absent), Role::Absent);
assert_eq!(provenance_role(&Provenance::Simulated), Role::Simulated);
}
#[test]
fn an_estimated_signal_does_not_render_as_a_counted_one() {
let counted = Signal {
id: "a".into(),
polarity: Polarity::Risk,
label: "x".into(),
detail: String::new(),
magnitude: 0.9,
measured: true,
targets: vec![],
evidence: vec![],
};
let guessed = Signal { measured: false, ..counted.clone() };
assert_eq!(signal_role(&counted), Role::Risk);
assert_eq!(signal_role(&guessed), Role::Estimated);
assert_ne!(signal_tag(&counted), signal_tag(&guessed));
}
#[test]
fn the_coverage_meter_shows_the_denominator_rather_than_a_percentage() {
let a = coverage_meter(Coverage { measured: 2, total: 5 });
let b = coverage_meter(Coverage { measured: 4, total: 10 });
assert_ne!(a, b);
assert_eq!(a.chars().count(), 5);
assert_eq!(b.chars().count(), 10);
assert_eq!(a.chars().filter(|c| *c == '▰').count(), 2);
}
#[test]
fn an_empty_coverage_is_not_an_empty_bar() {
assert_eq!(coverage_meter(Coverage { measured: 0, total: 0 }), "∅");
}
#[test]
fn a_wide_coverage_falls_back_to_the_label_not_to_a_proportional_bar() {
let m = coverage_meter(Coverage { measured: 30, total: 90 });
assert_eq!(m, "30/90");
}
#[test]
fn every_abstention_reason_carries_a_distinct_instruction() {
let all = [
Abstention::NoCandidates,
Abstention::AllForbidden { count: 2 },
Abstention::NoPositiveUtility { best: -0.1 },
Abstention::TooLittleMeasured {
coverage: Coverage { measured: 1, total: 5 },
floor: 0.4,
},
Abstention::Contested { by: "dqstar".into(), utility: -0.2, note: "n".into() },
];
let mut advice: Vec<&str> = all.iter().map(abstention_advice).collect();
advice.sort_unstable();
let before = advice.len();
advice.dedup();
assert_eq!(before, advice.len(), "two abstentions give the same instruction");
}
#[test]
fn truncation_budgets_the_ellipsis() {
assert_eq!(truncate("abcdef", 4), "abc…");
assert_eq!(truncate("abcdef", 4).chars().count(), 4);
assert_eq!(truncate("abc", 8), "abc");
}
#[test]
fn an_unbounded_extent_is_shouted_and_not_styled_as_a_measurement() {
use scema_world::{Domain, Entity, EntityKind, Extent};
let mut w = WorldState {
schema: Some(scema_world::WORLD_SCHEMA.into()),
observer: "t".into(),
entity: Entity { kind: EntityKind::Repository, locator: "/r".into(), label: "r".into() },
domain: Domain::Software,
observed_at: 0,
objects: vec![],
facts: vec![],
signals: vec![],
extent: Extent::partial(400, "walk capped"),
blind_spots: vec![],
};
let (text, role) = extent_line(&w);
assert!(text.contains("EXTENT UNBOUNDED"));
assert_ne!(role, Role::Measured, "an unknown denominator is not a measurement");
w.extent = Extent::complete(400, "walked");
assert_eq!(extent_line(&w).1, Role::Measured);
}
}