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::classifiers::Classifier;
51use feagi_structures::genomic::cortical_area::CorticalID;
52use feagi_structures::genomic::cortical_area::{
53 CorticalArea, CorticalAreaDimensions as Dimensions,
54};
55use feagi_structures::genomic::descriptors::GenomeCoordinate3D;
56use feagi_structures::genomic::{BrainRegion, RegionType};
57
58#[derive(Debug, Clone)]
60pub struct ParsedGenome {
61 pub genome_id: String,
63 pub genome_title: String,
64 pub version: String,
65
66 pub cortical_areas: Vec<CorticalArea>,
68
69 pub brain_regions: Vec<(BrainRegion, Option<String>)>, pub classifiers: Vec<Classifier>,
74
75 pub neuron_morphologies: HashMap<String, Value>,
77
78 pub physiology: Option<Value>,
80}
81
82#[derive(Debug, Clone, Deserialize, Serialize)]
84pub struct RawGenome {
85 pub genome_id: Option<String>,
86 pub genome_title: Option<String>,
87 pub genome_description: Option<String>,
88 pub version: String,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub genome_schema_version: Option<u32>,
95 pub blueprint: HashMap<String, RawCorticalArea>,
96 #[serde(default)]
97 pub brain_regions: HashMap<String, RawBrainRegion>,
98 #[serde(default)]
99 pub classifiers: HashMap<String, RawClassifier>,
100 #[serde(default)]
101 pub neuron_morphologies: HashMap<String, Value>,
102 #[serde(default)]
103 pub physiology: Option<Value>,
104 #[serde(default, skip_serializing_if = "Option::is_none")]
106 pub brain_regions_root: Option<String>,
107}
108
109#[derive(Debug, Clone, Deserialize, Serialize)]
111pub struct RawCorticalArea {
112 pub cortical_name: Option<String>,
113 pub block_boundaries: Option<Vec<u32>>,
114 pub relative_coordinate: Option<Vec<i32>>,
115 pub cortical_type: Option<String>,
116
117 pub group_id: Option<String>,
119 pub sub_group_id: Option<String>,
120 pub per_voxel_neuron_cnt: Option<u32>,
121 pub cortical_mapping_dst: Option<Value>,
122
123 pub synapse_attractivity: Option<f32>,
125 pub refractory_period: Option<u32>,
126 pub firing_threshold: Option<f32>,
127 pub firing_threshold_limit: Option<f32>,
128 pub firing_threshold_increment_x: Option<f32>,
129 pub firing_threshold_increment_y: Option<f32>,
130 pub firing_threshold_increment_z: Option<f32>,
131 pub leak_coefficient: Option<f32>,
132 pub leak_variability: Option<f32>,
133 pub neuron_excitability: Option<f32>,
134 pub postsynaptic_current: Option<f32>,
135 pub postsynaptic_current_max: Option<f32>,
136 pub degeneration: Option<f32>,
137 pub psp_uniform_distribution: Option<bool>,
138 pub mp_charge_accumulation: Option<bool>,
139 pub mp_driven_psp: Option<bool>,
140 pub visualization: Option<bool>,
141 pub burst_engine_activation: Option<bool>,
142 #[serde(rename = "2d_coordinate")]
143 pub coordinate_2d: Option<Vec<i32>>,
144
145 pub is_mem_type: Option<bool>,
147 pub longterm_mem_threshold: Option<u32>,
148 pub lifespan_growth_rate: Option<f32>,
149 pub init_lifespan: Option<u32>,
150 pub temporal_depth: Option<u32>,
151 pub mp_learning_enabled: Option<bool>,
152 pub min_window_activity: Option<u32>,
153 pub scan_skip_density: Option<f32>,
154 pub consecutive_fire_cnt_max: Option<u32>,
155 pub snooze_length: Option<u32>,
156
157 #[serde(flatten)]
159 pub other: HashMap<String, Value>,
160}
161
162#[derive(Debug, Clone, Deserialize, Serialize)]
164pub struct RawBrainRegion {
165 #[serde(alias = "name")]
166 pub title: Option<String>,
167 pub description: Option<String>,
168 pub parent_region_id: Option<String>,
169 pub coordinate_2d: Option<Vec<i32>>,
170 pub coordinate_3d: Option<Vec<i32>>,
171 #[serde(alias = "cortical_areas")]
172 pub areas: Option<Vec<String>>,
173 pub regions: Option<Vec<String>>,
174 pub inputs: Option<Vec<String>>,
175 pub outputs: Option<Vec<String>>,
176 pub designated_inputs: Option<Vec<String>>,
178 pub designated_outputs: Option<Vec<String>>,
179 pub signature: Option<String>,
180 pub properties: Option<HashMap<String, Value>>,
182}
183
184#[derive(Debug, Clone, Deserialize, Serialize)]
186pub struct RawClassifier {
187 #[serde(alias = "title")]
188 pub name: Option<String>,
189 pub parent_region_id: Option<String>,
190 #[serde(alias = "coordinate_3d")]
191 pub coordinates_3d: Option<Vec<i32>>,
192 pub kernel_area_id: Option<String>,
193 pub class_area_id: Option<String>,
194 pub fields: Option<Vec<feagi_structures::genomic::classifiers::ClassifierField>>,
196 pub field_area_id: Option<String>,
198 pub kernel_memory_id: Option<String>,
199 pub class_memory_id: Option<String>,
200 pub scan_twin_id: Option<String>,
202 pub properties: Option<HashMap<String, Value>>,
203}
204
205fn classifier_fields_from_raw(
206 raw: &RawClassifier,
207) -> Vec<feagi_structures::genomic::classifiers::ClassifierField> {
208 if let Some(fields) = &raw.fields {
209 return fields
210 .iter()
211 .filter(|field| !field.field_area_id.is_empty() && !field.scan_twin_id.is_empty())
212 .cloned()
213 .collect();
214 }
215 match (&raw.field_area_id, &raw.scan_twin_id) {
216 (Some(field_area_id), Some(scan_twin_id))
217 if !field_area_id.is_empty() && !scan_twin_id.is_empty() =>
218 {
219 vec![feagi_structures::genomic::classifiers::ClassifierField {
220 field_area_id: field_area_id.clone(),
221 scan_twin_id: scan_twin_id.clone(),
222 }]
223 }
224 _ => Vec::new(),
225 }
226}
227
228fn convert_dstmap_keys_to_base64(dstmap: &Value) -> Value {
232 if let Some(dstmap_obj) = dstmap.as_object() {
233 let mut converted = serde_json::Map::new();
234
235 for (dest_id_str, mapping_value) in dstmap_obj {
236 match string_to_cortical_id(dest_id_str) {
238 Ok(dest_cortical_id) => {
239 converted.insert(dest_cortical_id.as_base_64(), mapping_value.clone());
240 }
241 Err(e) => {
242 tracing::warn!(
244 "Failed to convert dstmap key '{}' to base64: {}, keeping original",
245 dest_id_str,
246 e
247 );
248 converted.insert(dest_id_str.clone(), mapping_value.clone());
249 }
250 }
251 }
252
253 Value::Object(converted)
254 } else {
255 dstmap.clone()
257 }
258}
259
260pub fn string_to_cortical_id(id_str: &str) -> EvoResult<CorticalID> {
264 use feagi_structures::genomic::cortical_area::CoreCorticalType;
265
266 if let Ok(cortical_id) = CorticalID::try_from_base_64(id_str) {
268 let mut bytes = [0u8; CorticalID::CORTICAL_ID_LENGTH];
269 cortical_id.write_id_to_bytes(&mut bytes);
270 if bytes == *b"___power" {
271 return Ok(CoreCorticalType::Power.to_cortical_id());
272 }
273 if bytes == *b"___death" {
274 return Ok(CoreCorticalType::Death.to_cortical_id());
275 }
276 if bytes == *b"___fatig" {
277 return Ok(CoreCorticalType::Fatigue.to_cortical_id());
278 }
279 if bytes == *b"___pain_" {
280 return Ok(CoreCorticalType::Pain.to_cortical_id());
281 }
282 if bytes == *b"___pleas" {
283 return Ok(CoreCorticalType::Pleasure.to_cortical_id());
284 }
285 if bytes == *b"___fear_" {
286 return Ok(CoreCorticalType::Fear.to_cortical_id());
287 }
288 if bytes == *b"___hope_" {
289 return Ok(CoreCorticalType::Hope.to_cortical_id());
290 }
291 return Ok(cortical_id);
292 }
293
294 if id_str == "_power" {
296 return Ok(CoreCorticalType::Power.to_cortical_id());
297 }
298 if id_str == "___pwr" {
300 return Ok(CoreCorticalType::Power.to_cortical_id());
301 }
302 if id_str == "___power" {
304 return Ok(CoreCorticalType::Power.to_cortical_id());
305 }
306 if id_str == "___pwr__" {
308 return Ok(CoreCorticalType::Power.to_cortical_id());
309 }
310 if id_str == "___death" {
311 return Ok(CoreCorticalType::Death.to_cortical_id());
312 }
313 if id_str == "___fatig" {
314 return Ok(CoreCorticalType::Fatigue.to_cortical_id());
315 }
316 if id_str == "___pain_" {
317 return Ok(CoreCorticalType::Pain.to_cortical_id());
318 }
319 if id_str == "___pleas" {
320 return Ok(CoreCorticalType::Pleasure.to_cortical_id());
321 }
322 if id_str == "___fear_" {
323 return Ok(CoreCorticalType::Fear.to_cortical_id());
324 }
325 if id_str == "___hope_" {
326 return Ok(CoreCorticalType::Hope.to_cortical_id());
327 }
328 if id_str == "_death" {
329 return Ok(CoreCorticalType::Death.to_cortical_id());
330 }
331 if id_str == "_fatigue" {
332 return Ok(CoreCorticalType::Fatigue.to_cortical_id());
333 }
334 if id_str == "_pain" {
335 return Ok(CoreCorticalType::Pain.to_cortical_id());
336 }
337 if id_str == "_pleasure" {
338 return Ok(CoreCorticalType::Pleasure.to_cortical_id());
339 }
340 if id_str == "_fear" {
341 return Ok(CoreCorticalType::Fear.to_cortical_id());
342 }
343 if id_str == "_hope" {
344 return Ok(CoreCorticalType::Hope.to_cortical_id());
345 }
346
347 if id_str.len() == 6 || id_str.len() == 8 {
349 CorticalID::try_from_legacy_ascii(id_str).map_err(|e| {
350 EvoError::InvalidArea(format!("Failed to convert cortical_id '{}': {}", id_str, e))
351 })
352 } else {
353 Err(EvoError::InvalidArea(format!(
354 "Invalid cortical_id length: '{}' (expected 6 or 8 ASCII chars, or base64)",
355 id_str
356 )))
357 }
358}
359
360pub struct GenomeParser;
362
363impl GenomeParser {
364 fn normalize_brain_region_cortical_id_list_properties(region: &mut BrainRegion, keys: &[&str]) {
366 for key in keys {
367 let Some(val) = region.get_property(key) else {
368 continue;
369 };
370 let Some(arr) = val.as_array() else {
371 continue;
372 };
373 let mut out: Vec<String> = Vec::new();
374 for item in arr {
375 let Some(s) = item.as_str() else {
376 continue;
377 };
378 match string_to_cortical_id(s) {
379 Ok(cortical_id) => out.push(cortical_id.as_base_64()),
380 Err(e) => {
381 warn!(target: "feagi-evo",
382 "Failed to convert brain region '{}' entry '{}': {}. Skipping.",
383 key, s, e);
384 }
385 }
386 }
387 if out.is_empty() {
388 region.properties.remove(*key);
389 } else {
390 region.add_property((*key).to_string(), serde_json::json!(out));
391 }
392 }
393 }
394
395 pub fn parse(json_str: &str) -> EvoResult<ParsedGenome> {
413 let raw: RawGenome = serde_json::from_str(json_str)
415 .map_err(|e| EvoError::InvalidGenome(format!("Failed to parse JSON: {}", e)))?;
416
417 if !raw.version.starts_with("2.") && !raw.version.starts_with("3.") && raw.version != "3" {
419 return Err(EvoError::InvalidGenome(format!(
420 "Unsupported genome version: {}. Expected 2.x or 3.x",
421 raw.version
422 )));
423 }
424
425 let cortical_areas = Self::parse_cortical_areas(&raw.blueprint)?;
427
428 let brain_regions = Self::parse_brain_regions(&raw.brain_regions)?;
430 let classifiers = Self::parse_classifiers(&raw.classifiers)?;
431
432 Ok(ParsedGenome {
433 genome_id: raw.genome_id.unwrap_or_else(|| "unknown".to_string()),
434 genome_title: raw.genome_title.unwrap_or_else(|| "Untitled".to_string()),
435 version: raw.version,
436 cortical_areas,
437 brain_regions,
438 classifiers,
439 neuron_morphologies: raw.neuron_morphologies,
440 physiology: raw.physiology,
441 })
442 }
443
444 fn parse_cortical_areas(
446 blueprint: &HashMap<String, RawCorticalArea>,
447 ) -> EvoResult<Vec<CorticalArea>> {
448 let mut areas = Vec::with_capacity(blueprint.len());
449
450 for (cortical_id_str, raw_area) in blueprint.iter() {
451 if cortical_id_str.is_empty() {
453 warn!(target: "feagi-evo","Skipping empty cortical_id");
454 continue;
455 }
456
457 let cortical_id = match string_to_cortical_id(cortical_id_str) {
459 Ok(id) => id,
460 Err(e) => {
461 warn!(target: "feagi-evo","Skipping invalid cortical_id '{}': {}", cortical_id_str, e);
462 continue;
463 }
464 };
465
466 let name = raw_area
468 .cortical_name
469 .clone()
470 .unwrap_or_else(|| cortical_id_str.clone());
471
472 let dimensions = if let Some(boundaries) = &raw_area.block_boundaries {
473 if boundaries.len() != 3 {
474 return Err(EvoError::InvalidArea(format!(
475 "Invalid block_boundaries for {}: expected 3 values, got {}",
476 cortical_id_str,
477 boundaries.len()
478 )));
479 }
480 Dimensions::new(boundaries[0], boundaries[1], boundaries[2])
481 .map_err(|e| EvoError::InvalidArea(format!("Invalid dimensions: {}", e)))?
482 } else {
483 warn!(target: "feagi-evo","Cortical area {} missing block_boundaries, defaulting to 1x1x1", cortical_id_str);
485 Dimensions::new(1, 1, 1).map_err(|e| {
486 EvoError::InvalidArea(format!("Invalid default dimensions: {}", e))
487 })?
488 };
489
490 let position = if let Some(coords) = &raw_area.relative_coordinate {
491 if coords.len() != 3 {
492 return Err(EvoError::InvalidArea(format!(
493 "Invalid relative_coordinate for {}: expected 3 values, got {}",
494 cortical_id_str,
495 coords.len()
496 )));
497 }
498 GenomeCoordinate3D::new(coords[0], coords[1], coords[2])
499 } else {
500 warn!(target: "feagi-evo","Cortical area {} missing relative_coordinate, defaulting to (0,0,0)", cortical_id_str);
502 GenomeCoordinate3D::new(0, 0, 0)
503 };
504
505 let cortical_type = cortical_id.as_cortical_type().map_err(|e| {
507 EvoError::InvalidArea(format!(
508 "Failed to determine cortical type from ID {}: {}",
509 cortical_id_str, e
510 ))
511 })?;
512
513 let mut area = CorticalArea::new(
515 cortical_id,
516 0, name,
518 dimensions,
519 position,
520 cortical_type,
521 )?;
522
523 if let Some(ref cortical_type_str) = raw_area.cortical_type {
525 area.properties.insert(
526 "cortical_group".to_string(),
527 serde_json::json!(cortical_type_str),
528 );
529 }
530
531 if let Some(v) = raw_area.synapse_attractivity {
534 area.properties
535 .insert("synapse_attractivity".to_string(), serde_json::json!(v));
536 }
537 if let Some(v) = raw_area.refractory_period {
538 area.properties
539 .insert("refractory_period".to_string(), serde_json::json!(v));
540 }
541 if let Some(v) = raw_area.firing_threshold {
542 area.properties
543 .insert("firing_threshold".to_string(), serde_json::json!(v));
544 }
545 if let Some(v) = raw_area.firing_threshold_limit {
546 area.properties
547 .insert("firing_threshold_limit".to_string(), serde_json::json!(v));
548 }
549 if let Some(v) = raw_area.firing_threshold_increment_x {
550 area.properties.insert(
551 "firing_threshold_increment_x".to_string(),
552 serde_json::json!(v),
553 );
554 }
555 if let Some(v) = raw_area.firing_threshold_increment_y {
556 area.properties.insert(
557 "firing_threshold_increment_y".to_string(),
558 serde_json::json!(v),
559 );
560 }
561 if let Some(v) = raw_area.firing_threshold_increment_z {
562 area.properties.insert(
563 "firing_threshold_increment_z".to_string(),
564 serde_json::json!(v),
565 );
566 }
567 if let Some(v) = raw_area.leak_coefficient {
568 area.properties
569 .insert("leak_coefficient".to_string(), serde_json::json!(v));
570 }
571 if let Some(v) = raw_area.leak_variability {
572 area.properties
573 .insert("leak_variability".to_string(), serde_json::json!(v));
574 }
575 if let Some(v) = raw_area.neuron_excitability {
576 area.properties
577 .insert("neuron_excitability".to_string(), serde_json::json!(v));
578 }
579 if let Some(v) = raw_area.postsynaptic_current {
580 area.properties
581 .insert("postsynaptic_current".to_string(), serde_json::json!(v));
582 }
583 if let Some(v) = raw_area.postsynaptic_current_max {
584 area.properties
585 .insert("postsynaptic_current_max".to_string(), serde_json::json!(v));
586 }
587 if let Some(v) = raw_area.degeneration {
588 area.properties
589 .insert("degeneration".to_string(), serde_json::json!(v));
590 }
591
592 if let Some(v) = raw_area.psp_uniform_distribution {
594 area.properties
595 .insert("psp_uniform_distribution".to_string(), serde_json::json!(v));
596 }
597 if let Some(v) = raw_area.mp_charge_accumulation {
598 area.properties
599 .insert("mp_charge_accumulation".to_string(), serde_json::json!(v));
600 }
601 if let Some(v) = raw_area.mp_driven_psp {
602 area.properties
603 .insert("mp_driven_psp".to_string(), serde_json::json!(v));
604 tracing::info!(
605 target: "feagi-evo",
606 "[GENOME-LOAD] Loaded mp_driven_psp={} for area {}",
607 v,
608 cortical_id_str
609 );
610 } else {
611 tracing::debug!(
612 target: "feagi-evo",
613 "[GENOME-LOAD] mp_driven_psp not found in raw_area for {}, will use default=false",
614 cortical_id_str
615 );
616 }
617 if let Some(v) = raw_area.visualization {
618 area.properties
619 .insert("visualization".to_string(), serde_json::json!(v));
620 area.properties
622 .insert("visible".to_string(), serde_json::json!(v));
623 }
624 if let Some(v) = raw_area.burst_engine_activation {
625 area.properties
626 .insert("burst_engine_active".to_string(), serde_json::json!(v));
627 }
628 if let Some(v) = raw_area.is_mem_type {
629 area.properties
630 .insert("is_mem_type".to_string(), serde_json::json!(v));
631 }
632
633 if let Some(v) = raw_area.longterm_mem_threshold {
635 area.properties
636 .insert("longterm_mem_threshold".to_string(), serde_json::json!(v));
637 }
638 if let Some(v) = raw_area.lifespan_growth_rate {
639 area.properties
640 .insert("lifespan_growth_rate".to_string(), serde_json::json!(v));
641 }
642 if let Some(v) = raw_area.init_lifespan {
643 area.properties
644 .insert("init_lifespan".to_string(), serde_json::json!(v));
645 }
646 if let Some(v) = raw_area.temporal_depth {
647 area.properties
648 .insert("temporal_depth".to_string(), serde_json::json!(v));
649 }
650 if let Some(v) = raw_area.mp_learning_enabled {
651 area.properties
652 .insert("mp_learning_enabled".to_string(), serde_json::json!(v));
653 }
654 if let Some(v) = raw_area.min_window_activity {
655 area.properties
656 .insert("min_window_activity".to_string(), serde_json::json!(v));
657 }
658 if let Some(v) = raw_area.scan_skip_density {
659 area.properties
660 .insert("scan_skip_density".to_string(), serde_json::json!(v));
661 }
662 if let Some(v) = raw_area.consecutive_fire_cnt_max {
663 area.properties
664 .insert("consecutive_fire_cnt_max".to_string(), serde_json::json!(v));
665 area.properties
667 .insert("consecutive_fire_limit".to_string(), serde_json::json!(v));
668 }
669 if let Some(v) = raw_area.snooze_length {
670 area.properties
671 .insert("snooze_period".to_string(), serde_json::json!(v));
672 }
673
674 if let Some(v) = &raw_area.group_id {
676 area.properties
677 .insert("group_id".to_string(), serde_json::json!(v));
678 }
679 if let Some(v) = &raw_area.sub_group_id {
680 area.properties
681 .insert("sub_group_id".to_string(), serde_json::json!(v));
682 }
683 if let Some(v) = raw_area.per_voxel_neuron_cnt {
685 area.properties
686 .insert("neurons_per_voxel".to_string(), serde_json::json!(v));
687 }
688 if let Some(v) = &raw_area.cortical_mapping_dst {
689 let converted_dstmap = convert_dstmap_keys_to_base64(v);
691 area.properties
692 .insert("cortical_mapping_dst".to_string(), converted_dstmap);
693 }
694 if let Some(v) = &raw_area.coordinate_2d {
695 area.properties
696 .insert("2d_coordinate".to_string(), serde_json::json!(v));
697 }
698
699 for (key, value) in &raw_area.other {
701 area.properties.insert(key.clone(), value.clone());
702 }
703
704 areas.push(area);
708 }
709
710 Ok(areas)
711 }
712
713 fn parse_brain_regions(
715 raw_regions: &HashMap<String, RawBrainRegion>,
716 ) -> EvoResult<Vec<(BrainRegion, Option<String>)>> {
717 let mut regions = Vec::with_capacity(raw_regions.len());
718
719 for (region_id_str, raw_region) in raw_regions.iter() {
720 let title = raw_region
721 .title
722 .clone()
723 .unwrap_or_else(|| region_id_str.clone());
724
725 let region_id = match RegionID::from_string(region_id_str) {
728 Ok(id) => id,
729 Err(_) => {
730 RegionID::new()
733 }
734 };
735
736 let region_type = RegionType::Undefined; let mut region = BrainRegion::new(region_id, title, region_type)?;
739
740 if let Some(props) = &raw_region.properties {
742 for (k, v) in props {
743 region.add_property(k.clone(), v.clone());
744 }
745 }
746
747 if let Some(areas) = &raw_region.areas {
749 for area_id in areas {
750 match string_to_cortical_id(area_id) {
752 Ok(cortical_id) => {
753 region.add_area(cortical_id);
754 }
755 Err(e) => {
756 warn!(target: "feagi-evo",
757 "Failed to convert brain region area ID '{}' to CorticalID: {}. Skipping.",
758 area_id, e);
759 }
760 }
761 }
762 }
763
764 if let Some(desc) = &raw_region.description {
766 region.add_property("description".to_string(), serde_json::json!(desc));
767 }
768 if let Some(coord_2d) = &raw_region.coordinate_2d {
769 region.add_property("coordinate_2d".to_string(), serde_json::json!(coord_2d));
770 }
771 if let Some(coord_3d) = &raw_region.coordinate_3d {
772 region.add_property("coordinate_3d".to_string(), serde_json::json!(coord_3d));
773 }
774 if let Some(inputs) = &raw_region.inputs {
776 let input_ids: Vec<String> = inputs
777 .iter()
778 .filter_map(|id| match string_to_cortical_id(id) {
779 Ok(cortical_id) => Some(cortical_id.as_base_64()),
780 Err(e) => {
781 warn!(target: "feagi-evo",
782 "Failed to convert brain region input ID '{}': {}. Skipping.",
783 id, e);
784 None
785 }
786 })
787 .collect();
788 if !input_ids.is_empty() {
789 region.add_property("inputs".to_string(), serde_json::json!(input_ids));
790 }
791 }
792 if let Some(outputs) = &raw_region.outputs {
793 let output_ids: Vec<String> = outputs
794 .iter()
795 .filter_map(|id| match string_to_cortical_id(id) {
796 Ok(cortical_id) => Some(cortical_id.as_base_64()),
797 Err(e) => {
798 warn!(target: "feagi-evo",
799 "Failed to convert brain region output ID '{}': {}. Skipping.",
800 id, e);
801 None
802 }
803 })
804 .collect();
805 if !output_ids.is_empty() {
806 region.add_property("outputs".to_string(), serde_json::json!(output_ids));
807 }
808 }
809 if let Some(signature) = &raw_region.signature {
810 region.add_property("signature".to_string(), serde_json::json!(signature));
811 }
812
813 if let Some(d) = &raw_region.designated_inputs {
814 let ids: Vec<String> = d
815 .iter()
816 .filter_map(|id| match string_to_cortical_id(id) {
817 Ok(cortical_id) => Some(cortical_id.as_base_64()),
818 Err(e) => {
819 warn!(target: "feagi-evo",
820 "Failed to convert designated_inputs entry '{}': {}. Skipping.",
821 id, e);
822 None
823 }
824 })
825 .collect();
826 if !ids.is_empty() {
827 region.add_property("designated_inputs".to_string(), serde_json::json!(ids));
828 }
829 }
830 if let Some(d) = &raw_region.designated_outputs {
831 let ids: Vec<String> = d
832 .iter()
833 .filter_map(|id| match string_to_cortical_id(id) {
834 Ok(cortical_id) => Some(cortical_id.as_base_64()),
835 Err(e) => {
836 warn!(target: "feagi-evo",
837 "Failed to convert designated_outputs entry '{}': {}. Skipping.",
838 id, e);
839 None
840 }
841 })
842 .collect();
843 if !ids.is_empty() {
844 region.add_property("designated_outputs".to_string(), serde_json::json!(ids));
845 }
846 }
847
848 Self::normalize_brain_region_cortical_id_list_properties(
849 &mut region,
850 &[
851 "inputs",
852 "outputs",
853 "designated_inputs",
854 "designated_outputs",
855 ],
856 );
857
858 let parent_id = raw_region.parent_region_id.clone();
860 if let Some(ref parent_id_str) = parent_id {
861 region.add_property(
863 "parent_region_id".to_string(),
864 serde_json::json!(parent_id_str),
865 );
866 }
867
868 regions.push((region, parent_id));
869 }
870
871 Ok(regions)
872 }
873
874 fn parse_classifiers(
875 raw_classifiers: &HashMap<String, RawClassifier>,
876 ) -> EvoResult<Vec<Classifier>> {
877 let mut classifiers = Vec::with_capacity(raw_classifiers.len());
878 for (classifier_id, raw) in raw_classifiers {
879 let name = raw
880 .name
881 .clone()
882 .filter(|n| !n.trim().is_empty())
883 .ok_or_else(|| {
884 EvoError::InvalidArea(format!("Classifier '{}' is missing name", classifier_id))
885 })?;
886 let parent_region_id = raw
887 .parent_region_id
888 .clone()
889 .filter(|n| !n.trim().is_empty())
890 .ok_or_else(|| {
891 EvoError::InvalidArea(format!(
892 "Classifier '{}' is missing parent_region_id",
893 classifier_id
894 ))
895 })?;
896 let coordinates_3d = match &raw.coordinates_3d {
897 Some(coords) if coords.len() == 3 => [coords[0], coords[1], coords[2]],
898 Some(coords) => {
899 return Err(EvoError::InvalidArea(format!(
900 "Classifier '{}' coordinates_3d must have 3 values, got {}",
901 classifier_id,
902 coords.len()
903 )))
904 }
905 None => [0, 0, 0],
906 };
907 let kernel_memory_id = raw.kernel_memory_id.clone().ok_or_else(|| {
908 EvoError::InvalidArea(format!(
909 "Classifier '{}' is missing kernel_memory_id",
910 classifier_id
911 ))
912 })?;
913 let class_memory_id = raw.class_memory_id.clone().ok_or_else(|| {
914 EvoError::InvalidArea(format!(
915 "Classifier '{}' is missing class_memory_id",
916 classifier_id
917 ))
918 })?;
919 let fields = classifier_fields_from_raw(raw);
920 classifiers.push(Classifier {
921 classifier_id: classifier_id.clone(),
922 name,
923 parent_region_id,
924 coordinates_3d,
925 kernel_area_id: raw.kernel_area_id.clone(),
926 class_area_id: raw.class_area_id.clone(),
927 fields,
928 kernel_memory_id,
929 class_memory_id,
930 properties: raw.properties.clone().unwrap_or_default(),
931 });
932 }
933 Ok(classifiers)
934 }
935}
936
937#[cfg(test)]
938mod tests {
939 use super::*;
940
941 #[test]
942 fn test_parse_minimal_genome() {
943 let json = r#"{
946 "version": "2.1",
947 "blueprint": {
948 "_power": {
949 "cortical_name": "Test Area",
950 "block_boundaries": [10, 10, 10],
951 "relative_coordinate": [0, 0, 0],
952 "cortical_type": "CORE"
953 }
954 },
955 "brain_regions": {
956 "root": {
957 "title": "Root",
958 "parent_region_id": null,
959 "areas": ["_power"]
960 }
961 }
962 }"#;
963
964 let parsed = GenomeParser::parse(json).unwrap();
965
966 assert_eq!(parsed.version, "2.1");
967 assert_eq!(parsed.cortical_areas.len(), 1);
968 assert_eq!(
970 parsed.cortical_areas[0].cortical_id.as_base_64(),
971 "X19fcG93ZXI="
972 );
973 assert_eq!(parsed.cortical_areas[0].name, "Test Area");
974 assert_eq!(parsed.brain_regions.len(), 1);
975
976 assert!(parsed.cortical_areas[0]
979 .cortical_id
980 .as_cortical_type()
981 .is_ok());
982 }
983
984 #[test]
985 fn test_parse_multiple_areas() {
986 let json = r#"{
988 "version": "2.1",
989 "blueprint": {
990 "_power": {
991 "cortical_name": "Area 1",
992 "cortical_type": "CORE",
993 "block_boundaries": [5, 5, 5],
994 "relative_coordinate": [0, 0, 0]
995 },
996 "_death": {
997 "cortical_name": "Area 2",
998 "cortical_type": "CORE",
999 "block_boundaries": [10, 10, 10],
1000 "relative_coordinate": [5, 0, 0]
1001 }
1002 }
1003 }"#;
1004
1005 let parsed = GenomeParser::parse(json).unwrap();
1006 assert!(parsed.classifiers.is_empty());
1007
1008 assert_eq!(parsed.cortical_areas.len(), 2);
1009
1010 for area in &parsed.cortical_areas {
1012 assert!(
1013 area.cortical_id.as_cortical_type().is_ok(),
1014 "Area {} should have cortical_type_new populated",
1015 area.cortical_id
1016 );
1017 }
1018 }
1019
1020 #[test]
1021 fn test_string_to_cortical_id_legacy_power_shorthand() {
1022 use feagi_structures::genomic::cortical_area::CoreCorticalType;
1025 let id = string_to_cortical_id("___pwr").unwrap();
1026 assert_eq!(
1027 id.as_base_64(),
1028 CoreCorticalType::Power.to_cortical_id().as_base_64()
1029 );
1030 }
1031
1032 #[test]
1033 fn test_parse_raw_imu_magnetometer_wire_id() {
1034 let json = r#"{
1037 "version": "3.0",
1038 "blueprint": {
1039 "aXJpbScAAgA=": {
1040 "cortical_name": "feagi_body_imu__Abdomen-2",
1041 "block_boundaries": [3, 1, 10],
1042 "relative_coordinate": [90, 0, -10],
1043 "cortical_type": "IPU"
1044 }
1045 },
1046 "brain_regions": {}
1047 }"#;
1048
1049 let parsed = GenomeParser::parse(json).expect("Raw IMU magnetometer genome");
1050 assert_eq!(parsed.cortical_areas.len(), 1);
1051 assert_eq!(
1052 parsed.cortical_areas[0].cortical_id.as_base_64(),
1053 "aXJpbScAAgA="
1054 );
1055 parsed.cortical_areas[0]
1056 .cortical_id
1057 .as_cortical_type()
1058 .expect("magnetometer IO flag must decode");
1059 }
1060
1061 #[test]
1062 fn test_parse_positional_servo_speed_wire_id() {
1063 let json = r#"{
1066 "version": "3.0",
1067 "blueprint": {
1068 "b3BzZSEAAAA=": {
1069 "cortical_name": "Positional Servo Speed",
1070 "block_boundaries": [6, 1, 20],
1071 "relative_coordinate": [-58, 0, -10],
1072 "cortical_type": "OPU"
1073 }
1074 },
1075 "brain_regions": {}
1076 }"#;
1077
1078 let parsed = GenomeParser::parse(json).expect("Positional Servo Speed genome");
1079 assert_eq!(parsed.cortical_areas.len(), 1);
1080 assert_eq!(
1081 parsed.cortical_areas[0].cortical_id.as_base_64(),
1082 "b3BzZSEAAAA="
1083 );
1084 parsed.cortical_areas[0]
1085 .cortical_id
1086 .as_cortical_type()
1087 .expect("positional servo speed IO flag must decode");
1088 }
1089
1090 #[test]
1091 fn test_string_to_cortical_id_legacy_power_padded() {
1092 use feagi_structures::genomic::cortical_area::CoreCorticalType;
1094 let id = string_to_cortical_id("___pwr__").unwrap();
1095 assert_eq!(
1096 id.as_base_64(),
1097 CoreCorticalType::Power.to_cortical_id().as_base_64()
1098 );
1099 }
1100
1101 #[test]
1102 fn test_parse_with_properties() {
1103 let json = r#"{
1104 "version": "2.1",
1105 "blueprint": {
1106 "mem001": {
1107 "cortical_name": "Memory Area",
1108 "block_boundaries": [8, 8, 8],
1109 "relative_coordinate": [0, 0, 0],
1110 "cortical_type": "MEMORY",
1111 "is_mem_type": true,
1112 "firing_threshold": 50.0,
1113 "leak_coefficient": 0.9
1114 }
1115 }
1116 }"#;
1117
1118 let parsed = GenomeParser::parse(json).unwrap();
1119
1120 assert_eq!(parsed.cortical_areas.len(), 1);
1121 let area = &parsed.cortical_areas[0];
1122
1123 use feagi_structures::genomic::cortical_area::CorticalAreaType;
1125 assert!(matches!(area.cortical_type, CorticalAreaType::Memory(_)));
1126
1127 assert!(area.properties.contains_key("is_mem_type"));
1129 assert!(area.properties.contains_key("firing_threshold"));
1130 assert!(area.properties.contains_key("cortical_group"));
1131
1132 assert!(
1134 area.cortical_id.as_cortical_type().is_ok(),
1135 "cortical_id should be parseable to cortical_type"
1136 );
1137 if let Ok(cortical_type) = area.cortical_id.as_cortical_type() {
1138 use feagi_structures::genomic::cortical_area::CorticalAreaType;
1139 assert!(
1140 matches!(cortical_type, CorticalAreaType::Memory(_)),
1141 "Should be classified as MEMORY type"
1142 );
1143 }
1144 }
1145
1146 #[test]
1148 fn test_parse_v3_brain_region_nested_properties_retains_designated_io() {
1149 let json = r#"{
1150 "version": "3.0",
1151 "blueprint": {
1152 "_power": {
1153 "cortical_name": "Core",
1154 "block_boundaries": [10, 10, 10],
1155 "relative_coordinate": [0, 0, 0],
1156 "cortical_type": "CORE"
1157 }
1158 },
1159 "brain_regions": {
1160 "550e8400-e29b-41d4-a716-446655440000": {
1161 "name": "Sub",
1162 "cortical_areas": ["_power"],
1163 "properties": {
1164 "designated_inputs": ["_power"],
1165 "designated_outputs": []
1166 }
1167 }
1168 }
1169 }"#;
1170
1171 let parsed = GenomeParser::parse(json).unwrap();
1172 assert_eq!(parsed.brain_regions.len(), 1);
1173 let (region, _) = &parsed.brain_regions[0];
1174 let di = region
1175 .get_property("designated_inputs")
1176 .and_then(|v| v.as_array())
1177 .expect("designated_inputs");
1178 assert_eq!(di.len(), 1);
1179 assert_eq!(di[0].as_str().unwrap(), "X19fcG93ZXI=");
1180 }
1181
1182 #[test]
1183 fn test_parse_brain_region_plain_text_description() {
1184 let json = r#"{
1185 "version": "2.1",
1186 "blueprint": {
1187 "_power": {
1188 "cortical_name": "Core",
1189 "block_boundaries": [10, 10, 10],
1190 "relative_coordinate": [0, 0, 0],
1191 "cortical_type": "CORE"
1192 }
1193 },
1194 "brain_regions": {
1195 "root": {
1196 "title": "Root",
1197 "description": "Holds core physiology and embodiment IO",
1198 "parent_region_id": null,
1199 "areas": ["_power"]
1200 }
1201 }
1202 }"#;
1203
1204 let parsed = GenomeParser::parse(json).unwrap();
1205 assert_eq!(parsed.brain_regions.len(), 1);
1206 let (region, _) = &parsed.brain_regions[0];
1207 assert_eq!(
1208 region.get_property("description"),
1209 Some(&serde_json::json!(
1210 "Holds core physiology and embodiment IO"
1211 ))
1212 );
1213 }
1214
1215 #[test]
1216 fn test_invalid_version() {
1217 let json = r#"{
1218 "version": "1.0",
1219 "blueprint": {}
1220 }"#;
1221
1222 let result = GenomeParser::parse(json);
1223 assert!(result.is_err());
1224 }
1225
1226 #[test]
1227 fn test_malformed_json() {
1228 let json = r#"{ "version": "2.1", "blueprint": { malformed"#;
1229
1230 let result = GenomeParser::parse(json);
1231 assert!(result.is_err());
1232 }
1233
1234 #[test]
1235 fn test_cortical_type_new_population() {
1236 use feagi_structures::genomic::cortical_area::CoreCorticalType;
1239 let power_id = CoreCorticalType::Power.to_cortical_id().as_base_64();
1240 let json = format!(
1241 r#"{{
1242 "version": "2.1",
1243 "blueprint": {{
1244 "cvision1": {{
1245 "cortical_name": "Test Custom Vision",
1246 "cortical_type": "CUSTOM",
1247 "block_boundaries": [10, 10, 1],
1248 "relative_coordinate": [0, 0, 0]
1249 }},
1250 "cmotor01": {{
1251 "cortical_name": "Test Custom Motor",
1252 "cortical_type": "CUSTOM",
1253 "block_boundaries": [5, 5, 1],
1254 "relative_coordinate": [0, 0, 0]
1255 }},
1256 "{}": {{
1257 "cortical_name": "Test Core",
1258 "cortical_type": "CORE",
1259 "block_boundaries": [1, 1, 1],
1260 "relative_coordinate": [0, 0, 0]
1261 }}
1262 }}
1263 }}"#,
1264 power_id
1265 );
1266
1267 let parsed = GenomeParser::parse(&json).unwrap();
1268 assert_eq!(parsed.cortical_areas.len(), 3);
1269
1270 for area in &parsed.cortical_areas {
1272 assert!(
1273 area.cortical_id.as_cortical_type().is_ok(),
1274 "Area {} should have cortical_type_new populated",
1275 area.cortical_id
1276 );
1277
1278 assert!(
1280 area.properties.contains_key("cortical_group"),
1281 "Area {} should have cortical_group property",
1282 area.cortical_id
1283 );
1284
1285 if let Some(prop_group) = area
1287 .properties
1288 .get("cortical_group")
1289 .and_then(|v| v.as_str())
1290 {
1291 assert!(
1292 !prop_group.is_empty(),
1293 "Area {} should have non-empty cortical_group property",
1294 area.cortical_id.as_base_64()
1295 );
1296 }
1297 }
1298 }
1299
1300 #[test]
1301 fn test_parse_classifiers_key_parallel_to_regions() {
1302 let json = r#"{
1303 "version": "3.0",
1304 "blueprint": {
1305 "cfield": {
1306 "cortical_name": "Field",
1307 "cortical_type": "CUSTOM",
1308 "block_boundaries": [4, 4, 1],
1309 "relative_coordinate": [0, 0, 0]
1310 },
1311 "mkmem1": {
1312 "cortical_name": "KernelMem",
1313 "cortical_type": "MEMORY",
1314 "block_boundaries": [2, 2, 2],
1315 "relative_coordinate": [10, 0, 0]
1316 },
1317 "mcmem1": {
1318 "cortical_name": "ClassMem",
1319 "cortical_type": "MEMORY",
1320 "block_boundaries": [2, 2, 2],
1321 "relative_coordinate": [20, 0, 0]
1322 },
1323 "cscan1": {
1324 "cortical_name": "ScanTwin",
1325 "cortical_type": "CUSTOM",
1326 "block_boundaries": [4, 4, 3],
1327 "relative_coordinate": [30, 0, 0]
1328 }
1329 },
1330 "brain_regions": {
1331 "root": {
1332 "title": "root",
1333 "parent_region_id": "",
1334 "coordinate_2d": [0, 0],
1335 "coordinate_3d": [0, 0, 0],
1336 "areas": ["cfield", "mkmem1", "mcmem1", "cscan1"],
1337 "regions": [],
1338 "inputs": [],
1339 "outputs": []
1340 }
1341 },
1342 "classifiers": {
1343 "clf-1": {
1344 "name": "object_class",
1345 "parent_region_id": "root",
1346 "coordinates_3d": [30, 0, 0],
1347 "field_area_id": "cfield",
1348 "kernel_memory_id": "mkmem1",
1349 "class_memory_id": "mcmem1",
1350 "scan_twin_id": "cscan1"
1351 }
1352 }
1353 }"#;
1354
1355 let parsed = GenomeParser::parse(json).expect("classifier genome");
1356 assert_eq!(parsed.classifiers.len(), 1);
1357 let classifier = &parsed.classifiers[0];
1358 assert_eq!(classifier.classifier_id, "clf-1");
1359 assert_eq!(classifier.name, "object_class");
1360 assert_eq!(classifier.parent_region_id, "root");
1361 assert_eq!(classifier.fields.len(), 1);
1362 assert_eq!(classifier.fields[0].field_area_id, "cfield");
1363 assert_eq!(classifier.kernel_memory_id, "mkmem1");
1364 assert_eq!(classifier.class_memory_id, "mcmem1");
1365 assert_eq!(classifier.fields[0].scan_twin_id, "cscan1");
1366 assert_eq!(classifier.owned_area_ids().len(), 3);
1367 }
1368}