1crate::ix!();
3
4#[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 Simple,
32
33 Weighted(
36
37 WeightedNodeVariantPolicy
39 ),
40
41 WeightedWithLimits(
43
44 WeightedNodeVariantPolicyWithLimits
47 ),
48
49 DepthBased(
52
53 DepthBasedNodeVariantPolicy
57 ),
58
59 AlwaysAggregate,
61
62 AlwaysDispatch,
64
65 AlwaysLeafHolder,
67
68 Phased(
71 PhasedNodeVariantPolicy
74 ),
75
76 Scripted(
79
80 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 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 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 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 let recognized = [
132 "Simple",
133 "Weighted",
134 "WeightedWithLimits",
135 "DepthBased",
136 "AlwaysAggregate",
137 "AlwaysDispatch",
138 "AlwaysLeafHolder",
139 "Phased",
140 "Scripted",
141 ];
142
143 let mut found_keys = Vec::new();
145 for k in top.keys() {
146 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 let mut new_map = serde_json::Map::new();
160 new_map.insert("variant_name".to_string(), serde_json::Value::String(recognized_key));
161
162 match variant_val {
164 serde_json::Value::Object(o) => {
165 let mut subo = o.clone();
167 flatten_all_fields(&mut subo);
168 for (k,v) in subo.into_iter() {
170 new_map.insert(k, v);
171 }
172 }
173 serde_json::Value::Array(arr) => {
174 debug!("(JustifiedTreeExpansionPolicy) single-key is an array => we'll parse [ subobject, conf, justification ] if present.");
176 if !arr.is_empty() {
177 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 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 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 debug!("(JustifiedTreeExpansionPolicy) single recognized key => got a non-object/array => ignoring or unify minimal");
203 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 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 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 if let Some(f0_val) = top.remove("field_0") {
254 if let Some(arr) = f0_val.as_array() {
256 if !arr.is_empty() {
257 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 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 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 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 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 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 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 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 #[traced_test]
546 fn test_tree_expansion_policy_type_template() {
547 let tpl = <TreeExpansionPolicy as AiJsonTemplate>::to_template();
549 assert!(tpl.is_object(), "Expected a JSON object for enum template");
551
552 let root_obj = tpl.as_object().unwrap();
553 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 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 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 { .. } => { },
646 other => panic!("Expected Simple, got {:?}", other),
647 }
648 }
649
650 #[test]
651 fn test_bare_string_simple() {
652 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 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 } else {
670 panic!("Expected WeightedWithLimits variant, got {:?}", pol);
671 }
672 }
673
674 #[test]
675 fn test_pascal_case_depth_based() {
676 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 } else {
684 panic!("Expected DepthBased variant, got {:?}", pol);
685 }
686 }
687
688 #[test]
689 fn test_upper_snake_always_leaf_holder() {
690 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 } else {
698 panic!("Expected AlwaysLeafHolder variant, got {:?}", pol);
699 }
700 }
701
702 #[test]
703 fn test_single_key_object_scripted() {
704 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 } 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 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 let parsed_value: JsonValue = serde_json::from_str(raw_json)
918 .expect("JSON must be valid syntax");
919
920 let result = JustifiedGrowerTreeConfiguration::fuzzy_from_json_value(&parsed_value);
922
923 assert!(
925 result.is_ok(),
926 "Real snippet should parse successfully, got error: {:?}",
927 result.err()
928 );
929 let jcfg = result.unwrap();
930
931 match jcfg.tree_expansion_policy() {
933 JustifiedTreeExpansionPolicy::WeightedWithLimits { field_0, .. } => {
934 info!("Got WeightedWithLimits as intended!");
935 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 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 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 }
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 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 }
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 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 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 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 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 #[test]
1159 fn test_single_key_complexity_balanced() {
1160 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 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 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 #[test]
1227 fn test_single_key_capstone_single() {
1228 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 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 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 #[test]
1298 fn test_array_form_weighted_with_limits_full() {
1299 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 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 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 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 #[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 assert_eq!(*cfg.depth(), 4);
1477 assert_eq!(*cfg.breadth(), 7);
1478 assert_eq!(*cfg.density(), 9);
1479
1480 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 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 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}