capability_grower_configuration/
tree_expansion_policy.rs

1// ---------------- [ File: capability-grower-configuration/src/tree_expansion_policy.rs ]
2crate::ix!();
3
4/// This structure is the primary enum for controlling how we choose among `Dispatch`, `Aggregate`, and
5/// `LeafHolder` nodes at each level of the tree generation procedure.
6///
7/// Each variant indicates a strategy for determining the mechanism of choosing the node-kind.
8///
9/// The choice should be made to optimally configure the grower for our target domain. Different
10/// domains will require different variant selections here.
11///
12/// It is important for the language model to determine intelligent behavior for its selection.
13///
14/// TODO: *here we would like to indicate some useful guidelines about how to do this successfully*.
15///
16#[derive(
17    SaveLoad,
18    Debug, 
19    Clone, 
20    PartialEq, 
21    Serialize, 
22    Deserialize, 
23    AiJsonTemplateWithJustification,
24    AiJsonTemplate)]
25#[serde(rename_all = "snake_case")]
26pub enum TreeExpansionPolicy {
27
28    /// This strategy will always pick `Dispatch` for intermediate nodes (levels < final) and `LeafHolder` for
29    /// the final level. It will ignore aggregator nodes entirely. This is the classic “enum tree until depth,
30    /// then leaves at the bottom.”
31    Simple,
32
33    /// At each level, this strategy will randomly pick among aggregator/dispatch/leaf with the given weights.
34    /// (It has no built-in depth constraints.)
35    Weighted(
36
37        // Consider the relative frequencies of each particular kind of node.
38        WeightedNodeVariantPolicy
39    ),
40
41    /// This strategy is the same as `Weighted`, but with inline constraints like aggregator max depth, etc.
42    WeightedWithLimits(
43
44        // Consider for our target domain the likelihoods of needing a node of a particular kind at
45        // this level, as well as when node kinds turn on/off within the overall flow.
46        WeightedNodeVariantPolicyWithLimits
47    ),
48
49    /// This strategy will define the level at which aggregator nodes “begin,” as well as the level at which leaf nodes “begin,”
50    /// with dispatch nodes used before the aggregator nodes start.
51    DepthBased(
52
53        /// Here we need to take into account the characteristics of the target domain in order to
54        /// choose specifically the levels at which nodes of a particular kind *begin* and when
55        /// they *end.
56        DepthBasedNodeVariantPolicy
57    ),
58
59    /// This strategy will always pick aggregator except for the final level (which presumably is a leaf).
60    AlwaysAggregate,
61
62    /// This strategy will always pick dispatch nodes except for during the final level.
63    AlwaysDispatch,
64
65    /// This strategy will force every node to be a leaf, ignoring depth. Typically used for flattening or testing.
66    AlwaysLeafHolder,
67
68    /// This strategy will specify a list of “phases,” each determining weights for aggregator/dispatch/leaf generation starting at
69    /// a given depth, continuing until the next phase or the end.
70    Phased(
71        /// Here we need to take into account how many phases are useful for our target domain and
72        /// what their parameters are.
73        PhasedNodeVariantPolicy
74    ),
75
76    /// This strategy uses a direct “per-level” approach via a map from `u8` (level) → aggregator/dispatch/leaf
77    /// probabilities. If a level is missing, the downstream generator code will fallback or clamp to last known.
78    Scripted(
79
80        /// Here we need to take into account the target domain in order to choose relevant levels
81        /// and their respective parameters
82        ScriptedNodeVariantPolicy
83    ),
84}
85
86impl Default for TreeExpansionPolicy {
87
88    fn default() -> Self {
89        TreeExpansionPolicy::DepthBased(Default::default())
90    }
91}
92
93impl FuzzyFromJsonValue for JustifiedTreeExpansionPolicy {
94    fn fuzzy_from_json_value(value: &serde_json::Value) -> Result<Self, FuzzyFromJsonValueError> {
95        trace!("(JustifiedTreeExpansionPolicy) Entering fuzzy_from_json_value => {}", value);
96
97        // 1) If null => default => Simple
98        if value.is_null() {
99            debug!("(JustifiedTreeExpansionPolicy) Null => returning Simple");
100            return Ok(Self::Simple {
101                variant_confidence: 0.0,
102                variant_justification: String::new(),
103            });
104        }
105
106        // 2) If string => unify => { "variant_name": "someString" }
107        if let Some(s) = value.as_str() {
108            let mut map = serde_json::Map::new();
109            map.insert("variant_name".to_string(), serde_json::Value::String(s.to_string()));
110            let wrapped = serde_json::Value::Object(map);
111            return Self::fuzzy_from_json_value(&wrapped);
112        }
113
114        // 3) Must be object => flatten all 'fields'
115        let mut top = match value.as_object() {
116            Some(m) => {
117                let mut c = m.clone();
118                flatten_all_fields(&mut c);
119                c
120            }
121            None => {
122                error!("(JustifiedTreeExpansionPolicy) Not an object => fail");
123                return Err(FuzzyFromJsonValueError::NotAnObject {
124                    target_type: "JustifiedTreeExpansionPolicy",
125                    actual: value.clone(),
126                });
127            }
128        };
129
130        // recognized variants
131        let recognized = [
132            "Simple",
133            "Weighted",
134            "WeightedWithLimits",
135            "DepthBased",
136            "AlwaysAggregate",
137            "AlwaysDispatch",
138            "AlwaysLeafHolder",
139            "Phased",
140            "Scripted",
141        ];
142
143        // find single recognized key
144        let mut found_keys = Vec::new();
145        for k in top.keys() {
146            // normalize (remove underscores, ignore case)
147            let normk = k.replace("_","").to_lowercase();
148            if recognized.iter().any(|r| r.replace("_","").to_lowercase() == normk) {
149                found_keys.push(k.clone());
150            }
151        }
152
153        if found_keys.len() == 1 {
154            let recognized_key = found_keys[0].clone();
155            trace!("(JustifiedTreeExpansionPolicy) single recognized key='{}' => unify", recognized_key);
156            let variant_val = top.remove(&recognized_key).unwrap_or(serde_json::Value::Null);
157
158            // new map => { "variant_name": recognized_key, ... leftover top-level ... }
159            let mut new_map = serde_json::Map::new();
160            new_map.insert("variant_name".to_string(), serde_json::Value::String(recognized_key));
161
162            // handle the variant_val if object or array
163            match variant_val {
164                serde_json::Value::Object(o) => {
165                    // flatten
166                    let mut subo = o.clone();
167                    flatten_all_fields(&mut subo);
168                    // merge subo
169                    for (k,v) in subo.into_iter() {
170                        new_map.insert(k, v);
171                    }
172                }
173                serde_json::Value::Array(arr) => {
174                    // We interpret the array in the form [ subobject, variant_conf, variant_just ] if length >= 1
175                    debug!("(JustifiedTreeExpansionPolicy) single-key is an array => we'll parse [ subobject, conf, justification ] if present.");
176                    if !arr.is_empty() {
177                        // The first element is the subobject
178                        let first = &arr[0];
179                        if let Some(ob) = first.as_object() {
180                            let mut subo = ob.clone();
181                            flatten_all_fields(&mut subo);
182                            for (kk,vv) in subo.into_iter() {
183                                new_map.insert(kk, vv);
184                            }
185                        }
186                        // 2nd element => variant_conf
187                        if arr.len() > 1 {
188                            if let Some(num) = arr[1].as_f64() {
189                                new_map.insert("variant_confidence".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(num).unwrap()));
190                            }
191                        }
192                        // 3rd element => variant_justification
193                        if arr.len() > 2 {
194                            if let Some(jst) = arr[2].as_str() {
195                                new_map.insert("variant_justification".to_string(), serde_json::Value::String(jst.to_string()));
196                            }
197                        }
198                    }
199                }
200                other => {
201                    // if it's e.g. string/number => we treat that as subobject or ignore
202                    debug!("(JustifiedTreeExpansionPolicy) single recognized key => got a non-object/array => ignoring or unify minimal");
203                    // we'll store it as "variant_confidence" if it's f64
204                    if let Some(num) = other.as_f64() {
205                        new_map.insert("variant_confidence".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(num).unwrap()));
206                    }
207                }
208            }
209
210            // also merge leftover top-level
211            for (k,v) in top.into_iter() {
212                new_map.insert(k, v);
213            }
214
215            let wrapped = serde_json::Value::Object(new_map);
216            return Self::fuzzy_from_json_value(&wrapped);
217        }
218
219        // if not single recognized => expect "variant_name"
220        let var_name_val = match top.remove("variant_name") {
221            Some(v) => v,
222            None => {
223                error!("(JustifiedTreeExpansionPolicy) Missing 'variant_name' => fail");
224                return Err(FuzzyFromJsonValueError::MissingField {
225                    field_name: "variant_name",
226                    target_type: "JustifiedTreeExpansionPolicy",
227                });
228            }
229        };
230        let var_name_str = match var_name_val.as_str() {
231            Some(s) => s,
232            None => {
233                error!("(JustifiedTreeExpansionPolicy) 'variant_name' not a string => fail => got {:?}", var_name_val);
234                return Err(FuzzyFromJsonValueError::Other {
235                    target_type: "JustifiedTreeExpansionPolicy",
236                    detail: format!("'variant_name' must be string => got {:?}", var_name_val),
237                });
238            }
239        };
240
241        let v_conf = top
242            .remove("variant_confidence")
243            .and_then(|x| x.as_f64())
244            .unwrap_or(0.0);
245        let v_just = top
246            .remove("variant_justification")
247            .and_then(|x| x.as_str().map(|s| s.to_string()))
248            .unwrap_or_default();
249
250        trace!("(JustifiedTreeExpansionPolicy) final => variant_name='{}', conf={}, just='{}'", var_name_str, v_conf, v_just);
251
252        // unify if there's a "field_0" array or object
253        if let Some(f0_val) = top.remove("field_0") {
254            // if array => parse first as object, second as conf, third as just
255            if let Some(arr) = f0_val.as_array() {
256                if !arr.is_empty() {
257                    // first => subobject
258                    if let Some(objval) = arr.get(0) {
259                        if let Some(ob) = objval.as_object() {
260                            let mut subo = ob.clone();
261                            flatten_all_fields(&mut subo);
262                            for (kk, vv) in subo.into_iter() {
263                                top.insert(kk, vv);
264                            }
265                        }
266                    }
267                    // second => conf
268                    if arr.len() > 1 {
269                        if let Some(num) = arr[1].as_f64() {
270                            top.insert("variant_confidence".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(num).unwrap()));
271                        }
272                    }
273                    // third => justification
274                    if arr.len() > 2 {
275                        if let Some(sjust) = arr[2].as_str() {
276                            top.insert("variant_justification".to_string(), serde_json::Value::String(sjust.to_string()));
277                        }
278                    }
279                }
280            } else if let Some(ob) = f0_val.as_object() {
281                let mut subo = ob.clone();
282                flatten_all_fields(&mut subo);
283                for (kk,vv) in subo.into_iter() {
284                    top.insert(kk, vv);
285                }
286            }
287        }
288
289        // after that, re-check if we replaced variant_confidence or justification
290        let final_conf = top.remove("variant_confidence").and_then(|x| x.as_f64()).unwrap_or(v_conf);
291        let final_just = top.remove("variant_justification").and_then(|x| x.as_str().map(|s| s.to_string())).unwrap_or(v_just);
292
293        // now match
294        let canonical = var_name_str.replace("_","").to_lowercase();
295        match canonical.as_str() {
296            "simple" => Ok(Self::Simple {
297                variant_confidence: final_conf,
298                variant_justification: final_just,
299            }),
300            "weighted" => {
301                let sub = JustifiedWeightedNodeVariantPolicy::fuzzy_from_json_value(&serde_json::Value::Object(top))?;
302                Ok(Self::Weighted {
303                    field_0: sub,
304                    field_0_confidence: 0.0,
305                    field_0_justification: String::new(),
306                    variant_confidence: final_conf,
307                    variant_justification: final_just,
308                })
309            }
310            "weightedwithlimits" => {
311                let sub = JustifiedWeightedNodeVariantPolicyWithLimits::fuzzy_from_json_value(&serde_json::Value::Object(top))?;
312                Ok(Self::WeightedWithLimits {
313                    field_0: sub,
314                    field_0_confidence: 0.0,
315                    field_0_justification: String::new(),
316                    variant_confidence: final_conf,
317                    variant_justification: final_just,
318                })
319            }
320            "depthbased" => {
321                let sub = JustifiedDepthBasedNodeVariantPolicy::fuzzy_from_json_value(&serde_json::Value::Object(top))?;
322                Ok(Self::DepthBased {
323                    field_0: sub,
324                    field_0_confidence: 0.0,
325                    field_0_justification: String::new(),
326                    variant_confidence: final_conf,
327                    variant_justification: final_just,
328                })
329            }
330            "alwaysaggregate" => Ok(Self::AlwaysAggregate {
331                variant_confidence: final_conf,
332                variant_justification: final_just,
333            }),
334            "alwaysdispatch" => Ok(Self::AlwaysDispatch {
335                variant_confidence: final_conf,
336                variant_justification: final_just,
337            }),
338            "alwaysleafholder" => Ok(Self::AlwaysLeafHolder {
339                variant_confidence: final_conf,
340                variant_justification: final_just,
341            }),
342            "phased" => {
343                let sub = JustifiedPhasedNodeVariantPolicy::fuzzy_from_json_value(&serde_json::Value::Object(top))?;
344                Ok(Self::Phased {
345                    field_0: sub,
346                    field_0_confidence: 0.0,
347                    field_0_justification: String::new(),
348                    variant_confidence: final_conf,
349                    variant_justification: final_just,
350                })
351            }
352            "scripted" => {
353                let sub = JustifiedScriptedNodeVariantPolicy::fuzzy_from_json_value(&serde_json::Value::Object(top))?;
354                Ok(Self::Scripted {
355                    field_0: sub,
356                    field_0_confidence: 0.0,
357                    field_0_justification: String::new(),
358                    variant_confidence: final_conf,
359                    variant_justification: final_just,
360                })
361            }
362            other => {
363                error!("(JustifiedTreeExpansionPolicy) Unknown variant='{}'", other);
364                Err(FuzzyFromJsonValueError::Other {
365                    target_type: "JustifiedTreeExpansionPolicy",
366                    detail: format!("Unknown variant_name='{}'", var_name_str),
367                })
368            }
369        }
370    }
371}
372
373#[cfg(test)]
374mod test_tree_expansion_policy {
375    use super::*;
376
377    // ------------------------------------------------------------------
378    // 1) Tests verifying normal instance-based usage of serde (round-trip).
379    //    These tests confirm you can serialize each variant to JSON (as data)
380    //    and recover the same variant.  This is separate from AiJsonTemplate,
381    //    which is purely a *type-level* reflection.
382    // ------------------------------------------------------------------
383
384    /// Round-trip a policy via `serde_json` and compare equality. 
385    /// (This ensures normal instance-based serialization works.)
386    fn round_trip_serde(policy: &TreeExpansionPolicy) -> TreeExpansionPolicy {
387        let ser = serde_json::to_string_pretty(policy)
388            .expect("Serialization must succeed");
389        let de: TreeExpansionPolicy = serde_json::from_str(&ser)
390            .expect("Deserialization must succeed");
391        de
392    }
393
394    #[traced_test]
395    fn test_serde_default_policy() {
396        // The default impl chooses DepthBased(aggregator=2, leaf=4)
397        let def = TreeExpansionPolicy::default();
398        if let TreeExpansionPolicy::DepthBased(cfg) = &def {
399            assert_eq!(*cfg.aggregator_start_level(), 2);
400            assert_eq!(*cfg.leaf_start_level(), 4);
401        } else {
402            panic!("Default should produce a DepthBased(...) variant");
403        }
404
405        // Round-trip
406        let after = round_trip_serde(&def);
407        assert_eq!(after, def);
408    }
409
410    #[traced_test]
411    fn test_serde_simple_variant() {
412        let policy = TreeExpansionPolicy::Simple;
413        let after = round_trip_serde(&policy);
414        assert_eq!(after, policy);
415    }
416
417    #[traced_test]
418    fn test_serde_weighted_variant() {
419        let w = WeightedNodeVariantPolicyBuilder::default()
420            .aggregator_weight(0.3)
421            .dispatch_weight(0.4)
422            .leaf_holder_weight(0.3)
423            .build()
424            .unwrap();
425        let policy = TreeExpansionPolicy::Weighted(w);
426
427        let after = round_trip_serde(&policy);
428        assert_eq!(after, policy);
429    }
430
431    #[traced_test]
432    fn test_serde_weighted_with_limits_variant() {
433        let wwl = WeightedNodeVariantPolicyWithLimitsBuilder::default()
434            .aggregator_weight(0.4)
435            .dispatch_weight(0.4)
436            .leaf_holder_weight(0.2)
437            .aggregator_max_depth(Some(3))
438            .leaf_min_depth(Some(2))
439            .build()
440            .unwrap();
441        let policy = TreeExpansionPolicy::WeightedWithLimits(wwl);
442
443        let after = round_trip_serde(&policy);
444        assert_eq!(after, policy);
445    }
446
447    #[traced_test]
448    fn test_serde_depth_based_variant() {
449        let db = DepthBasedNodeVariantPolicyBuilder::default()
450            .aggregator_start_level(1)
451            .leaf_start_level(5)
452            .build()
453            .unwrap();
454        let policy = TreeExpansionPolicy::DepthBased(db);
455
456        let after = round_trip_serde(&policy);
457        assert_eq!(after, policy);
458    }
459
460    #[traced_test]
461    fn test_serde_always_aggregate_variant() {
462        let policy = TreeExpansionPolicy::AlwaysAggregate;
463        let after = round_trip_serde(&policy);
464        assert_eq!(after, policy);
465    }
466
467    #[traced_test]
468    fn test_serde_always_dispatch_variant() {
469        let policy = TreeExpansionPolicy::AlwaysDispatch;
470        let after = round_trip_serde(&policy);
471        assert_eq!(after, policy);
472    }
473
474    #[traced_test]
475    fn test_serde_always_leaf_holder_variant() {
476        let policy = TreeExpansionPolicy::AlwaysLeafHolder;
477        let after = round_trip_serde(&policy);
478        assert_eq!(after, policy);
479    }
480
481    #[traced_test]
482    fn test_serde_phased_variant() {
483        let p1 = NodeVariantPhaseRangeBuilder::default()
484            .start_level(0)
485            .aggregator_weight(0.1)
486            .dispatch_weight(0.7)
487            .leaf_weight(0.2)
488            .build()
489            .unwrap();
490        let p2 = NodeVariantPhaseRangeBuilder::default()
491            .start_level(5)
492            .aggregator_weight(0.4)
493            .dispatch_weight(0.3)
494            .leaf_weight(0.3)
495            .build()
496            .unwrap();
497
498        let phased_cfg = PhasedNodeVariantPolicyBuilder::default()
499            .phases(vec![p1, p2])
500            .build()
501            .unwrap();
502
503        let policy = TreeExpansionPolicy::Phased(phased_cfg);
504
505        let after = round_trip_serde(&policy);
506        assert_eq!(after, policy);
507    }
508
509    #[traced_test]
510    fn test_serde_scripted_variant() {
511        let lvw1 = NodeVariantLevelWeightsBuilder::default()
512            .aggregator_chance(0.4)
513            .dispatch_chance(0.4)
514            .leaf_chance(0.2)
515            .build()
516            .unwrap();
517        let lvw2 = NodeVariantLevelWeightsBuilder::default()
518            .aggregator_chance(0.1)
519            .dispatch_chance(0.1)
520            .leaf_chance(0.8)
521            .build()
522            .unwrap();
523
524        let mut levels_map = HashMap::new();
525        levels_map.insert(0_u8, lvw1);
526        levels_map.insert(3_u8, lvw2);
527
528        let scripted_cfg = ScriptedNodeVariantPolicyBuilder::default()
529            .levels(levels_map)
530            .build()
531            .unwrap();
532
533        let policy = TreeExpansionPolicy::Scripted(scripted_cfg);
534
535        let after = round_trip_serde(&policy);
536        assert_eq!(after, policy);
537    }
538
539    // ------------------------------------------------------------------
540    // 2) AiJsonTemplate: purely *type-level* reflection. We'll call 
541    //    `::<Type as AiJsonTemplate>::to_template()`, since it's a 
542    //    static/associated function (no instance data).
543    // ------------------------------------------------------------------
544
545    #[traced_test]
546    fn test_tree_expansion_policy_type_template() {
547        // Because AiJsonTemplate is a static reflection approach, we do:
548        let tpl = <TreeExpansionPolicy as AiJsonTemplate>::to_template();
549        // We can do minimal checks, e.g. confirm it's an object with "type":"complex_enum" or similar
550        assert!(tpl.is_object(), "Expected a JSON object for enum template");
551
552        let root_obj = tpl.as_object().unwrap();
553        // Typically for an enum with non-unit variants, your macro sets "type" = "complex_enum" 
554        // or something. Let's check. This depends on your actual macro expansion.
555        let typ = root_obj.get("type").and_then(|v| v.as_str()).unwrap_or("");
556        assert_eq!(typ, "complex_enum", "We expect a complex enum template for TreeExpansionPolicy");
557    }
558
559    #[traced_test]
560    fn test_weighted_node_variant_policy_type_template() {
561        let tpl = <WeightedNodeVariantPolicy as AiJsonTemplate>::to_template();
562        assert!(tpl.is_object(), "Expected a JSON object for WeightedNodeVariantPolicy template");
563
564        // If your macro sets "type":"struct", we can check that:
565        let root_obj = tpl.as_object().unwrap();
566        let typ = root_obj.get("type").and_then(|v| v.as_str()).unwrap_or("");
567        assert_eq!(typ, "struct", "Usually WeightedNodeVariantPolicy is treated as a struct by AiJsonTemplate");
568    }
569
570    #[traced_test]
571    fn test_weighted_with_limits_policy_type_template() {
572        let tpl = <WeightedNodeVariantPolicyWithLimits as AiJsonTemplate>::to_template();
573        assert!(tpl.is_object());
574        let root_obj = tpl.as_object().unwrap();
575        let typ = root_obj.get("type").and_then(|v| v.as_str()).unwrap_or("");
576        assert_eq!(typ, "struct");
577    }
578
579    #[traced_test]
580    fn test_depth_based_node_variant_policy_type_template() {
581        let tpl = <DepthBasedNodeVariantPolicy as AiJsonTemplate>::to_template();
582        assert!(tpl.is_object());
583        let root_obj = tpl.as_object().unwrap();
584        let typ = root_obj.get("type").and_then(|v| v.as_str()).unwrap_or("");
585        assert_eq!(typ, "struct");
586    }
587
588    #[traced_test]
589    fn test_phased_node_variant_policy_type_template() {
590        let tpl = <PhasedNodeVariantPolicy as AiJsonTemplate>::to_template();
591        assert!(tpl.is_object());
592        let root_obj = tpl.as_object().unwrap();
593        let typ = root_obj.get("type").and_then(|v| v.as_str()).unwrap_or("");
594        assert_eq!(typ, "struct");
595    }
596
597    #[traced_test]
598    fn test_node_variant_phase_range_type_template() {
599        let tpl = <NodeVariantPhaseRange as AiJsonTemplate>::to_template();
600        assert!(tpl.is_object());
601        let root_obj = tpl.as_object().unwrap();
602        let typ = root_obj.get("type").and_then(|v| v.as_str()).unwrap_or("");
603        assert_eq!(typ, "struct");
604    }
605
606    #[traced_test]
607    fn test_scripted_node_variant_policy_type_template() {
608        let tpl = <ScriptedNodeVariantPolicy as AiJsonTemplate>::to_template();
609        assert!(tpl.is_object());
610        let root_obj = tpl.as_object().unwrap();
611        let typ = root_obj.get("type").and_then(|v| v.as_str()).unwrap_or("");
612        assert_eq!(typ, "struct");
613    }
614
615    #[traced_test]
616    fn test_node_variant_level_weights_type_template() {
617        let tpl = <NodeVariantLevelWeights as AiJsonTemplate>::to_template();
618        assert!(tpl.is_object());
619        let root_obj = tpl.as_object().unwrap();
620        let typ = root_obj.get("type").and_then(|v| v.as_str()).unwrap_or("");
621        assert_eq!(typ, "struct");
622    }
623
624    // Helper to wrap a "variant_name" into a JSON object with optional subfields
625    fn wrap_variant(variant_name: &str, extra: Option<serde_json::Value>) -> serde_json::Value {
626        let mut base = serde_json::Map::new();
627        base.insert("variant_name".to_string(), json!(variant_name));
628        if let Some(obj) = extra {
629            if let Some(submap) = obj.as_object() {
630                for (k,v) in submap.iter() {
631                    base.insert(k.clone(), v.clone());
632                }
633            }
634        }
635        json!(base)
636    }
637
638    #[test]
639    fn test_null_input() {
640        let input = json!(null);
641        let result = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&input);
642        assert!(result.is_ok(), "Should default to Simple if null");
643        let pol = result.unwrap();
644        match pol {
645            JustifiedTreeExpansionPolicy::Simple { .. } => { /* pass */ },
646            other => panic!("Expected Simple, got {:?}", other),
647        }
648    }
649
650    #[test]
651    fn test_bare_string_simple() {
652        // "Simple" => unify => parse => expect JustifiedTreeExpansionPolicy::Simple
653        let input = json!("Simple");
654        let result = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&input);
655        assert!(result.is_ok(), "Bare string 'Simple' must parse OK");
656        let pol = result.unwrap();
657        matches!(pol, JustifiedTreeExpansionPolicy::Simple {..});
658    }
659
660    #[test]
661    fn test_camel_case_weighted_with_limits() {
662        // "WeightedWithLimits" => normalized => "weighted_with_limits"
663        let input = wrap_variant("WeightedWithLimits", None);
664        let result = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&input);
665        assert!(result.is_ok(), "CamelCase WeightedWithLimits must parse OK");
666        let pol = result.unwrap();
667        if let JustifiedTreeExpansionPolicy::WeightedWithLimits { .. } = pol {
668            // success
669        } else {
670            panic!("Expected WeightedWithLimits variant, got {:?}", pol);
671        }
672    }
673
674    #[test]
675    fn test_pascal_case_depth_based() {
676        // "DepthBased" => normalized => "depth_based"
677        let input = wrap_variant("DepthBased", None);
678        let result = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&input);
679        assert!(result.is_ok(), "CamelCase DepthBased must parse OK");
680        let pol = result.unwrap();
681        if let JustifiedTreeExpansionPolicy::DepthBased {..} = pol {
682            // success
683        } else {
684            panic!("Expected DepthBased variant, got {:?}", pol);
685        }
686    }
687
688    #[test]
689    fn test_upper_snake_always_leaf_holder() {
690        // "ALWAYS_LEAF_HOLDER" => normalized => "always_leaf_holder"
691        let input = wrap_variant("ALWAYS_LEAF_HOLDER", None);
692        let result = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&input);
693        assert!(result.is_ok(), "UPPER_SNAKE => always_leaf_holder");
694        let pol = result.unwrap();
695        if let JustifiedTreeExpansionPolicy::AlwaysLeafHolder {..} = pol {
696            // success
697        } else {
698            panic!("Expected AlwaysLeafHolder variant, got {:?}", pol);
699        }
700    }
701
702    #[test]
703    fn test_single_key_object_scripted() {
704        // Single-key form:
705        // {
706        //   "Scripted": {
707        //       "some_subfield": ...
708        //   }
709        // }
710        let input = json!({
711            "Scripted": {
712                "some_random_field": 123
713            }
714        });
715        let result = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&input);
716        assert!(result.is_ok(), "Single-key object 'Scripted' must parse OK");
717        let pol = result.unwrap();
718        if let JustifiedTreeExpansionPolicy::Scripted { field_0, .. } = pol {
719            // success, though we haven't tested subfields in detail.
720            
721            // Justified -> normal type
722            // let normal = field_0.into(); 
723            
724            // or do something with it
725        } else {
726            panic!("Expected Scripted variant, got {:?}", pol);
727        }
728    }
729
730    #[test]
731    fn test_unknown_variant() {
732        let input = wrap_variant("MadeUpStuff", None);
733        let result = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&input);
734        assert!(result.is_err(), "Should fail for unknown variant");
735    }
736
737    #[traced_test]
738    fn test_real_snippet_can_parse() {
739        // Here's the actual JSON snippet you provided, embedded in a raw string.
740        // (You can omit or shorten it if needed, but below we show it in full.)
741        let raw_json = r#"
742        {
743          "fields": {
744              "aggregator_depth_limit": 3,
745              "aggregator_depth_limit_confidence": 0.8,
746              "aggregator_depth_limit_justification": "Aggregator nodes should group high-level categories in the early levels. Limiting them to level 3 ensures deeper layers focus on more direct dispatch or leaf nodes, which suits the broad yet progressively specialized domain of natural-movement.",
747              "aggregator_preference": 0.4,
748              "aggregator_preference_confidence": 0.85,
749              "aggregator_preference_justification": "A moderate preference for aggregator nodes accommodates conceptual grouping without overshadowing dispatch nodes, aligning with the multi-environment nature of natural-movement.",
750              "ai_confidence": {
751                  "fields": {
752                      "base_factor": 1.2,
753                      "base_factor_confidence": 0.95,
754                      "base_factor_justification": "We introduce a moderate positive offset when the AI is confident, allowing more sub-branches and deeper exploration where clarity is high.",
755                      "factor_multiplier": 0.4,
756                      "factor_multiplier_confidence": 0.8,
757                      "factor_multiplier_justification": "We scale expansions proportionally to confidence at a moderate level, ensuring that higher certainty fosters richer branching."
758                  },
759                  "has_justification": true,
760                  "struct_docs": " This struct should define how AI confidence modifies branching factor:\n\n If present, we interpret these fields to scale or reduce the final branch factor based on AI's\n certainty.\n",
761                  "struct_name": "AiTreeBranchingConfidenceConfiguration",
762                  "type": "struct"
763              },
764              "ai_confidence_confidence": 0.9,
765              "ai_confidence_justification": "We enable AI-driven scaling of branching to harness domain knowledge, supporting deeper expansions when clarity around sub-environments (forest, cave, etc.) is high.",
766              "allow_early_leaves": true,
767              "allow_early_leaves_confidence": 0.9,
768              "allow_early_leaves_justification": "Some sub-branches may be straightforward and conclude early, reflecting simpler movements that do not require many nested subdivisions.",
769              "balance_symmetry": 0.6,
770              "balance_symmetry_confidence": 0.7,
771              "balance_symmetry_justification": "A moderately balanced tree highlights uniform coverage of skills while still allowing variation for the diverse sub-environments.",
772              "breadth": 9,
773              "breadth_confidence": 0.9,
774              "breadth_justification": "The domain covers various natural terrain and movement modalities, so we choose a broad branching factor to capture diversity within each level.",
775              "capstone": {
776                  "fields": {
777                      "mode": {
778                          "variant_name": "Probabilistic",
779                          "variant_confidence": 0.8,
780                          "variant_justification": "Some leaves stand out as apex skills in natural movement, but not necessarily just one or none. Probability-based capstones fit well.",
781                          "variant_docs": " This variant designates a fraction of leaves as highlight/capstones."
782                      },
783                      "mode_confidence": 0.8,
784                      "mode_justification": "We occasionally designate certain high-level endpoints to highlight advanced or pinnacle movement skills.",
785                      "probability": 0.15,
786                      "probability_confidence": 0.7,
787                      "probability_justification": "A moderate fraction of leaves can be elevated to capstone status to represent specialized or top-tier competencies."
788                  },
789                  "has_justification": true,
790                  "struct_docs": " In the context of our tree-generation system, a capstone node is a special type of leaf node\n that represents a culminating skill, concept, or endpoint of a hierarchical branch. \n\n Capstone nodes stand out clearly as a final, specialized mastery points—often signifying an advanced,\n important, or unique skill within a skill-tree.\n\n This struct merges the chosen capstone mode with its relevant probability (if any).\n",
791                  "struct_name": "CapstoneGenerationConfiguration",
792                  "type": "struct"
793              },
794              "capstone_confidence": 0.9,
795              "capstone_justification": "Natural-movement includes a few exceptionally advanced endpoints. This approach ensures those highlights are integrated.",
796              "complexity": {
797                  "variant_name": "Complex",
798                  "variant_confidence": 0.85,
799                  "variant_justification": "The numerous sub-environments and potential overlap with neighbors (e.g. forest-orienteering, river-crossing) warrant in-depth structures.",
800                  "variant_docs": " This variant indicates a highly detailed, more complex approach."
801              },
802              "complexity_confidence": 0.85,
803              "complexity_justification": "We want to capture the broad variety of movement and adaptability across different terrains and conditions.",
804              "density": 10,
805              "density_confidence": 0.8,
806              "density_justification": "Each leaf node should describe multiple specific variations or techniques, reflecting the richness of natural movement at its endpoint.",
807              "depth": 7,
808              "depth_confidence": 0.9,
809              "depth_justification": "A deeper hierarchy is vital to capture fundamental through advanced practices in varied terrains, providing ample layering of complexity.",
810              "dispatch_depth_limit": 6,
811              "dispatch_depth_limit_confidence": 0.8,
812              "dispatch_depth_limit_justification": "This ensures that dispatch nodes primarily exist in mid-layers, reserving the deepest layer for leaf finalization of specialized skill sets.",
813              "leaf_granularity": 0.7,
814              "leaf_granularity_confidence": 0.8,
815              "leaf_granularity_justification": "Leaves describe fairly detailed aspects of movement, though not to an extreme, preserving usability and avoiding excessive minutiae.",
816              "leaf_min_depth": 5,
817              "leaf_min_depth_confidence": 0.8,
818              "leaf_min_depth_justification": "We enforce deeper expansions (levels < 5) before concluding with leaves, allowing intermediate branches for broad skill subdivisions.",
819              "level_skipping": {
820                  "fields": {
821                      "leaf_probability_per_level": [
822                          0,
823                          0,
824                          0.05,
825                          0.1,
826                          0.15,
827                          0.25,
828                          0.4
829                      ],
830                      "leaf_probability_per_level_confidence": 0.7,
831                      "leaf_probability_per_level_justification": "At upper layers, it is uncommon to finalize too soon, but deeper levels see increased likelihood of culminating branches where specialized skills appear."
832                  },
833                  "has_justification": true,
834                  "struct_docs": " This struct should define how you skip certain levels early, turning them into leaves based on probabilities.",
835                  "struct_name": "LevelSkippingConfiguration",
836                  "type": "struct"
837              },
838              "level_skipping_confidence": 0.75,
839              "level_skipping_justification": "We allow a controlled chance of early termination to reflect straightforward or quickly mastered subdomains.",
840              "level_specific": null,
841              "level_specific_confidence": 0,
842              "level_specific_justification": "A uniform configuration across levels is sufficient given the other constraints, so no per-level overrides are necessary.",
843              "ordering": {
844                  "variant_name": "DifficultyAscending",
845                  "variant_confidence": 0.75,
846                  "variant_justification": "Natural movement fundamentals appear first, with more advanced concepts introduced later.",
847                  "variant_docs": " This variant starts with simpler branches first, advanced last."
848              },
849              "ordering_confidence": 0.75,
850              "ordering_justification": "This approach clarifies progression for learners beginning with basic techniques.",
851              "partial_subbranch_probability": 0.8,
852              "partial_subbranch_probability_confidence": 0.9,
853              "partial_subbranch_probability_justification": "We include most branches to capture variety but exclude some less critical or niche expansions, preventing unnecessary overlap.",
854              "tree_expansion_policy": {
855                  "variant_name": "WeightedWithLimits",
856                  "variant_confidence": 0.95,
857                  "variant_justification": "A weighted approach with depth constraints ensures aggregator usage early, dispatch in mid-layers, and leaf finalization at deeper levels. This matches the layered and structured growth needed for natural-movement.",
858                  "variant_docs": " This strategy is the same as `Weighted`, but with inline constraints like aggregator max depth, etc.",
859                  "fields": {
860                      "field_0": {
861                          "fields": {
862                              "aggregator_max_depth": 3,
863                              "aggregator_max_depth_confidence": 0.9,
864                              "aggregator_max_depth_justification": "Aggregators group broad skill sets in early levels, after which dispatch takes over more specialized subdividing.",
865                              "aggregator_weight": 0.3,
866                              "aggregator_weight_confidence": 0.75,
867                              "aggregator_weight_justification": "Aggregator nodes appear but are not dominant, allowing for some conceptual bundling.",
868                              "dispatch_max_depth": 6,
869                              "dispatch_max_depth_confidence": 0.9,
870                              "dispatch_max_depth_justification": "Dispatch nodes persist through intermediate layers but do not appear at the deepest level, ensuring leaves finalize advanced skills.",
871                              "dispatch_weight": 0.5,
872                              "dispatch_weight_confidence": 0.8,
873                              "dispatch_weight_justification": "Dispatch is the primary node type for mid-layer expansions, suiting the domain’s subdivided movement styles.",
874                              "leaf_holder_weight": 0.2,
875                              "leaf_holder_weight_confidence": 0.7,
876                              "leaf_holder_weight_justification": "While finalization is critical, we still emphasize structuring sub-branches before reaching the leaf level.",
877                              "leaf_min_depth": 5,
878                              "leaf_min_depth_confidence": 0.9,
879                              "leaf_min_depth_justification": "We restrict leaves to deeper levels so intermediate expansions can thoroughly elaborate skill subsets."
880                          },
881                          "has_justification": true,
882                          "struct_docs": " This policy is the same as Weighted, but with built-in depth constraints to forbid aggregator or\n dispatch nodes beyond a certain depth. We can use it to forbid leaves before a certain depth.",
883                          "struct_name": "WeightedNodeVariantPolicyWithLimits",
884                          "type": "struct"
885                      },
886                      "field_0_confidence": 0.95,
887                      "field_0_justification": "We tailor the probability distribution to reflect structured branching from general movement categories down to specific advanced leaves."
888                  }
889              },
890              "tree_expansion_policy_confidence": 0.95,
891              "tree_expansion_policy_justification": "Weighted distribution, restricted by level, fits the layered complexity and ensures a coherent shift from broad to specialized concepts.",
892              "weighted_branching": {
893                  "fields": {
894                      "mean": 9,
895                      "mean_confidence": 0.85,
896                      "mean_justification": "We want an average branching factor near our base breadth, ensuring consistency with the overall tree design.",
897                      "variance": 3,
898                      "variance_confidence": 0.8,
899                      "variance_justification": "We allow moderate variability around the mean, simulating natural complexity and preventing uniform branching at every node."
900                  },
901                  "has_justification": true,
902                  "struct_docs": " This struct should define a **mean ± variance** approach for branching factor,\n letting you generate more **natural/organic** trees rather than uniform child counts.",
903                  "struct_name": "WeightedBranchingConfiguration",
904                  "type": "struct"
905              },
906              "weighted_branching_confidence": 0.8,
907              "weighted_branching_justification": "Natural movement often involves organic, uneven expansions; a mean-variance approach to branching factor aligns with this domain."
908          },
909          "has_justification": true,
910          "struct_docs": " We set the GrowerTreeConfiguration to refine the shape of our model trees:\n - **Depth**: # of hierarchical levels (≥1)\n - **Breadth**: baseline sibling count (≥1)\n - **Density**: variants at leaves (≥1)\n - **leaf_granularity**: fraction [0..1] controlling fine detail at leaves\n - **balance_symmetry**: fraction [0..1] controlling symmetrical vs. varied structure\n - **complexity**: overall complexity level (simple, balanced, or complex)\n\n Optional advanced sub-structs:\n - **level_specific**: arrays of breadth/density overrides for each level\n - **weighted_branching**: mean ± variance approach\n - **level_skipping**: per-level leaf probabilities\n - **capstone**: highlight nodes (off, single, or fraction)\n - **ordering**: sub-branch sorting approach\n - **ai_confidence**: modifies branch factor based on AI certainty\n\n New fields:\n - **aggregator_preference**: fraction [0..1] controlling how often aggregator is used vs. dispatch\n - **allow_early_leaves**: if true, allows leaf nodes to appear before the final depth\n - **partial_subbranch_probability**: fraction [0..1] controlling how often each child sub-branch is included\n - **tree_expansion_policy**: advanced logic for picking among aggregator/dispatch/leaf\n - **aggregator_depth_limit**, **dispatch_depth_limit**, **leaf_min_depth**: optional constraints on usage of aggregator, dispatch, or leaf nodes by depth\n\n Your justification must indicate why each justified parameter value was chosen deliberately by\n using knowledge and an understanding of the target domain as a guide. Since each target domain\n is different, the trees which map them will have different shapes and different\n GrowerTreeConfiguration parameters. Your job during justification is to explain clearly why\n a given parameter setting makes sense in the provided target domain.",
911          "struct_name": "GrowerTreeConfiguration",
912          "type": "struct"
913        }
914        "#;
915
916        // 1) Parse it as serde_json::Value
917        let parsed_value: JsonValue = serde_json::from_str(raw_json)
918            .expect("JSON must be valid syntax");
919
920        // 2) Attempt fuzzy parse
921        let result = JustifiedGrowerTreeConfiguration::fuzzy_from_json_value(&parsed_value);
922
923        // 3) Check success
924        assert!(
925            result.is_ok(),
926            "Real snippet should parse successfully, got error: {:?}",
927            result.err()
928        );
929        let jcfg = result.unwrap();
930
931        // 4) Optional: Inspect `jcfg.tree_expansion_policy` to confirm we got WeightedWithLimits
932        match jcfg.tree_expansion_policy() {
933            JustifiedTreeExpansionPolicy::WeightedWithLimits { field_0, .. } => {
934                info!("Got WeightedWithLimits as intended!");
935                // You could do additional checks on aggregator_weight, etc.
936                assert_eq!(*field_0.leaf_holder_weight(), 0.2, "should match snippet");
937            }
938            other => {
939                panic!("Expected WeightedWithLimits from real snippet, got {:?}", other);
940            }
941        }
942
943        // 5) If we want, we can do further checks on jcfg.ai_confidence, jcfg.depth, etc.
944        assert_eq!(*jcfg.depth(), 7, "Should parse depth=7 from snippet");
945        assert!((jcfg.ai_confidence().as_ref().unwrap().factor_multiplier() - 0.4).abs() < 1e-6);
946    }
947
948    #[traced_test]
949    async fn test_parse_null() {
950        trace!("Entering test_parse_null");
951        let val = serde_json::Value::Null;
952        let parsed = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&val);
953        match parsed {
954            Ok(variant) => {
955                info!("Parsed variant: {:?}", variant);
956                // By default, null => Simple variant
957                assert!(matches!(variant, JustifiedTreeExpansionPolicy::Simple { .. }));
958            }
959            Err(e) => {
960                error!("Should not fail on null => got error: {:?}", e);
961                panic!("test_parse_null failed => expected Ok, got Err");
962            }
963        }
964        trace!("Exiting test_parse_null");
965    }
966
967    #[traced_test]
968    async fn test_parse_as_string_simple() {
969        trace!("Entering test_parse_as_string_simple");
970        let val = serde_json::Value::String("Simple".to_string());
971        let parsed = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&val);
972        match parsed {
973            Ok(variant) => {
974                debug!("Parsed variant as string => {:?}", variant);
975                assert!(matches!(variant, JustifiedTreeExpansionPolicy::Simple { .. }));
976            }
977            Err(e) => {
978                error!("Should have parsed 'Simple' => got error: {:?}", e);
979                panic!("test_parse_as_string_simple failed => expected Ok, got Err");
980            }
981        }
982        trace!("Exiting test_parse_as_string_simple");
983    }
984
985    #[traced_test]
986    async fn test_parse_as_string_unrecognized() {
987        trace!("Entering test_parse_as_string_unrecognized");
988        let val = serde_json::Value::String("NotARealVariant".to_string());
989        let parsed = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&val);
990        match parsed {
991            Ok(variant) => {
992                error!("Unexpectedly parsed variant: {:?}", variant);
993                panic!("test_parse_as_string_unrecognized failed => expected Err, got Ok");
994            }
995            Err(e) => {
996                info!("Got expected error for unrecognized variant => {:?}", e);
997                // We expect an error because the variant is unknown
998            }
999        }
1000        trace!("Exiting test_parse_as_string_unrecognized");
1001    }
1002
1003    #[traced_test]
1004    async fn test_parse_missing_variant_name() {
1005        trace!("Entering test_parse_missing_variant_name");
1006        // No recognized variant key, no "variant_name"
1007        let val = json!({"some_key": true});
1008        let parsed = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&val);
1009        match parsed {
1010            Ok(variant) => {
1011                error!("Unexpectedly parsed => {:?}", variant);
1012                panic!("test_parse_missing_variant_name failed => expected Err, got Ok");
1013            }
1014            Err(e) => {
1015                info!("Got expected error => {:?}", e);
1016                // Missing 'variant_name' => FuzzyFromJsonValueError::MissingField
1017            }
1018        }
1019        trace!("Exiting test_parse_missing_variant_name");
1020    }
1021
1022    #[traced_test]
1023    async fn test_parse_single_variant_key_always_dispatch() {
1024        trace!("Entering test_parse_single_variant_key_always_dispatch");
1025        // shape => { "AlwaysDispatch": { "variant_confidence":0.95, ... }, "other_info": 123 }
1026        let val = json!({
1027            "AlwaysDispatch": {
1028                "variant_confidence": 0.95,
1029                "variant_justification": "Direct dispatch usage"
1030            },
1031            "some_other_field": 123
1032        });
1033        let parsed = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&val);
1034        match parsed {
1035            Ok(variant) => {
1036                debug!("Parsed single-key variant => {:?}", variant);
1037                assert!(matches!(variant, JustifiedTreeExpansionPolicy::AlwaysDispatch { .. }));
1038            }
1039            Err(e) => {
1040                error!("Expected success => got error: {:?}", e);
1041                panic!("test_parse_single_variant_key_always_dispatch => expected Ok, got Err");
1042            }
1043        }
1044        trace!("Exiting test_parse_single_variant_key_always_dispatch");
1045    }
1046
1047    #[traced_test]
1048    async fn test_parse_depth_based_object() {
1049        trace!("Entering test_parse_depth_based_object");
1050        // shape => { "variant_name": "DepthBased", "aggregator_start_level": 2, ... }
1051        let val = json!({
1052            "variant_name": "DepthBased",
1053            "aggregator_start_level": 2,
1054            "leaf_start_level": 5
1055        });
1056        let parsed = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&val);
1057        match parsed {
1058            Ok(variant) => {
1059                debug!("Parsed DepthBased => {:?}", variant);
1060                if let JustifiedTreeExpansionPolicy::DepthBased { field_0, .. } = variant {
1061                    assert_eq!(*field_0.aggregator_start_level(), 2);
1062                    assert_eq!(*field_0.leaf_start_level(), 5);
1063                } else {
1064                    panic!("Expected DepthBased => got {:?}", variant);
1065                }
1066            }
1067            Err(e) => {
1068                error!("Expected success => got error: {:?}", e);
1069                panic!("test_parse_depth_based_object => expected Ok, got Err");
1070            }
1071        }
1072        trace!("Exiting test_parse_depth_based_object");
1073    }
1074
1075    #[traced_test]
1076    async fn test_parse_phased() {
1077        trace!("Entering test_parse_phased");
1078        // shape => { "Phased": { "phases": [ { "start_level": 0, "aggregator_weight":1.0 }, ... ] }, "variant_confidence":0.8 }
1079        let val = json!({
1080            "Phased": {
1081                "phases": [
1082                    { "start_level": 0, "aggregator_weight": 1.0, "dispatch_weight": 0.0, "leaf_weight": 0.0 }
1083                ]
1084            },
1085            "variant_confidence": 0.8
1086        });
1087        let parsed = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&val);
1088        match parsed {
1089            Ok(variant) => {
1090                info!("Parsed phased => {:?}", variant);
1091                if let JustifiedTreeExpansionPolicy::Phased { field_0, variant_confidence, .. } = variant {
1092                    assert_eq!(variant_confidence, 0.8);
1093                    assert_eq!(field_0.phases().len(), 1);
1094                    assert_eq!(*field_0.phases()[0].aggregator_weight(), 1.0);
1095                } else {
1096                    panic!("Expected Phased => got {:?}", variant);
1097                }
1098            }
1099            Err(e) => {
1100                error!("Expected success => got error: {:?}", e);
1101                panic!("test_parse_phased => expected Ok, got Err");
1102            }
1103        }
1104        trace!("Exiting test_parse_phased");
1105    }
1106
1107    #[traced_test]
1108    async fn test_parse_real_json_snippet() {
1109        trace!("Entering test_parse_real_json_snippet");
1110        // The JSON snippet the user specifically gave us that was failing earlier
1111        let snippet = r#"
1112        {
1113          "DepthBased": {
1114              "field_0": {
1115                  "aggregator_start_level": 2,
1116                  "aggregator_start_level_confidence": 0.8,
1117                  "aggregator_start_level_justification": "Aggregator usage begins at level 2 to consolidate intermediate techniques of fire-environment-qigong.",
1118                  "leaf_start_level": 5,
1119                  "leaf_start_level_confidence": 0.9,
1120                  "leaf_start_level_justification": "Leaves begin at level 5, ensuring enough aggregator expansions before final specialized nodes."
1121              },
1122              "field_0_confidence": 0.8,
1123              "field_0_justification": "This structured approach fosters progressive complexity and a clear delineation between preliminary, aggregated, and final specialized nodes."
1124          },
1125          "variant_confidence": 0.9,
1126          "variant_justification": "A depth-based approach cleanly partitions the tree into early dispatch, mid aggregator, and final leaf expansions, fitting a layered qigong system."
1127        }
1128        "#;
1129        let json_val: serde_json::Value = serde_json::from_str(snippet)
1130            .expect("Real snippet must be valid JSON");
1131        let parsed = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&json_val);
1132        match parsed {
1133            Ok(variant) => {
1134                info!("Successfully parsed real snippet => {:?}", variant);
1135                if let JustifiedTreeExpansionPolicy::DepthBased { field_0, variant_confidence, variant_justification, .. } = variant {
1136                    debug!("DepthBased subfields => aggregator_start_level={}, leaf_start_level={}",
1137                           field_0.aggregator_start_level(), field_0.leaf_start_level());
1138                    assert_eq!(variant_confidence, 0.9);
1139                    assert!(variant_justification.contains("cleanly partitions the tree"));
1140                    assert_eq!(*field_0.aggregator_start_level(), 2);
1141                    assert_eq!(*field_0.leaf_start_level(), 5);
1142                } else {
1143                    panic!("Expected DepthBased variant => got {:?}", variant);
1144                }
1145            }
1146            Err(e) => {
1147                error!("Failed to parse the real JSON snippet => {:?}", e);
1148                panic!("test_parse_real_json_snippet => expected Ok, got Err");
1149            }
1150        }
1151        trace!("Exiting test_parse_real_json_snippet");
1152    }
1153
1154    ////////////////////////////////////////////////////////////////////////
1155    // 1) Testing Single-Key Object Form for ConfigurationComplexity
1156    ////////////////////////////////////////////////////////////////////////
1157
1158    #[test]
1159    fn test_single_key_complexity_balanced() {
1160        // JSON: { "Balanced": { "variant_confidence": 0.88, "variant_justification": "Reason" } }
1161        let input = json!({
1162            "Balanced": {
1163                "variant_confidence": 0.88,
1164                "variant_justification": "We want a balanced approach"
1165            }
1166        });
1167        let parsed = JustifiedConfigurationComplexity::fuzzy_from_json_value(&input);
1168        assert!(
1169            parsed.is_ok(),
1170            "Expected Balanced variant single-key parse => got error: {:?}",
1171            parsed.err()
1172        );
1173        let c = parsed.unwrap();
1174        match c {
1175            JustifiedConfigurationComplexity::Balanced {
1176                variant_confidence,
1177                variant_justification,
1178            } => {
1179                assert!((variant_confidence - 0.88).abs() < 1e-6);
1180                assert_eq!(
1181                    variant_justification,
1182                    "We want a balanced approach"
1183                );
1184            }
1185            other => panic!("Expected Balanced => got {:?}", other),
1186        }
1187    }
1188
1189    #[test]
1190    fn test_single_key_complexity_simple_empty_obj() {
1191        // JSON: { "Simple": {} }
1192        let input = json!({ "Simple": {} });
1193        let parsed = JustifiedConfigurationComplexity::fuzzy_from_json_value(&input);
1194        assert!(
1195            parsed.is_ok(),
1196            "Expected single-key object => 'Simple': got error: {:?}",
1197            parsed.err()
1198        );
1199        let c = parsed.unwrap();
1200        match c {
1201            JustifiedConfigurationComplexity::Simple { variant_confidence, variant_justification } => {
1202                assert_eq!(variant_confidence, 0.0);
1203                assert!(variant_justification.is_empty());
1204            }
1205            other => panic!("Expected Simple => got {:?}", other),
1206        }
1207    }
1208
1209    #[test]
1210    fn test_single_key_complexity_unknown() {
1211        // JSON: { "FooBar": {} }
1212        // Should fail because "FooBar" is not recognized (Simple,Balanced,Complex).
1213        let input = json!({ "FooBar": {} });
1214        let parsed = JustifiedConfigurationComplexity::fuzzy_from_json_value(&input);
1215        assert!(
1216            parsed.is_err(),
1217            "Expected error for unknown single-key complexity, got Ok: {:?}",
1218            parsed.ok()
1219        );
1220    }
1221
1222    ////////////////////////////////////////////////////////////////////////
1223    // 2) Testing Single-Key Object Form for CapstoneMode
1224    ////////////////////////////////////////////////////////////////////////
1225
1226    #[test]
1227    fn test_single_key_capstone_single() {
1228        // JSON: { "Single": {} }
1229        let input = json!({ 
1230            "Single": { 
1231                "variant_confidence": 0.92, 
1232                "variant_justification": "Only one capstone leaf needed"
1233            } 
1234        });
1235        let parsed = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
1236        assert!(parsed.is_ok(), "Failed to parse single-key 'Single'");
1237        let mode = parsed.unwrap();
1238        match mode {
1239            JustifiedCapstoneMode::Single { variant_confidence, variant_justification } => {
1240                assert!((variant_confidence - 0.92).abs() < 1e-6);
1241                assert_eq!(variant_justification, "Only one capstone leaf needed");
1242            }
1243            _ => panic!("Expected Single capstone variant"),
1244        }
1245    }
1246
1247    #[test]
1248    fn test_single_key_capstone_probabilistic_empty_obj() {
1249        // JSON: { "Probabilistic": {} }
1250        // no variant_confidence => default 0
1251        let input = json!({ "Probabilistic": {} });
1252        let parsed = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
1253        assert!(parsed.is_ok(), "single-key 'Probabilistic': got error: {:?}", parsed.err());
1254        let mode = parsed.unwrap();
1255        match mode {
1256            JustifiedCapstoneMode::Probabilistic {
1257                variant_confidence, variant_justification
1258            } => {
1259                assert_eq!(variant_confidence, 0.0);
1260                assert_eq!(variant_justification, "");
1261            }
1262            other => panic!("Expected Probabilistic => got {:?}", other),
1263        }
1264    }
1265
1266    #[test]
1267    fn test_single_key_capstone_off_with_fields() {
1268        // JSON: { "Off": { "variant_confidence": 0.5, "variant_justification": "No capstones" } }
1269        let input = json!({
1270            "Off": {
1271                "variant_confidence": 0.5,
1272                "variant_justification": "No capstones"
1273            }
1274        });
1275        let parsed = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
1276        assert!(parsed.is_ok(), "Expected 'Off' => got error: {:?}", parsed.err());
1277        let mode = parsed.unwrap();
1278        match mode {
1279            JustifiedCapstoneMode::Off { variant_confidence, variant_justification } => {
1280                assert!((variant_confidence - 0.5).abs() < 1e-6);
1281                assert_eq!(variant_justification, "No capstones");
1282            }
1283            other => panic!("Expected Off => got {:?}", other),
1284        }
1285    }
1286
1287    ////////////////////////////////////////////////////////////////////////
1288    // 3) Testing Array-Based Single Recognized Key for TreeExpansionPolicy
1289    ////////////////////////////////////////////////////////////////////////
1290    //    e.g.: "WeightedWithLimits": [
1291    //      { "aggregator_weight": 0.3, ... },
1292    //      0.9,
1293    //      "We use WeightedWithLimits for reason..."
1294    //    ]
1295    ////////////////////////////////////////////////////////////////////////
1296
1297    #[test]
1298    fn test_array_form_weighted_with_limits_full() {
1299        // shape => {
1300        //   "WeightedWithLimits": [
1301        //       { "aggregator_weight":0.3, "dispatch_weight":0.5, "leaf_holder_weight":0.2 },
1302        //       0.9,
1303        //       "Balanced approach with depth constraints"
1304        //   ]
1305        // }
1306        let input = json!({
1307            "WeightedWithLimits": [
1308                {
1309                    "aggregator_weight": 0.3,
1310                    "dispatch_weight": 0.5,
1311                    "leaf_holder_weight": 0.2,
1312                    "aggregator_max_depth": 3
1313                },
1314                0.9,
1315                "Balanced approach with depth constraints"
1316            ]
1317        });
1318        let parsed = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&input);
1319        assert!(parsed.is_ok(), "Expected WeightedWithLimits array form => got error: {:?}", parsed.err());
1320        let pol = parsed.unwrap();
1321        match pol {
1322            JustifiedTreeExpansionPolicy::WeightedWithLimits {
1323                field_0,
1324                variant_confidence,
1325                variant_justification,
1326                ..
1327            } => {
1328                assert!((variant_confidence - 0.9).abs() < 1e-6);
1329                assert_eq!(
1330                    variant_justification,
1331                    "Balanced approach with depth constraints"
1332                );
1333                assert_eq!(*field_0.aggregator_weight(), 0.3);
1334                assert_eq!(*field_0.dispatch_weight(), 0.5);
1335                assert_eq!(*field_0.leaf_holder_weight(), 0.2);
1336                assert_eq!(*field_0.aggregator_max_depth(), Some(3));
1337            }
1338            other => panic!("Expected WeightedWithLimits => got {:?}", other),
1339        }
1340    }
1341
1342    #[test]
1343    fn test_array_form_weighted_conf_only() {
1344        // shape => { "Weighted": [ { aggregator_weight:0.4, dispatch_weight:0.3, leaf_holder_weight:0.3 }, 0.75 ] }
1345        // no justification
1346        let input = json!({
1347            "Weighted": [
1348                {
1349                    "aggregator_weight": 0.4,
1350                    "dispatch_weight": 0.3,
1351                    "leaf_holder_weight": 0.3
1352                },
1353                0.75
1354            ]
1355        });
1356        let parsed = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&input);
1357        assert!(parsed.is_ok(), "Expected Weighted => got error: {:?}", parsed.err());
1358        let pol = parsed.unwrap();
1359        match pol {
1360            JustifiedTreeExpansionPolicy::Weighted {
1361                variant_confidence,
1362                variant_justification,
1363                field_0,
1364                ..
1365            } => {
1366                assert!((variant_confidence - 0.75).abs() < 1e-6);
1367                assert!(variant_justification.is_empty());
1368                assert_eq!(*field_0.aggregator_weight(), 0.4);
1369                assert_eq!(*field_0.dispatch_weight(), 0.3);
1370                assert_eq!(*field_0.leaf_holder_weight(), 0.3);
1371            }
1372            other => panic!("Expected Weighted => got {:?}", other),
1373        }
1374    }
1375
1376    #[test]
1377    fn test_array_form_weighted_unknown_third_element() {
1378        // shape => { "Weighted": [ { aggregator_weight:0.4 }, 0.75, 999 ] }
1379        // We'll interpret 999 as 'variant_justification'? => but it's not a string => so we default it to empty.
1380        let input = json!({
1381            "Weighted": [
1382                { "aggregator_weight": 0.4, "dispatch_weight": 0.4, "leaf_holder_weight": 0.2 },
1383                0.75,
1384                999
1385            ]
1386        });
1387        let parsed = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&input);
1388        assert!(parsed.is_ok(), "Should parse ignoring the non-string justification");
1389        let pol = parsed.unwrap();
1390        match pol {
1391            JustifiedTreeExpansionPolicy::Weighted {
1392                variant_confidence,
1393                variant_justification,
1394                field_0,
1395                ..
1396            } => {
1397                assert!((variant_confidence - 0.75).abs() < 1e-6);
1398                assert!(variant_justification.is_empty(), "Non-string => default empty");
1399                assert_eq!(*field_0.aggregator_weight(), 0.4);
1400            }
1401            other => panic!("Expected Weighted => got {:?}", other),
1402        }
1403    }
1404
1405    #[test]
1406    fn test_single_key_is_number_for_variant_conf() {
1407        // shape => { "Phased": 0.8 }
1408        // => interpret as => variant_name="Phased", variant_confidence=0.8, default subobject empty
1409        let input = json!({
1410            "Phased": 0.8
1411        });
1412        let parsed = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(&input);
1413        assert!(parsed.is_ok(), "Should parse => variant_confidence=0.8, no phases");
1414        let pol = parsed.unwrap();
1415        match pol {
1416            JustifiedTreeExpansionPolicy::Phased {
1417                variant_confidence,
1418                field_0,
1419                ..
1420            } => {
1421                assert!((variant_confidence - 0.8).abs() < 1e-6);
1422                assert!(field_0.phases().is_empty(), "No phases => empty by default");
1423            }
1424            other => panic!("Expected Phased => got {:?}", other),
1425        }
1426    }
1427
1428    ////////////////////////////////////////////////////////////////////////
1429    // 4) Integration: top-level JustifiedGrowerTreeConfiguration snippet
1430    ////////////////////////////////////////////////////////////////////////
1431
1432    /// This test simulates a top-level snippet with these new shapes:
1433    /// - complexity => { "Balanced": {...} }
1434    /// - capstone => { "Single": {} }
1435    /// - tree_expansion_policy => { "WeightedWithLimits": [ { ... }, 0.9, "...just..." ] }
1436    #[test]
1437    fn test_integration_jcfg_with_single_key_and_array_forms() {
1438        let input = json!({
1439            "fields": {
1440                "depth": 4,
1441                "breadth": 7,
1442                "density": 9,
1443                "complexity": { 
1444                    "Balanced": {
1445                        "variant_confidence": 0.8,
1446                        "variant_justification": "Balanced complexity for mid-level judo"
1447                    }
1448                },
1449                "capstone": {
1450                    "mode": {
1451                        "Single": {
1452                            "variant_confidence": 0.95,
1453                            "variant_justification": "Just one final pinnacle skill"
1454                        }
1455                    }
1456                },
1457                "tree_expansion_policy": {
1458                    "WeightedWithLimits": [
1459                        {
1460                            "aggregator_weight": 0.3,
1461                            "dispatch_weight": 0.4,
1462                            "leaf_holder_weight": 0.3
1463                        },
1464                        0.88,
1465                        "We impose limits to keep aggregator usage moderate"
1466                    ]
1467                }
1468            }
1469        });
1470
1471        let parsed = JustifiedGrowerTreeConfiguration::fuzzy_from_json_value(&input);
1472        assert!(parsed.is_ok(), "Should parse top-level snippet => got error: {:?}", parsed.err());
1473        let cfg = parsed.unwrap();
1474
1475        // Check fields
1476        assert_eq!(*cfg.depth(), 4);
1477        assert_eq!(*cfg.breadth(), 7);
1478        assert_eq!(*cfg.density(), 9);
1479
1480        // complexity => Balanced
1481        match cfg.complexity() {
1482            JustifiedConfigurationComplexity::Balanced {
1483                variant_confidence,
1484                variant_justification,
1485            } => {
1486                assert!((variant_confidence - 0.8).abs() < 1e-6);
1487                assert_eq!(variant_justification, "Balanced complexity for mid-level judo");
1488            }
1489            other => panic!("Expected Balanced => got {:?}", other),
1490        }
1491
1492        // capstone => check mode Single
1493        match cfg.capstone() {
1494            Some(cap) => match cap.mode() {
1495                JustifiedCapstoneMode::Single {
1496                    variant_confidence,
1497                    variant_justification,
1498                } => {
1499                    assert!((variant_confidence - 0.95).abs() < 1e-6);
1500                    assert_eq!(variant_justification, "Just one final pinnacle skill");
1501                }
1502                other => panic!("Expected Single => got {:?}", other),
1503            },
1504            None => panic!("capstone missing => fail"),
1505        }
1506
1507        // expansion policy => WeightedWithLimits
1508        match cfg.tree_expansion_policy() {
1509            JustifiedTreeExpansionPolicy::WeightedWithLimits {
1510                field_0,
1511                variant_confidence,
1512                variant_justification,
1513                ..
1514            } => {
1515                assert!((variant_confidence - 0.88).abs() < 1e-6);
1516                assert_eq!(
1517                    variant_justification,
1518                    "We impose limits to keep aggregator usage moderate"
1519                );
1520                assert_eq!(*field_0.aggregator_weight(), 0.3);
1521                assert_eq!(*field_0.dispatch_weight(), 0.4);
1522                assert_eq!(*field_0.leaf_holder_weight(), 0.3);
1523            }
1524            other => panic!("Expected WeightedWithLimits => got {:?}", other),
1525        }
1526    }
1527}