paycheck_utils/lib.rs
1//! This library contains utility functions for calculating paycheck withholdings and net income given a hypothetical hourly wage and weekly working hours. The idea is pretty much like the "Sample Paycheck" tool found in the [Paycom](https://www.paycom.com/software/employee-self-service/) employee portal, but aimed at having a little more functionality and customization.
2//!
3//! The entire library was developed with the perspective of an hourly paid employee in mind, focusing on bi-weekly paychecks as the standard pay period to simulate how employees typically view and plan their income.
4//!
5//! The primary question this library aims to answer is: "Given an hourly wage and number of hours worked per week, what would my net paycheck be after taxes and deductions?"
6//!
7//! The secondary question this library aims to answer is: "Given a total monthly expenses amount and hourly wage, how many hours would I need to work to cover my expenses with "x" amount left over after taxes and deductions?" (this will be implemented in a future version).
8//!
9//! The library is structured into several modules:
10//! - `withholdings`: Contains functions to estimate federal tax withholdings, Social Security, and Medicare deductions.
11//! - `deductions`: Defines structures and functions for handling pre-tax and post-tax deductions.
12//! - `income`: Contains functions to calculate gross paycheck based on hourly wage and hours worked.
13//! - `expenses`: Defines structures and functions for managing monthly expenses.
14//! - `utils`: Contains tax and time related constants necessary for calculations.
15//!
16//! A CLI will be implemented in a future version to allow a user to input their hourly wage, hours worked, filing status, deductions and expenses to see their estimated net paycheck and other relevant financial metrics.
17
18pub mod deductions;
19pub mod expenses;
20pub mod income;
21pub mod utils;
22pub mod withholdings;
23
24pub use crate::deductions::*;
25pub use crate::expenses::*;
26pub use crate::income::*;
27pub use crate::utils::*;
28pub use crate::withholdings::*;
29
30/// Represents an employment scenario with hourly rate, hours worked per week, filing status, and deductions.
31/// Possible deductions avaialable are defined in the `deductions` module.
32///
33/// # Example
34/// ```
35/// use paycheck_utils::FilingStatus;
36/// use paycheck_utils::EmploymentScenario;
37/// use paycheck_utils::PreTaxDeductions;
38/// use paycheck_utils::PreTaxDeduction;
39/// use paycheck_utils::PostTaxDeductions;
40/// use paycheck_utils::PostTaxDeduction;
41/// use paycheck_utils::Expenses;
42///
43/// let new_job_scenario = EmploymentScenario::new(
44/// 30.0, // hourly rate
45/// 40.0, // hours per week
46/// FilingStatus::Single, // filing status
47/// PreTaxDeductions::new(vec![
48/// PreTaxDeduction::Medical(Some(150.0)),
49/// PreTaxDeduction::Dental(Some(50.0)),
50/// PreTaxDeduction::Vision(Some(15.0)),
51/// PreTaxDeduction::Traditional401K(Some(200.0)),
52/// ]), // pre-tax deductions
53/// PostTaxDeductions::new(vec![PostTaxDeduction::Roth401K(Some(100.0))]), // post-tax deductions
54/// Expenses::new(vec![]) // expenses
55/// );
56/// ```
57pub struct EmploymentScenario {
58 pub hourly_rate: f32,
59 pub hours_per_week: f32,
60 pub filing_status: FilingStatus,
61 pub pretax_deductions: PreTaxDeductions,
62 pub posttax_deductions: PostTaxDeductions,
63 pub expenses: Expenses,
64}
65
66impl EmploymentScenario {
67 pub fn new(
68 hourly_rate: f32,
69 hours_per_week: f32,
70 filing_status: FilingStatus,
71 pretax_deductions: PreTaxDeductions,
72 posttax_deductions: PostTaxDeductions,
73 expenses: Expenses,
74 ) -> Self {
75 EmploymentScenario {
76 hourly_rate,
77 hours_per_week,
78 filing_status,
79 pretax_deductions,
80 posttax_deductions,
81 expenses,
82 }
83 }
84
85 /// Calculates the net paycheck based on the employment scenario's parameters.
86 /// The calculations consider gross income, pre-tax deductions, federal tax withholdings, Social Security, Medicare, and post-tax deductions.
87 /// The IRS defined constants used to make calculations (such as tax rates, thresholds and standard deductions) are defined in the `utils` module.
88 /// This IRS method and flow for calculating withholdings is based on the 2026 federal tax year guidelines and can be summarized as follows:
89 /// 1. Calculate gross paycheck on hourly rate and hours worked.
90 /// 2. Subtract pre-tax deductions from gross paycheck to get adjusted gross paycheck.
91 /// 3. Calculate federal tax withholdings based on annualized adjusted gross paycheck and filing status.
92 /// 4. Calculate Social Security and Medicare withholdings based on adjusted gross paycheck.
93 /// 5. Subtract federal tax withholdings, Social Security, Medicare, and post-tax deductions from adjusted gross paycheck to get net paycheck.
94 ///
95 /// # Example
96 /// ```
97 /// use paycheck_utils::PostTaxDeduction;
98 /// use paycheck_utils::PreTaxDeduction;
99 /// use paycheck_utils::FilingStatus;
100 /// use paycheck_utils::EmploymentScenario;
101 /// use paycheck_utils::PreTaxDeductions;
102 /// use paycheck_utils::PostTaxDeductions;
103 /// use paycheck_utils::Expenses;
104 /// use paycheck_utils::Expense;
105 ///
106 /// let pretax_deductions = PreTaxDeductions::new(vec![
107 /// PreTaxDeduction::Medical(Some(100.0)),
108 /// PreTaxDeduction::Dental(Some(50.0)),
109 /// PreTaxDeduction::Vision(Some(25.0)),
110 /// PreTaxDeduction::Traditional401K(Some(200.0)),
111 /// PreTaxDeduction::HSA(Some(150.0)),
112 /// ]); // total = 525.0
113 /// let posttax_deductions = PostTaxDeductions::new(vec![
114 /// PostTaxDeduction::Roth401K(Some(100.0)),
115 /// PostTaxDeduction::VoluntaryLife(Some(30.0)),
116 /// ]); // total = 130.0
117 /// let scenario = EmploymentScenario::new(
118 /// 25.0, // hourly rate
119 /// 45.0, // hours per week (bi-weekly paycheck = 90 hours [10 hours overtime])
120 /// FilingStatus::Single, // single filing status for standard deduction
121 /// pretax_deductions, // total = 525.0
122 /// posttax_deductions, // total = 130.0
123 /// Expenses::new(vec![
124 /// Expense::Housing(Some(2000.0)),
125 /// Expense::Energy(Some(300.0)),
126 /// ]), // total = 2300.0
127 /// );
128 /// let net_paycheck = scenario.calculate_net_paycheck();
129 /// assert_eq!(net_paycheck, 1440.33);
130 ///
131 /// // Explanation of calculation:
132 /// // 1. Gross Paycheck: (25.0 * 80) + (25.0 * 10 * 1.5) = 2000.0 + 375.0 = 2375.0
133 /// // 2. Adjusted Gross Paycheck: 2375.0 - 525.0 = 1850.0 (after pre-tax deductions)
134 /// // 3. Federal Withholding (annualized AGP = 1850.0 * 26 = 48100.0): Using 2026 tax brackets for Single filer:
135 /// // - 10% on first 12,400 = 12,400 * 0.10 = 1,240.0
136 /// // - 12% on amount over 12,400 up to 50,400 = (48,100.0 - 12,400.0) * 0.12 = 4,290.0
137 /// // - Total annual federal tax = 1,240.0 + 4,290.0 = 5,530.0
138 /// // - Bi-weekly federal withholding = 5,530.0 / 26 = 212.69
139 /// // 4. Social Security Withholding: 1850.0 * 0.062 = 114.70
140 /// // 5. Medicare Withholding: 1850.0 * 0.0145 = 26.83
141 /// // 6. Post-Tax Deductions: 100.0 + 30.0 = 130.0
142 /// // 7. Total Deductions: 212.69 + 114.70 + 26.83 + 130.0 = 484.22
143 /// // 8. Net Paycheck: 1850.0 - 212.69 - 114.70 - 26.83 - 130.0 = 1440.33
144 /// ```
145 /// # Returns
146 /// An `f32` representing the calculated net paycheck amount.
147 ///
148 /// # Panics
149 /// This function does not explicitly panic, but it assumes that the input values (hourly rate, hours worked, deductions) are valid and reasonable.
150 ///
151 /// # Errors
152 /// This function does not return errors, but invalid input values may lead to incorrect calculations.
153 ///
154 /// # Notes
155 /// The calculations are based on the 2026 federal tax year guidelines and may need to be updated for future tax years.
156 pub fn calculate_net_paycheck(&self) -> f32 {
157 let mut gross_paycheck = determine_gross_paycheck(self.hourly_rate, self.hours_per_week);
158 let total_pretax = self.pretax_deductions.total_pretax_deductions();
159 gross_paycheck -= total_pretax;
160 let federal_withholding =
161 estimate_paycheck_federal_withholdings(gross_paycheck, self.filing_status);
162 let social_security = estimate_social_security_withholding(gross_paycheck);
163 let medicare = estimate_medicare_withholding(gross_paycheck);
164 let total_posttax = self.posttax_deductions.total_posttax_deductions();
165
166 round_2_decimals(
167 gross_paycheck - federal_withholding - social_security - medicare - total_posttax,
168 )
169 }
170
171 /// Compares the total monthly expenses to the calculated monthly net income.
172 /// Returns a tuple containing the monthly net income, total monthly expenses, and the difference between the two.
173 /// # Example
174 /// ```
175 /// // This example uses the same data as the `calculate_net_paycheck` example to demonstrate the comparison.
176 ///
177 /// use paycheck_utils::PostTaxDeduction;
178 /// use paycheck_utils::PreTaxDeduction;
179 /// use paycheck_utils::FilingStatus;
180 /// use paycheck_utils::EmploymentScenario;
181 /// use paycheck_utils::PreTaxDeductions;
182 /// use paycheck_utils::PostTaxDeductions;
183 /// use paycheck_utils::Expense;
184 /// use paycheck_utils::Expenses;
185 ///
186 /// let pretax_deductions = PreTaxDeductions::new(vec![
187 /// PreTaxDeduction::Medical(Some(100.0)),
188 /// PreTaxDeduction::Dental(Some(50.0)),
189 /// PreTaxDeduction::Vision(Some(25.0)),
190 /// PreTaxDeduction::Traditional401K(Some(200.0)),
191 /// PreTaxDeduction::HSA(Some(150.0)),
192 /// ]); // total = 525.0
193 /// let posttax_deductions = PostTaxDeductions::new(vec![
194 /// PostTaxDeduction::Roth401K(Some(100.0)),
195 /// PostTaxDeduction::VoluntaryLife(Some(30.0)),
196 /// ]); // total = 130.0
197 /// let expenses = Expenses::new(vec![
198 /// Expense::Housing(Some(1500.0)),
199 /// Expense::Energy(Some(200.0)),
200 /// Expense::Water(Some(50.0)),
201 /// Expense::Groceries(Some(400.0)),
202 /// Expense::Phone(Some(80.0)),
203 /// Expense::Internet(Some(60.0)),
204 /// ]); // total = 2290.0
205 /// let scenario = EmploymentScenario::new(
206 /// 25.0, // hourly rate
207 /// 45.0, // hours per week
208 /// FilingStatus::Single, // filing status
209 /// pretax_deductions,
210 /// posttax_deductions,
211 /// expenses,
212 /// );
213 /// let (monthly_net_income, total_monthly_expenses, difference) = scenario.compare_monthly_expenses_to_monthly_income();
214 /// assert_eq!(monthly_net_income, 2880.66);
215 /// assert_eq!(total_monthly_expenses, 2290.0);
216 /// assert_eq!(difference, 590.66);
217 /// ```
218 /// # Returns
219 /// A tuple containing:
220 /// - `f32`: Monthly net income
221 /// - `f32`: Total monthly expenses
222 /// - `f32`: Difference between monthly net income and total monthly expenses
223 pub fn compare_monthly_expenses_to_monthly_income(&self) -> (f32, f32, f32) {
224 let monthly_net_income = self.calculate_net_paycheck() * 2.0;
225 let total_monthly_expenses = self.expenses.total_monthly_expenses();
226 (
227 round_2_decimals(monthly_net_income),
228 round_2_decimals(total_monthly_expenses),
229 round_2_decimals(monthly_net_income - total_monthly_expenses)
230 )
231 }
232}
233
234
235// UNIT TEST FOR LIBRARY
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 #[test]
241 fn test_calculate_net_paycheck() {
242 let pretax_deductions = PreTaxDeductions::new(vec![
243 PreTaxDeduction::Medical(Some(100.0)),
244 PreTaxDeduction::Dental(Some(50.0)),
245 PreTaxDeduction::Vision(Some(25.0)),
246 PreTaxDeduction::Traditional401K(Some(200.0)),
247 PreTaxDeduction::HSA(Some(150.0)),
248 ]);
249 let posttax_deductions = PostTaxDeductions::new(vec![
250 PostTaxDeduction::Roth401K(Some(100.0)),
251 PostTaxDeduction::VoluntaryLife(Some(30.0)),
252 ]);
253 let expenses = Expenses::new(vec![
254 Expense::Housing(Some(2000.0)),
255 Expense::Energy(Some(200.0)),
256 Expense::Water(Some(50.0)),
257 Expense::Internet(Some(60.0)),
258 Expense::Phone(Some(80.0)),
259 Expense::Vehicle(Some(300.0)),
260 Expense::VehicleInsurance(Some(150.0)),
261 Expense::VehicleGas(Some(100.0)),
262 Expense::Groceries(Some(400.0)),
263 ]);
264 let scenario = EmploymentScenario::new(
265 25.0,
266 45.0,
267 FilingStatus::Single,
268 pretax_deductions,
269 posttax_deductions,
270 expenses,
271 );
272 let net_paycheck = scenario.calculate_net_paycheck();
273 assert_eq!(net_paycheck, 1440.33);
274 }
275
276 #[test]
277 fn test_compare_monthly_expenses_to_monthly_income() {
278 let pretax_deductions = PreTaxDeductions::new(vec![
279 PreTaxDeduction::Medical(Some(100.0)),
280 PreTaxDeduction::Dental(Some(50.0)),
281 PreTaxDeduction::Vision(Some(25.0)),
282 PreTaxDeduction::Traditional401K(Some(200.0)),
283 PreTaxDeduction::HSA(Some(150.0)),
284 ]);
285 let posttax_deductions = PostTaxDeductions::new(vec![
286 PostTaxDeduction::Roth401K(Some(100.0)),
287 PostTaxDeduction::VoluntaryLife(Some(30.0)),
288 ]);
289 let expenses = Expenses::new(vec![
290 Expense::Housing(Some(1500.0)),
291 Expense::Energy(Some(200.0)),
292 Expense::Water(Some(50.0)),
293 Expense::Groceries(Some(400.0)),
294 Expense::Phone(Some(80.0)),
295 Expense::Internet(Some(60.0)),
296 ]);
297 let scenario = EmploymentScenario::new(
298 25.0,
299 45.0,
300 FilingStatus::Single,
301 pretax_deductions,
302 posttax_deductions,
303 expenses,
304 );
305 let (monthly_net_income, total_monthly_expenses, difference) = scenario.compare_monthly_expenses_to_monthly_income();
306 assert_eq!(monthly_net_income, 2880.66);
307 assert_eq!(total_monthly_expenses, 2290.0);
308 assert_eq!(difference, 590.66);
309 }
310}