capability_grower_configuration/
capstone_mode.rs

1// ---------------- [ File: capability-grower-configuration/src/capstone_mode.rs ]
2crate::ix!();
3
4/// This enum should specify how we generate "capstone" nodes per tree.
5/// - Off → No special capstone node will be generated.
6/// - Single → Exactly one capstone leaf will be generated.
7/// - Probabilistic → a fraction of leaves per level will become capstones.
8#[derive(
9    Copy,
10    SaveLoad,
11    Debug, 
12    Clone, 
13    PartialEq, 
14    Eq, 
15    Serialize, 
16    Default,
17    Deserialize, 
18    AiJsonTemplate,
19    AiJsonTemplateWithJustification,
20)]
21pub enum CapstoneMode {
22    /// This variant should be used if we do not want any special capstones.
23    #[default]
24    Off,
25    /// This variant designates exactly one "capstone" leaf in the entire tree.
26    Single,
27    /// This variant designates a fraction of leaves as highlight/capstones.
28    Probabilistic,
29}
30
31impl FuzzyFromJsonValue for JustifiedCapstoneMode {
32    fn fuzzy_from_json_value(value: &serde_json::Value) -> Result<Self, FuzzyFromJsonValueError> {
33        trace!("(JustifiedCapstoneMode) Entering fuzzy_from_json_value");
34
35        // 1) If null => default Off
36        if value.is_null() {
37            debug!("(JustifiedCapstoneMode) Null => defaulting to Off");
38            return Ok(Self::Off {
39                variant_confidence: 0.0,
40                variant_justification: String::new(),
41            });
42        }
43
44        // 2) If string => interpret as variant name with default confidence/justification
45        if let Some(variant_str) = value.as_str() {
46            trace!("(JustifiedCapstoneMode) Found string => '{}'", variant_str);
47            return match variant_str {
48                "Off" => Ok(Self::Off {
49                    variant_confidence: 0.0,
50                    variant_justification: String::new(),
51                }),
52                "Single" => Ok(Self::Single {
53                    variant_confidence: 0.0,
54                    variant_justification: String::new(),
55                }),
56                "Probabilistic" => Ok(Self::Probabilistic {
57                    variant_confidence: 0.0,
58                    variant_justification: String::new(),
59                }),
60                other => {
61                    error!("(JustifiedCapstoneMode) Unknown string variant='{}'", other);
62                    Err(FuzzyFromJsonValueError::Other {
63                        target_type: "JustifiedCapstoneMode",
64                        detail: format!(
65                            "Unknown string variant='{}'; expected [Off,Single,Probabilistic]",
66                            other
67                        ),
68                    })
69                }
70            };
71        }
72
73        // 3) Must be object => flatten if needed
74        let mut obj = match value.as_object() {
75            Some(m) => {
76                trace!("(JustifiedCapstoneMode) Found object => flattening if any 'fields' sub-objects.");
77                let mut cloned = m.clone();
78                flatten_all_fields(&mut cloned);
79                cloned
80            }
81            None => {
82                error!("(JustifiedCapstoneMode) Not an object => fail!");
83                return Err(FuzzyFromJsonValueError::NotAnObject {
84                    target_type: "JustifiedCapstoneMode",
85                    actual: value.clone(),
86                });
87            }
88        };
89
90        // 3A) Single-key object form => e.g. { "Single": {...} }, or just { "Single": {} }
91        //     We'll unify that into { "variant_name":"Single", ...subfields... }.
92        if obj.len() == 1 && !obj.contains_key("variant_name") {
93            let (maybe_variant, nested) = {
94                let mut iter = obj.into_iter();
95                iter.next().unwrap()
96            };
97            trace!("(JustifiedCapstoneMode) single-key object => key='{}'", maybe_variant);
98
99            let mut sub_obj = match nested.as_object() {
100                Some(sub) => {
101                    // flatten sub
102                    let mut sclone = sub.clone();
103                    flatten_all_fields(&mut sclone);
104                    sclone
105                }
106                None => serde_json::Map::new(),
107            };
108
109            // parse variant_confidence & justification if present
110            let conf = sub_obj
111                .remove("variant_confidence")
112                .and_then(|x| x.as_f64())
113                .unwrap_or(0.0);
114            let just = sub_obj
115                .remove("variant_justification")
116                .and_then(|x| x.as_str().map(|s| s.to_string()))
117                .unwrap_or_default();
118
119            return match maybe_variant.as_str() {
120                "Off" => Ok(Self::Off {
121                    variant_confidence: conf,
122                    variant_justification: just,
123                }),
124                "Single" => Ok(Self::Single {
125                    variant_confidence: conf,
126                    variant_justification: just,
127                }),
128                "Probabilistic" => Ok(Self::Probabilistic {
129                    variant_confidence: conf,
130                    variant_justification: just,
131                }),
132                other => {
133                    error!("(JustifiedCapstoneMode) Unknown single-key='{}'", other);
134                    Err(FuzzyFromJsonValueError::Other {
135                        target_type: "JustifiedCapstoneMode",
136                        detail: format!("Unknown single key='{}' for capstone mode", other),
137                    })
138                }
139            };
140        }
141
142        // 3B) Otherwise, standard object => expect "variant_name"
143        let var_name_val = match obj.remove("variant_name") {
144            Some(v) => v,
145            None => {
146                error!("(JustifiedCapstoneMode) Missing 'variant_name' => fail");
147                return Err(FuzzyFromJsonValueError::Other {
148                    target_type: "JustifiedCapstoneMode",
149                    detail: "No 'variant_name' found => cannot parse Off/Single/Probabilistic".to_string(),
150                });
151            }
152        };
153        let var_name = match var_name_val.as_str() {
154            Some(s) => s,
155            None => {
156                error!("(JustifiedCapstoneMode) 'variant_name' not a string => fail");
157                return Err(FuzzyFromJsonValueError::Other {
158                    target_type: "JustifiedCapstoneMode",
159                    detail: format!("Expected string for 'variant_name', got {:?}", var_name_val),
160                });
161            }
162        };
163
164        let conf = obj
165            .remove("variant_confidence")
166            .and_then(|v| v.as_f64())
167            .unwrap_or(0.0);
168        let just = obj
169            .remove("variant_justification")
170            .and_then(|v| v.as_str().map(|s| s.to_string()))
171            .unwrap_or_default();
172
173        match var_name {
174            "Off" => Ok(Self::Off {
175                variant_confidence: conf,
176                variant_justification: just,
177            }),
178            "Single" => Ok(Self::Single {
179                variant_confidence: conf,
180                variant_justification: just,
181            }),
182            "Probabilistic" => Ok(Self::Probabilistic {
183                variant_confidence: conf,
184                variant_justification: just,
185            }),
186            other => {
187                error!("(JustifiedCapstoneMode) Unknown variant='{}'", other);
188                Err(FuzzyFromJsonValueError::Other {
189                    target_type: "JustifiedCapstoneMode",
190                    detail: format!("Unknown variant_name='{}'", other),
191                })
192            }
193        }
194    }
195}
196
197#[cfg(test)]
198mod test_fuzzy_from_json_value_for_justified_capstone_mode {
199    use super::*;
200    use serde_json::json;
201    use tracing::{debug, error, info, trace};
202    use traced_test::traced_test;
203
204    #[traced_test]
205    fn test_null_input() {
206        trace!("test_null_input: starting");
207        let input = json!(null);
208        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
209        assert!(
210            result.is_ok(),
211            "Expected Ok for null input, got error: {:?}",
212            result.err()
213        );
214        let parsed = result.unwrap();
215        assert!(
216            matches!(parsed, JustifiedCapstoneMode::Off {..}),
217            "Expected Off variant for null input, got: {:?}",
218            parsed
219        );
220        if let JustifiedCapstoneMode::Off {
221            variant_confidence,
222            variant_justification,
223        } = parsed
224        {
225            assert_eq!(variant_confidence, 0.0, "Expected 0.0 confidence for null input");
226            assert_eq!(
227                variant_justification, "",
228                "Expected empty justification for null input"
229            );
230        }
231    }
232
233    #[traced_test]
234    fn test_string_off() {
235        trace!("test_string_off: starting");
236        let input = json!("Off");
237        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
238        assert!(
239            result.is_ok(),
240            "Expected Ok for string 'Off', got error: {:?}",
241            result.err()
242        );
243        let parsed = result.unwrap();
244        assert!(
245            matches!(parsed, JustifiedCapstoneMode::Off {..}),
246            "Expected Off variant, got: {:?}",
247            parsed
248        );
249    }
250
251    #[traced_test]
252    fn test_string_single() {
253        trace!("test_string_single: starting");
254        let input = json!("Single");
255        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
256        assert!(
257            result.is_ok(),
258            "Expected Ok for string 'Single', got error: {:?}",
259            result.err()
260        );
261        let parsed = result.unwrap();
262        assert!(
263            matches!(parsed, JustifiedCapstoneMode::Single {..}),
264            "Expected Single variant, got: {:?}",
265            parsed
266        );
267    }
268
269    #[traced_test]
270    fn test_string_probabilistic() {
271        trace!("test_string_probabilistic: starting");
272        let input = json!("Probabilistic");
273        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
274        assert!(
275            result.is_ok(),
276            "Expected Ok for string 'Probabilistic', got error: {:?}",
277            result.err()
278        );
279        let parsed = result.unwrap();
280        assert!(
281            matches!(parsed, JustifiedCapstoneMode::Probabilistic {..}),
282            "Expected Probabilistic variant, got: {:?}",
283            parsed
284        );
285    }
286
287    #[traced_test]
288    fn test_single_key_object_off() {
289        trace!("test_single_key_object_off: starting");
290        let input = json!({
291            "Off": {
292                "variant_confidence": 0.9,
293                "variant_justification": "Felt best to turn capstones off"
294            }
295        });
296        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
297        assert!(
298            result.is_ok(),
299            "Expected Ok for single-key object 'Off', got error: {:?}",
300            result.err()
301        );
302        let parsed = result.unwrap();
303        match parsed {
304            JustifiedCapstoneMode::Off {
305                variant_confidence,
306                variant_justification,
307            } => {
308                assert_eq!(
309                    variant_confidence, 0.9,
310                    "Expected 0.9 confidence from single-key object"
311                );
312                assert_eq!(
313                    variant_justification,
314                    "Felt best to turn capstones off",
315                    "Justification mismatch"
316                );
317            }
318            _ => panic!("Expected Off variant, got: {:?}", parsed),
319        }
320    }
321
322    #[traced_test]
323    fn test_single_key_object_single() {
324        trace!("test_single_key_object_single: starting");
325        let input = json!({
326            "Single": {
327                "variant_confidence": 0.8,
328                "variant_justification": "A single apex skill is perfect"
329            }
330        });
331        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
332        assert!(
333            result.is_ok(),
334            "Expected Ok for single-key object 'Single', got error: {:?}",
335            result.err()
336        );
337        let parsed = result.unwrap();
338        match parsed {
339            JustifiedCapstoneMode::Single {
340                variant_confidence,
341                variant_justification,
342            } => {
343                assert_eq!(
344                    variant_confidence, 0.8,
345                    "Expected 0.8 confidence from single-key object"
346                );
347                assert_eq!(
348                    variant_justification,
349                    "A single apex skill is perfect",
350                    "Justification mismatch"
351                );
352            }
353            _ => panic!("Expected Single variant, got: {:?}", parsed),
354        }
355    }
356
357    #[traced_test]
358    fn test_single_key_object_probabilistic() {
359        trace!("test_single_key_object_probabilistic: starting");
360        let input = json!({
361            "Probabilistic": {
362                "variant_confidence": 0.7,
363                "variant_justification": "Some portion of leaves should be apex skills"
364            }
365        });
366        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
367        assert!(
368            result.is_ok(),
369            "Expected Ok for single-key object 'Probabilistic', got error: {:?}",
370            result.err()
371        );
372        let parsed = result.unwrap();
373        match parsed {
374            JustifiedCapstoneMode::Probabilistic {
375                variant_confidence,
376                variant_justification,
377            } => {
378                assert_eq!(
379                    variant_confidence, 0.7,
380                    "Expected 0.7 confidence from single-key object"
381                );
382                assert_eq!(
383                    variant_justification,
384                    "Some portion of leaves should be apex skills",
385                    "Justification mismatch"
386                );
387            }
388            _ => panic!("Expected Probabilistic variant, got: {:?}", parsed),
389        }
390    }
391
392    #[traced_test]
393    fn test_standard_object_probabilistic() {
394        trace!("test_standard_object_probabilistic: starting");
395        let input = json!({
396            "variant_name": "Probabilistic",
397            "variant_confidence": 0.42,
398            "variant_justification": "Some fraction of leaves qualify"
399        });
400        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
401        assert!(
402            result.is_ok(),
403            "Expected Ok for standard object 'Probabilistic', got error: {:?}",
404            result.err()
405        );
406        let parsed = result.unwrap();
407        match parsed {
408            JustifiedCapstoneMode::Probabilistic {
409                variant_confidence,
410                variant_justification,
411            } => {
412                assert_eq!(variant_confidence, 0.42, "Confidence mismatch");
413                assert_eq!(
414                    variant_justification, "Some fraction of leaves qualify",
415                    "Justification mismatch"
416                );
417            }
418            _ => panic!("Expected Probabilistic variant, got: {:?}", parsed),
419        }
420    }
421
422    #[traced_test]
423    fn test_standard_object_off() {
424        trace!("test_standard_object_off: starting");
425        let input = json!({
426            "variant_name": "Off",
427            "variant_confidence": 1.0,
428            "variant_justification": "Force capstones off entirely"
429        });
430        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
431        assert!(
432            result.is_ok(),
433            "Expected Ok for standard object 'Off', got error: {:?}",
434            result.err()
435        );
436        let parsed = result.unwrap();
437        match parsed {
438            JustifiedCapstoneMode::Off {
439                variant_confidence,
440                variant_justification,
441            } => {
442                assert_eq!(variant_confidence, 1.0, "Confidence mismatch");
443                assert_eq!(
444                    variant_justification,
445                    "Force capstones off entirely",
446                    "Justification mismatch"
447                );
448            }
449            _ => panic!("Expected Off variant, got: {:?}", parsed),
450        }
451    }
452
453    #[traced_test]
454    fn test_unknown_variant_string() {
455        trace!("test_unknown_variant_string: starting");
456        let input = json!("RandomStuff");
457        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
458        assert!(
459            result.is_err(),
460            "Expected an error for unknown string variant, got Ok: {:?}",
461            result.ok()
462        );
463    }
464
465    #[traced_test]
466    fn test_unknown_variant_key() {
467        trace!("test_unknown_variant_key: starting");
468        let input = json!({
469            "RandomStuff": {
470                "variant_confidence": 0.5,
471                "variant_justification": "No idea what this is"
472            }
473        });
474        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
475        assert!(
476            result.is_err(),
477            "Expected an error for unknown single-key variant, got Ok: {:?}",
478            result.ok()
479        );
480    }
481
482    #[traced_test]
483    fn test_missing_variant_name_in_object() {
484        trace!("test_missing_variant_name_in_object: starting");
485        let input = json!({
486            "variant_confidence": 0.5,
487            "variant_justification": "Missing the variant_name field"
488        });
489        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
490        assert!(
491            result.is_err(),
492            "Expected an error for missing 'variant_name', got Ok: {:?}",
493            result.ok()
494        );
495    }
496
497    #[traced_test]
498    fn test_non_object_non_string() {
499        trace!("test_non_object_non_string: starting");
500        let input = json!(123);
501        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
502        assert!(
503            result.is_err(),
504            "Expected an error for numeric input, got Ok: {:?}",
505            result.ok()
506        );
507    }
508
509    #[traced_test]
510    fn test_malformed_variant_name_type() {
511        trace!("test_malformed_variant_name_type: starting");
512        let input = json!({
513            "variant_name": 123
514        });
515        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
516        assert!(
517            result.is_err(),
518            "Expected an error for integer 'variant_name', got Ok: {:?}",
519            result.ok()
520        );
521    }
522
523    #[traced_test]
524    fn test_non_number_variant_confidence() {
525        trace!("test_non_number_variant_confidence: starting");
526        let input = json!({
527            "variant_name": "Single",
528            "variant_confidence": "not a number",
529            "variant_justification": "But let's see if it defaults"
530        });
531        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
532        assert!(
533            result.is_ok(),
534            "Expected Ok even if variant_confidence is not numeric, got error: {:?}",
535            result.err()
536        );
537        let parsed = result.unwrap();
538        match parsed {
539            JustifiedCapstoneMode::Single {
540                variant_confidence,
541                variant_justification,
542            } => {
543                assert_eq!(
544                    variant_confidence, 0.0,
545                    "Expected confidence to default to 0.0 if not numeric"
546                );
547                assert_eq!(
548                    variant_justification, "But let's see if it defaults",
549                    "Justification mismatch"
550                );
551            }
552            _ => panic!("Expected Single variant, got: {:?}", parsed),
553        }
554    }
555
556    #[traced_test]
557    fn test_non_string_variant_justification() {
558        trace!("test_non_string_variant_justification: starting");
559        let input = json!({
560            "variant_name": "Probabilistic",
561            "variant_confidence": 0.9,
562            "variant_justification": 999
563        });
564        let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
565        assert!(
566            result.is_ok(),
567            "Expected Ok even if variant_justification is not a string, got error: {:?}",
568            result.err()
569        );
570        let parsed = result.unwrap();
571        match parsed {
572            JustifiedCapstoneMode::Probabilistic {
573                variant_confidence,
574                variant_justification,
575            } => {
576                assert_eq!(variant_confidence, 0.9, "Confidence mismatch");
577                assert_eq!(
578                    variant_justification, "",
579                    "Expected empty justification if not a valid string"
580                );
581            }
582            _ => panic!("Expected Probabilistic variant, got: {:?}", parsed),
583        }
584    }
585}