feagi_evolutionary/genome/
saver.rs1use serde_json::{json, Value};
14use std::collections::HashMap;
15
16use crate::types::{EvoError, EvoResult};
17#[cfg(test)]
18use feagi_structures::genomic::brain_regions::RegionID;
19use feagi_structures::genomic::cortical_area::{CorticalArea, CorticalID};
20#[cfg(test)]
21use feagi_structures::genomic::cortical_area::{
22 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
23};
24use feagi_structures::genomic::BrainRegion;
25
26pub struct GenomeSaver;
28
29impl GenomeSaver {
30 #[deprecated(
49 note = "Use feagi_evolutionary::save_genome_to_json(RuntimeGenome) instead. This produces incomplete v2.1 format."
50 )]
51 pub fn save_to_json(
52 cortical_areas: &HashMap<CorticalID, CorticalArea>,
53 brain_regions: &HashMap<String, (BrainRegion, Option<String>)>,
54 genome_id: Option<String>,
55 genome_title: Option<String>,
56 ) -> EvoResult<String> {
57 let mut blueprint = serde_json::Map::new();
59
60 for (cortical_id, area) in cortical_areas {
61 let cortical_id_str = cortical_id.as_base_64();
62 let mut area_data = serde_json::Map::new();
63
64 area_data.insert("cortical_name".to_string(), json!(area.name));
66 area_data.insert(
67 "block_boundaries".to_string(),
68 json!([
69 area.dimensions.width,
70 area.dimensions.height,
71 area.dimensions.depth
72 ]),
73 );
74 area_data.insert(
75 "relative_coordinate".to_string(),
76 json!([area.position.x, area.position.y, area.position.z]),
77 );
78
79 let cortical_type = area
81 .properties
82 .get("cortical_group")
83 .and_then(|v| v.as_str())
84 .unwrap_or("CUSTOM");
85 area_data.insert("cortical_type".to_string(), json!(cortical_type));
86
87 for (key, value) in &area.properties {
89 area_data.insert(key.clone(), value.clone());
90 }
91
92 blueprint.insert(cortical_id_str, Value::Object(area_data));
93 }
94
95 let mut regions_map = serde_json::Map::new();
97
98 for (region_id, (region, parent_id)) in brain_regions {
99 let mut region_data = serde_json::Map::new();
100
101 region_data.insert("title".to_string(), json!(region.name));
102 region_data.insert(
103 "parent_region_id".to_string(),
104 if let Some(ref parent) = parent_id {
105 json!(parent)
106 } else {
107 Value::Null
108 },
109 );
110
111 let areas: Vec<String> = region
113 .cortical_areas
114 .iter()
115 .map(|id| id.as_base_64())
116 .collect();
117 region_data.insert("areas".to_string(), json!(areas));
118
119 for (key, value) in ®ion.properties {
121 region_data.insert(key.clone(), value.clone());
122 }
123
124 region_data.insert("regions".to_string(), json!(Vec::<String>::new()));
126
127 regions_map.insert(region_id.to_string(), Value::Object(region_data));
128 }
129
130 let genome = json!({
132 "genome_id": genome_id.unwrap_or_else(||
133 format!("genome_{}", chrono::Utc::now().timestamp())
134 ),
135 "genome_title": genome_title.unwrap_or_else(|| "Exported Genome".to_string()),
136 "version": "2.1",
137 "blueprint": blueprint,
138 "brain_regions": regions_map,
139 "neuron_morphologies": {},
140 "physiology": {}
141 });
142
143 serde_json::to_string_pretty(&genome)
145 .map_err(|e| EvoError::Internal(format!("Failed to serialize genome: {}", e)))
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use feagi_structures::genomic::RegionType;
153
154 #[test]
155 fn test_save_minimal_genome() {
156 let mut cortical_areas = HashMap::new();
157 let mut brain_regions = HashMap::new();
158
159 use feagi_structures::genomic::cortical_area::CoreCorticalType;
161 let cortical_id = CoreCorticalType::Power.to_cortical_id();
162 let area = CorticalArea::new(
163 cortical_id,
164 0,
165 "Test Area".to_string(),
166 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
167 (0, 0, 0).into(),
168 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
169 )
170 .unwrap();
171
172 cortical_areas.insert(cortical_id, area);
173
174 let region =
176 BrainRegion::new(RegionID::new(), "Root".to_string(), RegionType::Undefined).unwrap();
177
178 brain_regions.insert("root".to_string(), (region, None));
179
180 #[allow(deprecated)]
182 let json = GenomeSaver::save_to_json(
183 &cortical_areas,
184 &brain_regions,
185 Some("test-001".to_string()),
186 Some("Test Genome".to_string()),
187 )
188 .unwrap();
189
190 let parsed: Value = serde_json::from_str(&json).unwrap();
192
193 assert_eq!(parsed["genome_id"], "test-001");
194 assert_eq!(parsed["genome_title"], "Test Genome");
195 assert_eq!(parsed["version"], "2.1");
196 assert!(parsed["blueprint"].is_object());
197 assert!(parsed["brain_regions"].is_object());
198 }
199
200 #[test]
201 fn test_roundtrip() {
202 use crate::genome::GenomeParser;
203
204 use feagi_structures::genomic::cortical_area::CoreCorticalType;
206 let mut cortical_areas = HashMap::new();
207 let cortical_id = CoreCorticalType::Power.to_cortical_id();
208 let area = CorticalArea::new(
209 cortical_id,
210 0,
211 "Test Area".to_string(),
212 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
213 (5, 5, 5).into(),
214 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
215 )
216 .unwrap();
217 cortical_areas.insert(cortical_id, area);
218
219 let mut brain_regions = HashMap::new();
220 let region = BrainRegion::new(
221 RegionID::new(),
222 "Root Region".to_string(),
223 RegionType::Undefined,
224 )
225 .unwrap();
226 brain_regions.insert("root".to_string(), (region, None));
227
228 #[allow(deprecated)]
230 let json = GenomeSaver::save_to_json(
231 &cortical_areas,
232 &brain_regions,
233 Some("test-roundtrip".to_string()),
234 Some("Roundtrip Test".to_string()),
235 )
236 .unwrap();
237
238 let parsed = GenomeParser::parse(&json).unwrap();
240
241 assert_eq!(parsed.genome_id, "test-roundtrip");
243 assert_eq!(parsed.genome_title, "Roundtrip Test");
244 assert_eq!(parsed.cortical_areas.len(), 1);
245 assert_eq!(parsed.brain_regions.len(), 1);
246
247 let area = &parsed.cortical_areas[0];
248 let expected_power_id =
250 feagi_structures::genomic::cortical_area::CoreCorticalType::Power.to_cortical_id();
251 assert_eq!(area.cortical_id, expected_power_id);
252 assert_eq!(area.name, "Test Area");
253 assert_eq!(area.dimensions.width, 10);
254 assert_eq!(area.position, (5, 5, 5).into());
255 }
256}