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 #[serde(default, skip_serializing_if = "Option::is_none")]
90 pub genome_schema_version: Option<u32>,
91 pub blueprint: HashMap<String, RawCorticalArea>,
92 #[serde(default)]
93 pub brain_regions: HashMap<String, RawBrainRegion>,
94 #[serde(default)]
95 pub neuron_morphologies: HashMap<String, Value>,
96 #[serde(default)]
97 pub physiology: Option<Value>,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub brain_regions_root: Option<String>,
101}
102
103#[derive(Debug, Clone, Deserialize, Serialize)]
105pub struct RawCorticalArea {
106 pub cortical_name: Option<String>,
107 pub block_boundaries: Option<Vec<u32>>,
108 pub relative_coordinate: Option<Vec<i32>>,
109 pub cortical_type: Option<String>,
110
111 pub group_id: Option<String>,
113 pub sub_group_id: Option<String>,
114 pub per_voxel_neuron_cnt: Option<u32>,
115 pub cortical_mapping_dst: Option<Value>,
116
117 pub synapse_attractivity: Option<f32>,
119 pub refractory_period: Option<u32>,
120 pub firing_threshold: Option<f32>,
121 pub firing_threshold_limit: Option<f32>,
122 pub firing_threshold_increment_x: Option<f32>,
123 pub firing_threshold_increment_y: Option<f32>,
124 pub firing_threshold_increment_z: Option<f32>,
125 pub leak_coefficient: Option<f32>,
126 pub leak_variability: Option<f32>,
127 pub neuron_excitability: Option<f32>,
128 pub postsynaptic_current: Option<f32>,
129 pub postsynaptic_current_max: Option<f32>,
130 pub degeneration: Option<f32>,
131 pub psp_uniform_distribution: Option<bool>,
132 pub mp_charge_accumulation: Option<bool>,
133 pub mp_driven_psp: Option<bool>,
134 pub visualization: Option<bool>,
135 pub burst_engine_activation: Option<bool>,
136 #[serde(rename = "2d_coordinate")]
137 pub coordinate_2d: Option<Vec<i32>>,
138
139 pub is_mem_type: Option<bool>,
141 pub longterm_mem_threshold: Option<u32>,
142 pub lifespan_growth_rate: Option<f32>,
143 pub init_lifespan: Option<u32>,
144 pub temporal_depth: Option<u32>,
145 pub consecutive_fire_cnt_max: Option<u32>,
146 pub snooze_length: Option<u32>,
147
148 #[serde(flatten)]
150 pub other: HashMap<String, Value>,
151}
152
153#[derive(Debug, Clone, Deserialize, Serialize)]
155pub struct RawBrainRegion {
156 #[serde(alias = "name")]
157 pub title: Option<String>,
158 pub description: Option<String>,
159 pub parent_region_id: Option<String>,
160 pub coordinate_2d: Option<Vec<i32>>,
161 pub coordinate_3d: Option<Vec<i32>>,
162 #[serde(alias = "cortical_areas")]
163 pub areas: Option<Vec<String>>,
164 pub regions: Option<Vec<String>>,
165 pub inputs: Option<Vec<String>>,
166 pub outputs: Option<Vec<String>>,
167 pub designated_inputs: Option<Vec<String>>,
169 pub designated_outputs: Option<Vec<String>>,
170 pub signature: Option<String>,
171 pub properties: Option<HashMap<String, Value>>,
173}
174
175fn convert_dstmap_keys_to_base64(dstmap: &Value) -> Value {
179 if let Some(dstmap_obj) = dstmap.as_object() {
180 let mut converted = serde_json::Map::new();
181
182 for (dest_id_str, mapping_value) in dstmap_obj {
183 match string_to_cortical_id(dest_id_str) {
185 Ok(dest_cortical_id) => {
186 converted.insert(dest_cortical_id.as_base_64(), mapping_value.clone());
187 }
188 Err(e) => {
189 tracing::warn!(
191 "Failed to convert dstmap key '{}' to base64: {}, keeping original",
192 dest_id_str,
193 e
194 );
195 converted.insert(dest_id_str.clone(), mapping_value.clone());
196 }
197 }
198 }
199
200 Value::Object(converted)
201 } else {
202 dstmap.clone()
204 }
205}
206
207pub fn string_to_cortical_id(id_str: &str) -> EvoResult<CorticalID> {
211 use feagi_structures::genomic::cortical_area::CoreCorticalType;
212
213 if let Ok(cortical_id) = CorticalID::try_from_base_64(id_str) {
215 let mut bytes = [0u8; CorticalID::CORTICAL_ID_LENGTH];
216 cortical_id.write_id_to_bytes(&mut bytes);
217 if bytes == *b"___power" {
218 return Ok(CoreCorticalType::Power.to_cortical_id());
219 }
220 if bytes == *b"___death" {
221 return Ok(CoreCorticalType::Death.to_cortical_id());
222 }
223 if bytes == *b"___fatig" {
224 return Ok(CoreCorticalType::Fatigue.to_cortical_id());
225 }
226 if bytes == *b"___pain_" {
227 return Ok(CoreCorticalType::Pain.to_cortical_id());
228 }
229 if bytes == *b"___pleas" {
230 return Ok(CoreCorticalType::Pleasure.to_cortical_id());
231 }
232 if bytes == *b"___fear_" {
233 return Ok(CoreCorticalType::Fear.to_cortical_id());
234 }
235 if bytes == *b"___hope_" {
236 return Ok(CoreCorticalType::Hope.to_cortical_id());
237 }
238 return Ok(cortical_id);
239 }
240
241 if id_str == "_power" {
243 return Ok(CoreCorticalType::Power.to_cortical_id());
244 }
245 if id_str == "___pwr" {
247 return Ok(CoreCorticalType::Power.to_cortical_id());
248 }
249 if id_str == "___power" {
251 return Ok(CoreCorticalType::Power.to_cortical_id());
252 }
253 if id_str == "___pwr__" {
255 return Ok(CoreCorticalType::Power.to_cortical_id());
256 }
257 if id_str == "___death" {
258 return Ok(CoreCorticalType::Death.to_cortical_id());
259 }
260 if id_str == "___fatig" {
261 return Ok(CoreCorticalType::Fatigue.to_cortical_id());
262 }
263 if id_str == "___pain_" {
264 return Ok(CoreCorticalType::Pain.to_cortical_id());
265 }
266 if id_str == "___pleas" {
267 return Ok(CoreCorticalType::Pleasure.to_cortical_id());
268 }
269 if id_str == "___fear_" {
270 return Ok(CoreCorticalType::Fear.to_cortical_id());
271 }
272 if id_str == "___hope_" {
273 return Ok(CoreCorticalType::Hope.to_cortical_id());
274 }
275 if id_str == "_death" {
276 return Ok(CoreCorticalType::Death.to_cortical_id());
277 }
278 if id_str == "_fatigue" {
279 return Ok(CoreCorticalType::Fatigue.to_cortical_id());
280 }
281 if id_str == "_pain" {
282 return Ok(CoreCorticalType::Pain.to_cortical_id());
283 }
284 if id_str == "_pleasure" {
285 return Ok(CoreCorticalType::Pleasure.to_cortical_id());
286 }
287 if id_str == "_fear" {
288 return Ok(CoreCorticalType::Fear.to_cortical_id());
289 }
290 if id_str == "_hope" {
291 return Ok(CoreCorticalType::Hope.to_cortical_id());
292 }
293
294 if id_str.len() == 6 || id_str.len() == 8 {
296 CorticalID::try_from_legacy_ascii(id_str).map_err(|e| {
297 EvoError::InvalidArea(format!("Failed to convert cortical_id '{}': {}", id_str, e))
298 })
299 } else {
300 Err(EvoError::InvalidArea(format!(
301 "Invalid cortical_id length: '{}' (expected 6 or 8 ASCII chars, or base64)",
302 id_str
303 )))
304 }
305}
306
307pub struct GenomeParser;
309
310impl GenomeParser {
311 fn normalize_brain_region_cortical_id_list_properties(region: &mut BrainRegion, keys: &[&str]) {
313 for key in keys {
314 let Some(val) = region.get_property(key) else {
315 continue;
316 };
317 let Some(arr) = val.as_array() else {
318 continue;
319 };
320 let mut out: Vec<String> = Vec::new();
321 for item in arr {
322 let Some(s) = item.as_str() else {
323 continue;
324 };
325 match string_to_cortical_id(s) {
326 Ok(cortical_id) => out.push(cortical_id.as_base_64()),
327 Err(e) => {
328 warn!(target: "feagi-evo",
329 "Failed to convert brain region '{}' entry '{}': {}. Skipping.",
330 key, s, e);
331 }
332 }
333 }
334 if out.is_empty() {
335 region.properties.remove(*key);
336 } else {
337 region.add_property((*key).to_string(), serde_json::json!(out));
338 }
339 }
340 }
341
342 pub fn parse(json_str: &str) -> EvoResult<ParsedGenome> {
360 let raw: RawGenome = serde_json::from_str(json_str)
362 .map_err(|e| EvoError::InvalidGenome(format!("Failed to parse JSON: {}", e)))?;
363
364 if !raw.version.starts_with("2.") && !raw.version.starts_with("3.") && raw.version != "3" {
366 return Err(EvoError::InvalidGenome(format!(
367 "Unsupported genome version: {}. Expected 2.x or 3.x",
368 raw.version
369 )));
370 }
371
372 let cortical_areas = Self::parse_cortical_areas(&raw.blueprint)?;
374
375 let brain_regions = Self::parse_brain_regions(&raw.brain_regions)?;
377
378 Ok(ParsedGenome {
379 genome_id: raw.genome_id.unwrap_or_else(|| "unknown".to_string()),
380 genome_title: raw.genome_title.unwrap_or_else(|| "Untitled".to_string()),
381 version: raw.version,
382 cortical_areas,
383 brain_regions,
384 neuron_morphologies: raw.neuron_morphologies,
385 physiology: raw.physiology,
386 })
387 }
388
389 fn parse_cortical_areas(
391 blueprint: &HashMap<String, RawCorticalArea>,
392 ) -> EvoResult<Vec<CorticalArea>> {
393 let mut areas = Vec::with_capacity(blueprint.len());
394
395 for (cortical_id_str, raw_area) in blueprint.iter() {
396 if cortical_id_str.is_empty() {
398 warn!(target: "feagi-evo","Skipping empty cortical_id");
399 continue;
400 }
401
402 let cortical_id = match string_to_cortical_id(cortical_id_str) {
404 Ok(id) => id,
405 Err(e) => {
406 warn!(target: "feagi-evo","Skipping invalid cortical_id '{}': {}", cortical_id_str, e);
407 continue;
408 }
409 };
410
411 let name = raw_area
413 .cortical_name
414 .clone()
415 .unwrap_or_else(|| cortical_id_str.clone());
416
417 let dimensions = if let Some(boundaries) = &raw_area.block_boundaries {
418 if boundaries.len() != 3 {
419 return Err(EvoError::InvalidArea(format!(
420 "Invalid block_boundaries for {}: expected 3 values, got {}",
421 cortical_id_str,
422 boundaries.len()
423 )));
424 }
425 Dimensions::new(boundaries[0], boundaries[1], boundaries[2])
426 .map_err(|e| EvoError::InvalidArea(format!("Invalid dimensions: {}", e)))?
427 } else {
428 warn!(target: "feagi-evo","Cortical area {} missing block_boundaries, defaulting to 1x1x1", cortical_id_str);
430 Dimensions::new(1, 1, 1).map_err(|e| {
431 EvoError::InvalidArea(format!("Invalid default dimensions: {}", e))
432 })?
433 };
434
435 let position = if let Some(coords) = &raw_area.relative_coordinate {
436 if coords.len() != 3 {
437 return Err(EvoError::InvalidArea(format!(
438 "Invalid relative_coordinate for {}: expected 3 values, got {}",
439 cortical_id_str,
440 coords.len()
441 )));
442 }
443 GenomeCoordinate3D::new(coords[0], coords[1], coords[2])
444 } else {
445 warn!(target: "feagi-evo","Cortical area {} missing relative_coordinate, defaulting to (0,0,0)", cortical_id_str);
447 GenomeCoordinate3D::new(0, 0, 0)
448 };
449
450 let cortical_type = cortical_id.as_cortical_type().map_err(|e| {
452 EvoError::InvalidArea(format!(
453 "Failed to determine cortical type from ID {}: {}",
454 cortical_id_str, e
455 ))
456 })?;
457
458 let mut area = CorticalArea::new(
460 cortical_id,
461 0, name,
463 dimensions,
464 position,
465 cortical_type,
466 )?;
467
468 if let Some(ref cortical_type_str) = raw_area.cortical_type {
470 area.properties.insert(
471 "cortical_group".to_string(),
472 serde_json::json!(cortical_type_str),
473 );
474 }
475
476 if let Some(v) = raw_area.synapse_attractivity {
479 area.properties
480 .insert("synapse_attractivity".to_string(), serde_json::json!(v));
481 }
482 if let Some(v) = raw_area.refractory_period {
483 area.properties
484 .insert("refractory_period".to_string(), serde_json::json!(v));
485 }
486 if let Some(v) = raw_area.firing_threshold {
487 area.properties
488 .insert("firing_threshold".to_string(), serde_json::json!(v));
489 }
490 if let Some(v) = raw_area.firing_threshold_limit {
491 area.properties
492 .insert("firing_threshold_limit".to_string(), serde_json::json!(v));
493 }
494 if let Some(v) = raw_area.firing_threshold_increment_x {
495 area.properties.insert(
496 "firing_threshold_increment_x".to_string(),
497 serde_json::json!(v),
498 );
499 }
500 if let Some(v) = raw_area.firing_threshold_increment_y {
501 area.properties.insert(
502 "firing_threshold_increment_y".to_string(),
503 serde_json::json!(v),
504 );
505 }
506 if let Some(v) = raw_area.firing_threshold_increment_z {
507 area.properties.insert(
508 "firing_threshold_increment_z".to_string(),
509 serde_json::json!(v),
510 );
511 }
512 if let Some(v) = raw_area.leak_coefficient {
513 area.properties
514 .insert("leak_coefficient".to_string(), serde_json::json!(v));
515 }
516 if let Some(v) = raw_area.leak_variability {
517 area.properties
518 .insert("leak_variability".to_string(), serde_json::json!(v));
519 }
520 if let Some(v) = raw_area.neuron_excitability {
521 area.properties
522 .insert("neuron_excitability".to_string(), serde_json::json!(v));
523 }
524 if let Some(v) = raw_area.postsynaptic_current {
525 area.properties
526 .insert("postsynaptic_current".to_string(), serde_json::json!(v));
527 }
528 if let Some(v) = raw_area.postsynaptic_current_max {
529 area.properties
530 .insert("postsynaptic_current_max".to_string(), serde_json::json!(v));
531 }
532 if let Some(v) = raw_area.degeneration {
533 area.properties
534 .insert("degeneration".to_string(), serde_json::json!(v));
535 }
536
537 if let Some(v) = raw_area.psp_uniform_distribution {
539 area.properties
540 .insert("psp_uniform_distribution".to_string(), serde_json::json!(v));
541 }
542 if let Some(v) = raw_area.mp_charge_accumulation {
543 area.properties
544 .insert("mp_charge_accumulation".to_string(), serde_json::json!(v));
545 }
546 if let Some(v) = raw_area.mp_driven_psp {
547 area.properties
548 .insert("mp_driven_psp".to_string(), serde_json::json!(v));
549 tracing::info!(
550 target: "feagi-evo",
551 "[GENOME-LOAD] Loaded mp_driven_psp={} for area {}",
552 v,
553 cortical_id_str
554 );
555 } else {
556 tracing::debug!(
557 target: "feagi-evo",
558 "[GENOME-LOAD] mp_driven_psp not found in raw_area for {}, will use default=false",
559 cortical_id_str
560 );
561 }
562 if let Some(v) = raw_area.visualization {
563 area.properties
564 .insert("visualization".to_string(), serde_json::json!(v));
565 area.properties
567 .insert("visible".to_string(), serde_json::json!(v));
568 }
569 if let Some(v) = raw_area.burst_engine_activation {
570 area.properties
571 .insert("burst_engine_active".to_string(), serde_json::json!(v));
572 }
573 if let Some(v) = raw_area.is_mem_type {
574 area.properties
575 .insert("is_mem_type".to_string(), serde_json::json!(v));
576 }
577
578 if let Some(v) = raw_area.longterm_mem_threshold {
580 area.properties
581 .insert("longterm_mem_threshold".to_string(), serde_json::json!(v));
582 }
583 if let Some(v) = raw_area.lifespan_growth_rate {
584 area.properties
585 .insert("lifespan_growth_rate".to_string(), serde_json::json!(v));
586 }
587 if let Some(v) = raw_area.init_lifespan {
588 area.properties
589 .insert("init_lifespan".to_string(), serde_json::json!(v));
590 }
591 if let Some(v) = raw_area.temporal_depth {
592 area.properties
593 .insert("temporal_depth".to_string(), serde_json::json!(v));
594 }
595 if let Some(v) = raw_area.consecutive_fire_cnt_max {
596 area.properties
597 .insert("consecutive_fire_cnt_max".to_string(), serde_json::json!(v));
598 area.properties
600 .insert("consecutive_fire_limit".to_string(), serde_json::json!(v));
601 }
602 if let Some(v) = raw_area.snooze_length {
603 area.properties
604 .insert("snooze_period".to_string(), serde_json::json!(v));
605 }
606
607 if let Some(v) = &raw_area.group_id {
609 area.properties
610 .insert("group_id".to_string(), serde_json::json!(v));
611 }
612 if let Some(v) = &raw_area.sub_group_id {
613 area.properties
614 .insert("sub_group_id".to_string(), serde_json::json!(v));
615 }
616 if let Some(v) = raw_area.per_voxel_neuron_cnt {
618 area.properties
619 .insert("neurons_per_voxel".to_string(), serde_json::json!(v));
620 }
621 if let Some(v) = &raw_area.cortical_mapping_dst {
622 let converted_dstmap = convert_dstmap_keys_to_base64(v);
624 area.properties
625 .insert("cortical_mapping_dst".to_string(), converted_dstmap);
626 }
627 if let Some(v) = &raw_area.coordinate_2d {
628 area.properties
629 .insert("2d_coordinate".to_string(), serde_json::json!(v));
630 }
631
632 for (key, value) in &raw_area.other {
634 area.properties.insert(key.clone(), value.clone());
635 }
636
637 areas.push(area);
641 }
642
643 Ok(areas)
644 }
645
646 fn parse_brain_regions(
648 raw_regions: &HashMap<String, RawBrainRegion>,
649 ) -> EvoResult<Vec<(BrainRegion, Option<String>)>> {
650 let mut regions = Vec::with_capacity(raw_regions.len());
651
652 for (region_id_str, raw_region) in raw_regions.iter() {
653 let title = raw_region
654 .title
655 .clone()
656 .unwrap_or_else(|| region_id_str.clone());
657
658 let region_id = match RegionID::from_string(region_id_str) {
661 Ok(id) => id,
662 Err(_) => {
663 RegionID::new()
666 }
667 };
668
669 let region_type = RegionType::Undefined; let mut region = BrainRegion::new(region_id, title, region_type)?;
672
673 if let Some(props) = &raw_region.properties {
675 for (k, v) in props {
676 region.add_property(k.clone(), v.clone());
677 }
678 }
679
680 if let Some(areas) = &raw_region.areas {
682 for area_id in areas {
683 match string_to_cortical_id(area_id) {
685 Ok(cortical_id) => {
686 region.add_area(cortical_id);
687 }
688 Err(e) => {
689 warn!(target: "feagi-evo",
690 "Failed to convert brain region area ID '{}' to CorticalID: {}. Skipping.",
691 area_id, e);
692 }
693 }
694 }
695 }
696
697 if let Some(desc) = &raw_region.description {
699 region.add_property("description".to_string(), serde_json::json!(desc));
700 }
701 if let Some(coord_2d) = &raw_region.coordinate_2d {
702 region.add_property("coordinate_2d".to_string(), serde_json::json!(coord_2d));
703 }
704 if let Some(coord_3d) = &raw_region.coordinate_3d {
705 region.add_property("coordinate_3d".to_string(), serde_json::json!(coord_3d));
706 }
707 if let Some(inputs) = &raw_region.inputs {
709 let input_ids: Vec<String> = inputs
710 .iter()
711 .filter_map(|id| match string_to_cortical_id(id) {
712 Ok(cortical_id) => Some(cortical_id.as_base_64()),
713 Err(e) => {
714 warn!(target: "feagi-evo",
715 "Failed to convert brain region input ID '{}': {}. Skipping.",
716 id, e);
717 None
718 }
719 })
720 .collect();
721 if !input_ids.is_empty() {
722 region.add_property("inputs".to_string(), serde_json::json!(input_ids));
723 }
724 }
725 if let Some(outputs) = &raw_region.outputs {
726 let output_ids: Vec<String> = outputs
727 .iter()
728 .filter_map(|id| match string_to_cortical_id(id) {
729 Ok(cortical_id) => Some(cortical_id.as_base_64()),
730 Err(e) => {
731 warn!(target: "feagi-evo",
732 "Failed to convert brain region output ID '{}': {}. Skipping.",
733 id, e);
734 None
735 }
736 })
737 .collect();
738 if !output_ids.is_empty() {
739 region.add_property("outputs".to_string(), serde_json::json!(output_ids));
740 }
741 }
742 if let Some(signature) = &raw_region.signature {
743 region.add_property("signature".to_string(), serde_json::json!(signature));
744 }
745
746 if let Some(d) = &raw_region.designated_inputs {
747 let ids: Vec<String> = d
748 .iter()
749 .filter_map(|id| match string_to_cortical_id(id) {
750 Ok(cortical_id) => Some(cortical_id.as_base_64()),
751 Err(e) => {
752 warn!(target: "feagi-evo",
753 "Failed to convert designated_inputs entry '{}': {}. Skipping.",
754 id, e);
755 None
756 }
757 })
758 .collect();
759 if !ids.is_empty() {
760 region.add_property("designated_inputs".to_string(), serde_json::json!(ids));
761 }
762 }
763 if let Some(d) = &raw_region.designated_outputs {
764 let ids: Vec<String> = d
765 .iter()
766 .filter_map(|id| match string_to_cortical_id(id) {
767 Ok(cortical_id) => Some(cortical_id.as_base_64()),
768 Err(e) => {
769 warn!(target: "feagi-evo",
770 "Failed to convert designated_outputs entry '{}': {}. Skipping.",
771 id, e);
772 None
773 }
774 })
775 .collect();
776 if !ids.is_empty() {
777 region.add_property("designated_outputs".to_string(), serde_json::json!(ids));
778 }
779 }
780
781 Self::normalize_brain_region_cortical_id_list_properties(
782 &mut region,
783 &[
784 "inputs",
785 "outputs",
786 "designated_inputs",
787 "designated_outputs",
788 ],
789 );
790
791 let parent_id = raw_region.parent_region_id.clone();
793 if let Some(ref parent_id_str) = parent_id {
794 region.add_property(
796 "parent_region_id".to_string(),
797 serde_json::json!(parent_id_str),
798 );
799 }
800
801 regions.push((region, parent_id));
802 }
803
804 Ok(regions)
805 }
806}
807
808#[cfg(test)]
809mod tests {
810 use super::*;
811
812 #[test]
813 fn test_parse_minimal_genome() {
814 let json = r#"{
817 "version": "2.1",
818 "blueprint": {
819 "_power": {
820 "cortical_name": "Test Area",
821 "block_boundaries": [10, 10, 10],
822 "relative_coordinate": [0, 0, 0],
823 "cortical_type": "CORE"
824 }
825 },
826 "brain_regions": {
827 "root": {
828 "title": "Root",
829 "parent_region_id": null,
830 "areas": ["_power"]
831 }
832 }
833 }"#;
834
835 let parsed = GenomeParser::parse(json).unwrap();
836
837 assert_eq!(parsed.version, "2.1");
838 assert_eq!(parsed.cortical_areas.len(), 1);
839 assert_eq!(
841 parsed.cortical_areas[0].cortical_id.as_base_64(),
842 "X19fcG93ZXI="
843 );
844 assert_eq!(parsed.cortical_areas[0].name, "Test Area");
845 assert_eq!(parsed.brain_regions.len(), 1);
846
847 assert!(parsed.cortical_areas[0]
850 .cortical_id
851 .as_cortical_type()
852 .is_ok());
853 }
854
855 #[test]
856 fn test_parse_multiple_areas() {
857 let json = r#"{
859 "version": "2.1",
860 "blueprint": {
861 "_power": {
862 "cortical_name": "Area 1",
863 "cortical_type": "CORE",
864 "block_boundaries": [5, 5, 5],
865 "relative_coordinate": [0, 0, 0]
866 },
867 "_death": {
868 "cortical_name": "Area 2",
869 "cortical_type": "CORE",
870 "block_boundaries": [10, 10, 10],
871 "relative_coordinate": [5, 0, 0]
872 }
873 }
874 }"#;
875
876 let parsed = GenomeParser::parse(json).unwrap();
877
878 assert_eq!(parsed.cortical_areas.len(), 2);
879
880 for area in &parsed.cortical_areas {
882 assert!(
883 area.cortical_id.as_cortical_type().is_ok(),
884 "Area {} should have cortical_type_new populated",
885 area.cortical_id
886 );
887 }
888 }
889
890 #[test]
891 fn test_string_to_cortical_id_legacy_power_shorthand() {
892 use feagi_structures::genomic::cortical_area::CoreCorticalType;
895 let id = string_to_cortical_id("___pwr").unwrap();
896 assert_eq!(
897 id.as_base_64(),
898 CoreCorticalType::Power.to_cortical_id().as_base_64()
899 );
900 }
901
902 #[test]
903 fn test_string_to_cortical_id_legacy_power_padded() {
904 use feagi_structures::genomic::cortical_area::CoreCorticalType;
906 let id = string_to_cortical_id("___pwr__").unwrap();
907 assert_eq!(
908 id.as_base_64(),
909 CoreCorticalType::Power.to_cortical_id().as_base_64()
910 );
911 }
912
913 #[test]
914 fn test_parse_with_properties() {
915 let json = r#"{
916 "version": "2.1",
917 "blueprint": {
918 "mem001": {
919 "cortical_name": "Memory Area",
920 "block_boundaries": [8, 8, 8],
921 "relative_coordinate": [0, 0, 0],
922 "cortical_type": "MEMORY",
923 "is_mem_type": true,
924 "firing_threshold": 50.0,
925 "leak_coefficient": 0.9
926 }
927 }
928 }"#;
929
930 let parsed = GenomeParser::parse(json).unwrap();
931
932 assert_eq!(parsed.cortical_areas.len(), 1);
933 let area = &parsed.cortical_areas[0];
934
935 use feagi_structures::genomic::cortical_area::CorticalAreaType;
937 assert!(matches!(area.cortical_type, CorticalAreaType::Memory(_)));
938
939 assert!(area.properties.contains_key("is_mem_type"));
941 assert!(area.properties.contains_key("firing_threshold"));
942 assert!(area.properties.contains_key("cortical_group"));
943
944 assert!(
946 area.cortical_id.as_cortical_type().is_ok(),
947 "cortical_id should be parseable to cortical_type"
948 );
949 if let Ok(cortical_type) = area.cortical_id.as_cortical_type() {
950 use feagi_structures::genomic::cortical_area::CorticalAreaType;
951 assert!(
952 matches!(cortical_type, CorticalAreaType::Memory(_)),
953 "Should be classified as MEMORY type"
954 );
955 }
956 }
957
958 #[test]
960 fn test_parse_v3_brain_region_nested_properties_retains_designated_io() {
961 let json = r#"{
962 "version": "3.0",
963 "blueprint": {
964 "_power": {
965 "cortical_name": "Core",
966 "block_boundaries": [10, 10, 10],
967 "relative_coordinate": [0, 0, 0],
968 "cortical_type": "CORE"
969 }
970 },
971 "brain_regions": {
972 "550e8400-e29b-41d4-a716-446655440000": {
973 "name": "Sub",
974 "cortical_areas": ["_power"],
975 "properties": {
976 "designated_inputs": ["_power"],
977 "designated_outputs": []
978 }
979 }
980 }
981 }"#;
982
983 let parsed = GenomeParser::parse(json).unwrap();
984 assert_eq!(parsed.brain_regions.len(), 1);
985 let (region, _) = &parsed.brain_regions[0];
986 let di = region
987 .get_property("designated_inputs")
988 .and_then(|v| v.as_array())
989 .expect("designated_inputs");
990 assert_eq!(di.len(), 1);
991 assert_eq!(di[0].as_str().unwrap(), "X19fcG93ZXI=");
992 }
993
994 #[test]
995 fn test_invalid_version() {
996 let json = r#"{
997 "version": "1.0",
998 "blueprint": {}
999 }"#;
1000
1001 let result = GenomeParser::parse(json);
1002 assert!(result.is_err());
1003 }
1004
1005 #[test]
1006 fn test_malformed_json() {
1007 let json = r#"{ "version": "2.1", "blueprint": { malformed"#;
1008
1009 let result = GenomeParser::parse(json);
1010 assert!(result.is_err());
1011 }
1012
1013 #[test]
1014 fn test_cortical_type_new_population() {
1015 use feagi_structures::genomic::cortical_area::CoreCorticalType;
1018 let power_id = CoreCorticalType::Power.to_cortical_id().as_base_64();
1019 let json = format!(
1020 r#"{{
1021 "version": "2.1",
1022 "blueprint": {{
1023 "cvision1": {{
1024 "cortical_name": "Test Custom Vision",
1025 "cortical_type": "CUSTOM",
1026 "block_boundaries": [10, 10, 1],
1027 "relative_coordinate": [0, 0, 0]
1028 }},
1029 "cmotor01": {{
1030 "cortical_name": "Test Custom Motor",
1031 "cortical_type": "CUSTOM",
1032 "block_boundaries": [5, 5, 1],
1033 "relative_coordinate": [0, 0, 0]
1034 }},
1035 "{}": {{
1036 "cortical_name": "Test Core",
1037 "cortical_type": "CORE",
1038 "block_boundaries": [1, 1, 1],
1039 "relative_coordinate": [0, 0, 0]
1040 }}
1041 }}
1042 }}"#,
1043 power_id
1044 );
1045
1046 let parsed = GenomeParser::parse(&json).unwrap();
1047 assert_eq!(parsed.cortical_areas.len(), 3);
1048
1049 for area in &parsed.cortical_areas {
1051 assert!(
1052 area.cortical_id.as_cortical_type().is_ok(),
1053 "Area {} should have cortical_type_new populated",
1054 area.cortical_id
1055 );
1056
1057 assert!(
1059 area.properties.contains_key("cortical_group"),
1060 "Area {} should have cortical_group property",
1061 area.cortical_id
1062 );
1063
1064 if let Some(prop_group) = area
1066 .properties
1067 .get("cortical_group")
1068 .and_then(|v| v.as_str())
1069 {
1070 assert!(
1071 !prop_group.is_empty(),
1072 "Area {} should have non-empty cortical_group property",
1073 area.cortical_id.as_base_64()
1074 );
1075 }
1076 }
1077 }
1078}