Skip to content

Add rules-based tax-unit construction engine + PyPI release machinery#3

Merged
MaxGhenis merged 2 commits into
mainfrom
init-microunit-package
May 30, 2026
Merged

Add rules-based tax-unit construction engine + PyPI release machinery#3
MaxGhenis merged 2 commits into
mainfrom
init-microunit-package

Conversation

@MaxGhenis
Copy link
Copy Markdown
Contributor

@MaxGhenis MaxGhenis commented May 30, 2026

Summary

Adds the rules-based tax-unit / filing-status construction engine extracted from policyengine-us-data (source: upstream/main@f7458313), fulfilling roadmap item 2 ("Move reusable tax-unit construction out of policyengine-us-data / policyengine-us"). This is a careful copy-and-repackage — no logic changes to the engine — integrated additively with the existing unit-assignment scaffold (UnitPartition, assign_*_partition, diagnostics, registry are all untouched). microunit becomes a publishable dependency for the CPS/ACS pipelines in policyengine-us-data and for microplex-us.

New modules

  • src/microunit/tax_unit_construction.py — the core engine. Public entry construct_tax_units(person, year, mode) with modes "policyengine" (default) and "census_documented", plus HEAD/SPOUSE/DEPENDENT role constants. The only change vs. the source is the internal import (now microunit.rule_helpers); there are zero non-import edits to the logic.
  • src/microunit/rule_helpers.py — dependency/filing rule helpers (renamed from tax_unit_rule_helpers.py). The optional policyengine_us import shim is dropped; the qualifying-relative gross income limit now loads from packaged data, so the engine no longer depends on policyengine-us.
  • src/microunit/data/dependent_gross_income_limit.yaml — vendored IRC 151(d) personal/dependent exemption-amount values (through 2026; used by the IRC 152(d)(1)(B) gross income test), loaded via importlib.resources.

Integration (additive)

  • __init__.py — additively exports construct_tax_units, the role constants, the modes, CPSRelationshipCode, and the rule helpers. The pre-existing public API is unchanged.
  • units/tax.py — adds construct_tax_partition(), a UnitPartition adapter over construct_tax_units, fulfilling the prior "Full rules-based tax-unit construction should be ported here" TODO. assign_tax_partition still preserves native source IDs.
  • units/__init__.py — exports construct_tax_partition.
  • pyproject.toml — adds numpy and pyyaml deps; ships the YAML as wheel/sdist package data. uv.lock regenerated for the new direct deps.
  • README.md — documents the engine, the two modes, the input column contract, and the ACS-mapping boundary; marks roadmap item 2 done.

Public API

from microunit import (
    construct_tax_units, estimate_dependent_gross_income,
    HEAD, SPOUSE, DEPENDENT,
    POLICYENGINE_MODE, CENSUS_DOCUMENTED_MODE, SUPPORTED_TAX_UNIT_CONSTRUCTION_MODES,
    CPSRelationshipCode,
    dependent_gross_income_limit, qualifying_child_age_test,
    reference_relationship_allows_qualifying_child,
    reference_relationship_allows_qualifying_relative, related_to_head_or_spouse,
    REFERENCE_PERSON_CODES, REFERENCE_SPOUSE_CODES,
    REFERENCE_QUALIFYING_CHILD_CODES, REFERENCE_QUALIFYING_RELATIVE_CODES,
)
from microunit.units import construct_tax_partition  # UnitPartition adapter

construct_tax_units(person, year, mode="policyengine") returns (person_assignments, tax_unit): person_assignments carries TAX_ID, tax_unit_role_input (bytes), is_related_to_head_or_spouse; tax_unit carries TAX_ID and filing_status_input (bytes).

ACS boundary decision

acs_to_cps_columns.py (~500 lines of ACS-PUMS-specific RELSHIPP/RELP translation and heuristic spouse/parent inference) is intentionally not included. microunit operates on already-normalized CPS-like person frames; ACS column mapping — and the ACS-specific tests that exercise it and the ACS dataset writer — stay in policyengine-us-data, whose thin ACS wrapper can keep mapping columns and then call microunit.construct_tax_units. Documented in the README.

Verification

Verified from a fresh clone of this branch and from an out-of-tree installed wheel:

  • uv run pytest43 passed (existing scaffold suite + the full ported CPS construction suite + new adapter/import tests). Reproducible from the frozen uv.lock.
  • uv run ruff check → clean (whole repo); uv run ruff format --check → clean on all added/modified files.
  • Built a wheel, installed it into a clean venv outside the source tree, and confirmed import microunit / construct_tax_units(...) / construct_tax_partition(...) end-to-end and that the packaged YAML resolves via importlib.resources (dependent_gross_income_limit(2024) == 5050, (2026) == 5300).

Pre-PyPI consumer install spec

git+https://github.com/PolicyEngine/microunit@init-microunit-package

🤖 Generated with Claude Code

@MaxGhenis MaxGhenis force-pushed the init-microunit-package branch from abb33e4 to 046881e Compare May 30, 2026 13:05
Extract the rules-based tax-unit / filing-status construction engine from
policyengine-us-data into microunit (roadmap item 2). The engine is copied
verbatim (no logic changes) and made source-agnostic and self-contained. This
integrates additively with the existing unit-assignment scaffold.

