Skip to main content

prosaic_core/
hedge.rs

1//! Hedging / confidence language.
2//!
3//! Turn a confidence score into a hedge word that reflects certainty.
4//! Input is an integer 0..=100 representing a percentage. For callers
5//! working with floats in the 0.0–1.0 range, multiply by 100 and round
6//! before passing in.
7
8/// Which form the hedge should take.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub enum HedgeMode {
12    /// Adverb: "certainly", "likely", "probably", "possibly", "perhaps".
13    /// Fits templates like "X {conf|hedge} broke the build".
14    #[default]
15    Adverb,
16    /// Modal verb: "must", "should", "may", "might", "could". Fits
17    /// templates like "X {conf|hedge:modal} break the build".
18    Modal,
19    /// Prefix phrase: "It is certain that", "It is likely that",
20    /// "Probably", "Possibly", "Perhaps". Fits templates where the
21    /// hedge leads a clause.
22    Prefix,
23}
24
25/// Map a 0..=100 score into a hedge word for the given mode.
26/// Values outside the range are clamped.
27pub fn hedge(score: i64, mode: HedgeMode) -> &'static str {
28    let bucket = bucket_of(score);
29    match (mode, bucket) {
30        (HedgeMode::Adverb, Bucket::Certain) => "certainly",
31        (HedgeMode::Adverb, Bucket::Likely) => "likely",
32        (HedgeMode::Adverb, Bucket::Probable) => "probably",
33        (HedgeMode::Adverb, Bucket::Possible) => "possibly",
34        (HedgeMode::Adverb, Bucket::Unlikely) => "perhaps",
35
36        (HedgeMode::Modal, Bucket::Certain) => "must",
37        (HedgeMode::Modal, Bucket::Likely) => "should",
38        (HedgeMode::Modal, Bucket::Probable) => "may",
39        (HedgeMode::Modal, Bucket::Possible) => "might",
40        (HedgeMode::Modal, Bucket::Unlikely) => "could",
41
42        (HedgeMode::Prefix, Bucket::Certain) => "it is certain that",
43        (HedgeMode::Prefix, Bucket::Likely) => "it is likely that",
44        (HedgeMode::Prefix, Bucket::Probable) => "probably",
45        (HedgeMode::Prefix, Bucket::Possible) => "possibly",
46        (HedgeMode::Prefix, Bucket::Unlikely) => "perhaps",
47    }
48}
49
50/// Parse a mode spec ("adverb", "modal", "prefix") from a pipe argument.
51pub fn parse_mode(spec: &str) -> Option<HedgeMode> {
52    match spec {
53        "adverb" => Some(HedgeMode::Adverb),
54        "modal" => Some(HedgeMode::Modal),
55        "prefix" => Some(HedgeMode::Prefix),
56        _ => None,
57    }
58}
59
60#[derive(Debug, Clone, Copy)]
61enum Bucket {
62    Certain,
63    Likely,
64    Probable,
65    Possible,
66    Unlikely,
67}
68
69fn bucket_of(score: i64) -> Bucket {
70    match score.clamp(0, 100) {
71        90..=100 => Bucket::Certain,
72        70..=89 => Bucket::Likely,
73        50..=69 => Bucket::Probable,
74        30..=49 => Bucket::Possible,
75        _ => Bucket::Unlikely,
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn adverb_buckets() {
85        assert_eq!(hedge(95, HedgeMode::Adverb), "certainly");
86        assert_eq!(hedge(80, HedgeMode::Adverb), "likely");
87        assert_eq!(hedge(60, HedgeMode::Adverb), "probably");
88        assert_eq!(hedge(40, HedgeMode::Adverb), "possibly");
89        assert_eq!(hedge(10, HedgeMode::Adverb), "perhaps");
90    }
91
92    #[test]
93    fn modal_buckets() {
94        assert_eq!(hedge(95, HedgeMode::Modal), "must");
95        assert_eq!(hedge(80, HedgeMode::Modal), "should");
96        assert_eq!(hedge(60, HedgeMode::Modal), "may");
97        assert_eq!(hedge(40, HedgeMode::Modal), "might");
98        assert_eq!(hedge(10, HedgeMode::Modal), "could");
99    }
100
101    #[test]
102    fn prefix_buckets() {
103        assert_eq!(hedge(95, HedgeMode::Prefix), "it is certain that");
104        assert_eq!(hedge(80, HedgeMode::Prefix), "it is likely that");
105        assert_eq!(hedge(60, HedgeMode::Prefix), "probably");
106        assert_eq!(hedge(40, HedgeMode::Prefix), "possibly");
107        assert_eq!(hedge(10, HedgeMode::Prefix), "perhaps");
108    }
109
110    #[test]
111    fn out_of_range_scores_clamp() {
112        assert_eq!(hedge(-50, HedgeMode::Adverb), "perhaps");
113        assert_eq!(hedge(200, HedgeMode::Adverb), "certainly");
114    }
115
116    #[test]
117    fn bucket_boundaries() {
118        assert_eq!(hedge(89, HedgeMode::Adverb), "likely");
119        assert_eq!(hedge(90, HedgeMode::Adverb), "certainly");
120        assert_eq!(hedge(69, HedgeMode::Adverb), "probably");
121        assert_eq!(hedge(70, HedgeMode::Adverb), "likely");
122    }
123}