diff --git a/app.py b/app.py index f74e934..55a088a 100644 --- a/app.py +++ b/app.py @@ -18,7 +18,8 @@ with income_col: st.markdown("### Income Information") - income, social_security_retirement = render_income_inputs() + # Update to unpack all four return values + income, social_security_retirement, tip_income, overtime_income = render_income_inputs() itemized_deductions = render_itemized_deductions() # Calculate button @@ -35,6 +36,8 @@ "spouse_age": spouse_age, "income": income, "social_security_retirement": social_security_retirement, + "tip_income": tip_income, + "overtime_income": overtime_income, **itemized_deductions } @@ -55,7 +58,7 @@ with tab2: # Display credit components - credit_df = format_credit_components(results_df, state) # Pass the state code + credit_df = format_credit_components(results_df, state) if credit_df is not None: st.markdown(credit_df.to_markdown()) else: diff --git a/config.py b/config.py index c792839..d513f2a 100644 --- a/config.py +++ b/config.py @@ -17,11 +17,15 @@ [Learn more about the Harris CTC](https://policyengine.org/us/research/harris-ctc) - **High Earners Reform**: Adjustments to tax rates and thresholds for high-income earners - **Restored EITC**: Bringing back the expanded American Rescue Plan version of the Earned Income Tax Credit +- **Tip Income Tax Relief**: Exempting tip income from both income and payroll taxes + ### Trump Economic Package The Trump platform includes: - **Social Security Tax Exemption**: Eliminating taxes on Social Security benefits [Learn more about the Social Security proposal](https://policyengine.org/us/research/social-security-tax-exemption) +- **Tip Income Tax Relief**: Exempting all tip income from both income and payroll taxes +- **Overtime Tax Relief**: Exempting all overtime income from both income and payroll taxes """ NOTES = """ @@ -32,4 +36,5 @@ - The calculator uses the PolicyEngine US microsimulation model. - Actual impacts may vary based on individual circumstances and final policy implementations. - Proposals are modeled based on currently available information and may be updated as more details are released. +- Tax exemptions for tip and overtime income apply to both income tax and payroll taxes under the specified reforms. """ \ No newline at end of file diff --git a/reforms.py b/reforms.py index dad239a..e528169 100644 --- a/reforms.py +++ b/reforms.py @@ -69,6 +69,16 @@ }, "gov.irs.credits.eitc.phase_out.start[0].amount": { "2025-01-01.2025-12-31": 13706, + }, + # Tip Income Tax Exemption + "gov.contrib.tax_exempt.in_effect": { + "2025-01-01.2100-12-31": True + }, + "gov.contrib.tax_exempt.tip_income.income_tax_exempt": { + "2025-01-01.2100-12-31": True + }, + "gov.contrib.tax_exempt.tip_income.payroll_tax_exempt": { + "2025-01-01.2100-12-31": True } }, "Trump": { @@ -78,6 +88,22 @@ }, "gov.irs.social_security.taxability.rate.base": { "2024-01-01.2100-12-31": 0 + }, + # Trump Tax Exemptions + "gov.contrib.tax_exempt.in_effect": { + "2025-01-01.2100-12-31": True + }, + "gov.contrib.tax_exempt.overtime.income_tax_exempt": { + "2025-01-01.2100-12-31": True + }, + "gov.contrib.tax_exempt.overtime.payroll_tax_exempt": { + "2025-01-01.2100-12-31": True + }, + "gov.contrib.tax_exempt.tip_income.income_tax_exempt": { + "2025-01-01.2100-12-31": True + }, + "gov.contrib.tax_exempt.tip_income.payroll_tax_exempt": { + "2025-01-01.2100-12-31": True } } -} +} \ No newline at end of file diff --git a/results.py b/results.py index e9031cc..9790fe9 100644 --- a/results.py +++ b/results.py @@ -18,21 +18,38 @@ def load_credits_from_yaml(package, resource_path): def create_situation(state, is_married, child_ages, income, social_security_retirement, head_age, spouse_age=None, medical_expenses=0, real_estate_taxes=0, interest_expense=0, charitable_cash=0, charitable_non_cash=0, - qualified_business_income=0, casualty_loss=0): + qualified_business_income=0, casualty_loss=0, tip_income=0, + overtime_income=0, reform_name="Baseline"): + """ + Creates a situation dictionary for the simulation. + service_industry_wages are only included for specific reforms: + - For Harris reform: includes tip_income + - For Trump reform: includes both tip_income and overtime_income + - For Baseline: all income is treated as regular employment_income + """ + # Initialize person dict with common attributes + person_dict = { + "age": {YEAR: head_age}, + "employment_income": {YEAR: income}, + "social_security_retirement": {YEAR: social_security_retirement}, + "medical_out_of_pocket_expenses": {YEAR: medical_expenses}, + "interest_expense": {YEAR: interest_expense}, + "charitable_cash_donations": {YEAR: charitable_cash}, + "charitable_non_cash_donations": {YEAR: charitable_non_cash}, + "qualified_business_income": {YEAR: qualified_business_income}, + "casualty_loss": {YEAR: casualty_loss}, + "real_estate_taxes": {YEAR: real_estate_taxes}, + } + + # Add service_industry_wages only for relevant reforms + if reform_name == "Harris": + person_dict["service_industry_wages"] = {YEAR: tip_income} + elif reform_name == "Trump": + person_dict["service_industry_wages"] = {YEAR: tip_income + overtime_income} + situation = { "people": { - "adult": { - "age": {YEAR: head_age}, - "employment_income": {YEAR: income}, - "social_security_retirement": {YEAR: social_security_retirement}, - "medical_out_of_pocket_expenses": {YEAR: medical_expenses}, - "interest_expense": {YEAR: interest_expense}, - "charitable_cash_donations": {YEAR: charitable_cash}, - "charitable_non_cash_donations": {YEAR: charitable_non_cash}, - "qualified_business_income": {YEAR: qualified_business_income}, - "casualty_loss": {YEAR: casualty_loss}, - "real_estate_taxes": {YEAR: real_estate_taxes}, - }, + "adult": person_dict }, "families": {"family": {"members": ["adult"]}}, "marital_units": {"marital_unit": {"members": ["adult"]}}, @@ -53,16 +70,26 @@ def create_situation(state, is_married, child_ages, income, social_security_reti "spm_units": {"household": {"members": ["adult"]}}, } + # Add children for i, age in enumerate(child_ages): child_id = f"child_{i}" situation["people"][child_id] = {"age": {YEAR: age}} for unit in ["families", "tax_units", "households", "spm_units"]: situation[unit][list(situation[unit].keys())[0]]["members"].append(child_id) + # Add spouse if married if is_married and spouse_age is not None: - situation["people"]["spouse"] = { + spouse_dict = { "age": {YEAR: spouse_age}, + "employment_income": {YEAR: 0}, } + # Add service_industry_wages for spouse only if reform applies + if reform_name == "Harris": + spouse_dict["service_industry_wages"] = {YEAR: 0} + elif reform_name == "Trump": + spouse_dict["service_industry_wages"] = {YEAR: 0} + + situation["people"]["spouse"] = spouse_dict for unit in ["families", "marital_units", "tax_units", "households", "spm_units"]: situation[unit][list(situation[unit].keys())[0]]["members"].append("spouse") @@ -78,19 +105,47 @@ def calculate_values(categories, simulation, year): result_dict[category] = 0 return result_dict -def calculate_consolidated_results(reform_name, state, is_married, child_ages, income, social_security_retirement, - head_age, spouse_age=None, medical_expenses=0, real_estate_taxes=0, - interest_expense=0, charitable_cash=0, charitable_non_cash=0, - qualified_business_income=0, casualty_loss=0): +def calculate_consolidated_results(reform_name, state, is_married, child_ages, income, + social_security_retirement, head_age, spouse_age=None, + medical_expenses=0, real_estate_taxes=0, interest_expense=0, + charitable_cash=0, charitable_non_cash=0, qualified_business_income=0, + casualty_loss=0, tip_income=0, overtime_income=0): """ Calculates metrics for a single reform with detailed breakdowns. """ - # Create situation dictionary + # For baseline, add all income to regular employment income + if reform_name == "Baseline": + total_income = income + tip_income + overtime_income + tip_income = 0 + overtime_income = 0 + else: + total_income = income + # For Harris reform, only tip income is exempt + if reform_name == "Harris": + total_income += overtime_income + overtime_income = 0 + # For Trump reform, both tip and overtime remain separate + # (no need to modify as both will be handled in service_industry_wages) + + # Create situation dictionary with reform name situation = create_situation( - state, is_married, child_ages, income, social_security_retirement, - head_age, spouse_age, medical_expenses, real_estate_taxes, interest_expense, - charitable_cash, charitable_non_cash, qualified_business_income, - casualty_loss + state=state, + is_married=is_married, + child_ages=child_ages, + income=total_income, + social_security_retirement=social_security_retirement, + head_age=head_age, + spouse_age=spouse_age, + medical_expenses=medical_expenses, + real_estate_taxes=real_estate_taxes, + interest_expense=interest_expense, + charitable_cash=charitable_cash, + charitable_non_cash=charitable_non_cash, + qualified_business_income=qualified_business_income, + casualty_loss=casualty_loss, + tip_income=tip_income, + overtime_income=overtime_income, + reform_name=reform_name ) # Set up simulation @@ -101,14 +156,17 @@ def calculate_consolidated_results(reform_name, state, is_married, child_ages, i reform = Reform.from_dict(reform_dict, country_id="us") simulation = Simulation(reform=reform, situation=situation) - # # Get categories for credits - # federal_refundable_credits = IncomeTaxRefundableCredits.adds - # state_refundable_credits = StateRefundableCredits.adds + # Get metrics + household_net_income = simulation.calculate("household_net_income", YEAR)[0] + household_refundable_tax_credits = simulation.calculate("household_refundable_tax_credits", YEAR)[0] + household_tax_before_refundable_credits = simulation.calculate("household_tax_before_refundable_credits", YEAR)[0] package = "policyengine_us" resource_path_federal = "parameters/gov/irs/credits/refundable.yaml" resource_path_state = f"parameters/gov/states/{state.lower()}/tax/income/credits/refundable.yaml" + + # Get categories for credits try: federal_refundable_credits = load_credits_from_yaml(package, resource_path_federal) except FileNotFoundError: @@ -119,12 +177,6 @@ def calculate_consolidated_results(reform_name, state, is_married, child_ages, i except FileNotFoundError: state_refundable_credits = [] - - # Calculate main metrics - household_net_income = simulation.calculate("household_net_income", YEAR)[0] - household_refundable_tax_credits = simulation.calculate("household_refundable_tax_credits", YEAR)[0] - household_tax_before_refundable_credits = simulation.calculate("household_tax_before_refundable_credits", YEAR)[0] - # Calculate credit breakdowns federal_credits_dict = calculate_values(federal_refundable_credits, simulation, YEAR) state_credits_dict = calculate_values(state_refundable_credits, simulation, YEAR) diff --git a/ui_components.py b/ui_components.py index 925818d..09a0f6f 100644 --- a/ui_components.py +++ b/ui_components.py @@ -55,11 +55,46 @@ def render_personal_info(): return is_married, state, child_ages, head_age, spouse_age def render_income_inputs(): - income = st.slider("Annual wages and salaries", min_value=0, max_value=500000, value=30000, step=500, format="$%d") - social_security_retirement = st.slider("Annual social security retirement income", min_value=0, max_value=500000, value=0, step=500, format="$%d") + income = st.slider( + "Annual wages and salaries", + min_value=0, + max_value=500000, + value=30000, + step=500, + format="$%d", + help="Regular wages and salaries (excluding overtime and tips)" + ) - return income, social_security_retirement - + social_security_retirement = st.slider( + "Annual social security retirement income", + min_value=0, + max_value=500000, + value=0, + step=500, + format="$%d" + ) + + tip_income = st.slider( + "Annual tip income", + min_value=0, + max_value=100000, + value=0, + step=100, + format="$%d", + help="Total annual income from tips" + ) + + overtime_income = st.slider( + "Annual overtime income", + min_value=0, + max_value=100000, + value=0, + step=100, + format="$%d", + help="Total annual income from overtime work" + ) + + return income, social_security_retirement, tip_income, overtime_income def render_itemized_deductions(): show_itemized = st.expander("Itemized deduction sources", expanded=False)