use serde::{Deserialize, Serialize};
use super::PerStatus;
use crate::plan::{Dollars, FilingStatus};
const STATUTORY_HSA_PENALTY: f64 = 0.20;
const STATUTORY_OASDI: f64 = 0.062;
const STATUTORY_MEDICARE: f64 = 0.0145;
const STATUTORY_ADDITIONAL_MEDICARE: f64 = 0.009;
const STATUTORY_ADDITIONAL_MEDICARE_OVER: PerStatus<Dollars> = PerStatus {
single: 200_000,
married_joint: 250_000,
};
const STATUTORY_SELF_EMPLOYMENT_SHARE: f64 = 0.9235;
fn statutory_hsa_penalty() -> f64 {
STATUTORY_HSA_PENALTY
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct EarlyWithdrawal {
pub penalty: f64,
#[serde(default = "statutory_hsa_penalty")]
pub hsa_penalty: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct PayrollParams {
pub oasdi: f64,
pub medicare: f64,
pub additional_medicare: f64,
pub additional_medicare_over: PerStatus<Dollars>,
pub self_employment_share: f64,
}
impl Default for PayrollParams {
fn default() -> Self {
Self {
oasdi: STATUTORY_OASDI,
medicare: STATUTORY_MEDICARE,
additional_medicare: STATUTORY_ADDITIONAL_MEDICARE,
additional_medicare_over: STATUTORY_ADDITIONAL_MEDICARE_OVER,
self_employment_share: STATUTORY_SELF_EMPLOYMENT_SHARE,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct RmdDivisor {
pub age: u8,
pub divisor: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct RmdTable {
pub divisors: Vec<RmdDivisor>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct PhaseOut {
pub from: Dollars,
pub to: Dollars,
}
impl ContributionLimits {
#[must_use]
pub fn spouse_ira_deduction_phase_out(&self, status: FilingStatus) -> Option<PhaseOut> {
self.ira_deduction_phase_out_spouse
.filter(|_| status == FilingStatus::MarriedJoint)
}
}
impl PhaseOut {
#[must_use]
pub fn position(self, magi: Dollars) -> f64 {
if magi <= self.from {
return 0.0;
}
if magi >= self.to || self.to <= self.from {
return 1.0;
}
(magi - self.from) as f64 / (self.to - self.from) as f64
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct ContributionLimits {
pub employer_plan: Dollars,
pub employer_plan_catch_up_50: Dollars,
pub employer_plan_catch_up_60: Dollars,
pub overall_plan: Dollars,
pub simple: Dollars,
pub simple_catch_up_50: Dollars,
pub simple_catch_up_60: Dollars,
pub ira: Dollars,
pub ira_catch_up_50: Dollars,
pub hsa_self: Dollars,
pub hsa_family: Dollars,
pub hsa_catch_up_55: Dollars,
pub roth_ira_phase_out: PerStatus<PhaseOut>,
pub ira_deduction_phase_out: PerStatus<PhaseOut>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ira_deduction_phase_out_spouse: Option<PhaseOut>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct IrmaaTier {
pub magi_over: PerStatus<Dollars>,
pub part_b: Dollars,
pub part_d: Dollars,
}