Skip to main content

turnframe_core/
effort.rs

1//! How much judgment a turn buys: more model calls behind each step, never more authority.
2
3use serde::{Deserialize, Serialize};
4
5/// How hard a turn works to be read correctly.
6#[derive(
7    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize,
8)]
9#[serde(rename_all = "snake_case")]
10#[non_exhaustive]
11pub enum Effort {
12    /// Fewer calls: no reply review, no step prose, a shorter transcript.
13    Low,
14    /// Today's behaviour, and the default.
15    #[default]
16    Medium,
17    /// More calls: votes, some reasoning, and a check of the whole turn.
18    High,
19}
20
21impl Effort {
22    /// Every level, lowest first.
23    pub const ALL: [Self; 3] = [Self::Low, Self::Medium, Self::High];
24
25    /// Stable label, for records, metrics and configuration.
26    #[must_use]
27    pub const fn as_str(self) -> &'static str {
28        match self {
29            Self::Low => "low",
30            Self::Medium => "medium",
31            Self::High => "high",
32        }
33    }
34}
35
36impl std::fmt::Display for Effort {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        f.write_str(self.as_str())
39    }
40}
41
42/// A label that names no level.
43#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
44#[error("`{0}` is not an effort level: low, medium or high")]
45pub struct UnknownEffort(pub String);
46
47impl std::str::FromStr for Effort {
48    type Err = UnknownEffort;
49
50    fn from_str(label: &str) -> Result<Self, Self::Err> {
51        Self::ALL
52            .into_iter()
53            .find(|effort| effort.as_str() == label)
54            .ok_or_else(|| UnknownEffort(label.to_owned()))
55    }
56}