capability_grower_configuration/
grower_tree_configuration.rs

1// ---------------- [ File: capability-grower-configuration/src/grower_tree_configuration.rs ]
2crate::ix!();
3
4/// We set the GrowerTreeConfiguration to refine the shape of our model trees:
5/// - **Depth**: # of hierarchical levels (≥1)
6/// - **Breadth**: baseline sibling count (≥1)
7/// - **Density**: variants at leaves (≥1)
8/// - **leaf_granularity**: fraction [0..1] controlling fine detail at leaves
9/// - **balance_symmetry**: fraction [0..1] controlling symmetrical vs. varied structure
10/// - **complexity**: overall complexity level (simple, balanced, or complex)
11///
12/// Optional advanced sub-structs:
13/// - **level_specific**: arrays of breadth/density overrides for each level
14/// - **weighted_branching**: mean ± variance approach
15/// - **level_skipping**: per-level leaf probabilities
16/// - **capstone**: highlight nodes (off, single, or fraction)
17/// - **ordering**: sub-branch sorting approach
18/// - **ai_confidence**: modifies branch factor based on AI certainty
19///
20/// New fields:
21/// - **aggregator_preference**: fraction [0..1] controlling how often aggregator is used vs. dispatch
22/// - **allow_early_leaves**: if true, allows leaf nodes to appear before the final depth
23/// - **partial_subbranch_probability**: fraction [0..1] controlling how often each child sub-branch is included
24/// - **tree_expansion_policy**: advanced logic for picking among aggregator/dispatch/leaf
25/// - **aggregator_depth_limit**, **dispatch_depth_limit**, **leaf_min_depth**: optional constraints on usage of aggregator, dispatch, or leaf nodes by depth
26///
27/// Your justification must indicate why each justified parameter value was chosen deliberately by
28/// using knowledge and an understanding of the target domain as a guide. Since each target domain
29/// is different, the trees which map them will have different shapes and different
30/// GrowerTreeConfiguration parameters. Your job during justification is to explain clearly why
31/// a given parameter setting makes sense in the provided target domain.
32#[derive(
33    SaveLoad,
34    Debug,
35    Clone,
36    PartialEq,
37    Getters,
38    Builder,
39    Serialize,
40    Deserialize,
41    AiJsonTemplate,
42    AiJsonTemplateWithJustification,
43)]
44#[builder(pattern = "owned", setter(into), build_fn(name = "try_build"))]
45#[getset(get = "pub")]
46pub struct GrowerTreeConfiguration {
47
48    /// This field holds *verbatim* the lower-kebab-case target-name belonging to the tree grow process.
49    #[builder(default = "\"target\".to_string()")]
50    #[serde(default)]
51    target_name: String,
52
53    /// Set this integer to define how many levels exist from root to leaf in the tree (≥5).
54    /// - Setting a higher number → deeper specialization (depth).
55    /// - Setting a lower number → simpler, more general modeling.
56    #[builder(default = "4")]
57    #[serde(deserialize_with = "fuzzy_u8")]
58    depth: u8,
59
60    /// Set this integer to define how many siblings or sub-branches each node has by default (≥7).
61    /// - Higher → broad coverage, e.g. "Spread (Loosen)."
62    /// - Lower → narrower, e.g. "Narrow (Tighten)."
63    #[builder(default = "2")]
64    #[serde(deserialize_with = "fuzzy_u8")]
65    breadth: u8,
66
67    /// Set this integer to define how many variants (children) each leaf node holds by default (≥9).
68    /// - Higher → more fullness ("Dense").
69    /// - Lower → sparser representation ("Sparse").
70    #[builder(default = "2")]
71    #[serde(deserialize_with = "fuzzy_u8")]
72    density: u8,
73
74    /// Set this fraction [0..1] to specify how finely detailed each leaf item becomes.
75    /// - 0 = coarse (broad, less specialized leaves),
76    /// - 1 = extremely specific leaves.
77    #[builder(default = "0.5")]
78    leaf_granularity: f32,
79
80    /// Set this fraction [0..1] to numerically characerize the symmetrical vs. unbalanced structure of our tree.
81    /// - 0.0 = random/unbalanced,
82    /// - 1.0 = perfectly balanced.
83    #[builder(default = "0.5")]
84    balance_symmetry: f32,
85
86    /// Set this field to select between "simple", "balanced", or "complex" for overall usage patterns.
87    #[builder(default)]
88    complexity: ConfigurationComplexity,
89
90    /// Set this optional sub-struct to specify the arrays for per-level breadth & density overrides.
91    #[builder(default)]
92    level_specific: Option<TreeLevelSpecificConfiguration>,
93
94    /// Set this optional sub-struct to define mean ± variance if you want random branching factors.
95    #[builder(default)]
96    weighted_branching: Option<WeightedBranchingConfiguration>,
97
98    /// Set this optional sub-struct to define probabilities for skipping deeper expansions at certain levels.
99    #[builder(default)]
100    level_skipping: Option<LevelSkippingConfiguration>,
101
102    /// Set this optional sub-struct to define the characteristics of capstone leaves, how many of
103    /// them there are, and our chances of encountering one at any given level.
104    #[builder(default)]
105    capstone: Option<CapstoneGenerationConfiguration>,
106
107    /// Set this optional sub-struct to define how sub-branches get ordered (alphabetical, difficulty, random).
108    #[builder(default)]
109    ordering: Option<SubBranchOrdering>,
110
111    /// Set this optional sub-struct to define how AI confidence modifies branch factor (if any).
112    #[builder(default)]
113    ai_confidence: Option<AiTreeBranchingConfidenceConfiguration>,
114
115    /// Set this fraction [0..1] to control how often aggregator nodes are used vs. dispatch nodes at
116    /// intermediate levels. 
117    /// - 0.0 => always dispatch 
118    /// - 1.0 => always aggregator 
119    /// - 0.5 => half aggregator, half dispatch
120    /// 
121    /// This is a simplistic alternative to `tree_expansion_policy`. If tree_expansion_policy is set to something
122    /// more advanced, this field might be ignored or used as a fallback.
123    #[builder(default = "0.5")]
124    aggregator_preference: f32,
125
126    /// If true, leaf nodes may appear before the final `depth`, i.e. some sub-branches might terminate
127    /// early with a LeafHolder. 
128    /// If false, all leaves appear exactly at `depth`.
129    #[builder(default = "false")]
130    allow_early_leaves: bool,
131
132    /// Fraction [0..1] controlling how often each potential child sub-branch is included.
133    /// - 0.0 => no optional sub-branches 
134    /// - 1.0 => all sub-branches are included
135    /// 
136    /// This might tie into `ChildSpec` `optional` or `probability` fields, e.g. randomizing 
137    /// which children appear. 
138    #[builder(default = "1.0")]
139    partial_subbranch_probability: f32,
140
141    /// An advanced enum describing how to pick among `Dispatch`, `Aggregate`, or `LeafHolder`
142    /// at each level. 
143    /// - If you set this to `NodeVariantStrategy::Simple`, you get a straightforward approach
144    ///   where we do dispatch at intermediate nodes and leaves at final depth.
145    /// - If you use `Weighted`, you can do random picks among aggregator/dispatch/leaf.
146    /// - If you do `DepthBased`, you can specify exactly which level to start aggregator vs. leaf.
147    #[builder(default)]
148    tree_expansion_policy: TreeExpansionPolicy,
149
150    /// If set, aggregator nodes will not appear deeper than this level. 
151    /// If `None`, no limit. 
152    /// If `Some(n)`, aggregator variant is disallowed for levels > n.
153    #[builder(default)]
154    #[serde(deserialize_with = "fuzzy_option_u8")]
155    #[serde(default)]
156    aggregator_depth_limit: Option<u8>,
157
158    /// If set, dispatch nodes will not appear deeper than this level.
159    /// If `None`, no limit.
160    /// If `Some(n)`, dispatch variant is disallowed for levels > n.
161    #[builder(default)]
162    #[serde(deserialize_with = "fuzzy_option_u8")]
163    #[serde(default)]
164    dispatch_depth_limit: Option<u8>,
165
166    /// If set, leaf-holder nodes cannot appear before this level (forces deeper expansions).
167    /// If `None`, no minimum.
168    /// If `Some(n)`, we skip leaf-holder variants until level ≥ n.
169    #[builder(default)]
170    #[serde(deserialize_with = "fuzzy_option_u8")]
171    #[serde(default)]
172    leaf_min_depth: Option<u8>,
173}
174
175impl Default for GrowerTreeConfiguration {
176    fn default() -> Self {
177        GrowerTreeConfigurationBuilder::default()
178            .build()
179            .unwrap()
180    }
181}
182
183impl GrowerTreeConfigurationBuilder {
184    /// Consume the builder, construct the config, then validate it before returning.
185    pub fn build(
186        self,
187    ) -> Result<GrowerTreeConfiguration, GrowerTreeConfigurationError> {
188
189        // First, use the generated try_build (which checks for missing fields)
190        //
191        // IMPORTANT: this cannot be set to `build` or we will get a hidden recusion
192        let config = self.try_build()?;
193        // Then validate all semantic constraints
194        config.validate()?;
195        Ok(config)
196    }
197}
198
199impl GrowerTreeConfiguration {
200
201    /// This function should validate all fields: baseline parameters and each advanced sub-struct if present.
202    /// Returns an error if any invalid configuration is detected.
203    #[instrument(level = "trace", skip(self))]
204    pub fn validate(&self) -> Result<(), GrowerTreeConfigurationError> {
205        if self.depth == 0 {
206            return Err(GrowerTreeConfigurationError::DepthMustBeAtLeastOne);
207        }
208        if self.breadth == 0 {
209            return Err(GrowerTreeConfigurationError::BreadthMustBeAtLeastOne);
210        }
211        if self.density == 0 {
212            return Err(GrowerTreeConfigurationError::DensityMustBeAtLeastOne);
213        }
214
215        if !(0.0..=1.0).contains(&self.leaf_granularity) {
216            return Err(GrowerTreeConfigurationError::LeafGranularityMustBeWithinZeroOne);
217        }
218        if !(0.0..=1.0).contains(&self.balance_symmetry) {
219            return Err(GrowerTreeConfigurationError::BalanceSymmetryMustBeWithinZeroOne);
220        }
221
222        if let Some(ref ls) = self.level_specific {
223            ls.validate(self.depth)?;
224        }
225        if let Some(ref wb) = self.weighted_branching {
226            wb.validate()?;
227        }
228        if let Some(ref skip) = self.level_skipping {
229            skip.validate(self.depth)?;
230        }
231        if let Some(ref cap) = self.capstone {
232            cap.validate()?;
233        }
234        if let Some(ref ai_conf) = self.ai_confidence {
235            ai_conf.validate()?;
236        }
237
238        Ok(())
239    }
240
241    /// This function should parse a `GrowerTreeConfiguration` from TOML string input.
242    #[instrument(level = "trace")]
243    pub fn from_toml_str(s: &str) -> Result<Self, GrowerTreeConfigurationError> {
244        let cfg: GrowerTreeConfiguration = toml::from_str(s)
245            .map_err(GrowerTreeConfigurationError::TomlDeError)?;
246        Ok(cfg)
247    }
248
249    /// This function should convert the `GrowerTreeConfiguration` into a pretty TOML string.
250    #[instrument(level = "trace", skip(self))]
251    pub fn to_toml_string(&self) -> Result<String, GrowerTreeConfigurationError> {
252        let out = toml::to_string_pretty(self)
253            .map_err(GrowerTreeConfigurationError::TomlSerError)?;
254        Ok(out)
255    }
256
257    /// This function should load a `GrowerTreeConfiguration` from a TOML file on disk.
258    #[instrument(level = "trace", skip(path))]
259    pub fn from_toml_file<P: AsRef<Path>>(path: P) -> Result<Self, GrowerTreeConfigurationError> {
260        let contents = std::fs::read_to_string(path)
261            .map_err(GrowerTreeConfigurationError::IoError)?;
262        Self::from_toml_str(&contents)
263    }
264
265    /// This function should write the `GrowerTreeConfiguration` to a file in TOML format.
266    #[instrument(level = "trace", skip(self, path))]
267    pub fn to_toml_file<P: AsRef<Path>>(&self, path: P) -> Result<(), GrowerTreeConfigurationError> {
268        let data = self.to_toml_string()?;
269        std::fs::write(path, data).map_err(GrowerTreeConfigurationError::IoError)?;
270        Ok(())
271    }
272}
273
274impl FuzzyFromJsonValue for JustifiedGrowerTreeConfiguration {
275    fn fuzzy_from_json_value(value: &serde_json::Value) -> Result<Self, FuzzyFromJsonValueError> {
276        trace!("(JustifiedGrowerTreeConfiguration) Entering fuzzy_from_json_value");
277
278        // Must be an object => flatten deeply
279        let mut obj = match value.as_object() {
280            Some(m) => {
281                trace!("(JustifiedGrowerTreeConfiguration) Received JSON object => BFS-flatten all 'fields'.");
282                let mut cloned = m.clone();
283                flatten_all_fields(&mut cloned);
284                cloned
285            }
286            None => {
287                error!("(JustifiedGrowerTreeConfiguration) Received non-object => cannot parse!");
288                return Err(FuzzyFromJsonValueError::NotAnObject {
289                    target_type: "JustifiedGrowerTreeConfiguration",
290                    actual: value.clone(),
291                });
292            }
293        };
294
295        let target_name_val = if let Some(raw) = obj.get("target_name") {
296            raw.to_string().to_kebab_case()
297        } else {
298            warn!("(JustifiedGrowerTreeConfiguration) 'target-name' missing => will default to target-name-unset");
299            "target-name-unset".to_string()
300        };
301
302        let target_name_conf = get_f64_field(&obj, "target_name_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
303        let target_name_just = get_string_field(&obj, "target_name_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
304
305        // 1) depth => fuzzy_u8
306        let depth_val = if let Some(raw) = obj.get("depth") {
307            match fuzzy_u8_from_value(raw) {
308                Ok(u) => u,
309                Err(e) => {
310                    error!("(JustifiedGrowerTreeConfiguration) parse 'depth' => {}", e);
311                    return Err(FuzzyFromJsonValueError::Other {
312                        target_type: "JustifiedGrowerTreeConfiguration",
313                        detail: format!("Cannot parse depth: {}", e),
314                    });
315                }
316            }
317        } else {
318            debug!("(JustifiedGrowerTreeConfiguration) 'depth' missing => default 0");
319            0
320        };
321        let depth_conf = get_f64_field(&obj, "depth_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
322        let depth_just = get_string_field(&obj, "depth_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
323
324        // 2) breadth => fuzzy_u8
325        let breadth_val = if let Some(raw) = obj.get("breadth") {
326            match fuzzy_u8_from_value(raw) {
327                Ok(u) => u,
328                Err(e) => {
329                    error!("(JustifiedGrowerTreeConfiguration) parse 'breadth' => {}", e);
330                    return Err(FuzzyFromJsonValueError::Other {
331                        target_type: "JustifiedGrowerTreeConfiguration",
332                        detail: format!("Cannot parse breadth: {}", e),
333                    });
334                }
335            }
336        } else {
337            debug!("(JustifiedGrowerTreeConfiguration) 'breadth' missing => default 0");
338            0
339        };
340        let breadth_conf = get_f64_field(&obj, "breadth_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
341        let breadth_just = get_string_field(&obj, "breadth_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
342
343        // 3) density => fuzzy_u8
344        let density_val = if let Some(raw) = obj.get("density") {
345            match fuzzy_u8_from_value(raw) {
346                Ok(u) => u,
347                Err(e) => {
348                    error!("(JustifiedGrowerTreeConfiguration) parse 'density' => {}", e);
349                    return Err(FuzzyFromJsonValueError::Other {
350                        target_type: "JustifiedGrowerTreeConfiguration",
351                        detail: format!("Cannot parse density: {}", e),
352                    });
353                }
354            }
355        } else {
356            debug!("(JustifiedGrowerTreeConfiguration) 'density' missing => default 0");
357            0
358        };
359        let density_conf = get_f64_field(&obj, "density_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
360        let density_just = get_string_field(&obj, "density_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
361
362        // leaf_granularity => f32
363        let lg_f64 = get_f64_field(&obj, "leaf_granularity", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
364        let leaf_granularity = lg_f64 as f32;
365        let lg_conf = get_f64_field(&obj, "leaf_granularity_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
366        let lg_just = get_string_field(&obj, "leaf_granularity_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
367
368        // balance_symmetry => f32
369        let bs_f64 = get_f64_field(&obj, "balance_symmetry", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
370        let balance_symmetry = bs_f64 as f32;
371        let bs_conf = get_f64_field(&obj, "balance_symmetry_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
372        let bs_just = get_string_field(&obj, "balance_symmetry_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
373
374        // aggregator_preference => f32
375        let ap_f64 = get_f64_field(&obj, "aggregator_preference", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
376        let aggregator_preference = ap_f64 as f32;
377        let ap_conf = get_f64_field(&obj, "aggregator_preference_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
378        let ap_just = get_string_field(&obj, "aggregator_preference_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
379
380        // allow_early_leaves => bool
381        let allow_early_leaves = match obj.get("allow_early_leaves") {
382            Some(serde_json::Value::Bool(b)) => *b,
383            _ => {
384                debug!("(JustifiedGrowerTreeConfiguration) no 'allow_early_leaves' => false");
385                false
386            }
387        };
388        let ael_conf = get_f64_field(&obj, "allow_early_leaves_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
389        let ael_just = get_string_field(&obj, "allow_early_leaves_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
390
391        // partial_subbranch_probability => f32
392        let psp_f64 = get_f64_field(&obj, "partial_subbranch_probability", "JustifiedGrowerTreeConfiguration").unwrap_or(1.0);
393        let partial_subbranch_probability = psp_f64 as f32;
394        let psp_conf = get_f64_field(&obj, "partial_subbranch_probability_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
395        let psp_just = get_string_field(&obj, "partial_subbranch_probability_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
396
397        // aggregator_depth_limit => fuzzy_option_u8
398        let adl_val = obj.get("aggregator_depth_limit");
399        let aggregator_depth_limit = match adl_val {
400            Some(val) => match fuzzy_option_u8_from_value(val) {
401                Ok(opt) => opt,
402                Err(e) => {
403                    error!("(JustifiedGrowerTreeConfiguration) aggregator_depth_limit parse => {}", e);
404                    return Err(FuzzyFromJsonValueError::Other {
405                        target_type: "JustifiedGrowerTreeConfiguration",
406                        detail: format!("Error aggregator_depth_limit: {:?}", e),
407                    });
408                }
409            },
410            None => None,
411        };
412        let adl_conf = get_f64_field(&obj, "aggregator_depth_limit_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
413        let adl_just = get_string_field(&obj, "aggregator_depth_limit_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
414
415        // dispatch_depth_limit => fuzzy_option_u8
416        let ddl_val = obj.get("dispatch_depth_limit");
417        let dispatch_depth_limit = match ddl_val {
418            Some(val) => match fuzzy_option_u8_from_value(val) {
419                Ok(opt) => opt,
420                Err(e) => {
421                    error!("(JustifiedGrowerTreeConfiguration) dispatch_depth_limit parse => {}", e);
422                    return Err(FuzzyFromJsonValueError::Other {
423                        target_type: "JustifiedGrowerTreeConfiguration",
424                        detail: format!("Error dispatch_depth_limit: {:?}", e),
425                    });
426                }
427            },
428            None => None,
429        };
430        let ddl_conf = get_f64_field(&obj, "dispatch_depth_limit_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
431        let ddl_just = get_string_field(&obj, "dispatch_depth_limit_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
432
433        // leaf_min_depth => fuzzy_option_u8
434        let lmd_val = obj.get("leaf_min_depth");
435        let leaf_min_depth = match lmd_val {
436            Some(val) => match fuzzy_option_u8_from_value(val) {
437                Ok(opt) => opt,
438                Err(e) => {
439                    error!("(JustifiedGrowerTreeConfiguration) leaf_min_depth parse => {}", e);
440                    return Err(FuzzyFromJsonValueError::Other {
441                        target_type: "JustifiedGrowerTreeConfiguration",
442                        detail: format!("Error leaf_min_depth: {:?}", e),
443                    });
444                }
445            },
446            None => None,
447        };
448        let lmd_conf = get_f64_field(&obj, "leaf_min_depth_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
449        let lmd_just = get_string_field(&obj, "leaf_min_depth_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
450
451        // complexity => optional => fallback Balanced if missing
452        let complexity_val = obj.get("complexity");
453        let (complexity, complexity_conf, complexity_just) = match complexity_val {
454            Some(cv) => {
455                let cparsed = JustifiedConfigurationComplexity::fuzzy_from_json_value(cv)?;
456                let cc = get_f64_field(&obj, "complexity_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
457                let cj = get_string_field(&obj, "complexity_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
458                (cparsed, cc, cj)
459            }
460            None => {
461                debug!("(JustifiedGrowerTreeConfiguration) no 'complexity' => default Balanced");
462                let fallback = JustifiedConfigurationComplexity::Balanced {
463                    variant_confidence: 0.0,
464                    variant_justification: "".to_string(),
465                };
466                (fallback, 0.0, "".to_string())
467            }
468        };
469
470        // level_specific => optional
471        let level_spec_parsed = match obj.get("level_specific") {
472            Some(x) => Some(JustifiedTreeLevelSpecificConfiguration::fuzzy_from_json_value(x)?),
473            None => None,
474        };
475        let ls_conf = get_f64_field(&obj, "level_specific_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
476        let ls_just = get_string_field(&obj, "level_specific_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
477
478        // weighted_branching => optional
479        let wb_parsed = match obj.get("weighted_branching") {
480            Some(x) => Some(JustifiedWeightedBranchingConfiguration::fuzzy_from_json_value(x)?),
481            None => None,
482        };
483        let wb_conf = get_f64_field(&obj, "weighted_branching_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
484        let wb_just = get_string_field(&obj, "weighted_branching_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
485
486        // level_skipping => optional
487        let lskip_parsed = match obj.get("level_skipping") {
488            Some(x) => Some(JustifiedLevelSkippingConfiguration::fuzzy_from_json_value(x)?),
489            None => None,
490        };
491        let lskip_conf = get_f64_field(&obj, "level_skipping_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
492        let lskip_just = get_string_field(&obj, "level_skipping_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
493
494        // capstone => optional
495        let cap_parsed = match obj.get("capstone") {
496            Some(x) => Some(JustifiedCapstoneGenerationConfiguration::fuzzy_from_json_value(x)?),
497            None => None,
498        };
499        let cap_conf = get_f64_field(&obj, "capstone_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
500        let cap_just = get_string_field(&obj, "capstone_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
501
502        // ordering => optional
503        let ordering_parsed = match obj.get("ordering") {
504            Some(x) => Some(JustifiedSubBranchOrdering::fuzzy_from_json_value(x)?),
505            None => None,
506        };
507        let ordering_conf = get_f64_field(&obj, "ordering_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
508        let ordering_just = get_string_field(&obj, "ordering_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
509
510        // ai_confidence => optional
511        let ai_conf_parsed = match obj.get("ai_confidence") {
512            Some(x) => Some(JustifiedAiTreeBranchingConfidenceConfiguration::fuzzy_from_json_value(x)?),
513            None => None,
514        };
515        let aic_conf = get_f64_field(&obj, "ai_confidence_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
516        let aic_just = get_string_field(&obj, "ai_confidence_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
517
518        // tree_expansion_policy => optional => fallback "Simple" if missing
519        let tep_val = obj.get("tree_expansion_policy");
520        let (tree_expansion_policy, tep_conf, tep_just) = match tep_val {
521            Some(tv) => {
522                let parsed = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(tv)?;
523                let cf = get_f64_field(&obj, "tree_expansion_policy_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
524                let cj = get_string_field(&obj, "tree_expansion_policy_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
525                (parsed, cf, cj)
526            }
527            None => {
528                debug!("(JustifiedGrowerTreeConfiguration) no 'tree_expansion_policy' => default=>Simple");
529                let fallback = JustifiedTreeExpansionPolicy::Simple {
530                    variant_confidence: 0.0,
531                    variant_justification: "".to_string(),
532                };
533                (fallback, 0.0, "".to_string())
534            }
535        };
536
537        let jcfg = JustifiedGrowerTreeConfiguration {
538            target_name:               target_name_val,
539            target_name_confidence:    target_name_conf,
540            target_name_justification: target_name_just,
541            depth:                     depth_val,
542            depth_confidence:          depth_conf,
543            depth_justification:       depth_just,
544
545            breadth: breadth_val,
546            breadth_confidence: breadth_conf,
547            breadth_justification: breadth_just,
548
549            density: density_val,
550            density_confidence: density_conf,
551            density_justification: density_just,
552
553            leaf_granularity,
554            leaf_granularity_confidence: lg_conf,
555            leaf_granularity_justification: lg_just,
556
557            balance_symmetry,
558            balance_symmetry_confidence: bs_conf,
559            balance_symmetry_justification: bs_just,
560
561            complexity,
562            complexity_confidence: complexity_conf,
563            complexity_justification: complexity_just,
564
565            level_specific: level_spec_parsed,
566            level_specific_confidence: ls_conf,
567            level_specific_justification: ls_just,
568
569            weighted_branching: wb_parsed,
570            weighted_branching_confidence: wb_conf,
571            weighted_branching_justification: wb_just,
572
573            level_skipping: lskip_parsed,
574            level_skipping_confidence: lskip_conf,
575            level_skipping_justification: lskip_just,
576
577            capstone: cap_parsed,
578            capstone_confidence: cap_conf,
579            capstone_justification: cap_just,
580
581            ordering: ordering_parsed,
582            ordering_confidence: ordering_conf,
583            ordering_justification: ordering_just,
584
585            ai_confidence: ai_conf_parsed,
586            ai_confidence_confidence: aic_conf,
587            ai_confidence_justification: aic_just,
588
589            aggregator_preference,
590            aggregator_preference_confidence: ap_conf,
591            aggregator_preference_justification: ap_just,
592
593            allow_early_leaves,
594            allow_early_leaves_confidence: ael_conf,
595            allow_early_leaves_justification: ael_just,
596
597            partial_subbranch_probability,
598            partial_subbranch_probability_confidence: psp_conf,
599            partial_subbranch_probability_justification: psp_just,
600
601            aggregator_depth_limit,
602            aggregator_depth_limit_confidence: adl_conf,
603            aggregator_depth_limit_justification: adl_just,
604
605            dispatch_depth_limit,
606            dispatch_depth_limit_confidence: ddl_conf,
607            dispatch_depth_limit_justification: ddl_just,
608
609            leaf_min_depth,
610            leaf_min_depth_confidence: lmd_conf,
611            leaf_min_depth_justification: lmd_just,
612
613            tree_expansion_policy,
614            tree_expansion_policy_confidence: tep_conf,
615            tree_expansion_policy_justification: tep_just,
616        };
617
618        trace!("(JustifiedGrowerTreeConfiguration) Successfully constructed => returning Ok.");
619        Ok(jcfg)
620    }
621}
622
623#[cfg(test)]
624mod tests {
625    use super::*;
626
627    // ------------------------------------------------------------------------
628    // Sub-structure test modules, verifying each with an exhaustive approach
629    // ------------------------------------------------------------------------
630
631    mod test_weighted_branching {
632        use super::*;
633
634        #[traced_test]
635        fn test_valid() {
636            let wb = WeightedBranchingConfigurationBuilder::default()
637                .mean(4)
638                .variance(3)
639                .build()
640                .unwrap();
641            assert!(wb.validate().is_ok());
642        }
643
644        #[traced_test]
645        fn test_variance_exceeds_mean() {
646            let wb = WeightedBranchingConfigurationBuilder::default()
647                .mean(2)
648                .variance(4)
649                .build()
650                .unwrap();
651            let res = wb.validate();
652            assert!(res.is_err());
653            assert!(matches!(res.err().unwrap(), GrowerTreeConfigurationError::WeightedBranchMeanLessThanVariance));
654        }
655
656        #[traced_test]
657        fn test_mean_zero() {
658            let wb = WeightedBranchingConfigurationBuilder::default()
659                .mean(0)
660                .variance(0)
661                .build()
662                .unwrap();
663            let res = wb.validate();
664            assert!(res.is_err());
665            assert!(matches!(res.err().unwrap(), GrowerTreeConfigurationError::WeightedBranchMeanCannotBeZero));
666        }
667    }
668
669    mod test_level_specific {
670        use super::*;
671
672        #[traced_test]
673        fn test_valid() {
674            let ls = TreeLevelSpecificConfigurationBuilder::default()
675                .breadth_per_level(vec![3,4])
676                .density_per_level(vec![2,5])
677                .build()
678                .unwrap();
679            assert!(ls.validate(3).is_ok());
680        }
681
682        #[traced_test]
683        fn test_breadth_len_exceeds_depth() {
684            let ls = TreeLevelSpecificConfigurationBuilder::default()
685                .breadth_per_level(vec![3,4,5,6])
686                .density_per_level(vec![2])
687                .build()
688                .unwrap();
689            let res = ls.validate(3);
690            assert!(res.is_err());
691            assert!(matches!(res.err().unwrap(), GrowerTreeConfigurationError::LevelSpecificBreadthLenExceedsDepth));
692        }
693    }
694
695    mod test_level_skipping {
696        use super::*;
697
698        #[traced_test]
699        fn test_valid() {
700            let skip = LevelSkippingConfigurationBuilder::default()
701                .leaf_probability_per_level(vec![0.0,0.2,0.4])
702                .build()
703                .unwrap();
704            assert!(skip.validate(3).is_ok());
705        }
706
707        #[traced_test]
708        fn test_probability_exceeds_depth() {
709            let skip = LevelSkippingConfigurationBuilder::default()
710                .leaf_probability_per_level(vec![0.0,0.2,0.4,0.6])
711                .build()
712                .unwrap();
713            let res = skip.validate(3);
714            assert!(res.is_err());
715            assert!(matches!(res.err().unwrap(), GrowerTreeConfigurationError::LeafProbabilityLenExceedsDepth));
716        }
717
718        #[traced_test]
719        fn test_probability_out_of_range() {
720            let skip = LevelSkippingConfigurationBuilder::default()
721                .leaf_probability_per_level(vec![-0.1,1.1])
722                .build()
723                .unwrap();
724            let res = skip.validate(3);
725            assert!(res.is_err());
726            // We expect LeafProbabilityOutOfRange
727        }
728    }
729
730    mod test_capstone {
731        use super::*;
732
733        #[traced_test]
734        fn test_off_ok() {
735            let cap = CapstoneGenerationConfigurationBuilder::default()
736                .mode(CapstoneMode::Off)
737                .probability(0.0)
738                .build()
739                .unwrap();
740            assert!(cap.validate().is_ok());
741        }
742
743        #[traced_test]
744        fn test_single_ok() {
745            let cap = CapstoneGenerationConfigurationBuilder::default()
746                .mode(CapstoneMode::Single)
747                .probability(0.0)
748                .build()
749                .unwrap();
750            assert!(cap.validate().is_ok());
751        }
752
753        #[traced_test]
754        fn test_probabilistic_valid() {
755            let cap = CapstoneGenerationConfigurationBuilder::default()
756                .mode(CapstoneMode::Probabilistic)
757                .probability(0.5)
758                .build()
759                .unwrap();
760            assert!(cap.validate().is_ok());
761        }
762
763        #[traced_test]
764        fn test_probabilistic_out_of_range() {
765            let cap = CapstoneGenerationConfigurationBuilder::default()
766                .mode(CapstoneMode::Probabilistic)
767                .probability(1.2)
768                .build()
769                .unwrap();
770            let res = cap.validate();
771            assert!(res.is_err());
772            assert!(matches!(res.err().unwrap(), GrowerTreeConfigurationError::CapstoneProbabilityOutOfRange));
773        }
774    }
775
776    mod test_ai_confidence {
777        use super::*;
778
779        #[traced_test]
780        fn test_valid() {
781            let aic = AiTreeBranchingConfidenceConfigurationBuilder::default()
782                .base_factor(4)
783                .factor_multiplier(0.5)
784                .build()
785                .unwrap();
786            assert!(aic.validate().is_ok());
787        }
788
789        #[traced_test]
790        fn test_negative_multiplier() {
791            let aic = AiTreeBranchingConfidenceConfigurationBuilder::default()
792                .base_factor(2)
793                .factor_multiplier(-0.1)
794                .build()
795                .unwrap();
796            let res = aic.validate();
797            assert!(res.is_err());
798            assert!(matches!(res.err().unwrap(), GrowerTreeConfigurationError::AIConfidenceFactorNegative));
799        }
800    }
801
802    // ------------------------------------------------------------------------
803    // Finally, test the main struct with sub-structs
804    // ------------------------------------------------------------------------
805
806    #[traced_test]
807    fn test_main_config_ok() {
808        let cfg = GrowerTreeConfigurationBuilder::default()
809            .depth(3)
810            .breadth(2)
811            .density(2)
812            .leaf_granularity(0.5)
813            .balance_symmetry(0.5)
814            .complexity(ConfigurationComplexity::Balanced)
815            // Provide sub-struct
816            .weighted_branching(Some(
817                WeightedBranchingConfigurationBuilder::default()
818                    .mean(4)
819                    .variance(1)
820                    .build()
821                    .unwrap()
822            ))
823            .build()
824            .unwrap();
825        cfg.validate().unwrap();
826    }
827
828    #[traced_test]
829    fn test_main_config_toml_round_trip() {
830        let cfg = GrowerTreeConfigurationBuilder::default()
831            .depth(2)
832            .breadth(2)
833            .density(2)
834            .leaf_granularity(0.6)
835            .balance_symmetry(0.6)
836            .complexity(ConfigurationComplexity::Complex)
837            .build()
838            .unwrap();
839
840        let toml_str = cfg.to_toml_string().unwrap();
841        let parsed = GrowerTreeConfiguration::from_toml_str(&toml_str).unwrap();
842        parsed.validate().unwrap();
843        assert_eq!(parsed, cfg);
844    }
845
846    #[traced_test]
847    fn test_main_config_file_io() {
848        let dir = tempdir().unwrap();
849        let file_path = dir.path().join("cfg.toml");
850
851        let cfg = GrowerTreeConfigurationBuilder::default()
852            .depth(5)
853            .breadth(3)
854            .density(4)
855            .leaf_granularity(0.8)
856            .balance_symmetry(0.4)
857            .complexity(ConfigurationComplexity::Simple)
858            .build()
859            .unwrap();
860
861        cfg.validate().unwrap();
862        cfg.to_toml_file(&file_path).unwrap();
863
864        let loaded = GrowerTreeConfiguration::from_toml_file(&file_path).unwrap();
865        loaded.validate().unwrap();
866        assert_eq!(loaded, cfg);
867    }
868
869    #[traced_test]
870    #[disable]
871    fn test_ai_json_template_schema_generation() {
872        // 1) Construct a GrowerTreeConfiguration with some sample data:
873        let config = GrowerTreeConfigurationBuilder::default()
874            .depth(3)
875            .breadth(2)
876            .density(2)
877            .leaf_granularity(0.7)
878            .balance_symmetry(0.5)
879            .complexity(ConfigurationComplexity::Complex)
880            // Provide a sub-struct, e.g. weighted branching
881            .weighted_branching(Some(
882                WeightedBranchingConfigurationBuilder::default()
883                    .mean(4)
884                    .variance(1)
885                    .build()
886                    .unwrap()
887            ))
888            .build()
889            .expect("Failed building GrowerTreeConfiguration");
890
891        // 2) Call to_template(), producing the JSON schema describing the config for AI generation:
892        let schema_json = GrowerTreeConfiguration::to_template();
893
894        // 3) Optionally print it out for debugging/logging:
895        info!("AiJsonTemplate => {:#?}", schema_json);
896
897        // 4) Perform some basic assertions on the schema structure:
898        //    e.g., check that "fields" is present, or "struct_name" is correct, etc.
899        assert!(schema_json.is_object(), "Top-level schema should be an object.");
900
901        let root_obj = schema_json.as_object().unwrap();
902        assert!(root_obj.contains_key("struct_name"), "Schema must contain 'struct_name'");
903        assert_eq!(root_obj["struct_name"], "GrowerTreeConfiguration");
904
905        // 5) Check that the "fields" sub-object exists:
906        let fields_val = root_obj.get("fields").expect("Schema must contain 'fields'");
907        assert!(fields_val.is_object(), "'fields' should be an object.");
908
909        // 6) Inspect one of the fields (e.g. "leaf_granularity") to ensure it has instructions:
910        let fields_obj = fields_val.as_object().unwrap();
911        let leaf_granularity_schema = fields_obj.get("leaf_granularity").expect("Expected 'leaf_granularity' in schema");
912
913        // Example: leaf_granularity_schema should have "type":"number", "required":true, etc.
914        let schema_map = leaf_granularity_schema.as_object().expect("Should be an object schema");
915        assert_eq!(schema_map["type"], "number", "leaf_granularity must be numeric in AI schema");
916        assert_eq!(schema_map["required"], true, "leaf_granularity is not optional.");
917
918        // 7) If desired, parse instructions or do more assertions here.
919        // e.g., let instructions = schema_map["generation_instructions"].as_str().unwrap();
920        //       assert!(instructions.contains("fraction [0..1]"), "Should mention fraction range in doc comment.");
921
922        // If it reaches here, the test is considered passing.
923    }
924}