use crate::mt_config::MortTableConfig;
use bon::Builder;
use garde::Validate;
#[derive(Debug, Clone, Validate, Builder)]
#[garde(allow_unvalidated)]
pub struct SurvivalFunctionParams {
#[garde(dive)]
pub mt: MortTableConfig,
#[garde(range(max = 150.0))]
pub x: f64,
#[garde(range(max = 150.0))]
pub t: f64,
#[garde(range(max = 150.0))]
pub k: f64,
#[garde(range(max = 150))]
pub entry_age: Option<u32>,
}
impl SurvivalFunctionParams {
pub fn validate_all(&self) -> Result<(), garde::Report> {
self.validate()?;
self.validate_custom_constraints()
}
fn validate_custom_constraints(&self) -> Result<(), garde::Report> {
let mut report = garde::Report::new();
let mut errors: ErrorVec = Vec::new();
let age_bounds = collect_age_bounds_errors(&self.mt, &mut errors);
let (min_age, max_age) = match age_bounds {
Some(bounds) => bounds,
None => {
for (path, message) in errors {
report.append(garde::Path::new(path), garde::Error::new(message));
}
return Err(report);
}
};
validate_age_boundaries(self.x, min_age, max_age, &mut errors);
let x = self.x;
let t = self.t;
let k = self.k;
if x + t + k > max_age {
errors.push(("", format!(
"age + deferral + term ({x} + {t} + {k}) cannot exceed max age {max_age} from mortality table"
)));
}
validate_entry_age(self.entry_age, self.x, min_age, &mut errors);
for (path, message) in errors {
report.append(garde::Path::new(path), garde::Error::new(message));
}
if report.is_empty() {
Ok(())
} else {
Err(report)
}
}
}
#[derive(Debug, Clone, Validate, Builder)]
#[garde(allow_unvalidated)]
pub struct SingleLifeParams {
#[garde(dive)]
pub mt: MortTableConfig,
pub i: f64,
#[garde(range(max = 150))]
pub x: u32,
#[garde(range(max = 150))]
pub n: Option<u32>,
#[garde(range(max = 150))]
pub t: u32,
#[garde(range(min = 1))]
pub m: u32,
#[garde(range(min = 1))]
pub moment: u32,
#[garde(range(max = 150))]
pub entry_age: Option<u32>,
}
impl SingleLifeParams {
pub fn validate_all(&self) -> Result<(), garde::Report> {
self.validate()?;
self.validate_custom_constraints()
}
fn validate_custom_constraints(&self) -> Result<(), garde::Report> {
let mut report = garde::Report::new();
let mut errors: ErrorVec = Vec::new();
let age_bounds = collect_age_bounds_errors(&self.mt, &mut errors);
let (min_age, max_age) = match age_bounds {
Some(bounds) => bounds,
None => {
for (path, message) in errors {
report.append(garde::Path::new(path), garde::Error::new(message));
}
return Err(report);
}
};
validate_age_boundaries(self.x as f64, min_age, max_age, &mut errors);
let x = self.x as f64;
let t = self.t as f64;
let n = self.n.unwrap_or(0) as f64;
if x + t + n > max_age {
errors.push(("", format!(
"age + deferral + term ({x} + {t} + {n}) cannot exceed max age {max_age} from mortality table"
)));
}
validate_entry_age(self.entry_age, x, min_age, &mut errors);
for (path, message) in errors {
report.append(garde::Path::new(path), garde::Error::new(message));
}
if report.is_empty() {
Ok(())
} else {
Err(report)
}
}
}
#[derive(Debug, Clone, Validate, Builder)]
#[garde(allow_unvalidated)]
pub struct GetValueFunctionValidation {
#[garde(dive)]
pub mt: MortTableConfig,
#[garde(range(max = 150))]
pub x: u32,
#[garde(range(max = 150))]
pub entry_age: Option<u32>,
}
impl GetValueFunctionValidation {
pub fn validate_all(&self) -> Result<(), garde::Report> {
self.validate()?;
self.validate_custom_constraints()
}
fn validate_custom_constraints(&self) -> Result<(), garde::Report> {
let mut report = garde::Report::new();
let mut errors: ErrorVec = Vec::new();
let age_bounds = collect_age_bounds_errors(&self.mt, &mut errors);
let (min_age, max_age) = match age_bounds {
Some(bounds) => bounds,
None => {
for (path, message) in errors {
report.append(garde::Path::new(path), garde::Error::new(message));
}
return Err(report);
}
};
validate_age_boundaries(self.x as f64, min_age, max_age, &mut errors);
let x = self.x as f64;
validate_entry_age(self.entry_age, x, min_age, &mut errors);
for (path, message) in errors {
report.append(garde::Path::new(path), garde::Error::new(message));
}
if report.is_empty() {
Ok(())
} else {
Err(report)
}
}
}
type ErrorVec = Vec<(&'static str, String)>;
fn collect_age_bounds_errors(mt: &MortTableConfig, errors: &mut ErrorVec) -> Option<(f64, f64)> {
let min_age_result = mt.min_age();
let max_age_result = mt.max_age();
match (&min_age_result, &max_age_result) {
(Ok(min), Ok(max)) => Some((*min as f64, *max as f64)),
_ => {
if min_age_result.is_err() {
errors.push(("mt", "Failed to get min_age from mortality table".into()));
}
if max_age_result.is_err() {
errors.push(("mt", "Failed to get max_age from mortality table".into()));
}
None
}
}
}
fn validate_age_boundaries(x: f64, min_age: f64, max_age: f64, errors: &mut ErrorVec) {
if x < min_age || x > max_age {
errors.push((
"x",
format!("age {x} must be between {min_age} and {max_age} from mortality table"),
));
}
}
fn validate_entry_age(entry_age: Option<u32>, x: f64, min_age: f64, errors: &mut ErrorVec) {
if let Some(entry_age) = entry_age {
if (entry_age as f64) > x {
errors.push((
"entry_age",
format!("entry_age {entry_age} cannot exceed age {x}"),
));
}
if (entry_age as f64) < min_age {
errors.push(("entry_age", format!(
"entry_age {entry_age} cannot be less than min age {min_age} from mortality table"
)));
}
}
}