use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt};
use crate::data::event::{AUCMethod, BLQRule, Route};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NCAOptions {
pub auc_method: AUCMethod,
pub blq_rule: BLQRule,
pub lambda_z: LambdaZOptions,
pub tau: Option<f64>,
pub auc_interval: Option<(f64, f64)>,
pub c0_methods: Vec<C0Method>,
pub max_auc_extrap_pct: f64,
pub concentration_threshold: Option<f64>,
pub route_override: Option<Route>,
pub outeq: usize,
pub dose_times: Option<Vec<f64>>,
}
impl Default for NCAOptions {
fn default() -> Self {
Self {
auc_method: AUCMethod::LinUpLogDown,
blq_rule: BLQRule::Exclude,
lambda_z: LambdaZOptions::default(),
tau: None,
auc_interval: None,
c0_methods: vec![C0Method::Observed, C0Method::LogSlope, C0Method::FirstConc],
max_auc_extrap_pct: 20.0,
concentration_threshold: None,
route_override: None,
outeq: 0,
dose_times: None,
}
}
}
impl NCAOptions {
pub fn bioequivalence() -> Self {
Self {
lambda_z: LambdaZOptions {
min_r_squared: 0.90,
min_points: 3,
..Default::default()
},
max_auc_extrap_pct: 20.0,
..Default::default()
}
}
pub fn sparse() -> Self {
Self {
lambda_z: LambdaZOptions {
min_r_squared: 0.80,
min_points: 3,
..Default::default()
},
max_auc_extrap_pct: 30.0,
..Default::default()
}
}
pub fn with_auc_method(mut self, method: AUCMethod) -> Self {
self.auc_method = method;
self
}
pub fn with_blq_rule(mut self, rule: BLQRule) -> Self {
self.blq_rule = rule;
self
}
pub fn with_tau(mut self, tau: f64) -> Self {
self.tau = Some(tau);
self
}
pub fn with_auc_interval(mut self, start: f64, end: f64) -> Self {
self.auc_interval = Some((start, end));
self
}
pub fn with_lambda_z(mut self, options: LambdaZOptions) -> Self {
self.lambda_z = options;
self
}
pub fn with_min_r_squared(mut self, min_r_squared: f64) -> Self {
self.lambda_z.min_r_squared = min_r_squared;
self
}
pub fn with_c0_methods(mut self, methods: Vec<C0Method>) -> Self {
self.c0_methods = methods;
self
}
pub fn with_concentration_threshold(mut self, threshold: f64) -> Self {
self.concentration_threshold = Some(threshold);
self
}
pub fn with_route(mut self, route: Route) -> Self {
self.route_override = Some(route);
self
}
pub fn with_outeq(mut self, outeq: usize) -> Self {
self.outeq = outeq;
self
}
pub fn with_dose_times(mut self, times: Vec<f64>) -> Self {
self.dose_times = Some(times);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LambdaZOptions {
pub method: LambdaZMethod,
pub min_points: usize,
pub max_points: Option<usize>,
pub min_r_squared: f64,
pub min_span_ratio: f64,
pub include_tmax: bool,
pub adj_r_squared_factor: f64,
pub exclude_indices: Vec<usize>,
}
impl Default for LambdaZOptions {
fn default() -> Self {
Self {
method: LambdaZMethod::AdjR2,
min_points: 3,
max_points: None,
min_r_squared: 0.90,
min_span_ratio: 2.0,
include_tmax: false,
adj_r_squared_factor: 0.0001, exclude_indices: Vec::new(),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum LambdaZMethod {
#[default]
AdjR2,
R2,
Manual(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum C0Method {
Observed,
LogSlope,
FirstConc,
Cmin,
Zero,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NCAResult {
pub subject_id: Option<String>,
pub occasion: Option<usize>,
pub dose_amount: Option<f64>,
pub route: Option<Route>,
pub infusion_duration: Option<f64>,
pub exposure: ExposureParams,
pub terminal: Option<TerminalParams>,
pub clearance: Option<ClearanceParams>,
pub route_params: Option<RouteParams>,
pub steady_state: Option<SteadyStateParams>,
pub multi_dose: Option<MultiDoseParams>,
pub quality: Quality,
}
impl NCAResult {
pub fn half_life(&self) -> Option<f64> {
self.terminal.as_ref().map(|t| t.half_life)
}
pub fn c0(&self) -> Option<f64> {
match &self.route_params {
Some(RouteParams::IVBolus(p)) => Some(p.c0),
_ => None,
}
}
pub fn vd(&self) -> Option<f64> {
match &self.route_params {
Some(RouteParams::IVBolus(p)) => Some(p.vd),
_ => None,
}
}
pub fn vss(&self) -> Option<f64> {
self.clearance.as_ref().and_then(|c| c.vss)
}
pub fn ceoi(&self) -> Option<f64> {
match &self.route_params {
Some(RouteParams::IVInfusion(p)) => p.ceoi,
_ => None,
}
}
pub fn mrt_iv(&self) -> Option<f64> {
match &self.route_params {
Some(RouteParams::IVInfusion(p)) => p.mrt_iv,
_ => None,
}
}
pub fn to_params(&self) -> HashMap<&'static str, f64> {
let mut p = HashMap::new();
p.insert("cmax", self.exposure.cmax);
p.insert("tmax", self.exposure.tmax);
p.insert("clast", self.exposure.clast);
p.insert("tlast", self.exposure.tlast);
if let Some(v) = self.exposure.tfirst {
p.insert("tfirst", v);
}
p.insert("auc_last", self.exposure.auc_last);
if let Some(v) = self.exposure.auc_inf_obs {
p.insert("auc_inf_obs", v);
}
if let Some(v) = self.exposure.auc_inf_pred {
p.insert("auc_inf_pred", v);
}
if let Some(v) = self.exposure.auc_pct_extrap_obs {
p.insert("auc_pct_extrap_obs", v);
}
if let Some(v) = self.exposure.auc_pct_extrap_pred {
p.insert("auc_pct_extrap_pred", v);
}
if let Some(v) = self.exposure.auc_partial {
p.insert("auc_partial", v);
}
if let Some(v) = self.exposure.aumc_last {
p.insert("aumc_last", v);
}
if let Some(v) = self.exposure.aumc_inf {
p.insert("aumc_inf", v);
}
if let Some(v) = self.exposure.tlag {
p.insert("tlag", v);
}
if let Some(v) = self.exposure.cmax_dn {
p.insert("cmax_dn", v);
}
if let Some(v) = self.exposure.auc_last_dn {
p.insert("auc_last_dn", v);
}
if let Some(v) = self.exposure.auc_inf_dn {
p.insert("auc_inf_dn", v);
}
if let Some(v) = self.exposure.time_above_mic {
p.insert("time_above_mic", v);
}
if let Some(v) = self.dose_amount {
p.insert("dose", v);
}
if let Some(ref t) = self.terminal {
p.insert("lambda_z", t.lambda_z);
p.insert("half_life", t.half_life);
if let Some(mrt) = t.mrt {
p.insert("mrt", mrt);
}
if let Some(eff_hl) = t.effective_half_life {
p.insert("effective_half_life", eff_hl);
}
if let Some(kel) = t.kel {
p.insert("kel", kel);
}
if let Some(ref reg) = t.regression {
if reg.corrxy.is_finite() {
p.insert("lambda_z_corrxy", reg.corrxy);
}
}
}
if let Some(ref c) = self.clearance {
p.insert("cl_f", c.cl_f);
p.insert("vz_f", c.vz_f);
if let Some(vss) = c.vss {
p.insert("vss", vss);
}
}
if let Some(ref rp) = self.route_params {
match rp {
RouteParams::IVBolus(ref b) => {
p.insert("c0", b.c0);
p.insert("vd", b.vd);
}
RouteParams::IVInfusion(ref inf) => {
p.insert("infusion_duration", inf.infusion_duration);
if let Some(mrt_iv) = inf.mrt_iv {
p.insert("mrt_iv", mrt_iv);
}
if let Some(ceoi) = inf.ceoi {
p.insert("ceoi", ceoi);
}
}
RouteParams::Extravascular => {}
}
}
if let Some(ref ss) = self.steady_state {
p.insert("tau", ss.tau);
p.insert("auc_tau", ss.auc_tau);
p.insert("cmin", ss.cmin);
p.insert("cmax_ss", ss.cmax_ss);
p.insert("cavg", ss.cavg);
p.insert("fluctuation", ss.fluctuation);
p.insert("swing", ss.swing);
p.insert("peak_trough_ratio", ss.peak_trough_ratio);
if let Some(acc) = ss.accumulation {
p.insert("accumulation", acc);
}
}
p
}
pub fn to_row(&self) -> Vec<(&'static str, Option<f64>)> {
let mut row = Vec::with_capacity(40);
row.push(("cmax", Some(self.exposure.cmax)));
row.push(("tmax", Some(self.exposure.tmax)));
row.push(("clast", Some(self.exposure.clast)));
row.push(("tlast", Some(self.exposure.tlast)));
row.push(("tfirst", self.exposure.tfirst));
row.push(("auc_last", Some(self.exposure.auc_last)));
row.push(("auc_inf_obs", self.exposure.auc_inf_obs));
row.push(("auc_inf_pred", self.exposure.auc_inf_pred));
row.push(("auc_pct_extrap_obs", self.exposure.auc_pct_extrap_obs));
row.push(("auc_pct_extrap_pred", self.exposure.auc_pct_extrap_pred));
row.push(("auc_partial", self.exposure.auc_partial));
row.push(("aumc_last", self.exposure.aumc_last));
row.push(("aumc_inf", self.exposure.aumc_inf));
row.push(("tlag", self.exposure.tlag));
if let Some(ref t) = self.terminal {
row.push(("lambda_z", Some(t.lambda_z)));
row.push(("half_life", Some(t.half_life)));
row.push(("mrt", t.mrt));
row.push(("effective_half_life", t.effective_half_life));
row.push(("kel", t.kel));
} else {
row.push(("lambda_z", None));
row.push(("half_life", None));
row.push(("mrt", None));
row.push(("effective_half_life", None));
row.push(("kel", None));
}
if let Some(ref c) = self.clearance {
row.push(("cl_f", Some(c.cl_f)));
row.push(("vz_f", Some(c.vz_f)));
row.push(("vss", c.vss));
} else {
row.push(("cl_f", None));
row.push(("vz_f", None));
row.push(("vss", None));
}
match self.route_params.as_ref() {
Some(RouteParams::IVBolus(ref b)) => {
row.push(("c0", Some(b.c0)));
row.push(("vd", Some(b.vd)));
row.push(("infusion_duration", None));
row.push(("ceoi", None));
}
Some(RouteParams::IVInfusion(ref inf)) => {
row.push(("c0", None));
row.push(("vd", None));
row.push(("infusion_duration", Some(inf.infusion_duration)));
row.push(("ceoi", inf.ceoi));
}
Some(RouteParams::Extravascular) | None => {
row.push(("c0", None));
row.push(("vd", None));
row.push(("infusion_duration", None));
row.push(("ceoi", None));
}
}
if let Some(ref ss) = self.steady_state {
row.push(("tau", Some(ss.tau)));
row.push(("auc_tau", Some(ss.auc_tau)));
row.push(("cmin", Some(ss.cmin)));
row.push(("cmax_ss", Some(ss.cmax_ss)));
row.push(("cavg", Some(ss.cavg)));
row.push(("fluctuation", Some(ss.fluctuation)));
row.push(("swing", Some(ss.swing)));
row.push(("peak_trough_ratio", Some(ss.peak_trough_ratio)));
row.push(("accumulation", ss.accumulation));
} else {
row.push(("tau", None));
row.push(("auc_tau", None));
row.push(("cmin", None));
row.push(("cmax_ss", None));
row.push(("cavg", None));
row.push(("fluctuation", None));
row.push(("swing", None));
row.push(("peak_trough_ratio", None));
row.push(("accumulation", None));
}
row.push(("cmax_dn", self.exposure.cmax_dn));
row.push(("auc_last_dn", self.exposure.auc_last_dn));
row.push(("auc_inf_dn", self.exposure.auc_inf_dn));
row.push(("time_above_mic", self.exposure.time_above_mic));
row.push(("dose", self.dose_amount));
row
}
}
impl fmt::Display for NCAResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "╔══════════════════════════════════════╗")?;
writeln!(f, "║ NCA Results ║")?;
writeln!(f, "╠══════════════════════════════════════╣")?;
if let Some(ref id) = self.subject_id {
writeln!(f, "║ Subject: {:<27} ║", id)?;
}
if let Some(occ) = self.occasion {
writeln!(f, "║ Occasion: {:<26} ║", occ)?;
}
if let Some(amount) = self.dose_amount {
let route_str = self
.route
.map(|r| format!("{:?}", r))
.unwrap_or_else(|| "Unknown".to_string());
writeln!(
f,
"║ Dose: {:<30} ║",
format!("{:.2} ({})", amount, route_str)
)?;
}
writeln!(f, "╠══════════════════════════════════════╣")?;
writeln!(f, "║ EXPOSURE ║")?;
writeln!(
f,
"║ Cmax: {:>10.4} at Tmax={:<6.2} ║",
self.exposure.cmax, self.exposure.tmax
)?;
writeln!(
f,
"║ AUClast: {:>10.4} ║",
self.exposure.auc_last
)?;
if let Some(v) = self.exposure.auc_inf_obs {
writeln!(f, "║ AUCinf(obs): {:>10.4} ║", v)?;
}
if let Some(v) = self.exposure.auc_inf_pred {
writeln!(f, "║ AUCinf(pred): {:>10.4} ║", v)?;
}
writeln!(
f,
"║ Clast: {:>10.4} at Tlast={:<5.2}║",
self.exposure.clast, self.exposure.tlast
)?;
if let Some(ref t) = self.terminal {
writeln!(f, "╠══════════════════════════════════════╣")?;
writeln!(f, "║ TERMINAL ║")?;
writeln!(f, "║ λz: {:>10.5} ║", t.lambda_z)?;
writeln!(f, "║ t½: {:>10.2} ║", t.half_life)?;
if let Some(eff_hl) = t.effective_half_life {
writeln!(f, "║ t½eff: {:>10.2} ║", eff_hl)?;
}
if let Some(kel) = t.kel {
writeln!(f, "║ Kel: {:>10.5} ║", kel)?;
}
if let Some(ref reg) = t.regression {
writeln!(f, "║ R²: {:>10.4} ║", reg.r_squared)?;
if reg.corrxy.is_finite() {
writeln!(f, "║ corrxy: {:>10.4} ║", reg.corrxy)?;
}
}
}
if let Some(ref c) = self.clearance {
writeln!(f, "╠══════════════════════════════════════╣")?;
writeln!(f, "║ CLEARANCE ║")?;
writeln!(f, "║ CL/F: {:>10.4} ║", c.cl_f)?;
writeln!(f, "║ Vz/F: {:>10.4} ║", c.vz_f)?;
}
if let Some(ref rp) = self.route_params {
match rp {
RouteParams::IVBolus(ref b) => {
writeln!(f, "╠══════════════════════════════════════╣")?;
writeln!(f, "║ IV BOLUS ║")?;
writeln!(f, "║ C0: {:>10.4} ║", b.c0)?;
writeln!(f, "║ Vd: {:>10.4} ║", b.vd)?;
}
RouteParams::IVInfusion(ref inf) => {
writeln!(f, "╠══════════════════════════════════════╣")?;
writeln!(f, "║ IV INFUSION ║")?;
writeln!(
f,
"║ Dur: {:>10.4} ║",
inf.infusion_duration
)?;
}
RouteParams::Extravascular => {}
}
}
if !self.quality.warnings.is_empty() {
writeln!(f, "╠══════════════════════════════════════╣")?;
writeln!(f, "║ WARNINGS ║")?;
for w in &self.quality.warnings {
writeln!(f, "║ • {:<32} ║", format!("{}", w))?;
}
}
writeln!(f, "╚══════════════════════════════════════╝")?;
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExposureParams {
pub cmax: f64,
pub tmax: f64,
pub clast: f64,
pub tlast: f64,
pub tfirst: Option<f64>,
pub auc_last: f64,
pub auc_inf_obs: Option<f64>,
pub auc_inf_pred: Option<f64>,
pub auc_pct_extrap_obs: Option<f64>,
pub auc_pct_extrap_pred: Option<f64>,
pub auc_partial: Option<f64>,
pub aumc_last: Option<f64>,
pub aumc_inf: Option<f64>,
pub tlag: Option<f64>,
pub cmax_dn: Option<f64>,
pub auc_last_dn: Option<f64>,
pub auc_inf_dn: Option<f64>,
pub time_above_mic: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TerminalParams {
pub lambda_z: f64,
pub half_life: f64,
pub mrt: Option<f64>,
pub effective_half_life: Option<f64>,
pub kel: Option<f64>,
pub regression: Option<RegressionStats>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegressionStats {
pub r_squared: f64,
pub adj_r_squared: f64,
pub corrxy: f64,
pub n_points: usize,
pub time_first: f64,
pub time_last: f64,
pub span_ratio: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClearanceParams {
pub cl_f: f64,
pub vz_f: f64,
pub vss: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IVBolusParams {
pub c0: f64,
pub vd: f64,
pub c0_method: Option<C0Method>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IVInfusionParams {
pub infusion_duration: f64,
pub mrt_iv: Option<f64>,
pub ceoi: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RouteParams {
IVBolus(IVBolusParams),
IVInfusion(IVInfusionParams),
Extravascular,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SteadyStateParams {
pub tau: f64,
pub auc_tau: f64,
pub cmin: f64,
pub cmax_ss: f64,
pub cavg: f64,
pub fluctuation: f64,
pub swing: f64,
pub peak_trough_ratio: f64,
pub accumulation: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultiDoseParams {
pub dose_times: Vec<f64>,
pub auc_intervals: Vec<f64>,
pub cmax_intervals: Vec<f64>,
pub tmax_intervals: Vec<f64>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Quality {
pub warnings: Vec<Warning>,
}
impl Quality {
pub fn errors(&self) -> Vec<&Warning> {
self.warnings
.iter()
.filter(|w| w.severity() == Severity::Error)
.collect()
}
pub fn warnings_only(&self) -> Vec<&Warning> {
self.warnings
.iter()
.filter(|w| w.severity() == Severity::Warning)
.collect()
}
pub fn info(&self) -> Vec<&Warning> {
self.warnings
.iter()
.filter(|w| w.severity() == Severity::Info)
.collect()
}
pub fn has_errors(&self) -> bool {
self.warnings
.iter()
.any(|w| w.severity() == Severity::Error)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Severity {
Info,
Warning,
Error,
}
impl fmt::Display for Severity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Severity::Info => write!(f, "INFO"),
Severity::Warning => write!(f, "WARN"),
Severity::Error => write!(f, "ERROR"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Warning {
HighExtrapolation {
pct: f64,
threshold: f64,
},
PoorFit {
r_squared: f64,
threshold: f64,
},
LambdaZNotEstimable,
ShortTerminalPhase {
span_ratio: f64,
threshold: f64,
},
LowCmax,
MixedRoutes {
routes: Vec<Route>,
},
}
impl Warning {
pub fn severity(&self) -> Severity {
match self {
Warning::LambdaZNotEstimable | Warning::LowCmax => Severity::Error,
Warning::HighExtrapolation { .. } | Warning::PoorFit { .. } => Severity::Warning,
Warning::ShortTerminalPhase { .. } | Warning::MixedRoutes { .. } => Severity::Info,
}
}
}
impl fmt::Display for Warning {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Warning::HighExtrapolation { pct, threshold } => {
write!(
f,
"AUC extrapolation {:.1}% exceeds {:.1}% threshold",
pct, threshold
)
}
Warning::PoorFit {
r_squared,
threshold,
} => {
write!(f, "λz R²={:.4} below minimum {:.4}", r_squared, threshold)
}
Warning::LambdaZNotEstimable => write!(f, "λz could not be estimated"),
Warning::ShortTerminalPhase {
span_ratio,
threshold,
} => {
write!(
f,
"Terminal phase span ratio {:.2} below minimum {:.2}",
span_ratio, threshold
)
}
Warning::LowCmax => write!(f, "Cmax ≤ 0"),
Warning::MixedRoutes { routes } => {
write!(f, "Mixed routes detected: {:?}", routes)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nca_options_default() {
let opts = NCAOptions::default();
assert_eq!(opts.auc_method, AUCMethod::LinUpLogDown);
assert_eq!(opts.blq_rule, BLQRule::Exclude);
assert!(opts.tau.is_none());
assert_eq!(opts.max_auc_extrap_pct, 20.0);
}
#[test]
fn test_nca_options_builder() {
let opts = NCAOptions::default()
.with_auc_method(AUCMethod::Linear)
.with_blq_rule(BLQRule::LoqOver2)
.with_tau(24.0)
.with_min_r_squared(0.95);
assert_eq!(opts.auc_method, AUCMethod::Linear);
assert_eq!(opts.blq_rule, BLQRule::LoqOver2);
assert_eq!(opts.tau, Some(24.0));
assert_eq!(opts.lambda_z.min_r_squared, 0.95);
}
#[test]
fn test_nca_options_presets() {
let be = NCAOptions::bioequivalence();
assert_eq!(be.lambda_z.min_r_squared, 0.90);
assert_eq!(be.max_auc_extrap_pct, 20.0);
let sparse = NCAOptions::sparse();
assert_eq!(sparse.lambda_z.min_r_squared, 0.80);
assert_eq!(sparse.max_auc_extrap_pct, 30.0);
}
fn make_result_with(
route_params: Option<RouteParams>,
clearance: Option<ClearanceParams>,
) -> NCAResult {
NCAResult {
subject_id: None,
occasion: None,
dose_amount: Some(100.0),
route: Some(crate::data::Route::Extravascular),
infusion_duration: None,
exposure: ExposureParams {
cmax: 10.0,
tmax: 1.0,
clast: 1.0,
tlast: 8.0,
tfirst: None,
auc_last: 50.0,
auc_inf_obs: None,
auc_inf_pred: None,
auc_pct_extrap_obs: None,
auc_pct_extrap_pred: None,
auc_partial: None,
aumc_last: None,
aumc_inf: None,
tlag: None,
cmax_dn: None,
auc_last_dn: None,
auc_inf_dn: None,
time_above_mic: None,
},
terminal: None,
clearance,
route_params,
steady_state: None,
multi_dose: None,
quality: Quality::default(),
}
}
#[test]
fn test_accessor_c0_iv_bolus() {
let result = make_result_with(
Some(RouteParams::IVBolus(IVBolusParams {
c0: 25.0,
vd: 20.0,
c0_method: None,
})),
None,
);
assert_eq!(result.c0(), Some(25.0));
assert_eq!(result.vd(), Some(20.0));
}
#[test]
fn test_accessor_c0_not_bolus() {
let result = make_result_with(Some(RouteParams::Extravascular), None);
assert_eq!(result.c0(), None);
assert_eq!(result.vd(), None);
}
#[test]
fn test_accessor_vss() {
let result = make_result_with(
None,
Some(ClearanceParams {
cl_f: 5.0,
vz_f: 10.0,
vss: Some(15.0),
}),
);
assert_eq!(result.vss(), Some(15.0));
}
#[test]
fn test_accessor_vss_none() {
let result = make_result_with(None, None);
assert_eq!(result.vss(), None);
}
#[test]
fn test_accessor_ceoi_infusion() {
let result = make_result_with(
Some(RouteParams::IVInfusion(IVInfusionParams {
infusion_duration: 1.0,
mrt_iv: Some(4.0),
ceoi: Some(30.0),
})),
None,
);
assert_eq!(result.ceoi(), Some(30.0));
assert_eq!(result.mrt_iv(), Some(4.0));
}
#[test]
fn test_accessor_ceoi_not_infusion() {
let result = make_result_with(Some(RouteParams::Extravascular), None);
assert_eq!(result.ceoi(), None);
assert_eq!(result.mrt_iv(), None);
}
}