Skip to content

Commit 1bbfc33

Browse files
committed
Merge remote-tracking branch 'upstream/main' into codex/release-manifest
2 parents a8d469d + d6ac877 commit 1bbfc33

21 files changed

Lines changed: 1669 additions & 59 deletions

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
## [1.78.1] - 2026-04-12
2+
3+
### Changed
4+
5+
- Add SSTB QBI split inputs to `policyengine-us-data` by exposing
6+
`sstb_self_employment_income`, `sstb_w2_wages_from_qualified_business`, and
7+
`sstb_unadjusted_basis_qualified_property` from the existing PUF/calibration
8+
pipeline. The current split follows the legacy all-or-nothing
9+
`business_is_sstb` flag, so mixed SSTB/non-SSTB allocations remain approximate
10+
until more granular source data or imputation is added.
11+
12+
13+
## [1.78.0] - 2026-04-12
14+
15+
### Added
16+
17+
- Add comparison-mode CTC diagnostics to `validate_national_h5`, including child-count and child-age drift reporting between national artifacts.
18+
19+
120
## [1.77.0] - 2026-04-10
221

322
### Added

docs/appendix.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,19 @@ for iteration in range(5000):
112112
- w2_wages_from_qualified_business
113113
- unadjusted_basis_qualified_property
114114
- business_is_sstb
115+
- sstb_self_employment_income
116+
- sstb_w2_wages_from_qualified_business
117+
- sstb_unadjusted_basis_qualified_property
115118
- qualified_reit_and_ptp_income
116119
- qualified_bdc_income
117120
- farm_operations_income
118121
- estate_income_would_be_qualified
119122
- farm_operations_income_would_be_qualified
120123
- farm_rent_income_would_be_qualified
124+
125+
The current PUF/calibration pipeline uses the legacy `business_is_sstb` flag to
126+
split these SSTB variables on an all-or-nothing basis. It does not yet infer
127+
mixed SSTB and non-SSTB allocations within the same record.
121128
- partnership_s_corp_income_would_be_qualified
122129
- rental_income_would_be_qualified
123130
- self_employment_income_would_be_qualified

policyengine_us_data/__init__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from importlib import import_module
22

33
from .geography import ZIP_CODE_DATASET
4+
from .utils.policyengine import ensure_policyengine_us_compat_variables
5+
6+
ensure_policyengine_us_compat_variables()
47

58
_LAZY_EXPORTS = {
69
"CPS_2024": (
@@ -26,7 +29,16 @@
2629

2730
def __getattr__(name: str):
2831
if name not in _LAZY_EXPORTS:
29-
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
32+
try:
33+
value = import_module(f"{__name__}.{name}")
34+
except ModuleNotFoundError as exc:
35+
if exc.name == f"{__name__}.{name}":
36+
raise AttributeError(
37+
f"module {__name__!r} has no attribute {name!r}"
38+
) from exc
39+
raise
40+
globals()[name] = value
41+
return value
3042

3143
module_name, attribute_name = _LAZY_EXPORTS[name]
3244
value = getattr(import_module(module_name), attribute_name)

policyengine_us_data/calibration/check_staging_sums.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
VARIABLES = [
2424
"adjusted_gross_income",
2525
"employment_income",
26-
"self_employment_income",
26+
"total_self_employment_income",
2727
"tax_unit_partnership_s_corp_income",
2828
"taxable_pension_income",
2929
"dividend_income",

policyengine_us_data/calibration/puf_impute.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,12 @@
5050
"pre_tax_contributions",
5151
"taxable_ira_distributions",
5252
"self_employment_income",
53+
"sstb_self_employment_income",
5354
"w2_wages_from_qualified_business",
5455
"unadjusted_basis_qualified_property",
5556
"business_is_sstb",
57+
"sstb_w2_wages_from_qualified_business",
58+
"sstb_unadjusted_basis_qualified_property",
5659
"short_term_capital_gains",
5760
"qualified_dividend_income",
5861
"charitable_cash_donations",
@@ -122,6 +125,8 @@
122125
"w2_wages_from_qualified_business",
123126
"unadjusted_basis_qualified_property",
124127
"business_is_sstb",
128+
"sstb_w2_wages_from_qualified_business",
129+
"sstb_unadjusted_basis_qualified_property",
125130
"charitable_cash_donations",
126131
"self_employed_pension_contribution_ald",
127132
"unrecaptured_section_1250_gain",
@@ -693,6 +698,11 @@ def _impute_retirement_contributions(
693698
X_test[income_var] = puf_imputations[income_var]
694699
else:
695700
X_test[income_var] = cps_sim.calculate(income_var).values
701+
if "sstb_self_employment_income" in puf_imputations:
702+
X_test["self_employment_income"] = (
703+
X_test["self_employment_income"]
704+
+ puf_imputations["sstb_self_employment_income"]
705+
)
696706

697707
del cps_sim
698708

@@ -723,13 +733,13 @@ def _impute_retirement_contributions(
723733
catch_up_eligible = age >= 50
724734
limit_401k = limits["401k"] + catch_up_eligible * limits["401k_catch_up"]
725735
limit_ira = limits["ira"] + catch_up_eligible * limits["ira_catch_up"]
736+
se_income = X_test["self_employment_income"].values
726737
se_pension_cap = np.minimum(
727-
X_test["self_employment_income"].values * limits["se_pension_rate"],
738+
se_income * limits["se_pension_rate"],
728739
limits["se_pension_dollar_limit"],
729740
)
730741

731742
emp_income = X_test["employment_income"].values
732-
se_income = X_test["self_employment_income"].values
733743

734744
result = {}
735745
for var in CPS_RETIREMENT_VARIABLES:

policyengine_us_data/calibration/target_config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ include:
2222
geo_level: district
2323
- variable: real_estate_taxes
2424
geo_level: district
25-
- variable: self_employment_income
25+
- variable: total_self_employment_income
2626
geo_level: district
2727
- variable: taxable_pension_income
2828
geo_level: district
@@ -163,9 +163,9 @@ include:
163163
- variable: non_refundable_ctc
164164
geo_level: national
165165
domain_variable: adjusted_gross_income,non_refundable_ctc
166-
- variable: self_employment_income
166+
- variable: total_self_employment_income
167167
geo_level: national
168-
domain_variable: self_employment_income
168+
domain_variable: total_self_employment_income
169169
- variable: tax_unit_partnership_s_corp_income
170170
geo_level: national
171171
domain_variable: tax_unit_partnership_s_corp_income
@@ -199,7 +199,7 @@ include:
199199
# Restore old loss.py's self-employment return-count target.
200200
- variable: tax_unit_count
201201
geo_level: national
202-
domain_variable: self_employment_income
202+
domain_variable: total_self_employment_income
203203

204204
# === NATIONAL — identity / population count targets from old loss.py ===
205205
- variable: person_count

0 commit comments

Comments
 (0)