mod accounts;
mod allocation;
mod cliffs;
mod contributions;
mod dates;
mod diff;
mod escalation;
mod events;
mod expenses;
mod flows;
mod households;
mod incomes;
mod market;
pub(crate) use market::{INFLATION, VARIABLES, cholesky};
mod medicare;
mod places;
mod references;
mod residency;
mod scenario;
mod span;
mod triggers;
mod validate;
pub use accounts::{Account, AccountKind, TreatmentClass};
pub use allocation::{Allocation, AssetClass, ClassReturns, Mix, MixPhase};
pub use cliffs::Cliff;
pub use contributions::{Contribution, Match, Payer, Step};
pub use dates::PlanDate;
pub use diff::{Change, ChangeKind, diff};
pub use escalation::ColaSpec;
pub use events::Event;
pub use expenses::Expense;
pub use flows::{Conversion, Transfer};
pub use households::{FilingStatus, Household, Person, Residency};
pub use incomes::{Income, IncomeKind};
pub use market::{
ClassAssumption, Correlation, Correlations, Draw, HistoricalSettings, InflationAssumption,
Market, MonteCarloSettings,
};
pub use medicare::Medicare;
pub use places::{COUNTRIES, Place, US, US_STATES, place_name};
pub use scenario::{ID_KEY, Scenario, ScenarioError};
pub(crate) use span::{Node, Span};
pub use triggers::{Operand, Trigger, TriggerBasis, TriggerForm};
pub use validate::Issue;
pub(crate) use validate::push_issue;
use serde::{Deserialize, Serialize};
pub const SCHEMA_VERSION: u32 = 1;
pub type Dollars = i64;
#[derive(Debug, thiserror::Error)]
pub enum PlanError {
#[error(transparent)]
Parse(#[from] toml::de::Error),
#[error(transparent)]
Serialize(#[from] toml::ser::Error),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Settings {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
pub start_year: i16,
pub horizon_age: u8,
pub inflation: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub wage_growth: Option<f64>,
#[serde(default = "default_withdrawal_order")]
pub withdrawal_order: Vec<TreatmentClass>,
#[serde(skip_serializing_if = "Option::is_none")]
pub surplus_to: Option<String>,
}
fn default_withdrawal_order() -> Vec<TreatmentClass> {
vec![
TreatmentClass::Taxable,
TreatmentClass::Deferred,
TreatmentClass::Roth,
TreatmentClass::Hsa,
]
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Plan {
pub schema: u32,
pub plan: Settings,
pub household: Household,
#[serde(skip_serializing_if = "Option::is_none")]
pub medicare: Option<Medicare>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub events: Vec<Event>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub accounts: Vec<Account>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub income: Vec<Income>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub expenses: Vec<Expense>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub cliffs: Vec<Cliff>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub transfers: Vec<Transfer>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub conversions: Vec<Conversion>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub contributions: Vec<Contribution>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub residency: Vec<Residency>,
#[serde(skip_serializing_if = "Option::is_none")]
pub market: Option<Market>,
}
impl Plan {
#[must_use]
pub fn market(&self) -> &Market {
static NONE: Market = Market::NONE;
self.market.as_ref().unwrap_or(&NONE)
}
pub fn from_toml_str(text: &str) -> Result<Self, PlanError> {
Ok(toml::from_str(text)?)
}
pub fn from_toml_table(table: toml::Table) -> Result<Self, PlanError> {
Self::from_toml_str(&toml::to_string(&table)?)
}
pub fn to_toml_string(&self) -> Result<String, PlanError> {
Ok(toml::to_string_pretty(self)?)
}
#[must_use]
pub fn validate(&self) -> Vec<Issue> {
validate::validate(self)
}
#[must_use]
pub fn person(&self, id: &str) -> Option<&Person> {
self.household.people.iter().find(|person| person.id == id)
}
#[must_use]
pub fn person_name<'a>(&'a self, id: &'a str) -> &'a str {
self.person(id).map_or(id, Person::display_name)
}
#[must_use]
pub fn account(&self, id: &str) -> Option<&Account> {
self.accounts.iter().find(|account| account.id == id)
}
#[must_use]
pub fn surplus_account(&self) -> Option<&str> {
let swept = match &self.plan.surplus_to {
Some(id) => self.account(id),
None => self
.accounts
.iter()
.find(|account| account.kind == AccountKind::Cash),
};
swept.map(|account| account.id.as_str())
}
#[must_use]
pub fn income_source(&self, id: &str) -> Option<&Income> {
self.income.iter().find(|income| income.id == id)
}
}