feagi_structures/genomic/brain_regions/mod.rs
1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5BrainRegion data model.
6
7Represents a hierarchical grouping of cortical areas with functional significance.
8Moved from feagi-core/crates/feagi-bdu/src/models/brain_region.rs
9*/
10
11mod region_id;
12pub use region_id::RegionID;
13
14use crate::genomic::cortical_area::CorticalID;
15use crate::FeagiDataError;
16use serde::{Deserialize, Serialize};
17use std::collections::{HashMap, HashSet};
18
19/// Type of brain region (placeholder for future functional/anatomical classification)
20///
21/// Currently, no specific region types are defined. This enum serves as a placeholder
22/// for future extensions when functional or anatomical classification is implemented.
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "lowercase")]
25#[derive(Default)]
26pub enum RegionType {
27 /// Generic/undefined region type (placeholder)
28 #[default]
29 Undefined,
30}
31
32impl std::fmt::Display for RegionType {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 write!(f, "undefined")
35 }
36}
37
38/// Brain region metadata (genome representation)
39///
40/// A brain region is a hierarchical grouping of cortical areas that share
41/// functional or anatomical characteristics. Regions form a tree structure
42/// where each region can contain multiple cortical areas and sub-regions.
43///
44/// # Design Notes
45///
46/// - Regions are organizational constructs (not physical entities)
47/// - Used for genome editing, visualization, and bulk operations
48/// - Serializable for genome persistence
49/// - Properties stored as HashMap for maximum flexibility
50///
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct BrainRegion {
53 /// Unique identifier for this region
54 pub region_id: RegionID,
55
56 /// Human-readable name (mapped to "title" in genome JSON)
57 pub name: String,
58
59 /// Functional/anatomical type
60 pub region_type: RegionType,
61
62 /// Set of cortical area IDs contained in this region
63 #[serde(default)]
64 pub cortical_areas: HashSet<CorticalID>,
65
66 /// Additional user-defined properties
67 /// Commonly used keys: description, coordinate_2d, coordinate_3d, inputs, outputs, signature
68 #[serde(default)]
69 pub properties: HashMap<String, serde_json::Value>,
70}
71
72impl BrainRegion {
73 /// Create a new brain region
74 ///
75 /// # Arguments
76 ///
77 /// * `region_id` - Unique identifier (validated RegionID)
78 /// * `name` - Human-readable name
79 /// * `region_type` - Functional type
80 ///
81 /// # Errors
82 ///
83 /// Returns error if name is empty
84 ///
85 pub fn new(
86 region_id: RegionID,
87 name: String,
88 region_type: RegionType,
89 ) -> Result<Self, FeagiDataError> {
90 if name.trim().is_empty() {
91 return Err(FeagiDataError::BadParameters(
92 "name cannot be empty".to_string(),
93 ));
94 }
95
96 Ok(Self {
97 region_id,
98 name,
99 region_type,
100 cortical_areas: HashSet::new(),
101 properties: HashMap::new(),
102 })
103 }
104
105 /// Create a region with initial cortical areas
106 pub fn with_areas(mut self, areas: impl IntoIterator<Item = CorticalID>) -> Self {
107 self.cortical_areas.extend(areas);
108 self
109 }
110
111 /// Create a region with custom properties
112 pub fn with_properties(mut self, properties: HashMap<String, serde_json::Value>) -> Self {
113 self.properties = properties;
114 self
115 }
116
117 /// Add a cortical area to this region
118 ///
119 /// Returns `true` if the area was newly added, `false` if it was already present
120 ///
121 pub fn add_area(&mut self, area_id: CorticalID) -> bool {
122 self.cortical_areas.insert(area_id)
123 }
124
125 /// Remove a cortical area from this region
126 ///
127 /// Returns `true` if the area was present and removed, `false` if it wasn't present
128 ///
129 pub fn remove_area(&mut self, area_id: &CorticalID) -> bool {
130 self.cortical_areas.remove(area_id)
131 }
132
133 /// Check if this region contains a specific cortical area
134 pub fn contains_area(&self, area_id: &CorticalID) -> bool {
135 self.cortical_areas.contains(area_id)
136 }
137
138 /// Get all cortical area IDs in this region
139 pub fn get_all_areas(&self) -> Vec<&CorticalID> {
140 self.cortical_areas.iter().collect()
141 }
142
143 /// Get the number of cortical areas in this region
144 pub fn area_count(&self) -> usize {
145 self.cortical_areas.len()
146 }
147
148 /// Clear all cortical areas from this region
149 pub fn clear_areas(&mut self) {
150 self.cortical_areas.clear();
151 }
152
153 /// Get a property value by key
154 pub fn get_property(&self, key: &str) -> Option<&serde_json::Value> {
155 self.properties.get(key)
156 }
157
158 /// Add a property to the region
159 pub fn add_property(&mut self, key: String, value: serde_json::Value) {
160 self.properties.insert(key, value);
161 }
162
163 /// Convert to dictionary representation (for serialization)
164 pub fn to_dict(&self) -> serde_json::Value {
165 // Convert CorticalIDs to their base64 string representation for JSON
166 let area_ids: Vec<String> = self
167 .cortical_areas
168 .iter()
169 .map(|id| id.as_base_64())
170 .collect();
171
172 let mut dict = serde_json::json!({
173 "id": self.region_id.to_string(),
174 "name": self.name,
175 "region_type": self.region_type.to_string(),
176 "cortical_areas": area_ids,
177 });
178
179 // Add all properties from the HashMap
180 for (key, value) in &self.properties {
181 dict[key] = value.clone();
182 }
183
184 dict
185 }
186}