Skip to content

Source policy parameter values (dependency age cutoffs, exemption amount) from PolicyEngine parameters, not hardcoded literals #4

@MaxGhenis

Description

@MaxGhenis

Problem

microunit currently hardcodes / snapshots the federal policy values used by the dependency rules, instead of sourcing them from the canonical parameter tree. They happen to match PolicyEngine today, but nothing keeps them in sync — they will silently drift when PE (or the law) updates.

Inventory of what's baked in and its canonical PE parameter:

microunit (current) Where Canonical source in PE-US
non_student_age_limit = 19, student_age_limit = 24 (literal default args) rule_helpers.pyqualifying_child_age_test() gov.irs.dependent.ineligible_age.non_student (19, 2018-, §152) and .student (24, 2018-, §152)
Qualifying-relative gross-income limit (§152(d)(1)(B) = the §151(d) exemption amount), as a bundled year-keyed YAML data/dependent_gross_income_limit.yaml gov.irs.income.exemption.amount

The bundled gross-income YAML is already a faithful structural copy of PE's parameter (year-keyed from 2013, uprating: gov.irs.uprating, Rev. Proc. references) — it just isn't generated from PE, so it's hand-maintained and can go stale.

These are currently in agreement, so this is drift-prevention, not a bug fix, and is not urgent.

What we want

The policy values should come from a single source of truth: PolicyEngine's parameters now, and the Axiom / Rules-Foundation rules-as-code source later — with PE as the nearer-term source. microunit should stop being the place a federal constant is independently authored.

Hard design constraint — keep microunit runtime-independent of policyengine-us

rule_helpers.py is explicit that microunit "does not depend on policyengine-us being installed," and that independence matters: the one-off household-request path and microplex's tax-unit construction must not be forced to drag a full PE install into the runtime just to know "19."

So the fix should not add a hard PE runtime dependency. Preferred shape:

  1. Build-time codegen — generate microunit's bundled parameter data from PE's parameters at package-build/release time (a small script that reads gov.irs.dependent.ineligible_age.* and gov.irs.income.exemption.amount and writes microunit's data/*.yaml). The shipped data is then provably PE-derived and dated/uprated, and a CI check can fail if the committed snapshot diverges from PE.
  2. Optional runtime override — if policyengine-us is importable, allow reading parameters live; otherwise fall back to the bundled snapshot. Keeps the dependency optional.
  3. Later, swap the build-time source from PE to Axiom / Atlas (rules-as-code) without changing microunit's internals — the codegen seam is the same.

Scope boundary (important)

Only the parameter values move to PE/Axiom. The partition logic stays in microunit — PE has no "given these N people, return the tax-unit partition" callable; the partition is PE's input, not an output, which is microunit's whole reason to exist. This issue does not propose delegating the qualifying-child/relative logic to PE, only the dated values it parameterizes.

Why it's worth doing

  • Removes the only places microunit can silently disagree with the law / with PE.
  • Strengthens the entity-convergence story: microunit is eCPS's tax-unit engine, so sharing PE's exact dated parameters makes the partition provably track PE rather than approximate it.
  • Year-aware already: construct_tax_units(person, year, mode) takes a year and the sourced parameters are dated/uprated, so a year-keyed build is a natural fit.

Acceptance criteria

  • microunit's age cutoffs and gross-income limit are generated from PE parameters at build time (no hardcoded 19/24 literals as the source of truth; no hand-maintained value YAML).
  • A CI check fails if the committed parameter snapshot diverges from PE's current values.
  • No new required runtime dependency on policyengine-us (optional live-read is fine).
  • Codegen source is swappable to Axiom / Atlas later without touching construction internals.
  • Partition logic unchanged.

Related: the microplex-side adapter gap that prevents the 24-yo student branch from firing is tracked separately at PolicyEngine/microplex-us#122 (that's about feeding microunit the enrollment columns; this issue is about where the 24 comes from).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions