Add CPS-derived recodes: unmarried-partner, SSTB-QBI flag, ESI (G6/G8/G9)#127
Merged
Conversation
This was referenced Jun 1, 2026
…/G9)
Close four eCPS export-parity gaps so Microplex exports what the Enhanced
CPS exports. All four columns are REQUIRED by the frozen eCPS export
contract (src/microplex_us/pipelines/ecps_export_contract.json).
G8 is_unmarried_partner_of_household_head
Real recode of ASEC PERRP: codes {43,44,46,47} mark an unmarried partner
of the household head. Mirrors policyengine-us-data cps.py:190-195,:1219
(perrp.isin(PERRP_UNMARRIED_PARTNER_OF_HOUSEHOLD_HEAD_CODES)). Maps PERRP
in PERSON_VARIABLES, computes the bool leaf in _process_persons, adds it
to SAFE_POLICYENGINE_US_EXPORT_VARIABLES. Storable pe-us INPUT
(def_formula=0 in pinned pe-us 1.715.3). On the real 2023 CPS: 3,889 True.
G6 employer-sponsored insurance (two leaves)
- reported_owns_employer_sponsored_health_insurance_at_interview: real
recode NOW_OWNGRP == 1 (eCPS cps.py:1576-1578). Not (yet) a released
pe-us input variable, so its export entity is pinned in
POLICYENGINE_US_LEGACY_CONTRACT_VARIABLE_ENTITIES (person), exactly like
its reported_has_* siblings, to keep it on the eCPS-parity surface.
On the real 2023 CPS: 39,838 True.
- employer_sponsored_insurance_premiums: reproduces eCPS
impute_employer_sponsored_insurance_premiums (cps.py:229-273) verbatim
on the renamed CPS columns (NOW_OWNGRP/NOW_HIPAID/NOW_GRPFTYP/PHIP_VAL)
plus the MEPS-IC Table IV.A.1 (2024) plan-type priors. The priors are
constants eCPS itself hardcodes (not external data). Output matches the
eCPS reference function bit-for-bit on its own unit-test fixture.
Storable pe-us INPUT (def_formula=0 in pinned pe-us 1.715.3).
G9 sstb_self_employment_income_would_be_qualified
eCPS computes this in its PUF QBI simulation as
np.where(business_is_sstb, self_employment_income_would_be_qualified,
False) (policyengine-us-data puf.py:768). Microplex runs no PUF QBI
simulation and already exports business_is_sstb=False for every record,
so that expression collapses to a constant False. Exported as a constant
False default in POLICYENGINE_US_EXPORT_DEFAULTS -- the exact value eCPS
emits given MP's SSTB inputs, and more correct than the pe-us
default_value=True (which would wrongly qualify all SE income).
Consistent with MP's existing business_is_sstb/QBI-flag default treatment.
def_formula check (pinned pe-us 1.715.3, the eCPS pin): all storable INPUTs
(0 formulas/adds/subtracts), so plain-allowlist / plain-default is correct;
no POLICYENGINE_US_DATA_OVERRIDABLE_COMPUTED_EXPORT_VARIABLES entry needed.
Tests: tests/data_sources/test_cps_employer_insurance_and_partner.py (14
tests) exercises the real _process_persons (no stubbing), cross-checks the
ESI premium against the eCPS reference fixture, and asserts the export-config
wiring. Full tests/data_sources/ suite passes (23). No new failures elsewhere
(pre-existing microunit/pe-us import errors in this env are unchanged).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Documentation-only fixes from cycle review (no behavior change; comment and docstring text only): - Remove the fabricated G9 narrative. The claimed eCPS expression np.where(business_is_sstb, ..., False) at "puf.py:768" never existed (puf.py is 753 lines and the flag is never recoded in eCPS). Restate accurately: eCPS exports the pe-us default (True); Microplex exports False, which is tax-inert because Microplex carries no SSTB self-employment income, and which passes the name-only column-parity gate. - Fix the wrong ESI line citations (cps.py:1576-1581 / :229-273): cite the eCPS symbols on the unmerged max/esi-premiums-cbo branch (the NOW_OWNGRP == 1 flag and impute_employer_sponsored_insurance_premiums) instead. - Cite the unmarried-partner PERRP recode by branch/symbol rather than a fragile line number on an unmerged branch. - Correct the pinned pe-us version string 1.715.3 -> 1.715.2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
50e672b to
241f398
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes four eCPS export-parity gaps so Microplex exports what the Enhanced CPS exports. All four columns are REQUIRED by the frozen eCPS export contract (
src/microplex_us/pipelines/ecps_export_contract.json); none are forbidden.Follows the proven G7 (#125) /
social_security_retirement(#121) pattern: map the raw Census field inPERSON_VARIABLES(or compute the leaf in_process_persons), add the leaf to the export surface, and test against the real_process_persons.Per-column status
is_unmarried_partner_of_household_headpolicyengine-us-datacps.py:190-195,:1219—perrp.isin({43,44,46,47})reported_owns_employer_sponsored_health_insurance_at_interviewcps.py:1576-1578—NOW_OWNGRP == 1employer_sponsored_insurance_premiumscps.py:229-273(impute_employer_sponsored_insurance_premiums) + MEPS-IC 2024 plan priorssstb_self_employment_income_would_be_qualifiedpuf.py:768—np.where(business_is_sstb, …, False)collapses toFalsegiven MP'sbusiness_is_sstb=FalseAll four columns are fully closed — none are backlogged. Every required raw ASEC field (
PERRP,NOW_OWNGRP,NOW_HIPAID,NOW_GRPFTYP,PHIP_VAL) is present in the CPS person file MP already downloads (verified against the cached 2023 ASEC: 829 columns, all five present, zero nulls).Implementation notes
PERSON_VARIABLES; the bool/float leaves are computed in_process_personsand the staging columns dropped. Leaves added toSAFE_POLICYENGINE_US_EXPORT_VARIABLES.reported_has_*siblings — its export entity is pinned inPOLICYENGINE_US_LEGACY_CONTRACT_VARIABLE_ENTITIES(person). The recode itself is real (NOW_OWNGRP == 1), so the column still carries true per-record values rather than a constant.business_is_sstb=Falsefor every record, so eCPS'snp.where(business_is_sstb, self_employment_income_would_be_qualified, False)isFalseeverywhere. Exported as a constantFalseinPOLICYENGINE_US_EXPORT_DEFAULTS— the exact value eCPS emits given MP's inputs, and more correct than the pe-usdefault_value=True(which would wrongly QBI-qualify all SE income). Consistent with MP's existing QBI-flag default treatment.def_formulacheck (pinned pe-us 1.715.3 — the eCPS pin)def formulais_unmarried_partner_of_household_heademployer_sponsored_insurance_premiumssstb_self_employment_income_would_be_qualifieddefault_value=True)False)reported_owns_employer_sponsored_health_insurance_at_interviewpersonentityAll pe-us-present leaves are storable INPUTs, so none needs a
POLICYENGINE_US_DATA_OVERRIDABLE_COMPUTED_EXPORT_VARIABLESentry.Tests
tests/data_sources/test_cps_employer_insurance_and_partner.py— 14 tests exercising the real_process_persons(no stubbing):personentity, G9 constantFalse, all four REQUIRED by the contractEnd-to-end on the real 146,133-record 2023 CPS: 3,889 unmarried partners; 39,838 ESI policyholders; 36,033 nonzero ESI premiums (mean ≈ $12,025, capped at the $21,207 family total) — all real, non-degenerate, mirroring eCPS.
Pre-existing
microunit/pe-us import failures in this environment are unchanged by this branch (verified: identical pass/fail counts onorigin/main).🤖 Generated with Claude Code