diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f391397..d129567 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest] python-version: ["3.10", 3.11] steps: @@ -28,9 +28,5 @@ jobs: pip install -e . pip install pytest - - name: Make TAXSIM executables executable (Unix) - if: runner.os != 'Windows' - run: chmod +x resources/taxsim35/taxsim35-*.exe - - name: Run tests run: pytest tests/ diff --git a/policyengine_taxsim/config/variable_mappings.yaml b/policyengine_taxsim/config/variable_mappings.yaml index 3e06d13..4e78ead 100644 --- a/policyengine_taxsim/config/variable_mappings.yaml +++ b/policyengine_taxsim/config/variable_mappings.yaml @@ -357,8 +357,8 @@ policyengine_to_taxsim: full_text_group: "Federal Tax Calculation" group_column: 1 v30: - variable: na_pe - implemented: false + variable: household_net_income + implemented: true idtl: - full: 2 - full_text: 5 @@ -390,6 +390,9 @@ policyengine_to_taxsim: - mn: implemented: false variable: na_pe + - ia: + implemented: false + variable: na_pe - il: implemented: true variable: state_base_income @@ -413,25 +416,6 @@ policyengine_to_taxsim: group_order: 4 full_text_group: "State Tax Calculation" group_column: 1 - special_cases: - - ms: - implemented: false - variable: na_pe - - ar: - implemented: false - variable: na_pe - - mt: - implemented: false - variable: na_pe - - de: - implemented: false - variable: na_pe - - ia: - implemented: false - variable: na_pe - - ky: - implemented: false - variable: na_pe v35: variable: state_itemized_deductions implemented: true @@ -465,6 +449,13 @@ policyengine_to_taxsim: group_order: 4 full_text_group: "State Tax Calculation" group_column: 1 + special_cases: + - ms: + implemented: false + variable: na_pe + - ia: + implemented: false + variable: na_pe v41: variable: na_pe implemented: false @@ -488,6 +479,12 @@ policyengine_to_taxsim: - mn: implemented: false variable: na_pe + - ms: + implemented: false + variable: na_pe + - ia: + implemented: false + variable: na_pe rent_credit: variable: na_pe implemented: false @@ -522,6 +519,10 @@ policyengine_to_taxsim: group_order: 4 full_text_group: "State Tax Calculation" group_column: 1 + special_cases: + - ms: + implemented: false + variable: na_pe energy_fuel_credit: variable: na_pe implemented: false @@ -555,7 +556,7 @@ policyengine_to_taxsim: full_text_group: "State Tax Calculation" group_column: 1 variables: - - state_non_refundable_credit + - state_non_refundable_credits - state_refundable_credits energy_fuel_credit2: variable: na_pe @@ -924,5 +925,4 @@ taxsim_input_definition: - pbusinc: name: "27. Txpy/Spouse QBI w/o PO" - pprofinc: - name: "28. Txpy/Spouse SSTB w PO" - + name: "28. Txpy/Spouse SSTB w PO" \ No newline at end of file diff --git a/policyengine_taxsim/core/output_mapper.py b/policyengine_taxsim/core/output_mapper.py index f1c7cff..8d109cf 100644 --- a/policyengine_taxsim/core/output_mapper.py +++ b/policyengine_taxsim/core/output_mapper.py @@ -9,6 +9,8 @@ def generate_non_description_output(taxsim_output, mappings, year, state_name, simulation, output_type, logs): outputs = [] for key, each_item in mappings.items(): + state_initial = state_name.lower() + if each_item['implemented']: if key == "taxsimid": taxsim_output[key] = taxsim_output["taxsimid"] @@ -18,10 +20,28 @@ def generate_non_description_output(taxsim_output, mappings, year, state_name, s taxsim_output[key] = get_state_number(state_name) elif 'variables' in each_item and len(each_item['variables']) > 0: pe_variables = each_item['variables'] - taxsim_output[key] = simulate_multiple(simulation, pe_variables, year) + + if 'special_cases' in each_item: + found_state = next((each for each in each_item['special_cases'] if state_initial in each), + None) + if found_state and found_state[state_initial]['implemented']: + pe_variable = found_state[state_initial]['variable'].replace("state", + state_initial) if "state" in \ + found_state[ + state_initial][ + 'variable'] else \ + found_state[state_initial]['variable'] + taxsim_output[key] = simulate(simulation, pe_variable, year) + + outputs.append({'variable': pe_variable, 'value': taxsim_output[key]}) + else: + taxsim_output[key] = simulate_multiple(simulation, pe_variables, year, state_initial) + + for pe_variable in pe_variables: + outputs.append({'variable': pe_variable, 'value': taxsim_output[key]}) + else: pe_variable = each_item['variable'] - state_initial = state_name.lower() if "state" in pe_variable: pe_variable = pe_variable.replace("state", state_initial) @@ -30,11 +50,39 @@ def generate_non_description_output(taxsim_output, mappings, year, state_name, s if output_type in entry.values(): if 'special_cases' in each_item: - found_state = next((each for each in each_item['special_cases'] if state_initial in each), None) + found_state = next((each for each in each_item['special_cases'] if state_initial in each), + None) if found_state and found_state[state_initial]['implemented']: - pe_variable = found_state[state_initial]['variable'].replace("state", state_initial) if "state" in found_state[state_initial]['variable'] else found_state[state_initial]['variable'] - taxsim_output[key] = simulate(simulation, pe_variable, year) - outputs.append({'variable': pe_variable, 'value': taxsim_output[key]}) + pe_variable = found_state[state_initial]['variable'].replace("state", + state_initial) if "state" in \ + found_state[ + state_initial][ + 'variable'] else \ + found_state[state_initial]['variable'] + taxsim_output[key] = simulate(simulation, pe_variable, year) + + outputs.append({'variable': pe_variable, 'value': taxsim_output[key]}) + continue + + if 'pre_simulation' in each_item: + found_state = next((each for each in each_item['pre_simulation'] if state_initial in each), + None) + if found_state and found_state[state_initial]['implemented']: + pre_simulation_variable = found_state[state_initial]['pre_variable'] + use_indiv = simulate_to_decide(simulation, pre_simulation_variable, year) + variables = found_state[state_initial]['variables'] + + if use_indiv: + pe_variable = variables[0] + else: + pe_variable = variables[1] + + taxsim_output[key] = simulate(simulation, pe_variable, year) + outputs.append({'variable': pe_variable, 'value': taxsim_output[key]}) + + else: + taxsim_output[key] = simulate(simulation, pe_variable, year) + outputs.append({'variable': pe_variable, 'value': taxsim_output[key]}) file_name = f"{taxsim_output['taxsimid']}-{state_name}.yaml" generate_pe_tests_yaml(simulation.situation_input, outputs, file_name, logs) @@ -120,14 +168,39 @@ def generate_text_description_output(taxsim_input, mappings, year, state_name, s elif var_name == "state": value = f"{get_state_number(state_name)}{' ' * LEFT_MARGIN}{state_name}" elif 'variables' in each_item and len(each_item['variables']) > 0: - value = simulate_multiple(simulation, each_item['variables'], year) + value = simulate_multiple(simulation, each_item['variables'], year, state_initial) else: if 'special_cases' in each_item: found_state = next((each for each in each_item['special_cases'] if state_initial in each), None) if found_state and found_state[state_initial]['implemented']: - variable = found_state[state_initial]['variable'].replace("state", state_initial) if "state" in found_state[state_initial]['variable'] else found_state[state_initial]['variable'] - value = simulate(simulation, variable, year) - outputs.append({'variable': variable, 'value': value}) + variable = found_state[state_initial]['variable'].replace("state", + state_initial) if "state" in \ + found_state[ + state_initial][ + 'variable'] else \ + found_state[state_initial]['variable'] + value = simulate(simulation, variable, year) + + outputs.append({'variable': variable, 'value': value}) + continue + + if 'pre_simulation' in each_item: + found_state = next((each for each in each_item['pre_simulation'] if state_initial in each), + None) + if found_state and found_state[state_initial]['implemented']: + pre_simulation_variable = found_state[state_initial]['pre_variable'] + use_indiv = simulate_to_decide(simulation, pre_simulation_variable, year) + variables = found_state[state_initial]['variables'] + if use_indiv: + value = simulate(simulation, variables[0], year) + else: + value = simulate(simulation, variables[1], year) + + outputs.append({'variable': variable, 'value': value}) + + else: + value = simulate(simulation, variable, year) + outputs.append({'variable': variable, 'value': value}) # Format the base value if isinstance(value, (int, float)): @@ -138,14 +211,20 @@ def generate_text_description_output(taxsim_input, mappings, year, state_name, s # Format second column value if needed if has_second_column: if 'variables' in each_item and len(each_item['variables']) > 0: - second_value = simulate_multiple(simulation_1dollar_more, each_item['variables'], year) + second_value = simulate_multiple(simulation_1dollar_more, each_item['variables'], year, state_initial) else: if 'special_cases' in each_item: found_state = next((each for each in each_item['special_cases'] if state_initial in each), None) if found_state and found_state[state_initial]['implemented']: - variable = found_state[state_initial]['variable'].replace("state", state_initial) if "state" in found_state[state_initial]['variable'] else found_state[state_initial]['variable'] - second_value = simulate(simulation_1dollar_more, variable, year) + variable = found_state[state_initial]['variable'].replace("state", + state_initial) if "state" in \ + found_state[ + state_initial][ + 'variable'] else \ + found_state[state_initial]['variable'] + second_value = simulate(simulation_1dollar_more, variable, year) + continue if isinstance(second_value, (int, float)): formatted_second_value = f"{second_value:>8.1f}" @@ -316,14 +395,28 @@ def export_household(taxsim_input, policyengine_situation, logs): def simulate(simulation, variable, year): try: - return to_roundedup_number(simulation.calculate(variable, period=year)) + return to_roundedup_number(sum(simulation.calculate(variable, period=year))) except Exception as error: + print(error) return 0.00 -def simulate_multiple(simulation, variables, year): +def simulate_to_decide(simulation, variable, year) -> bool: try: - total = sum(to_roundedup_number(simulation.calculate(variable, period=year)) for variable in variables) + return simulation.calculate(variable, period=year)[0] except Exception as error: - total = 0.00 - return to_roundedup_number(total) + print(error) + return False + + +def simulate_multiple(simulation, variables, year, state): + try: + return to_roundedup_number( + sum( + sum(simulation.calculate(variable.replace("state", state), period=year)) + for variable in variables + ) + ) + except Exception as error: + print(error) + return 0.00 diff --git a/resources/taxsim35/input-all-states-married.csv b/resources/taxsim35/input-all-states-married.csv new file mode 100644 index 0000000..002b027 --- /dev/null +++ b/resources/taxsim35/input-all-states-married.csv @@ -0,0 +1,52 @@ +taxsimid,state,mstat,year,ltcg,mtr,idtl,pwages,swages +1,1,2,2023,100000,11,2,100000,80000 +2,2,2,2023,100000,11,2,100000,80000 +3,3,2,2023,100000,11,2,100000,80000 +4,4,2,2023,100000,11,2,100000,80000 +5,5,2,2023,100000,11,2,100000,80000 +6,6,2,2023,100000,11,2,100000,80000 +7,7,2,2023,100000,11,2,100000,80000 +8,8,2,2023,100000,11,2,100000,80000 +9,9,2,2023,100000,11,2,100000,80000 +10,10,2,2023,100000,11,2,100000,80000 +11,11,2,2023,100000,11,2,100000,80000 +12,12,2,2023,100000,11,2,100000,80000 +13,13,2,2023,100000,11,2,100000,80000 +14,14,2,2023,100000,11,2,100000,80000 +15,15,2,2023,100000,11,2,100000,80000 +16,16,2,2023,100000,11,2,100000,80000 +17,17,2,2023,100000,11,2,100000,80000 +18,18,2,2023,100000,11,2,100000,80000 +19,19,2,2023,100000,11,2,100000,80000 +20,20,2,2023,100000,11,2,100000,80000 +21,21,2,2023,100000,11,2,100000,80000 +22,22,2,2023,100000,11,2,100000,80000 +23,23,2,2023,100000,11,2,100000,80000 +24,24,2,2023,100000,11,2,100000,80000 +25,25,2,2023,100000,11,2,100000,80000 +26,26,2,2023,100000,11,2,100000,80000 +27,27,2,2023,100000,11,2,100000,80000 +28,28,2,2023,100000,11,2,100000,80000 +29,29,2,2023,100000,11,2,100000,80000 +30,30,2,2023,100000,11,2,100000,80000 +31,31,2,2023,100000,11,2,100000,80000 +32,32,2,2023,100000,11,2,100000,80000 +33,33,2,2023,100000,11,2,100000,80000 +34,34,2,2023,100000,11,2,100000,80000 +35,35,2,2023,100000,11,2,100000,80000 +36,36,2,2023,100000,11,2,100000,80000 +37,37,2,2023,100000,11,2,100000,80000 +38,38,2,2023,100000,11,2,100000,80000 +39,39,2,2023,100000,11,2,100000,80000 +40,40,2,2023,100000,11,2,100000,80000 +41,41,2,2023,100000,11,2,100000,80000 +42,42,2,2023,100000,11,2,100000,80000 +43,43,2,2023,100000,11,2,100000,80000 +44,44,2,2023,100000,11,2,100000,80000 +45,45,2,2023,100000,11,2,100000,80000 +46,46,2,2023,100000,11,2,100000,80000 +47,47,2,2023,100000,11,2,100000,80000 +48,48,2,2023,100000,11,2,100000,80000 +49,49,2,2023,100000,11,2,100000,80000 +50,50,2,2023,100000,11,2,100000,80000 +51,51,2,2023,100000,11,2,100000,80000 diff --git a/resources/taxsim35/input-all-states-single.csv b/resources/taxsim35/input-all-states-single.csv new file mode 100644 index 0000000..e0b63f2 --- /dev/null +++ b/resources/taxsim35/input-all-states-single.csv @@ -0,0 +1,52 @@ +taxsimid,state,mstat,year,ltcg,mtr,idtl +1,1,1,2023,100000,11,2 +2,2,1,2023,100000,11,2 +3,3,1,2023,100000,11,2 +4,4,1,2023,100000,11,2 +5,5,1,2023,100000,11,2 +6,6,1,2023,100000,11,2 +7,7,1,2023,100000,11,2 +8,8,1,2023,100000,11,2 +9,9,1,2023,100000,11,2 +10,10,1,2023,100000,11,2 +11,11,1,2023,100000,11,2 +12,12,1,2023,100000,11,2 +13,13,1,2023,100000,11,2 +14,14,1,2023,100000,11,2 +15,15,1,2023,100000,11,2 +16,16,1,2023,100000,11,2 +17,17,1,2023,100000,11,2 +18,18,1,2023,100000,11,2 +19,19,1,2023,100000,11,2 +20,20,1,2023,100000,11,2 +21,21,1,2023,100000,11,2 +22,22,1,2023,100000,11,2 +23,23,1,2023,100000,11,2 +24,24,1,2023,100000,11,2 +25,25,1,2023,100000,11,2 +26,26,1,2023,100000,11,2 +27,27,1,2023,100000,11,2 +28,28,1,2023,100000,11,2 +29,29,1,2023,100000,11,2 +30,30,1,2023,100000,11,2 +31,31,1,2023,100000,11,2 +32,32,1,2023,100000,11,2 +33,33,1,2023,100000,11,2 +34,34,1,2023,100000,11,2 +35,35,1,2023,100000,11,2 +36,36,1,2023,100000,11,2 +37,37,1,2023,100000,11,2 +38,38,1,2023,100000,11,2 +39,39,1,2023,100000,11,2 +40,40,1,2023,100000,11,2 +41,41,1,2023,100000,11,2 +42,42,1,2023,100000,11,2 +43,43,1,2023,100000,11,2 +44,44,1,2023,100000,11,2 +45,45,1,2023,100000,11,2 +46,46,1,2023,100000,11,2 +47,47,1,2023,100000,11,2 +48,48,1,2023,100000,11,2 +49,49,1,2023,100000,11,2 +50,50,1,2023,100000,11,2 +51,51,1,2023,100000,11,2