1use serde::{Deserialize, Serialize};
44use serde_json::Value;
45use std::collections::HashMap;
46use tracing::warn;
47
48use crate::types::{EvoError, EvoResult};
49use feagi_structures::genomic::brain_regions::RegionID;
50use feagi_structures::genomic::cortical_area::CorticalID;
51use feagi_structures::genomic::cortical_area::{
52 CorticalArea, CorticalAreaDimensions as Dimensions,
53};
54use feagi_structures::genomic::descriptors::GenomeCoordinate3D;
55use feagi_structures::genomic::{BrainRegion, RegionType};
56
57#[derive(Debug, Clone)]
59pub struct ParsedGenome {
60 pub genome_id: String,
62 pub genome_title: String,
63 pub version: String,
64
65 pub cortical_areas: Vec<CorticalArea>,
67
68 pub brain_regions: Vec<(BrainRegion, Option<String>)>, pub neuron_morphologies: HashMap<String, Value>,
73
74 pub physiology: Option<Value>,
76}
77
78#[derive(Debug, Clone, Deserialize, Serialize)]
80pub struct RawGenome {
81 pub genome_id: Option<String>,
82 pub genome_title: Option<String>,
83 pub genome_description: Option<String>,
84 pub version: String,
85 pub blueprint: HashMap<String, RawCorticalArea>,
86 #[serde(default)]
87 pub brain_regions: HashMap<String, RawBrainRegion>,
88 #[serde(default)]
89 pub neuron_morphologies: HashMap<String, Value>,
90 #[serde(default)]
91 pub physiology: Option<Value>,
92 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub brain_regions_root: Option<String>,
95}
96
97#[derive(Debug, Clone, Deserialize, Serialize)]
99pub struct RawCorticalArea {
100 pub cortical_name: Option<String>,
101 pub block_boundaries: Option<Vec<u32>>,
102 pub relative_coordinate: Option<Vec<i32>>,
103 pub cortical_type: Option<String>,
104
105 pub group_id: Option<String>,
107 pub sub_group_id: Option<String>,
108 pub per_voxel_neuron_cnt: Option<u32>,
109 pub cortical_mapping_dst: Option<Value>,
110
111 pub synapse_attractivity: Option<f32>,
113 pub refractory_period: Option<u32>,
114 pub firing_threshold: Option<f32>,
115 pub firing_threshold_limit: Option<f32>,
116 pub firing_threshold_increment_x: Option<f32>,
117 pub firing_threshold_increment_y: Option<f32>,
118 pub firing_threshold_increment_z: Option<f32>,
119 pub leak_coefficient: Option<f32>,
120 pub leak_variability: Option<f32>,
121 pub neuron_excitability: Option<f32>,
122 pub postsynaptic_current: Option<f32>,
123 pub postsynaptic_current_max: Option<f32>,
124 pub degeneration: Option<f32>,
125 pub psp_uniform_distribution: Option<bool>,
126 pub mp_charge_accumulation: Option<bool>,
127 pub mp_driven_psp: Option<bool>,
128 pub visualization: Option<bool>,
129 pub burst_engine_activation: Option<bool>,
130 #[serde(rename = "2d_coordinate")]
131 pub coordinate_2d: Option<Vec<i32>>,
132
133 pub is_mem_type: Option<bool>,
135 pub longterm_mem_threshold: Option<u32>,
136 pub lifespan_growth_rate: Option<f32>,
137 pub init_lifespan: Option<u32>,
138 pub temporal_depth: Option<u32>,
139 pub consecutive_fire_cnt_max: Option<u32>,
140 pub snooze_length: Option<u32>,
141
142 #[serde(flatten)]
144 pub other: HashMap<String, Value>,
145}
146
147#[derive(Debug, Clone, Deserialize, Serialize)]
149pub struct RawBrainRegion {
150 pub title: Option<String>,
151 pub description: Option<String>,
152 pub parent_region_id: Option<String>,
153 pub coordinate_2d: Option<Vec<i32>>,
154 pub coordinate_3d: Option<Vec<i32>>,
155 pub areas: Option<Vec<String>>,
156 pub regions: Option<Vec<String>>,
157 pub inputs: Option<Vec<String>>,
158 pub outputs: Option<Vec<String>>,
159 pub signature: Option<String>,
160}
161
162fn convert_dstmap_keys_to_base64(dstmap: &Value) -> Value {
166 if let Some(dstmap_obj) = dstmap.as_object() {
167 let mut converted = serde_json::Map::new();
168
169 for (dest_id_str, mapping_value) in dstmap_obj {
170 match string_to_cortical_id(dest_id_str) {
172 Ok(dest_cortical_id) => {
173 converted.insert(dest_cortical_id.as_base_64(), mapping_value.clone());
174 }
175 Err(e) => {
176 tracing::warn!(
178 "Failed to convert dstmap key '{}' to base64: {}, keeping original",
179 dest_id_str,
180 e
181 );
182 converted.insert(dest_id_str.clone(), mapping_value.clone());
183 }
184 }
185 }
186
187 Value::Object(converted)
188 } else {
189 dstmap.clone()
191 }
192}
193
194pub fn string_to_cortical_id(id_str: &str) -> EvoResult<CorticalID> {
198 use feagi_structures::genomic::cortical_area::CoreCorticalType;
199
200 if let Ok(cortical_id) = CorticalID::try_from_base_64(id_str) {
202 let mut bytes = [0u8; CorticalID::CORTICAL_ID_LENGTH];
203 cortical_id.write_id_to_bytes(&mut bytes);
204 if bytes == *b"___power" {
205 return Ok(CoreCorticalType::Power.to_cortical_id());
206 }
207 if bytes == *b"___death" {
208 return Ok(CoreCorticalType::Death.to_cortical_id());
209 }
210 if bytes == *b"___fatig" {
211 return Ok(CoreCorticalType::Fatigue.to_cortical_id());
212 }
213 return Ok(cortical_id);
214 }
215
216 if id_str == "_power" {
218 return Ok(CoreCorticalType::Power.to_cortical_id());
219 }
220 if id_str == "___pwr" {
222 return Ok(CoreCorticalType::Power.to_cortical_id());
223 }
224 if id_str == "___power" {
226 return Ok(CoreCorticalType::Power.to_cortical_id());
227 }
228 if id_str == "___pwr__" {
230 return Ok(CoreCorticalType::Power.to_cortical_id());
231 }
232 if id_str == "___death" {
233 return Ok(CoreCorticalType::Death.to_cortical_id());
234 }
235 if id_str == "___fatig" {
236 return Ok(CoreCorticalType::Fatigue.to_cortical_id());
237 }
238 if id_str == "_death" {
239 return Ok(CoreCorticalType::Death.to_cortical_id());
240 }
241 if id_str == "_fatigue" {
242 return Ok(CoreCorticalType::Fatigue.to_cortical_id());
243 }
244
245 if id_str.len() == 6 {
247 let mut bytes = [b'_'; 8];
249 bytes[..6].copy_from_slice(id_str.as_bytes());
250
251 CorticalID::try_from_bytes(&bytes).map_err(|e| {
252 EvoError::InvalidArea(format!("Failed to convert cortical_id '{}': {}", id_str, e))
253 })
254 } else if id_str.len() == 8 {
255 let mut bytes = [0u8; 8];
257 bytes.copy_from_slice(id_str.as_bytes());
258
259 CorticalID::try_from_bytes(&bytes).map_err(|e| {
260 EvoError::InvalidArea(format!("Failed to convert cortical_id '{}': {}", id_str, e))
261 })
262 } else {
263 Err(EvoError::InvalidArea(format!(
264 "Invalid cortical_id length: '{}' (expected 6 or 8 ASCII chars, or base64)",
265 id_str
266 )))
267 }
268}
269
270pub struct GenomeParser;
272
273impl GenomeParser {
274 pub fn parse(json_str: &str) -> EvoResult<ParsedGenome> {
292 let raw: RawGenome = serde_json::from_str(json_str)
294 .map_err(|e| EvoError::InvalidGenome(format!("Failed to parse JSON: {}", e)))?;
295
296 if !raw.version.starts_with("2.") && raw.version != "3.0" {
298 return Err(EvoError::InvalidGenome(format!(
299 "Unsupported genome version: {}. Expected 2.x or 3.0",
300 raw.version
301 )));
302 }
303
304 let cortical_areas = Self::parse_cortical_areas(&raw.blueprint)?;
306
307 let brain_regions = Self::parse_brain_regions(&raw.brain_regions)?;
309
310 Ok(ParsedGenome {
311 genome_id: raw.genome_id.unwrap_or_else(|| "unknown".to_string()),
312 genome_title: raw.genome_title.unwrap_or_else(|| "Untitled".to_string()),
313 version: raw.version,
314 cortical_areas,
315 brain_regions,
316 neuron_morphologies: raw.neuron_morphologies,
317 physiology: raw.physiology,
318 })
319 }
320
321 fn parse_cortical_areas(
323 blueprint: &HashMap<String, RawCorticalArea>,
324 ) -> EvoResult<Vec<CorticalArea>> {
325 let mut areas = Vec::with_capacity(blueprint.len());
326
327 for (cortical_id_str, raw_area) in blueprint.iter() {
328 if cortical_id_str.is_empty() {
330 warn!(target: "feagi-evo","Skipping empty cortical_id");
331 continue;
332 }
333
334 let cortical_id = match string_to_cortical_id(cortical_id_str) {
336 Ok(id) => id,
337 Err(e) => {
338 warn!(target: "feagi-evo","Skipping invalid cortical_id '{}': {}", cortical_id_str, e);
339 continue;
340 }
341 };
342
343 let name = raw_area
345 .cortical_name
346 .clone()
347 .unwrap_or_else(|| cortical_id_str.clone());
348
349 let dimensions = if let Some(boundaries) = &raw_area.block_boundaries {
350 if boundaries.len() != 3 {
351 return Err(EvoError::InvalidArea(format!(
352 "Invalid block_boundaries for {}: expected 3 values, got {}",
353 cortical_id_str,
354 boundaries.len()
355 )));
356 }
357 Dimensions::new(boundaries[0], boundaries[1], boundaries[2])
358 .map_err(|e| EvoError::InvalidArea(format!("Invalid dimensions: {}", e)))?
359 } else {
360 warn!(target: "feagi-evo","Cortical area {} missing block_boundaries, defaulting to 1x1x1", cortical_id_str);
362 Dimensions::new(1, 1, 1).map_err(|e| {
363 EvoError::InvalidArea(format!("Invalid default dimensions: {}", e))
364 })?
365 };
366
367 let position = if let Some(coords) = &raw_area.relative_coordinate {
368 if coords.len() != 3 {
369 return Err(EvoError::InvalidArea(format!(
370 "Invalid relative_coordinate for {}: expected 3 values, got {}",
371 cortical_id_str,
372 coords.len()
373 )));
374 }
375 GenomeCoordinate3D::new(coords[0], coords[1], coords[2])
376 } else {
377 warn!(target: "feagi-evo","Cortical area {} missing relative_coordinate, defaulting to (0,0,0)", cortical_id_str);
379 GenomeCoordinate3D::new(0, 0, 0)
380 };
381
382 let cortical_type = cortical_id.as_cortical_type().map_err(|e| {
384 EvoError::InvalidArea(format!(
385 "Failed to determine cortical type from ID {}: {}",
386 cortical_id_str, e
387 ))
388 })?;
389
390 let mut area = CorticalArea::new(
392 cortical_id,
393 0, name,
395 dimensions,
396 position,
397 cortical_type,
398 )?;
399
400 if let Some(ref cortical_type_str) = raw_area.cortical_type {
402 area.properties.insert(
403 "cortical_group".to_string(),
404 serde_json::json!(cortical_type_str),
405 );
406 }
407
408 if let Some(v) = raw_area.synapse_attractivity {
411 area.properties
412 .insert("synapse_attractivity".to_string(), serde_json::json!(v));
413 }
414 if let Some(v) = raw_area.refractory_period {
415 area.properties
416 .insert("refractory_period".to_string(), serde_json::json!(v));
417 }
418 if let Some(v) = raw_area.firing_threshold {
419 area.properties
420 .insert("firing_threshold".to_string(), serde_json::json!(v));
421 }
422 if let Some(v) = raw_area.firing_threshold_limit {
423 area.properties
424 .insert("firing_threshold_limit".to_string(), serde_json::json!(v));
425 }
426 if let Some(v) = raw_area.firing_threshold_increment_x {
427 area.properties.insert(
428 "firing_threshold_increment_x".to_string(),
429 serde_json::json!(v),
430 );
431 }
432 if let Some(v) = raw_area.firing_threshold_increment_y {
433 area.properties.insert(
434 "firing_threshold_increment_y".to_string(),
435 serde_json::json!(v),
436 );
437 }
438 if let Some(v) = raw_area.firing_threshold_increment_z {
439 area.properties.insert(
440 "firing_threshold_increment_z".to_string(),
441 serde_json::json!(v),
442 );
443 }
444 if let Some(v) = raw_area.leak_coefficient {
445 area.properties
446 .insert("leak_coefficient".to_string(), serde_json::json!(v));
447 }
448 if let Some(v) = raw_area.leak_variability {
449 area.properties
450 .insert("leak_variability".to_string(), serde_json::json!(v));
451 }
452 if let Some(v) = raw_area.neuron_excitability {
453 area.properties
454 .insert("neuron_excitability".to_string(), serde_json::json!(v));
455 }
456 if let Some(v) = raw_area.postsynaptic_current {
457 area.properties
458 .insert("postsynaptic_current".to_string(), serde_json::json!(v));
459 }
460 if let Some(v) = raw_area.postsynaptic_current_max {
461 area.properties
462 .insert("postsynaptic_current_max".to_string(), serde_json::json!(v));
463 }
464 if let Some(v) = raw_area.degeneration {
465 area.properties
466 .insert("degeneration".to_string(), serde_json::json!(v));
467 }
468
469 if let Some(v) = raw_area.psp_uniform_distribution {
471 area.properties
472 .insert("psp_uniform_distribution".to_string(), serde_json::json!(v));
473 }
474 if let Some(v) = raw_area.mp_charge_accumulation {
475 area.properties
476 .insert("mp_charge_accumulation".to_string(), serde_json::json!(v));
477 }
478 if let Some(v) = raw_area.mp_driven_psp {
479 area.properties
480 .insert("mp_driven_psp".to_string(), serde_json::json!(v));
481 tracing::info!(
482 target: "feagi-evo",
483 "[GENOME-LOAD] Loaded mp_driven_psp={} for area {}",
484 v,
485 cortical_id_str
486 );
487 } else {
488 tracing::debug!(
489 target: "feagi-evo",
490 "[GENOME-LOAD] mp_driven_psp not found in raw_area for {}, will use default=false",
491 cortical_id_str
492 );
493 }
494 if let Some(v) = raw_area.visualization {
495 area.properties
496 .insert("visualization".to_string(), serde_json::json!(v));
497 area.properties
499 .insert("visible".to_string(), serde_json::json!(v));
500 }
501 if let Some(v) = raw_area.burst_engine_activation {
502 area.properties
503 .insert("burst_engine_active".to_string(), serde_json::json!(v));
504 }
505 if let Some(v) = raw_area.is_mem_type {
506 area.properties
507 .insert("is_mem_type".to_string(), serde_json::json!(v));
508 }
509
510 if let Some(v) = raw_area.longterm_mem_threshold {
512 area.properties
513 .insert("longterm_mem_threshold".to_string(), serde_json::json!(v));
514 }
515 if let Some(v) = raw_area.lifespan_growth_rate {
516 area.properties
517 .insert("lifespan_growth_rate".to_string(), serde_json::json!(v));
518 }
519 if let Some(v) = raw_area.init_lifespan {
520 area.properties
521 .insert("init_lifespan".to_string(), serde_json::json!(v));
522 }
523 if let Some(v) = raw_area.temporal_depth {
524 area.properties
525 .insert("temporal_depth".to_string(), serde_json::json!(v));
526 }
527 if let Some(v) = raw_area.consecutive_fire_cnt_max {
528 area.properties
529 .insert("consecutive_fire_cnt_max".to_string(), serde_json::json!(v));
530 area.properties
532 .insert("consecutive_fire_limit".to_string(), serde_json::json!(v));
533 }
534 if let Some(v) = raw_area.snooze_length {
535 area.properties
536 .insert("snooze_period".to_string(), serde_json::json!(v));
537 }
538
539 if let Some(v) = &raw_area.group_id {
541 area.properties
542 .insert("group_id".to_string(), serde_json::json!(v));
543 }
544 if let Some(v) = &raw_area.sub_group_id {
545 area.properties
546 .insert("sub_group_id".to_string(), serde_json::json!(v));
547 }
548 if let Some(v) = raw_area.per_voxel_neuron_cnt {
550 area.properties
551 .insert("neurons_per_voxel".to_string(), serde_json::json!(v));
552 }
553 if let Some(v) = &raw_area.cortical_mapping_dst {
554 let converted_dstmap = convert_dstmap_keys_to_base64(v);
556 area.properties
557 .insert("cortical_mapping_dst".to_string(), converted_dstmap);
558 }
559 if let Some(v) = &raw_area.coordinate_2d {
560 area.properties
561 .insert("2d_coordinate".to_string(), serde_json::json!(v));
562 }
563
564 for (key, value) in &raw_area.other {
566 area.properties.insert(key.clone(), value.clone());
567 }
568
569 areas.push(area);
573 }
574
575 Ok(areas)
576 }
577
578 fn parse_brain_regions(
580 raw_regions: &HashMap<String, RawBrainRegion>,
581 ) -> EvoResult<Vec<(BrainRegion, Option<String>)>> {
582 let mut regions = Vec::with_capacity(raw_regions.len());
583
584 for (region_id_str, raw_region) in raw_regions.iter() {
585 let title = raw_region
586 .title
587 .clone()
588 .unwrap_or_else(|| region_id_str.clone());
589
590 let region_id = match RegionID::from_string(region_id_str) {
593 Ok(id) => id,
594 Err(_) => {
595 RegionID::new()
598 }
599 };
600
601 let region_type = RegionType::Undefined; let mut region = BrainRegion::new(region_id, title, region_type)?;
604
605 if let Some(areas) = &raw_region.areas {
607 for area_id in areas {
608 match string_to_cortical_id(area_id) {
610 Ok(cortical_id) => {
611 region.add_area(cortical_id);
612 }
613 Err(e) => {
614 warn!(target: "feagi-evo",
615 "Failed to convert brain region area ID '{}' to CorticalID: {}. Skipping.",
616 area_id, e);
617 }
618 }
619 }
620 }
621
622 if let Some(desc) = &raw_region.description {
624 region.add_property("description".to_string(), serde_json::json!(desc));
625 }
626 if let Some(coord_2d) = &raw_region.coordinate_2d {
627 region.add_property("coordinate_2d".to_string(), serde_json::json!(coord_2d));
628 }
629 if let Some(coord_3d) = &raw_region.coordinate_3d {
630 region.add_property("coordinate_3d".to_string(), serde_json::json!(coord_3d));
631 }
632 if let Some(inputs) = &raw_region.inputs {
634 let input_ids: Vec<String> = inputs
635 .iter()
636 .filter_map(|id| match string_to_cortical_id(id) {
637 Ok(cortical_id) => Some(cortical_id.as_base_64()),
638 Err(e) => {
639 warn!(target: "feagi-evo",
640 "Failed to convert brain region input ID '{}': {}. Skipping.",
641 id, e);
642 None
643 }
644 })
645 .collect();
646 if !input_ids.is_empty() {
647 region.add_property("inputs".to_string(), serde_json::json!(input_ids));
648 }
649 }
650 if let Some(outputs) = &raw_region.outputs {
651 let output_ids: Vec<String> = outputs
652 .iter()
653 .filter_map(|id| match string_to_cortical_id(id) {
654 Ok(cortical_id) => Some(cortical_id.as_base_64()),
655 Err(e) => {
656 warn!(target: "feagi-evo",
657 "Failed to convert brain region output ID '{}': {}. Skipping.",
658 id, e);
659 None
660 }
661 })
662 .collect();
663 if !output_ids.is_empty() {
664 region.add_property("outputs".to_string(), serde_json::json!(output_ids));
665 }
666 }
667 if let Some(signature) = &raw_region.signature {
668 region.add_property("signature".to_string(), serde_json::json!(signature));
669 }
670
671 let parent_id = raw_region.parent_region_id.clone();
673 if let Some(ref parent_id_str) = parent_id {
674 region.add_property(
676 "parent_region_id".to_string(),
677 serde_json::json!(parent_id_str),
678 );
679 }
680
681 regions.push((region, parent_id));
682 }
683
684 Ok(regions)
685 }
686}
687
688#[cfg(test)]
689mod tests {
690 use super::*;
691
692 #[test]
693 fn test_parse_minimal_genome() {
694 let json = r#"{
697 "version": "2.1",
698 "blueprint": {
699 "_power": {
700 "cortical_name": "Test Area",
701 "block_boundaries": [10, 10, 10],
702 "relative_coordinate": [0, 0, 0],
703 "cortical_type": "CORE"
704 }
705 },
706 "brain_regions": {
707 "root": {
708 "title": "Root",
709 "parent_region_id": null,
710 "areas": ["_power"]
711 }
712 }
713 }"#;
714
715 let parsed = GenomeParser::parse(json).unwrap();
716
717 assert_eq!(parsed.version, "2.1");
718 assert_eq!(parsed.cortical_areas.len(), 1);
719 assert_eq!(
721 parsed.cortical_areas[0].cortical_id.as_base_64(),
722 "X19fcG93ZXI="
723 );
724 assert_eq!(parsed.cortical_areas[0].name, "Test Area");
725 assert_eq!(parsed.brain_regions.len(), 1);
726
727 assert!(parsed.cortical_areas[0]
730 .cortical_id
731 .as_cortical_type()
732 .is_ok());
733 }
734
735 #[test]
736 fn test_parse_multiple_areas() {
737 let json = r#"{
739 "version": "2.1",
740 "blueprint": {
741 "_power": {
742 "cortical_name": "Area 1",
743 "cortical_type": "CORE",
744 "block_boundaries": [5, 5, 5],
745 "relative_coordinate": [0, 0, 0]
746 },
747 "_death": {
748 "cortical_name": "Area 2",
749 "cortical_type": "CORE",
750 "block_boundaries": [10, 10, 10],
751 "relative_coordinate": [5, 0, 0]
752 }
753 }
754 }"#;
755
756 let parsed = GenomeParser::parse(json).unwrap();
757
758 assert_eq!(parsed.cortical_areas.len(), 2);
759
760 for area in &parsed.cortical_areas {
762 assert!(
763 area.cortical_id.as_cortical_type().is_ok(),
764 "Area {} should have cortical_type_new populated",
765 area.cortical_id
766 );
767 }
768 }
769
770 #[test]
771 fn test_string_to_cortical_id_legacy_power_shorthand() {
772 use feagi_structures::genomic::cortical_area::CoreCorticalType;
775 let id = string_to_cortical_id("___pwr").unwrap();
776 assert_eq!(
777 id.as_base_64(),
778 CoreCorticalType::Power.to_cortical_id().as_base_64()
779 );
780 }
781
782 #[test]
783 fn test_string_to_cortical_id_legacy_power_padded() {
784 use feagi_structures::genomic::cortical_area::CoreCorticalType;
786 let id = string_to_cortical_id("___pwr__").unwrap();
787 assert_eq!(
788 id.as_base_64(),
789 CoreCorticalType::Power.to_cortical_id().as_base_64()
790 );
791 }
792
793 #[test]
794 fn test_parse_with_properties() {
795 let json = r#"{
796 "version": "2.1",
797 "blueprint": {
798 "mem001": {
799 "cortical_name": "Memory Area",
800 "block_boundaries": [8, 8, 8],
801 "relative_coordinate": [0, 0, 0],
802 "cortical_type": "MEMORY",
803 "is_mem_type": true,
804 "firing_threshold": 50.0,
805 "leak_coefficient": 0.9
806 }
807 }
808 }"#;
809
810 let parsed = GenomeParser::parse(json).unwrap();
811
812 assert_eq!(parsed.cortical_areas.len(), 1);
813 let area = &parsed.cortical_areas[0];
814
815 use feagi_structures::genomic::cortical_area::CorticalAreaType;
817 assert!(matches!(area.cortical_type, CorticalAreaType::Memory(_)));
818
819 assert!(area.properties.contains_key("is_mem_type"));
821 assert!(area.properties.contains_key("firing_threshold"));
822 assert!(area.properties.contains_key("cortical_group"));
823
824 assert!(
826 area.cortical_id.as_cortical_type().is_ok(),
827 "cortical_id should be parseable to cortical_type"
828 );
829 if let Ok(cortical_type) = area.cortical_id.as_cortical_type() {
830 use feagi_structures::genomic::cortical_area::CorticalAreaType;
831 assert!(
832 matches!(cortical_type, CorticalAreaType::Memory(_)),
833 "Should be classified as MEMORY type"
834 );
835 }
836 }
837
838 #[test]
839 fn test_invalid_version() {
840 let json = r#"{
841 "version": "1.0",
842 "blueprint": {}
843 }"#;
844
845 let result = GenomeParser::parse(json);
846 assert!(result.is_err());
847 }
848
849 #[test]
850 fn test_malformed_json() {
851 let json = r#"{ "version": "2.1", "blueprint": { malformed"#;
852
853 let result = GenomeParser::parse(json);
854 assert!(result.is_err());
855 }
856
857 #[test]
858 fn test_cortical_type_new_population() {
859 use feagi_structures::genomic::cortical_area::CoreCorticalType;
862 let power_id = CoreCorticalType::Power.to_cortical_id().as_base_64();
863 let json = format!(
864 r#"{{
865 "version": "2.1",
866 "blueprint": {{
867 "cvision1": {{
868 "cortical_name": "Test Custom Vision",
869 "cortical_type": "CUSTOM",
870 "block_boundaries": [10, 10, 1],
871 "relative_coordinate": [0, 0, 0]
872 }},
873 "cmotor01": {{
874 "cortical_name": "Test Custom Motor",
875 "cortical_type": "CUSTOM",
876 "block_boundaries": [5, 5, 1],
877 "relative_coordinate": [0, 0, 0]
878 }},
879 "{}": {{
880 "cortical_name": "Test Core",
881 "cortical_type": "CORE",
882 "block_boundaries": [1, 1, 1],
883 "relative_coordinate": [0, 0, 0]
884 }}
885 }}
886 }}"#,
887 power_id
888 );
889
890 let parsed = GenomeParser::parse(&json).unwrap();
891 assert_eq!(parsed.cortical_areas.len(), 3);
892
893 for area in &parsed.cortical_areas {
895 assert!(
896 area.cortical_id.as_cortical_type().is_ok(),
897 "Area {} should have cortical_type_new populated",
898 area.cortical_id
899 );
900
901 assert!(
903 area.properties.contains_key("cortical_group"),
904 "Area {} should have cortical_group property",
905 area.cortical_id
906 );
907
908 if let Some(prop_group) = area
910 .properties
911 .get("cortical_group")
912 .and_then(|v| v.as_str())
913 {
914 assert!(
915 !prop_group.is_empty(),
916 "Area {} should have non-empty cortical_group property",
917 area.cortical_id.as_base_64()
918 );
919 }
920 }
921 }
922}