policyengine_calculate_taxes() now runs StitchedRunner, so tax years before
2021 are computed by the bundled TAXSIM-35 as on the command line, with
engine = "policyengine" to compute every year with PolicyEngine as before.
setup_policyengine() installs the policyengine-taxsim release matching the R
package from PyPI in one pip call, without --upgrade, and can pin both
packages, install from a folder of wheels with no network access, and build
the environment from a given Python.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The R package now gives the same results as the command line.
policyengine_calculate_taxes()sends tax years before 2021 to the bundled TAXSIM-35, aspolicyengine-taxsimdoes, andsetup_policyengine()installs one released policyengine-taxsim from PyPI instead of GitHub'smainwith--upgrade, with a way to install from a folder of wheels on machines without internet access.This changes results for R users with pre-2021 records. Those records came from PolicyEngine, which this project compares with TAXSIM-35 only from 2021 on; they now come from TAXSIM-35. Results for 2021 and later are unchanged (tested below).
engine = "policyengine"keeps the old behavior. The changelog fragment ischangedand says so.Routing by year
policyengine_calculate_taxes()callsStitchedRunner(wasPolicyEngineRunner). New argumentengine = c("auto", "policyengine"):"policyengine"passespe_min_year = 0, so every record goes to PolicyEngine and rows still come back in input order.Calculating taxes: 2 records with PolicyEngine (tax years from 2021 on), 1 with TAXSIM-35 (earlier years).... The cutoff is read fromStitchedRunner.PE_MIN_YEAR.NAwhere an engine produces none, plus TAXSIM-35's build column.yearnow stops with "Every record needs a tax year", since the year picks the engine.StitchedRunner(before March 2026) gets an error saying to runsetup_policyengine(force = TRUE).idtlis still set fromreturn_all_information(0 or 2); the docs now say anidtlcolumn in the data is ignored.Setup
pip install policyengine-taxsim==<pin>from PyPI, one pip call, no--upgrade.<pin>is.PE_TAXSIM_VERSIONinR/setup.R, currently3.1.1.policyengine_taxsim_versionandpolicyengine_us_versionpin each package; both go into the same pip call, so pip resolves them together (before, policyengine-us was pinned in a second call after an upgrading install).wheel_dir: installs only from that folder (--no-index --find-links <dir>). Without a version, the folder decides which policyengine-taxsim. The folder is checked for a matchingpolicyengine_taxsim-*.whlbefore anything is removed. If no compatible Python is found, the function stops instead of downloading Miniconda.python: build the environment from a given interpreter, skipping reticulate's search. Useful when wheels fit one Python version, or a machine has 3.10 and 3.11.virtualenv_create(..., packages = FALSE). By default reticulate upgrades pip, wheel and setuptools and installs numpy from the index at that step, which would defeatwheel_dir(reticulate 1.47.0R/virtualenv.R). pip is run directly becausereticulate::virtualenv_install()always adds--upgrade(R/pip.R,pip_install())."latest", a missing folder, a folder without the wheel) stop beforeforce = TRUEremoves an existing environment.setup_policyengine(force = TRUE, policyengine_taxsim_version = ..., policyengine_us_version = ...)call that reinstalls them. Asking for pins when an environment already exists says nothing was installed.Keeping the pin current
.github/bump_version.py, which the release job runs, now rewrites.PE_TAXSIM_VERSIONtogether withpyproject.tomland__init__.py, and exits non-zero if it can't find exactly one such line. So the R package on any commit ofmainpins the release made from that commit (or, between the merge and the version commit, the previous release, which also hasStitchedRunner).tests/test_r_package.pyfails if the pin andpyproject.tomldiffer. Every release from 2.10.0 on is on PyPI, so the pin has been installable for the whole period this applies to.How this relates to other open PRs
policyengine_versions()report andprovenancefiles). Built independently frommainso this can land without waiting for Add --version and an opt-in --provenance sidecar to the CLI #1412. Its newr-packageCI job andtests/testthat.Rare copied verbatim, and the DESCRIPTION Suggests lines match, so those merge cleanly. A trial merge conflicts only inpolicyengine_calculate_taxes()'s@paramblock and signature (both add an argument aftershow_progress). Beyond the textual conflict, R wrapper and dashboard refresh: report the TAXSIM build and record provenance #1414's provenance code assumesPolicyEngineRunner: it recordsengines = list(policyengine = n, taxsim = 0)and readsrunner$scorp_treatment, whichStitchedRunnerdoesn't have. I left the resolution on R wrapper and dashboard refresh: report the TAXSIM build and record provenance #1414.engine = "policyengine"returns input order with or without it, becauseStitchedRunnerrestores the order, and the tests compare bytaxsimid.StitchedRunner.engine_counts()gives the same counts as this PR's R.engine_counts().Invariants and how they are tested
PE_MIN_YEAR, TAXSIM-35 the rest, nothing dropped or duplicated. Hypothesis property test onStitchedRunner(tests/test_r_package.py), and a 200-case randomized test of the R counts, including that a later cutoff never sends more records to PolicyEngine.pe_min_year = 0sends every record to PolicyEngine, for any years (Hypothesis).policyengine_calculate_taxes()equalspolicyengine-taxsim policyengineon the same CSV, foridtl0 and 2 (R integration test; mixed 2015–2023 records, single and joint, with dependents).engine = "policyengine"equalsPolicyEngineRunneron the same records, in R (integration test) and in Python (StitchedRunner(pe_min_year=0)vsPolicyEngineRunner).engine = "policyengine"agree on every record from 2021 on, and TAXSIM-35's build column is filled exactly for the pre-2021 records.--no-index/--find-linksappear exactly whenwheel_diris given: exhaustive over taxsim version × us version × folder (R unit test). Withwheel_dir, no Miniconda download (unit test) and no network at all (CI, below).update_r_pinmakes the pin read back as that version, changes no other line, and is idempotent (Hypothesis); the full release step on each fragment type moves pyproject,__init__and the R pin together (parametrized).CI
r-package(from R wrapper and dashboard refresh: report the TAXSIM build and record provenance #1414): unit tests plus the integration tests against this checkout, with Python required.r-package-installjob, which runssetup_policyengine()for real in fresh virtualenv roots: (1) the firstpolicyengine_calculate_taxes()call installs the pinned release from PyPI and computes a 2019 and a 2023 record; (2)pip downloadthe wheels (the command the README gives), then install from them and compute insideunshare --netwith only loopback (.github/without-network.sh), with a test that fails if PyPI is reachable; (3) the same with both versions pinned.Tests run locally
testthat::test_local()with no Python environment: 1,259 expectations pass; 5 tests skip (the 2 integration tests and the 3 install tests).POLICYENGINETAXSIM_REQUIRE_PYTHON=true): 1,125 expectations pass, 0 skipped. This covers invariants 3 to 6 above.pytest tests/test_r_package.py tests/test_stitched_runner.py: 29 passed (12 new).ruff format --check .: clean.test-install.R. The machine was under a hold on creating virtual environments, so these run only in CI'sr-package-installjob.What the change does to one record (same environment): for a 2015 California single filer with $30,000 wages and one child,
fiitaxwas −2,136.73 from PolicyEngine and is now −1,190.37 from TAXSIM-35 (build cd2026081819). A 2019 Texas joint filer gave 8,749 from both.Not changed
compare_with_taxsim()still runs both engines on every record (its purpose).docs/(not onmainuntil Add an input guide, a design document, and security and deployment notes #1419 merges; see above).axiom: n/a: R wrapper engine selection and installation; no policy change.
🤖 Generated with Claude Code