Skip to content

Commit e6a009b

Browse files
committed
Add PUF-native challenger donor conditioning
1 parent 74553a8 commit e6a009b

11 files changed

Lines changed: 1073 additions & 49 deletions

docs/imputation-conditioning-contract.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ The current donor-conditioning modes are:
5757
- use a PE-style structural predictor backbone declared in variable semantics
5858
- optionally admit a narrow supplemental shared set from the *actual*
5959
compatible overlap
60+
- `pe_plus_puf_native_challenger`
61+
- keep the same PE structural predictor backbone
62+
- for the explicitly marked problematic PUF tax-leaf blocks only, append a
63+
narrow source-native raw-overlap set declared in semantics
64+
- treat this as a non-default challenger lane, not as part of the PE-aligned
65+
contract
6066

6167
For the current PUF IRS tax-leaf family, PE alignment means the structural-only
6268
path. The local `policyengine-us-data`
@@ -85,6 +91,8 @@ Experimental:
8591

8692
- whether `all_shared`, `top_correlated`, or `pe_prespecified` wins for a given
8793
block family
94+
- whether `pe_plus_puf_native_challenger` is worth keeping after a real
95+
checkpoint comparison
8896
- whether a particular variable should admit a
8997
`supplemental_shared_condition_vars` set
9098
- which compatible shared predictors should be let back into a PE-structured

docs/methodology-ledger.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1813,3 +1813,50 @@ canonical description is:
18131813
conditioning
18141814
- treat any future widening as an explicit challenger experiment using
18151815
source-native PUF predictors, not as a PE-alignment patch
1816+
1817+
## 2026-04-14 PUF native challenger diagnostic smoke
1818+
1819+
- run:
1820+
- `artifacts/live_pe_us_data_rebuild_checkpoint_20260414_pe_plus_puf_native_challenger_diag_smoke/puf-native-challenger-diag-smoke-v1`
1821+
- question:
1822+
- if we add an explicit non-default challenger lane that keeps the PE
1823+
structural backbone but appends a narrow source-native PUF overlap, do
1824+
those vars actually enter the four problematic tax-leaf blocks on a live
1825+
artifact?
1826+
- setup:
1827+
- `donor_imputer_condition_selection = pe_plus_puf_native_challenger`
1828+
- keep the PE structural predictors for the PUF IRS tax-leaf family
1829+
- append only explicit source-native challengers:
1830+
- dividend / taxable-interest blocks:
1831+
`self_employment_income`, `rental_income`,
1832+
`social_security_retirement`
1833+
- taxable-pension block:
1834+
`social_security_retirement`, `social_security_disability`,
1835+
`unemployment_compensation`
1836+
- partnership block:
1837+
`self_employment_income`, `rental_income`, `alimony_income`
1838+
- read:
1839+
- the challenger vars now enter the live artifact for all four targeted
1840+
blocks
1841+
- selected sets were:
1842+
- dividend split:
1843+
PE structural backbone + `self_employment_income`, `rental_income`,
1844+
`social_security_retirement`
1845+
- `taxable_interest_income`:
1846+
PE structural backbone + `self_employment_income`, `rental_income`,
1847+
`social_security_retirement`
1848+
- `taxable_pension_income`:
1849+
PE structural backbone + `social_security_retirement`,
1850+
`social_security_disability`, `unemployment_compensation`
1851+
- `partnership_s_corp_income`:
1852+
PE structural backbone + `self_employment_income`, `rental_income`
1853+
while `alimony_income` failed with `incompatible_condition_support`
1854+
- interpretation:
1855+
- this clears the immediate blocker from the earlier failed supplement patch:
1856+
we now have a real opt-in challenger lane whose native PUF predictors are
1857+
visible in live `donor_conditioning_diagnostics`
1858+
- the next real question is no longer "can the vars get in?" but "does this
1859+
challenger help or hurt the PE-oracle losses once we run a full checkpoint"
1860+
- next step:
1861+
- run one matched broader checkpoint with this challenger mode and compare it
1862+
against the structural-only PE-aligned default

docs/source-semantics.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ two distinct selection modes:
8383
backbone
8484
- optionally admit a narrow `supplemental_shared_condition_vars` set from the
8585
actual shared overlap, instead of reopening the full common-predictor pool
86+
- `pe_plus_puf_native_challenger` selection:
87+
- keep the same PE structural backbone
88+
- for the explicitly marked problematic PUF tax-leaf blocks only, append a
89+
narrow set of source-native raw-overlap predictors declared in semantics
90+
- treat that lane as an opt-in challenger, not as a PE-alignment update
8691

8792
For the problematic PUF tax-leaf family, the PE-aligned default is still the
8893
structural backbone only. The local `policyengine-us-data`
@@ -124,8 +129,11 @@ executed donor block, including:
124129
- selected condition vars
125130
- shared vars that were available but dropped
126131
- requested supplemental shared vars
132+
- requested challenger shared vars
127133
- raw-stage supplemental rejection reasons
134+
- raw-stage challenger rejection reasons
128135
- prepared-stage supplemental rejection reasons
136+
- prepared-stage challenger rejection reasons
129137
- whether the block used a prepared condition surface
130138

