From eaa83a1c99fb0a155240064a5dfb63a40ace2bd1 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 18:15:06 -0400 Subject: [PATCH 1/3] Pay no Colorado sales tax refund for tax year 2026 C.R.S. 39-22-2003(2) allows the state sales tax refund for a tax year only "if there were excess state revenues for the fiscal year ending in that tax year". FY 2025-26 ended below the Referendum C cap: the LCS June 2026 forecast (Table 9) showed $0 in every tax year 2026 tier, and on 2026-09-08 the State Controller certified a $175.9 million shortfall. policyengine-us had no such condition and carried the tax year 2025 tier table ($19 to $59 per person) into 2026. Add gov.states.co.tax.income.credits.sales_tax_refund.in_effect (false for 2026; true again from 2027, when the LCS June 2026 forecast expects the six-tier refund to apply) and multiply the refund by it. Tests: 2026 single and joint cases and a scenario_043 household with no Colorado refundable credits (PolicyBench reference audit 2026-10-05), plus Hypothesis invariants: zero refund in 2026, the 2025 refund equals eligible filers times the LCS March 2026 Table 8 tier, bounds, and the intended carry-forward of the 2025 table into 2027. Co-Authored-By: Claude Opus 5.5 --- ...-sales-tax-refund-2026-no-surplus.fixed.md | 1 + .../credits/sales_tax_refund/in_effect.yaml | 23 +++ .../test_co_sales_tax_refund_invariants.py | 183 ++++++++++++++++++ .../sales_tax_refund/co_sales_tax_refund.yaml | 66 +++++++ .../sales_tax_refund/co_sales_tax_refund.py | 16 +- 5 files changed, 282 insertions(+), 7 deletions(-) create mode 100644 changelog.d/co-sales-tax-refund-2026-no-surplus.fixed.md create mode 100644 policyengine_us/parameters/gov/states/co/tax/income/credits/sales_tax_refund/in_effect.yaml create mode 100644 policyengine_us/tests/core/test_co_sales_tax_refund_invariants.py diff --git a/changelog.d/co-sales-tax-refund-2026-no-surplus.fixed.md b/changelog.d/co-sales-tax-refund-2026-no-surplus.fixed.md new file mode 100644 index 00000000000..ed6004ec48e --- /dev/null +++ b/changelog.d/co-sales-tax-refund-2026-no-surplus.fixed.md @@ -0,0 +1 @@ +Pay no Colorado state sales tax refund for tax year 2026: C.R.S. 39-22-2003(2) allows it only if the fiscal year ending in the tax year had excess state revenues, and FY 2025-26 fell short of the Referendum C cap. diff --git a/policyengine_us/parameters/gov/states/co/tax/income/credits/sales_tax_refund/in_effect.yaml b/policyengine_us/parameters/gov/states/co/tax/income/credits/sales_tax_refund/in_effect.yaml new file mode 100644 index 00000000000..ee85b4d4f73 --- /dev/null +++ b/policyengine_us/parameters/gov/states/co/tax/income/credits/sales_tax_refund/in_effect.yaml @@ -0,0 +1,23 @@ +description: Colorado pays the state sales tax refund for a tax year if this is true, which requires excess state revenues for the state fiscal year ending in that tax year. +values: + 2021-01-01: true + # FY 2025-26 revenue fell $175.9 million short of the Referendum C cap, as + # the State Controller certified on 2026-09-08, so no tax year 2026 refund. + 2026-01-01: false + # The LCS June 2026 forecast expects the six-tier refund to apply for tax + # years 2027 and 2028; the tier amounts carry forward until CDOR publishes + # each year's table. + 2027-01-01: true +metadata: + unit: bool + period: year + label: Colorado sales tax refund in effect + reference: + - title: C.R.S. 39-22-2003(2) + href: https://colorado.public.law/statutes/crs_39-22-2003 + - title: Colorado Legislative Council Staff, Economic and Revenue Forecast, June 2026, Table 9 (tax year 2026 refunds $0 in every tier) + href: https://content.leg.colorado.gov/sites/default/files/june-2026-forecast-for-posting-accessible_0.pdf#page=31 + - title: Colorado Legislative Council Staff, Economic and Revenue Forecast, June 2026 (six-tier refund expected for tax years 2027 and 2028) + href: https://content.leg.colorado.gov/sites/default/files/june-2026-forecast-for-posting-accessible_0.pdf#page=30 + - title: Colorado Legislative Council Staff, Economic and Revenue Forecast, September 2026 (State Controller certification of FY 2025-26) + href: https://content.leg.colorado.gov/sites/default/files/2026-09/sept2026-forecast-with-cover-for-remediation-and-posting-accessible.pdf#page=9 diff --git a/policyengine_us/tests/core/test_co_sales_tax_refund_invariants.py b/policyengine_us/tests/core/test_co_sales_tax_refund_invariants.py new file mode 100644 index 00000000000..70f4ff271cb --- /dev/null +++ b/policyengine_us/tests/core/test_co_sales_tax_refund_invariants.py @@ -0,0 +1,183 @@ +"""Invariants for Colorado's state sales tax refund, C.R.S. 39-22-2003. + +Subsection (2) allows the refund for a tax year only "if there were excess +state revenues for the fiscal year ending in that tax year". FY 2025-26 ended +below the Referendum C cap (State Controller certification of 2026-09-08), so +no tax year 2026 refund is paid. Tax year 2025 refunds come from the FY 2024-25 +surplus, at the per-person tier amounts in the Legislative Council Staff March +2026 forecast, Table 8. + +Hypothesis draws batches of tax units (single, joint, or head of household +with a dependent), each with a modified AGI, filer ages and wages; a seeded +population of 100 units adds breadth. Each batch runs as one vectorized +simulation per year. For every tax unit: + +1. Gate: in 2026 the refund is zero, whatever the income, filing status or + eligibility. +2. Differential: in 2025 the refund equals the number of eligible filers + times the published tier amount for the unit's modified AGI, with the + tiers taken from the forecast table rather than from the parameters. +3. Bounds: 0 <= refund <= 2 x the top tier amount, and the refund is + positive exactly when a filer is eligible (2025). +4. Intended carry-forward: in 2027 the refund equals the 2025 refund. The + LCS June 2026 forecast expects a tax year 2027 refund, and the 2025 tier + amounts stand in until CDOR publishes the tax year 2027 table. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation + +TOLERANCE = 0.01 # dollars + +# LCS March 2026 forecast, Table 8, "Tax Year 2025 Refunds from FY 2024-25 +# TABOR Refund Obligation", single filers: "up to $52,000" $19, "$52,001 to +# $105,000" $25, "$105,001 to $168,000" $29, "$168,001 to $233,000" $35, +# "$233,001 to $299,000" $37, "$299,001 and up" $59. +PUBLISHED_2025_STARTS = np.array([52_001, 105_001, 168_001, 233_001, 299_001]) +PUBLISHED_2025_AMOUNTS = np.array([19, 25, 29, 35, 37, 59]) + +agis = st.one_of( + st.integers(-50_000, 0).map(float), + st.integers(0, 400_000).map(float), + st.sampled_from([52_000.0, 52_001.0, 105_000.0, 299_000.0, 299_001.0]), +) +ages = st.one_of(st.integers(14, 17), st.integers(18, 90)) +wages = st.one_of(st.just(0.0), st.integers(1, 100_000).map(float)) + + +@st.composite +def tax_units(draw): + kind = draw(st.sampled_from(["single", "joint", "hoh"])) + return { + "kind": kind, + "agi": draw(agis), + "head": {"age": draw(ages), "employment_income": draw(wages)}, + "spouse": ( + {"age": draw(ages), "employment_income": draw(wages)} + if kind == "joint" + else None + ), + } + + +SEED = 20261006 + + +def _seeded_units(n=100): + rng = np.random.default_rng(SEED) + + def person(): + return { + "age": int(rng.integers(14, 91)), + "employment_income": float(rng.integers(0, 100_001)) + if rng.random() < 0.5 + else 0.0, + } + + units = [] + for _ in range(n): + kind = rng.choice(["single", "joint", "hoh"]) + units.append( + { + "kind": kind, + "agi": float(rng.integers(-50_000, 400_001)), + "head": person(), + "spouse": person() if kind == "joint" else None, + } + ) + return units + + +def _situation(units, year): + people, groups = {}, {"tax_units": {}, "households": {}} + for i, unit in enumerate(units): + head = f"head_{i}" + people[head] = {**unit["head"], "is_tax_unit_head": True} + members = [head] + if unit["spouse"] is not None: + spouse = f"spouse_{i}" + people[spouse] = {**unit["spouse"], "is_tax_unit_spouse": True} + members.append(spouse) + if unit["kind"] == "hoh": + child = f"child_{i}" + people[child] = {"age": 8, "is_tax_unit_dependent": True} + members.append(child) + groups["tax_units"][f"tax_unit_{i}"] = { + "members": members, + "co_modified_agi": unit["agi"], + "co_income_tax_before_non_refundable_credits": 0, + } + groups["households"][f"household_{i}"] = { + "members": members, + "state_code": "CO", + } + people = { + name: {key: {year: value} for key, value in values.items()} + for name, values in people.items() + } + groups = { + group: { + name: { + key: value if key == "members" else {year: value} + for key, value in values.items() + } + for name, values in entities.items() + } + for group, entities in groups.items() + } + return {"people": people, **groups} + + +def _run(units, year): + sim = Simulation(situation=_situation(units, year)) + eligible = np.asarray( + sim.calculate("co_sales_tax_refund_person_eligible", year), dtype=float + ) + unit = sim.populations["tax_unit"].members_entity_id + return { + "refund": np.asarray(sim.calculate("co_sales_tax_refund", year), dtype=float), + "agi": np.asarray(sim.calculate("co_modified_agi", year), dtype=float), + "count": np.bincount(unit, weights=eligible, minlength=len(units)), + } + + +def _check(units): + by_year = {year: _run(units, year) for year in (2025, 2026, 2027)} + published = PUBLISHED_2025_AMOUNTS[ + np.searchsorted(PUBLISHED_2025_STARTS, by_year[2025]["agi"], side="right") + ] + + # 1. Gate: no tax year 2026 refund. + np.testing.assert_array_equal(by_year[2026]["refund"], 0) + + # 2. Differential against the published tax year 2025 table. + base = by_year[2025] + np.testing.assert_allclose( + base["refund"], base["count"] * published, atol=TOLERANCE + ) + + # 3. Bounds. + for run in by_year.values(): + assert (run["refund"] >= 0).all() + assert (run["refund"] <= 2 * PUBLISHED_2025_AMOUNTS.max()).all() + np.testing.assert_array_equal(base["refund"] > 0, base["count"] > 0) + + # 4. Intended carry-forward of the 2025 table into 2027. + np.testing.assert_allclose(by_year[2027]["refund"], base["refund"], atol=TOLERANCE) + + +@settings( + max_examples=4, + deadline=None, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given(st.lists(tax_units(), min_size=1, max_size=12)) +def test_co_sales_tax_refund_invariants(units): + _check(units) + + +def test_co_sales_tax_refund_invariants_seeded_population(): + _check(_seeded_units()) diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/sales_tax_refund/co_sales_tax_refund.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/sales_tax_refund/co_sales_tax_refund.yaml index 15896add05b..14accf3d1c1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/sales_tax_refund/co_sales_tax_refund.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/sales_tax_refund/co_sales_tax_refund.yaml @@ -461,3 +461,69 @@ state_code: CO output: co_sales_tax_refund: 153 + +# C.R.S. 39-22-2003(2) allows the refund for a tax year only "if there were +# excess state revenues for the fiscal year ending in that tax year". FY 2025-26 +# ended on 2026-06-30 below the Referendum C cap: the LCS June 2026 forecast +# (Table 9) showed $0 in every tax year 2026 tier, and on 2026-09-08 the State +# Controller certified a $175.9 million shortfall (LCS September 2026 forecast, +# p. 9). PolicyBench reference audit 2026-10-05, scenario_043. +- name: 2026 Single - no refund without FY 2025-26 excess state revenues + period: 2026 + input: + state_code: CO + age: 66 + co_modified_agi: 4_625 + co_sales_tax_refund_eligible: true + filing_status: SINGLE + output: + co_sales_tax_refund: 0 + +- name: 2026 Joint - no refund for either eligible spouse + period: 2026 + input: + people: + head: + age: 40 + is_tax_unit_head: true + spouse: + age: 40 + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [head, spouse] + co_modified_agi: 400_000 + co_income_tax_before_non_refundable_credits: 0 + filing_status: JOINT + households: + household: + members: [head, spouse] + state_code: CO + output: + co_sales_tax_refund_eligible: true + co_sales_tax_refund: 0 + +# scenario_043: a childless single filer aged 66 with $4,088 of wages and $553 +# of taxable pension. The federal EITC requires a childless filer to be under +# 65 (26 U.S.C. 32(c)(1)(A)(ii)(II)), and no other Colorado refundable credit +# applies, so with no tax year 2026 sales tax refund the total is $0. +- name: 2026 scenario_043 - no Colorado refundable credits for a childless 66-year-old + period: 2026 + input: + people: + person1: + age: 66 + employment_income: 4_088 + taxable_private_pension_income: 553 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: CO + output: + co_sales_tax_refund_eligible: true + co_eitc: 0 + co_sales_tax_refund: 0 + co_refundable_credits: 0 diff --git a/policyengine_us/variables/gov/states/co/tax/income/credits/sales_tax_refund/co_sales_tax_refund.py b/policyengine_us/variables/gov/states/co/tax/income/credits/sales_tax_refund/co_sales_tax_refund.py index 9b8f70a1a6d..6aee75e662a 100644 --- a/policyengine_us/variables/gov/states/co/tax/income/credits/sales_tax_refund/co_sales_tax_refund.py +++ b/policyengine_us/variables/gov/states/co/tax/income/credits/sales_tax_refund/co_sales_tax_refund.py @@ -8,22 +8,24 @@ class co_sales_tax_refund(Variable): unit = USD definition_period = YEAR reference = ( - # C.R.S. 39-22-2003(1)(a) defines a "qualified individual"; (3) sets the - # per-person refund amount and its doubling for a joint return. + # C.R.S. 39-22-2003(1)(a) defines a "qualified individual"; (2) allows + # the refund only for a tax year whose fiscal year had excess state + # revenues; (3) sets the per-person refund amount and its doubling for + # a joint return. "https://leg.colorado.gov/sites/default/files/images/olls/crs2023-title-39.pdf", "https://tax.colorado.gov/sites/tax/files/documents/DR_104_Book_2022.pdf#page=23", ) defined_for = "co_sales_tax_refund_eligible" def formula(tax_unit, period, parameters): - p = parameters(period).gov.states.co.tax.income.credits.sales_tax_refund.amount - if p.flat_amount_enabled: - amount = p.amount + p = parameters(period).gov.states.co.tax.income.credits.sales_tax_refund + if p.amount.flat_amount_enabled: + amount = p.amount.amount else: agi = tax_unit("co_modified_agi", period) - amount = p.scale.calc(agi) + amount = p.amount.scale.calc(agi) # The refund is claimed per qualifying individual (head and/or spouse), # so multiply the per-person amount by the number of eligible filers # rather than doubling for every joint return. eligible_count = add(tax_unit, period, ["co_sales_tax_refund_person_eligible"]) - return eligible_count * amount + return eligible_count * amount * p.in_effect From 395c31dd46d52d26d3866468334629af5e8284c1 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 19:45:56 -0400 Subject: [PATCH 2/3] Run the Colorado refund invariants in one simulation per draw Key every input by all three years so each Hypothesis draw and the seeded population build one simulation instead of three, per the repository's CI-cost rules. Locally (macOS, load average about 60) the file takes 25.6 s of user CPU for the whole pytest process including the model import, with 0.91 GB peak RSS. Co-Authored-By: Claude Opus 5.5 --- .../test_co_sales_tax_refund_invariants.py | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/policyengine_us/tests/core/test_co_sales_tax_refund_invariants.py b/policyengine_us/tests/core/test_co_sales_tax_refund_invariants.py index 70f4ff271cb..f3baf056b41 100644 --- a/policyengine_us/tests/core/test_co_sales_tax_refund_invariants.py +++ b/policyengine_us/tests/core/test_co_sales_tax_refund_invariants.py @@ -10,7 +10,7 @@ Hypothesis draws batches of tax units (single, joint, or head of household with a dependent), each with a modified AGI, filer ages and wages; a seeded population of 100 units adds breadth. Each batch runs as one vectorized -simulation per year. For every tax unit: +simulation covering all three years. For every tax unit: 1. Gate: in 2026 the refund is zero, whatever the income, filing status or eligibility. @@ -91,7 +91,14 @@ def person(): return units -def _situation(units, year): +YEARS = (2025, 2026, 2027) + + +def _situation(units): + # One simulation holds every year: each input is keyed by all of YEARS. + def every_year(value): + return {year: value for year in YEARS} + people, groups = {}, {"tax_units": {}, "households": {}} for i, unit in enumerate(units): head = f"head_{i}" @@ -107,45 +114,40 @@ def _situation(units, year): members.append(child) groups["tax_units"][f"tax_unit_{i}"] = { "members": members, - "co_modified_agi": unit["agi"], - "co_income_tax_before_non_refundable_credits": 0, + "co_modified_agi": every_year(unit["agi"]), + "co_income_tax_before_non_refundable_credits": every_year(0), } groups["households"][f"household_{i}"] = { "members": members, - "state_code": "CO", + "state_code": every_year("CO"), } people = { - name: {key: {year: value} for key, value in values.items()} + name: {key: every_year(value) for key, value in values.items()} for name, values in people.items() } - groups = { - group: { - name: { - key: value if key == "members" else {year: value} - for key, value in values.items() - } - for name, values in entities.items() - } - for group, entities in groups.items() - } return {"people": people, **groups} -def _run(units, year): - sim = Simulation(situation=_situation(units, year)) - eligible = np.asarray( - sim.calculate("co_sales_tax_refund_person_eligible", year), dtype=float - ) +def _run(units): + sim = Simulation(situation=_situation(units)) unit = sim.populations["tax_unit"].members_entity_id - return { - "refund": np.asarray(sim.calculate("co_sales_tax_refund", year), dtype=float), - "agi": np.asarray(sim.calculate("co_modified_agi", year), dtype=float), - "count": np.bincount(unit, weights=eligible, minlength=len(units)), - } + by_year = {} + for year in YEARS: + eligible = np.asarray( + sim.calculate("co_sales_tax_refund_person_eligible", year), dtype=float + ) + by_year[year] = { + "refund": np.asarray( + sim.calculate("co_sales_tax_refund", year), dtype=float + ), + "agi": np.asarray(sim.calculate("co_modified_agi", year), dtype=float), + "count": np.bincount(unit, weights=eligible, minlength=len(units)), + } + return by_year def _check(units): - by_year = {year: _run(units, year) for year in (2025, 2026, 2027)} + by_year = _run(units) published = PUBLISHED_2025_AMOUNTS[ np.searchsorted(PUBLISHED_2025_STARTS, by_year[2025]["agi"], side="right") ] From 663956bff983d5b996a0b26a0e955f1f0bab3311 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 8 Oct 2026 17:27:50 -0400 Subject: [PATCH 3/3] Stop pinning the placeholder 2027 Colorado refund; test every 2026 tier boundary The review of this PR found that the invariants test pinned the 2027 refund to the 2025 tier amounts. Those amounts are a placeholder until CDOR publishes the tax year 2027 table, and LCS's September 2026 forecast projects $43 for the lowest single tier against the $19 carried forward. - Drop that check from the invariants test and its docstring. - Label the 2027 values as placeholders in in_effect.yaml. - Add YAML cases showing a $0 refund for an eligible single filer at every 2025 tier boundary in tax year 2026. Co-Authored-By: Claude Opus 5.5 --- .../credits/sales_tax_refund/in_effect.yaml | 6 +- .../test_co_sales_tax_refund_invariants.py | 8 +- .../sales_tax_refund/co_sales_tax_refund.yaml | 134 ++++++++++++++++++ 3 files changed, 140 insertions(+), 8 deletions(-) diff --git a/policyengine_us/parameters/gov/states/co/tax/income/credits/sales_tax_refund/in_effect.yaml b/policyengine_us/parameters/gov/states/co/tax/income/credits/sales_tax_refund/in_effect.yaml index ee85b4d4f73..7c6a9c6ebc4 100644 --- a/policyengine_us/parameters/gov/states/co/tax/income/credits/sales_tax_refund/in_effect.yaml +++ b/policyengine_us/parameters/gov/states/co/tax/income/credits/sales_tax_refund/in_effect.yaml @@ -5,8 +5,10 @@ values: # the State Controller certified on 2026-09-08, so no tax year 2026 refund. 2026-01-01: false # The LCS June 2026 forecast expects the six-tier refund to apply for tax - # years 2027 and 2028; the tier amounts carry forward until CDOR publishes - # each year's table. + # years 2027 and 2028. Until CDOR publishes the tax year 2027 table, the + # tax year 2025 tier amounts stand in as placeholders; they are not + # published 2027 amounts (the LCS September 2026 forecast projects $43 for + # the lowest single tier, against the $19 carried forward). 2027-01-01: true metadata: unit: bool diff --git a/policyengine_us/tests/core/test_co_sales_tax_refund_invariants.py b/policyengine_us/tests/core/test_co_sales_tax_refund_invariants.py index f3baf056b41..6f6436a4fb7 100644 --- a/policyengine_us/tests/core/test_co_sales_tax_refund_invariants.py +++ b/policyengine_us/tests/core/test_co_sales_tax_refund_invariants.py @@ -19,9 +19,8 @@ tiers taken from the forecast table rather than from the parameters. 3. Bounds: 0 <= refund <= 2 x the top tier amount, and the refund is positive exactly when a filer is eligible (2025). -4. Intended carry-forward: in 2027 the refund equals the 2025 refund. The - LCS June 2026 forecast expects a tax year 2027 refund, and the 2025 tier - amounts stand in until CDOR publishes the tax year 2027 table. +The tax year 2027 amounts are placeholders (the 2025 tier amounts stand in +until CDOR publishes the 2027 table), so no invariant pins them. """ import numpy as np @@ -167,9 +166,6 @@ def _check(units): assert (run["refund"] <= 2 * PUBLISHED_2025_AMOUNTS.max()).all() np.testing.assert_array_equal(base["refund"] > 0, base["count"] > 0) - # 4. Intended carry-forward of the 2025 table into 2027. - np.testing.assert_allclose(by_year[2027]["refund"], base["refund"], atol=TOLERANCE) - @settings( max_examples=4, diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/sales_tax_refund/co_sales_tax_refund.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/sales_tax_refund/co_sales_tax_refund.yaml index 14accf3d1c1..350411f45e5 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/sales_tax_refund/co_sales_tax_refund.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/sales_tax_refund/co_sales_tax_refund.yaml @@ -527,3 +527,137 @@ co_eitc: 0 co_sales_tax_refund: 0 co_refundable_credits: 0 + +# Every 2025 tier boundary in tax year 2026: each single filer is eligible +# (age 18 or older) and gets $0. In 2025 these units would get $19, $19, $25, +# $29, $35, $37 and $59 (LCS March 2026 forecast, Table 8). +- name: 2026 - no refund at any tier for eligible single filers + period: 2026 + input: + people: + person1: + age: 30 + person2: + age: 30 + person3: + age: 30 + person4: + age: 30 + person5: + age: 30 + person6: + age: 30 + person7: + age: 30 + tax_units: + tax_unit1: + members: [person1] + co_modified_agi: -10000 + co_income_tax_before_non_refundable_credits: 0 + tax_unit2: + members: [person2] + co_modified_agi: 52000 + co_income_tax_before_non_refundable_credits: 0 + tax_unit3: + members: [person3] + co_modified_agi: 52001 + co_income_tax_before_non_refundable_credits: 0 + tax_unit4: + members: [person4] + co_modified_agi: 105001 + co_income_tax_before_non_refundable_credits: 0 + tax_unit5: + members: [person5] + co_modified_agi: 168001 + co_income_tax_before_non_refundable_credits: 0 + tax_unit6: + members: [person6] + co_modified_agi: 233001 + co_income_tax_before_non_refundable_credits: 0 + tax_unit7: + members: [person7] + co_modified_agi: 299001 + co_income_tax_before_non_refundable_credits: 0 + households: + household1: + members: [person1] + state_code: CO + household2: + members: [person2] + state_code: CO + household3: + members: [person3] + state_code: CO + household4: + members: [person4] + state_code: CO + household5: + members: [person5] + state_code: CO + household6: + members: [person6] + state_code: CO + household7: + members: [person7] + state_code: CO + output: + co_sales_tax_refund_eligible: [true, true, true, true, true, true, true] + co_sales_tax_refund: [0, 0, 0, 0, 0, 0, 0] + +# Positive controls for 2025 to 2027, with zero, one and two eligible +# filers. 2027: the gate is on again and the tax year 2025 amounts stand in +# until CDOR publishes the tax year 2027 table (an intended placeholder). +- name: 2025 to 2027 - refunds for zero, one and two eligible filers + period: 2027 + input: + people: + person1: + age: {2025: 40, 2026: 40, 2027: 40} + person2: + age: {2025: 40, 2026: 40, 2027: 40} + person3: + age: {2025: 40, 2026: 40, 2027: 40} + person4: + age: {2025: 17, 2026: 17, 2027: 17} + person5: + age: {2025: 16, 2026: 16, 2027: 16} + tax_units: + tax_unit1: + members: [person1, person2] + co_modified_agi: {2025: 60_000, 2026: 60_000, 2027: 60_000} + co_income_tax_before_non_refundable_credits: {2025: 0, 2026: 0, 2027: 0} + filing_status: {2025: JOINT, 2026: JOINT, 2027: JOINT} + tax_unit2: + members: [person3] + co_modified_agi: {2025: 4_625, 2026: 4_625, 2027: 4_625} + co_income_tax_before_non_refundable_credits: {2025: 0, 2026: 0, 2027: 0} + tax_unit3: + members: [person4] + co_modified_agi: {2025: 0, 2026: 0, 2027: 0} + co_income_tax_before_non_refundable_credits: {2025: 0, 2026: 0, 2027: 0} + tax_unit4: + members: [person5] + co_modified_agi: {2025: 0, 2026: 0, 2027: 0} + co_income_tax_before_non_refundable_credits: {2025: 0, 2026: 0, 2027: 0} + households: + household1: + members: [person1, person2] + state_code: {2025: CO, 2026: CO, 2027: CO} + household2: + members: [person3] + state_code: {2025: CO, 2026: CO, 2027: CO} + household3: + members: [person4] + state_code: {2025: CO, 2026: CO, 2027: CO} + household4: + members: [person5] + state_code: {2025: CO, 2026: CO, 2027: CO} + output: + co_sales_tax_refund_person_eligible: + 2027: [true, true, true, false, false] + # Joint, both eligible, second tier: 2 x $25. Single, first tier: $19. + # Under-18 filers with no wages or tax are not qualified individuals. + co_sales_tax_refund: + 2025: [50, 19, 0, 0] + 2026: [0, 0, 0, 0] + 2027: [50, 19, 0, 0]