dez
Quick CI / quick-test (push) Failing after 1m12s
Multi-Platform CI / test-platforms (ubuntu-22.04) (push) Successful in 7m13s
Multi-Platform CI / test-platforms (windows-latest) (push) Successful in 7m8s
Multi-Platform CI / package (ubuntu-22.04) (push) Failing after 7m12s
Multi-Platform CI / package (windows-latest) (push) Failing after 7m13s
Quick CI / quick-test (push) Failing after 1m12s
Multi-Platform CI / test-platforms (ubuntu-22.04) (push) Successful in 7m13s
Multi-Platform CI / test-platforms (windows-latest) (push) Successful in 7m8s
Multi-Platform CI / package (ubuntu-22.04) (push) Failing after 7m12s
Multi-Platform CI / package (windows-latest) (push) Failing after 7m13s
This commit is contained in:
+1
-1
@@ -1,4 +1,5 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use medicallib_rust::Organ;
|
||||
|
||||
fn bench_heart_update(c: &mut Criterion) {
|
||||
c.bench_function("heart_update_1s", |b| {
|
||||
@@ -11,4 +12,3 @@ fn bench_heart_update(c: &mut Criterion) {
|
||||
|
||||
criterion_group!(benches, bench_heart_update);
|
||||
criterion_main!(benches);
|
||||
|
||||
|
||||
@@ -5,4 +5,3 @@ fn main() {
|
||||
p.update(0.5);
|
||||
println!("{}", p.patient_summary());
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,17 @@ fn main() {
|
||||
{
|
||||
use tracing::{info, Level};
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
let subscriber = FmtSubscriber::builder().with_max_level(Level::INFO).finish();
|
||||
let subscriber = FmtSubscriber::builder()
|
||||
.with_max_level(Level::INFO)
|
||||
.finish();
|
||||
let _ = tracing::subscriber::set_global_default(subscriber);
|
||||
info!("starting simulation");
|
||||
}
|
||||
|
||||
let mut p = medicallib_rust::Patient::new("trace-demo").unwrap()
|
||||
let mut p = medicallib_rust::Patient::new("trace-demo")
|
||||
.unwrap()
|
||||
.initialize_default()
|
||||
.with_lungs();
|
||||
p.update(0.2);
|
||||
println!("{}", p.patient_summary());
|
||||
}
|
||||
|
||||
|
||||
@@ -5,4 +5,3 @@ fn main() {
|
||||
let class = medicallib_rust::classify_bmi(bmi);
|
||||
println!("BMI: {:.2} kg/m^2 — {:?}", bmi, class);
|
||||
}
|
||||
|
||||
|
||||
+38
-12
@@ -23,6 +23,7 @@ pub const ML_EINVAL: i32 = 2;
|
||||
|
||||
/// Opaque patient handle type for C consumers.
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct MLPatient {
|
||||
inner: Patient,
|
||||
}
|
||||
@@ -31,7 +32,9 @@ pub struct MLPatient {
|
||||
/// On success writes result to `out_bmi`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn medicallib_bmi(weight_kg: f32, height_m: f32, out_bmi: *mut f32) -> i32 {
|
||||
if out_bmi.is_null() { return ML_EINVAL; }
|
||||
if out_bmi.is_null() {
|
||||
return ML_EINVAL;
|
||||
}
|
||||
match crate::calculate_bmi(weight_kg, height_m) {
|
||||
Ok(v) => unsafe {
|
||||
*out_bmi = v;
|
||||
@@ -44,11 +47,15 @@ pub extern "C" fn medicallib_bmi(weight_kg: f32, height_m: f32, out_bmi: *mut f3
|
||||
/// Create a new patient with id string. Returns null on error.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ml_patient_new(id: *const c_char) -> *mut MLPatient {
|
||||
if id.is_null() { return ptr::null_mut(); }
|
||||
if id.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let cstr = unsafe { CStr::from_ptr(id) };
|
||||
match cstr.to_str() {
|
||||
Ok(s) => match Patient::new(s) {
|
||||
Ok(p) => Box::into_raw(Box::new(MLPatient { inner: p.initialize_default() })),
|
||||
Ok(p) => Box::into_raw(Box::new(MLPatient {
|
||||
inner: p.initialize_default(),
|
||||
})),
|
||||
Err(_) => ptr::null_mut(),
|
||||
},
|
||||
Err(_) => ptr::null_mut(),
|
||||
@@ -58,15 +65,21 @@ pub extern "C" fn ml_patient_new(id: *const c_char) -> *mut MLPatient {
|
||||
/// Destroy a patient handle. Accepts null.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ml_patient_free(p: *mut MLPatient) {
|
||||
if p.is_null() { return; }
|
||||
unsafe { drop(Box::from_raw(p)); }
|
||||
if p.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
drop(Box::from_raw(p));
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a newly-allocated C string summary for the patient.
|
||||
/// Returns null on error. Free the returned string with `ml_string_free`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ml_patient_summary(p: *const MLPatient) -> *mut c_char {
|
||||
if p.is_null() { return ptr::null_mut(); }
|
||||
if p.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let summary = unsafe { (*p).inner.patient_summary() };
|
||||
match CString::new(summary) {
|
||||
Ok(s) => s.into_raw(),
|
||||
@@ -77,22 +90,32 @@ pub extern "C" fn ml_patient_summary(p: *const MLPatient) -> *mut c_char {
|
||||
/// Frees a C string previously returned by this library. Accepts null.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ml_string_free(s: *mut c_char) {
|
||||
if s.is_null() { return; }
|
||||
unsafe { drop(CString::from_raw(s)); }
|
||||
if s.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
drop(CString::from_raw(s));
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance patient simulation by dt seconds.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ml_patient_update(p: *mut MLPatient, dt_seconds: f32) -> i32 {
|
||||
if p.is_null() { return ML_EINVAL; }
|
||||
unsafe { (*p).inner.update(dt_seconds); }
|
||||
if p.is_null() {
|
||||
return ML_EINVAL;
|
||||
}
|
||||
unsafe {
|
||||
(*p).inner.update(dt_seconds);
|
||||
}
|
||||
ML_OK
|
||||
}
|
||||
|
||||
/// Return organ summary by type code. See header for codes. Caller frees string.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ml_patient_organ_summary(p: *const MLPatient, organ_code: u32) -> *mut c_char {
|
||||
if p.is_null() { return ptr::null_mut(); }
|
||||
if p.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let kind = match organ_code {
|
||||
0 => OrganType::Heart,
|
||||
1 => OrganType::Lungs,
|
||||
@@ -111,7 +134,10 @@ pub extern "C" fn ml_patient_organ_summary(p: *const MLPatient, organ_code: u32)
|
||||
};
|
||||
let summary = unsafe { (*p).inner.organ_summary(kind) };
|
||||
match summary {
|
||||
Ok(s) => match CString::new(s) { Ok(cs) => cs.into_raw(), Err(_) => ptr::null_mut() },
|
||||
Ok(s) => match CString::new(s) {
|
||||
Ok(cs) => cs.into_raw(),
|
||||
Err(_) => ptr::null_mut(),
|
||||
},
|
||||
Err(_) => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -27,9 +27,9 @@
|
||||
#![warn(missing_docs, rust_2018_idioms, missing_debug_implementations)]
|
||||
|
||||
mod error;
|
||||
mod types;
|
||||
mod organs;
|
||||
mod patient;
|
||||
mod types;
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
pub mod ffi;
|
||||
|
||||
+25
-6
@@ -12,18 +12,37 @@ pub struct Bladder {
|
||||
|
||||
impl Bladder {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self { info: OrganInfo::new(id, OrganType::Bladder), volume_ml: 100.0, pressure: 5.0 }
|
||||
Self {
|
||||
info: OrganInfo::new(id, OrganType::Bladder),
|
||||
volume_ml: 100.0,
|
||||
pressure: 5.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Organ for Bladder {
|
||||
fn id(&self) -> &str { self.info.id() }
|
||||
fn organ_type(&self) -> OrganType { self.info.kind() }
|
||||
fn id(&self) -> &str {
|
||||
self.info.id()
|
||||
}
|
||||
fn organ_type(&self) -> OrganType {
|
||||
self.info.kind()
|
||||
}
|
||||
fn update(&mut self, _dt_seconds: f32) {
|
||||
// simplistic pressure-volume relation
|
||||
self.pressure = (self.volume_ml / 50.0).clamp(0.0, 30.0);
|
||||
}
|
||||
fn summary(&self) -> String { format!("Bladder[id={}, vol={:.0} ml, P={:.1}]", self.id(), self.volume_ml, self.pressure) }
|
||||
fn as_any(&self) -> &dyn core::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any { self }
|
||||
fn summary(&self) -> String {
|
||||
format!(
|
||||
"Bladder[id={}, vol={:.0} ml, P={:.1}]",
|
||||
self.id(),
|
||||
self.volume_ml,
|
||||
self.pressure
|
||||
)
|
||||
}
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
+20
-7
@@ -12,23 +12,36 @@ pub struct Brain {
|
||||
|
||||
impl Brain {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self { info: OrganInfo::new(id, OrganType::Brain), consciousness: 100, activity_index: 1.0 }
|
||||
Self {
|
||||
info: OrganInfo::new(id, OrganType::Brain),
|
||||
consciousness: 100,
|
||||
activity_index: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Organ for Brain {
|
||||
fn id(&self) -> &str { self.info.id() }
|
||||
fn organ_type(&self) -> OrganType { self.info.kind() }
|
||||
fn id(&self) -> &str {
|
||||
self.info.id()
|
||||
}
|
||||
fn organ_type(&self) -> OrganType {
|
||||
self.info.kind()
|
||||
}
|
||||
fn update(&mut self, _dt_seconds: f32) {
|
||||
self.activity_index = self.activity_index.clamp(0.0, 2.0);
|
||||
}
|
||||
fn summary(&self) -> String {
|
||||
format!(
|
||||
"Brain[id={}, GCS~{}, activity={:.2}]",
|
||||
self.id(), self.consciousness, self.activity_index
|
||||
self.id(),
|
||||
self.consciousness,
|
||||
self.activity_index
|
||||
)
|
||||
}
|
||||
fn as_any(&self) -> &dyn core::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any { self }
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-6
@@ -10,17 +10,30 @@ pub struct Esophagus {
|
||||
|
||||
impl Esophagus {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self { info: OrganInfo::new(id, OrganType::Esophagus), reflux: 0 }
|
||||
Self {
|
||||
info: OrganInfo::new(id, OrganType::Esophagus),
|
||||
reflux: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Organ for Esophagus {
|
||||
fn id(&self) -> &str { self.info.id() }
|
||||
fn organ_type(&self) -> OrganType { self.info.kind() }
|
||||
fn id(&self) -> &str {
|
||||
self.info.id()
|
||||
}
|
||||
fn organ_type(&self) -> OrganType {
|
||||
self.info.kind()
|
||||
}
|
||||
fn update(&mut self, _dt_seconds: f32) {
|
||||
self.reflux = self.reflux.min(100);
|
||||
}
|
||||
fn summary(&self) -> String { format!("Esophagus[id={}, reflux={}]", self.id(), self.reflux) }
|
||||
fn as_any(&self) -> &dyn core::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any { self }
|
||||
fn summary(&self) -> String {
|
||||
format!("Esophagus[id={}, reflux={}]", self.id(), self.reflux)
|
||||
}
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,16 +10,28 @@ pub struct Gallbladder {
|
||||
|
||||
impl Gallbladder {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self { info: OrganInfo::new(id, OrganType::Gallbladder), bile_ml: 30.0 }
|
||||
Self {
|
||||
info: OrganInfo::new(id, OrganType::Gallbladder),
|
||||
bile_ml: 30.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Organ for Gallbladder {
|
||||
fn id(&self) -> &str { self.info.id() }
|
||||
fn organ_type(&self) -> OrganType { self.info.kind() }
|
||||
fn id(&self) -> &str {
|
||||
self.info.id()
|
||||
}
|
||||
fn organ_type(&self) -> OrganType {
|
||||
self.info.kind()
|
||||
}
|
||||
fn update(&mut self, _dt_seconds: f32) {}
|
||||
fn summary(&self) -> String { format!("Gallbladder[id={}, bile={:.0} ml]", self.id(), self.bile_ml) }
|
||||
fn as_any(&self) -> &dyn core::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any { self }
|
||||
fn summary(&self) -> String {
|
||||
format!("Gallbladder[id={}, bile={:.0} ml]", self.id(), self.bile_ml)
|
||||
}
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-6
@@ -1,17 +1,22 @@
|
||||
use super::{Organ, OrganInfo};
|
||||
use crate::types::{BloodPressure, OrganType};
|
||||
|
||||
/// Cardiac model with simple rate and arterial pressure coupling.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Heart {
|
||||
info: OrganInfo,
|
||||
/// Heart rate in beats per minute.
|
||||
pub heart_rate_bpm: f32,
|
||||
/// Arterial blood pressure snapshot.
|
||||
pub arterial_bp: BloodPressure,
|
||||
/// ECG lead count configured for this heart.
|
||||
pub leads: u8,
|
||||
/// Simplified arrhythmia flag; increases HR variability.
|
||||
pub arrhythmia: bool,
|
||||
}
|
||||
|
||||
impl Heart {
|
||||
/// Construct a new heart with the given id and number of ECG leads.
|
||||
pub fn new(id: impl Into<String>, leads: u8) -> Self {
|
||||
Self {
|
||||
info: OrganInfo::new(id, OrganType::Heart),
|
||||
@@ -24,15 +29,19 @@ impl Heart {
|
||||
}
|
||||
|
||||
impl Organ for Heart {
|
||||
fn id(&self) -> &str { self.info.id() }
|
||||
fn organ_type(&self) -> OrganType { self.info.kind() }
|
||||
fn id(&self) -> &str {
|
||||
self.info.id()
|
||||
}
|
||||
fn organ_type(&self) -> OrganType {
|
||||
self.info.kind()
|
||||
}
|
||||
fn update(&mut self, dt_seconds: f32) {
|
||||
let dt = dt_seconds.max(0.0).min(10.0);
|
||||
let dt = dt_seconds.clamp(0.0, 10.0);
|
||||
let target = 70.0f32;
|
||||
let mut diff = target - self.heart_rate_bpm;
|
||||
if self.arrhythmia {
|
||||
// add variability
|
||||
diff += (self.heart_rate_bpm.sin() * 5.0);
|
||||
diff += self.heart_rate_bpm.sin() * 5.0;
|
||||
}
|
||||
self.heart_rate_bpm += 0.1 * diff * (dt / 1.0);
|
||||
// crude BP coupling to HR
|
||||
@@ -51,6 +60,10 @@ impl Organ for Heart {
|
||||
self.arterial_bp.diastolic
|
||||
)
|
||||
}
|
||||
fn as_any(&self) -> &dyn core::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any { self }
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,21 @@ pub struct Intestines {
|
||||
|
||||
impl Intestines {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self { info: OrganInfo::new(id, OrganType::Intestines), absorption: 80, peristalsis: true }
|
||||
Self {
|
||||
info: OrganInfo::new(id, OrganType::Intestines),
|
||||
absorption: 80,
|
||||
peristalsis: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Organ for Intestines {
|
||||
fn id(&self) -> &str { self.info.id() }
|
||||
fn organ_type(&self) -> OrganType { self.info.kind() }
|
||||
fn id(&self) -> &str {
|
||||
self.info.id()
|
||||
}
|
||||
fn organ_type(&self) -> OrganType {
|
||||
self.info.kind()
|
||||
}
|
||||
fn update(&mut self, dt_seconds: f32) {
|
||||
if self.peristalsis {
|
||||
// minor oscillation around 80
|
||||
@@ -26,7 +34,17 @@ impl Organ for Intestines {
|
||||
self.absorption = val as u8;
|
||||
}
|
||||
}
|
||||
fn summary(&self) -> String { format!("Intestines[id={}, absorption={}]", self.id(), self.absorption) }
|
||||
fn as_any(&self) -> &dyn core::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any { self }
|
||||
fn summary(&self) -> String {
|
||||
format!(
|
||||
"Intestines[id={}, absorption={}]",
|
||||
self.id(),
|
||||
self.absorption
|
||||
)
|
||||
}
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
+20
-6
@@ -12,18 +12,32 @@ pub struct Kidneys {
|
||||
|
||||
impl Kidneys {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self { info: OrganInfo::new(id, OrganType::Kidneys), gfr: 100.0, electrolyte_balance: 0.0 }
|
||||
Self {
|
||||
info: OrganInfo::new(id, OrganType::Kidneys),
|
||||
gfr: 100.0,
|
||||
electrolyte_balance: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Organ for Kidneys {
|
||||
fn id(&self) -> &str { self.info.id() }
|
||||
fn organ_type(&self) -> OrganType { self.info.kind() }
|
||||
fn id(&self) -> &str {
|
||||
self.info.id()
|
||||
}
|
||||
fn organ_type(&self) -> OrganType {
|
||||
self.info.kind()
|
||||
}
|
||||
fn update(&mut self, _dt_seconds: f32) {
|
||||
self.gfr = self.gfr.clamp(0.0, 200.0);
|
||||
self.electrolyte_balance = self.electrolyte_balance.clamp(-1.0, 1.0);
|
||||
}
|
||||
fn summary(&self) -> String { format!("Kidneys[id={}, GFR={:.0} ml/min]", self.id(), self.gfr) }
|
||||
fn as_any(&self) -> &dyn core::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any { self }
|
||||
fn summary(&self) -> String {
|
||||
format!("Kidneys[id={}, GFR={:.0} ml/min]", self.id(), self.gfr)
|
||||
}
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
+27
-6
@@ -14,17 +14,38 @@ pub struct Liver {
|
||||
|
||||
impl Liver {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self { info: OrganInfo::new(id, OrganType::Liver), detox: 100, metabolism: 1.0, enzymes: 1.0 }
|
||||
Self {
|
||||
info: OrganInfo::new(id, OrganType::Liver),
|
||||
detox: 100,
|
||||
metabolism: 1.0,
|
||||
enzymes: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Organ for Liver {
|
||||
fn id(&self) -> &str { self.info.id() }
|
||||
fn organ_type(&self) -> OrganType { self.info.kind() }
|
||||
fn id(&self) -> &str {
|
||||
self.info.id()
|
||||
}
|
||||
fn organ_type(&self) -> OrganType {
|
||||
self.info.kind()
|
||||
}
|
||||
fn update(&mut self, _dt_seconds: f32) {
|
||||
self.enzymes = self.enzymes.clamp(0.0, 2.0);
|
||||
}
|
||||
fn summary(&self) -> String { format!("Liver[id={}, detox={}, k={:.2}, enz={:.2}]", self.id(), self.detox, self.metabolism, self.enzymes) }
|
||||
fn as_any(&self) -> &dyn core::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any { self }
|
||||
fn summary(&self) -> String {
|
||||
format!(
|
||||
"Liver[id={}, detox={}, k={:.2}, enz={:.2}]",
|
||||
self.id(),
|
||||
self.detox,
|
||||
self.metabolism,
|
||||
self.enzymes
|
||||
)
|
||||
}
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
+29
-8
@@ -1,39 +1,60 @@
|
||||
use super::{Organ, OrganInfo};
|
||||
use crate::types::OrganType;
|
||||
|
||||
/// Pulmonary model tracking respiratory rate and oxygen saturation.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Lungs {
|
||||
info: OrganInfo,
|
||||
/// Respiratory rate in breaths per minute.
|
||||
pub respiratory_rate_bpm: f32,
|
||||
/// Peripheral oxygen saturation percent.
|
||||
pub spo2_pct: f32,
|
||||
/// Respiratory distress flag reduces SpO2.
|
||||
pub distress: bool,
|
||||
}
|
||||
|
||||
impl Lungs {
|
||||
/// Construct lungs with a given id.
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self { info: OrganInfo::new(id, OrganType::Lungs), respiratory_rate_bpm: 14.0, spo2_pct: 98.0, distress: false }
|
||||
Self {
|
||||
info: OrganInfo::new(id, OrganType::Lungs),
|
||||
respiratory_rate_bpm: 14.0,
|
||||
spo2_pct: 98.0,
|
||||
distress: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Organ for Lungs {
|
||||
fn id(&self) -> &str { self.info.id() }
|
||||
fn organ_type(&self) -> OrganType { self.info.kind() }
|
||||
fn id(&self) -> &str {
|
||||
self.info.id()
|
||||
}
|
||||
fn organ_type(&self) -> OrganType {
|
||||
self.info.kind()
|
||||
}
|
||||
fn update(&mut self, dt_seconds: f32) {
|
||||
let dt = dt_seconds.max(0.0).min(10.0);
|
||||
let dt = dt_seconds.clamp(0.0, 10.0);
|
||||
let target_rr = 14.0;
|
||||
self.respiratory_rate_bpm += 0.1 * (target_rr - self.respiratory_rate_bpm) * (dt / 1.0);
|
||||
// distress drifts SpO2 downward
|
||||
if self.distress { self.spo2_pct -= 0.5 * (dt / 1.0); }
|
||||
if self.distress {
|
||||
self.spo2_pct -= 0.5 * (dt / 1.0);
|
||||
}
|
||||
// keep spo2 in [70, 100]
|
||||
self.spo2_pct = self.spo2_pct.clamp(70.0, 100.0);
|
||||
}
|
||||
fn summary(&self) -> String {
|
||||
format!(
|
||||
"Lungs[id={}, RR={:.1} bpm, SpO2={:.0}%]",
|
||||
self.id(), self.respiratory_rate_bpm, self.spo2_pct
|
||||
self.id(),
|
||||
self.respiratory_rate_bpm,
|
||||
self.spo2_pct
|
||||
)
|
||||
}
|
||||
fn as_any(&self) -> &dyn core::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any { self }
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
+39
-27
@@ -1,6 +1,6 @@
|
||||
//! Organ system trait and implementations.
|
||||
|
||||
use crate::types::{BloodPressure, OrganType};
|
||||
use crate::types::OrganType;
|
||||
use core::fmt::Debug;
|
||||
|
||||
/// Common metadata for organs.
|
||||
@@ -12,47 +12,59 @@ pub struct OrganInfo {
|
||||
|
||||
impl OrganInfo {
|
||||
pub fn new(id: impl Into<String>, kind: OrganType) -> Self {
|
||||
Self { id: id.into(), kind }
|
||||
Self {
|
||||
id: id.into(),
|
||||
kind,
|
||||
}
|
||||
}
|
||||
pub fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
pub fn kind(&self) -> OrganType {
|
||||
self.kind
|
||||
}
|
||||
pub fn id(&self) -> &str { &self.id }
|
||||
pub fn kind(&self) -> OrganType { self.kind }
|
||||
}
|
||||
|
||||
/// Trait implemented by all organs.
|
||||
pub trait Organ: Debug + Send {
|
||||
/// Stable string identifier for this organ instance.
|
||||
fn id(&self) -> &str;
|
||||
/// Kind of organ (e.g., heart, lungs).
|
||||
fn organ_type(&self) -> OrganType;
|
||||
/// Advance the organ simulation by `dt_seconds`.
|
||||
fn update(&mut self, dt_seconds: f32);
|
||||
/// One-line human-readable status summary.
|
||||
fn summary(&self) -> String;
|
||||
/// Type-erased reference for downcasting.
|
||||
fn as_any(&self) -> &dyn core::any::Any;
|
||||
/// Type-erased mutable reference for downcasting.
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any;
|
||||
}
|
||||
|
||||
mod heart;
|
||||
mod lungs;
|
||||
mod brain;
|
||||
mod spinal_cord;
|
||||
mod stomach;
|
||||
mod liver;
|
||||
mod gallbladder;
|
||||
mod pancreas;
|
||||
mod intestines;
|
||||
mod esophagus;
|
||||
mod kidneys;
|
||||
mod bladder;
|
||||
mod brain;
|
||||
mod esophagus;
|
||||
mod gallbladder;
|
||||
mod heart;
|
||||
mod intestines;
|
||||
mod kidneys;
|
||||
mod liver;
|
||||
mod lungs;
|
||||
mod pancreas;
|
||||
mod spinal_cord;
|
||||
mod spleen;
|
||||
mod stomach;
|
||||
|
||||
pub use heart::Heart;
|
||||
pub use lungs::Lungs;
|
||||
pub use brain::Brain;
|
||||
pub use spinal_cord::SpinalCord;
|
||||
pub use stomach::Stomach;
|
||||
pub use liver::Liver;
|
||||
pub use gallbladder::Gallbladder;
|
||||
pub use pancreas::Pancreas;
|
||||
pub use intestines::Intestines;
|
||||
pub use esophagus::Esophagus;
|
||||
pub use kidneys::Kidneys;
|
||||
pub use bladder::Bladder;
|
||||
pub use brain::Brain;
|
||||
pub use esophagus::Esophagus;
|
||||
pub use gallbladder::Gallbladder;
|
||||
pub use heart::Heart;
|
||||
pub use intestines::Intestines;
|
||||
pub use kidneys::Kidneys;
|
||||
pub use liver::Liver;
|
||||
pub use lungs::Lungs;
|
||||
pub use pancreas::Pancreas;
|
||||
pub use spinal_cord::SpinalCord;
|
||||
pub use spleen::Spleen;
|
||||
|
||||
pub use stomach::Stomach;
|
||||
|
||||
+19
-7
@@ -10,16 +10,28 @@ pub struct Pancreas {
|
||||
|
||||
impl Pancreas {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self { info: OrganInfo::new(id, OrganType::Pancreas), insulin: 1.0 }
|
||||
Self {
|
||||
info: OrganInfo::new(id, OrganType::Pancreas),
|
||||
insulin: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Organ for Pancreas {
|
||||
fn id(&self) -> &str { self.info.id() }
|
||||
fn organ_type(&self) -> OrganType { self.info.kind() }
|
||||
fn id(&self) -> &str {
|
||||
self.info.id()
|
||||
}
|
||||
fn organ_type(&self) -> OrganType {
|
||||
self.info.kind()
|
||||
}
|
||||
fn update(&mut self, _dt_seconds: f32) {}
|
||||
fn summary(&self) -> String { format!("Pancreas[id={}, insulin={:.2}]", self.id(), self.insulin) }
|
||||
fn as_any(&self) -> &dyn core::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any { self }
|
||||
fn summary(&self) -> String {
|
||||
format!("Pancreas[id={}, insulin={:.2}]", self.id(), self.insulin)
|
||||
}
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,17 +11,37 @@ pub struct SpinalCord {
|
||||
|
||||
impl SpinalCord {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self { info: OrganInfo::new(id, OrganType::SpinalCord), signal_integrity: 100, injury: false }
|
||||
Self {
|
||||
info: OrganInfo::new(id, OrganType::SpinalCord),
|
||||
signal_integrity: 100,
|
||||
injury: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Organ for SpinalCord {
|
||||
fn id(&self) -> &str { self.info.id() }
|
||||
fn organ_type(&self) -> OrganType { self.info.kind() }
|
||||
fn update(&mut self, _dt_seconds: f32) {
|
||||
if self.injury { self.signal_integrity = self.signal_integrity.saturating_sub(1); }
|
||||
fn id(&self) -> &str {
|
||||
self.info.id()
|
||||
}
|
||||
fn organ_type(&self) -> OrganType {
|
||||
self.info.kind()
|
||||
}
|
||||
fn update(&mut self, _dt_seconds: f32) {
|
||||
if self.injury {
|
||||
self.signal_integrity = self.signal_integrity.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
fn summary(&self) -> String {
|
||||
format!(
|
||||
"SpinalCord[id={}, integrity={}]",
|
||||
self.id(),
|
||||
self.signal_integrity
|
||||
)
|
||||
}
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
fn summary(&self) -> String { format!("SpinalCord[id={}, integrity={}]", self.id(), self.signal_integrity) }
|
||||
fn as_any(&self) -> &dyn core::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any { self }
|
||||
}
|
||||
|
||||
+19
-7
@@ -10,16 +10,28 @@ pub struct Spleen {
|
||||
|
||||
impl Spleen {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self { info: OrganInfo::new(id, OrganType::Spleen), immune_activity: 80 }
|
||||
Self {
|
||||
info: OrganInfo::new(id, OrganType::Spleen),
|
||||
immune_activity: 80,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Organ for Spleen {
|
||||
fn id(&self) -> &str { self.info.id() }
|
||||
fn organ_type(&self) -> OrganType { self.info.kind() }
|
||||
fn id(&self) -> &str {
|
||||
self.info.id()
|
||||
}
|
||||
fn organ_type(&self) -> OrganType {
|
||||
self.info.kind()
|
||||
}
|
||||
fn update(&mut self, _dt_seconds: f32) {}
|
||||
fn summary(&self) -> String { format!("Spleen[id={}, immune={}]", self.id(), self.immune_activity) }
|
||||
fn as_any(&self) -> &dyn core::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any { self }
|
||||
fn summary(&self) -> String {
|
||||
format!("Spleen[id={}, immune={}]", self.id(), self.immune_activity)
|
||||
}
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-7
@@ -10,16 +10,28 @@ pub struct Stomach {
|
||||
|
||||
impl Stomach {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self { info: OrganInfo::new(id, OrganType::Stomach), acid_level: 50 }
|
||||
Self {
|
||||
info: OrganInfo::new(id, OrganType::Stomach),
|
||||
acid_level: 50,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Organ for Stomach {
|
||||
fn id(&self) -> &str { self.info.id() }
|
||||
fn organ_type(&self) -> OrganType { self.info.kind() }
|
||||
fn id(&self) -> &str {
|
||||
self.info.id()
|
||||
}
|
||||
fn organ_type(&self) -> OrganType {
|
||||
self.info.kind()
|
||||
}
|
||||
fn update(&mut self, _dt_seconds: f32) {}
|
||||
fn summary(&self) -> String { format!("Stomach[id={}, acid={}]", self.id(), self.acid_level) }
|
||||
fn as_any(&self) -> &dyn core::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any { self }
|
||||
fn summary(&self) -> String {
|
||||
format!("Stomach[id={}, acid={}]", self.id(), self.acid_level)
|
||||
}
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+76
-20
@@ -9,7 +9,9 @@ use crate::types::{Blood, BloodPressure, OrganType};
|
||||
pub struct Patient {
|
||||
id: String,
|
||||
organs: Vec<Box<dyn Organ>>,
|
||||
/// Laboratory blood panel snapshot.
|
||||
pub blood: Blood,
|
||||
/// Non-invasive brachial blood pressure.
|
||||
pub blood_pressure: BloodPressure,
|
||||
}
|
||||
|
||||
@@ -18,7 +20,9 @@ impl Patient {
|
||||
pub fn new(id: impl Into<String>) -> crate::Result<Self> {
|
||||
let id = id.into();
|
||||
if !is_valid_id(&id) {
|
||||
return Err(MedicalError::Validation("patient id must be [A-Za-z0-9_-]+ and 1..64 chars"));
|
||||
return Err(MedicalError::Validation(
|
||||
"patient id must be [A-Za-z0-9_-]+ and 1..64 chars",
|
||||
));
|
||||
}
|
||||
Ok(Self {
|
||||
id,
|
||||
@@ -29,7 +33,9 @@ impl Patient {
|
||||
}
|
||||
|
||||
/// Patient identifier.
|
||||
pub fn id(&self) -> &str { &self.id }
|
||||
pub fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// Add an organ to the patient.
|
||||
pub fn add_organ(&mut self, organ: impl Organ + 'static) {
|
||||
@@ -38,7 +44,10 @@ impl Patient {
|
||||
|
||||
/// Find the first organ matching the given type.
|
||||
pub fn find_organ(&self, kind: OrganType) -> Option<&dyn Organ> {
|
||||
self.organs.iter().map(|b| b.as_ref()).find(|o| o.organ_type() == kind)
|
||||
self.organs
|
||||
.iter()
|
||||
.map(|b| b.as_ref())
|
||||
.find(|o| o.organ_type() == kind)
|
||||
}
|
||||
|
||||
/// Find a typed organ by downcasting.
|
||||
@@ -79,26 +88,67 @@ impl Patient {
|
||||
/// Attach a default organ by type.
|
||||
pub fn with_organ(mut self, kind: OrganType) -> Self {
|
||||
match kind {
|
||||
OrganType::Heart => { let id = format!("{}-heart", self.id); self.add_organ(Heart::new(id, 12)); }
|
||||
OrganType::Lungs => { let id = format!("{}-lungs", self.id); self.add_organ(Lungs::new(id)); }
|
||||
OrganType::Brain => { let id = format!("{}-brain", self.id); self.add_organ(crate::organs::Brain::new(id)); }
|
||||
OrganType::SpinalCord => { let id = format!("{}-sc", self.id); self.add_organ(crate::organs::SpinalCord::new(id)); }
|
||||
OrganType::Stomach => { let id = format!("{}-stomach", self.id); self.add_organ(crate::organs::Stomach::new(id)); }
|
||||
OrganType::Liver => { let id = format!("{}-liver", self.id); self.add_organ(crate::organs::Liver::new(id)); }
|
||||
OrganType::Gallbladder => { let id = format!("{}-gb", self.id); self.add_organ(crate::organs::Gallbladder::new(id)); }
|
||||
OrganType::Pancreas => { let id = format!("{}-pancreas", self.id); self.add_organ(crate::organs::Pancreas::new(id)); }
|
||||
OrganType::Intestines => { let id = format!("{}-intestines", self.id); self.add_organ(crate::organs::Intestines::new(id)); }
|
||||
OrganType::Esophagus => { let id = format!("{}-eso", self.id); self.add_organ(crate::organs::Esophagus::new(id)); }
|
||||
OrganType::Kidneys => { let id = format!("{}-kidneys", self.id); self.add_organ(crate::organs::Kidneys::new(id)); }
|
||||
OrganType::Bladder => { let id = format!("{}-bladder", self.id); self.add_organ(crate::organs::Bladder::new(id)); }
|
||||
OrganType::Spleen => { let id = format!("{}-spleen", self.id); self.add_organ(crate::organs::Spleen::new(id)); }
|
||||
OrganType::Heart => {
|
||||
let id = format!("{}-heart", self.id);
|
||||
self.add_organ(Heart::new(id, 12));
|
||||
}
|
||||
OrganType::Lungs => {
|
||||
let id = format!("{}-lungs", self.id);
|
||||
self.add_organ(Lungs::new(id));
|
||||
}
|
||||
OrganType::Brain => {
|
||||
let id = format!("{}-brain", self.id);
|
||||
self.add_organ(crate::organs::Brain::new(id));
|
||||
}
|
||||
OrganType::SpinalCord => {
|
||||
let id = format!("{}-sc", self.id);
|
||||
self.add_organ(crate::organs::SpinalCord::new(id));
|
||||
}
|
||||
OrganType::Stomach => {
|
||||
let id = format!("{}-stomach", self.id);
|
||||
self.add_organ(crate::organs::Stomach::new(id));
|
||||
}
|
||||
OrganType::Liver => {
|
||||
let id = format!("{}-liver", self.id);
|
||||
self.add_organ(crate::organs::Liver::new(id));
|
||||
}
|
||||
OrganType::Gallbladder => {
|
||||
let id = format!("{}-gb", self.id);
|
||||
self.add_organ(crate::organs::Gallbladder::new(id));
|
||||
}
|
||||
OrganType::Pancreas => {
|
||||
let id = format!("{}-pancreas", self.id);
|
||||
self.add_organ(crate::organs::Pancreas::new(id));
|
||||
}
|
||||
OrganType::Intestines => {
|
||||
let id = format!("{}-intestines", self.id);
|
||||
self.add_organ(crate::organs::Intestines::new(id));
|
||||
}
|
||||
OrganType::Esophagus => {
|
||||
let id = format!("{}-eso", self.id);
|
||||
self.add_organ(crate::organs::Esophagus::new(id));
|
||||
}
|
||||
OrganType::Kidneys => {
|
||||
let id = format!("{}-kidneys", self.id);
|
||||
self.add_organ(crate::organs::Kidneys::new(id));
|
||||
}
|
||||
OrganType::Bladder => {
|
||||
let id = format!("{}-bladder", self.id);
|
||||
self.add_organ(crate::organs::Bladder::new(id));
|
||||
}
|
||||
OrganType::Spleen => {
|
||||
let id = format!("{}-spleen", self.id);
|
||||
self.add_organ(crate::organs::Spleen::new(id));
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Advance simulation by `dt_seconds`.
|
||||
pub fn update(&mut self, dt_seconds: f32) {
|
||||
for organ in &mut self.organs { organ.update(dt_seconds); }
|
||||
for organ in &mut self.organs {
|
||||
organ.update(dt_seconds);
|
||||
}
|
||||
// Simple inter-organ signaling: low SpO2 nudges heart rate higher.
|
||||
if let Some(spo2) = self.find_organ_typed::<Lungs>().map(|l| l.spo2_pct) {
|
||||
if let Some(heart) = self.find_organ_typed_mut::<Heart>() {
|
||||
@@ -111,7 +161,10 @@ impl Patient {
|
||||
let produced_opt = self
|
||||
.find_organ_typed::<crate::organs::Kidneys>()
|
||||
.map(|kidneys| (kidneys.gfr * (dt_seconds / 60.0)).max(0.0) * 0.5); // ml
|
||||
if let (Some(produced), Some(bladder)) = (produced_opt, self.find_organ_typed_mut::<crate::organs::Bladder>()) {
|
||||
if let (Some(produced), Some(bladder)) = (
|
||||
produced_opt,
|
||||
self.find_organ_typed_mut::<crate::organs::Bladder>(),
|
||||
) {
|
||||
bladder.volume_ml += produced;
|
||||
}
|
||||
}
|
||||
@@ -147,8 +200,11 @@ impl Default for Patient {
|
||||
|
||||
fn is_valid_id(id: &str) -> bool {
|
||||
let len = id.len();
|
||||
if !(1..=64).contains(&len) { return false; }
|
||||
id.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
|
||||
if !(1..=64).contains(&len) {
|
||||
return false;
|
||||
}
|
||||
id.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+13
-1
@@ -6,18 +6,31 @@ use core::fmt;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
/// Known organ categories.
|
||||
pub enum OrganType {
|
||||
/// Heart
|
||||
Heart,
|
||||
/// Lungs
|
||||
Lungs,
|
||||
/// Brain
|
||||
Brain,
|
||||
/// Spinal cord
|
||||
SpinalCord,
|
||||
/// Stomach
|
||||
Stomach,
|
||||
/// Liver
|
||||
Liver,
|
||||
/// Gallbladder
|
||||
Gallbladder,
|
||||
/// Pancreas
|
||||
Pancreas,
|
||||
/// Intestines
|
||||
Intestines,
|
||||
/// Esophagus
|
||||
Esophagus,
|
||||
/// Kidneys
|
||||
Kidneys,
|
||||
/// Urinary bladder
|
||||
Bladder,
|
||||
/// Spleen
|
||||
Spleen,
|
||||
}
|
||||
|
||||
@@ -89,4 +102,3 @@ impl Blood {
|
||||
hgb_ok && hct_ok && spo2_ok && glucose_ok
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,4 +5,3 @@ fn bmi_works() {
|
||||
let cls = medicallib_rust::classify_bmi(bmi);
|
||||
assert_eq!(cls, medicallib_rust::BMIClassification::Normal);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#[cfg(feature = "ffi")]
|
||||
#[test]
|
||||
fn ffi_bmi_and_patient() {
|
||||
unsafe {
|
||||
let mut out: f32 = 0.0;
|
||||
let rc = medicallib_rust::ffi::medicallib_bmi(70.0, 1.75, &mut out as *mut f32);
|
||||
assert_eq!(rc, medicallib_rust::ffi::ML_OK);
|
||||
@@ -16,13 +15,11 @@ fn ffi_bmi_and_patient() {
|
||||
assert!(!s.is_null());
|
||||
medicallib_rust::ffi::ml_string_free(s);
|
||||
medicallib_rust::ffi::ml_patient_free(p);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
#[test]
|
||||
fn ffi_errors() {
|
||||
unsafe {
|
||||
// Null out pointer arguments should error gracefully
|
||||
let rc = medicallib_rust::ffi::medicallib_bmi(70.0, 1.75, std::ptr::null_mut());
|
||||
assert_eq!(rc, medicallib_rust::ffi::ML_EINVAL);
|
||||
@@ -31,5 +28,4 @@ fn ffi_errors() {
|
||||
// Summary with null patient returns null
|
||||
let s = medicallib_rust::ffi::ml_patient_summary(std::ptr::null());
|
||||
assert!(s.is_null());
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,8 +1,9 @@
|
||||
#[test]
|
||||
fn default_patient_heart() {
|
||||
let mut p = medicallib_rust::Patient::new("int-01").unwrap().initialize_default();
|
||||
let mut p = medicallib_rust::Patient::new("int-01")
|
||||
.unwrap()
|
||||
.initialize_default();
|
||||
p.update(0.1);
|
||||
let s = p.patient_summary();
|
||||
assert!(s.contains("Heart"));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user