Skip to content

ACS local release: supply source-backed SPM independence (is_spm_independent_minor_role) so every SPM unit has a classified adult #1263

Open

Description

@MaxGhenis

Problem

The certified ACS local-area release populace-us-2024-buildo-acs-local-767312d60-20260923T074941Z (populace_us_2024_acs_local.h5) has no source-backed SPM independence. policyengine.py has used it for US state and congressional-district regions since policyengine 6.2.2. Under canonical SPM (spm-calculator 1.0.0), many SPM units therefore have no classified adult, and every state-region simulation is refused with SPM_COMPOSITION_REQUIRED: "SPM unit has no classified adult: supply source-backed independence or household head/spouse structure".

That refusal blocks every PolicyEngine/policyengine-sim-api deploy past policyengine 6.2.1 (sim-api#747). Its beta verification runs three state/ut US tests. Production stays on 6.2.1 until this is fixed or worked around.

Rule (spm-calculator 1.0.0, spm_calculator/policyengine_adapter.py)

spm_measurement_adults = sum(age >= 18 | (age >= 15 & is_spm_independent_minor_role)). The role defaults to is_household_head | is_household_spouse unless it is supplied as an input. policyengine_amount refuses any unit with spm_measurement_adults < 1.

Evidence

Aggregates only. The columns were read from the release file over HTTP range requests.

ACS local (…acs-local-767312d60-20260923T074941Z) National (populace-us-2024-spm-20260915)
Persons 3,589,209 166,321
SPM units 1,591,514 59,900
is_spm_independent_minor_role column absent present
is_household_spouse column absent absent
is_household_head column present present
SPM units with no classified adult 5,274 (148 of them in Utah households) 0 (all 28 minors-only units have a role member aged 15+)

The national release passes end to end on the same packages: policyengine 6.2.7, policyengine-us 2.2.1, policyengine-core 3.33.0 and spm-calculator 1.0.0. Its 2026 year file computes SPM thresholds for all 59,900 units.

Ask

Rebuild the ACS local release with source-backed SPM independence, as the national release has (source_spm_person_independence, source_spm_independence_provenance). Every person should carry is_spm_independent_minor_role, plus head/spouse structure where the source supports it.

Acceptance: zero SPM units without a classified adult under the rule above, checked by the release gate. Then certify the new release in policyengine.py.

Interim

The UK Autumn Budget CI coordinator has queued a temporary policyengine.py revert for Max: state and congressional-district regions filter the national file, as in 6.2.1, until this lands. The full diagnosis is held by the W4 session (PolicyEngine/policyengine.py receipt and qualification workstream).

Activity

  1. MaxGhenis commented on Oct 13, 2026

    @MaxGhenis
    ContributorAuthor

    Existing work: branch us-acs-spm-independent-minor-role (pushed 10 Oct, no PR). Its commits are 319dec5 "WIP: derive the ACS SPM independence role and refuse default fills", a merge of main, and a8bc2fb "Test, document and validate the ACS SPM independence role". It is most of the fix. Against this issue's acceptance criterion (zero SPM units without a classified adult, checked by the release gate), I read the diff, not run it:

    What it does:

    • Derives the role for every ACS person. with_acs_spm_independence_role (microcosm/build/us_runtime/acs_inputs.py) treats each ACS household as one SPM unit and reads the role off RELSHIPP (reference person and spouse), following the ASEC rule. Its validation notes say this reproduces Census's SPM adult counts on the pinned ASEC files in every unit but one across three vintages (experiments/acs-spm-role-asec-validation.md).
    • Refuses a housing-unit SPM unit left with no classified adult, along with off-domain RELSHIPP, a missing age, and a unit with other than exactly one head.
    • Treats the role as a declared source input. tools/build_us_acs_local_release.py refuses a null or default-filled role (DeclaredSourceInputNullError) and says to rebuild the staging.

    What it still lacks for the criterion:

    1. Group-quarters children stay unclassified. A group-quarters person under 15 is a one-person SPM unit with no classified adult. The branch counts these units in its receipt instead of refusing them (docs/us-acs-spm-role.md, "Group-quarters children"), on the grounds that the source places group quarters outside the SPM universe. But spm-calculator 1.0.0's canonical SPM has no group-quarters exclusion, so each such unit would still raise SPM_COMPOSITION_REQUIRED at run time. The criterion needs one of three things:

      • wire the SPM measurement universe in, so these persons are excluded from SPM measurement in both the data and the runtime;
      • give them a classified adult;
      • refuse the release while any exist.

      Which of these is right is a design call.

    2. No release-level gate on the final artifact. The zero-adult refusal runs inside the ACS derivation, for housing units only. Nothing in build_us_acs_local_release.py counts SPM units with spm_measurement_adults < 1 (spm-calculator's rule) over the assembled release before publishing. That check is what this issue asks for.

    3. The rest of the path: a PR, then a rebuilt staging (the builder already says pre-existing staging carries the role only on donor rows), a new release, and certification in policyengine.py. That certification would also revert policyengine.py#604, the temporary national-file regions.

    Ownership: the session that appears to own the branch ("Register is_spm_independent_minor_role for ACS staging") is no longer among the live sessions. I have not touched the branch. The UK Budget CI coordinator is finding it an owner.

  2. MaxGhenis commented on Oct 13, 2026

    @MaxGhenis
    ContributorAuthor

    Owner: this session owns #1263 now. The branch is in PR #1264 (draft), with main merged.

    Gap 1, group-quarters children, needs Max's call (queued as d1484). The branch's role classifies 3,328 of the certified release's 5,274 unclassified units: 3,300 group-quarters teens aged 15 to 17, plus 28 donor units whose role column the old export held back. The remaining 1,946 units are one-person ACS group-quarters records of a child under 15. No role can give them a classified adult. 49 states plus DC have at least one (Utah has 27), so every state region still fails at run time unless they are handled. They are about 0.005% of the ACS spine's weight. Counts were measured on the cached Build O file, whose composition equals the certified release's on every field.

    spm-calculator needs no change. The refusal in spm-calculator's policyengine_amount does not read the universe status, but under policyengine-us#9462 the country owns scope. scoped_spm_amount replaces the calculator's five amount formulas and passes only INCLUDED units to masked_policyengine_amount, which is on pe-us main and applies the composition refusal only to the units it is given.

    The blocker is the rest of the chain:

    • d096 (parked): the export marker for the formula-owned status variable;
    • #9462 itself: a draft whose owner is archived, and strict, so it also needs a national child that declares scope;
    • policyengine.py#519 (draft): nullable SPM summaries.

    Options:

    The recommendation is (c) now, with (a) as the destination.

    Gap 2 is independent of that call. The release gate I'm adding fails the release on any SPM unit the runtime would measure that has no classified adult. That is every unit under an engine without universe scoping, or only the INCLUDED units once the engine scopes and the file declares. So no option can certify a release that crashes at run time.

  3. MaxGhenis commented on Oct 16, 2026

    @MaxGhenis
    ContributorAuthor

    Reopened: microcosm#1264 merged the code (d61bce0), but this issue's acceptance is a certified ACS local release with zero measured SPM units without a classified adult, checked by the release gate on the final artifact. The rebuild is running on Modal under Max's ruling d1484; I'll close this when the release passes its gates, publishes tag-only and is certified in policyengine.py.

  4. MaxGhenis commented on Oct 18, 2026

    @MaxGhenis
    ContributorAuthor

    The rebuild is now certified in policyengine.py and deployed. This adds evidence for the acceptance condition in this issue.

    The remaining interim exclusion (d1484 c, the 1,946 one-person group-quarters children under 15) is disclosed in the release. Its flip condition is the first release certified against an engine that scopes SPM by spm_unit_spm_universe_status.

    Closing this issue is left to its owner session, which set the close condition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions