|
56 | 56 | from policyengine_us_data.utils.dataset_validation import ( |
57 | 57 | assert_no_computed_policyengine_us_variables_exported, |
58 | 58 | ) |
59 | | -from policyengine_us_data.utils.retirement_limits import ( |
60 | | - get_retirement_limits, |
61 | | - get_se_pension_limits, |
62 | | -) |
63 | 59 | from policyengine_us_data.utils.randomness import seeded_rng |
64 | 60 |
|
65 | 61 | logger = logging.getLogger(__name__) |
@@ -154,11 +150,11 @@ def _supports_structural_mortgage_inputs() -> bool: |
154 | 150 | "taxable_sep_distributions", |
155 | 151 | "tax_exempt_sep_distributions", |
156 | 152 | # Retirement contributions |
157 | | - "traditional_401k_contributions", |
158 | | - "roth_401k_contributions", |
159 | | - "traditional_ira_contributions", |
160 | | - "roth_ira_contributions", |
161 | | - "self_employed_pension_contributions", |
| 153 | + "traditional_401k_contributions_desired", |
| 154 | + "roth_401k_contributions_desired", |
| 155 | + "traditional_ira_contributions_desired", |
| 156 | + "roth_ira_contributions_desired", |
| 157 | + "self_employed_pension_contributions_desired", |
162 | 158 | # Social Security sub-components |
163 | 159 | "social_security_retirement", |
164 | 160 | "social_security_disability", |
@@ -753,50 +749,34 @@ def _impute_cps_only_variables( |
753 | 749 |
|
754 | 750 |
|
755 | 751 | def apply_retirement_constraints(predictions, X_test, time_period): |
756 | | - """Enforce IRS contribution limits on retirement variable predictions. |
| 752 | + """Clean retirement contribution predictions for data-domain eligibility. |
757 | 753 |
|
758 | 754 | Args: |
759 | 755 | predictions: DataFrame of QRF predictions for retirement |
760 | 756 | contribution variables. |
761 | 757 | X_test: DataFrame with at least ``age``, |
762 | 758 | ``employment_income``, and ``self_employment_income``. |
763 | | - time_period: Tax year (int) for IRS limit look-up. |
| 759 | + time_period: Tax year (int), accepted for API compatibility. |
764 | 760 |
|
765 | 761 | Returns: |
766 | | - DataFrame with constrained values (same columns). |
| 762 | + DataFrame with cleaned values (same columns). |
767 | 763 | """ |
768 | | - limits = get_retirement_limits(time_period) |
769 | | - se_limits = get_se_pension_limits(time_period) |
770 | | - |
771 | | - age = X_test["age"].values |
772 | | - catch_up = age >= 50 |
773 | 764 | emp_income = X_test["employment_income"].values |
774 | 765 | se_income = X_test["self_employment_income"].values |
775 | 766 |
|
776 | | - limit_401k = limits["401k"] + catch_up * limits["401k_catch_up"] |
777 | | - limit_ira = limits["ira"] + catch_up * limits["ira_catch_up"] |
778 | | - se_pension_cap = np.minimum( |
779 | | - se_income * se_limits["se_pension_rate"], |
780 | | - se_limits["se_pension_dollar_limit"], |
781 | | - ) |
782 | | - |
783 | | - # Explicit mapping: variable -> (cap array, zero_mask or None). |
| 767 | + # Explicit mapping: variable -> zero_mask or None. Statutory limits |
| 768 | + # are applied by PolicyEngine-US plain contribution variables. |
784 | 769 | _CONSTRAINT_MAP = { |
785 | | - "traditional_401k_contributions": (limit_401k, emp_income == 0), |
786 | | - "roth_401k_contributions": (limit_401k, emp_income == 0), |
787 | | - "traditional_ira_contributions": (limit_ira, None), |
788 | | - "roth_ira_contributions": (limit_ira, None), |
789 | | - "self_employed_pension_contributions": ( |
790 | | - se_pension_cap, |
791 | | - se_income == 0, |
792 | | - ), |
| 770 | + "traditional_401k_contributions_desired": emp_income == 0, |
| 771 | + "roth_401k_contributions_desired": emp_income == 0, |
| 772 | + "traditional_ira_contributions_desired": None, |
| 773 | + "roth_ira_contributions_desired": None, |
| 774 | + "self_employed_pension_contributions_desired": se_income == 0, |
793 | 775 | } |
794 | 776 |
|
795 | 777 | result = predictions.clip(lower=0) |
796 | 778 | for var in result.columns: |
797 | | - cap, zero_mask = _CONSTRAINT_MAP.get(var, (None, None)) |
798 | | - if cap is not None: |
799 | | - result[var] = np.minimum(result[var].values, cap) |
| 779 | + zero_mask = _CONSTRAINT_MAP.get(var) |
800 | 780 | if zero_mask is not None: |
801 | 781 | result.loc[zero_mask, var] = 0 |
802 | 782 |
|
@@ -836,11 +816,11 @@ def reconcile_ss_subcomponents(predictions, total_ss): |
836 | 816 |
|
837 | 817 |
|
838 | 818 | _RETIREMENT_VARS = { |
839 | | - "traditional_401k_contributions", |
840 | | - "roth_401k_contributions", |
841 | | - "traditional_ira_contributions", |
842 | | - "roth_ira_contributions", |
843 | | - "self_employed_pension_contributions", |
| 819 | + "traditional_401k_contributions_desired", |
| 820 | + "roth_401k_contributions_desired", |
| 821 | + "traditional_ira_contributions_desired", |
| 822 | + "roth_ira_contributions_desired", |
| 823 | + "self_employed_pension_contributions_desired", |
844 | 824 | } |
845 | 825 |
|
846 | 826 | _SS_SUBCOMPONENT_VARS = { |
|
0 commit comments