1use crate::{MorphologyParameters, RuntimeGenome};
15#[allow(unused_imports)]
17use feagi_structures::genomic::cortical_area::CorticalID;
18use serde_json::Value;
19use std::collections::HashSet;
20use std::str::FromStr;
21
22#[derive(Debug, Clone)]
24pub struct ValidationResult {
25 pub valid: bool,
27 pub errors: Vec<String>,
29 pub warnings: Vec<String>,
31}
32
33impl ValidationResult {
34 pub fn new() -> Self {
36 Self {
37 valid: true,
38 errors: Vec::new(),
39 warnings: Vec::new(),
40 }
41 }
42
43 pub fn add_error(&mut self, error: String) {
45 self.valid = false;
46 self.errors.push(error);
47 }
48
49 pub fn add_warning(&mut self, warning: String) {
51 self.warnings.push(warning);
52 }
53
54 pub fn merge(&mut self, other: ValidationResult) {
56 if !other.valid {
57 self.valid = false;
58 }
59 self.errors.extend(other.errors);
60 self.warnings.extend(other.warnings);
61 }
62}
63
64impl Default for ValidationResult {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70pub fn validate_genome(genome: &RuntimeGenome) -> ValidationResult {
72 let mut result = ValidationResult::new();
73
74 validate_metadata(genome, &mut result);
76
77 validate_cortical_areas(genome, &mut result);
79
80 validate_morphologies(genome, &mut result);
82
83 validate_physiology(genome, &mut result);
85
86 cross_validate(genome, &mut result);
88
89 result
90}
91
92pub fn auto_fix_genome(genome: &mut RuntimeGenome) -> usize {
103 use tracing::info;
104
105 let mut fixes_applied = 0;
106
107 if genome.physiology.simulation_timestep <= 0.0 {
109 let default_timestep = crate::runtime::PhysiologyConfig::default().simulation_timestep;
110 info!(
111 "🔧 AUTO-FIX: Invalid simulation_timestep {} → {} (default)",
112 genome.physiology.simulation_timestep, default_timestep
113 );
114 genome.physiology.simulation_timestep = default_timestep;
115 fixes_applied += 1;
116 }
117
118 if genome.physiology.max_age == 0 {
119 let default_age = crate::runtime::PhysiologyConfig::default().max_age;
120 info!("🔧 AUTO-FIX: max_age 0 → {} (default)", default_age);
121 genome.physiology.max_age = default_age;
122 fixes_applied += 1;
123 }
124
125 if genome.physiology.quantization_precision.is_empty() {
127 let default_precision = crate::runtime::default_quantization_precision();
128 info!(
129 "🔧 AUTO-FIX: Missing quantization_precision → '{}' (default)",
130 default_precision
131 );
132 genome.physiology.quantization_precision = default_precision;
133 fixes_applied += 1;
134 } else {
135 use feagi_npu_neural::types::Precision;
137 match Precision::from_str(&genome.physiology.quantization_precision) {
138 Ok(precision) => {
139 let canonical = precision.as_str().to_string();
140 if genome.physiology.quantization_precision != canonical {
141 info!(
142 "🔧 AUTO-FIX: Quantization precision '{}' → '{}' (normalized)",
143 genome.physiology.quantization_precision, canonical
144 );
145 genome.physiology.quantization_precision = canonical;
146 fixes_applied += 1;
147 }
148 }
149 Err(_) => {
150 let default_precision = crate::runtime::default_quantization_precision();
152 info!(
153 "🔧 AUTO-FIX: Invalid quantization_precision '{}' → '{}' (default)",
154 genome.physiology.quantization_precision, default_precision
155 );
156 genome.physiology.quantization_precision = default_precision;
157 fixes_applied += 1;
158 }
159 }
160 }
161
162 for (cortical_id, area) in &mut genome.cortical_areas {
163 let cortical_id_display = cortical_id.to_string();
164 if area.dimensions.width == 0 {
166 info!(
167 "🔧 AUTO-FIX: Cortical area '{}' width 0 → 1",
168 cortical_id_display
169 );
170 area.dimensions.width = 1;
171 fixes_applied += 1;
172 }
173 if area.dimensions.height == 0 {
174 info!(
175 "🔧 AUTO-FIX: Cortical area '{}' height 0 → 1",
176 cortical_id_display
177 );
178 area.dimensions.height = 1;
179 fixes_applied += 1;
180 }
181 if area.dimensions.depth == 0 {
182 info!(
183 "🔧 AUTO-FIX: Cortical area '{}' depth 0 → 1",
184 cortical_id_display
185 );
186 area.dimensions.depth = 1;
187 fixes_applied += 1;
188 }
189
190 let neurons_per_voxel = area
192 .properties
193 .get("neurons_per_voxel")
194 .and_then(|v| v.as_u64())
195 .unwrap_or(0) as u32;
196 if neurons_per_voxel == 0 {
197 info!(
198 "🔧 AUTO-FIX: Cortical area '{}' neurons_per_voxel 0 → 1",
199 cortical_id_display
200 );
201 area.properties
202 .insert("neurons_per_voxel".to_string(), serde_json::json!(1));
203 fixes_applied += 1;
204 }
205 }
206
207 if fixes_applied > 0 {
208 info!(
209 "🔧 AUTO-FIX: Applied {} automatic corrections to genome",
210 fixes_applied
211 );
212 }
213
214 fixes_applied
215}
216
217fn validate_metadata(genome: &RuntimeGenome, result: &mut ValidationResult) {
219 if genome.metadata.genome_id.is_empty() {
220 result.add_error("Genome ID is empty".to_string());
221 }
222
223 if genome.metadata.version.is_empty() {
224 result.add_error("Genome version is empty".to_string());
225 }
226
227 if genome.metadata.version != "2.0" {
228 result.add_warning(format!(
229 "Genome version '{}' may not be fully supported (expected '2.0')",
230 genome.metadata.version
231 ));
232 }
233}
234
235fn validate_cortical_areas(genome: &RuntimeGenome, result: &mut ValidationResult) {
237 if genome.cortical_areas.is_empty() {
238 result.add_warning("Genome has no cortical areas defined".to_string());
239 return;
240 }
241
242 for (cortical_id, area) in &genome.cortical_areas {
243 let cortical_id_display = cortical_id.to_string();
244
245 validate_cortical_id_format(cortical_id, &cortical_id_display, result);
247
248 if area.dimensions.width == 0 || area.dimensions.height == 0 || area.dimensions.depth == 0 {
250 result.add_warning(format!(
251 "AUTO-FIX: Cortical area '{}' has zero dimension(s): {}x{}x{} - will be corrected to minimum (1,1,1)",
252 cortical_id_display, area.dimensions.width, area.dimensions.height, area.dimensions.depth
253 ));
254 }
256
257 let neurons_per_voxel = area
259 .properties
260 .get("neurons_per_voxel")
261 .and_then(|v| v.as_u64())
262 .unwrap_or(0) as u32;
263 if neurons_per_voxel == 0 {
264 result.add_warning(format!(
265 "AUTO-FIX: Cortical area '{}' has neurons_per_voxel=0 - will be corrected to 1",
266 cortical_id_display
267 ));
268 }
269
270 let total_voxels = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
272 if total_voxels > 1_000_000 {
273 result.add_warning(format!(
274 "Cortical area '{}' has very large dimensions: {} total voxels",
275 cortical_id_display, total_voxels
276 ));
277 }
278
279 if area.name.is_empty() {
281 result.add_warning(format!(
282 "Cortical area '{}' has empty name",
283 cortical_id_display
284 ));
285 }
286 }
287}
288
289fn validate_cortical_id_format(
291 _cortical_id: &CorticalID,
292 display: &str,
293 result: &mut ValidationResult,
294) {
295 if display.len() != 8 && display.len() != 12 {
299 result.add_error(format!(
300 "Invalid cortical ID length: '{}' is {} characters (must be 8 or 12)",
301 display,
302 display.len()
303 ));
304 return;
305 }
306
307 if display.starts_with('_') {
309 validate_core_area_id(display, result);
310 return;
311 }
312
313 if display.starts_with('c') {
315 if !display.chars().all(|c| c.is_alphanumeric() || c == '_') {
318 result.add_warning(format!(
319 "Custom cortical ID '{}' contains non-alphanumeric characters",
320 display
321 ));
322 }
323 return;
324 }
325
326 validate_io_area_id(display, result);
328}
329
330fn validate_core_area_id(display: &str, result: &mut ValidationResult) {
332 use feagi_structures::genomic::cortical_area::CoreCorticalType;
333
334 let valid_core_ids: Vec<String> = vec![
336 CoreCorticalType::Power.to_cortical_id().to_string(), CoreCorticalType::Death.to_cortical_id().to_string(), CoreCorticalType::Fatigue.to_cortical_id().to_string(), CoreCorticalType::Pain.to_cortical_id().to_string(), CoreCorticalType::Pleasure.to_cortical_id().to_string(), CoreCorticalType::Fear.to_cortical_id().to_string(), CoreCorticalType::Hope.to_cortical_id().to_string(), ];
344
345 if !valid_core_ids.contains(&display.to_string()) {
346 result.add_error(format!(
347 "Invalid CORE cortical ID: '{}' - must be one of: {:?}",
348 display, valid_core_ids
349 ));
350 }
351}
352
353fn validate_io_area_id(display: &str, result: &mut ValidationResult) {
355 let first_char = display.chars().next().unwrap_or('_');
359 let unit_prefix = &display[1..4]; const VALID_IPU_PREFIXES: &[&str] = &[
363 "svi", "aud", "tac", "olf", "vis", "dpt", ];
370
371 const VALID_OPU_PREFIXES: &[&str] = &[
373 "mot", "voc", "gaz", "pse", "mis", ];
379
380 let is_valid_ipu = first_char == 'i' && VALID_IPU_PREFIXES.contains(&unit_prefix);
381 let is_valid_opu = first_char == 'o' && VALID_OPU_PREFIXES.contains(&unit_prefix);
382
383 if !is_valid_ipu && !is_valid_opu {
384 if display.starts_with("iic") || display.starts_with("omot") || display.starts_with("ogaz")
386 {
387 result.add_error(format!(
388 "INVALID OLD-FORMAT cortical ID: '{}' - not compliant with feagi-data-processing templates. \
389 Valid IPU format: 'i' + unit_prefix (e.g., 'isvi____'). \
390 Valid OPU format: 'o' + unit_prefix (e.g., 'omot____'). \
391 Valid IPU units: {:?}, Valid OPU units: {:?}. \
392 This genome needs migration to the new format.",
393 display, VALID_IPU_PREFIXES, VALID_OPU_PREFIXES
394 ));
395 } else {
396 result.add_warning(format!(
397 "Unknown cortical ID: '{}' (first char: '{}', unit: '{}') - may not follow feagi-data-processing template system. \
398 Valid IPU format: 'i' + {:?}. Valid OPU format: 'o' + {:?}",
399 display, first_char, unit_prefix, VALID_IPU_PREFIXES, VALID_OPU_PREFIXES
400 ));
401 }
402 return;
403 }
404
405 let suffix = &display[4..];
407
408 if first_char == 'i' && unit_prefix == "svi" {
410 if let Some(index_char) = display.chars().nth(4) {
411 if index_char.is_ascii_digit() {
412 let digit = index_char as u8 - b'0';
413 if digit > 8 {
414 result.add_error(format!(
415 "Invalid SegmentedVision index: '{}' in '{}' - SegmentedVision has 9 areas (indices 0-8)",
416 digit, display
417 ));
418 }
419 }
420 }
421 }
422
423 if !suffix.chars().all(|c| c.is_alphanumeric() || c == '_') {
425 result.add_warning(format!(
426 "Cortical ID '{}' has invalid characters in suffix (should be alphanumeric or underscore)",
427 display
428 ));
429 }
430}
431
432fn validate_morphologies(genome: &RuntimeGenome, result: &mut ValidationResult) {
434 if genome.morphologies.count() == 0 {
435 result.add_warning("Genome has no morphologies defined".to_string());
436 return;
437 }
438
439 let required_core = vec!["block_to_block", "projector"];
441 for morph_id in required_core {
442 if !genome.morphologies.contains(morph_id) {
443 result.add_warning(format!(
444 "Missing recommended core morphology: '{}'",
445 morph_id
446 ));
447 }
448 }
449
450 for (morphology_id, morphology) in genome.morphologies.iter() {
451 validate_single_morphology(morphology_id, morphology, result);
452 }
453}
454
455fn validate_single_morphology(
457 morphology_id: &str,
458 morphology: &crate::Morphology,
459 result: &mut ValidationResult,
460) {
461 match &morphology.parameters {
462 MorphologyParameters::Vectors { vectors } => {
463 if vectors.is_empty() {
464 result.add_error(format!(
465 "Morphology '{}' (vectors) has no vectors defined",
466 morphology_id
467 ));
468 }
469
470 for (i, vec) in vectors.iter().enumerate() {
472 if vec[0] == 0 && vec[1] == 0 && vec[2] == 0 {
473 result.add_warning(format!(
474 "Morphology '{}' has zero vector at index {}: [{}, {}, {}]",
475 morphology_id, i, vec[0], vec[1], vec[2]
476 ));
477 }
478 }
479 }
480
481 MorphologyParameters::Patterns { patterns } => {
482 if patterns.is_empty() {
483 result.add_error(format!(
484 "Morphology '{}' (patterns) has no patterns defined",
485 morphology_id
486 ));
487 }
488
489 for (i, pattern) in patterns.iter().enumerate() {
490 if pattern[0].len() != 3 || pattern[1].len() != 3 {
491 result.add_error(format!(
492 "Morphology '{}' pattern {} has invalid structure (expected [src[3], dst[3]])",
493 morphology_id, i
494 ));
495 }
496 }
497 }
498
499 MorphologyParameters::Functions {} => {
500 }
502
503 MorphologyParameters::Composite {
504 src_seed,
505 src_pattern,
506 mapper_morphology,
507 } => {
508 if src_seed[0] == 0 || src_seed[1] == 0 || src_seed[2] == 0 {
510 result.add_warning(format!(
511 "Morphology '{}' has zero dimension in src_seed: [{}, {}, {}]",
512 morphology_id, src_seed[0], src_seed[1], src_seed[2]
513 ));
514 }
515
516 if src_pattern.is_empty() {
518 result.add_error(format!(
519 "Morphology '{}' (composite) has empty src_pattern",
520 morphology_id
521 ));
522 }
523
524 if mapper_morphology.is_empty() {
526 result.add_error(format!(
527 "Morphology '{}' (composite) has empty mapper_morphology reference",
528 morphology_id
529 ));
530 }
531 }
532 }
533}
534
535fn validate_physiology(genome: &RuntimeGenome, result: &mut ValidationResult) {
537 let phys = &genome.physiology;
538
539 if phys.simulation_timestep <= 0.0 {
540 result.add_error(format!(
541 "Invalid simulation_timestep: {} (must be > 0.0)",
542 phys.simulation_timestep
543 ));
544 }
545
546 if phys.simulation_timestep > 1.0 {
547 result.add_warning(format!(
548 "Very large simulation_timestep: {} seconds (typical: 0.01-0.1)",
549 phys.simulation_timestep
550 ));
551 }
552
553 if phys.max_age == 0 {
554 result.add_warning("max_age is 0 (neurons will never age)".to_string());
555 }
556
557 if phys.plasticity_queue_depth == 0 {
558 result.add_warning("plasticity_queue_depth is 0 (no plasticity history)".to_string());
559 }
560
561 validate_quantization_precision(&phys.quantization_precision, result);
563}
564
565fn validate_quantization_precision(precision: &str, result: &mut ValidationResult) {
567 use feagi_npu_neural::types::Precision;
568
569 match Precision::from_str(precision) {
571 Ok(parsed_precision) => {
572 if precision != parsed_precision.as_str() {
574 result.add_warning(format!(
575 "Quantization precision '{}' normalized to '{}'",
576 precision,
577 parsed_precision.as_str()
578 ));
579 }
580 }
581 Err(_) => {
582 result.add_error(format!(
583 "Invalid quantization_precision: '{}' (must be 'fp32', 'fp16', or 'int8')",
584 precision
585 ));
586 }
587 }
588}
589
590fn cross_validate(genome: &RuntimeGenome, result: &mut ValidationResult) {
592 let morphology_ids: HashSet<String> =
594 genome.morphologies.morphology_ids().into_iter().collect();
595
596 for (cortical_id, area) in &genome.cortical_areas {
598 let cortical_id_display = cortical_id.to_string();
599 if let Some(Value::Object(dstmap)) = area.properties.get("dstmap") {
600 for (dest_area, rules) in dstmap {
601 if let Ok(dest_cortical_id) =
603 crate::genome::parser::string_to_cortical_id(dest_area)
604 {
605 if !genome.cortical_areas.contains_key(&dest_cortical_id) {
606 result.add_error(format!(
607 "Cortical area '{}' references non-existent destination area '{}' in dstmap",
608 cortical_id_display, dest_area
609 ));
610 }
611 } else {
612 result.add_error(format!(
613 "Cortical area '{}' has invalid destination area ID '{}' in dstmap",
614 cortical_id_display, dest_area
615 ));
616 }
617
618 if let Value::Array(rules_array) = rules {
620 for rule in rules_array {
621 if let Value::Array(rule_array) = rule {
622 if let Some(Value::String(morph_id)) = rule_array.first() {
623 if !morphology_ids.contains(morph_id) {
624 result.add_error(format!(
625 "Cortical area '{}' references undefined morphology '{}' in dstmap rule",
626 cortical_id_display, morph_id
627 ));
628 }
629 }
630 }
631 }
632 }
633 }
634 }
635 }
636
637 for (region_id, region) in &genome.brain_regions {
639 for cortical_id in ®ion.cortical_areas {
641 if !genome.cortical_areas.contains_key(cortical_id) {
642 result.add_error(format!(
643 "Brain region '{}' references non-existent cortical area '{}'",
644 region_id, cortical_id
645 ));
646 }
647 }
648 }
649
650 for (classifier_id, classifier) in &genome.classifiers {
651 if classifier.name.trim().is_empty() {
652 result.add_error(format!("Classifier '{}' has an empty name", classifier_id));
653 }
654 if !genome
655 .brain_regions
656 .contains_key(&classifier.parent_region_id)
657 {
658 result.add_error(format!(
659 "Classifier '{}' references unknown parent_region_id '{}'",
660 classifier_id, classifier.parent_region_id
661 ));
662 }
663 for area_id in classifier.owned_area_ids() {
664 if crate::genome::parser::string_to_cortical_id(&area_id)
665 .ok()
666 .and_then(|id| genome.cortical_areas.get(&id).map(|_| ()))
667 .is_none()
668 {
669 result.add_error(format!(
670 "Classifier '{}' references missing owned area '{}'",
671 classifier_id, area_id
672 ));
673 }
674 }
675 for area_id in classifier.input_area_ids() {
676 if crate::genome::parser::string_to_cortical_id(&area_id)
677 .ok()
678 .and_then(|id| genome.cortical_areas.get(&id).map(|_| ()))
679 .is_none()
680 {
681 result.add_error(format!(
682 "Classifier '{}' references missing input area '{}'",
683 classifier_id, area_id
684 ));
685 }
686 }
687 }
688
689 for (morphology_id, morphology) in genome.morphologies.iter() {
691 if let MorphologyParameters::Composite {
692 mapper_morphology, ..
693 } = &morphology.parameters
694 {
695 if !morphology_ids.contains(mapper_morphology) {
696 result.add_error(format!(
697 "Composite morphology '{}' references undefined mapper morphology '{}'",
698 morphology_id, mapper_morphology
699 ));
700 }
701 }
702 }
703}
704
705#[cfg(test)]
706mod tests {
707 use super::*;
708 use crate::{
709 GenomeMetadata, GenomeSignatures, GenomeStats, MorphologyRegistry, PhysiologyConfig,
710 };
711 use std::collections::HashMap;
712
713 #[test]
714 fn test_validate_empty_genome() {
715 let genome = RuntimeGenome {
716 metadata: GenomeMetadata {
717 genome_id: "test".to_string(),
718 genome_title: "Test".to_string(),
719 genome_description: "".to_string(),
720 version: "2.0".to_string(),
721 timestamp: 0.0,
722 brain_regions_root: None,
723 },
724 cortical_areas: HashMap::new(),
725 brain_regions: HashMap::new(),
726 classifiers: HashMap::new(),
727 morphologies: MorphologyRegistry::new(),
728 physiology: PhysiologyConfig::default(),
729 signatures: GenomeSignatures {
730 genome: "0".to_string(),
731 blueprint: "0".to_string(),
732 physiology: "0".to_string(),
733 morphologies: None,
734 },
735 stats: GenomeStats::default(),
736 };
737
738 let result = validate_genome(&genome);
739
740 assert!(!result.warnings.is_empty());
742 println!("Warnings: {:?}", result.warnings);
743 }
744
745 #[test]
746 fn test_validate_valid_genome() {
747 let mut genome = RuntimeGenome {
748 metadata: GenomeMetadata {
749 genome_id: "test_genome".to_string(),
750 genome_title: "Test Genome".to_string(),
751 genome_description: "Valid test genome".to_string(),
752 version: "2.0".to_string(),
753 timestamp: 1234567890.0,
754 brain_regions_root: None,
755 },
756 cortical_areas: HashMap::new(),
757 brain_regions: HashMap::new(),
758 classifiers: HashMap::new(),
759 morphologies: MorphologyRegistry::new(),
760 physiology: PhysiologyConfig::default(),
761 signatures: GenomeSignatures {
762 genome: "abc123".to_string(),
763 blueprint: "def456".to_string(),
764 physiology: "ghi789".to_string(),
765 morphologies: None,
766 },
767 stats: GenomeStats::default(),
768 };
769
770 use feagi_structures::genomic::cortical_area::CustomCorticalType;
772 use feagi_structures::genomic::cortical_area::{
773 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
774 };
775 let test_id = CoreCorticalType::Power.to_cortical_id();
776 let area = CorticalArea::new(
777 test_id,
778 0,
779 "Test Area".to_string(),
780 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
781 (0, 0, 0).into(),
782 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
783 )
784 .expect("Failed to create cortical area");
785
786 genome.cortical_areas.insert(test_id, area);
787
788 let result = validate_genome(&genome);
789
790 println!("Errors: {:?}", result.errors);
792 println!("Warnings: {:?}", result.warnings);
793
794 assert!(result.errors.is_empty());
796 assert!(!result.warnings.is_empty()); }
798
799 #[test]
800 fn test_validate_quantization_precision() {
801 let mut genome = create_minimal_genome();
802
803 genome.physiology.quantization_precision = "fp32".to_string();
805 let result = validate_genome(&genome);
806 assert!(result.errors.is_empty(), "fp32 should be valid");
807
808 genome.physiology.quantization_precision = "int8".to_string();
810 let result = validate_genome(&genome);
811 assert!(result.errors.is_empty(), "int8 should be valid");
812
813 genome.physiology.quantization_precision = "i8".to_string();
815 let result = validate_genome(&genome);
816 assert!(result.errors.is_empty(), "i8 should be valid");
817 assert!(
818 result.warnings.iter().any(|w| w.contains("normalized")),
819 "Should warn about normalization"
820 );
821
822 genome.physiology.quantization_precision = "invalid".to_string();
824 let result = validate_genome(&genome);
825 assert!(!result.errors.is_empty(), "invalid should produce error");
826 assert!(
827 result
828 .errors
829 .iter()
830 .any(|e| e.contains("Invalid quantization_precision")),
831 "Should have quantization error"
832 );
833 }
834
835 #[test]
836 fn test_auto_fix_quantization_precision() {
837 let mut genome = create_minimal_genome();
839 genome.physiology.quantization_precision = "".to_string();
840
841 let fixes = auto_fix_genome(&mut genome);
842 assert!(fixes > 0, "Should apply at least one fix");
843 assert_eq!(
844 genome.physiology.quantization_precision, "int8",
845 "Should default to int8"
846 );
847
848 genome.physiology.quantization_precision = "i8".to_string();
850 let _fixes = auto_fix_genome(&mut genome);
851 assert_eq!(
852 genome.physiology.quantization_precision, "int8",
853 "Should normalize i8 to int8"
854 );
855
856 genome.physiology.quantization_precision = "invalid".to_string();
858 let _fixes = auto_fix_genome(&mut genome);
859 assert_eq!(
860 genome.physiology.quantization_precision, "int8",
861 "Invalid should default to int8"
862 );
863 }
864
865 #[test]
866 fn test_validate_classifier_requires_parent_region() {
867 let mut genome = create_minimal_genome();
868 genome.classifiers.insert(
869 "clf-1".to_string(),
870 feagi_structures::genomic::classifiers::Classifier {
871 classifier_id: "clf-1".to_string(),
872 name: "demo".to_string(),
873 parent_region_id: "missing".to_string(),
874 coordinates_3d: [0, 0, 0],
875 kernel_area_id: None,
876 class_area_id: None,
877 fields: vec![feagi_structures::genomic::classifiers::ClassifierField {
878 field_area_id: "cfield".to_string(),
879 scan_twin_id: "cscan1".to_string(),
880 }],
881 kernel_memory_id: "mkmem1".to_string(),
882 class_memory_id: "mcmem1".to_string(),
883 properties: HashMap::new(),
884 },
885 );
886 let result = validate_genome(&genome);
887 assert!(result
888 .errors
889 .iter()
890 .any(|error| error.contains("unknown parent_region_id")));
891 assert!(result
892 .errors
893 .iter()
894 .any(|error| error.contains("missing owned area")));
895 }
896
897 fn create_minimal_genome() -> RuntimeGenome {
898 RuntimeGenome {
899 metadata: GenomeMetadata {
900 genome_id: "test".to_string(),
901 genome_title: "Test".to_string(),
902 genome_description: "".to_string(),
903 version: "2.0".to_string(),
904 timestamp: 0.0,
905 brain_regions_root: None,
906 },
907 cortical_areas: HashMap::new(),
908 brain_regions: HashMap::new(),
909 classifiers: HashMap::new(),
910 morphologies: MorphologyRegistry::new(),
911 physiology: PhysiologyConfig::default(),
912 signatures: GenomeSignatures {
913 genome: "0".to_string(),
914 blueprint: "0".to_string(),
915 physiology: "0".to_string(),
916 morphologies: None,
917 },
918 stats: GenomeStats::default(),
919 }
920 }
921}