Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,20 @@ def us_nonzero_shares(
if requested is not None and column not in requested:
continue
shares[column] = nonzero_share(table[column])

# Frame weights are typed pipeline state, not ordinary table data. The US
# adapter materializes the authoritative vector as ``household_weight``
# when writing PolicyEngine H5, and the coverage gate mirrors that export
# behavior; mirror it here too so the parity universe measures what is
# persisted instead of reading the deliberately absent table column as an
# all-zero layer (the Build M sparse failure: the reference's finished
# export carries the column, the candidate keeps it as typed state).
if (
(requested is None or "household_weight" in requested)
and "household_weight" not in shares
and "household" in frame.weighted_entities
):
shares["household_weight"] = nonzero_share(
pd.Series(frame.weights_for("household").values)
)
return shares
28 changes: 28 additions & 0 deletions packages/populace-build/tests/test_us_nonzero_shares.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,31 @@ def test_column_restriction_filters_raw_source_columns(self) -> None:
)

assert set(shares) == {"student_loan_interest"}


class TestTypedWeightMirror:
def test_household_weight_measures_typed_frame_weights(self) -> None:
"""The parity universe measures weights as the export persists them.

Frame weights are typed pipeline state, not a table column; the H5
adapter materializes them as ``household_weight`` and the coverage
gate mirrors that. Build M's sparse run failed eCPS parity because
this module read the deliberately absent table column as an all-zero
layer while the reference's finished export carries it — the mirror
keeps the two gates and the export telling one story.
"""

shares = us_nonzero_shares(_frame(), columns=["household_weight"])
assert shares["household_weight"] == 1.0

def test_table_column_still_wins_when_present(self) -> None:
frame = _frame()
tables = {entity: frame.table(entity).copy() for entity in frame.entities}
tables["household"]["household_weight"] = [0.0, 5.0]
rebuilt = Frame(
tables,
frame.schema,
{entity: frame.weights_for(entity) for entity in frame.weighted_entities},
)
shares = us_nonzero_shares(rebuilt, columns=["household_weight"])
assert shares["household_weight"] == 0.5
Loading