use crate::conventions::Usd;
use crate::tax::types::FilingStatus;
use rust_decimal_macros::dec;
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OrdinaryBracket {
pub lower: Usd, pub rate: Usd, }
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OrdinarySchedule {
pub brackets: Vec<OrdinaryBracket>, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LtcgBreakpoints {
pub max_zero: Usd, pub max_fifteen: Usd, }
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TaxTable {
pub year: i32,
pub source: &'static str, pub ordinary: BTreeMap<FilingStatus, OrdinarySchedule>,
pub ltcg: BTreeMap<FilingStatus, LtcgBreakpoints>,
pub gift_annual_exclusion: Usd,
pub ss_wage_base: Usd,
pub gift_lifetime_exclusion: Usd,
}
impl TaxTable {
fn key(status: FilingStatus) -> FilingStatus {
match status {
FilingStatus::Qss => FilingStatus::Mfj,
s => s,
}
}
pub fn ordinary_for(&self, status: FilingStatus) -> &OrdinarySchedule {
&self.ordinary[&Self::key(status)]
}
pub fn ltcg_for(&self, status: FilingStatus) -> &LtcgBreakpoints {
&self.ltcg[&Self::key(status)]
}
}
pub trait TaxTables {
fn table_for(&self, year: i32) -> Option<&TaxTable>;
}
impl TaxTables for BTreeMap<i32, TaxTable> {
fn table_for(&self, year: i32) -> Option<&TaxTable> {
self.get(&year)
}
}
pub const NIIT_RATE: Usd = dec!(0.038);
pub const SE_RATE_SS: Usd = dec!(0.124);
pub const SE_RATE_MEDICARE: Usd = dec!(0.029);
pub const SE_RATE_ADDL_MEDICARE: Usd = dec!(0.009);
pub const SE_NET_EARNINGS_FACTOR: Usd = dec!(0.9235);
pub fn se_addl_medicare_threshold(status: FilingStatus) -> Usd {
match status {
FilingStatus::Mfj | FilingStatus::Qss => dec!(250000),
FilingStatus::Single | FilingStatus::HoH => dec!(200000),
FilingStatus::Mfs => dec!(125000),
}
}
pub const QUALIFIED_APPRAISAL_THRESHOLD: Usd = dec!(5000);
pub fn niit_threshold(status: FilingStatus) -> Usd {
match status {
FilingStatus::Mfj | FilingStatus::Qss => dec!(250000),
FilingStatus::Single | FilingStatus::HoH => dec!(200000),
FilingStatus::Mfs => dec!(125000),
}
}
pub fn loss_limit(status: FilingStatus) -> Usd {
match status {
FilingStatus::Mfs => dec!(1500),
_ => dec!(3000),
}
}
#[cfg(test)]
pub(crate) fn synthetic_table(year: i32) -> TaxTable {
let mut ordinary = BTreeMap::new();
ordinary.insert(
FilingStatus::Single,
OrdinarySchedule {
brackets: vec![
OrdinaryBracket {
lower: dec!(0),
rate: dec!(0.10),
},
OrdinaryBracket {
lower: dec!(10000),
rate: dec!(0.22),
},
OrdinaryBracket {
lower: dec!(100000),
rate: dec!(0.32),
},
],
},
);
let mut ltcg = BTreeMap::new();
ltcg.insert(
FilingStatus::Single,
LtcgBreakpoints {
max_zero: dec!(40000),
max_fifteen: dec!(400000),
},
);
TaxTable {
year,
source: "SYNTHETIC",
ordinary,
ltcg,
gift_annual_exclusion: dec!(19000),
ss_wage_base: dec!(176100),
gift_lifetime_exclusion: dec!(13_990_000),
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn statutory_values_are_constant_across_years() {
for status in [
FilingStatus::Single,
FilingStatus::Mfj,
FilingStatus::Mfs,
FilingStatus::HoH,
FilingStatus::Qss,
] {
assert_eq!(niit_threshold(status), niit_threshold(status));
}
assert_eq!(niit_threshold(FilingStatus::Mfj), dec!(250000));
assert_eq!(niit_threshold(FilingStatus::Qss), dec!(250000));
assert_eq!(niit_threshold(FilingStatus::Single), dec!(200000));
assert_eq!(niit_threshold(FilingStatus::HoH), dec!(200000));
assert_eq!(niit_threshold(FilingStatus::Mfs), dec!(125000));
assert_eq!(NIIT_RATE, dec!(0.038));
assert_eq!(QUALIFIED_APPRAISAL_THRESHOLD, dec!(5000));
assert_eq!(loss_limit(FilingStatus::Mfs), dec!(1500));
assert_eq!(loss_limit(FilingStatus::Single), dec!(3000));
assert_eq!(loss_limit(FilingStatus::Mfj), dec!(3000));
assert_eq!(loss_limit(FilingStatus::HoH), dec!(3000));
assert_eq!(loss_limit(FilingStatus::Qss), dec!(3000));
}
#[test]
fn qss_uses_mfj_schedule() {
let mut t = synthetic_table(2025);
t.ordinary.insert(
FilingStatus::Mfj,
OrdinarySchedule {
brackets: vec![
OrdinaryBracket {
lower: dec!(0),
rate: dec!(0.10),
},
OrdinaryBracket {
lower: dec!(50000),
rate: dec!(0.22),
},
],
},
);
t.ltcg.insert(
FilingStatus::Mfj,
LtcgBreakpoints {
max_zero: dec!(80000),
max_fifteen: dec!(500000),
},
);
assert_eq!(
t.ordinary_for(FilingStatus::Qss).brackets,
t.ordinary_for(FilingStatus::Mfj).brackets
);
assert_eq!(
*t.ltcg_for(FilingStatus::Qss),
*t.ltcg_for(FilingStatus::Mfj)
);
}
}