use super::observation::ObservationProfile;
use crate::data::event::{AUCMethod, BLQRule};
use crate::data::observation_error::ObservationError;
use crate::nca::analyze::{analyze, AnalysisContext};
use crate::nca::calc::tlag_from_raw;
use crate::nca::error::NCAError;
use crate::nca::types::{NCAOptions, NCAResult, Warning};
use crate::{Data, Occasion, Subject};
use rayon::prelude::*;
#[derive(Debug, Clone)]
pub struct SubjectNCAResult {
pub subject_id: String,
pub occasions: Vec<Result<NCAResult, NCAError>>,
}
impl SubjectNCAResult {
pub fn successes(&self) -> Vec<&NCAResult> {
self.occasions
.iter()
.filter_map(|r| r.as_ref().ok())
.collect()
}
pub fn errors(&self) -> Vec<&NCAError> {
self.occasions
.iter()
.filter_map(|r| r.as_ref().err())
.collect()
}
}
pub trait NCA {
fn nca(&self, options: &NCAOptions) -> Result<NCAResult, NCAError>;
fn nca_all(&self, options: &NCAOptions) -> Vec<Result<NCAResult, NCAError>>;
}
pub trait NCAPopulation {
fn nca_grouped(&self, options: &NCAOptions) -> Vec<SubjectNCAResult>;
}
use crate::data::Route;
impl Occasion {
pub fn nca_with_dose(
&self,
dose_amount: f64,
route: Route,
infusion_duration: Option<f64>,
options: &NCAOptions,
) -> Result<NCAResult, NCAError> {
let profile = ObservationProfile::from_occasion(self, options.outeq, &options.blq_rule)?;
let (times, concs, censoring) = self.get_observations(options.outeq);
let raw_tlag = tlag_from_raw(×, &concs, &censoring);
analyze(&AnalysisContext {
profile: &profile,
dose_amount: Some(dose_amount),
route,
infusion_duration,
options,
raw_tlag,
subject_id: None,
occasion: Some(self.index()),
})
}
}
impl NCA for Occasion {
fn nca(&self, options: &NCAOptions) -> Result<NCAResult, NCAError> {
nca_occasion(self, options, None)
}
fn nca_all(&self, options: &NCAOptions) -> Vec<Result<NCAResult, NCAError>> {
vec![self.nca(options)]
}
}
impl Subject {
pub fn nca_with_dose(
&self,
dose_amount: f64,
route: Route,
infusion_duration: Option<f64>,
options: &NCAOptions,
) -> Result<NCAResult, NCAError> {
let occasion = self
.occasions()
.iter()
.next()
.ok_or(NCAError::InvalidParameter {
param: "occasion".to_string(),
value: "none found".to_string(),
})?;
occasion.nca_with_dose(dose_amount, route, infusion_duration, options)
}
}
impl NCA for Subject {
fn nca(&self, options: &NCAOptions) -> Result<NCAResult, NCAError> {
self.occasions()
.first()
.map(|occ| nca_occasion(occ, options, Some(self.id())))
.unwrap_or(Err(NCAError::InvalidParameter {
param: "occasion".to_string(),
value: "none found".to_string(),
}))
}
fn nca_all(&self, options: &NCAOptions) -> Vec<Result<NCAResult, NCAError>> {
self.occasions()
.par_iter()
.map(|occasion| nca_occasion(occasion, options, Some(self.id())))
.collect()
}
}
impl NCA for Data {
fn nca(&self, options: &NCAOptions) -> Result<NCAResult, NCAError> {
self.subjects()
.first()
.map(|s| s.nca(options))
.unwrap_or(Err(NCAError::InvalidParameter {
param: "subject".to_string(),
value: "none found".to_string(),
}))
}
fn nca_all(&self, options: &NCAOptions) -> Vec<Result<NCAResult, NCAError>> {
self.subjects()
.par_iter()
.flat_map(|subject| subject.nca_all(options))
.collect()
}
}
impl NCAPopulation for Data {
fn nca_grouped(&self, options: &NCAOptions) -> Vec<SubjectNCAResult> {
self.subjects()
.par_iter()
.map(|subject| {
let occasions = subject
.occasions()
.par_iter()
.map(|occasion| nca_occasion(occasion, options, Some(subject.id())))
.collect();
SubjectNCAResult {
subject_id: subject.id().to_string(),
occasions,
}
})
.collect()
}
}
fn nca_occasion(
occasion: &Occasion,
options: &NCAOptions,
subject_id: Option<&str>,
) -> Result<NCAResult, NCAError> {
let outeq = options.outeq;
let profile = ObservationProfile::from_occasion(occasion, outeq, &options.blq_rule)?;
let (times, concs, censoring) = occasion.get_observations(outeq);
let raw_tlag = tlag_from_raw(×, &concs, &censoring);
let dose_amount = {
let d = occasion.total_dose();
if d > 0.0 {
Some(d)
} else {
None
}
};
let route = options.route_override.unwrap_or_else(|| occasion.route());
let infusion_duration = occasion.infusion_duration();
let mut result = analyze(&AnalysisContext {
profile: &profile,
dose_amount,
route,
infusion_duration,
options,
raw_tlag,
subject_id,
occasion: Some(occasion.index()),
})?;
let routes = occasion.routes();
if routes.len() > 1 && options.route_override.is_none() {
result
.quality
.warnings
.push(Warning::MixedRoutes { routes });
}
Ok(result)
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum MetricsError {
#[error(transparent)]
Observation(#[from] ObservationError),
#[error("Output equation {outeq} not found in subject{}", subject_id.as_ref().map(|id| format!(" '{}'", id)).unwrap_or_default())]
OutputEquationNotFound {
outeq: usize,
subject_id: Option<String>,
},
}
pub trait ObservationMetrics {
fn auc_blq(
&self,
outeq: usize,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Vec<Result<f64, MetricsError>>;
fn auc_interval_blq(
&self,
outeq: usize,
start: f64,
end: f64,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Vec<Result<f64, MetricsError>>;
fn cmax_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>>;
fn tmax_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>>;
fn clast_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>>;
fn tlast_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>>;
fn aumc_blq(
&self,
outeq: usize,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Vec<Result<f64, MetricsError>>;
fn auc(&self, outeq: usize, method: &AUCMethod) -> Vec<Result<f64, MetricsError>> {
self.auc_blq(outeq, method, &BLQRule::Exclude)
}
fn auc_interval(
&self,
outeq: usize,
start: f64,
end: f64,
method: &AUCMethod,
) -> Vec<Result<f64, MetricsError>> {
self.auc_interval_blq(outeq, start, end, method, &BLQRule::Exclude)
}
fn cmax(&self, outeq: usize) -> Vec<Result<f64, MetricsError>> {
self.cmax_blq(outeq, &BLQRule::Exclude)
}
fn tmax(&self, outeq: usize) -> Vec<Result<f64, MetricsError>> {
self.tmax_blq(outeq, &BLQRule::Exclude)
}
fn clast(&self, outeq: usize) -> Vec<Result<f64, MetricsError>> {
self.clast_blq(outeq, &BLQRule::Exclude)
}
fn tlast(&self, outeq: usize) -> Vec<Result<f64, MetricsError>> {
self.tlast_blq(outeq, &BLQRule::Exclude)
}
fn aumc(&self, outeq: usize, method: &AUCMethod) -> Vec<Result<f64, MetricsError>> {
self.aumc_blq(outeq, method, &BLQRule::Exclude)
}
fn auc_first(&self, outeq: usize, method: &AUCMethod) -> Result<f64, MetricsError> {
self.auc(outeq, method)
.into_iter()
.next()
.unwrap_or(Err(MetricsError::Observation(
ObservationError::InsufficientData { n: 0, required: 2 },
)))
}
fn cmax_first(&self, outeq: usize) -> Result<f64, MetricsError> {
self.cmax(outeq)
.into_iter()
.next()
.unwrap_or(Err(MetricsError::Observation(
ObservationError::InsufficientData { n: 0, required: 2 },
)))
}
fn tmax_first(&self, outeq: usize) -> Result<f64, MetricsError> {
self.tmax(outeq)
.into_iter()
.next()
.unwrap_or(Err(MetricsError::Observation(
ObservationError::InsufficientData { n: 0, required: 2 },
)))
}
fn clast_first(&self, outeq: usize) -> Result<f64, MetricsError> {
self.clast(outeq)
.into_iter()
.next()
.unwrap_or(Err(MetricsError::Observation(
ObservationError::InsufficientData { n: 0, required: 2 },
)))
}
fn tlast_first(&self, outeq: usize) -> Result<f64, MetricsError> {
self.tlast(outeq)
.into_iter()
.next()
.unwrap_or(Err(MetricsError::Observation(
ObservationError::InsufficientData { n: 0, required: 2 },
)))
}
fn aumc_first(&self, outeq: usize, method: &AUCMethod) -> Result<f64, MetricsError> {
self.aumc(outeq, method)
.into_iter()
.next()
.unwrap_or(Err(MetricsError::Observation(
ObservationError::InsufficientData { n: 0, required: 2 },
)))
}
fn auc_interval_first(
&self,
outeq: usize,
start: f64,
end: f64,
method: &AUCMethod,
) -> Result<f64, MetricsError> {
self.auc_interval(outeq, start, end, method)
.into_iter()
.next()
.unwrap_or(Err(MetricsError::Observation(
ObservationError::InsufficientData { n: 0, required: 2 },
)))
}
fn auc_blq_first(
&self,
outeq: usize,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Result<f64, MetricsError> {
self.auc_blq(outeq, method, blq_rule)
.into_iter()
.next()
.unwrap_or(Err(MetricsError::Observation(
ObservationError::InsufficientData { n: 0, required: 2 },
)))
}
fn cmax_blq_first(&self, outeq: usize, blq_rule: &BLQRule) -> Result<f64, MetricsError> {
self.cmax_blq(outeq, blq_rule)
.into_iter()
.next()
.unwrap_or(Err(MetricsError::Observation(
ObservationError::InsufficientData { n: 0, required: 2 },
)))
}
fn auc_interval_blq_first(
&self,
outeq: usize,
start: f64,
end: f64,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Result<f64, MetricsError> {
self.auc_interval_blq(outeq, start, end, method, blq_rule)
.into_iter()
.next()
.unwrap_or(Err(MetricsError::Observation(
ObservationError::InsufficientData { n: 0, required: 2 },
)))
}
}
impl ObservationMetrics for Occasion {
fn auc_blq(
&self,
outeq: usize,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Vec<Result<f64, MetricsError>> {
vec![auc_occasion(self, outeq, method, blq_rule)]
}
fn auc_interval_blq(
&self,
outeq: usize,
start: f64,
end: f64,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Vec<Result<f64, MetricsError>> {
vec![auc_interval_occasion(
self, outeq, start, end, method, blq_rule,
)]
}
fn cmax_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>> {
vec![cmax_occasion(self, outeq, blq_rule)]
}
fn tmax_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>> {
vec![tmax_occasion(self, outeq, blq_rule)]
}
fn clast_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>> {
vec![clast_occasion(self, outeq, blq_rule)]
}
fn tlast_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>> {
vec![tlast_occasion(self, outeq, blq_rule)]
}
fn aumc_blq(
&self,
outeq: usize,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Vec<Result<f64, MetricsError>> {
vec![aumc_occasion(self, outeq, method, blq_rule)]
}
}
impl ObservationMetrics for Subject {
fn auc_blq(
&self,
outeq: usize,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Vec<Result<f64, MetricsError>> {
self.occasions()
.par_iter()
.map(|o| auc_occasion(o, outeq, method, blq_rule))
.collect()
}
fn auc_interval_blq(
&self,
outeq: usize,
start: f64,
end: f64,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Vec<Result<f64, MetricsError>> {
self.occasions()
.par_iter()
.map(|o| auc_interval_occasion(o, outeq, start, end, method, blq_rule))
.collect()
}
fn cmax_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>> {
self.occasions()
.par_iter()
.map(|o| cmax_occasion(o, outeq, blq_rule))
.collect()
}
fn tmax_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>> {
self.occasions()
.par_iter()
.map(|o| tmax_occasion(o, outeq, blq_rule))
.collect()
}
fn clast_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>> {
self.occasions()
.par_iter()
.map(|o| clast_occasion(o, outeq, blq_rule))
.collect()
}
fn tlast_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>> {
self.occasions()
.par_iter()
.map(|o| tlast_occasion(o, outeq, blq_rule))
.collect()
}
fn aumc_blq(
&self,
outeq: usize,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Vec<Result<f64, MetricsError>> {
self.occasions()
.par_iter()
.map(|o| aumc_occasion(o, outeq, method, blq_rule))
.collect()
}
}
impl ObservationMetrics for Data {
fn auc_blq(
&self,
outeq: usize,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Vec<Result<f64, MetricsError>> {
self.subjects()
.par_iter()
.flat_map(|s| s.auc_blq(outeq, method, blq_rule))
.collect()
}
fn auc_interval_blq(
&self,
outeq: usize,
start: f64,
end: f64,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Vec<Result<f64, MetricsError>> {
self.subjects()
.par_iter()
.flat_map(|s| s.auc_interval_blq(outeq, start, end, method, blq_rule))
.collect()
}
fn cmax_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>> {
self.subjects()
.par_iter()
.flat_map(|s| s.cmax_blq(outeq, blq_rule))
.collect()
}
fn tmax_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>> {
self.subjects()
.par_iter()
.flat_map(|s| s.tmax_blq(outeq, blq_rule))
.collect()
}
fn clast_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>> {
self.subjects()
.par_iter()
.flat_map(|s| s.clast_blq(outeq, blq_rule))
.collect()
}
fn tlast_blq(&self, outeq: usize, blq_rule: &BLQRule) -> Vec<Result<f64, MetricsError>> {
self.subjects()
.par_iter()
.flat_map(|s| s.tlast_blq(outeq, blq_rule))
.collect()
}
fn aumc_blq(
&self,
outeq: usize,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Vec<Result<f64, MetricsError>> {
self.subjects()
.par_iter()
.flat_map(|s| s.aumc_blq(outeq, method, blq_rule))
.collect()
}
}
fn auc_occasion(
occasion: &Occasion,
outeq: usize,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Result<f64, MetricsError> {
let profile = ObservationProfile::from_occasion(occasion, outeq, blq_rule)?;
Ok(profile.auc_last(method)?)
}
fn auc_interval_occasion(
occasion: &Occasion,
outeq: usize,
start: f64,
end: f64,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Result<f64, MetricsError> {
let profile = ObservationProfile::from_occasion(occasion, outeq, blq_rule)?;
Ok(profile.auc_interval(start, end, method)?)
}
fn cmax_occasion(
occasion: &Occasion,
outeq: usize,
blq_rule: &BLQRule,
) -> Result<f64, MetricsError> {
let profile = ObservationProfile::from_occasion(occasion, outeq, blq_rule)?;
Ok(profile.cmax())
}
fn tmax_occasion(
occasion: &Occasion,
outeq: usize,
blq_rule: &BLQRule,
) -> Result<f64, MetricsError> {
let profile = ObservationProfile::from_occasion(occasion, outeq, blq_rule)?;
Ok(profile.tmax())
}
fn clast_occasion(
occasion: &Occasion,
outeq: usize,
blq_rule: &BLQRule,
) -> Result<f64, MetricsError> {
let profile = ObservationProfile::from_occasion(occasion, outeq, blq_rule)?;
Ok(profile.clast())
}
fn tlast_occasion(
occasion: &Occasion,
outeq: usize,
blq_rule: &BLQRule,
) -> Result<f64, MetricsError> {
let profile = ObservationProfile::from_occasion(occasion, outeq, blq_rule)?;
Ok(profile.tlast())
}
fn aumc_occasion(
occasion: &Occasion,
outeq: usize,
method: &AUCMethod,
blq_rule: &BLQRule,
) -> Result<f64, MetricsError> {
let profile = ObservationProfile::from_occasion(occasion, outeq, blq_rule)?;
Ok(profile.aumc_last(method)?)
}