feat(ffi): add bloodstream and bladder metrics API
Multi-Platform CI / test-platforms (ubuntu-22.04) (push) Successful in 18s
Multi-Platform CI / test-platforms (windows-latest) (push) Successful in 18s
Multi-Platform CI / Package for Linux x86_64 (push) Has been skipped
Multi-Platform CI / Package for Windows x86_64 (push) Has been skipped
Multi-Platform CI / Create GitHub Release (push) Has been skipped

- add C FFI enums MLBladderPhase, MLMetabolicState, MLPerfusionState
- add C FFI structs MLBloodstreamMetrics and MLBladderMetrics
- add ml_patient_bloodstream_metrics and ml_patient_bladder_metrics
  to populate metrics for a patient (mirrored in src/ffi.rs)
- update examples/c/ffi_example.c to print new metrics
- add tests for FFI metrics (tests/ffi.rs)

organ model expansions
- bloodstream: add metrics() snapshot and detailed physiology:
  plasma proteins/oncotic pressure, lymph return, RBC cohort tracking,
  erythropoiesis/clearance with HIF, iron/folate/B12 stores, platelets,
  coagulation/fibrinolysis, immune cell counts, complement, acid–base
- bladder: introduce adaptive compliance, reflex gating, cortical/voluntary
  modulators, safety indices; add metrics(), summary, and unit tests
- brain: add homeostatic drives (respiratory, thirst, hunger, thermo, pain),
  brainstem nuclei (NTS/RVLM/CVLM, nAmb/DMV, RTN), sleep cycle timing,
  cerebrovascular autoregulation; wire drives into autonomic control
- heart: add phase-based cycle (valves and atria), conduction system,
  RAAS regulation, improved coronary perfusion
- intestines: add micronutrient absorption feeding erythropoiesis

patient coupling
- expose Patient::bloodstream_metrics() and ::bladder_metrics()
- integrate new organ signals (kidney osmolality, spleen culling, liver
  proteins) and brain–lung/continence control pathways
- re-export BladderMetrics and BloodstreamMetrics in lib.rs

note
- existing FFI remains compatible; this is a surface addition
- ffi/medicallib.h kept in sync with src/ffi.rs
This commit is contained in:
2025-09-30 02:38:27 -07:00
parent 5cf6bbda48
commit 31a0b8a485
17 changed files with 3767 additions and 148 deletions
+70
View File
@@ -0,0 +1,70 @@
use medicallib_rust::{Bladder, Organ, OrganType, Patient};
#[test]
fn bladder_metrics_match_patient_api() {
let patient = Patient::new("metrics")
.unwrap()
.initialize_default()
.with_organ(OrganType::Bladder);
let metrics_via_patient = patient.bladder_metrics().expect("bladder present");
let bladder = patient.find_organ_typed::<Bladder>().unwrap();
let direct = bladder.metrics();
assert!((metrics_via_patient.urgency - direct.urgency).abs() < 1e-6);
assert_eq!(metrics_via_patient.phase, direct.phase);
assert_eq!(metrics_via_patient.capacity_ml, direct.capacity_ml);
}
#[test]
fn voluntary_hold_updates_metrics() {
let mut patient = Patient::new("hold")
.unwrap()
.initialize_default()
.with_organ(OrganType::Bladder);
{
let bladder = patient.find_organ_typed_mut::<Bladder>().unwrap();
bladder.volume_ml = bladder.micturition_threshold_ml + 140.0;
bladder.urgency = 0.82;
bladder.cortical_inhibition = 0.1;
bladder.voluntary_hold_command = 0.1;
bladder.continence_training = 0.45;
bladder.update(0.5);
}
let baseline = patient.bladder_metrics().unwrap();
{
let bladder = patient.find_organ_typed_mut::<Bladder>().unwrap();
bladder.cortical_inhibition = 0.9;
bladder.voluntary_hold_command = 0.9;
bladder.continence_training = 0.9;
bladder.update(0.5);
}
let hold = patient.bladder_metrics().unwrap();
assert!(hold.voluntary_hold_fraction > baseline.voluntary_hold_fraction);
assert!(hold.cortical_gate_fraction > baseline.cortical_gate_fraction);
assert!(hold.cortical_gate_fraction <= 1.0);
assert!(hold.voluntary_hold_fraction <= 1.0);
}
#[cfg(feature = "ffi")]
#[test]
fn ffi_exposes_bladder_metrics() {
use core::mem::MaybeUninit;
use medicallib_rust::ffi::{
ml_patient_bladder_metrics, ml_patient_free, ml_patient_new, ml_patient_update,
MLBladderMetrics, MLPatient, ML_ERR, ML_OK,
};
use std::ffi::CString;
unsafe {
let id = CString::new("ffi-metrics").unwrap();
let patient: *mut MLPatient = ml_patient_new(id.as_ptr());
assert!(!patient.is_null());
assert_eq!(ml_patient_update(patient, 0.5), ML_OK);
let mut metrics = MaybeUninit::<MLBladderMetrics>::zeroed();
let status = ml_patient_bladder_metrics(patient as *const MLPatient, metrics.as_mut_ptr());
assert_eq!(status, ML_ERR);
ml_patient_free(patient);
}
}
+21
View File
@@ -29,3 +29,24 @@ fn ffi_errors() {
let s = medicallib_rust::ffi::ml_patient_summary(std::ptr::null());
assert!(s.is_null());
}
#[cfg(feature = "ffi")]
#[test]
fn ffi_bloodstream_metrics() {
use medicallib_rust::ffi::*;
use std::ffi::CString;
let id = CString::new("ffi-blood").unwrap();
let p = ml_patient_new(id.as_ptr());
assert!(!p.is_null());
let mut metrics = std::mem::MaybeUninit::<MLBloodstreamMetrics>::uninit();
assert_eq!(ml_patient_update(p, 0.5), ML_OK);
let rc = ml_patient_bloodstream_metrics(p, metrics.as_mut_ptr());
assert_eq!(rc, ML_OK);
let metrics = unsafe { metrics.assume_init() };
assert!(metrics.oncotic_pressure_mm_hg > 15.0);
assert!(metrics.rbc_mature_fraction > 0.4);
ml_patient_free(p);
}