1use std::collections::HashMap;
11
12use qualia_core_db::container_10d::ProvenanceSidecar;
13use qualia_core_db::hypermedia::{
14 container_to_nquins, descriptors_to_nquins, AssetRef, AssetRole, Descriptors,
15 HypermediaContainer,
16};
17use qualia_core_db::render::compile_10d::{compile_organ_asset, CompiledAsset};
18use qualia_core_db::NQuin;
19use wellfare_core::anatomy::{body_system_for_organ, AnatomyModel};
20
21pub struct CompiledOrgan {
23 pub organ_key: String,
24 pub system_id: String,
25 pub asset: CompiledAsset,
26}
27
28pub struct BodyCompileResult {
30 pub model: AnatomyModel,
31 pub organs: Vec<CompiledOrgan>,
32 pub unmapped: Vec<String>,
34 pub failed: Vec<(String, String)>,
36}
37
38impl BodyCompileResult {
39 pub fn compiled_count(&self) -> usize {
41 self.organs.len()
42 }
43}
44
45fn format_of(organ_key: &str) -> &'static str {
47 match organ_key
48 .rsplit('.')
49 .next()
50 .unwrap_or("")
51 .to_ascii_lowercase()
52 .as_str()
53 {
54 "obj" => "obj",
55 "stl" => "stl",
56 "gltf" => "gltf",
57 _ => "glb",
58 }
59}
60
61pub fn compile_body(model: AnatomyModel, organs: &[(String, Vec<u8>)]) -> BodyCompileResult {
63 let mut compiled = Vec::new();
64 let mut unmapped = Vec::new();
65 let mut failed = Vec::new();
66 for (organ_key, bytes) in organs {
67 let Some(system_id) = body_system_for_organ(organ_key) else {
68 unmapped.push(organ_key.clone());
69 continue;
70 };
71 let fmt = format_of(organ_key);
72 let uri = format!("urn:qualia:anatomy:{}:{organ_key}", model.as_str());
73 let provenance = ProvenanceSidecar::new(
77 format!("urn:hra:ccf:{organ_key}").into_bytes(),
78 "model/gltf-binary",
79 "CC-BY-4.0",
80 );
81 match compile_organ_asset(
82 bytes,
83 Some(fmt),
84 &uri,
85 fmt,
86 Some(system_id),
87 Some(model.as_str()),
88 Some(&provenance),
89 ) {
90 Ok(asset) => compiled.push(CompiledOrgan {
91 organ_key: organ_key.clone(),
92 system_id: system_id.to_string(),
93 asset,
94 }),
95 Err(e) => failed.push((organ_key.clone(), e.to_string())),
96 }
97 }
98 BodyCompileResult {
99 model,
100 organs: compiled,
101 unmapped,
102 failed,
103 }
104}
105
106pub struct OrganContainer {
117 pub container: HypermediaContainer,
118 pub quins: Vec<NQuin>,
119 pub lexicon: HashMap<u64, String>,
120}
121
122fn organ_uri(model: AnatomyModel, organ_key: &str) -> String {
124 format!("urn:qualia:anatomy:{}:{organ_key}", model.as_str())
125}
126
127pub fn organ_container(
130 organ: &CompiledOrgan,
131 model: AnatomyModel,
132 source_url: Option<&str>,
133) -> OrganContainer {
134 let ouri = organ_uri(model, &organ.organ_key);
135 let source_uri = source_url
136 .map(str::to_string)
137 .unwrap_or_else(|| format!("urn:hra:ccf:{}", organ.organ_key));
138
139 let primary = AssetRef::new(
140 &ouri,
141 organ.asset.compiled_digest as u64,
142 "model/qualia-10d",
143 AssetRole::Primary,
144 )
145 .derived_from(&source_uri);
146 let mut source = AssetRef::new(
147 &source_uri,
148 organ.asset.source_digest as u64,
149 "model/gltf-binary",
150 AssetRole::Source,
151 )
152 .with_licence("CC-BY-4.0");
153 source.creator = Some("Human Reference Atlas (CCF), lod.humanatlas.io".into());
154 let provenance = AssetRef::new(
155 format!("{ouri}#provenance"),
156 0,
157 "application/ld+json",
158 AssetRole::Provenance,
159 );
160
161 let container = HypermediaContainer::new(
162 format!("urn:qualia:container:{}", organ.organ_key),
163 primary.clone(),
164 )
165 .with_related(source)
166 .with_related(provenance);
167
168 let (mut quins, mut lexicon) = container_to_nquins(&container);
169 quins.extend(organ.asset.quins.iter().cloned());
171 for (k, v) in &organ.asset.lexicon {
172 lexicon.entry(*k).or_insert_with(|| v.clone());
173 }
174
175 let label = organ
177 .organ_key
178 .rsplit('/')
179 .next()
180 .unwrap_or(&organ.organ_key)
181 .trim_end_matches(".glb")
182 .trim_end_matches(".obj")
183 .trim_end_matches(".gltf")
184 .to_string();
185 let descriptors = Descriptors {
186 topics: vec!["anatomy".into(), "biology".into(), organ.system_id.clone()],
187 depicts: vec![label],
188 document_type: Some("3d-anatomy-model".into()),
189 ..Default::default()
190 };
191 let (dq, dl) = descriptors_to_nquins(primary.subject(), &descriptors);
192 quins.extend(dq);
193 for (k, v) in dl {
194 lexicon.entry(k).or_insert(v);
195 }
196
197 OrganContainer {
198 container,
199 quins,
200 lexicon,
201 }
202}
203
204pub fn body_container(model: AnatomyModel, organs: &[OrganContainer]) -> OrganContainer {
208 let body_uri = format!("urn:qualia:anatomy:{}:body", model.as_str());
209 let primary = AssetRef::new(&body_uri, 0, "application/qualia-body", AssetRole::Primary);
210 let mut container = HypermediaContainer::new(
211 format!("urn:qualia:container:{}:body", model.as_str()),
212 primary.clone(),
213 );
214 for oc in organs {
215 let p = &oc.container.primary;
217 container = container.with_related(AssetRef::new(
218 &p.uri,
219 p.digest,
220 &p.media_type,
221 AssetRole::Related,
222 ));
223 }
224
225 let (mut quins, mut lexicon) = container_to_nquins(&container);
226 for oc in organs {
227 quins.extend(oc.quins.iter().cloned());
228 for (k, v) in &oc.lexicon {
229 lexicon.entry(*k).or_insert_with(|| v.clone());
230 }
231 }
232 let descriptors = Descriptors {
233 topics: vec!["anatomy".into(), "biology".into()],
234 depicts: vec![format!("{} body", model.as_str())],
235 document_type: Some("3d-anatomy-body".into()),
236 ..Default::default()
237 };
238 let (dq, dl) = descriptors_to_nquins(primary.subject(), &descriptors);
239 quins.extend(dq);
240 for (k, v) in dl {
241 lexicon.entry(k).or_insert(v);
242 }
243 OrganContainer {
244 container,
245 quins,
246 lexicon,
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 const TRI_OBJ: &[u8] = b"v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3\n";
257
258 #[test]
259 fn compile_body_resolves_systems_binds_facts_and_reports_unmapped() {
260 let organs = vec![
261 ("3d-vh-m-lung.obj".to_string(), TRI_OBJ.to_vec()),
262 (
263 "3d-vh-m-blood-vasculature.obj".to_string(),
264 TRI_OBJ.to_vec(),
265 ),
266 ("3d-vh-m-flux-capacitor.obj".to_string(), TRI_OBJ.to_vec()),
267 ];
268 let result = compile_body(AnatomyModel::Male, &organs);
269 assert_eq!(result.compiled_count(), 2);
270 assert_eq!(
271 result.unmapped,
272 vec!["3d-vh-m-flux-capacitor.obj".to_string()]
273 );
274 assert!(result.failed.is_empty());
275
276 let lung = result
278 .organs
279 .iter()
280 .find(|o| o.organ_key.contains("lung"))
281 .unwrap();
282 assert_eq!(lung.system_id, "respiratory");
283 let vasc = result
284 .organs
285 .iter()
286 .find(|o| o.organ_key.contains("vasculature"))
287 .unwrap();
288 assert_eq!(vasc.system_id, "circulatory");
289
290 for organ in &result.organs {
292 let vals: Vec<&str> = organ.asset.lexicon.values().map(String::as_str).collect();
293 assert!(
294 vals.contains(&organ.system_id.as_str()),
295 "bodySystem fact present"
296 );
297 assert!(vals.contains(&"male"), "anatomyModel fact present");
298 assert!(
299 organ.asset.container_10d.len() > 64,
300 "sealed .10d container"
301 );
302 }
303 }
304
305 #[test]
308 #[ignore = "requires a real GLB on disk via QUALIA_TEST_GLB"]
309 fn compile_real_ccf_organ_end_to_end() {
310 let path = std::env::var("QUALIA_TEST_GLB").expect("set QUALIA_TEST_GLB to a .glb path");
311 let bytes = std::fs::read(&path).expect("read glb");
312 let src_len = bytes.len();
313 let filename = std::path::Path::new(&path)
314 .file_name()
315 .unwrap()
316 .to_string_lossy()
317 .to_string();
318 let result = compile_body(AnatomyModel::Male, &[(filename.clone(), bytes)]);
319 assert_eq!(
320 result.compiled_count(),
321 1,
322 "unmapped={:?} failed={:?}",
323 result.unmapped,
324 result.failed
325 );
326 let organ = &result.organs[0];
327 let mesh = qualia_core_db::render::compile_10d::decode_10d_mesh(&organ.asset.container_10d)
329 .unwrap();
330 eprintln!(
331 "REAL CCF ORGAN {filename} → system={} · {} verts / {} tris · GLB {src_len} B → .10d {} B ({:.2}x)",
332 organ.system_id,
333 mesh.vertex_count(),
334 mesh.triangle_count(),
335 organ.asset.container_10d.len(),
336 src_len as f64 / organ.asset.container_10d.len() as f64,
337 );
338 }
339
340 #[test]
343 #[ignore = "live network: discovers + fetches + compiles a full model (QUALIA_TEST_MODEL=male|female)"]
344 fn compile_full_body_from_sparql() {
345 use crate::wellfair::ccf_resolver::{
346 discover_ref_organs, fetch_glb, organs_for_model, HRA_SPARQL_ENDPOINT,
347 };
348 let model = match std::env::var("QUALIA_TEST_MODEL").as_deref() {
349 Ok("female") => AnatomyModel::Female,
350 _ => AnatomyModel::Male,
351 };
352 let all = discover_ref_organs(HRA_SPARQL_ENDPOINT).expect("SPARQL discovery");
353 let set = organs_for_model(&all, model);
354 eprintln!(
355 "discovered {} total organs, {} {}",
356 all.len(),
357 set.len(),
358 model.as_str()
359 );
360 assert!(
361 set.len() > 20,
362 "expected a full {} set, got {}",
363 model.as_str(),
364 set.len()
365 );
366
367 let mut fetched = Vec::new();
368 let mut total_glb = 0usize;
369 for organ in &set {
370 match fetch_glb(&organ.glb_url) {
371 Ok(bytes) => {
372 total_glb += bytes.len();
373 fetched.push((organ.filename.clone(), bytes));
374 }
375 Err(e) => eprintln!(" fetch FAILED {}: {e}", organ.filename),
376 }
377 }
378
379 let result = compile_body(model, &fetched);
380 let total_10d: usize = result
381 .organs
382 .iter()
383 .map(|o| o.asset.container_10d.len())
384 .sum();
385 let mut systems: Vec<&str> = result.organs.iter().map(|o| o.system_id.as_str()).collect();
386 systems.sort();
387 systems.dedup();
388 eprintln!(
389 "{} BODY: {} / {} organs compiled · {} systems {:?} · unmapped={:?} failed={:?} · GLB {} B → .10d {} B ({:.2}x)",
390 model.as_str().to_uppercase(),
391 result.compiled_count(),
392 set.len(),
393 systems.len(),
394 systems,
395 result.unmapped,
396 result.failed.iter().map(|(k, _)| k).collect::<Vec<_>>(),
397 total_glb,
398 total_10d,
399 total_glb as f64 / total_10d.max(1) as f64,
400 );
401 assert!(
403 result.unmapped.is_empty(),
404 "unmapped organs: {:?}",
405 result.unmapped
406 );
407 assert!(
408 result.failed.is_empty(),
409 "failed organs: {:?}",
410 result.failed
411 );
412 }
413
414 #[test]
415 fn bad_bytes_are_reported_not_silently_dropped() {
416 let organs = vec![("3d-vh-f-lung.glb".to_string(), vec![0u8, 1, 2, 3])];
418 let result = compile_body(AnatomyModel::Female, &organs);
419 assert_eq!(result.compiled_count(), 0);
420 assert_eq!(result.failed.len(), 1);
421 assert_eq!(result.failed[0].0, "3d-vh-f-lung.glb");
422 }
423
424 #[test]
425 fn organ_becomes_a_searchable_provenance_carrying_container() {
426 use qualia_core_db::hypermedia::{by_topic, derived_from, provenance_of};
427 let organs = vec![("3d-vh-m-lung.obj".to_string(), TRI_OBJ.to_vec())];
428 let body = compile_body(AnatomyModel::Male, &organs);
429 let organ = &body.organs[0];
430 let oc = organ_container(
431 organ,
432 AnatomyModel::Male,
433 Some("https://cdn.humanatlas.io/hra/lung.glb"),
434 );
435
436 let primary = oc.container.primary.subject();
437 assert!(
439 by_topic(&oc.quins, "respiratory").contains(&primary),
440 "found by system topic"
441 );
442 assert!(by_topic(&oc.quins, "anatomy").contains(&primary));
443 assert!(
445 !derived_from(&oc.quins, primary).is_empty(),
446 "derived from its source GLB"
447 );
448 assert!(
449 provenance_of(&oc.quins, primary).is_some(),
450 "provenance record bound"
451 );
452 let vals: Vec<&str> = oc.lexicon.values().map(String::as_str).collect();
454 assert!(
455 vals.contains(&"respiratory"),
456 "manifest system fact present in the container"
457 );
458 assert!(
459 oc.quins.iter().all(|q| q.verify_ecc_parity()),
460 "all quins valid parity"
461 );
462 }
463
464 #[test]
465 fn body_container_bundles_the_organs_as_a_semantic_unit() {
466 use qualia_core_db::hypermedia::bundled;
467 let organs = vec![
468 ("3d-vh-m-lung.obj".to_string(), TRI_OBJ.to_vec()),
469 (
470 "3d-vh-m-blood-vasculature.obj".to_string(),
471 TRI_OBJ.to_vec(),
472 ),
473 ];
474 let result = compile_body(AnatomyModel::Male, &organs);
475 let ocs: Vec<_> = result
476 .organs
477 .iter()
478 .map(|o| organ_container(o, AnatomyModel::Male, None))
479 .collect();
480 let body = body_container(AnatomyModel::Male, &ocs);
481 let members = bundled(&body.quins, body.container.subject());
483 assert!(members.len() >= 2, "body bundles its organ members");
484 }
485}