131139
Use `python -m microplex_us.pipelines.summarize_donor_conditioning <artifact>`

src/microplex_us/pipelines/pe_us_data_rebuild_checkpoint.py

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import argparse
66
import json
7+
import logging
8+
import sys
79
from dataclasses import dataclass, replace
810
from datetime import UTC, datetime
911
from pathlib import Path
@@ -67,6 +69,23 @@
6769

6870
DEFAULT_CHECKPOINT_IMPUTATION_ABLATION_EVAL_FRACTION = 0.25
6971
MIN_CHECKPOINT_IMPUTATION_ABLATION_HOUSEHOLDS = 8
72+
LOGGER = logging.getLogger(__name__)
73+
74+
75+
def _root_logger_has_handlers() -> bool:
76+
return bool(logging.getLogger().handlers)
77+
78+
79+
def _emit_checkpoint_progress(message: str, /, **context: object) -> None:
80+
details = ", ".join(
81+
f"{key}={value}"
82+
for key, value in context.items()
83+
if value is not None and value != ""
84+
)
85+
line = f"{message} [{details}]" if details else message
86+
LOGGER.info(line)
87+
if not LOGGER.handlers and not _root_logger_has_handlers():
88+
print(line, file=sys.stderr, flush=True)
7089

7190

7291
def _resolve_checkpoint_calibration_target_variables(
@@ -1865,6 +1884,14 @@ def run_policyengine_us_data_rebuild_checkpoint(
18651884
"rebuild_profile_expected": True,
18661885
**dict(run_registry_metadata or {}),
18671886
}
1887+
_emit_checkpoint_progress(
1888+
"PE-US-data rebuild checkpoint: starting build",
1889+
output_root=Path(output_root).expanduser(),
1890+
version_id=version_id or "auto",
1891+
target_profile=resolved_config.policyengine_target_profile,
1892+
donor_condition_selection=resolved_config.donor_imputer_condition_selection,
1893+
providers=",".join(provider_names),
1894+
)
18681895

18691896
artifacts = build_and_save_versioned_us_microplex_from_source_providers(
18701897
providers=list(resolved_providers),
@@ -1889,6 +1916,19 @@ def run_policyengine_us_data_rebuild_checkpoint(
18891916
run_registry_metadata=resolved_registry_metadata,
18901917
enable_child_tax_unit_agi_drift=True,
18911918
)
1919+
_emit_checkpoint_progress(
1920+
"PE-US-data rebuild checkpoint: build complete",
1921+
artifact_dir=artifacts.artifact_paths.output_dir,
1922+
frontier_metric=frontier_metric,
1923+
)
1924+
_emit_checkpoint_progress(
1925+
"PE-US-data rebuild checkpoint: attaching PE evidence",
1926+
artifact_dir=artifacts.artifact_paths.output_dir,
1927+
compute_harness=not defer_policyengine_harness,
1928+
compute_native_scores=not defer_policyengine_native_score,
1929+
compute_native_audit=not defer_native_audit,
1930+
compute_imputation_ablation=not defer_imputation_ablation,
1931+
)
18921932
evidence = attach_policyengine_us_data_rebuild_checkpoint_evidence(
18931933
artifacts.artifact_paths.output_dir,
18941934
build_result=artifacts.build_result,
@@ -1912,11 +1952,21 @@ def run_policyengine_us_data_rebuild_checkpoint(
19121952
run_index_path=run_index_path,
19131953
run_registry_metadata=resolved_registry_metadata,
19141954
)
1955+
_emit_checkpoint_progress(
1956+
"PE-US-data rebuild checkpoint: evidence complete",
1957+
parity_path=evidence.parity_path,
1958+
native_audit_path=evidence.native_audit_path,
1959+
imputation_ablation_path=evidence.imputation_ablation_path,
1960+
)
19151961
refreshed_artifacts = _load_checkpoint_versioned_artifacts(
19161962
build_result=artifacts.build_result,
19171963
artifact_root=artifacts.artifact_paths.output_dir,
19181964
frontier_metric=frontier_metric,
19191965
)
1966+
_emit_checkpoint_progress(
1967+
"PE-US-data rebuild checkpoint: checkpoint ready",
1968+
artifact_dir=refreshed_artifacts.artifact_paths.output_dir,
1969+
)
19201970
return PEUSDataRebuildCheckpointResult(
19211971
build_config=resolved_config,
19221972
provider_names=provider_names,
@@ -1948,6 +1998,7 @@ def main(argv: list[str] | None = None) -> None:
19481998
parser.add_argument("--calibration-target-profile")
19491999
parser.add_argument("--n-synthetic", type=int, default=100_000)
19502000
parser.add_argument("--random-seed", type=int, default=42)
2001+
parser.add_argument("--donor-imputer-condition-selection")
19512002
parser.add_argument("--cps-source-year", type=int, default=2023)
19522003
parser.add_argument("--puf-target-year", type=int)
19532004
parser.add_argument("--puf-cps-reference-year", type=int)
@@ -1983,6 +2034,15 @@ def main(argv: list[str] | None = None) -> None:
19832034
parser.add_argument("--require-policyengine-native-score", action="store_true")
19842035
args = parser.parse_args(argv)
19852036

2037+
config_overrides = {
2038+
"n_synthetic": int(args.n_synthetic),
2039+
"random_seed": int(args.random_seed),
2040+
}
2041+
if args.donor_imputer_condition_selection is not None:
2042+
config_overrides["donor_imputer_condition_selection"] = (
2043+
args.donor_imputer_condition_selection
2044+
)
2045+
19862046
result = run_policyengine_us_data_rebuild_checkpoint(
19872047
output_root=args.output_root,
19882048
policyengine_baseline_dataset=args.baseline_dataset,
@@ -1996,10 +2056,7 @@ def main(argv: list[str] | None = None) -> None:
19962056
calibration_target_variables=tuple(args.calibration_target_variable),
19972057
calibration_target_domains=tuple(args.calibration_target_domain),
19982058
calibration_target_geo_levels=tuple(args.calibration_target_geo_level),
1999-
config_overrides={
2000-
"n_synthetic": int(args.n_synthetic),
2001-
"random_seed": int(args.random_seed),
2002-
},
2059+
config_overrides=config_overrides,
20032060
cps_source_year=args.cps_source_year,
20042061
cps_cache_dir=args.cps_cache_dir,
20052062
cps_download=not args.no_cps_download,

src/microplex_us/pipelines/summarize_donor_conditioning.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
import argparse
66
import json
77
from collections import Counter
8+
from collections.abc import Iterable
89
from pathlib import Path
9-
from typing import Any, Iterable
10+
from typing import Any
1011

1112

1213
def _resolve_artifact_dir(path: str | Path) -> Path:
@@ -50,6 +51,8 @@ def summarize_donor_conditioning(
5051
dropped_counter: Counter[str] = Counter()
5152
raw_supplemental_reason_counter: Counter[str] = Counter()
5253
supplemental_reason_counter: Counter[str] = Counter()
54+
raw_challenger_reason_counter: Counter[str] = Counter()
55+
challenger_reason_counter: Counter[str] = Counter()
5356
block_summaries: list[dict[str, Any]] = []
5457
for entry in diagnostics:
5558
selected = list(entry.get("selected_condition_vars", []))
@@ -60,6 +63,12 @@ def summarize_donor_conditioning(
6063
supplemental_status = list(
6164
entry.get("supplemental_shared_condition_var_status", [])
6265
)
66+
raw_challenger_status = list(
67+
entry.get("raw_challenger_shared_condition_var_status", [])
68+
)
69+
challenger_status = list(
70+
entry.get("challenger_shared_condition_var_status", [])
71+
)
6372
selected_counter.update(selected)
6473
dropped_counter.update(dropped)
6574
raw_supplemental_reason_counter.update(
@@ -72,6 +81,16 @@ def summarize_donor_conditioning(
7281
for status in supplemental_status
7382
if status.get("reason") is not None
7483
)
84+
raw_challenger_reason_counter.update(
85+
status.get("reason")
86+
for status in raw_challenger_status
87+
if status.get("reason") is not None
88+
)
89+
challenger_reason_counter.update(
90+
status.get("reason")
91+
for status in challenger_status
92+
if status.get("reason") is not None
93+
)
7594
block_summaries.append(
7695
{
7796
"donor_source": entry.get("donor_source"),
@@ -92,8 +111,13 @@ def summarize_donor_conditioning(
92111
"requested_supplemental_shared_condition_vars": list(
93112
entry.get("requested_supplemental_shared_condition_vars", [])
94113
),
114+
"requested_challenger_shared_condition_vars": list(
115+
entry.get("requested_challenger_shared_condition_vars", [])
116+
),
95117
"raw_supplemental_shared_condition_var_status": raw_supplemental_status,
118+
"raw_challenger_shared_condition_var_status": raw_challenger_status,
96119
"supplemental_shared_condition_var_status": supplemental_status,
120+
"challenger_shared_condition_var_status": challenger_status,
97121
"selected_condition_vars": selected,
98122
"dropped_shared_vars": dropped,
99123
}
@@ -108,9 +132,15 @@ def summarize_donor_conditioning(
108132
"raw_supplemental_shared_condition_reason_frequency": dict(
109133
sorted(raw_supplemental_reason_counter.items())
110134
),
135+
"raw_challenger_shared_condition_reason_frequency": dict(
136+
sorted(raw_challenger_reason_counter.items())
137+
),
111138
"supplemental_shared_condition_reason_frequency": dict(
112139
sorted(supplemental_reason_counter.items())
113140
),
141+
"challenger_shared_condition_reason_frequency": dict(
142+
sorted(challenger_reason_counter.items())
143+
),
114144
"blocks": block_summaries,
115145
}
116146

0 commit comments

Comments
 (0)