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