Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
93fe8cf
docs: Round 14 plan (confirmatory multi-state analyses)
cafeTechne Nov 8, 2026
ef20676
feat: MCP cache_import tool (full profile) sharing the CLI cache impo…
cafeTechne Nov 8, 2026
deceb3a
fix: drop deprecated inplace=True in format_dataframe (pandas 3.1 Pan…
cafeTechne Nov 8, 2026
910f982
validation: add Michigan (26) to the battery states; re-record fixtures
cafeTechne Nov 8, 2026
c2ded2a
docs: regenerate validation report with Michigan in the battery
cafeTechne Nov 8, 2026
f16df20
Merge Round 14 B: Michigan in the validation battery
cafeTechne Nov 8, 2026
58fa9e3
feat: PUMS peak-memory benchmark on synthetic files (PLAN_ROUND_14 C,…
cafeTechne Nov 8, 2026
a014dd2
fix: remove avoidable weight-matrix copies in PUMS tabulation and rea…
cafeTechne Nov 8, 2026
6d53343
feat: multi-state manifests and a declared RPP adjustment step (Round…
cafeTechne Nov 8, 2026
88f4d44
test/docs: MI/NY confirmatory manifest, frozen-fixture route test, ca…
cafeTechne Nov 8, 2026
2201017
Merge Round 14 A: multi-state manifests with a declared RPP adjustment
cafeTechne Nov 8, 2026
fbf4ee6
Merge Round 14 C+D: PUMS memory benchmark (R2), pandas 4 deprecation,…
cafeTechne Nov 8, 2026
63e52da
fix: PUMS median gathers each group's rows once (one stable argsort);…
cafeTechne Nov 8, 2026
74556fc
fix: Round 14 code-review findings for multi-state manifests and RPP …
cafeTechne Nov 8, 2026
b83d2c3
Merge Round 14 review fixes
cafeTechne Nov 8, 2026
4fa3b55
fix(manifest): never overwrite the input file when create_manifest fi…
cafeTechne Nov 8, 2026
8c2e6b3
release: v0.13.0 (Round 14); gate report
cafeTechne Nov 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ each tagged release (`v<version>`) is archived on Zenodo with its own DOI (see
[Releasing](#releasing)). Plans and acceptance criteria for each round are in
`PLAN_ROUND_<n>.md`.

## 0.13.0 (Round 14): confirmatory multi-state analyses

- Pre-registered manifests take several states (`geography.states`, 2-digit FIPS; not
with ZCTAs), derivations evaluated on replicates, and a declared RPP adjustment step
(`adjustments`, shared with `place_adjust_frame`; `rpp_year` required, filled in by
`create_manifest` into `<name>.completed.json` when omitted; exclusions run first;
RPP provenance recorded). The Michigan/New York study runs confirmatorily
(`examples/manifests/mi_ny_income_education.json`) and reproduces Round 13 exactly
(pct_bachelors 811.0, 90% CI [684.3, 937.7]).
- Validation battery adds Michigan (26); all 10 checks pass live and offline.
- PUMS memory measured (`scripts/pums_memory_benchmark.py`, `docs/audits/pums_memory.md`;
risk R2 closed): peak about 275 MiB + 2.8 KB per person record. Avoidable weight-matrix
copies removed (1.9M rows: 6.4 to 5.4 GiB peak, results bit-identical); grouped medians
gather each group once.
- MCP `cache_import` (full profile) with the CLI's safety rules; pandas 4 deprecation in
`format_dataframe` fixed.
- Review fixes: `run_manifest` records replicate-derivation errors as deviations instead
of raising; ACS refs with E/PE suffixes match their replicates; methods text reports how
many units received replicate SEs.

## 0.12.0 (Round 13): real data, end to end

- Live verification: the full battery against Census-published values passes live (10/10
Expand Down
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ authors:
- alias: cafeTechne
license: AGPL-3.0-or-later
repository-code: "https://github.com/cafeTechne/US-Census-5-Year-ACS-Data-Batch-Downloader"
version: "0.12.0"
version: "0.13.0"
doi: "10.5281/zenodo.23925301"
# doi: Zenodo concept DOI, added after the first archived release (see CHANGELOG.md "Releasing").
date-released: "2026-11-07"
date-released: "2026-11-08"
keywords:
- American Community Survey
- US Census Bureau
Expand Down
92 changes: 92 additions & 0 deletions PLAN_ROUND_14.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Round 14: Confirmatory Multi-State Analyses

**Date:** 2026-11-08
**Status:** Implemented (section 9); released as v0.13.0
**Baseline:** v0.12.0 (Round 13 merged)
**Companion:** [PLAN_ROUND_13.md](PLAN_ROUND_13.md) (section 11 lists the deferrals this round takes up)

## 0. Front-loaded findings

| ID | Source | Finding | Disposition |
| :--- | :--- | :--- | :--- |
| G1 | Round 13 E1 | The Michigan/New York case study is labelled *exploratory*. A `run_manifest` geography holds a single state, and there is no place-adjustment step, so the study cannot be pre-registered | Workstream A |
| G2 | Round 13 deferrals | The validation battery covers states 06, 36, 48, 17 and 56. Michigan, one of the owner's preferred test states (D13-2), is not included | Workstream B |
| G3 | PLAN_ROUND_11 R2/F3 | PUMS memory has only a static bound (one 81 x 1.9M float64 weight matrix, about 1.15 GiB). Nothing has been measured | Workstream C |
| G4 | Round 13 CI logs | Pandas 4 deprecation (`inplace` in `DataFrame.rename`, `census/export.py:40`). `cache import` has no MCP route | Workstream D |
| G5 | Scheduled CI | The first weekly live-validation run is due 2026-11-09 | Confirm it after the round lands; triage it as in Round 13 A1 |

Same rules as before: tolerances, bands and thresholds are not tuned to make a check pass. Every failure is classified as a defect, a Census revision, rounding, or flakiness.

## 1. The improvement this round delivers

A researcher can pre-register a multi-state, price-adjusted analysis as a manifest and run it confirmatorily from the CLI or MCP, with the same blocking deviations, replicate policy and provenance as the single-state route. The Michigan/New York study then becomes the worked confirmatory example. The validation battery covers Michigan, and PUMS memory use is measured rather than estimated.

No new estimator families or data providers. PLAN_ROUND_12 sec. 10 deferrals still apply.

## 2. Workstreams

| Priority | Workstream | Owner |
| :--- | :--- | :--- |
| P0 | A. Multi-state manifests with a place-adjustment step | agent A |
| P1 | B. Michigan in the validation battery | agent B |
| P1 | C. PUMS memory benchmark (R2) | agent C |
| P2 | D. Small items: pandas 4 deprecations, MCP `cache_import` | agent C (after C) |

A, B and C touch disjoint modules (`manifest`/workflows, `validation`/fixtures, `pums`/scripts) and run in parallel in separate git worktrees.

## 3. Workstream A: confirmatory multi-state analyses (P0)

- **A1. Several states in `geography`.** Accept `"states": ["26", "36"]` as an alternative to `"state"`. A manifest gives one of the two, never both. The extraction runs per state and stacks the results. The manifest hash, validation errors and run status behave as they do for one state. Single-state manifests and their hashes are unchanged; the hash of the existing example manifest must not change.
- **A2. Place adjustment as a declared step.** For example `"adjustments": [{"type": "rpp", "variables": [...], "rpp_year": 2023}]`. It reuses the Round 13 `place_adjust_frame` core (CBSA membership, nonmetro fallback, replicate scaling), not a second implementation. RPP provenance is recorded in the manifest output, including the custom-table hash when a custom table is used.
- **A3. Derived variables** if the manifest cannot already express `pct_bachelors` from B15002 cells: reuse the existing `derive` machinery and its replicate evaluation.
- **A4. Example and test.**
- Add `examples/manifests/mi_ny_income_education.json` (pre-registered hypothesis: positive pct_bachelors slope on RPP-adjusted income).
- Add a frozen-fixture offline test of the whole route, and a `CENSUS_LIVE_TESTS=1` live test.
- Update `docs/case_study_mi_ny.md` so the confirmatory manifest route comes first, with the exploratory route kept as the sensitivity analysis.
- Done when the live confirmatory run reproduces the Round 13 coefficient (811.0, 90% CI [684.3, 937.7]) or explains any difference.

## 4. Workstream B: Michigan in the validation battery (P1)

Add `"26"` to the battery's states, re-record the frozen fixtures live (`--record`), and report how much the fixture set grows. Rerun the battery live and offline. A new failure is triaged before anything else; no tolerance changes. Regenerate `docs/audits/validation_report.md`.

## 5. Workstream C: PUMS memory benchmark (P1)

Add `scripts/pums_memory_benchmark.py`, using bounded synthetic PUMS-shaped CSVs only (no downloads):
- sizes 100k, 500k and 1.9M person rows;
- peak RSS per phase (download or read, parse, concatenate, weight matrix, tabulation);
- several chunk sizes, with identical estimates across chunk sizes verified;
- platform and dependency versions recorded.

Write the results to `docs/audits/pums_memory.md`, with a documented limit and a recommendation. Change code only if the measurement shows a defect, such as a needless copy. No streaming rewrite unless the owner approves one.

## 6. Workstream D: small items (P2)

- Fix pandas 4 deprecations found by running the suite with `-W error::FutureWarning` and the Pandas4Warning class.
- Add a `cache_import` MCP tool (full profile) wrapping `census.cache.import_frozen`, with the same safety rules as the CLI: atomic, and a synthetic import into the default cache needs an explicit flag.

## 7. Release

v0.13.0 with a Zenodo DOI: code review (`/code-review high`), fixes, full gate (`--jobs 0`), PR, CI, merge, tag, Zenodo confirmation.

## 8. Defaults taken without asking (owner may override)

| ID | Default |
| :--- | :--- |
| D14-1 | Multi-state manifests stack states; cross-state models need an explicit state term in the formula (no automatic fixed effects) |
| D14-2 | RPP year defaults to the latest bundled year, and the manifest records it. A mismatch with the ACS period is a note, not a blocking deviation |
| D14-3 | The Michigan fixtures are committed if the fixture set stays under about 5 MB in total; otherwise the agent reports the size first |

---

## 9. Implementation status (2026-11-08)

| Slice | Result |
| :--- | :--- |
| A multi-state manifests | `states`, derivations on replicates, declared RPP `adjustments` (one shared implementation with `place_adjust_frame`). Live MI/NY confirmatory run: no deviations, identical to Round 13 (811.0 [684.3, 937.7]). Existing manifest hashes unchanged |
| B Michigan | Added to the battery; fixtures 2.0 to 2.2 MB; 10/10 checks pass live and offline; no tolerance changes |
| C PUMS memory (R2) | Measured: about 275 MiB + 2,829 B/row; 1.9M rows peaks at 5.4 GiB after removing avoidable copies (was 6.4). 128 GiB holds every state's records at once. R2 closed |
| D small items | MCP `cache_import`; the only pandas 4 deprecation in census/ fixed |
| Review | `/code-review high`: 10 findings fixed with tests (`tests/test_review_fixes_r14.py`). Parent change: `create_manifest` never overwrites its input |
| Gate | Full `--gate --jobs 0`: PASS, 17 min; coverage table unchanged |

**Deferred:** adjustments limited to one release and not combinable with aggregation; `run_manifest` does not fetch VRE replicates itself; the offline multi-state test uses frozen CBSA membership (the overlay is covered by the live test).
2 changes: 1 addition & 1 deletion census/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Headless Census ACS data client and processing library."""

__version__ = "0.12.0"
__version__ = "0.13.0"

from census.api import (
DATASETS,
Expand Down
28 changes: 5 additions & 23 deletions census/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
from census import errors
from census import workflows
from census.api import CensusAPIClient, infer_dataset
from census.cache import get_default_cache, import_frozen, read_frozen
from census.cache import get_default_cache
from census.concordance import BLOCKING_KINDS, apply_overrides, build_concordance, validate_all_crosswalks
from census.metadata import available_vintages, describe_variable
from census.pipeline import (
Expand Down Expand Up @@ -386,33 +386,15 @@ def cmd_tool(args: argparse.Namespace) -> int:

def cmd_cache(args: argparse.Namespace) -> int:
cache = get_default_cache()
if args.action == "import":
# same rules as the MCP cache_import tool: atomic; synthetic into the default cache needs the flag
return _emit(workflows.import_into_cache(cache, args.source, args.allow_default_cache,
allow_hint="--allow-default-cache"), args.json)
if cache is None:
if args.action == "import":
return _emit(errors.error_result(errors.INVALID_INPUT, "The query cache is disabled "
"(CENSUS_CACHE_DISABLED); nothing to import into."), args.json)
return _emit({"success": True, "cache": "disabled"}, args.json)
if args.action == "clear":
# responses and the synthetic marker: an empty cache has no synthetic data left to label
return _emit({"success": True, **cache.clear_all(), "path": cache.path}, args.json)
if args.action == "import":
if not args.source:
return _emit(errors.error_result(errors.INVALID_INPUT, "cache import needs a frozen file or directory"),
args.json)
try:
frozen = read_frozen(args.source) # every file validated before anything is written
if (any(f["synthetic"] is not None for f in frozen) and not os.getenv("CENSUS_CACHE_PATH")
and not args.allow_default_cache):
return _emit(errors.error_result(
errors.INVALID_INPUT,
f"Refusing to import SYNTHETIC responses into the default query cache ({cache.path}): the "
"marker would label every later result built from that cache synthetic, including real "
"Census data. Set CENSUS_CACHE_PATH to a separate cache file (e.g. export "
"CENSUS_CACHE_PATH=$PWD/synthetic_cache.db), or pass --allow-default-cache.",
next_step="set CENSUS_CACHE_PATH and run cache import again"), args.json)
result = import_frozen(cache, frozen)
except (OSError, ValueError) as exc:
return _emit(errors.error_result(errors.INVALID_INPUT, f"{type(exc).__name__}: {exc}"), args.json)
return _emit({"success": True, **result, **cache.stats()}, args.json)
return _emit({"success": True, **cache.stats()}, args.json)


Expand Down
4 changes: 3 additions & 1 deletion census/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ def format_dataframe(
if variable_descriptions is not None
else get_variable_descriptions(table_code)
)
df.rename(columns=descriptions, inplace=True)
# The caller's frame is relabelled in place, as rename(..., inplace=True) did before
# pandas 3.1 deprecated that keyword (PDEP-8); no data are copied.
df.columns = [descriptions.get(c, c) for c in df.columns]
return df


Expand Down
Loading
Loading