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 == "___death" {
229 return Ok(CoreCorticalType::Death.to_cortical_id());
230 }
231 if id_str == "___fatig" {
232 return Ok(CoreCorticalType::Fatigue.to_cortical_id());
233 }
234 if id_str == "_death" {
235 return Ok(CoreCorticalType::Death.to_cortical_id());
236 }
237 if id_str == "_fatigue" {
238 return Ok(CoreCorticalType::Fatigue.to_cortical_id());
239 }
240
241 if id_str.len() == 6 {
243 let mut bytes = [b'_'; 8];
245 bytes[..6].copy_from_slice(id_str.as_bytes());
246
247 CorticalID::try_from_bytes(&bytes).map_err(|e| {
248 EvoError::InvalidArea(format!("Failed to convert cortical_id '{}': {}", id_str, e))
249 })
250 } else if id_str.len() == 8 {
251 let mut bytes = [0u8; 8];
253 bytes.copy_from_slice(id_str.as_bytes());
254
255 CorticalID::try_from_bytes(&bytes).map_err(|e| {
256 EvoError::InvalidArea(format!("Failed to convert cortical_id '{}': {}", id_str, e))
257 })
258 } else {
259 Err(EvoError::InvalidArea(format!(
260 "Invalid cortical_id length: '{}' (expected 6 or 8 ASCII chars, or base64)",
261 id_str
262 )))
263 }
264}
265
266pub struct GenomeParser;
268
269impl GenomeParser {
270 pub fn parse(json_str: &str) -> EvoResult<ParsedGenome> {
288 let raw: RawGenome = serde_json::from_str(json_str)
290 .map_err(|e| EvoError::InvalidGenome(format!("Failed to parse JSON: {}", e)))?;
291
292 if !raw.version.starts_with("2.") && raw.version != "3.0" {
294 return Err(EvoError::InvalidGenome(format!(
295 "Unsupported genome version: {}. Expected 2.x or 3.0",
296 raw.version
297 )));
298 }
299
300 let cortical_areas = Self::parse_cortical_areas(&raw.blueprint)?;
302
303 let brain_regions = Self::parse_brain_regions(&raw.brain_regions)?;
305
306 Ok(ParsedGenome {
307 genome_id: raw.genome_id.unwrap_or_else(|| "unknown".to_string()),
308 genome_title: raw.genome_title.unwrap_or_else(|| "Untitled".to_string()),
309 version: raw.version,
310 cortical_areas,
311 brain_regions,
312 neuron_morphologies: raw.neuron_morphologies,
313 physiology: raw.physiology,
314 })
315 }
316
317 fn parse_cortical_areas(
319 blueprint: &HashMap<String, RawCorticalArea>,
320 ) -> EvoResult<Vec<CorticalArea>> {
321 let mut areas = Vec::with_capacity(blueprint.len());
322
323 for (cortical_id_str, raw_area) in blueprint.iter() {
324 if cortical_id_str.is_empty() {
326 warn!(target: "feagi-evo","Skipping empty cortical_id");
327 continue;
328 }
329
330 let cortical_id = match string_to_cortical_id(cortical_id_str) {
332 Ok(id) => id,
333 Err(e) => {
334 warn!(target: "feagi-evo","Skipping invalid cortical_id '{}': {}", cortical_id_str, e);
335 continue;
336 }
337 };
338
339 let name = raw_area
341 .cortical_name
342 .clone()
343 .unwrap_or_else(|| cortical_id_str.clone());
344
345 let dimensions = if let Some(boundaries) = &raw_area.block_boundaries {
346 if boundaries.len() != 3 {
347 return Err(EvoError::InvalidArea(format!(
348 "Invalid block_boundaries for {}: expected 3 values, got {}",
349 cortical_id_str,
350 boundaries.len()
351 )));
352 }
353 Dimensions::new(boundaries[0], boundaries[1], boundaries[2])
354 .map_err(|e| EvoError::InvalidArea(format!("Invalid dimensions: {}", e)))?
355 } else {
356 warn!(target: "feagi-evo","Cortical area {} missing block_boundaries, defaulting to 1x1x1", cortical_id_str);
358 Dimensions::new(1, 1, 1).map_err(|e| {
359 EvoError::InvalidArea(format!("Invalid default dimensions: {}", e))
360 })?
361 };
362
363 let position = if let Some(coords) = &raw_area.relative_coordinate {
364 if coords.len() != 3 {
365 return Err(EvoError::InvalidArea(format!(
366 "Invalid relative_coordinate for {}: expected 3 values, got {}",
367 cortical_id_str,
368 coords.len()
369 )));
370 }
371 GenomeCoordinate3D::new(coords[0], coords[1], coords[2])
372 } else {
373 warn!(target: "feagi-evo","Cortical area {} missing relative_coordinate, defaulting to (0,0,0)", cortical_id_str);
375 GenomeCoordinate3D::new(0, 0, 0)
376 };
377
378 let cortical_type = cortical_id.as_cortical_type().map_err(|e| {
380 EvoError::InvalidArea(format!(
381 "Failed to determine cortical type from ID {}: {}",
382 cortical_id_str, e
383 ))
384 })?;
385
386 let mut area = CorticalArea::new(
388 cortical_id,
389 0, name,
391 dimensions,
392 position,
393 cortical_type,
394 )?;
395
396 if let Some(ref cortical_type_str) = raw_area.cortical_type {
398 area.properties.insert(
399 "cortical_group".to_string(),
400 serde_json::json!(cortical_type_str),
401 );
402 }
403
404 if let Some(v) = raw_area.synapse_attractivity {
407 area.properties
408 .insert("synapse_attractivity".to_string(), serde_json::json!(v));
409 }
410 if let Some(v) = raw_area.refractory_period {
411 area.properties
412 .insert("refractory_period".to_string(), serde_json::json!(v));
413 }
414 if let Some(v) = raw_area.firing_threshold {
415 area.properties
416 .insert("firing_threshold".to_string(), serde_json::json!(v));
417 }
418 if let Some(v) = raw_area.firing_threshold_limit {
419 area.properties
420 .insert("firing_threshold_limit".to_string(), serde_json::json!(v));
421 }
422 if let Some(v) = raw_area.firing_threshold_increment_x {
423 area.properties.insert(
424 "firing_threshold_increment_x".to_string(),
425 serde_json::json!(v),
426 );
427 }
428 if let Some(v) = raw_area.firing_threshold_increment_y {
429 area.properties.insert(
430 "firing_threshold_increment_y".to_string(),
431 serde_json::json!(v),
432 );
433 }
434 if let Some(v) = raw_area.firing_threshold_increment_z {
435 area.properties.insert(
436 "firing_threshold_increment_z".to_string(),
437 serde_json::json!(v),
438 );
439 }
440 if let Some(v) = raw_area.leak_coefficient {
441 area.properties
442 .insert("leak_coefficient".to_string(), serde_json::json!(v));
443 }
444 if let Some(v) = raw_area.leak_variability {
445 area.properties
446 .insert("leak_variability".to_string(), serde_json::json!(v));
447 }
448 if let Some(v) = raw_area.neuron_excitability {
449 area.properties
450 .insert("neuron_excitability".to_string(), serde_json::json!(v));
451 }
452 if let Some(v) = raw_area.postsynaptic_current {
453 area.properties
454 .insert("postsynaptic_current".to_string(), serde_json::json!(v));
455 }
456 if let Some(v) = raw_area.postsynaptic_current_max {
457 area.properties
458 .insert("postsynaptic_current_max".to_string(), serde_json::json!(v));
459 }
460 if let Some(v) = raw_area.degeneration {
461 area.properties
462 .insert("degeneration".to_string(), serde_json::json!(v));
463 }
464
465 if let Some(v) = raw_area.psp_uniform_distribution {
467 area.properties
468 .insert("psp_uniform_distribution".to_string(), serde_json::json!(v));
469 }
470 if let Some(v) = raw_area.mp_charge_accumulation {
471 area.properties
472 .insert("mp_charge_accumulation".to_string(), serde_json::json!(v));
473 }
474 if let Some(v) = raw_area.mp_driven_psp {
475 area.properties
476 .insert("mp_driven_psp".to_string(), serde_json::json!(v));
477 tracing::info!(
478 target: "feagi-evo",
479 "[GENOME-LOAD] Loaded mp_driven_psp={} for area {}",
480 v,
481 cortical_id_str
482 );
483 } else {
484 tracing::debug!(
485 target: "feagi-evo",
486 "[GENOME-LOAD] mp_driven_psp not found in raw_area for {}, will use default=false",
487 cortical_id_str
488 );
489 }
490 if let Some(v) = raw_area.visualization {
491 area.properties
492 .insert("visualization".to_string(), serde_json::json!(v));
493 area.properties
495 .insert("visible".to_string(), serde_json::json!(v));
496 }
497 if let Some(v) = raw_area.burst_engine_activation {
498 area.properties
499 .insert("burst_engine_active".to_string(), serde_json::json!(v));
500 }
501 if let Some(v) = raw_area.is_mem_type {
502 area.properties
503 .insert("is_mem_type".to_string(), serde_json::json!(v));
504 }
505
506 if let Some(v) = raw_area.longterm_mem_threshold {
508 area.properties
509 .insert("longterm_mem_threshold".to_string(), serde_json::json!(v));
510 }
511 if let Some(v) = raw_area.lifespan_growth_rate {
512 area.properties
513 .insert("lifespan_growth_rate".to_string(), serde_json::json!(v));
514 }
515 if let Some(v) = raw_area.init_lifespan {
516 area.properties
517 .insert("init_lifespan".to_string(), serde_json::json!(v));
518 }
519 if let Some(v) = raw_area.temporal_depth {
520 area.properties
521 .insert("temporal_depth".to_string(), serde_json::json!(v));
522 }
523 if let Some(v) = raw_area.consecutive_fire_cnt_max {
524 area.properties
525 .insert("consecutive_fire_cnt_max".to_string(), serde_json::json!(v));
526 area.properties
528 .insert("consecutive_fire_limit".to_string(), serde_json::json!(v));
529 }
530 if let Some(v) = raw_area.snooze_length {
531 area.properties
532 .insert("snooze_period".to_string(), serde_json::json!(v));
533 }
534
535 if let Some(v) = &raw_area.group_id {
537 area.properties
538 .insert("group_id".to_string(), serde_json::json!(v));
539 }
540 if let Some(v) = &raw_area.sub_group_id {
541 area.properties
542 .insert("sub_group_id".to_string(), serde_json::json!(v));
543 }
544 if let Some(v) = raw_area.per_voxel_neuron_cnt {
546 area.properties
547 .insert("neurons_per_voxel".to_string(), serde_json::json!(v));
548 }
549 if let Some(v) = &raw_area.cortical_mapping_dst {
550 let converted_dstmap = convert_dstmap_keys_to_base64(v);
552 area.properties
553 .insert("cortical_mapping_dst".to_string(), converted_dstmap);
554 }
555 if let Some(v) = &raw_area.coordinate_2d {
556 area.properties
557 .insert("2d_coordinate".to_string(), serde_json::json!(v));
558 }
559
560 for (key, value) in &raw_area.other {
562 area.properties.insert(key.clone(), value.clone());
563 }
564
565 areas.push(area);
569 }
570
571 Ok(areas)
572 }
573
574 fn parse_brain_regions(
576 raw_regions: &HashMap<String, RawBrainRegion>,
577 ) -> EvoResult<Vec<(BrainRegion, Option<String>)>> {
578 let mut regions = Vec::with_capacity(raw_regions.len());
579
580 for (region_id_str, raw_region) in raw_regions.iter() {
581 let title = raw_region
582 .title
583 .clone()
584 .unwrap_or_else(|| region_id_str.clone());
585
586 let region_id = match RegionID::from_string(region_id_str) {
589 Ok(id) => id,
590 Err(_) => {
591 RegionID::new()
594 }
595 };
596
597 let region_type = RegionType::Undefined; let mut region = BrainRegion::new(region_id, title, region_type)?;
600
601 if let Some(areas) = &raw_region.areas {
603 for area_id in areas {
604 match string_to_cortical_id(area_id) {
606 Ok(cortical_id) => {
607 region.add_area(cortical_id);
608 }
609 Err(e) => {
610 warn!(target: "feagi-evo",
611 "Failed to convert brain region area ID '{}' to CorticalID: {}. Skipping.",
612 area_id, e);
613 }
614 }
615 }
616 }
617
618 if let Some(desc) = &raw_region.description {
620 region.add_property("description".to_string(), serde_json::json!(desc));
621 }
622 if let Some(coord_2d) = &raw_region.coordinate_2d {
623 region.add_property("coordinate_2d".to_string(), serde_json::json!(coord_2d));
624 }
625 if let Some(coord_3d) = &raw_region.coordinate_3d {
626 region.add_property("coordinate_3d".to_string(), serde_json::json!(coord_3d));
627 }
628 if let Some(inputs) = &raw_region.inputs {
630 let input_ids: Vec<String> = inputs
631 .iter()
632 .filter_map(|id| match string_to_cortical_id(id) {
633 Ok(cortical_id) => Some(cortical_id.as_base_64()),
634 Err(e) => {
635 warn!(target: "feagi-evo",
636 "Failed to convert brain region input ID '{}': {}. Skipping.",
637 id, e);
638 None
639 }
640 })
641 .collect();
642 if !input_ids.is_empty() {
643 region.add_property("inputs".to_string(), serde_json::json!(input_ids));
644 }
645 }
646 if let Some(outputs) = &raw_region.outputs {
647 let output_ids: Vec<String> = outputs
648 .iter()
649 .filter_map(|id| match string_to_cortical_id(id) {
650 Ok(cortical_id) => Some(cortical_id.as_base_64()),
651 Err(e) => {
652 warn!(target: "feagi-evo",
653 "Failed to convert brain region output ID '{}': {}. Skipping.",
654 id, e);
655 None
656 }
657 })
658 .collect();
659 if !output_ids.is_empty() {
660 region.add_property("outputs".to_string(), serde_json::json!(output_ids));
661 }
662 }
663 if let Some(signature) = &raw_region.signature {
664 region.add_property("signature".to_string(), serde_json::json!(signature));
665 }
666
667 let parent_id = raw_region.parent_region_id.clone();
669 if let Some(ref parent_id_str) = parent_id {
670 region.add_property(
672 "parent_region_id".to_string(),
673 serde_json::json!(parent_id_str),
674 );
675 }
676
677 regions.push((region, parent_id));
678 }
679
680 Ok(regions)
681 }
682}
683
684#[cfg(test)]
685mod tests {
686 use super::*;
687
688 #[test]
689 fn test_parse_minimal_genome() {
690 let json = r#"{
693 "version": "2.1",
694 "blueprint": {
695 "_power": {
696 "cortical_name": "Test Area",
697 "block_boundaries": [10, 10, 10],
698 "relative_coordinate": [0, 0, 0],
699 "cortical_type": "CORE"
700 }
701 },
702 "brain_regions": {
703 "root": {
704 "title": "Root",
705 "parent_region_id": null,
706 "areas": ["_power"]
707 }
708 }
709 }"#;
710
711 let parsed = GenomeParser::parse(json).unwrap();
712
713 assert_eq!(parsed.version, "2.1");
714 assert_eq!(parsed.cortical_areas.len(), 1);
715 assert_eq!(
717 parsed.cortical_areas[0].cortical_id.as_base_64(),
718 "X19fcG93ZXI="
719 );
720 assert_eq!(parsed.cortical_areas[0].name, "Test Area");
721 assert_eq!(parsed.brain_regions.len(), 1);
722
723 assert!(parsed.cortical_areas[0]
726 .cortical_id
727 .as_cortical_type()
728 .is_ok());
729 }
730
731 #[test]
732 fn test_parse_multiple_areas() {
733 let json = r#"{
735 "version": "2.1",
736 "blueprint": {
737 "_power": {
738 "cortical_name": "Area 1",
739 "cortical_type": "CORE",
740 "block_boundaries": [5, 5, 5],
741 "relative_coordinate": [0, 0, 0]
742 },
743 "_death": {
744 "cortical_name": "Area 2",
745 "cortical_type": "CORE",
746 "block_boundaries": [10, 10, 10],
747 "relative_coordinate": [5, 0, 0]
748 }
749 }
750 }"#;
751
752 let parsed = GenomeParser::parse(json).unwrap();
753
754 assert_eq!(parsed.cortical_areas.len(), 2);
755
756 for area in &parsed.cortical_areas {
758 assert!(
759 area.cortical_id.as_cortical_type().is_ok(),
760 "Area {} should have cortical_type_new populated",
761 area.cortical_id
762 );
763 }
764 }
765
766 #[test]
767 fn test_string_to_cortical_id_legacy_power_shorthand() {
768 use feagi_structures::genomic::cortical_area::CoreCorticalType;
771 let id = string_to_cortical_id("___pwr").unwrap();
772 assert_eq!(
773 id.as_base_64(),
774 CoreCorticalType::Power.to_cortical_id().as_base_64()
775 );
776 }
777
778 #[test]
779 fn test_parse_with_properties() {
780 let json = r#"{
781 "version": "2.1",
782 "blueprint": {
783 "mem001": {
784 "cortical_name": "Memory Area",
785 "block_boundaries": [8, 8, 8],
786 "relative_coordinate": [0, 0, 0],
787 "cortical_type": "MEMORY",
788 "is_mem_type": true,
789 "firing_threshold": 50.0,
790 "leak_coefficient": 0.9
791 }
792 }
793 }"#;
794
795 let parsed = GenomeParser::parse(json).unwrap();
796
797 assert_eq!(parsed.cortical_areas.len(), 1);
798 let area = &parsed.cortical_areas[0];
799
800 use feagi_structures::genomic::cortical_area::CorticalAreaType;
802 assert!(matches!(area.cortical_type, CorticalAreaType::Memory(_)));
803
804 assert!(area.properties.contains_key("is_mem_type"));
806 assert!(area.properties.contains_key("firing_threshold"));
807 assert!(area.properties.contains_key("cortical_group"));
808
809 assert!(
811 area.cortical_id.as_cortical_type().is_ok(),
812 "cortical_id should be parseable to cortical_type"
813 );
814 if let Ok(cortical_type) = area.cortical_id.as_cortical_type() {
815 use feagi_structures::genomic::cortical_area::CorticalAreaType;
816 assert!(
817 matches!(cortical_type, CorticalAreaType::Memory(_)),
818 "Should be classified as MEMORY type"
819 );
820 }
821 }
822
823 #[test]
824 fn test_invalid_version() {
825 let json = r#"{
826 "version": "1.0",
827 "blueprint": {}
828 }"#;
829
830 let result = GenomeParser::parse(json);
831 assert!(result.is_err());
832 }
833
834 #[test]
835 fn test_malformed_json() {
836 let json = r#"{ "version": "2.1", "blueprint": { malformed"#;
837
838 let result = GenomeParser::parse(json);
839 assert!(result.is_err());
840 }
841
842 #[test]
843 fn test_cortical_type_new_population() {
844 use feagi_structures::genomic::cortical_area::CoreCorticalType;
847 let power_id = CoreCorticalType::Power.to_cortical_id().as_base_64();
848 let json = format!(
849 r#"{{
850 "version": "2.1",
851 "blueprint": {{
852 "cvision1": {{
853 "cortical_name": "Test Custom Vision",
854 "cortical_type": "CUSTOM",
855 "block_boundaries": [10, 10, 1],
856 "relative_coordinate": [0, 0, 0]
857 }},
858 "cmotor01": {{
859 "cortical_name": "Test Custom Motor",
860 "cortical_type": "CUSTOM",
861 "block_boundaries": [5, 5, 1],
862 "relative_coordinate": [0, 0, 0]
863 }},
864 "{}": {{
865 "cortical_name": "Test Core",
866 "cortical_type": "CORE",
867 "block_boundaries": [1, 1, 1],
868 "relative_coordinate": [0, 0, 0]
869 }}
870 }}
871 }}"#,
872 power_id
873 );
874
875 let parsed = GenomeParser::parse(&json).unwrap();
876 assert_eq!(parsed.cortical_areas.len(), 3);
877
878 for area in &parsed.cortical_areas {
880 assert!(
881 area.cortical_id.as_cortical_type().is_ok(),
882 "Area {} should have cortical_type_new populated",
883 area.cortical_id
884 );
885
886 assert!(
888 area.properties.contains_key("cortical_group"),
889 "Area {} should have cortical_group property",
890 area.cortical_id
891 );
892
893 if let Some(prop_group) = area
895 .properties
896 .get("cortical_group")
897 .and_then(|v| v.as_str())
898 {
899 assert!(
900 !prop_group.is_empty(),
901 "Area {} should have non-empty cortical_group property",
902 area.cortical_id.as_base_64()
903 );
904 }
905 }
906 }
907}