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