use crate::conventions::{round_dollar, Usd};
use crate::tax::compute::{ordinary_tax_on, preferential_tax};
use crate::tax::tables::{LtcgBreakpoints, OrdinarySchedule};
use rust_decimal_macros::dec;
pub const TAX_TABLE_CEILING: Usd = dec!(100_000);
fn bin_midpoint(ti: Usd) -> Usd {
debug_assert!(ti >= Usd::ZERO && ti < TAX_TABLE_CEILING);
if ti < dec!(5) {
return dec!(2.5);
}
if ti < dec!(15) {
return dec!(10);
}
if ti < dec!(25) {
return dec!(20);
}
let width = if ti < dec!(3000) { dec!(25) } else { dec!(50) };
let lower = (ti / width).floor() * width;
lower + width / dec!(2)
}
fn worksheet_tax(schedule: &OrdinarySchedule, amt: Usd) -> Usd {
let amt = amt.max(Usd::ZERO);
if amt < TAX_TABLE_CEILING {
round_dollar(ordinary_tax_on(schedule, bin_midpoint(amt)))
} else {
ordinary_tax_on(schedule, amt)
}
}
pub fn regular_tax(schedule: &OrdinarySchedule, taxable_income: Usd) -> Usd {
round_dollar(worksheet_tax(schedule, taxable_income))
}
pub fn qdcgt_line16(
schedule: &OrdinarySchedule,
bp: &LtcgBreakpoints,
taxable_income: Usd,
qual_div: Usd,
net_ltcg: Usd,
) -> Usd {
let z = Usd::ZERO;
let ti = taxable_income.max(z);
let pref_full = (qual_div.max(z)) + (net_ltcg.max(z)); let bottom = (ti - pref_full).max(z); let pref = pref_full.min(ti); let split = preferential_tax(bp, bottom, pref);
let l23 = worksheet_tax(schedule, bottom) + split.tax; let l24 = worksheet_tax(schedule, ti); round_dollar(l23.min(l24)) }
pub fn first_unbinnable_edge(schedule: &OrdinarySchedule) -> Option<Usd> {
schedule
.brackets
.iter()
.map(|b| b.lower)
.filter(|&e| e > Usd::ZERO && e < TAX_TABLE_CEILING)
.find(|&e| (e % dec!(25)) != Usd::ZERO)
}
pub fn assert_edges_binnable(schedule: &OrdinarySchedule, ctx: &str) {
if let Some(e) = first_unbinnable_edge(schedule) {
panic!("bracket edge {e} < $100k is not a $25 multiple (Tax-Table unbinnable): {ctx}");
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tax::tables::OrdinaryBracket;
fn single_2024() -> OrdinarySchedule {
OrdinarySchedule {
brackets: vec![
OrdinaryBracket {
lower: dec!(0),
rate: dec!(0.10),
},
OrdinaryBracket {
lower: dec!(11600),
rate: dec!(0.12),
},
OrdinaryBracket {
lower: dec!(47150),
rate: dec!(0.22),
},
OrdinaryBracket {
lower: dec!(100525),
rate: dec!(0.24),
},
OrdinaryBracket {
lower: dec!(191950),
rate: dec!(0.32),
},
OrdinaryBracket {
lower: dec!(243725),
rate: dec!(0.35),
},
OrdinaryBracket {
lower: dec!(609350),
rate: dec!(0.37),
},
],
}
}
fn mfj_2024() -> OrdinarySchedule {
OrdinarySchedule {
brackets: vec![
OrdinaryBracket {
lower: dec!(0),
rate: dec!(0.10),
},
OrdinaryBracket {
lower: dec!(23200),
rate: dec!(0.12),
},
OrdinaryBracket {
lower: dec!(94300),
rate: dec!(0.22),
},
OrdinaryBracket {
lower: dec!(201050),
rate: dec!(0.24),
},
OrdinaryBracket {
lower: dec!(383900),
rate: dec!(0.32),
},
OrdinaryBracket {
lower: dec!(487450),
rate: dec!(0.35),
},
OrdinaryBracket {
lower: dec!(731200),
rate: dec!(0.37),
},
],
}
}
fn bp_single_2024() -> LtcgBreakpoints {
LtcgBreakpoints {
max_zero: dec!(47025),
max_fifteen: dec!(518900),
}
}
fn bp_mfj_2024() -> LtcgBreakpoints {
LtcgBreakpoints {
max_zero: dec!(94050),
max_fifteen: dec!(583750),
}
}
#[test]
fn bin_midpoints_match_irs_structure() {
assert_eq!(bin_midpoint(dec!(0)), dec!(2.5));
assert_eq!(bin_midpoint(dec!(4)), dec!(2.5));
assert_eq!(bin_midpoint(dec!(5)), dec!(10));
assert_eq!(bin_midpoint(dec!(14)), dec!(10));
assert_eq!(bin_midpoint(dec!(15)), dec!(20));
assert_eq!(bin_midpoint(dec!(24)), dec!(20));
assert_eq!(bin_midpoint(dec!(25)), dec!(37.5)); assert_eq!(bin_midpoint(dec!(49)), dec!(37.5));
assert_eq!(bin_midpoint(dec!(2975)), dec!(2987.5)); assert_eq!(bin_midpoint(dec!(3000)), dec!(3025)); assert_eq!(bin_midpoint(dec!(58000)), dec!(58025));
assert_eq!(bin_midpoint(dec!(58049)), dec!(58025));
assert_eq!(bin_midpoint(dec!(99999)), dec!(99975)); }
#[test]
fn tax_table_cells_reproduce() {
let s = single_2024();
let m = mfj_2024();
assert_eq!(worksheet_tax(&s, dec!(58000)), dec!(7819)); assert_eq!(worksheet_tax(&s, dec!(60000)), dec!(8259)); assert_eq!(worksheet_tax(&m, dec!(79000)), dec!(9019));
assert_eq!(worksheet_tax(&m, dec!(85000)), dec!(9739));
assert_eq!(worksheet_tax(&s, dec!(100000)), dec!(17053.00));
assert_eq!(worksheet_tax(&s, dec!(120000)), dec!(21842.50));
}
#[test]
fn qdcgt_example_a_mfj_table() {
let l16 = qdcgt_line16(
&mfj_2024(),
&bp_mfj_2024(),
dec!(85000),
dec!(6000),
dec!(0),
);
assert_eq!(l16, dec!(9019));
}
#[test]
fn qdcgt_example_b_single_tcw() {
let l16 = qdcgt_line16(
&single_2024(),
&bp_single_2024(),
dec!(120000),
dec!(0),
dec!(20000),
);
assert_eq!(l16, dec!(20053));
}
#[test]
fn qdcgt_example_c_single_loss_year() {
let l16 = qdcgt_line16(
&single_2024(),
&bp_single_2024(),
dec!(60000),
dec!(2000),
dec!(0),
);
assert_eq!(l16, dec!(8119));
}
#[test]
fn kat1_pref_cap_pref_exceeds_ti() {
let l16 = qdcgt_line16(
&single_2024(),
&bp_single_2024(),
dec!(35400),
dec!(50000),
dec!(0),
);
assert_eq!(l16, dec!(0));
}
#[test]
fn kat2_binding_min_same_bin() {
let l16 = qdcgt_line16(
&single_2024(),
&bp_single_2024(),
dec!(58010),
dec!(10),
dec!(0),
);
assert_eq!(l16, dec!(7819));
}
#[test]
fn kat3_edge_binnability_and_midpoint_edge() {
assert_eq!(first_unbinnable_edge(&single_2024()), None);
assert_eq!(first_unbinnable_edge(&mfj_2024()), None);
let bad = OrdinarySchedule {
brackets: vec![
OrdinaryBracket {
lower: dec!(0),
rate: dec!(0.10),
},
OrdinaryBracket {
lower: dec!(12340),
rate: dec!(0.12),
},
],
};
assert_eq!(first_unbinnable_edge(&bad), Some(dec!(12340)));
let mid = OrdinarySchedule {
brackets: vec![
OrdinaryBracket {
lower: dec!(0),
rate: dec!(0.10),
},
OrdinaryBracket {
lower: dec!(11925),
rate: dec!(0.12),
},
],
};
assert_eq!(first_unbinnable_edge(&mid), None);
assert_eq!(worksheet_tax(&mid, dec!(11925)), dec!(1193));
}
#[test]
fn regular_tax_table_and_tcw() {
let s = single_2024();
assert_eq!(regular_tax(&s, dec!(58000)), dec!(7819)); assert_eq!(regular_tax(&s, dec!(120000)), dec!(21843)); assert_eq!(regular_tax(&s, dec!(0)), dec!(0));
}
}