New modules:
- src/microunit/tax_unit_construction.py: core engine. Public entry
  construct_tax_units(person, year, mode) with "policyengine" (default) and
  "census_documented" modes; HEAD/SPOUSE/DEPENDENT role constants. The only
  change vs. the source is the internal import (now microunit.rule_helpers);
  zero non-import edits to the logic.
- src/microunit/rule_helpers.py: dependency/filing rule helpers (renamed from
  tax_unit_rule_helpers). The optional policyengine_us import shim is dropped;
  the qualifying-relative gross income limit now loads from packaged data, so
  the engine no longer depends on policyengine-us.
- src/microunit/data/dependent_gross_income_limit.yaml: vendored IRC 151(d)
  exemption-amount values (through 2026), loaded via importlib.resources.

Integration:
- __init__.py: additively export construct_tax_units, the role constants,
  modes, CPSRelationshipCode, and the rule helpers (existing API unchanged).
- units/tax.py: add construct_tax_partition(), a UnitPartition adapter over
  construct_tax_units, fulfilling the prior "port rules-based tax-unit
  construction here" TODO. assign_tax_partition still preserves native IDs.
- units/__init__.py: export construct_tax_partition.
- pyproject.toml: add numpy and pyyaml deps; ship the YAML as wheel/sdist data.
- uv.lock: regenerated for the new direct dependencies.
- README.md: document the engine, the two modes, the input contract, and the
  ACS-column-mapping boundary.

Tests (60 passing total): test_tax_unit_construction.py ports the full CPS
suite to the microunit namespace; test_tax_partition_adapter.py covers the new
adapter; test_import.py checks the public API and packaged-data resolution.

ACS boundary: acs_to_cps_columns.py (ACS-PUMS-specific RELSHIPP/RELP and
spouse/parent inference) is intentionally NOT included. microunit takes
already-normalized CPS-like person frames; ACS column mapping and the
ACS-specific tests remain in policyengine-us-data.

Extracted from PolicyEngine/policyengine-us-data@f7458313.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MaxGhenis MaxGhenis force-pushed the init-microunit-package branch from 046881e to d3eccbb Compare May 30, 2026 13:16
Productionize microunit so it publishes to PyPI via PolicyEngine's standard
micro* release flow (matches microcalibrate/microimpute):

- .github/workflows/pr.yaml: ruff (lint + format) + pytest (3.11/3.13/3.14)
  + build, on PRs
- .github/workflows/versioning.yaml: on merge to main, bump the version from
  changelog.d fragments (towncrier) and publish to PyPI via secrets.PYPI
- .github/workflows/changelog_entry.yaml: require a changelog fragment on PRs
- .github/workflows/main.yml: tests on push to main
- .github/{bump_version.py,fetch_version.py,publish-git-tag.sh}
- Makefile (install/test/check-format/format/build/changelog)
- towncrier config + CHANGELOG.md + changelog.d/ with the initial fragment
- LICENSE (MIT)

Base version set to 0.0.0 so the first auto-bump publishes 0.1.0. Existing
core.py/diagnostics.py are ruff-format-normalized for the new format gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MaxGhenis MaxGhenis changed the title Add rules-based tax-unit construction engine from policyengine-us-data Add rules-based tax-unit construction engine + PyPI release machinery May 30, 2026
@MaxGhenis
Copy link
Copy Markdown
Contributor Author

Added: release machinery to productionize for PyPI

Pushed a8ab456 on top of the package, making microunit publishable on PyPI via PolicyEngine's standard micro* release flow (matches microcalibrate/microimpute):

  • .github/workflows/versioning.yaml — on merge to main, infers the semver bump from changelog.d/ fragments (towncrier), commits the bump via the PolicyEngine app token, and publishes to PyPI (secrets.PYPI, skip-existing).
  • .github/workflows/pr.yaml — ruff (lint + format) + pytest on 3.11/3.13/3.14 + build, on PRs.
  • .github/workflows/changelog_entry.yaml — requires a changelog fragment on PRs; main.yml — tests on push to main.
  • .github/{bump_version.py,fetch_version.py,publish-git-tag.sh}, Makefile, towncrier config, CHANGELOG.md, changelog.d/ (initial added fragment), LICENSE (MIT).

Base version set to 0.0.0 so the first auto-bump on merge publishes 0.1.0. The org PYPI/APP_ID/APP_PRIVATE_KEY secrets are already visible to this repo, so no credential setup is needed.

Verified locally end-to-end: bump 0.0.0 → 0.1.0, towncrier renders the changelog and consumes the fragment, python -m build produces microunit-0.1.0 wheel + sdist, ruff/format clean, 43 tests pass, all four workflows are valid YAML.

On merge → PyPI publish, the consumers (policyengine-us-data#1157, microplex-us#114) switch from the git+https pin to microunit>=0.1.0.

@MaxGhenis MaxGhenis marked this pull request as ready for review May 30, 2026 18:49
@MaxGhenis MaxGhenis merged commit 501e0d4 into main May 30, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant