1use crate::common::ApiState;
8use crate::common::{ApiError, ApiResult, Json, Query, State};
9use std::collections::HashMap;
10
11#[utoipa::path(
13 post,
14 path = "/v1/cortical_mapping/afferents",
15 tag = "cortical_mapping"
16)]
17pub async fn post_afferents(
18 State(_state): State<ApiState>,
19 Json(_req): Json<HashMap<String, String>>,
20) -> ApiResult<Json<Vec<String>>> {
21 Err(ApiError::internal("Not yet implemented"))
22}
23
24#[utoipa::path(
26 post,
27 path = "/v1/cortical_mapping/efferents",
28 tag = "cortical_mapping"
29)]
30pub async fn post_efferents(
31 State(_state): State<ApiState>,
32 Json(_req): Json<HashMap<String, String>>,
33) -> ApiResult<Json<Vec<String>>> {
34 Err(ApiError::internal("Not yet implemented"))
35}
36
37#[utoipa::path(
39 post,
40 path = "/v1/cortical_mapping/mapping_properties",
41 tag = "cortical_mapping",
42 responses(
43 (status = 200, description = "Cortical mapping connections", body = Vec<serde_json::Value>),
44 (status = 500, description = "Internal server error")
45 )
46)]
47pub async fn post_mapping_properties(
48 State(state): State<ApiState>,
49 Json(req): Json<HashMap<String, serde_json::Value>>,
50) -> ApiResult<Json<Vec<serde_json::Value>>> {
51 use tracing::debug;
52
53 let src_area = req
54 .get("src_cortical_area")
55 .and_then(|v| v.as_str())
56 .ok_or_else(|| ApiError::invalid_input("Missing src_cortical_area"))?;
57
58 let dst_area = req
59 .get("dst_cortical_area")
60 .and_then(|v| v.as_str())
61 .ok_or_else(|| ApiError::invalid_input("Missing dst_cortical_area"))?;
62
63 debug!(target: "feagi-api", "Getting mapping properties: {} -> {}", src_area, dst_area);
64
65 let connectome_service = state.connectome_service.as_ref();
66
67 let src_area_info = connectome_service
69 .get_cortical_area(src_area)
70 .await
71 .map_err(|e| {
72 ApiError::not_found("Cortical area", &format!("Source area {}: {}", src_area, e))
73 })?;
74
75 let mapping_dst = src_area_info
77 .properties
78 .get("cortical_mapping_dst")
79 .and_then(|v| v.as_object());
80
81 if mapping_dst.is_none() {
82 debug!(target: "feagi-api", "No cortical_mapping_dst found for {}", src_area);
83 return Ok(Json(vec![]));
84 }
85
86 let connections = mapping_dst
88 .unwrap()
89 .get(dst_area)
90 .and_then(|v| v.as_array());
91
92 if connections.is_none() {
93 debug!(target: "feagi-api", "No connections found from {} to {}", src_area, dst_area);
94 return Ok(Json(vec![]));
95 }
96
97 let mut formatted = Vec::new();
99 for conn in connections.unwrap() {
100 if let Some(arr) = conn.as_array() {
101 if arr.len() < 8 {
105 return Err(ApiError::invalid_input(format!(
106 "Invalid dstmap rule array (expected 8 elements including plasticity_window), got {}: {:?}",
107 arr.len(),
108 arr
109 )));
110 }
111 let morphology_id = arr[0]
113 .as_str()
114 .ok_or_else(|| ApiError::invalid_input("morphology_id must be a string"))?;
115 let morphology_scalar = arr[1].clone();
116 let psc_multiplier = arr[2].as_i64().ok_or_else(|| {
117 ApiError::invalid_input("postSynapticCurrent_multiplier must be an integer")
118 })?;
119 let plasticity_flag = arr[3]
120 .as_bool()
121 .ok_or_else(|| ApiError::invalid_input("plasticity_flag must be a boolean"))?;
122 let plasticity_constant = arr[4]
123 .as_i64()
124 .ok_or_else(|| ApiError::invalid_input("plasticity_constant must be an integer"))?;
125 let ltp_multiplier = arr[5]
126 .as_i64()
127 .ok_or_else(|| ApiError::invalid_input("ltp_multiplier must be an integer"))?;
128 let ltd_multiplier = arr[6]
129 .as_i64()
130 .ok_or_else(|| ApiError::invalid_input("ltd_multiplier must be an integer"))?;
131 let plasticity_window = arr[7]
132 .as_i64()
133 .ok_or_else(|| ApiError::invalid_input("plasticity_window must be an integer"))?;
134
135 formatted.push(serde_json::json!({
136 "morphology_id": morphology_id,
137 "morphology_scalar": morphology_scalar,
138 "postSynapticCurrent_multiplier": psc_multiplier,
139 "plasticity_flag": plasticity_flag,
140 "plasticity_constant": plasticity_constant,
141 "ltp_multiplier": ltp_multiplier,
142 "ltd_multiplier": ltd_multiplier,
143 "plasticity_window": plasticity_window,
144 }));
145 } else if let Some(obj) = conn.as_object() {
146 let morphology_id = obj
148 .get("morphology_id")
149 .and_then(|v| v.as_str())
150 .ok_or_else(|| ApiError::invalid_input("morphology_id must be a string"))?;
151 let morphology_scalar = obj
152 .get("morphology_scalar")
153 .cloned()
154 .ok_or_else(|| ApiError::invalid_input("morphology_scalar missing"))?;
155 let psc_multiplier = obj
156 .get("postSynapticCurrent_multiplier")
157 .and_then(|v| v.as_i64())
158 .ok_or_else(|| {
159 ApiError::invalid_input("postSynapticCurrent_multiplier must be an integer")
160 })?;
161 let plasticity_flag = obj
162 .get("plasticity_flag")
163 .and_then(|v| v.as_bool())
164 .ok_or_else(|| ApiError::invalid_input("plasticity_flag must be a boolean"))?;
165 let plasticity_constant = obj
166 .get("plasticity_constant")
167 .and_then(|v| v.as_i64())
168 .ok_or_else(|| ApiError::invalid_input("plasticity_constant must be an integer"))?;
169 let ltp_multiplier = obj
170 .get("ltp_multiplier")
171 .and_then(|v| v.as_i64())
172 .ok_or_else(|| ApiError::invalid_input("ltp_multiplier must be an integer"))?;
173 let ltd_multiplier = obj
174 .get("ltd_multiplier")
175 .and_then(|v| v.as_i64())
176 .ok_or_else(|| ApiError::invalid_input("ltd_multiplier must be an integer"))?;
177 let plasticity_window = obj
178 .get("plasticity_window")
179 .and_then(|v| v.as_i64())
180 .ok_or_else(|| ApiError::invalid_input("plasticity_window must be an integer"))?;
181
182 formatted.push(serde_json::json!({
183 "morphology_id": morphology_id,
184 "morphology_scalar": morphology_scalar,
185 "postSynapticCurrent_multiplier": psc_multiplier,
186 "plasticity_flag": plasticity_flag,
187 "plasticity_constant": plasticity_constant,
188 "ltp_multiplier": ltp_multiplier,
189 "ltd_multiplier": ltd_multiplier,
190 "plasticity_window": plasticity_window,
191 }));
192 }
193 }
194
195 debug!(target: "feagi-api", "Returning {} mapping connections from {} to {}", formatted.len(), src_area, dst_area);
196 Ok(Json(formatted))
197}
198
199#[utoipa::path(
201 put,
202 path = "/v1/cortical_mapping/mapping_properties",
203 tag = "cortical_mapping",
204 responses(
205 (status = 200, description = "Cortical mapping updated successfully", body = HashMap<String, serde_json::Value>),
206 (status = 404, description = "Cortical area not found"),
207 (status = 500, description = "Internal server error")
208 )
209)]
210pub async fn put_mapping_properties(
211 State(state): State<ApiState>,
212 Json(req): Json<HashMap<String, serde_json::Value>>,
213) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
214 use tracing::{debug, info};
215
216 let src_area = req
217 .get("src_cortical_area")
218 .and_then(|v| v.as_str())
219 .ok_or_else(|| ApiError::invalid_input("Missing src_cortical_area"))?;
220
221 let dst_area = req
222 .get("dst_cortical_area")
223 .and_then(|v| v.as_str())
224 .ok_or_else(|| ApiError::invalid_input("Missing dst_cortical_area"))?;
225
226 let mapping_string = req
227 .get("mapping_string")
228 .and_then(|v| v.as_array())
229 .ok_or_else(|| ApiError::invalid_input("Missing mapping_string"))?;
230
231 info!(
232 target: "feagi-api",
233 "PUT cortical mapping: {} -> {} with {} connections",
234 src_area,
235 dst_area,
236 mapping_string.len()
237 );
238 debug!(target: "feagi-api", "Mapping data: {:?}", mapping_string);
239
240 let connectome_service = state.connectome_service.as_ref();
241
242 let synapse_count = connectome_service
244 .update_cortical_mapping(
245 src_area.to_string(),
246 dst_area.to_string(),
247 mapping_string.clone(),
248 )
249 .await
250 .map_err(|e| match e {
251 feagi_services::types::ServiceError::InvalidInput(msg) => ApiError::invalid_input(msg),
252 _ => ApiError::internal(format!("Failed to update cortical mapping: {}", e)),
253 })?;
254
255 info!(target: "feagi-api", "Cortical mapping updated successfully: {} synapses created", synapse_count);
256
257 let mut response = HashMap::new();
259 response.insert(
260 "message".to_string(),
261 serde_json::json!(format!(
262 "Cortical mapping properties updated successfully from {} to {}",
263 src_area, dst_area
264 )),
265 );
266 response.insert(
267 "synapse_count".to_string(),
268 serde_json::json!(synapse_count),
269 );
270 response.insert("src_region".to_string(), serde_json::json!(null)); response.insert("dst_region".to_string(), serde_json::json!(null)); Ok(Json(response))
274}
275
276#[utoipa::path(
279 get,
280 path = "/v1/cortical_mapping/mapping",
281 tag = "cortical_mapping",
282 params(
283 ("src_cortical_area" = String, Query, description = "Source cortical area ID"),
284 ("dst_cortical_area" = String, Query, description = "Destination cortical area ID")
285 ),
286 responses(
287 (status = 200, description = "Mapping properties", body = HashMap<String, serde_json::Value>)
288 )
289)]
290pub async fn get_mapping(
291 State(state): State<ApiState>,
292 Query(params): Query<HashMap<String, String>>,
293) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
294 let src_area = params
295 .get("src_cortical_area")
296 .ok_or_else(|| ApiError::invalid_input("src_cortical_area required"))?;
297 let dst_area = params
298 .get("dst_cortical_area")
299 .ok_or_else(|| ApiError::invalid_input("dst_cortical_area required"))?;
300
301 let connectome_service = state.connectome_service.as_ref();
303
304 let src_area_info = connectome_service
306 .get_cortical_area(src_area)
307 .await
308 .map_err(|e| {
309 ApiError::not_found("Cortical area", &format!("Source area {}: {}", src_area, e))
310 })?;
311
312 let mapping_dst = src_area_info
314 .properties
315 .get("cortical_mapping_dst")
316 .and_then(|v| v.as_object());
317
318 if mapping_dst.is_none() {
319 return Ok(Json(HashMap::new()));
320 }
321
322 let connections = mapping_dst
324 .unwrap()
325 .get(dst_area)
326 .and_then(|v| v.as_array());
327
328 let mut response = HashMap::new();
329 response.insert(
330 "connections".to_string(),
331 serde_json::json!(connections.unwrap_or(&vec![])),
332 );
333
334 Ok(Json(response))
335}
336
337#[utoipa::path(
340 get,
341 path = "/v1/cortical_mapping/mapping_list",
342 tag = "cortical_mapping",
343 responses(
344 (status = 200, description = "List of all mappings", body = Vec<Vec<String>>)
345 )
346)]
347pub async fn get_mapping_list(State(state): State<ApiState>) -> ApiResult<Json<Vec<Vec<String>>>> {
348 let connectome_service = state.connectome_service.as_ref();
349
350 let areas = connectome_service
351 .list_cortical_areas()
352 .await
353 .map_err(|e| ApiError::internal(format!("Failed to list areas: {}", e)))?;
354
355 let mut mappings = Vec::new();
356
357 for area in &areas {
359 if let Ok(area_detail) = connectome_service
360 .get_cortical_area(&area.cortical_id)
361 .await
362 {
363 if let Some(mapping_dst) = area_detail.properties.get("cortical_mapping_dst") {
364 if let Some(dst_map) = mapping_dst.as_object() {
365 for dst_area_id in dst_map.keys() {
366 mappings.push(vec![area.cortical_id.clone(), dst_area_id.clone()]);
367 }
368 }
369 }
370 }
371 }
372
373 Ok(Json(mappings))
374}
375
376#[utoipa::path(
379 delete,
380 path = "/v1/cortical_mapping/mapping",
381 tag = "cortical_mapping",
382 responses(
383 (status = 200, description = "Mapping deleted", body = HashMap<String, String>)
384 )
385)]
386pub async fn delete_mapping(
387 State(_state): State<ApiState>,
388 Json(_request): Json<HashMap<String, String>>,
389) -> ApiResult<Json<HashMap<String, String>>> {
390 Ok(Json(HashMap::from([(
392 "message".to_string(),
393 "Mapping deletion not yet implemented".to_string(),
394 )])))
395}
396
397#[utoipa::path(
400 post,
401 path = "/v1/cortical_mapping/batch_update",
402 tag = "cortical_mapping",
403 responses(
404 (status = 200, description = "Batch update completed", body = HashMap<String, serde_json::Value>)
405 )
406)]
407pub async fn post_batch_update(
408 State(_state): State<ApiState>,
409 Json(_request): Json<Vec<HashMap<String, String>>>,
410) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
411 let mut response = HashMap::new();
413 response.insert(
414 "message".to_string(),
415 serde_json::json!("Batch update not yet implemented"),
416 );
417 response.insert("updated_count".to_string(), serde_json::json!(0));
418
419 Ok(Json(response))
420}
421
422#[utoipa::path(post, path = "/v1/cortical_mapping/mapping", tag = "cortical_mapping")]
425pub async fn post_mapping(
426 State(_state): State<ApiState>,
427 Json(_req): Json<HashMap<String, serde_json::Value>>,
428) -> ApiResult<Json<HashMap<String, String>>> {
429 Ok(Json(HashMap::from([(
430 "message".to_string(),
431 "Not yet implemented".to_string(),
432 )])))
433}
434
435#[utoipa::path(put, path = "/v1/cortical_mapping/mapping", tag = "cortical_mapping")]
437pub async fn put_mapping(
438 State(_state): State<ApiState>,
439 Json(_req): Json<HashMap<String, serde_json::Value>>,
440) -> ApiResult<Json<HashMap<String, String>>> {
441 Ok(Json(HashMap::from([(
442 "message".to_string(),
443 "Not yet implemented".to_string(),
444 )])))
445}