diff --git a/CHANGELOG.md b/CHANGELOG.md index 742e164..76f12fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,26 @@ each tagged release (`v`) is archived on Zenodo with its own DOI (see [Releasing](#releasing)). Plans and acceptance criteria for each round are in `PLAN_ROUND_.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 `.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 diff --git a/CITATION.cff b/CITATION.cff index 468c289..f19acd1 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -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 diff --git a/PLAN_ROUND_14.md b/PLAN_ROUND_14.md new file mode 100644 index 0000000..c19e622 --- /dev/null +++ b/PLAN_ROUND_14.md @@ -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). diff --git a/census/__init__.py b/census/__init__.py index f9fa0b0..0c1f8d6 100644 --- a/census/__init__.py +++ b/census/__init__.py @@ -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, diff --git a/census/__main__.py b/census/__main__.py index d5a8904..f0dfc68 100644 --- a/census/__main__.py +++ b/census/__main__.py @@ -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 ( @@ -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) diff --git a/census/export.py b/census/export.py index 3f936c7..2c0fb42 100644 --- a/census/export.py +++ b/census/export.py @@ -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 diff --git a/census/manifest.py b/census/manifest.py index e2da5df..58fd463 100644 --- a/census/manifest.py +++ b/census/manifest.py @@ -11,12 +11,15 @@ "created": "2026-11-02T12:00:00Z", # ISO 8601 "releases": [2019], # ACS 5-year end years "geography": {"level": "tract", "state": "36", "codes": ["061"]}, + # or several states, stacked: {"level": "county", "states": ["26", "36"]} "variables": [ # YEAR:CODE or concept IDs {"name": "income", "ref": "2019:B19013_001"}, {"name": "pop", "ref": "c_0123456789ab", "table": "B01003"} ], "aggregation": {"mapping": {"": ""}}, # optional "derivations": [{"name": "rate", "op": "proportion", "num": "a", "den": "b"}], + "adjustments": [{"type": "rpp", "variables": ["income"], # optional; after exclusions + "rpp_year": 2019, "areas": "county_metro" | "state"}], "exclusions": [{"rule": "cv_screen", "threshold": 30, "variables": ["income"], "action": "flag" | "exclude"}, {"rule": "geoid_exclude", "geoids": ["..."]}], @@ -32,6 +35,36 @@ "description": "...", "generator": "..."} } +``geography`` takes ``state`` (one state, or ``"all"``) or ``states`` (a list +of 2-digit FIPS codes), never both. With ``states`` each table is extracted +per state and the states are stacked into one frame; ``codes`` are then not +allowed (county codes are per state). Pooled models get no automatic state +term: a cross-state formula states it explicitly, e.g. ``C(GEOID.str[:2])``. + +``adjustments`` (optional) are pre-registered transformations applied to the +analysis frame after derivations and exclusions (so a pre-registered exclusion +can remove a unit without an RPP area; a remaining unit without one is a +blocking ``adjustment_failed``). ``"rpp"`` divides +dollar variables (``variables``: manifest variable or derivation names), their +SEs and any supplied replicates by the BEA Regional Price Parity / 100 of each +row's area (``areas``: ``"county_metro"``, the default for county geographies, +or ``"state"``), with the same code as the ``place_adjust_frame`` tool +(:func:`census.workflows_microdata.place_adjust`). ``rpp_year`` is required so the +manifest hash fixes the price year (:func:`create_manifest` fills in the latest +year in the RPP table when it is omitted, before hashing); the year used, every +RPP area and the RPP table's SHA-256 (bundled checksum, or the custom table's path and hash) are +recorded in ``run.json`` and ``bundle.json`` (``adjustments``). An RPP year +other than the release end year is a note, not a deviation. One release only. + +When replicates are supplied and the manifest has derivations, each +derivation absent from the replicate frame is evaluated on the replicates of +its inputs (replicate rows named by manifest variable name or ACS code), and +its SE becomes the exact replicate SE where its inputs have replicates. +``replicates.derived_on_replicates`` lists only derivations that received a +replicate SE, with per-derivation counts (``derived_on_replicates_rows``: +rows_replicate of rows in the analysed frame); a failure of that step is a +blocking ``replicates_incomplete`` deviation. + ``data_source`` (optional) *declares* where the input data come from. The labels follow where the data actually came from (:func:`observed_data_source`): data are synthetic when the client that served them carries a true @@ -91,7 +124,7 @@ BLOCKING_DEVIATIONS = ("blocked_variable", "extraction_failed", "variable_missing_from_extraction", "aggregation_failed", "analysis_frame_failed", "analysis_not_run", "replicates_unavailable", "analysis_failed", "replicates_incomplete", - "hypothesis_not_tested", "failed_calls", "data_source_mismatch") + "hypothesis_not_tested", "failed_calls", "data_source_mismatch", "adjustment_failed") DATA_SOURCE_KEYS = ("synthetic", "description", "generator") STATUS_EXPLORATORY = "exploratory" @@ -108,6 +141,12 @@ } STATISTICS = ("coefficient", "correlation", "trend_slope", "z_difference") EXCLUSION_RULES = ("cv_screen", "geoid_exclude") +ADJUSTMENT_TYPES = ("rpp",) +RPP_ADJUSTMENT_KEYS = ("type", "variables", "rpp_year", "areas") +# geography levels whose GEOIDs start with the state FIPS (state RPP areas; usable with +# geography.states). ZCTA GEOIDs are not state-prefixed and ZCTAs can cross state lines. +_STATE_PREFIXED_LEVELS = ("state", "county", "tract", "block group", "place") +FIPS2_RE = re.compile(r"^\d{2}$") FORMULA_FUNCTIONS = {"C", "I", "np", "log", "exp", "sqrt", "center", "standardize", "scale", "Q"} REFERENCES = { @@ -167,6 +206,159 @@ def _dataset_for(table: str) -> str: return infer_dataset(table) +def manifest_states(geography: Mapping[str, Any]) -> List[Optional[str]]: + """The geography's state selections: ``states`` as given, else ``[state]`` + (``[None]`` for levels that need no state).""" + if geography.get("states") is not None: + return [str(s) for s in geography["states"]] + return [geography.get("state")] + + +def manifest_entities(geography: Mapping[str, Any], years: Optional[List[int]] = None) -> List[Any]: + """Request entities for a manifest geography: one state's entities, or each + listed state's entities in order (extracted per state and stacked).""" + from .pipeline import build_entities + entities: List[Any] = [] + for st in manifest_states(geography): + entities.extend(build_entities(geography["level"], st, geography.get("codes"), years=years)) + return entities + + +def _check_geography(geo: Mapping[str, Any], errors: List[str]) -> None: + from .pipeline import expand_states, is_nationwide, normalize_geo_level + if not isinstance(geo, Mapping) or not geo.get("level"): + errors.append("geography.level is required") + return + state = geo.get("state") + if isinstance(state, str) and not is_nationwide(state): + bad = [p for p in state.split(",") if not FIPS2_RE.match(p.strip())] + if bad: + errors.append(f"geography.state: {bad[0]!r} must be a state FIPS code of exactly 2 digits " + "(e.g. \"06\", not \"6\"), or \"all\"") + return + states = geo.get("states") + if states is not None: + if "state" in geo: + errors.append("geography: give either 'state' or 'states', not both") + return + if not isinstance(states, list) or not states or not all(isinstance(s, str) for s in states): + errors.append("geography.states must be a non-empty list of 2-digit state FIPS strings, " + "e.g. [\"26\", \"36\"]") + return + for s in states: + if is_nationwide(s) or "," in s: + errors.append(f"geography.states: {s!r} must be one 2-digit state FIPS code (for every " + "state use \"state\": \"all\")") + return + if not FIPS2_RE.match(s): + errors.append(f"geography.states: {s!r} must be a state FIPS code of exactly 2 digits " + "(e.g. \"06\", not \"6\")") + return + try: + expand_states(s) + except ValueError as exc: + errors.append(f"geography.states: {exc}") + return + if len(set(states)) != len(states): + errors.append("geography.states lists a state more than once") + return + if geo.get("codes"): + errors.append("geography: 'codes' cannot be combined with 'states' (codes are per state); " + "list GEOIDs in a geoid_exclude exclusion or use one state per manifest") + return + try: + level = normalize_geo_level(geo["level"]) + except Exception: # noqa: BLE001 - reported by build_entities below + level = geo["level"] + if level == "zcta": + errors.append("geography.states does not apply to level 'zcta': ZCTA GEOIDs are not state-prefixed " + "and ZCTAs can cross state lines, so they cannot be extracted per state and stacked") + return + if level not in _STATE_PREFIXED_LEVELS: + errors.append(f"geography.states does not apply to level {geo['level']!r} (national geography)") + return + try: + manifest_entities(geo) + except Exception as exc: + errors.append(f"geography: {exc}") + + +def default_rpp_areas(level: Optional[str]) -> str: + """Default RPP ``areas`` of an adjustment for a (normalized) geography level: the + county's metro/nonmetro area for counties, else the state (validation and runs).""" + return "county_metro" if level == "county" else "state" + + +def latest_rpp_year() -> Optional[int]: + """Latest year in the active RPP table (filled in by create_manifest when ``rpp_year`` is omitted), or None.""" + from . import rpp + years = rpp._years() + return years[-1] if years else None + + +def _check_adjustments(manifest: Mapping[str, Any], names: List[str], errors: List[str]) -> None: + adjustments = manifest.get("adjustments") + if adjustments is None: + return + if not isinstance(adjustments, list): + errors.append("adjustments must be a list of adjustment steps") + return + from .pipeline import normalize_geo_level + geo = manifest.get("geography") or {} + try: + level = normalize_geo_level(geo.get("level", "")) + except Exception: # noqa: BLE001 - geography errors are reported by _check_geography + level = geo.get("level") + if adjustments and len(manifest.get("releases") or []) != 1: + errors.append("adjustments: an RPP adjustment needs exactly one release (dollars of one end year)") + if adjustments and manifest.get("aggregation") is not None: + errors.append("adjustments cannot be combined with aggregation (RPP areas are assigned per GEOID)") + adjusted: List[str] = [] + for i, adj in enumerate(adjustments): + if not isinstance(adj, Mapping): + errors.append(f"adjustments[{i}] must be an object") + continue + if adj.get("type") not in ADJUSTMENT_TYPES: + errors.append(f"adjustments[{i}]: type must be one of {ADJUSTMENT_TYPES}") + continue + for k in sorted(set(adj) - set(RPP_ADJUSTMENT_KEYS)): + errors.append(f"adjustments[{i}]: unknown key {k!r}; allowed {RPP_ADJUSTMENT_KEYS}") + vars_ = adj.get("variables") + if not isinstance(vars_, list) or not vars_: + errors.append(f"adjustments[{i}]: variables must be a non-empty list of manifest variable names") + vars_ = [] + for v in vars_: + if str(v) not in names: + errors.append(f"adjustments[{i}]: unknown variable {v!r}") + elif str(v) in adjusted: + errors.append(f"adjustments[{i}]: {v!r} is adjusted twice") + adjusted.append(str(v)) + areas = adj.get("areas", default_rpp_areas(level)) + if areas not in ("county_metro", "state"): + errors.append(f"adjustments[{i}]: areas must be 'county_metro' or 'state'") + elif areas == "county_metro" and level != "county": + errors.append(f"adjustments[{i}]: areas 'county_metro' needs geography level 'county'") + elif areas == "state" and level not in _STATE_PREFIXED_LEVELS: + errors.append(f"adjustments[{i}]: areas 'state' needs a state-based geography level " + f"{_STATE_PREFIXED_LEVELS}") + year = adj.get("rpp_year") + try: + from . import rpp + years = rpp._years() + except Exception as exc: # noqa: BLE001 - e.g. a bundled-data integrity failure + errors.append(f"adjustments[{i}]: RPP table unreadable ({type(exc).__name__}: {exc})") + continue + if not years: + errors.append(f"adjustments[{i}]: no RPP data available") + elif year is None: + errors.append(f"adjustments[{i}]: rpp_year is required (it fixes the price level in the " + f"pre-registration); the latest year in the RPP table is {years[-1]}. " + "create_manifest fills it in when omitted") + elif not isinstance(year, int) or isinstance(year, bool) or year not in years: + errors.append(f"adjustments[{i}]: rpp_year must be an integer year in the RPP table " + f"({years[0]}-{years[-1]})") + + def tables_and_refs(manifest: Mapping[str, Any]) -> Dict[str, List[str]]: """table code -> variable refs (in manifest order).""" out: Dict[str, List[str]] = {} @@ -294,15 +486,7 @@ def validate_manifest(manifest: Mapping[str, Any], resolve: bool = True, client: releases = manifest.get("releases") or [] if not releases or not all(isinstance(r, int) for r in releases): errors.append("releases must be a non-empty list of ACS 5-year end years (integers)") - geo = manifest.get("geography") or {} - if not geo.get("level"): - errors.append("geography.level is required") - else: - try: - from .pipeline import build_entities - build_entities(geo["level"], geo.get("state"), geo.get("codes")) - except Exception as exc: - errors.append(f"geography: {exc}") + _check_geography(manifest.get("geography") or {}, errors) level = manifest.get("confidence_level", 0.90) if not isinstance(level, (int, float)) or not 0 < float(level) < 1: errors.append("confidence_level must be in (0, 1), e.g. 0.90") @@ -346,6 +530,7 @@ def validate_manifest(manifest: Mapping[str, Any], resolve: bool = True, client: agg = manifest.get("aggregation") if agg is not None and not isinstance(agg.get("mapping"), Mapping): errors.append("aggregation.mapping must be an object GEOID -> group id") + _check_adjustments(manifest, names, errors) for i, ex in enumerate(manifest.get("exclusions") or []): if ex.get("rule") not in EXCLUSION_RULES: @@ -417,16 +602,50 @@ def create_manifest( When valid and ``path`` is given (or the input was a dict and ``path`` is given) the manifest is written there as pretty JSON. Commit the file to the repository to fix the pre-registration (PLAN_ROUND_8 decision 3). - Returns ``{"hash", "path", "errors", "valid"}``. + + An RPP adjustment without ``rpp_year`` gets the latest year in the active RPP + table before validation and hashing, so the hash fixes the applied year; the + result says so (``filled``, ``notes``) and the completed manifest is written to + ``path``, or, when no ``path`` is given, to a new sibling file + ``.completed.json`` (the input file is never overwritten; a dict input + without ``path`` gets it in ``manifest``). Commit and run the completed file. + Returns ``{"hash", "path", "errors", "valid", "notes", "filled"}`` (and + ``"manifest"`` when something was filled in). """ manifest = load_manifest(path_or_dict) + filled: List[Dict[str, Any]] = [] + adjustments = manifest.get("adjustments") + if isinstance(adjustments, list): + year = None + for i, adj in enumerate(adjustments): + if isinstance(adj, dict) and adj.get("type") == "rpp" and adj.get("rpp_year") is None: + try: + year = year or latest_rpp_year() + except Exception: # noqa: BLE001 - an unreadable RPP table is reported by validation + year = None + if year is not None: + adj["rpp_year"] = int(year) + filled.append({"adjustment": i, "rpp_year": int(year)}) errors = validate_manifest(manifest, resolve=resolve, client=client) out_path = str(path) if path else (str(path_or_dict) if not isinstance(path_or_dict, Mapping) else None) - if not errors and path: - Path(path).parent.mkdir(parents=True, exist_ok=True) - Path(path).write_text(json.dumps(manifest, indent=2, sort_keys=True, ensure_ascii=False) + "\n", - encoding="utf-8", newline="\n") - return {"hash": manifest_hash(manifest), "path": out_path, "errors": errors, "valid": not errors} + write_to = path + if not path and filled and out_path: + src = Path(out_path) + write_to = str(src.with_name(src.stem + ".completed" + (src.suffix or ".json"))) + out_path = write_to + if not errors and write_to: + Path(write_to).parent.mkdir(parents=True, exist_ok=True) + Path(write_to).write_text(json.dumps(manifest, indent=2, sort_keys=True, ensure_ascii=False) + "\n", + encoding="utf-8", newline="\n") + notes = [f"adjustments[{f['adjustment']}]: rpp_year was omitted; filled in with {f['rpp_year']}, the latest " + "year in the RPP table, before hashing" + (f" (written to {write_to})" if write_to and not errors + else "; commit the completed manifest") + for f in filled] + out = {"hash": manifest_hash(manifest), "path": out_path, "errors": errors, "valid": not errors, + "notes": notes, "filled": filled} + if filled: + out["manifest"] = manifest + return out # --------------------------------------------------------------------------- @@ -589,7 +808,7 @@ def run_manifest( replicates: Any = None, use_cache: bool = True, replicates_synthetic: Optional[bool] = None, ) -> Dict[str, Any]: """Execute a manifest exactly: plan -> extraction -> analysis frame -> - exclusions -> analyses -> hypothesis tests with the pre-registered + derivations on replicates -> exclusions -> adjustments -> analyses -> hypothesis tests with the pre-registered correction -> results bundle (tables, figures, methods.md). ``replicates``: optional pre-fetched replicate long frame (from @@ -612,7 +831,7 @@ def run_manifest( """ from .analysis import analysis_frame from .multitest import adjust_pvalues - from .pipeline import ResearchOptions, build_entities, plan_extraction, run_batch + from .pipeline import ResearchOptions, plan_extraction, run_batch from .report_tables import data_citation m = load_manifest(manifest) @@ -633,7 +852,7 @@ def run_manifest( corr_method = corr.get("method", "holm") releases = [int(r) for r in m["releases"]] geo = m["geography"] - entities = build_entities(geo["level"], geo.get("state"), geo.get("codes"), years=releases) + entities = manifest_entities(geo, years=releases) opts = ResearchOptions(use_cache=use_cache, include_codebook=True, concordance_mode=m.get("concordance_mode", "fail")) @@ -646,6 +865,8 @@ def run_manifest( plan = plan_extraction(table, entities, releases, "panel", opts, variables=refs, client=client) plan_pub = {k: v for k, v in plan.items() if not k.startswith("_")} extraction[table] = {"plan": plan_pub} + if geo.get("states") is not None: # one request set per state, stacked into one panel + extraction[table]["states"] = manifest_states(geo) if not plan.get("success"): deviations.append({"kind": "blocked_variable", "table": table, "variables": refs, "detail": plan.get("message") or plan.get("error_code")}) @@ -707,7 +928,20 @@ def run_manifest( except Exception as exc: deviations.append({"kind": "analysis_frame_failed", "detail": str(exc)}) - # 4. exclusions + # 3b. derivations on supplied replicates + work_reps = replicates + derived_on_replicates: Dict[str, Dict[str, int]] = {} + if frame is not None and replicates is not None and m.get("derivations"): + try: + frame, work_reps, derived_on_replicates = _derive_replicates(frame, replicates, m) + except Exception as exc: # noqa: BLE001 - data problems are deviations; run_manifest never raises on them + # replicate SEs of the derivations are unavailable: blocking, as when a strict replicate + # analysis meets them; the analyses run on the published SEs and record their own outcome + deviations.append({"kind": "replicates_incomplete", "detail": "derivations on replicates failed: " + f"{type(exc).__name__}: {exc}"}) + + # 4. exclusions (before adjustments: a pre-registered exclusion can remove a unit without an + # RPP area; the CV screen gives the same rows either way, since the RPP factor leaves CVs unchanged) exclusion_log: List[Dict[str, Any]] = [] if frame is not None: from .models import reliability_screen @@ -731,6 +965,17 @@ def run_manifest( frame = frame[keep].reset_index(drop=True) exclusion_log.append(entry) + # 4b. pre-registered adjustments (a unit still without an RPP area blocks: adjustment_failed) + adjustment_log: List[Dict[str, Any]] = [] + if frame is not None and m.get("adjustments"): + frame, work_reps, adjustment_log, failure = _apply_adjustments(frame, work_reps, m, releases[0]) + if failure: + deviations.append({"kind": "adjustment_failed", "detail": failure}) + frame = None + if frame is not None: # replicate-SE coverage of the units analysed + counts = {name: _replicate_rows(frame, name) for name in derived_on_replicates} + derived_on_replicates = {k: v for k, v in counts.items() if v["rows_replicate"]} + # 5. analyses available = set(columns) | {str(d["name"]) for d in m.get("derivations") or []} results: Dict[str, Any] = {} @@ -751,7 +996,7 @@ def run_manifest( "detail": "manifest requires replicate SEs; none supplied, analysis not run"}) continue try: - results[aid] = _run_analysis(a, frame, level, replicates, corr_method) + results[aid] = _run_analysis(a, frame, level, work_reps, corr_method) except Exception as exc: kind = "replicates_incomplete" if isinstance(exc, IncompleteReplicatesError) else "analysis_failed" deviations.append({"kind": kind, "analysis": aid, "detail": f"{type(exc).__name__}: {exc}"}) @@ -799,19 +1044,127 @@ def run_manifest( "rows": int(len(replicates)), "sha256": digest} if digest is None: rep_info["sha256_unavailable"] = reason + if derived_on_replicates: + rep_info["derived_on_replicates"] = list(derived_on_replicates) + rep_info["derived_on_replicates_rows"] = derived_on_replicates + scaled = [v for a in adjustment_log for v in a["variables"]] + if scaled: + rep_info["scaled_by_adjustments"] = scaled tagged = {aid: _tag(r, h, aid, status) for aid, r in results.items()} run_info = {"manifest_hash": h, "analysis_status": status, "deviations": deviations, "exclusions": exclusion_log, "extraction": extraction, "n_units": None if frame is None else int(len(frame)), "title": m.get("title"), "data_source": source, "replicates": rep_info} + if m.get("adjustments"): + run_info["adjustments"] = adjustment_log if not complete: run_info["status_note"] = INCOMPLETE_STATUS_TEXT tables_used = sorted(tables_and_refs(m)) citation = data_citation(releases, tables_used, synthetic=source["synthetic"], description=source.get("description")) bundle = _write_bundle(out / "bundle", m, h, tagged, hyp_df, run_info, citation) - return {"success": complete, "analysis_status": status, "data_source": source, "replicates": rep_info, - "manifest_hash": h, "deviations": deviations, "hypotheses": hyp_df, "results": tagged, - "frame": frame, "bundle": bundle, "output_dir": str(out), "errors": []} + out_res = {"success": complete, "analysis_status": status, "data_source": source, "replicates": rep_info, + "manifest_hash": h, "deviations": deviations, "hypotheses": hyp_df, "results": tagged, + "frame": frame, "bundle": bundle, "output_dir": str(out), "errors": []} + if m.get("adjustments"): + out_res["adjustments"] = adjustment_log + return out_res + + +def _replicate_rows(frame: pd.DataFrame, name: str) -> Dict[str, int]: + """{"rows_replicate": rows whose ``name`` SE is a replicate SE, "rows": all rows}.""" + col = frame.get(f"{name}_method") + k = 0 if col is None else int((col.astype(str) == "replicate").sum()) + return {"rows_replicate": k, "rows": int(len(frame))} + + +def _derive_replicates(frame: pd.DataFrame, replicates: Any, + m: Mapping[str, Any]) -> Tuple[pd.DataFrame, Any, Dict[str, Dict[str, int]]]: + """Evaluate manifest derivations missing from the supplied replicate frame on the + replicates of their inputs (:func:`census.analysis.attach_replicates`, the same + evaluation as build_analysis_frame). Inputs are found by manifest variable name or + by ACS code (``B15002_015``). The derived variables' SEs become exact replicate SEs; + other variables keep theirs. Derivations whose inputs lack replicates are left + out (a replicate analysis using them then stops as replicates_incomplete). + Returns (frame, replicates, {derivation: _replicate_rows counts}) listing only + derivations that received a replicate SE for at least one row. Raises on + malformed replicates (run_manifest records that as a deviation).""" + from .analysis import _default_name, attach_replicates + reps = replicates if isinstance(replicates, pd.DataFrame) else pd.DataFrame(replicates) + if "variable" not in reps.columns: + return frame, replicates, {} + have = {_default_name(v) for v in reps["variable"].astype(str)} + + def refs_of(d: Mapping[str, Any]) -> List[str]: + return [str(r) for part in ("num", "den") + for r in (d[part] if isinstance(d.get(part), list) else [d[part]] if d.get(part) is not None else [])] + + var_map: Dict[str, str] = {} # manifest name -> replicate variable + for v in m.get("variables") or []: + name, mm = str(v["name"]), YEAR_CODE_RE.match(str(v.get("ref", ""))) + if name in have: + var_map[name] = name + elif mm and _default_name(mm.group(2)) in have: # B19013_001E, DP04_0046PE as normalized + var_map[name] = _default_name(mm.group(2)) + todo: List[Dict[str, Any]] = [] + for d in m.get("derivations") or []: + name, refs = str(d["name"]), refs_of(d) + if name in have: + var_map[name] = name + elif refs and all(r in var_map or r in {str(t["name"]) for t in todo} for r in refs): + todo.append(dict(d)) + if not todo: + return frame, replicates, {} + new_frame, long = attach_replicates(frame, reps, variables=var_map, derive=todo, replace_se=True) + counts = {str(d["name"]): _replicate_rows(new_frame, str(d["name"])) for d in todo} + derived = [name for name, c in counts.items() if c["rows_replicate"]] + if not derived: + return frame, replicates, {} + out = frame.copy() + for name in derived: # only the derived variables take replicate SEs + for suffix in ("_se", "_cv", "_method"): + if f"{name}{suffix}" in new_frame.columns: + out[f"{name}{suffix}"] = new_frame[f"{name}{suffix}"].to_numpy() + # derived rows, and rows of inputs found by ACS code renamed to their manifest name + added = long[long["variable"].isin(derived + [n for n, c in var_map.items() if c != n])] + combined = pd.concat([reps, added], ignore_index=True) + combined.attrs = dict(getattr(reps, "attrs", {}) or {}) + return out, combined, {name: counts[name] for name in derived} + + +def _apply_adjustments(frame: pd.DataFrame, replicates: Any, m: Mapping[str, Any], + release: int) -> Tuple[Optional[pd.DataFrame], Any, List[Dict[str, Any]], Optional[str]]: + """Run the manifest's ``adjustments`` in order with the place_adjust_frame core. + Returns (frame, replicates, log, failure message or None).""" + from . import rpp + from .pipeline import normalize_geo_level + from .workflows_microdata import place_adjust + level = normalize_geo_level(m["geography"]["level"]) + log: List[Dict[str, Any]] = [] + for i, adj in enumerate(m.get("adjustments") or []): + variables = [str(v) for v in adj["variables"]] + areas = adj.get("areas", default_rpp_areas(level)) + rpp_year = int(adj["rpp_year"]) # required by validation, so the hash fixes it + try: + res = place_adjust(frame, variables, release, areas=areas, replicates=replicates, rpp_year=rpp_year) + except Exception as exc: # noqa: BLE001 - recorded as a blocking deviation, never substituted + return None, replicates, log, f"adjustments[{i}] ({adj.get('type')}): {type(exc).__name__}: {exc}" + frame = res["frame"] + if replicates is not None: + replicates = res["replicates"] + notes = list(res["notes"]) + info = rpp.price_parity_info() + table: Dict[str, Any] = {"source": "custom" if res["custom_table"] else "bundled"} + if res["custom_table"]: + table.update(res["custom_table"]) + else: + table["sha256"] = (info.get("csv_sha256") or {}).get("rpp.csv") + log.append({"type": adj["type"], "variables": variables, "areas": areas, "dollar_year": int(release), + "rpp_year": res["rpp_year"], "rpp_year_source": "manifest", + "rpp_year_matches_release": res["rpp_year"] == int(release), + "rpp_areas": res["rpp_areas"], "boundary_source": res["boundary_source"], + "rpp_table": table, "rows": int(len(frame)), "notes": notes, + "advisories": res["advisories"], "applied_by_default": False}) + return frame, replicates, log, None def effective_data_source(manifest: Mapping[str, Any], client: Any = None) -> Dict[str, Any]: @@ -977,13 +1330,15 @@ def _write_bundle(folder: Path, m, h, results, hyp_df, run_info, citation) -> Di payload["hypotheses"] = {"manifest_hash": h, "rows": hyp_df} payload["run"] = run_info from .report_tables import results_bundle + provenance = {"manifest_hash": h, "analysis_status": run_info["analysis_status"], + "deviations": len(run_info["deviations"]), "data_source": run_info.get("data_source"), + "replicates": run_info.get("replicates")} + if "adjustments" in run_info: # RPP provenance: years, areas, table hash + provenance["adjustments"] = [{k: a[k] for k in ("type", "variables", "areas", "dollar_year", "rpp_year", + "rpp_year_source", "rpp_areas", "boundary_source", + "rpp_table")} for a in run_info["adjustments"]] return results_bundle(folder, tables=tables, figures=figures_, methods=methods_section(m, run_info), - results=payload, provenance={"manifest_hash": h, - "analysis_status": run_info["analysis_status"], - "deviations": len(run_info["deviations"]), - "data_source": run_info.get("data_source"), - "replicates": run_info.get("replicates")}, - citation=citation) + results=payload, provenance=provenance, citation=citation) # --------------------------------------------------------------------------- @@ -1053,6 +1408,9 @@ def methods_section(manifest: Mapping[str, Any], results: Optional[Mapping[str, description=source.get("description")) lines.append(f"Data are {cite} Geography: {geo.get('level')}" + (f" (state {geo.get('state')})" if geo.get("state") else "") + + (f" (states {', '.join(map(str, geo['states']))}, extracted per state and stacked; no " + "automatic state effects, a state term enters only where a formula names one)" + if geo.get("states") else "") + (f", codes {', '.join(map(str, geo['codes']))}" if geo.get("codes") else "") + ".") if source["synthetic"] and source.get("generator"): lines.append(f"Synthetic data generator: {source['generator']}.") @@ -1071,16 +1429,47 @@ def methods_section(manifest: Mapping[str, Any], results: Optional[Mapping[str, cites.append("vre") lines += ["", "### Measures and uncertainty", "", "Standard errors of published estimates are MOE/1.645 (ACS MOEs are 90% margins)."] + rep_rows = (rep_info or {}).get("derived_on_replicates_rows") or {} for d in m.get("derivations") or []: + c = rep_rows.get(d["name"]) + if c and c["rows_replicate"] >= c["rows"]: + se_text = f"; SE from the derivation evaluated on each replicate (all {c['rows']} units)" + elif c: + se_text = (f"; replicate SE for {c['rows_replicate']} of {c['rows']} units; approximate SE (ACS " + "formulas, Handbook ch. 8) elsewhere") + else: + se_text = "; MOE by the ACS approximation formulas (Handbook ch. 8)" lines.append(f"- `{d['name']}` = {d.get('op', 'auto')} of {d.get('num')}" - + (f" over {d['den']}" if d.get("den") is not None else "") - + "; MOE by the ACS approximation formulas (Handbook ch. 8).") + + (f" over {d['den']}" if d.get("den") is not None else "") + se_text + ".") for ex in m.get("exclusions") or []: if ex["rule"] == "cv_screen": lines.append(f"Observations with CV > {ex.get('threshold', 30)}% on {', '.join(ex.get('variables', []))} " f"were {'excluded' if ex.get('action') == 'exclude' else 'kept and flagged'}.") else: lines.append(f"{len(ex.get('geoids', []))} pre-specified GEOIDs were excluded.") + run_adj = {i: a for i, a in enumerate(run.get("adjustments") or [])} + for i, adj in enumerate(m.get("adjustments") or []): + done = run_adj.get(i) + year = (done or {}).get("rpp_year") or adj.get("rpp_year") + areas = (done or {}).get("areas") or adj.get("areas") or "default" + lines.append(f"- Price adjustment{' (after the exclusions)' if m.get('exclusions') else ''}: " + f"{', '.join(adj.get('variables', []))} divided by the BEA Regional " + f"Price Parity ({year}, U.S. = 100) / 100 of each " + "unit's area" + + (" (the county's metropolitan statistical area, else the state's nonmetropolitan portion; " + "CBSA membership from Census boundaries of the OMB 23-01 delineation)" + if areas == "county_metro" else " (its state)" if areas == "state" else "") + + "; estimates, SEs and any supplied replicates are scaled by the same factor and CVs are " + "unchanged. " + "RPPs are treated as fixed: their own estimation error is not in any SE or interval.") + if done: + if not done.get("rpp_year_matches_release"): + lines.append(f" Note: the {done['rpp_year']} RPP was applied to dollars of {done['dollar_year']} " + "(the release end year).") + lines.append(f" {len(done.get('rpp_areas') or [])} RPP area(s) were used; RPP table " + + (f"custom {done['rpp_table'].get('path')} (SHA-256 {done['rpp_table'].get('sha256')}, " + "not verified)" if done["rpp_table"].get("source") == "custom" + else f"bundled (SHA-256 {done['rpp_table'].get('sha256')})") + ".") lines.append("Rows with missing or undefined estimates were dropped analysis by analysis (complete cases).") lines += ["", "### Analyses", ""] for a in m.get("analyses", []): diff --git a/census/pipeline.py b/census/pipeline.py index 23550b7..9d31cbc 100644 --- a/census/pipeline.py +++ b/census/pipeline.py @@ -90,7 +90,7 @@ from census.stats import add_reliability_columns, clean_census_sentinels, parse_variable_code from census.tables import load_variable_metadata -PACKAGE_VERSION = "0.12.0" +PACKAGE_VERSION = "0.13.0" CONCORDANCE_MODES = ("fail", "strict", "code") MAX_ISSUES_IN_RESULT = 100 diff --git a/census/pums.py b/census/pums.py index 631a26e..5480776 100644 --- a/census/pums.py +++ b/census/pums.py @@ -387,6 +387,7 @@ def read_pums_csv(source: Union[str, bytes, io.IOBase], record: str = "person", handle.close() zf.close() frame = pd.concat(parts, ignore_index=True) if parts else pd.DataFrame(columns=wanted) + del parts # the chunks, before conversion and column reordering make further copies frame = _convert(frame, STRING_COLUMNS | set(weights)) frame = frame[[c for c in wanted if c in frame.columns]] if memory_note: @@ -425,6 +426,9 @@ def fetch_pums( columns (``columns=None``) multiplies that several-fold, and multi-state all-column reads may need tens to hundreds of GB; ``columns=None`` on a zip larger than 200 MB therefore warns (UserWarning, attrs["memory_note"]). + docs/audits/pums_memory.md (scripts/pums_memory_benchmark.py, Round 14) + measures read, weight matrix and tabulation peaks at 100k-1.9M rows: + about 2.8 KB per record with the needed columns (5.3 GiB at 1.9M rows). """ p, rec, year = _product(product), _record(record), int(year) _check_release(year, p, allow_experimental) @@ -608,7 +612,10 @@ def _group_sum(ids: np.ndarray, mat: np.ndarray, n_groups: int) -> np.ndarray: ok = ids >= 0 out = np.zeros((n_groups, mat.shape[1])) if ok.any(): - sums = pd.DataFrame(mat[ok]).groupby(ids[ok]).sum() + # No copy of the n x 81 matrix when every row counts, and copy=False: pandas 3 + # copies a 2-D array passed to DataFrame by default (PLAN_ROUND_14 C). + rows = ok.all() + sums = pd.DataFrame(mat if rows else mat[ok], copy=False).groupby(ids if rows else ids[ok]).sum() out[sums.index.to_numpy()] = sums.to_numpy() return out @@ -634,12 +641,13 @@ def weighted_median(values: np.ndarray, weights: np.ndarray) -> np.ndarray: w = np.asarray(weights, float) if w.ndim == 1: w = w[:, None] - ok = np.isfinite(x) - x, w = x[ok], w[ok] - if not len(x): + keep = np.flatnonzero(np.isfinite(x)) + if not len(keep): return np.full(w.shape[1], np.nan) - order = np.argsort(x, kind="mergesort") - x, w = x[order], w[order] + # One gather of the weight rows (finite values in sorted order) rather than + # two successive copies (PLAN_ROUND_14 C); the order is the same. + keep = keep[np.argsort(x[keep], kind="mergesort")] + x, w = x[keep], w[keep] cum = np.cumsum(w, axis=0) half = cum[-1] / 2.0 reached = cum >= half - 1e-9 * np.abs(half) @@ -799,11 +807,16 @@ def tabulate( defined = _defined(sub, var, x) ids_ok = np.where(defined, ids, -1) est = np.full((G, REPLICATE_COUNT + 1), np.nan) + # One stable sort of the group ids; each group's rows are then a contiguous slice + # in original row order, gathered once (not G arrays of length N). Same rows in + # the same order as masking, so the medians are bit-identical. + order = np.argsort(ids_ok, kind="stable") + bounds = np.searchsorted(ids_ok[order], np.arange(G + 1), side="left") for g in range(G): - rows = ids_ok == g - if rows.any(): - est[g] = (binned_median(x[rows], W[rows], bins) if bins is not None - else weighted_median(x[rows], W[rows])) + rows = order[bounds[g]:bounds[g + 1]] + if len(rows): + xg, wg = x[rows], W[rows] + est[g] = (binned_median(xg, wg, bins) if bins is not None else weighted_median(xg, wg)) n = _group_count(ids_ok, G) notes.append("median by Census bracket interpolation over bins" if bins is not None else "weighted sample median (no interpolation); pass bins for the Census bracket method") diff --git a/census/validation.py b/census/validation.py index 8cd1dd6..94c3e97 100644 --- a/census/validation.py +++ b/census/validation.py @@ -105,7 +105,7 @@ class Battery: """What the battery covers (PLAN_ROUND_7.md section 8, F3 "default battery").""" - states: Tuple[str, ...] = ("06", "36", "48", "17", "56") + states: Tuple[str, ...] = ("06", "36", "48", "17", "56", "26") releases: Tuple[int, ...] = (2019, 2023) # Comparison Profile release -> the earlier, non-overlapping release it compares. cp_releases: Tuple[Tuple[int, int], ...] = ((2019, 2014), (2023, 2018)) @@ -2168,7 +2168,8 @@ def summarize(results: Sequence[ValidationResult]) -> Dict[str, Any]: "**F5 (methodological, criterion revised 2026-11-02):** household income: B19001's $10k-$50k-wide " "brackets are much coarser than the $2,500 distribution Census interpolates (Subject Definitions, " "'Median Income'), so a per-county 2% band measures bracket coarseness, not calculation error (first " - "live run, 2019-2023: 84.1% of counties within 2%, 99.8% within the published B19013 MOE). The check " + "live run, 2019-2023: 84.1% of counties within 2%, 99.8% within the published B19013 MOE; with Michigan " + "added in Round 14: 85.9% and 99.7%). The check " "asserts median relative error < 1% and >= 98% of counties within the published MOE; the 2% share is " "still reported.", "**F9 (informational, census/continuity.py):** the Round 6 continuity check flags real 2019->2023 change " @@ -2176,7 +2177,7 @@ def summarize(results: Sequence[ValidationResult]) -> Dict[str, Any]: "cells), so `run_batch` blocks those extractions in the default concordance mode. Its thresholds are " "documented for adjacent, overlapping releases but are applied to any requested pair.", "**F2 (methodological):** owner-occupied (DP04_0046) county MOEs from the Handbook proportion formula " - "miss the band in 215 of 998 county-years (median ratio 0.88); for every one of them the exact replicate " + "miss the band in 279 of 1,164 county-years (median ratio 0.88); for every one of them the exact replicate " "MOE reproduces the DP MOE within 0.1 point, so the gap is the covariance the approximation ignores. " "Age 65+ (12-cell sum) approximate MOEs are ~4x the DP MOE (near-controlled age totals).", "**F6:** Census's implied R-CPI-U-RS factor exceeds ours by 0.008% (2018->2023: 1.21729 = 449.3/369.1 " diff --git a/census/workflows.py b/census/workflows.py index f8bc310..b9ab956 100644 --- a/census/workflows.py +++ b/census/workflows.py @@ -19,7 +19,7 @@ from census.aggregate import NonAdditiveError, aggregate_then_derive from census.aggregate import aggregate_geographies as _aggregate_geographies from census.api import CensusAPIClient, infer_dataset -from census.cache import get_default_cache +from census.cache import get_default_cache, import_frozen, read_frozen from census.derived import difference as derive_difference from census.derived import percent_change as derive_percent_change from census.derived import product as derive_product @@ -739,3 +739,62 @@ def download_table( if isinstance(out, dict) and out.get("success"): out["years_requested"] = year_list return out + + +# --------------------------------------------------------------------------- +# Round 14 (D2): frozen API responses into the query cache (CLI `cache import`) +# --------------------------------------------------------------------------- + +def import_into_cache(cache: Any, source: Optional[str], allow_default_cache: bool = False, + allow_hint: str = "allow_default_cache=true") -> Dict[str, Any]: + """Shared by the CLI ``cache import`` and the MCP ``cache_import`` tool. + + Every file is validated before anything is written (atomic, as + :func:`census.cache.import_frozen`); synthetic responses go into the + default cache (CENSUS_CACHE_PATH unset) only with ``allow_default_cache``. + ``allow_hint`` names that switch in the refusal for the caller's interface. + """ + if cache is None: + return errors.error_result(errors.INVALID_INPUT, "The query cache is disabled " + "(CENSUS_CACHE_DISABLED); nothing to import into.") + if not source: + return errors.error_result(errors.INVALID_INPUT, "cache import needs a frozen file or directory") + try: + frozen = read_frozen(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 allow_default_cache): + return 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 " + f"CENSUS_CACHE_PATH=$PWD/synthetic_cache.db), or pass {allow_hint}.", + next_step="set CENSUS_CACHE_PATH and run cache import again") + result = import_frozen(cache, frozen) + except (OSError, ValueError) as exc: + return errors.error_result(errors.INVALID_INPUT, f"{type(exc).__name__}: {exc}") + return {"success": True, **result, **cache.stats()} + + +def cache_import(source: str, allow_default_cache: bool = False) -> Dict[str, Any]: + """Load frozen Census API responses into the local query cache, so later + extractions and run_manifest read them instead of api.census.gov (the + CLI's ``python -m census cache import``). + + A frozen file is JSON with ``request`` (build_request arguments: year, + geo_level, state_code, ...), ``requests`` ({table_code: API rows}) and, + for generated data, ``synthetic``. Atomic: every file is validated first; + on any error nothing is written. A synthetic source marks the whole cache + synthetic, so every later result built from it is labelled synthetic. + + Args: + source: A frozen response JSON file, or a directory of them. + allow_default_cache: Allow SYNTHETIC responses into the default cache + (CENSUS_CACHE_PATH unset). Refused by default: set + CENSUS_CACHE_PATH to a separate cache file instead. + + Returns success, imported (responses), sources (path, sha256, tables, + synthetic), synthetic (whether the cache is marked), and the cache's path + and entries. + """ + return import_into_cache(get_default_cache(), source, allow_default_cache) diff --git a/census/workflows_inference.py b/census/workflows_inference.py index 1939e12..93b8c3b 100644 --- a/census/workflows_inference.py +++ b/census/workflows_inference.py @@ -697,6 +697,11 @@ def save_json(result: Dict[str, Any], path: str) -> str: def create_manifest(manifest_path: str) -> Dict[str, Any]: """Validate a pre-registration manifest (JSON) and return its SHA-256 hash. Commit the manifest and hash BEFORE pulling data; then call run_manifest. + Several states: geography {"level": "county", "states": ["26", "36"]}; a price + adjustment: "adjustments": [{"type": "rpp", "variables": ["income"], "rpp_year": 2024}] + (rpp_year is required; when omitted, the latest RPP year is filled in before + hashing and the completed manifest is written to .completed.json, never + over the input; commit and run that file, as the result's notes say). The schema is documented in census/manifest.py and examples/manifests/. Example: create_manifest("examples/manifests/income_population_tracts.json") @@ -710,7 +715,7 @@ def create_manifest(manifest_path: str) -> Dict[str, Any]: if res.get("errors"): return _fail(errors.INVALID_INPUT, "Manifest invalid: " + "; ".join(map(str, res["errors"])), "fix the listed problems and call create_manifest again") - return _ok(f"Manifest valid; SHA-256 {res['hash']}.", + return _ok(f"Manifest valid; SHA-256 {res['hash']}." + "".join(f" {n}." for n in res.get("notes") or []), "commit the manifest, then run_manifest with the same path", **res) @@ -740,8 +745,8 @@ def run_manifest(manifest_path: str, output_dir: Optional[str] = None, "pass a CSV or Parquet file in the analysis replicate long format") try: res = _run(manifest_path, folder, replicates=reps, replicates_synthetic=replicates_synthetic) - except (FileNotFoundError, OSError, ValueError) as exc: - return _fail(errors.INVALID_INPUT, str(exc)) + except (FileNotFoundError, OSError, ValueError, KeyError) as exc: + return _fail(errors.INVALID_INPUT, f"{type(exc).__name__}: {exc}" if isinstance(exc, KeyError) else str(exc)) if res.get("errors"): return _fail(errors.INVALID_INPUT, "Manifest invalid: " + "; ".join(map(str, res["errors"])), "fix the manifest with create_manifest") @@ -753,6 +758,8 @@ def run_manifest(manifest_path: str, output_dir: Optional[str] = None, "analysis_status": status, "data_source": res.get("data_source"), "replicates": res.get("replicates"), "deviations": devs, "hypotheses": hyp, "bundle": res.get("bundle"), } + if "adjustments" in res: + out["adjustments"] = res["adjustments"] payload = _jsonable(out) if not res.get("success"): kinds = sorted({str(d.get("kind")) for d in devs}) diff --git a/census/workflows_microdata.py b/census/workflows_microdata.py index 4fe8bf1..cf96ad9 100644 --- a/census/workflows_microdata.py +++ b/census/workflows_microdata.py @@ -256,6 +256,101 @@ def _county_rpp_areas(geoids: List[str], year: int) -> Any: return county_cbsa_membership(counties, cbsas).set_index("GEOID"), cbsas.attrs.get("source_url") +def place_adjust(frame: Any, variables: List[str], year: int, areas: str = "county_metro", + replicates: Any = None, rpp_year: Optional[int] = None) -> Dict[str, Any]: + """Core of :func:`place_adjust_frame` and of the manifest ``adjustments`` step + (census.manifest): the one implementation of the RPP adjustment of a frame. + + ``frame``: analysis frame (DataFrame, GEOID per row; a modified copy is returned). + ``variables``: dollar variables; ```` and ``_se`` are divided by RPP/100. + ``year``: the dollars' year (ACS: release end year; also the county boundary year). + ``rpp_year``: the RPP year (default ``year``). ``replicates``: optional replicate + long frame; rows of ``variables`` are scaled by the same factor, and their rows + whose GEOID is not in the frame are dropped. + Returns {"frame", "replicates", "rpp_year", "parity", "rpp_areas", "boundary_source", + "notes", "advisories", "custom_table"}. Raises on bad input or a missing RPP. + """ + import pandas as pd + + from census import rpp + from census.analysis import REPLICATE_COLUMNS + + if areas not in _RPP_AREAS: + raise ValueError(f"areas must be one of {_RPP_AREAS}.") + if isinstance(variables, str): + variables = [variables] + variables = list(variables) + rpp_year = int(year if rpp_year is None else rpp_year) + frame = frame.copy() + missing = [v for v in variables if v not in frame.columns or f"{v}_se" not in frame.columns] + if missing: + raise KeyError(f"frame lacks {missing} (or their _se columns); use analysis variable names") + if "Year" in frame.columns and set(pd.to_numeric(frame["Year"]).astype(int)) - {int(year)}: + raise ValueError(f"frame has releases other than {int(year)}; adjust one release at a time") + geoids = [str(g) for g in frame["GEOID"]] + boundary_source = None + if areas == "county_metro": + if any(len(g) != 5 for g in geoids): + raise ValueError("areas='county_metro' needs county GEOIDs (5 digits); use areas='state'") + membership, boundary_source = _county_rpp_areas(sorted(set(geoids)), int(year)) + member = {g: membership.loc[g] for g in set(geoids)} + keys = [rpp.county_rpp_area(g, member[g]["CBSA"] if pd.notna(member[g]["CBSA"]) else None, + member[g]["LSAD"] if pd.notna(member[g]["LSAD"]) else None) + for g in geoids] + frame["cbsa"] = [member[g]["CBSA"] if pd.notna(member[g]["CBSA"]) else "" for g in geoids] + frame["cbsa_type"] = [{"M1": "metropolitan", "M2": "micropolitan"}.get(member[g]["LSAD"], "none") + if pd.notna(member[g]["LSAD"]) else "none" for g in geoids] + else: + keys = [g[:2] for g in geoids] + parity = {k: rpp.place_parity(k, rpp_year) for k in sorted(set(keys))} + frame["rpp_area"] = [parity[k]["geo_fips"] for k in keys] + frame["rpp_area_name"] = [parity[k]["geo_name"] for k in keys] + frame["rpp"] = [parity[k]["rpp"] for k in keys] + factor = 100.0 / frame["rpp"].to_numpy(float) + for v in variables: + frame[v] = pd.to_numeric(frame[v], errors="coerce").to_numpy(float) * factor + frame[f"{v}_se"] = pd.to_numeric(frame[f"{v}_se"], errors="coerce").to_numpy(float) * factor + reps = None + if replicates is not None: + reps = replicates.copy() + by_geoid = dict(zip(geoids, factor)) + sel = reps["variable"].isin(variables) + unmatched = sorted(set(reps.loc[sel, "GEOID"].astype(str)) - set(by_geoid)) + f = reps.loc[sel, "GEOID"].astype(str).map(by_geoid).to_numpy(float) + cols = ["estimate"] + [c for c in REPLICATE_COLUMNS if c in reps.columns] + # whole-number replicate files read as int64; pandas 3 refuses float values in int columns + reps[cols] = reps[cols].astype(float) + reps.loc[sel, cols] = reps.loc[sel, cols].to_numpy(float) * f[:, None] + if unmatched: + reps = reps[~(sel & reps["GEOID"].astype(str).isin(unmatched))] + counts = frame.groupby(["rpp_area", "rpp_area_name"]).agg(rpp=("rpp", "first"), rows=("rpp", "size")).reset_index() + notes = [f"PLACE ADJUSTMENT: {', '.join(variables)} divided by the BEA Regional Price Parity ({rpp_year}, " + "U.S.=100) of each row's area, i.e. expressed in national-average purchasing power; _se scaled by " + "the same factor."] + if rpp_year == int(year): + notes.append(f"ACS 5-year dollars are in end-year ({int(year)}) dollars; the {int(year)} RPP is applied to " + "the whole period.") + else: + notes.append(f"RPP YEAR: the {rpp_year} RPP is applied to ACS dollars of {int(year)} (the release end " + f"year); price-level differences between places are assumed unchanged between {rpp_year} " + f"and {int(year)}.") + if areas == "county_metro": + notes.append("RPP AREA: counties in a metropolitan CBSA use that MSA's RPP; micropolitan and rural " + "counties use the state nonmetropolitan-portion RPP (BEA nonmetro portions include " + f"micropolitan counties). CBSA membership from {boundary_source} (OMB bulletin 23-01 " + "delineation, as in BEA's RPPs).") + # provenance as in rpp.describe_place_adjustment: advisories and an unverified custom RPP table + advisories = sorted({a for p in parity.values() for a in p["advisories"]}) + custom = next((p["custom_table"] for p in parity.values() if p.get("custom_table")), None) + notes += advisories + if custom: + notes.append(rpp.custom_table_note(custom)) + notes.append(rpp.RPP_ESTIMATION_ERROR_NOTE) + return {"frame": frame, "replicates": reps, "rpp_year": rpp_year, "parity": parity, + "rpp_areas": _records(counts), "boundary_source": boundary_source, "notes": notes, + "advisories": advisories, "custom_table": custom} + + def place_adjust_frame( input_path: str, variables: List[str], @@ -264,6 +359,7 @@ def place_adjust_frame( replicates_path: Optional[str] = None, output_path: Optional[str] = None, replicates_output_path: Optional[str] = None, + rpp_year: Optional[int] = None, ) -> Dict[str, Any]: """Express dollar variables of an analysis frame in national-average purchasing power with BEA Regional Price Parities, row by row (value * 100 / RPP). @@ -279,95 +375,37 @@ def place_adjust_frame( "state" (the state RPP for every row). replicates_path: Replicate file from build_analysis_frame; the rows of the adjusted variables are scaled too (written to replicates_output_path). + rpp_year: RPP year when it differs from year (default: year); noted in the result. The RPP is treated as fixed: its own estimation error is not in the adjusted - SEs, MOEs or replicates. Never applied automatically. + SEs, MOEs or replicates. Never applied automatically. A manifest can declare the + same adjustment as an "adjustments" step (run_manifest). Example: place_adjust_frame("frame.parquet", ["income"], 2024) """ - import numpy as np - import pandas as pd - from census import rpp - from census.analysis import REPLICATE_COLUMNS - if areas not in _RPP_AREAS: - return _fail(errors.INVALID_INPUT, f"areas must be one of {_RPP_AREAS}.") if isinstance(variables, str): variables = [variables] - try: - frame = _load_frame(input_path) - missing = [v for v in variables if v not in frame.columns or f"{v}_se" not in frame.columns] - if missing: - raise KeyError(f"frame lacks {missing} (or their _se columns); use analysis variable names") - if "Year" in frame.columns and set(pd.to_numeric(frame["Year"]).astype(int)) - {int(year)}: - raise ValueError(f"frame has releases other than {int(year)}; adjust one release at a time") - geoids = [str(g) for g in frame["GEOID"]] - boundary_source = None - if areas == "county_metro": - if any(len(g) != 5 for g in geoids): - raise ValueError("areas='county_metro' needs county GEOIDs (5 digits); use areas='state'") - membership, boundary_source = _county_rpp_areas(sorted(set(geoids)), int(year)) - member = {g: membership.loc[g] for g in set(geoids)} - keys = [rpp.county_rpp_area(g, member[g]["CBSA"] if pd.notna(member[g]["CBSA"]) else None, - member[g]["LSAD"] if pd.notna(member[g]["LSAD"]) else None) - for g in geoids] - frame["cbsa"] = [member[g]["CBSA"] if pd.notna(member[g]["CBSA"]) else "" for g in geoids] - frame["cbsa_type"] = [{"M1": "metropolitan", "M2": "micropolitan"}.get(member[g]["LSAD"], "none") - if pd.notna(member[g]["LSAD"]) else "none" for g in geoids] - else: - keys = [g[:2] for g in geoids] - parity = {k: rpp.place_parity(k, int(year)) for k in sorted(set(keys))} - frame["rpp_area"] = [parity[k]["geo_fips"] for k in keys] - frame["rpp_area_name"] = [parity[k]["geo_name"] for k in keys] - frame["rpp"] = [parity[k]["rpp"] for k in keys] - factor = 100.0 / frame["rpp"].to_numpy(float) - for v in variables: - frame[v] = pd.to_numeric(frame[v], errors="coerce").to_numpy(float) * factor - frame[f"{v}_se"] = pd.to_numeric(frame[f"{v}_se"], errors="coerce").to_numpy(float) * factor + try: # place_adjust validates areas (ValueError, reported as invalid input below) + reps = _read_replicates(replicates_path) if replicates_path else None + res = place_adjust(_load_frame(input_path), variables, year, areas=areas, replicates=reps, + rpp_year=rpp_year) rep_out = None if replicates_path: - reps = _read_replicates(replicates_path) - by_geoid = dict(zip(geoids, factor)) - sel = reps["variable"].isin(variables) - unmatched = sorted(set(reps.loc[sel, "GEOID"].astype(str)) - set(by_geoid)) - f = reps.loc[sel, "GEOID"].astype(str).map(by_geoid).to_numpy(float) - cols = ["estimate"] + [c for c in REPLICATE_COLUMNS if c in reps.columns] - # whole-number replicate files read as int64; pandas 3 refuses float values in int columns - reps[cols] = reps[cols].astype(float) - reps.loc[sel, cols] = reps.loc[sel, cols].to_numpy(float) * f[:, None] - if unmatched: - reps = reps[~(sel & reps["GEOID"].astype(str).isin(unmatched))] - rep_out = _write_frame(reps, replicates_output_path + rep_out = _write_frame(res["replicates"], replicates_output_path or os.path.splitext(replicates_path)[0] + "_rpp.parquet", "replicates_rpp.parquet") except BundledDataIntegrityError as exc: return integrity_error_result(exc) except (*_ERRORS, rpp.RPPUnavailable) as exc: return _fail(errors.INVALID_INPUT, f"{type(exc).__name__}: {exc}") + frame = res["frame"] path = _write_frame(frame, output_path or os.path.splitext(input_path)[0] + "_rpp.parquet", "frame_rpp.parquet") - counts = frame.groupby(["rpp_area", "rpp_area_name"]).agg(rpp=("rpp", "first"), rows=("rpp", "size")).reset_index() - notes = [f"PLACE ADJUSTMENT: {', '.join(variables)} divided by the BEA Regional Price Parity ({int(year)}, " - "U.S.=100) of each row's area, i.e. expressed in national-average purchasing power; _se scaled by " - "the same factor.", - f"ACS 5-year dollars are in end-year ({int(year)}) dollars; the {int(year)} RPP is applied to the " - "whole period."] - if areas == "county_metro": - notes.append("RPP AREA: counties in a metropolitan CBSA use that MSA's RPP; micropolitan and rural " - "counties use the state nonmetropolitan-portion RPP (BEA nonmetro portions include " - f"micropolitan counties). CBSA membership from {boundary_source} (OMB bulletin 23-01 " - "delineation, as in BEA's RPPs).") - # provenance as in rpp.describe_place_adjustment: advisories and an unverified custom RPP table - advisories = sorted({a for p in parity.values() for a in p["advisories"]}) - custom = next((p["custom_table"] for p in parity.values() if p.get("custom_table")), None) - notes += advisories - if custom: - notes.append(rpp.custom_table_note(custom)) - notes.append(rpp.RPP_ESTIMATION_ERROR_NOTE) - return _ok(f"Adjusted {', '.join(variables)} for {len(frame)} rows with {len(parity)} RPP area(s).", + return _ok(f"Adjusted {', '.join(variables)} for {len(frame)} rows with {len(res['parity'])} RPP area(s).", "fit_model or make_map with input_path=output_path" + (" and replicates_path=replicates_output_path" if rep_out else "") + "; state in results that a regional price parity adjustment was applied", output_path=path, replicates_output_path=rep_out, areas=areas, year=int(year), - rpp_areas=_records(counts), notes=notes, advisories=advisories, custom_table=custom, - applied_by_default=False) + rpp_year=res["rpp_year"], rpp_areas=res["rpp_areas"], notes=res["notes"], + advisories=res["advisories"], custom_table=res["custom_table"], applied_by_default=False) def make_web_map( diff --git a/docs/audits/inference_validation.md b/docs/audits/inference_validation.md index f32add1..d4282cb 100644 --- a/docs/audits/inference_validation.md +++ b/docs/audits/inference_validation.md @@ -1,6 +1,6 @@ # Inference validation (PLAN_ROUND_8 workstream F1) -Generated by `scripts/inference_validation.py` (full configuration) on 2026-11-08; Python 3.14.4; 17.2 min. Seed 20261102. Do not edit by hand. +Generated by `scripts/inference_validation.py` (full configuration) on 2026-11-08; Python 3.14.4; 17.3 min. Seed 20261102. Do not edit by hand. Nominal interval level 90%; acceptance band 88%-92% (PLAN_ROUND_8 sec. 14.3). Monte Carlo SE = sqrt(c(1-c)/R) (Fay-Herriot: between-replication SE of the per-replication coverage, since areas share a fit). Rows with status `info` are comparisons (naive fits, targets a method does not claim) and are not gated. diff --git a/docs/audits/pums_memory.md b/docs/audits/pums_memory.md new file mode 100644 index 0000000..19bb2ef --- /dev/null +++ b/docs/audits/pums_memory.md @@ -0,0 +1,87 @@ +# PUMS memory benchmark + +Generated by `python scripts/pums_memory_benchmark.py` on 2026-11-08 (PLAN_ROUND_14 workstream C; PLAN_ROUND_11 R2/F3). Synthetic PUMS-shaped person files only; nothing was downloaded. + +## Environment + +- Platform: Linux-7.0.0-42-generic-x86_64-with-glibc2.43 (x86_64), 16 CPUs, 123 GiB RAM +- Python 3.14.4, numpy 2.5.4, pandas 3.1.1 +- Peak RSS: kernel high-water mark (VmHWM, the value `resource.getrusage().ru_maxrss` reports), one fresh subprocess per phase and chunk size. The weights and tabulate subprocesses read the file, then reset the high-water mark (`/proc/self/clear_refs`), so their peak is that phase on top of the resident frame. +- Baseline (interpreter, numpy, pandas, census.pums imported): 279 MiB + +## Inputs + +| Rows | Columns in file | Columns read | gzip size (MiB) | CSV size (MiB) | PUMAs | Frame in memory (MiB) | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 100,000 | 94 | 88 | 9 | 32 | 15 | 38 | +| 500,000 | 94 | 88 | 45 | 158 | 74 | 191 | +| 1,900,000 | 94 | 88 | 172 | 601 | 279 | 725 | + +Columns read: SERIALNO, SPORDER, STATE, PUMA, AGEP, PINCP, WAGP, PWGTP and PWGTP1-PWGTP80 (`read_pums_csv(columns=['AGEP', 'PINCP', 'WAGP'])`; identifiers, PUMA and weights are always kept). Weights are read as float32. A real 5-year person file has about 287 columns, not 94: the parser still tokenizes the columns it does not keep (longer read times than here) but does not store them, so the stored frame, and the peaks below, depend on the columns read (`--extra-columns` adds unread filler columns to check this). + +Width check (a separate run on the same day, `--sizes 500000 --chunksizes 50000,200000 --extra-columns 193`: 287 columns, 342 MiB CSV, 111 MiB gzip, same 88 columns read): the frame was the same 191 MiB, the end-to-end peak 1,656 MiB against 1,604 MiB for the 94-column file below (+3%), and the read took 4.9-5.7 s instead of about 2 s. Estimates and SEs were identical across its chunk sizes. + +## Peak RSS by phase (MiB) + +| Rows | read, chunk 50,000 | read, chunk 200,000 | read, chunk 1,000,000 | weight matrix (peak / increment) | tabulate (peak / increment) | end-to-end peak | bytes per row | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 100,000 | 375 | 353 | 353 | 411 / 62 | 560 / 211 | 560 | 2,946 | +| 500,000 | 706 | 713 | 661 | 950 / 309 | 1,604 / 973 | 1,604 | 2,779 | +| 1,900,000 | 1,851 | 1,874 | 1,969 | 2,804 / 1,174 | 5,406 / 3,891 | 5,406 | 2,829 | + +Increment: peak minus RSS at the start of the phase (the frame already resident). End-to-end peak: the largest of the read, weight-matrix and tabulate peaks, i.e. the peak of reading the file and running these tabulations in one process. Bytes per row: (end-to-end peak - baseline) / rows. The weight matrix alone is rows x 81 x 8 bytes (1,174 MiB at 1,900,000 rows). + +### Tabulation steps (MiB peak / seconds, default chunk size) + +| Rows | median PINCP (AGEP >= 15) | mean WAGP (AGEP >= 16) | mean WAGP by PUMA | +| ---: | ---: | ---: | ---: | +| 100,000 | 560 / 0.1 | 499 / 0.0 | 497 / 0.1 | +| 500,000 | 1,583 / 0.8 | 1,260 / 0.2 | 1,233 / 0.3 | +| 1,900,000 | 5,300 / 3.7 | 4,081 / 1.2 | 3,971 / 1.5 | + +### Wall time (seconds) + +| Rows | read, chunk 50,000 | read, chunk 200,000 | read, chunk 1,000,000 | weight matrix | tabulate | +| ---: | ---: | ---: | ---: | ---: | ---: | +| 100,000 | 0.4 | 0.4 | 0.4 | 0.01 | 0.4 | +| 500,000 | 2.0 | 1.9 | 1.9 | 0.05 | 1.4 | +| 1,900,000 | 7.6 | 7.7 | 8.5 | 0.14 | 6.5 | + +## Chunk-size equivalence + +| Rows | Chunk sizes compared | Frame hash identical | Estimates and SEs identical | +| ---: | ---: | :--- | :--- | +| 100,000 | 3 | yes | yes | +| 500,000 | 3 | yes | yes | +| 1,900,000 | 3 | yes | yes | + +Identical means bit-for-bit: every estimate, SE and unweighted n of the three tabulations (including all PUMA groups) compared as exact float values, and one hash over the frame read. + +## Scaling and practical limit + +A least-squares line through the end-to-end peaks gives peak = 275 MiB + 2,829 bytes x rows. With 2 GiB left for the operating system, the rows that fit with these columns are: + +| Machine RAM | Rows (person records) that fit | California-sized states (1.9M rows) | +| :--- | ---: | ---: | +| 16 GiB | 5,211,831 | 2.7 | +| 128 GiB | 47,722,509 | 25.1 | + +These are for the needed columns plus the 80 replicate weights. Each extra numeric column read adds 8 bytes per row (float64 after conversion) and each extra text column more (pandas strings); `columns=None` reads all 280+ columns of a real file and multiplies the frame several-fold (the PumsMemoryWarning above 200 MB). The figures exclude the download step: `fetch_pums` holds the compressed response in memory once while writing it to the cache (one zip, hundreds of MB for the largest states). + +## Before and after the Round 14 copy fixes + +Before: v0.12.0 census/pums.py, same synthetic files (same seed), same machine, run the same day. The fixes (census/pums.py) remove avoidable copies of the n x 81 weight matrix: `_group_sum` no longer copies it when every row counts and passes `copy=False` to DataFrame (pandas 3 copies a 2-D array by default); `weighted_median` gathers the rows it uses once instead of twice; `tabulate` no longer copies `W[rows]` per median group; `read_pums_csv` drops the parsed chunks before conversion. + +| Rows | read peak before / after | weight matrix before / after | tabulate peak before / after | end-to-end before / after | Estimates and SEs identical to before | +| ---: | ---: | ---: | ---: | ---: | :--- | +| 100,000 | 384 / 375 | 413 / 411 | 614 / 560 | 614 / 560 | yes | +| 500,000 | 746 / 713 | 1,014 / 950 | 1,871 / 1,604 | 1,871 / 1,604 | yes | +| 1,900,000 | 2,261 / 1,969 | 2,979 / 2,804 | 6,406 / 5,406 | 6,406 / 5,406 | yes | + +## Findings and recommendation + +- Memory scales linearly with rows, at about 2,829 bytes per person record for this column set; the largest state (about 1,900,000 records) peaked at 5.28 GiB. +- Chunk size does not bound memory (every chunk is kept until the frame is concatenated) and does not change any result: estimates, SEs and the frame are bit-identical across chunk sizes. Smaller chunks only lower the transient parser buffer. +- The peak comes from tabulation, which holds the frame, the 81-column float64 weight matrix and per-statistic temporaries at once; reading alone peaks lower. +- A 16 GiB machine handles any single state with the needed columns; the owner's 128 GiB machine holds about 25 California-sized files in one process (all states together are about 16 million 5-year person records). +- Recommendation: keep the Round 10 design (full state files on demand, needed columns only); no streaming rewrite is warranted. For many states, tabulate state by state and combine, or read with `columns=[...]` and `filter=` to keep only the rows and variables needed. diff --git a/docs/audits/validation_report.json b/docs/audits/validation_report.json index cda5258..b70db99 100644 --- a/docs/audits/validation_report.json +++ b/docs/audits/validation_report.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-11-08T02:01:53+00:00", + "generated_at": "2026-11-08T04:40:37+00:00", "mode": "live", "command": "python scripts/validate_against_census.py --live", "summary": { @@ -83,12 +83,12 @@ ] }, "mode": "live", - "seconds": 6.0 + "seconds": 11.6 }, { "check_id": "F2", - "description": "Data Profile percentages and percent MOEs rebuilt from detailed-table counts with derived.derive_columns (sum_estimates + proportion, Handbook 2020 ch. 8 formulas 1 and 6), states 06,36,48,17,56 and all their counties, releases 2019, 2023.", - "n_compared": 5040, + "description": "Data Profile percentages and percent MOEs rebuilt from detailed-table counts with derived.derive_columns (sum_estimates + proportion, Handbook 2020 ch. 8 formulas 1 and 6), states 06,36,48,17,56,26 and all their counties, releases 2019, 2023.", + "n_compared": 5880, "n_failed": 0, "max_abs_error": 0.04999999999999716, "max_rel_error": 0.04712041884816753, @@ -307,17 +307,17 @@ }, "per_construction": { "poverty_rate 2019 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, "moe_band_misses_explained_by_replicates": 0, "moe_failures": 0, - "max_abs_error": 0.03651679214870818, + "max_abs_error": 0.04380614821259776, "skipped": 0 }, "uninsured_rate 2019 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -327,7 +327,7 @@ "skipped": 0 }, "owner_occupied 2019 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -337,7 +337,7 @@ "skipped": 0 }, "age_65_plus 2019 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -347,7 +347,7 @@ "skipped": 0 }, "bachelors_or_higher 2019 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -357,7 +357,7 @@ "skipped": 0 }, "poverty_rate 2019 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -367,27 +367,27 @@ "skipped": 0 }, "uninsured_rate 2019 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, "moe_band_misses_explained_by_replicates": 0, "moe_failures": 0, - "max_abs_error": 0.04993646759847614, + "max_abs_error": 0.04996510816468991, "skipped": 0 }, "owner_occupied 2019 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, - "moe_band_misses": 135, - "moe_band_misses_explained_by_replicates": 135, + "moe_band_misses": 172, + "moe_band_misses_explained_by_replicates": 172, "moe_failures": 0, "max_abs_error": 0.04996686547382012, "skipped": 0 }, "age_65_plus 2019 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -397,7 +397,7 @@ "skipped": 0 }, "bachelors_or_higher 2019 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -407,17 +407,17 @@ "skipped": 0 }, "poverty_rate 2023 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, "moe_band_misses_explained_by_replicates": 0, "moe_failures": 0, - "max_abs_error": 0.03356974815006453, + "max_abs_error": 0.04944047403746765, "skipped": 0 }, "uninsured_rate 2023 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -427,7 +427,7 @@ "skipped": 0 }, "owner_occupied 2023 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -437,17 +437,17 @@ "skipped": 0 }, "age_65_plus 2023 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, "moe_band_misses_explained_by_replicates": 0, "moe_failures": 0, - "max_abs_error": 0.040554153650933955, + "max_abs_error": 0.044602771997876545, "skipped": 0 }, "bachelors_or_higher 2023 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -457,7 +457,7 @@ "skipped": 0 }, "poverty_rate 2023 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -467,7 +467,7 @@ "skipped": 0 }, "uninsured_rate 2023 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -477,17 +477,17 @@ "skipped": 0 }, "owner_occupied 2023 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, - "moe_band_misses": 80, - "moe_band_misses_explained_by_replicates": 80, + "moe_band_misses": 107, + "moe_band_misses_explained_by_replicates": 107, "moe_failures": 0, "max_abs_error": 0.04999999999999716, "skipped": 0 }, "age_65_plus 2023 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -497,7 +497,7 @@ "skipped": 0 }, "bachelors_or_higher 2023 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -509,59 +509,59 @@ }, "moe_ratio_ours_over_published": { "poverty_rate": { - "n": 1008, + "n": 1176, "min": 0.7736313821643819, - "p5": 0.9534212538422614, - "p25": 0.9884370013231081, - "median": 1.0002856232316664, - "p75": 1.0140808197710969, - "p95": 1.0508986869945134, - "max": 1.492935767601951 + "p5": 0.9554369497959188, + "p25": 0.9876097325820183, + "median": 1.0004333367260303, + "p75": 1.0146482058918944, + "p95": 1.0481317720389831, + "max": 1.4953084378391963 }, "uninsured_rate": { - "n": 1008, + "n": 1176, "min": 0.5245119880073353, - "p5": 0.7623261612371356, - "p25": 0.8382018345742577, - "median": 0.8947109410687769, - "p75": 0.959663952480057, - "p95": 1.0886752771469939, + "p5": 0.7602513283319245, + "p25": 0.8366644742921433, + "median": 0.8925526325526165, + "p75": 0.9589765222382747, + "p95": 1.0827144215893023, "max": 3.368149540320743 }, "owner_occupied": { - "n": 1008, - "min": 0.11740448706346762, - "p5": 0.5911391022180238, - "p25": 0.7804246992938717, - "median": 0.8847851481460042, - "p75": 0.9614225184624575, - "p95": 1.1256402470286904, + "n": 1176, + "min": 0.033101611186908554, + "p5": 0.5538309797594627, + "p25": 0.773165399793867, + "median": 0.8804264476946757, + "p75": 0.9602291433650332, + "p95": 1.1351535933009016, "max": 3.5880373603086078 }, "age_65_plus": { - "n": 972, + "n": 1135, "min": 0.32274979068910414, - "p5": 0.9133893879965173, - "p25": 2.806175898236696, - "median": 4.41323770953967, - "p75": 6.246878784493752, - "p95": 10.32043570564385, + "p5": 0.9745152132688413, + "p25": 3.0237311228219257, + "median": 4.64809113246433, + "p75": 6.543960270804267, + "p95": 10.371881329539105, "max": 24.355136826875203 }, "bachelors_or_higher": { - "n": 1008, + "n": 1176, "min": 0.6148187553996485, - "p5": 0.8995509215727955, - "p25": 0.970260790215502, - "median": 1.0213155010270547, - "p75": 1.0908896997218966, - "p95": 1.2085678597060883, + "p5": 0.9004757855672131, + "p25": 0.9692286593913597, + "median": 1.0205688957607086, + "p75": 1.0869595744621137, + "p95": 1.2101276755971069, "max": 3.9483660875278423 } }, "formula_mismatches": 0, - "proportion_ratio_fallbacks": 12, - "moe_band_misses_explained_by_replicates": 215, + "proportion_ratio_fallbacks": 22, + "moe_band_misses_explained_by_replicates": 279, "explained_sample": [ { "construction": "owner_occupied", @@ -686,12 +686,12 @@ ] }, "mode": "live", - "seconds": 6.85 + "seconds": 8.49 }, { "check_id": "F3", "description": "MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, variance.replicate_moe) vs the MOE published in the same VRE file; VRE estimates and MOEs vs API estimates and MOEs for the same cells. Tables B17001, B27010, B25003, B01001, B01003, B11001, B19001, B25063, state level, releases 2019, 2023, plus the committed county/state VRE fixtures.", - "n_compared": 29572, + "n_compared": 30496, "n_failed": 0, "max_abs_error": 0.4999709722534291, "max_rel_error": null, @@ -732,7 +732,7 @@ "model_based_rows": 0, "max_abs_diff": 0.49988453556113654, "moe_mismatches": 0, - "api_cells_compared": 590, + "api_cells_compared": 708, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -744,7 +744,7 @@ "model_based_rows": 11, "max_abs_diff": 0.4998932918888386, "moe_mismatches": 0, - "api_cells_compared": 660, + "api_cells_compared": 792, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -756,7 +756,7 @@ "model_based_rows": 0, "max_abs_diff": 0.49547866262082607, "moe_mismatches": 0, - "api_cells_compared": 30, + "api_cells_compared": 36, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -768,9 +768,9 @@ "model_based_rows": 0, "max_abs_diff": 0.49996392293905956, "moe_mismatches": 0, - "api_cells_compared": 490, + "api_cells_compared": 588, "api_cell_mismatches": 0, - "controlled_moe_cells": 5 + "controlled_moe_cells": 6 }, "B01003 2019": { "scope": "full national file (summary level 040)", @@ -780,9 +780,9 @@ "model_based_rows": 0, "max_abs_diff": null, "moe_mismatches": 0, - "api_cells_compared": 10, + "api_cells_compared": 12, "api_cell_mismatches": 0, - "controlled_moe_cells": 5 + "controlled_moe_cells": 6 }, "B11001 2019": { "scope": "full national file (summary level 040)", @@ -792,7 +792,7 @@ "model_based_rows": 0, "max_abs_diff": 0.4978310636047354, "moe_mismatches": 0, - "api_cells_compared": 90, + "api_cells_compared": 108, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -804,7 +804,7 @@ "model_based_rows": 0, "max_abs_diff": 0.4993840771894611, "moe_mismatches": 0, - "api_cells_compared": 170, + "api_cells_compared": 204, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -816,7 +816,7 @@ "model_based_rows": 0, "max_abs_diff": 0.499846723576411, "moe_mismatches": 0, - "api_cells_compared": 270, + "api_cells_compared": 324, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -828,7 +828,7 @@ "model_based_rows": 0, "max_abs_diff": 0.49969577847059554, "moe_mismatches": 0, - "api_cells_compared": 590, + "api_cells_compared": 708, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -840,7 +840,7 @@ "model_based_rows": 8, "max_abs_diff": 0.4999403443362098, "moe_mismatches": 0, - "api_cells_compared": 660, + "api_cells_compared": 792, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -852,7 +852,7 @@ "model_based_rows": 0, "max_abs_diff": 0.49928626996916137, "moe_mismatches": 0, - "api_cells_compared": 30, + "api_cells_compared": 36, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -864,9 +864,9 @@ "model_based_rows": 0, "max_abs_diff": 0.4997525480409877, "moe_mismatches": 0, - "api_cells_compared": 490, + "api_cells_compared": 588, "api_cell_mismatches": 0, - "controlled_moe_cells": 5 + "controlled_moe_cells": 6 }, "B01003 2023": { "scope": "full national file (summary level 040)", @@ -876,9 +876,9 @@ "model_based_rows": 0, "max_abs_diff": null, "moe_mismatches": 0, - "api_cells_compared": 10, + "api_cells_compared": 12, "api_cell_mismatches": 0, - "controlled_moe_cells": 5 + "controlled_moe_cells": 6 }, "B11001 2023": { "scope": "full national file (summary level 040)", @@ -888,7 +888,7 @@ "model_based_rows": 0, "max_abs_diff": 0.49874303933052033, "moe_mismatches": 0, - "api_cells_compared": 90, + "api_cells_compared": 108, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -900,7 +900,7 @@ "model_based_rows": 0, "max_abs_diff": 0.49951382657673093, "moe_mismatches": 0, - "api_cells_compared": 170, + "api_cells_compared": 204, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -912,7 +912,7 @@ "model_based_rows": 0, "max_abs_diff": 0.4994722210440443, "moe_mismatches": 0, - "api_cells_compared": 270, + "api_cells_compared": 324, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -958,16 +958,16 @@ } }, "replicate_rows_checked": 24952, - "api_cells_compared": 4620, + "api_cells_compared": 5544, "api_cell_mismatches": 0 }, "mode": "live", - "seconds": 9.0 + "seconds": 9.5 }, { "check_id": "F4", - "description": "Exact replicate proportions (replicate.replicate_proportion on VRE files) vs Data Profile percents and percent MOEs: states 06,36,48,17,56 for every construction with a VRE table, plus all their counties for owner_occupied; releases 2019, 2023.", - "n_compared": 1038, + "description": "Exact replicate proportions (replicate.replicate_proportion on VRE files) vs Data Profile percents and percent MOEs: states 06,36,48,17,56,26 for every construction with a VRE table, plus all their counties for owner_occupied; releases 2019, 2023.", + "n_compared": 1212, "n_failed": 0, "max_abs_error": 0.09837759799016546, "max_rel_error": null, @@ -995,72 +995,72 @@ "details": { "per_construction": { "poverty_rate 2019 state": { - "n": 5, + "n": 6, "failures": 0, - "max_estimate_error": 0.03651679214870818, - "max_moe_error": 0.02393998589620068, + "max_estimate_error": 0.04380614821259776, + "max_moe_error": 0.04960234914349701, "skipped": 0 }, "uninsured_rate 2019 state": { - "n": 5, + "n": 6, "failures": 0, "max_estimate_error": 0.04511322700916409, "max_moe_error": 0.04994497169954262, "skipped": 0 }, "owner_occupied 2019 state": { - "n": 5, + "n": 6, "failures": 0, "max_estimate_error": 0.04846751821835227, "max_moe_error": 0.044011990628512215, "skipped": 0 }, "age_65_plus 2019 state": { - "n": 5, + "n": 6, "failures": 0, "max_estimate_error": 0.04797550364362557, "max_moe_error": 0.09837759799016546, "skipped": 0, - "published_moe_at_display_floor": 5 + "published_moe_at_display_floor": 6 }, "owner_occupied 2019 county": { - "n": 499, + "n": 582, "failures": 0, "max_estimate_error": 0.04996686547382012, "max_moe_error": 0.049866982472014154, "skipped": 0 }, "poverty_rate 2023 state": { - "n": 5, + "n": 6, "failures": 0, - "max_estimate_error": 0.03356974815006453, + "max_estimate_error": 0.04944047403746765, "max_moe_error": 0.04916769747922606, "skipped": 0 }, "uninsured_rate 2023 state": { - "n": 5, + "n": 6, "failures": 0, "max_estimate_error": 0.04768904734502666, "max_moe_error": 0.04056661497016503, "skipped": 0 }, "owner_occupied 2023 state": { - "n": 5, + "n": 6, "failures": 0, "max_estimate_error": 0.03762119390653851, "max_moe_error": 0.035742822291887105, "skipped": 0 }, "age_65_plus 2023 state": { - "n": 5, + "n": 6, "failures": 0, - "max_estimate_error": 0.040554153650933955, + "max_estimate_error": 0.044602771997876545, "max_moe_error": 0.0983616748336616, "skipped": 0, - "published_moe_at_display_floor": 4 + "published_moe_at_display_floor": 5 }, "owner_occupied 2023 county": { - "n": 499, + "n": 582, "failures": 0, "max_estimate_error": 0.04999999999999716, "max_moe_error": 0.049901415046242015, @@ -1071,65 +1071,65 @@ "max_moe_error": 0.09837759799016546, "approximate_over_replicate_moe": { "poverty_rate state": { - "n": 10, + "n": 12, "min": 0.9973932075777341, - "p5": 0.9978147743473517, - "p25": 0.9985586361462659, + "p5": 0.9979084558517111, + "p25": 0.9987204883086159, "median": 0.9998334381640055, - "p75": 1.0007156223347162, - "p95": 1.0025456931650314, + "p75": 1.000730282423888, + "p95": 1.0022675419291092, "max": 1.0037973737266812 }, "uninsured_rate state": { - "n": 10, + "n": 12, "min": 0.7051799348769849, - "p5": 0.7137375419872339, - "p25": 0.7422443567188695, - "median": 0.7818525206367402, - "p75": 0.7956330708010112, - "p95": 0.8572450778988274, + "p5": 0.7156392324561781, + "p25": 0.7637002335050819, + "median": 0.7869317488089953, + "p75": 0.8025014805570005, + "p95": 0.8725018072000658, "max": 0.8869444583511927 }, "owner_occupied state": { - "n": 10, + "n": 12, "min": 0.8923353915038464, - "p5": 0.9223629423972761, - "p25": 1.2369931785012964, - "median": 1.2790646370898342, - "p75": 1.2939296775402536, - "p95": 1.3561068296827248, - "max": 1.3640124510373062 + "p5": 0.929035731484705, + "p25": 1.2457206843895248, + "median": 1.283935307811899, + "p75": 1.347947622211519, + "p95": 1.3936197167886188, + "max": 1.4298063749291121 }, "age_65_plus state": { - "n": 10, + "n": 12, "min": 4.773304094911635, - "p5": 5.432293303515204, - "p25": 7.89396453186003, + "p5": 5.578735349871553, + "p25": 8.070327705737789, "median": 12.356599715398112, - "p75": 17.595231361621554, - "p95": 21.988708609393594, + "p75": 15.668419542366417, + "p95": 21.607730735842374, "max": 23.703109040374095 }, "owner_occupied county": { - "n": 998, - "min": 0.11402469945309178, - "p5": 0.5819994095597515, - "p25": 0.7824720903505963, - "median": 0.8796293019122823, - "p75": 0.9636591439799281, - "p95": 1.0868608477420798, + "n": 1164, + "min": 0.03394176482653954, + "p5": 0.5554692609363415, + "p25": 0.7707697011792228, + "median": 0.8754906559828328, + "p75": 0.9609813239564091, + "p95": 1.0905260951161029, "max": 3.5535069687610403 } }, "pairings": "same constructions and Census definitions as F2 (bachelors_or_higher excluded: B15003 has no VRE file)" }, "mode": "live", - "seconds": 0.61 + "seconds": 0.74 }, { "check_id": "F5", - "description": "Medians interpolated from published distribution tables (medians.median_for_rows, linear interpolation) vs published medians, all counties of states 06,36,48,17,56, releases 2019, 2023.", - "n_compared": 1983, + "description": "Medians interpolated from published distribution tables (medians.median_for_rows, linear interpolation) vs published medians, all counties of states 06,36,48,17,56,26, releases 2019, 2023.", + "n_compared": 2315, "n_failed": 0, "max_abs_error": 16226.11627906977, "max_rel_error": 0.1985113138045458, @@ -1239,22 +1239,22 @@ "pairs": { "B25063->B25064_001 2019": { "definition": "Median gross rent (B25064) from the gross rent distribution (B25063, 24 cash-rent brackets; 'No cash rent' excluded). Census computes B25064 from the same brackets.", - "counties": 499, - "compared": 496, + "counties": 582, + "compared": 579, "excluded_top_or_bottom_coded_or_missing": 3, "max_abs_error": 0.5, - "median_abs_error": 0.23883986064225837, + "median_abs_error": 0.23744292237438458, "max_rel_error": 0.0009487666034155328, - "median_rel_error": 0.00030089067630395484, + "median_rel_error": 0.00029740145478872104, "share_within_2pct": 1.0, "approx_moe_over_published_moe": { - "n": 496, + "n": 579, "min": 0.6514412127358754, - "p5": 0.894596428650637, - "p25": 1.0424454239581795, - "median": 1.128010743474428, - "p75": 1.2131230539877658, - "p95": 1.401804095917858, + "p5": 0.9033103693911765, + "p25": 1.042242022506491, + "median": 1.125984816764382, + "p75": 1.2064967258369097, + "p95": 1.3940536662403271, "max": 3.282941519739477 }, "share_within_1_dollar": 1.0, @@ -1263,48 +1263,48 @@ }, "B19001->B19013_001 2019": { "definition": "Median household income (B19013) from household income brackets (B19001, 16 brackets). Census interpolates a finer internal distribution ($2,500 intervals below $250,000; Subject Definitions 2023, 'Median Income'), so exact agreement is not expected.", - "counties": 499, - "compared": 499, + "counties": 582, + "compared": 582, "excluded_top_or_bottom_coded_or_missing": 0, "max_abs_error": 5398.1666666666715, - "median_abs_error": 258.0281690140837, + "median_abs_error": 234.9357802822633, "max_rel_error": 0.06880502022364983, - "median_rel_error": 0.004856078477082083, - "share_within_2pct": 0.905811623246493, + "median_rel_error": 0.004526242972804987, + "share_within_2pct": 0.9192439862542955, "approx_moe_over_published_moe": { - "n": 499, + "n": 582, "min": 0.5721817734474332, - "p5": 0.826981323927377, - "p25": 0.9901451746977017, - "median": 1.1050403899416155, - "p75": 1.2539766360798126, - "p95": 1.517586881377449, + "p5": 0.8388430953154132, + "p25": 1.0022439172105853, + "median": 1.1036472172599594, + "p75": 1.2462187436889534, + "p95": 1.4825618386027943, "max": 2.7566364760267823 }, "counties_outside_2pct": 47, "criterion": "median relative error < 1% and >= 98% of counties within the published MOE (2% share reported, not asserted)", "criterion_met": true, - "share_within_published_moe": 0.9879759519038076, + "share_within_published_moe": 0.9896907216494846, "outside_2pct_but_within_published_moe": 46 }, "B25063->B25064_001 2023": { "definition": "Median gross rent (B25064) from the gross rent distribution (B25063, 24 cash-rent brackets; 'No cash rent' excluded). Census computes B25064 from the same brackets.", - "counties": 499, - "compared": 490, + "counties": 582, + "compared": 573, "excluded_top_or_bottom_coded_or_missing": 9, "max_abs_error": 0.5, - "median_abs_error": 0.22684391080616706, + "median_abs_error": 0.23076923076928324, "max_rel_error": 0.0009785677618069815, - "median_rel_error": 0.0002207326563185523, + "median_rel_error": 0.00022495755887916462, "share_within_2pct": 1.0, "approx_moe_over_published_moe": { - "n": 490, + "n": 573, "min": 0.5707367985396146, - "p5": 0.843171302726709, - "p25": 1.0281655600034902, - "median": 1.1168789341235161, - "p75": 1.226784865025155, - "p95": 1.4380230843865633, + "p5": 0.8450357070775417, + "p25": 1.030450913896718, + "median": 1.1167781135639878, + "p75": 1.2226920030193589, + "p95": 1.4173892910204984, "max": 7.554880616634601 }, "share_within_1_dollar": 1.0, @@ -1313,39 +1313,39 @@ }, "B19001->B19013_001 2023": { "definition": "Median household income (B19013) from household income brackets (B19001, 16 brackets). Census interpolates a finer internal distribution ($2,500 intervals below $250,000; Subject Definitions 2023, 'Median Income'), so exact agreement is not expected.", - "counties": 499, - "compared": 498, + "counties": 582, + "compared": 581, "excluded_top_or_bottom_coded_or_missing": 1, "max_abs_error": 16226.11627906977, - "median_abs_error": 446.5339830084995, + "median_abs_error": 435.19959278321767, "max_rel_error": 0.1985113138045458, - "median_rel_error": 0.006389973134895618, - "share_within_2pct": 0.8413654618473896, + "median_rel_error": 0.006355229464913636, + "share_within_2pct": 0.8588640275387264, "approx_moe_over_published_moe": { - "n": 498, + "n": 581, "min": 0.4454191627730805, - "p5": 0.7499554269308675, - "p25": 0.9314206768081066, - "median": 1.0550169294345477, - "p75": 1.194524298112461, - "p95": 1.506366979317927, + "p5": 0.7551186429582991, + "p25": 0.9449788203902701, + "median": 1.0699313688671936, + "p75": 1.2061307364797798, + "p95": 1.5063513752992483, "max": 55.87813186455595 }, - "counties_outside_2pct": 79, + "counties_outside_2pct": 82, "criterion": "median relative error < 1% and >= 98% of counties within the published MOE (2% share reported, not asserted)", "criterion_met": true, - "share_within_published_moe": 0.9979919678714859, - "outside_2pct_but_within_published_moe": 78 + "share_within_published_moe": 0.9965576592082617, + "outside_2pct_but_within_published_moe": 80 } } }, "mode": "live", - "seconds": 3.24 + "seconds": 3.0 }, { "check_id": "F6", - "description": "Comparison Profile (CP03) earlier-period dollar values, which Census publishes in current-period dollars, vs the earlier release's DP03 value adjusted with inflation.adjust_inflation (R-CPI-U-RS, the default), states 06,36,48,17,56, CP releases 2019 (vs 2014), 2023 (vs 2018).", - "n_compared": 125, + "description": "Comparison Profile (CP03) earlier-period dollar values, which Census publishes in current-period dollars, vs the earlier release's DP03 value adjusted with inflation.adjust_inflation (R-CPI-U-RS, the default), states 06,36,48,17,56,26, CP releases 2019 (vs 2014), 2023 (vs 2018).", + "n_compared": 150, "n_failed": 0, "max_abs_error": 275.5947195858462, "max_rel_error": 0.005741078234852225, @@ -1406,33 +1406,33 @@ "2019": 375.813 }, "census_implied_factor_all_items": { - "n": 50, + "n": 60, "min": 1.0746843377809618, - "p5": 1.076145198907045, - "p25": 1.0802195382581568, - "median": 1.0809600821614125, - "p75": 1.0809759547539666, - "p95": 1.0825238763942842, + "p5": 1.0773204081578227, + "p25": 1.0801767593654468, + "median": 1.0809592914613915, + "p75": 1.0809708958581514, + "p95": 1.0821819415103087, "max": 1.0838654177021603 }, "census_implied_factor_means_and_per_capita": { - "n": 20, - "min": 1.0809414249613398, - "p5": 1.0809530432938104, + "n": 24, + "min": 1.0809394484183146, + "p5": 1.0809432594348878, "p25": 1.0809600994303399, "median": 1.0809657866727096, "p75": 1.0809701408143002, - "p95": 1.080979121965741, + "p95": 1.0809787389980172, "max": 1.0809820447050202 }, "census_implied_factor_medians": { - "n": 30, + "n": 36, "min": 1.0746843377809618, - "p5": 1.0748980061926594, - "p25": 1.0800400819240237, - "median": 1.0805843994280213, - "p75": 1.0813419154779242, - "p95": 1.0833594072049817, + "p5": 1.0750013722228515, + "p25": 1.0799983347410265, + "median": 1.0803961190783977, + "p75": 1.0810451289198495, + "p95": 1.0830715220694618, "max": 1.0838654177021603 }, "implied_minus_ours_percent": 0.010325354924156649, @@ -1447,36 +1447,36 @@ "2023": 449.265 }, "census_implied_factor_all_items": { - "n": 75, + "n": 90, "min": 1.217095075874884, - "p5": 1.2172690671665287, - "p25": 1.2172780351481864, - "median": 1.2172863599013968, - "p75": 1.2172909991247587, - "p95": 1.2173506918729535, + "p5": 1.2172687528930333, + "p25": 1.2172782423061743, + "median": 1.2172863894784407, + "p75": 1.2172920057657355, + "p95": 1.2173483380353065, "max": 1.217607973421927 }, "census_implied_factor_means_and_per_capita": { - "n": 45, + "n": 54, "min": 1.217095075874884, - "p5": 1.2172694830791926, - "p25": 1.2172809028390055, - "median": 1.2172863599013968, - "p75": 1.2172931097568669, - "p95": 1.217359317116744, + "p5": 1.2172695703442484, + "p25": 1.217280932305722, + "median": 1.2172865448520538, + "p75": 1.2172940141155042, + "p95": 1.217355827531763, "max": 1.217607973421927 }, "census_implied_factor_medians": { - "n": 30, + "n": 36, "min": 1.2172652955167795, - "p5": 1.2172679035298999, - "p25": 1.2172738730731925, - "median": 1.217286132082998, - "p75": 1.2172880890236044, - "p95": 1.2172967018420697, + "p5": 1.2172671882402522, + "p25": 1.2172740281502468, + "median": 1.2172856268138728, + "p75": 1.2172877617920628, + "p95": 1.2172956567015745, "max": 1.2173020424055785 }, - "implied_minus_ours_percent": 0.007878521497461932, + "implied_minus_ours_percent": 0.007893716379658855, "current_period_cp_ne_dp": 0, "unmatched_cp_items": 28 } @@ -1484,12 +1484,12 @@ "median_items_outside_tolerance_not_asserted": 3 }, "mode": "live", - "seconds": 6.02 + "seconds": 7.06 }, { "check_id": "F7", - "description": "Comparison Profile significance flags (CP03 '..._SS' variables: '*' = the two periods differ at the 90% level, empty = not significant) vs stats.z_test on the CP estimates with the Data Profile MOEs of each release, states 06,36,48,17,56, CP releases 2019 (vs 2014), 2023 (vs 2018).", - "n_compared": 442, + "description": "Comparison Profile significance flags (CP03 '..._SS' variables: '*' = the two periods differ at the 90% level, empty = not significant) vs stats.z_test on the CP estimates with the Data Profile MOEs of each release, states 06,36,48,17,56,26, CP releases 2019 (vs 2014), 2023 (vs 2018).", + "n_compared": 522, "n_failed": 0, "max_abs_error": null, "max_rel_error": null, @@ -1647,6 +1647,25 @@ ], "decidable": false }, + { + "cp_release": 2019, + "GEOID": "26", + "item": "CP03_2019_021", + "label": "2010-2014 Estimates!!COMMUTING TO WORK!!Workers 16 years and over!!Public transportation (excluding taxicab)", + "estimate_late": 1.3, + "moe_late": 0.1, + "estimate_early": 1.4, + "moe_early": 0.1, + "z": 1.1632, + "ours_significant": false, + "census_significant": true, + "agree": false, + "z_range_under_rounding": [ + 0.0, + null + ], + "decidable": false + }, { "cp_release": 2019, "GEOID": "06", @@ -1742,6 +1761,25 @@ ], "decidable": false }, + { + "cp_release": 2019, + "GEOID": "26", + "item": "CP03_2019_033", + "label": "2010-2014 Estimates!!INDUSTRY!!Civilian employed population 16 years and over!!Agriculture, forestry, fishing and hunting, and mining", + "estimate_late": 1.2, + "moe_late": 0.1, + "estimate_early": 1.3, + "moe_early": 0.1, + "z": 1.1632, + "ours_significant": false, + "census_significant": true, + "agree": false, + "z_range_under_rounding": [ + 0.0, + null + ], + "decidable": false + }, { "cp_release": 2019, "GEOID": "36", @@ -1761,6 +1799,25 @@ ], "decidable": false }, + { + "cp_release": 2019, + "GEOID": "26", + "item": "CP03_2019_036", + "label": "2010-2014 Estimates!!INDUSTRY!!Civilian employed population 16 years and over!!Wholesale trade", + "estimate_late": 2.4, + "moe_late": 0.1, + "estimate_early": 2.5, + "moe_early": 0.1, + "z": 1.1632, + "ours_significant": false, + "census_significant": true, + "agree": false, + "z_range_under_rounding": [ + 0.0, + null + ], + "decidable": false + }, { "cp_release": 2019, "GEOID": "48", @@ -1912,63 +1969,6 @@ null ], "decidable": false - }, - { - "cp_release": 2023, - "GEOID": "06", - "item": "CP03_2023_006", - "label": "2014-2018 Estimates!!EMPLOYMENT STATUS!!Population 16 years and over!!In labor force!!Armed Forces", - "estimate_late": 0.5, - "moe_late": 0.1, - "estimate_early": 0.4, - "moe_early": 0.1, - "z": 1.1632, - "ours_significant": false, - "census_significant": true, - "agree": false, - "z_range_under_rounding": [ - 0.0, - null - ], - "decidable": false - }, - { - "cp_release": 2023, - "GEOID": "36", - "item": "CP03_2023_006", - "label": "2014-2018 Estimates!!EMPLOYMENT STATUS!!Population 16 years and over!!In labor force!!Armed Forces", - "estimate_late": 0.2, - "moe_late": 0.1, - "estimate_early": 0.1, - "moe_early": 0.1, - "z": 1.1632, - "ours_significant": false, - "census_significant": true, - "agree": false, - "z_range_under_rounding": [ - 0.0, - null - ], - "decidable": false - }, - { - "cp_release": 2023, - "GEOID": "48", - "item": "CP03_2023_006", - "label": "2014-2018 Estimates!!EMPLOYMENT STATUS!!Population 16 years and over!!In labor force!!Armed Forces", - "estimate_late": 0.5, - "moe_late": 0.1, - "estimate_early": 0.4, - "moe_early": 0.1, - "z": 1.1632, - "ours_significant": false, - "census_significant": true, - "agree": false, - "z_range_under_rounding": [ - 0.0, - null - ], - "decidable": false } ], "census_source": [ @@ -1990,51 +1990,51 @@ "per_release": { "2019 vs 2014": { "items": 83, - "tests": 236, - "agree": 211, - "agreement_rate": 0.8940677966101694, + "tests": 283, + "agree": 255, + "agreement_rate": 0.901060070671378, "overlap_years": 0, "skipped": { - "estimate or MOE not published": 80, - "income-bracket share re-tabulated by Census after inflation adjustment": 99 + "estimate or MOE not published": 96, + "income-bracket share re-tabulated by Census after inflation adjustment": 119 }, "unmatched_cp_items": 54, - "decidable_tests": 159, - "decidable_agree": 159 + "decidable_tests": 187, + "decidable_agree": 187 }, "2023 vs 2018": { "items": 109, - "tests": 380, - "agree": 360, - "agreement_rate": 0.9473684210526315, + "tests": 456, + "agree": 430, + "agreement_rate": 0.9429824561403509, "overlap_years": 0, "skipped": { - "estimate or MOE not published": 65, - "income-bracket share re-tabulated by Census after inflation adjustment": 100 + "estimate or MOE not published": 78, + "income-bracket share re-tabulated by Census after inflation adjustment": 120 }, "unmatched_cp_items": 28, - "decidable_tests": 283, - "decidable_agree": 283 + "decidable_tests": 335, + "decidable_agree": 335 } }, - "tests_total": 616, - "decidable_tests": 442, - "undecidable_tests": 174, + "tests_total": 739, + "decidable_tests": 522, + "undecidable_tests": 217, "agreement_rate_decidable": 1.0, - "agreement_rate_all_tests": 0.926948051948052, - "agreement_rate_undecidable": 0.7413793103448276, + "agreement_rate_all_tests": 0.9269282814614344, + "agreement_rate_undecidable": 0.7511520737327189, "disagreements_abs_z_minus_1645": { "n": 0 }, "census_flag_encoding": "'*' = statistically significant at 90%; null/empty = not significant (variable label 'Statistical Significance!!...', predicateType string)" }, "mode": "live", - "seconds": 1.07 + "seconds": 2.99 }, { "check_id": "F8", - "description": "Sum of all counties (aggregate.aggregate_geographies, approximate MOE) vs the published state estimate for additive counts (B01003_001, B25001_001, B11001_001, B11001_002, B11001_003, B11001_004, B11001_005, B11001_006, B11001_007, B11001_008, B11001_009), states 06,36,48,17,56, releases 2019, 2023.", - "n_compared": 110, + "description": "Sum of all counties (aggregate.aggregate_geographies, approximate MOE) vs the published state estimate for additive counts (B01003_001, B25001_001, B11001_001, B11001_002, B11001_003, B11001_004, B11001_005, B11001_006, B11001_007, B11001_008, B11001_009), states 06,36,48,17,56,26, releases 2019, 2023.", + "n_compared": 132, "n_failed": 0, "max_abs_error": 0.0, "max_rel_error": null, @@ -2051,109 +2051,109 @@ "details": { "moe_ratio_sum_approx_over_state_published": { "B25001_001": { - "n": 10, - "min": 0.9200765829347392, - "p5": 0.9702394078119005, - "p25": 1.376113237503102, + "n": 12, + "min": 0.8221877095170256, + "p5": 0.8760265898967681, + "p25": 1.2676735772392067, "median": 2.0520658986589773, - "p75": 2.497099099175031, - "p95": 3.2782897272583877, + "p75": 2.517283311110397, + "p95": 3.2695165690838586, "max": 3.3177689390437686 }, "B11001_001": { - "n": 10, + "n": 12, "min": 0.4904288084347447, - "p5": 0.49170432173210277, - "p25": 0.5180181980138052, + "p5": 0.49198776913151565, + "p25": 0.5206201731224245, "median": 0.5839980208520118, - "p75": 0.6883133246348668, - "p95": 1.0711316438856664, + "p75": 0.6275524245060846, + "p95": 1.0567856764168537, "max": 1.1356884974953243 }, "B11001_002": { - "n": 10, + "n": 12, "min": 0.533780401313172, - "p5": 0.5721635686135307, - "p25": 0.6292462134947351, + "p5": 0.5806931613469438, + "p25": 0.6294049227653183, "median": 0.7032767886762725, - "p75": 0.9109901178233026, - "p95": 1.0501421401593645, + "p75": 0.8641937561250256, + "p95": 1.0430654234605417, "max": 1.0819873653040675 }, "B11001_003": { - "n": 10, + "n": 12, "min": 0.46528240950424143, - "p5": 0.48385216156572847, - "p25": 0.5314522014696266, + "p5": 0.48797877313494786, + "p25": 0.5534631693048354, "median": 0.6217339720149735, - "p75": 0.7938373754606243, - "p95": 1.0150855579716258, + "p75": 0.7414718507922212, + "p95": 1.0107738701548448, "max": 1.0344881531471406 }, "B11001_004": { - "n": 10, + "n": 12, "min": 0.8596575885724728, - "p5": 0.8749576459107514, - "p25": 0.905182754190873, - "median": 0.9273229249160981, - "p75": 0.9628670519610404, - "p95": 1.0222514778347334, + "p5": 0.8783576586525912, + "p25": 0.8997092650435321, + "median": 0.9252882830790294, + "p75": 0.9576767116631979, + "p95": 1.0158690350395374, "max": 1.050972470413115 }, "B11001_005": { - "n": 10, + "n": 12, "min": 0.8105428352930195, - "p5": 0.8728236338560468, - "p25": 0.9714868053551331, - "median": 1.001570136611617, - "p75": 1.0432243849624605, - "p95": 1.0946760150295458, + "p5": 0.8614994918107594, + "p25": 0.9648362532205813, + "median": 0.9906652131583422, + "p75": 1.0421326463241056, + "p95": 1.0856472021508765, "max": 1.1353056729835578 }, "B11001_006": { - "n": 10, + "n": 12, "min": 0.8997271046769949, - "p5": 0.9005037475606822, - "p25": 0.9305906371650302, + "p5": 0.9006763348681682, + "p25": 0.9365119096497926, "median": 0.9542296283215247, - "p75": 0.9797943499486612, - "p95": 1.0295970755556445, + "p75": 0.9804248178274029, + "p95": 1.0252041252463, "max": 1.0493653519476953 }, "B11001_007": { - "n": 10, + "n": 12, "min": 0.8036290107590621, - "p5": 0.8734460689673557, - "p25": 0.966270164453756, - "median": 1.0152523560486246, - "p75": 1.0817042990472965, - "p95": 1.1055862828350502, + "p5": 0.866743298945988, + "p25": 0.9635052362954257, + "median": 0.9989317189619551, + "p75": 1.075198054771135, + "p95": 1.1045422107777148, "max": 1.11028460709306 }, "B11001_008": { - "n": 10, - "min": 0.894953290664733, - "p5": 0.9341284324260899, - "p25": 0.9912930176720269, - "median": 1.0352289844645162, - "p75": 1.0724867215601919, - "p95": 1.1475252611230733, + "n": 12, + "min": 0.8165772484883481, + "p5": 0.8596840716853598, + "p25": 0.9712384920561632, + "median": 1.0219268811229107, + "p75": 1.0544642643338071, + "p95": 1.1398033264210572, "max": 1.182273967282146 }, "B11001_009": { - "n": 10, - "min": 0.9022493372059337, - "p5": 0.9195040596699223, - "p25": 0.9615270005344628, - "median": 1.0165662850562542, - "p75": 1.0331031707050693, - "p95": 1.07798386716504, + "n": 12, + "min": 0.8566024005642212, + "p5": 0.8817082157171631, + "p25": 0.9505326361537468, + "median": 1.007390193064066, + "p75": 1.0282877042562046, + "p95": 1.0764420369011085, "max": 1.0849221033527314 } } }, "mode": "live", - "seconds": 2.28 + "seconds": 2.51 }, { "check_id": "F9", @@ -2206,12 +2206,12 @@ } }, "mode": "live", - "seconds": 3.03 + "seconds": 2.44 }, { "check_id": "F10", - "description": "ACS 2019-2023 table-based Summary File cells (bulk delivery on www2.census.gov) vs API cells for tables B01003, B25003, B17001, B19001, the nation and states 06,36,48,17,56.", - "n_compared": 960, + "description": "ACS 2019-2023 table-based Summary File cells (bulk delivery on www2.census.gov) vs API cells for tables B01003, B25003, B17001, B19001, the nation and states 06,36,48,17,56,26.", + "n_compared": 1120, "n_failed": 0, "max_abs_error": 0.0, "max_rel_error": null, @@ -2232,33 +2232,33 @@ "details": { "tables": { "B01003": { - "cells": 12, + "cells": 14, "mismatches": 0, - "rows": 6, + "rows": 7, "byte_range": "bytes=0-262143 of 18311997" }, "B25003": { - "cells": 36, + "cells": 42, "mismatches": 0, - "rows": 6, + "rows": 7, "byte_range": "bytes=0-262143 of 26878591" }, "B17001": { - "cells": 708, + "cells": 826, "mismatches": 0, - "rows": 6, + "rows": 7, "byte_range": "bytes=0-262143 of 118402375" }, "B19001": { - "cells": 204, + "cells": 238, "mismatches": 0, - "rows": 6, + "rows": 7, "byte_range": "bytes=0-262143 of 68859942" } } }, "mode": "live", - "seconds": 9.12 + "seconds": 9.25 } ] } diff --git a/docs/audits/validation_report.md b/docs/audits/validation_report.md index eba3f67..d325f23 100644 --- a/docs/audits/validation_report.md +++ b/docs/audits/validation_report.md @@ -1,6 +1,6 @@ # Validation against Census-published values -Generated: 2026-11-08T02:01:53+00:00 +Generated: 2026-11-08T04:40:37+00:00 Mode: live Command: `python scripts/validate_against_census.py --live` @@ -11,22 +11,22 @@ Each check recomputes a number the Census Bureau computed independently and comp | Check | Status | Compared | Failed | Max abs error | Max rel error | Tolerance | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | F1 | PASSED | 9800 | 0 | 0 | - | exact (float equality); sentinel codes and non-numeric cells must be missing in the output; a controlled MOE (-555555555) must be 0 and flagged in its _CONTROLLED column | -| F2 | PASSED | 5040 | 0 | 0.05 | 0.04712 | estimate: \|ours - DP\| <= 0.1 percentage point (DP is rounded to 0.1). MOE: our approximate MOE must equal an independent evaluation of the Handbook formulas (1e-9); for single-cell numerators (simple subset proportions) it must also be within max(0.2 point, 25%) of the DP MOE, unless the exact replicate MOE for the same geography reproduces the DP MOE within 0.1 point (then the gap is the approximation's ignored covariance and is counted as explained); for multi-cell numerators the ratio ours/DP is reported, not asserted | -| F3 | PASSED | 29572 | 0 | 0.5 | - | replicate MOE within 0.5 of the published (integer) MOE, i.e. one rounding unit; VRE cell == API cell exactly (an API MOE of -555555555 matches a controlled VRE row) | -| F4 | PASSED | 1038 | 0 | 0.09838 | - | \|ours - DP\| <= 0.1 percentage point for both the estimate and the MOE (DP rounded to 0.1) | -| F5 | PASSED | 1983 | 0 | 1.623e+04 | 0.1985 | gross rent: every county within max($1, 0.1%) of B25064. Household income: median \|relative error\| < 1% and >= 98% of counties within the published B19013 MOE (share within 2% reported, not asserted: B19001 is coarser than the distribution Census interpolates) | -| F6 | PASSED | 125 | 0 | 275.6 | 0.005741 | \|ours - CP\| <= max($1, 0.5% of CP), asserted for every item Census converts with a single factor (means, aggregates, per-capita); medians reported (see notes) | -| F7 | PASSED | 442 | 0 | - | - | agreement on >= 98% of tests that are decidable from the published (rounded) values; every disagreement listed with its z statistic and its z range under rounding | -| F8 | PASSED | 110 | 0 | 0 | - | estimate exact; MOE ratio (approximate sum MOE / published state MOE) reported, not asserted | +| F2 | PASSED | 5880 | 0 | 0.05 | 0.04712 | estimate: \|ours - DP\| <= 0.1 percentage point (DP is rounded to 0.1). MOE: our approximate MOE must equal an independent evaluation of the Handbook formulas (1e-9); for single-cell numerators (simple subset proportions) it must also be within max(0.2 point, 25%) of the DP MOE, unless the exact replicate MOE for the same geography reproduces the DP MOE within 0.1 point (then the gap is the approximation's ignored covariance and is counted as explained); for multi-cell numerators the ratio ours/DP is reported, not asserted | +| F3 | PASSED | 30496 | 0 | 0.5 | - | replicate MOE within 0.5 of the published (integer) MOE, i.e. one rounding unit; VRE cell == API cell exactly (an API MOE of -555555555 matches a controlled VRE row) | +| F4 | PASSED | 1212 | 0 | 0.09838 | - | \|ours - DP\| <= 0.1 percentage point for both the estimate and the MOE (DP rounded to 0.1) | +| F5 | PASSED | 2315 | 0 | 1.623e+04 | 0.1985 | gross rent: every county within max($1, 0.1%) of B25064. Household income: median \|relative error\| < 1% and >= 98% of counties within the published B19013 MOE (share within 2% reported, not asserted: B19001 is coarser than the distribution Census interpolates) | +| F6 | PASSED | 150 | 0 | 275.6 | 0.005741 | \|ours - CP\| <= max($1, 0.5% of CP), asserted for every item Census converts with a single factor (means, aggregates, per-capita); medians reported (see notes) | +| F7 | PASSED | 522 | 0 | - | - | agreement on >= 98% of tests that are decidable from the published (rounded) values; every disagreement listed with its z statistic and its z range under rounding | +| F8 | PASSED | 132 | 0 | 0 | - | estimate exact; MOE ratio (approximate sum MOE / published state MOE) reported, not asserted | | F9 | PASSED | 48 | 0 | - | - | as Round 6: no concept flagged as both significantly (z-test, 90%) and implausibly (>25% relative or >10 points) different at the national level | -| F10 | PASSED | 960 | 0 | 0 | - | exact (numeric equality of every estimate and MOE cell) | +| F10 | PASSED | 1120 | 0 | 0 | - | exact (numeric equality of every estimate and MOE cell) | ### Known findings - **F5 (resolved 2026-11-02, census/medians.py):** when the cumulative count reaches exactly 50% at a bracket boundary and the following bracket(s) are empty, Census publishes the midpoint of the empty gap (B25063 2019: Sterling County TX 48431, $850; Stonewall County TX 48433, $600). The interpolation now returns that midpoint; failures matching the pattern are diagnosed as failures_explained_by_empty_bracket_gap_midpoint. -- **F5 (methodological, criterion revised 2026-11-02):** household income: B19001's $10k-$50k-wide brackets are much coarser than the $2,500 distribution Census interpolates (Subject Definitions, 'Median Income'), so a per-county 2% band measures bracket coarseness, not calculation error (first live run, 2019-2023: 84.1% of counties within 2%, 99.8% within the published B19013 MOE). The check asserts median relative error < 1% and >= 98% of counties within the published MOE; the 2% share is still reported. +- **F5 (methodological, criterion revised 2026-11-02):** household income: B19001's $10k-$50k-wide brackets are much coarser than the $2,500 distribution Census interpolates (Subject Definitions, 'Median Income'), so a per-county 2% band measures bracket coarseness, not calculation error (first live run, 2019-2023: 84.1% of counties within 2%, 99.8% within the published B19013 MOE; with Michigan added in Round 14: 85.9% and 99.7%). The check asserts median relative error < 1% and >= 98% of counties within the published MOE; the 2% share is still reported. - **F9 (informational, census/continuity.py):** the Round 6 continuity check flags real 2019->2023 change in stable concepts (B19001_015-017 top income brackets in nominal dollars, B17001_015, four B27010 cells), so `run_batch` blocks those extractions in the default concordance mode. Its thresholds are documented for adjacent, overlapping releases but are applied to any requested pair. -- **F2 (methodological):** owner-occupied (DP04_0046) county MOEs from the Handbook proportion formula miss the band in 215 of 998 county-years (median ratio 0.88); for every one of them the exact replicate MOE reproduces the DP MOE within 0.1 point, so the gap is the covariance the approximation ignores. Age 65+ (12-cell sum) approximate MOEs are ~4x the DP MOE (near-controlled age totals). +- **F2 (methodological):** owner-occupied (DP04_0046) county MOEs from the Handbook proportion formula miss the band in 279 of 1,164 county-years (median ratio 0.88); for every one of them the exact replicate MOE reproduces the DP MOE within 0.1 point, so the gap is the covariance the approximation ignores. Age 65+ (12-cell sum) approximate MOEs are ~4x the DP MOE (near-controlled age totals). - **F6:** Census's implied R-CPI-U-RS factor exceeds ours by 0.008% (2018->2023: 1.21729 = 449.3/369.1 vs ours 449.265/369.1) and 0.010% (2014->2019), consistent with Census using the index rounded to one decimal or an earlier vintage of the research series. 2019 CP medians are re-derived by Census, not scaled (see F6 notes). ## F1: PASSED @@ -34,7 +34,7 @@ Each check recomputes a number the Census Bureau computed independently and comp Every cell of run_batch output (B01001, county level, releases 2019, 2023, concordance on; tidy CSV panel and Parquet) equals the raw API cell for the same GEOID, year and source code (Census sentinels -> missing, except controlled MOEs -> 0 with _CONTROLLED = True). - Tolerance: exact (float equality); sentinel codes and non-numeric cells must be missing in the output; a controlled MOE (-555555555) must be 0 and flagged in its _CONTROLLED column -- Compared: 9800; failed: 0; max abs error: 0; max rel error: -; time: 6.0 s +- Compared: 9800; failed: 0; max abs error: 0; max rel error: -; time: 11.6 s - Responses pass through run_batch unchanged via a client whose _get_json is served by the same source as the comparison, so the check isolates parsing, sentinel cleaning, concordance renaming, panel stacking, slug renaming and file export.
Details @@ -82,10 +82,10 @@ Sources: ## F2: PASSED -Data Profile percentages and percent MOEs rebuilt from detailed-table counts with derived.derive_columns (sum_estimates + proportion, Handbook 2020 ch. 8 formulas 1 and 6), states 06,36,48,17,56 and all their counties, releases 2019, 2023. +Data Profile percentages and percent MOEs rebuilt from detailed-table counts with derived.derive_columns (sum_estimates + proportion, Handbook 2020 ch. 8 formulas 1 and 6), states 06,36,48,17,56,26 and all their counties, releases 2019, 2023. - Tolerance: estimate: |ours - DP| <= 0.1 percentage point (DP is rounded to 0.1). MOE: our approximate MOE must equal an independent evaluation of the Handbook formulas (1e-9); for single-cell numerators (simple subset proportions) it must also be within max(0.2 point, 25%) of the DP MOE, unless the exact replicate MOE for the same geography reproduces the DP MOE within 0.1 point (then the gap is the approximation's ignored covariance and is counted as explained); for multi-cell numerators the ratio ours/DP is reported, not asserted -- Compared: 5040; failed: 0; max abs error: 0.05; max rel error: 0.04712; time: 6.85 s +- Compared: 5880; failed: 0; max abs error: 0.05; max rel error: 0.04712; time: 8.49 s - Census computes Data Profile MOEs from replicate weights, which capture the covariance between numerator and denominator; the Handbook approximation ignores it, so it can over- or understate the MOE (ACS General Handbook 2020, ch. 8). Where the approximate MOE misses the band, the exact replicate MOE for the same geography is computed: if it reproduces the published MOE, the toolkit's formula is right and the gap is the documented approximation error.
Details @@ -292,17 +292,17 @@ Data Profile percentages and percent MOEs rebuilt from detailed-table counts wit }, "per_construction": { "poverty_rate 2019 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, "moe_band_misses_explained_by_replicates": 0, "moe_failures": 0, - "max_abs_error": 0.03651679214870818, + "max_abs_error": 0.04380614821259776, "skipped": 0 }, "uninsured_rate 2019 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -312,7 +312,7 @@ Data Profile percentages and percent MOEs rebuilt from detailed-table counts wit "skipped": 0 }, "owner_occupied 2019 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -322,7 +322,7 @@ Data Profile percentages and percent MOEs rebuilt from detailed-table counts wit "skipped": 0 }, "age_65_plus 2019 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -332,7 +332,7 @@ Data Profile percentages and percent MOEs rebuilt from detailed-table counts wit "skipped": 0 }, "bachelors_or_higher 2019 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -342,7 +342,7 @@ Data Profile percentages and percent MOEs rebuilt from detailed-table counts wit "skipped": 0 }, "poverty_rate 2019 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -352,27 +352,27 @@ Data Profile percentages and percent MOEs rebuilt from detailed-table counts wit "skipped": 0 }, "uninsured_rate 2019 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, "moe_band_misses_explained_by_replicates": 0, "moe_failures": 0, - "max_abs_error": 0.04993646759847614, + "max_abs_error": 0.04996510816468991, "skipped": 0 }, "owner_occupied 2019 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, - "moe_band_misses": 135, - "moe_band_misses_explained_by_replicates": 135, + "moe_band_misses": 172, + "moe_band_misses_explained_by_replicates": 172, "moe_failures": 0, "max_abs_error": 0.04996686547382012, "skipped": 0 }, "age_65_plus 2019 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -382,7 +382,7 @@ Data Profile percentages and percent MOEs rebuilt from detailed-table counts wit "skipped": 0 }, "bachelors_or_higher 2019 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -392,17 +392,17 @@ Data Profile percentages and percent MOEs rebuilt from detailed-table counts wit "skipped": 0 }, "poverty_rate 2023 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, "moe_band_misses_explained_by_replicates": 0, "moe_failures": 0, - "max_abs_error": 0.03356974815006453, + "max_abs_error": 0.04944047403746765, "skipped": 0 }, "uninsured_rate 2023 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -412,7 +412,7 @@ Data Profile percentages and percent MOEs rebuilt from detailed-table counts wit "skipped": 0 }, "owner_occupied 2023 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -422,17 +422,17 @@ Data Profile percentages and percent MOEs rebuilt from detailed-table counts wit "skipped": 0 }, "age_65_plus 2023 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, "moe_band_misses_explained_by_replicates": 0, "moe_failures": 0, - "max_abs_error": 0.040554153650933955, + "max_abs_error": 0.044602771997876545, "skipped": 0 }, "bachelors_or_higher 2023 state": { - "n": 5, + "n": 6, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -442,7 +442,7 @@ Data Profile percentages and percent MOEs rebuilt from detailed-table counts wit "skipped": 0 }, "poverty_rate 2023 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -452,7 +452,7 @@ Data Profile percentages and percent MOEs rebuilt from detailed-table counts wit "skipped": 0 }, "uninsured_rate 2023 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -462,17 +462,17 @@ Data Profile percentages and percent MOEs rebuilt from detailed-table counts wit "skipped": 0 }, "owner_occupied 2023 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, - "moe_band_misses": 80, - "moe_band_misses_explained_by_replicates": 80, + "moe_band_misses": 107, + "moe_band_misses_explained_by_replicates": 107, "moe_failures": 0, "max_abs_error": 0.04999999999999716, "skipped": 0 }, "age_65_plus 2023 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -482,7 +482,7 @@ Data Profile percentages and percent MOEs rebuilt from detailed-table counts wit "skipped": 0 }, "bachelors_or_higher 2023 county": { - "n": 499, + "n": 582, "estimate_failures": 0, "formula_mismatches": 0, "moe_band_misses": 0, @@ -494,59 +494,59 @@ Data Profile percentages and percent MOEs rebuilt from detailed-table counts wit }, "moe_ratio_ours_over_published": { "poverty_rate": { - "n": 1008, + "n": 1176, "min": 0.7736313821643819, - "p5": 0.9534212538422614, - "p25": 0.9884370013231081, - "median": 1.0002856232316664, - "p75": 1.0140808197710969, - "p95": 1.0508986869945134, - "max": 1.492935767601951 + "p5": 0.9554369497959188, + "p25": 0.9876097325820183, + "median": 1.0004333367260303, + "p75": 1.0146482058918944, + "p95": 1.0481317720389831, + "max": 1.4953084378391963 }, "uninsured_rate": { - "n": 1008, + "n": 1176, "min": 0.5245119880073353, - "p5": 0.7623261612371356, - "p25": 0.8382018345742577, - "median": 0.8947109410687769, - "p75": 0.959663952480057, - "p95": 1.0886752771469939, + "p5": 0.7602513283319245, + "p25": 0.8366644742921433, + "median": 0.8925526325526165, + "p75": 0.9589765222382747, + "p95": 1.0827144215893023, "max": 3.368149540320743 }, "owner_occupied": { - "n": 1008, - "min": 0.11740448706346762, - "p5": 0.5911391022180238, - "p25": 0.7804246992938717, - "median": 0.8847851481460042, - "p75": 0.9614225184624575, - "p95": 1.1256402470286904, + "n": 1176, + "min": 0.033101611186908554, + "p5": 0.5538309797594627, + "p25": 0.773165399793867, + "median": 0.8804264476946757, + "p75": 0.9602291433650332, + "p95": 1.1351535933009016, "max": 3.5880373603086078 }, "age_65_plus": { - "n": 972, + "n": 1135, "min": 0.32274979068910414, - "p5": 0.9133893879965173, - "p25": 2.806175898236696, - "median": 4.41323770953967, - "p75": 6.246878784493752, - "p95": 10.32043570564385, + "p5": 0.9745152132688413, + "p25": 3.0237311228219257, + "median": 4.64809113246433, + "p75": 6.543960270804267, + "p95": 10.371881329539105, "max": 24.355136826875203 }, "bachelors_or_higher": { - "n": 1008, + "n": 1176, "min": 0.6148187553996485, - "p5": 0.8995509215727955, - "p25": 0.970260790215502, - "median": 1.0213155010270547, - "p75": 1.0908896997218966, - "p95": 1.2085678597060883, + "p5": 0.9004757855672131, + "p25": 0.9692286593913597, + "median": 1.0205688957607086, + "p75": 1.0869595744621137, + "p95": 1.2101276755971069, "max": 3.9483660875278423 } }, "formula_mismatches": 0, - "proportion_ratio_fallbacks": 12, - "moe_band_misses_explained_by_replicates": 215, + "proportion_ratio_fallbacks": 22, + "moe_band_misses_explained_by_replicates": 279, "explained_sample": [ { "construction": "owner_occupied", @@ -687,7 +687,7 @@ Sources: MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, variance.replicate_moe) vs the MOE published in the same VRE file; VRE estimates and MOEs vs API estimates and MOEs for the same cells. Tables B17001, B27010, B25003, B01001, B01003, B11001, B19001, B25063, state level, releases 2019, 2023, plus the committed county/state VRE fixtures. - Tolerance: replicate MOE within 0.5 of the published (integer) MOE, i.e. one rounding unit; VRE cell == API cell exactly (an API MOE of -555555555 matches a controlled VRE row) -- Compared: 29572; failed: 0; max abs error: 0.5; max rel error: -; time: 9.0 s +- Compared: 30496; failed: 0; max abs error: 0.5; max rel error: -; time: 9.5 s - Controlled rows (CME '*****', published MOE 0) and model-based rows (all 80 replicates equal the estimate, published MOE from the VRE documentation's eqs. 3-5) cannot be reproduced from replicates and are counted separately, as the VRE documentation prescribes.
Details @@ -703,7 +703,7 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 0, "max_abs_diff": 0.49988453556113654, "moe_mismatches": 0, - "api_cells_compared": 590, + "api_cells_compared": 708, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -715,7 +715,7 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 11, "max_abs_diff": 0.4998932918888386, "moe_mismatches": 0, - "api_cells_compared": 660, + "api_cells_compared": 792, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -727,7 +727,7 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 0, "max_abs_diff": 0.49547866262082607, "moe_mismatches": 0, - "api_cells_compared": 30, + "api_cells_compared": 36, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -739,9 +739,9 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 0, "max_abs_diff": 0.49996392293905956, "moe_mismatches": 0, - "api_cells_compared": 490, + "api_cells_compared": 588, "api_cell_mismatches": 0, - "controlled_moe_cells": 5 + "controlled_moe_cells": 6 }, "B01003 2019": { "scope": "full national file (summary level 040)", @@ -751,9 +751,9 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 0, "max_abs_diff": null, "moe_mismatches": 0, - "api_cells_compared": 10, + "api_cells_compared": 12, "api_cell_mismatches": 0, - "controlled_moe_cells": 5 + "controlled_moe_cells": 6 }, "B11001 2019": { "scope": "full national file (summary level 040)", @@ -763,7 +763,7 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 0, "max_abs_diff": 0.4978310636047354, "moe_mismatches": 0, - "api_cells_compared": 90, + "api_cells_compared": 108, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -775,7 +775,7 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 0, "max_abs_diff": 0.4993840771894611, "moe_mismatches": 0, - "api_cells_compared": 170, + "api_cells_compared": 204, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -787,7 +787,7 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 0, "max_abs_diff": 0.499846723576411, "moe_mismatches": 0, - "api_cells_compared": 270, + "api_cells_compared": 324, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -799,7 +799,7 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 0, "max_abs_diff": 0.49969577847059554, "moe_mismatches": 0, - "api_cells_compared": 590, + "api_cells_compared": 708, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -811,7 +811,7 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 8, "max_abs_diff": 0.4999403443362098, "moe_mismatches": 0, - "api_cells_compared": 660, + "api_cells_compared": 792, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -823,7 +823,7 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 0, "max_abs_diff": 0.49928626996916137, "moe_mismatches": 0, - "api_cells_compared": 30, + "api_cells_compared": 36, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -835,9 +835,9 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 0, "max_abs_diff": 0.4997525480409877, "moe_mismatches": 0, - "api_cells_compared": 490, + "api_cells_compared": 588, "api_cell_mismatches": 0, - "controlled_moe_cells": 5 + "controlled_moe_cells": 6 }, "B01003 2023": { "scope": "full national file (summary level 040)", @@ -847,9 +847,9 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 0, "max_abs_diff": null, "moe_mismatches": 0, - "api_cells_compared": 10, + "api_cells_compared": 12, "api_cell_mismatches": 0, - "controlled_moe_cells": 5 + "controlled_moe_cells": 6 }, "B11001 2023": { "scope": "full national file (summary level 040)", @@ -859,7 +859,7 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 0, "max_abs_diff": 0.49874303933052033, "moe_mismatches": 0, - "api_cells_compared": 90, + "api_cells_compared": 108, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -871,7 +871,7 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 0, "max_abs_diff": 0.49951382657673093, "moe_mismatches": 0, - "api_cells_compared": 170, + "api_cells_compared": 204, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -883,7 +883,7 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, "model_based_rows": 0, "max_abs_diff": 0.4994722210440443, "moe_mismatches": 0, - "api_cells_compared": 270, + "api_cells_compared": 324, "api_cell_mismatches": 0, "controlled_moe_cells": 0 }, @@ -929,7 +929,7 @@ MOEs recomputed from the 80 variance replicates (replicate.verify_published_moe, } }, "replicate_rows_checked": 24952, - "api_cells_compared": 4620, + "api_cells_compared": 5544, "api_cell_mismatches": 0 } ``` @@ -959,10 +959,10 @@ Sources: ## F4: PASSED -Exact replicate proportions (replicate.replicate_proportion on VRE files) vs Data Profile percents and percent MOEs: states 06,36,48,17,56 for every construction with a VRE table, plus all their counties for owner_occupied; releases 2019, 2023. +Exact replicate proportions (replicate.replicate_proportion on VRE files) vs Data Profile percents and percent MOEs: states 06,36,48,17,56,26 for every construction with a VRE table, plus all their counties for owner_occupied; releases 2019, 2023. - Tolerance: |ours - DP| <= 0.1 percentage point for both the estimate and the MOE (DP rounded to 0.1) -- Compared: 1038; failed: 0; max abs error: 0.09838; max rel error: -; time: 0.61 s +- Compared: 1212; failed: 0; max abs error: 0.09838; max rel error: -; time: 0.74 s - published_moe_at_display_floor counts rows where the DP shows a percent MOE of 0.1 while the exact replicate MOE is below 0.05 (it would round to 0.0). This occurs for the 65+ share at the state level, which is nearly controlled to the population estimates by age; Census does not display a 0.0 percent MOE for an uncontrolled item, so 0.1 acts as a floor. These rows pass only because the tolerance is 0.1; they are listed so the margin is visible.
Details @@ -971,72 +971,72 @@ Exact replicate proportions (replicate.replicate_proportion on VRE files) vs Dat { "per_construction": { "poverty_rate 2019 state": { - "n": 5, + "n": 6, "failures": 0, - "max_estimate_error": 0.03651679214870818, - "max_moe_error": 0.02393998589620068, + "max_estimate_error": 0.04380614821259776, + "max_moe_error": 0.04960234914349701, "skipped": 0 }, "uninsured_rate 2019 state": { - "n": 5, + "n": 6, "failures": 0, "max_estimate_error": 0.04511322700916409, "max_moe_error": 0.04994497169954262, "skipped": 0 }, "owner_occupied 2019 state": { - "n": 5, + "n": 6, "failures": 0, "max_estimate_error": 0.04846751821835227, "max_moe_error": 0.044011990628512215, "skipped": 0 }, "age_65_plus 2019 state": { - "n": 5, + "n": 6, "failures": 0, "max_estimate_error": 0.04797550364362557, "max_moe_error": 0.09837759799016546, "skipped": 0, - "published_moe_at_display_floor": 5 + "published_moe_at_display_floor": 6 }, "owner_occupied 2019 county": { - "n": 499, + "n": 582, "failures": 0, "max_estimate_error": 0.04996686547382012, "max_moe_error": 0.049866982472014154, "skipped": 0 }, "poverty_rate 2023 state": { - "n": 5, + "n": 6, "failures": 0, - "max_estimate_error": 0.03356974815006453, + "max_estimate_error": 0.04944047403746765, "max_moe_error": 0.04916769747922606, "skipped": 0 }, "uninsured_rate 2023 state": { - "n": 5, + "n": 6, "failures": 0, "max_estimate_error": 0.04768904734502666, "max_moe_error": 0.04056661497016503, "skipped": 0 }, "owner_occupied 2023 state": { - "n": 5, + "n": 6, "failures": 0, "max_estimate_error": 0.03762119390653851, "max_moe_error": 0.035742822291887105, "skipped": 0 }, "age_65_plus 2023 state": { - "n": 5, + "n": 6, "failures": 0, - "max_estimate_error": 0.040554153650933955, + "max_estimate_error": 0.044602771997876545, "max_moe_error": 0.0983616748336616, "skipped": 0, - "published_moe_at_display_floor": 4 + "published_moe_at_display_floor": 5 }, "owner_occupied 2023 county": { - "n": 499, + "n": 582, "failures": 0, "max_estimate_error": 0.04999999999999716, "max_moe_error": 0.049901415046242015, @@ -1047,53 +1047,53 @@ Exact replicate proportions (replicate.replicate_proportion on VRE files) vs Dat "max_moe_error": 0.09837759799016546, "approximate_over_replicate_moe": { "poverty_rate state": { - "n": 10, + "n": 12, "min": 0.9973932075777341, - "p5": 0.9978147743473517, - "p25": 0.9985586361462659, + "p5": 0.9979084558517111, + "p25": 0.9987204883086159, "median": 0.9998334381640055, - "p75": 1.0007156223347162, - "p95": 1.0025456931650314, + "p75": 1.000730282423888, + "p95": 1.0022675419291092, "max": 1.0037973737266812 }, "uninsured_rate state": { - "n": 10, + "n": 12, "min": 0.7051799348769849, - "p5": 0.7137375419872339, - "p25": 0.7422443567188695, - "median": 0.7818525206367402, - "p75": 0.7956330708010112, - "p95": 0.8572450778988274, + "p5": 0.7156392324561781, + "p25": 0.7637002335050819, + "median": 0.7869317488089953, + "p75": 0.8025014805570005, + "p95": 0.8725018072000658, "max": 0.8869444583511927 }, "owner_occupied state": { - "n": 10, + "n": 12, "min": 0.8923353915038464, - "p5": 0.9223629423972761, - "p25": 1.2369931785012964, - "median": 1.2790646370898342, - "p75": 1.2939296775402536, - "p95": 1.3561068296827248, - "max": 1.3640124510373062 + "p5": 0.929035731484705, + "p25": 1.2457206843895248, + "median": 1.283935307811899, + "p75": 1.347947622211519, + "p95": 1.3936197167886188, + "max": 1.4298063749291121 }, "age_65_plus state": { - "n": 10, + "n": 12, "min": 4.773304094911635, - "p5": 5.432293303515204, - "p25": 7.89396453186003, + "p5": 5.578735349871553, + "p25": 8.070327705737789, "median": 12.356599715398112, - "p75": 17.595231361621554, - "p95": 21.988708609393594, + "p75": 15.668419542366417, + "p95": 21.607730735842374, "max": 23.703109040374095 }, "owner_occupied county": { - "n": 998, - "min": 0.11402469945309178, - "p5": 0.5819994095597515, - "p25": 0.7824720903505963, - "median": 0.8796293019122823, - "p75": 0.9636591439799281, - "p95": 1.0868608477420798, + "n": 1164, + "min": 0.03394176482653954, + "p5": 0.5554692609363415, + "p25": 0.7707697011792228, + "median": 0.8754906559828328, + "p75": 0.9609813239564091, + "p95": 1.0905260951161029, "max": 3.5535069687610403 } }, @@ -1120,10 +1120,10 @@ Sources: ## F5: PASSED -Medians interpolated from published distribution tables (medians.median_for_rows, linear interpolation) vs published medians, all counties of states 06,36,48,17,56, releases 2019, 2023. +Medians interpolated from published distribution tables (medians.median_for_rows, linear interpolation) vs published medians, all counties of states 06,36,48,17,56,26, releases 2019, 2023. - Tolerance: gross rent: every county within max($1, 0.1%) of B25064. Household income: median |relative error| < 1% and >= 98% of counties within the published B19013 MOE (share within 2% reported, not asserted: B19001 is coarser than the distribution Census interpolates) -- Compared: 1983; failed: 0; max abs error: 1.623e+04; max rel error: 0.1985; time: 3.24 s +- Compared: 2315; failed: 0; max abs error: 1.623e+04; max rel error: 0.1985; time: 3.0 s - n_failed counts gross-rent counties outside the per-county bound plus household-income releases whose distribution criterion is not met (median relative error < 1% and >= 98% of counties within the published MOE; counties outside 2% are listed as counties_outside_2pct). Published medians at a top or bottom coding bound and medians we top/bottom-code are excluded.
Details @@ -1133,22 +1133,22 @@ Medians interpolated from published distribution tables (medians.median_for_rows "pairs": { "B25063->B25064_001 2019": { "definition": "Median gross rent (B25064) from the gross rent distribution (B25063, 24 cash-rent brackets; 'No cash rent' excluded). Census computes B25064 from the same brackets.", - "counties": 499, - "compared": 496, + "counties": 582, + "compared": 579, "excluded_top_or_bottom_coded_or_missing": 3, "max_abs_error": 0.5, - "median_abs_error": 0.23883986064225837, + "median_abs_error": 0.23744292237438458, "max_rel_error": 0.0009487666034155328, - "median_rel_error": 0.00030089067630395484, + "median_rel_error": 0.00029740145478872104, "share_within_2pct": 1.0, "approx_moe_over_published_moe": { - "n": 496, + "n": 579, "min": 0.6514412127358754, - "p5": 0.894596428650637, - "p25": 1.0424454239581795, - "median": 1.128010743474428, - "p75": 1.2131230539877658, - "p95": 1.401804095917858, + "p5": 0.9033103693911765, + "p25": 1.042242022506491, + "median": 1.125984816764382, + "p75": 1.2064967258369097, + "p95": 1.3940536662403271, "max": 3.282941519739477 }, "share_within_1_dollar": 1.0, @@ -1157,48 +1157,48 @@ Medians interpolated from published distribution tables (medians.median_for_rows }, "B19001->B19013_001 2019": { "definition": "Median household income (B19013) from household income brackets (B19001, 16 brackets). Census interpolates a finer internal distribution ($2,500 intervals below $250,000; Subject Definitions 2023, 'Median Income'), so exact agreement is not expected.", - "counties": 499, - "compared": 499, + "counties": 582, + "compared": 582, "excluded_top_or_bottom_coded_or_missing": 0, "max_abs_error": 5398.1666666666715, - "median_abs_error": 258.0281690140837, + "median_abs_error": 234.9357802822633, "max_rel_error": 0.06880502022364983, - "median_rel_error": 0.004856078477082083, - "share_within_2pct": 0.905811623246493, + "median_rel_error": 0.004526242972804987, + "share_within_2pct": 0.9192439862542955, "approx_moe_over_published_moe": { - "n": 499, + "n": 582, "min": 0.5721817734474332, - "p5": 0.826981323927377, - "p25": 0.9901451746977017, - "median": 1.1050403899416155, - "p75": 1.2539766360798126, - "p95": 1.517586881377449, + "p5": 0.8388430953154132, + "p25": 1.0022439172105853, + "median": 1.1036472172599594, + "p75": 1.2462187436889534, + "p95": 1.4825618386027943, "max": 2.7566364760267823 }, "counties_outside_2pct": 47, "criterion": "median relative error < 1% and >= 98% of counties within the published MOE (2% share reported, not asserted)", "criterion_met": true, - "share_within_published_moe": 0.9879759519038076, + "share_within_published_moe": 0.9896907216494846, "outside_2pct_but_within_published_moe": 46 }, "B25063->B25064_001 2023": { "definition": "Median gross rent (B25064) from the gross rent distribution (B25063, 24 cash-rent brackets; 'No cash rent' excluded). Census computes B25064 from the same brackets.", - "counties": 499, - "compared": 490, + "counties": 582, + "compared": 573, "excluded_top_or_bottom_coded_or_missing": 9, "max_abs_error": 0.5, - "median_abs_error": 0.22684391080616706, + "median_abs_error": 0.23076923076928324, "max_rel_error": 0.0009785677618069815, - "median_rel_error": 0.0002207326563185523, + "median_rel_error": 0.00022495755887916462, "share_within_2pct": 1.0, "approx_moe_over_published_moe": { - "n": 490, + "n": 573, "min": 0.5707367985396146, - "p5": 0.843171302726709, - "p25": 1.0281655600034902, - "median": 1.1168789341235161, - "p75": 1.226784865025155, - "p95": 1.4380230843865633, + "p5": 0.8450357070775417, + "p25": 1.030450913896718, + "median": 1.1167781135639878, + "p75": 1.2226920030193589, + "p95": 1.4173892910204984, "max": 7.554880616634601 }, "share_within_1_dollar": 1.0, @@ -1207,29 +1207,29 @@ Medians interpolated from published distribution tables (medians.median_for_rows }, "B19001->B19013_001 2023": { "definition": "Median household income (B19013) from household income brackets (B19001, 16 brackets). Census interpolates a finer internal distribution ($2,500 intervals below $250,000; Subject Definitions 2023, 'Median Income'), so exact agreement is not expected.", - "counties": 499, - "compared": 498, + "counties": 582, + "compared": 581, "excluded_top_or_bottom_coded_or_missing": 1, "max_abs_error": 16226.11627906977, - "median_abs_error": 446.5339830084995, + "median_abs_error": 435.19959278321767, "max_rel_error": 0.1985113138045458, - "median_rel_error": 0.006389973134895618, - "share_within_2pct": 0.8413654618473896, + "median_rel_error": 0.006355229464913636, + "share_within_2pct": 0.8588640275387264, "approx_moe_over_published_moe": { - "n": 498, + "n": 581, "min": 0.4454191627730805, - "p5": 0.7499554269308675, - "p25": 0.9314206768081066, - "median": 1.0550169294345477, - "p75": 1.194524298112461, - "p95": 1.506366979317927, + "p5": 0.7551186429582991, + "p25": 0.9449788203902701, + "median": 1.0699313688671936, + "p75": 1.2061307364797798, + "p95": 1.5063513752992483, "max": 55.87813186455595 }, - "counties_outside_2pct": 79, + "counties_outside_2pct": 82, "criterion": "median relative error < 1% and >= 98% of counties within the published MOE (2% share reported, not asserted)", "criterion_met": true, - "share_within_published_moe": 0.9979919678714859, - "outside_2pct_but_within_published_moe": 78 + "share_within_published_moe": 0.9965576592082617, + "outside_2pct_but_within_published_moe": 80 } } } @@ -1259,10 +1259,10 @@ Sources: ## F6: PASSED -Comparison Profile (CP03) earlier-period dollar values, which Census publishes in current-period dollars, vs the earlier release's DP03 value adjusted with inflation.adjust_inflation (R-CPI-U-RS, the default), states 06,36,48,17,56, CP releases 2019 (vs 2014), 2023 (vs 2018). +Comparison Profile (CP03) earlier-period dollar values, which Census publishes in current-period dollars, vs the earlier release's DP03 value adjusted with inflation.adjust_inflation (R-CPI-U-RS, the default), states 06,36,48,17,56,26, CP releases 2019 (vs 2014), 2023 (vs 2018). - Tolerance: |ours - CP| <= max($1, 0.5% of CP), asserted for every item Census converts with a single factor (means, aggregates, per-capita); medians reported (see notes) -- Compared: 125; failed: 0; max abs error: 275.6; max rel error: 0.005741; time: 6.02 s +- Compared: 150; failed: 0; max abs error: 275.6; max rel error: 0.005741; time: 7.06 s - Asserted: means, aggregates and per-capita values, which Census converts with a single factor (implied factor constant to 5 digits across items and states). Median items are compared and reported but not asserted: in the 2019 Comparison Profile their implied factors scatter (1.0747-1.0838 against 1.08097 for means), showing that Census re-derived those medians from inflation-adjusted distributions rather than scaling the published median, which no deflator can reproduce from published data; in the 2023 CP medians scale with the same constant factor and all pass. Median misses are listed with asserted=false. - Round 6 acceptance criterion 6: the Census-implied factor (median over mean/aggregate/per-capita items) is compared with our R-CPI-U-RS factor in details.factors.*.implied_minus_ours_percent. @@ -1279,33 +1279,33 @@ Comparison Profile (CP03) earlier-period dollar values, which Census publishes i "2019": 375.813 }, "census_implied_factor_all_items": { - "n": 50, + "n": 60, "min": 1.0746843377809618, - "p5": 1.076145198907045, - "p25": 1.0802195382581568, - "median": 1.0809600821614125, - "p75": 1.0809759547539666, - "p95": 1.0825238763942842, + "p5": 1.0773204081578227, + "p25": 1.0801767593654468, + "median": 1.0809592914613915, + "p75": 1.0809708958581514, + "p95": 1.0821819415103087, "max": 1.0838654177021603 }, "census_implied_factor_means_and_per_capita": { - "n": 20, - "min": 1.0809414249613398, - "p5": 1.0809530432938104, + "n": 24, + "min": 1.0809394484183146, + "p5": 1.0809432594348878, "p25": 1.0809600994303399, "median": 1.0809657866727096, "p75": 1.0809701408143002, - "p95": 1.080979121965741, + "p95": 1.0809787389980172, "max": 1.0809820447050202 }, "census_implied_factor_medians": { - "n": 30, + "n": 36, "min": 1.0746843377809618, - "p5": 1.0748980061926594, - "p25": 1.0800400819240237, - "median": 1.0805843994280213, - "p75": 1.0813419154779242, - "p95": 1.0833594072049817, + "p5": 1.0750013722228515, + "p25": 1.0799983347410265, + "median": 1.0803961190783977, + "p75": 1.0810451289198495, + "p95": 1.0830715220694618, "max": 1.0838654177021603 }, "implied_minus_ours_percent": 0.010325354924156649, @@ -1320,36 +1320,36 @@ Comparison Profile (CP03) earlier-period dollar values, which Census publishes i "2023": 449.265 }, "census_implied_factor_all_items": { - "n": 75, + "n": 90, "min": 1.217095075874884, - "p5": 1.2172690671665287, - "p25": 1.2172780351481864, - "median": 1.2172863599013968, - "p75": 1.2172909991247587, - "p95": 1.2173506918729535, + "p5": 1.2172687528930333, + "p25": 1.2172782423061743, + "median": 1.2172863894784407, + "p75": 1.2172920057657355, + "p95": 1.2173483380353065, "max": 1.217607973421927 }, "census_implied_factor_means_and_per_capita": { - "n": 45, + "n": 54, "min": 1.217095075874884, - "p5": 1.2172694830791926, - "p25": 1.2172809028390055, - "median": 1.2172863599013968, - "p75": 1.2172931097568669, - "p95": 1.217359317116744, + "p5": 1.2172695703442484, + "p25": 1.217280932305722, + "median": 1.2172865448520538, + "p75": 1.2172940141155042, + "p95": 1.217355827531763, "max": 1.217607973421927 }, "census_implied_factor_medians": { - "n": 30, + "n": 36, "min": 1.2172652955167795, - "p5": 1.2172679035298999, - "p25": 1.2172738730731925, - "median": 1.217286132082998, - "p75": 1.2172880890236044, - "p95": 1.2172967018420697, + "p5": 1.2172671882402522, + "p25": 1.2172740281502468, + "median": 1.2172856268138728, + "p75": 1.2172877617920628, + "p95": 1.2172956567015745, "max": 1.2173020424055785 }, - "implied_minus_ours_percent": 0.007878521497461932, + "implied_minus_ours_percent": 0.007893716379658855, "current_period_cp_ne_dp": 0, "unmatched_cp_items": 28 } @@ -1376,10 +1376,10 @@ Sources: ## F7: PASSED -Comparison Profile significance flags (CP03 '..._SS' variables: '*' = the two periods differ at the 90% level, empty = not significant) vs stats.z_test on the CP estimates with the Data Profile MOEs of each release, states 06,36,48,17,56, CP releases 2019 (vs 2014), 2023 (vs 2018). +Comparison Profile significance flags (CP03 '..._SS' variables: '*' = the two periods differ at the 90% level, empty = not significant) vs stats.z_test on the CP estimates with the Data Profile MOEs of each release, states 06,36,48,17,56,26, CP releases 2019 (vs 2014), 2023 (vs 2018). - Tolerance: agreement on >= 98% of tests that are decidable from the published (rounded) values; every disagreement listed with its z statistic and its z range under rounding -- Compared: 442; failed: 0; max abs error: -; max rel error: -; time: 1.07 s +- Compared: 522; failed: 0; max abs error: -; max rel error: -; time: 2.99 s - The API's cprofile endpoint publishes estimates (E), annotations (EA) and significance (SS) only, no MOEs; MOEs come from each release's Data Profile (PM for percent items, M for others). For dollar items the earlier MOE is scaled by the same R-CPI-U-RS factor as the estimate. - Census tests unrounded estimates and standard errors; we test the published (rounded) values, so disagreements are expected only for z close to the 1.645 critical value. - A test is 'decidable' when every combination of unrounded values consistent with the published rounding (estimates and MOEs +/- half a unit; a published percent MOE of 0.1 may be any value in (0, 0.15)) gives z on the same side of 1.645. Only decidable tests can disagree with Census because of a calculation error; agreement is asserted on them and reported for all tests. Failures list decidable disagreements first, then undecidable ones. @@ -1394,39 +1394,39 @@ Comparison Profile significance flags (CP03 '..._SS' variables: '*' = the two pe "per_release": { "2019 vs 2014": { "items": 83, - "tests": 236, - "agree": 211, - "agreement_rate": 0.8940677966101694, + "tests": 283, + "agree": 255, + "agreement_rate": 0.901060070671378, "overlap_years": 0, "skipped": { - "estimate or MOE not published": 80, - "income-bracket share re-tabulated by Census after inflation adjustment": 99 + "estimate or MOE not published": 96, + "income-bracket share re-tabulated by Census after inflation adjustment": 119 }, "unmatched_cp_items": 54, - "decidable_tests": 159, - "decidable_agree": 159 + "decidable_tests": 187, + "decidable_agree": 187 }, "2023 vs 2018": { "items": 109, - "tests": 380, - "agree": 360, - "agreement_rate": 0.9473684210526315, + "tests": 456, + "agree": 430, + "agreement_rate": 0.9429824561403509, "overlap_years": 0, "skipped": { - "estimate or MOE not published": 65, - "income-bracket share re-tabulated by Census after inflation adjustment": 100 + "estimate or MOE not published": 78, + "income-bracket share re-tabulated by Census after inflation adjustment": 120 }, "unmatched_cp_items": 28, - "decidable_tests": 283, - "decidable_agree": 283 + "decidable_tests": 335, + "decidable_agree": 335 } }, - "tests_total": 616, - "decidable_tests": 442, - "undecidable_tests": 174, + "tests_total": 739, + "decidable_tests": 522, + "undecidable_tests": 217, "agreement_rate_decidable": 1.0, - "agreement_rate_all_tests": 0.926948051948052, - "agreement_rate_undecidable": 0.7413793103448276, + "agreement_rate_all_tests": 0.9269282814614344, + "agreement_rate_undecidable": 0.7511520737327189, "disagreements_abs_z_minus_1645": { "n": 0 }, @@ -1448,12 +1448,15 @@ Failures / disagreements (first 25): | 2019 | 48 | CP03_2019_006 | 2010-2014 Estimates!!EMPLOYMENT STATUS!!Population 16 years and over!!In labor force!!Armed Forces | 0.4 | 0.1 | 0.5 | 0.1 | 1.1632 | False | True | False | [0.0, inf] | False | | 2019 | 06 | CP03_2019_007 | 2010-2014 Estimates!!EMPLOYMENT STATUS!!Population 16 years and over!!Not in labor force | 36.3 | 0.1 | 36.2 | 0.1 | 1.1632 | False | True | False | [0.0, inf] | False | | 2019 | 06 | CP03_2019_021 | 2010-2014 Estimates!!COMMUTING TO WORK!!Workers 16 years and over!!Public transportation (excluding taxicab) | 5.1 | 0.1 | 5.2 | 0.1 | 1.1632 | False | True | False | [np.float64(0.0), inf] | False | +| 2019 | 26 | CP03_2019_021 | 2010-2014 Estimates!!COMMUTING TO WORK!!Workers 16 years and over!!Public transportation (excluding taxicab) | 1.3 | 0.1 | 1.4 | 0.1 | 1.1632 | False | True | False | [0.0, inf] | False | | 2019 | 06 | CP03_2019_022 | 2010-2014 Estimates!!COMMUTING TO WORK!!Workers 16 years and over!!Walked | 2.6 | 0.1 | 2.7 | 0.1 | 1.1632 | False | True | False | [np.float64(0.0), inf] | False | | 2019 | 17 | CP03_2019_022 | 2010-2014 Estimates!!COMMUTING TO WORK!!Workers 16 years and over!!Walked | 3 | 0.1 | 3.1 | 0.1 | 1.1632 | False | True | False | [np.float64(0.0), inf] | False | | 2019 | 48 | CP03_2019_022 | 2010-2014 Estimates!!COMMUTING TO WORK!!Workers 16 years and over!!Walked | 1.5 | 0.1 | 1.6 | 0.1 | 1.1632 | False | True | False | [np.float64(0.0), inf] | False | | 2019 | 06 | CP03_2019_023 | 2010-2014 Estimates!!COMMUTING TO WORK!!Workers 16 years and over!!Other means | 2.6 | 0.1 | 2.5 | 0.1 | 1.1632 | False | True | False | [np.float64(0.0), inf] | False | | 2019 | 36 | CP03_2019_023 | 2010-2014 Estimates!!COMMUTING TO WORK!!Workers 16 years and over!!Other means | 2 | 0.1 | 1.9 | 0.1 | 1.1632 | False | True | False | [np.float64(0.0), inf] | False | +| 2019 | 26 | CP03_2019_033 | 2010-2014 Estimates!!INDUSTRY!!Civilian employed population 16 years and over!!Agriculture, forestry, fishing and hunting, and mining | 1.2 | 0.1 | 1.3 | 0.1 | 1.1632 | False | True | False | [np.float64(0.0), inf] | False | | 2019 | 36 | CP03_2019_034 | 2010-2014 Estimates!!INDUSTRY!!Civilian employed population 16 years and over!!Construction | 5.7 | 0.1 | 5.6 | 0.1 | 1.1632 | False | True | False | [np.float64(0.0), inf] | False | +| 2019 | 26 | CP03_2019_036 | 2010-2014 Estimates!!INDUSTRY!!Civilian employed population 16 years and over!!Wholesale trade | 2.4 | 0.1 | 2.5 | 0.1 | 1.1632 | False | True | False | [np.float64(0.0), inf] | False | | 2019 | 48 | CP03_2019_036 | 2010-2014 Estimates!!INDUSTRY!!Civilian employed population 16 years and over!!Wholesale trade | 2.9 | 0.1 | 3 | 0.1 | 1.1632 | False | True | False | [np.float64(0.0), inf] | False | | 2019 | 48 | CP03_2019_039 | 2010-2014 Estimates!!INDUSTRY!!Civilian employed population 16 years and over!!Information | 1.7 | 0.1 | 1.8 | 0.1 | 1.1632 | False | True | False | [np.float64(0.0), inf] | False | | 2019 | 36 | CP03_2019_040 | 2010-2014 Estimates!!INDUSTRY!!Civilian employed population 16 years and over!!Finance and insurance, and real estate and rental and leasing | 8 | 0.1 | 8.1 | 0.1 | 1.1632 | False | True | False | [0.0, inf] | False | @@ -1462,9 +1465,6 @@ Failures / disagreements (first 25): | 2019 | 17 | CP03_2019_050 | 2010-2014 Estimates!!CLASS OF WORKER!!Civilian employed population 16 years and over!!Unpaid family workers | 0.2 | 0.1 | 0.1 | 0.1 | 1.1632 | False | True | False | [0.0, inf] | False | | 2023 | 06 | CP03_2023_005 | 2014-2018 Estimates!!EMPLOYMENT STATUS!!Population 16 years and over!!In labor force!!Civilian labor force!!Unemployed | 4.1 | 0.1 | 4.2 | 0.1 | 1.1632 | False | True | False | [np.float64(0.0), inf] | False | | 2023 | 36 | CP03_2023_005 | 2014-2018 Estimates!!EMPLOYMENT STATUS!!Population 16 years and over!!In labor force!!Civilian labor force!!Unemployed | 3.9 | 0.1 | 3.8 | 0.1 | 1.1632 | False | True | False | [np.float64(0.0), inf] | False | -| 2023 | 06 | CP03_2023_006 | 2014-2018 Estimates!!EMPLOYMENT STATUS!!Population 16 years and over!!In labor force!!Armed Forces | 0.5 | 0.1 | 0.4 | 0.1 | 1.1632 | False | True | False | [0.0, inf] | False | -| 2023 | 36 | CP03_2023_006 | 2014-2018 Estimates!!EMPLOYMENT STATUS!!Population 16 years and over!!In labor force!!Armed Forces | 0.2 | 0.1 | 0.1 | 0.1 | 1.1632 | False | True | False | [0.0, inf] | False | -| 2023 | 48 | CP03_2023_006 | 2014-2018 Estimates!!EMPLOYMENT STATUS!!Population 16 years and over!!In labor force!!Armed Forces | 0.5 | 0.1 | 0.4 | 0.1 | 1.1632 | False | True | False | [0.0, inf] | False | Sources: @@ -1474,10 +1474,10 @@ Sources: ## F8: PASSED -Sum of all counties (aggregate.aggregate_geographies, approximate MOE) vs the published state estimate for additive counts (B01003_001, B25001_001, B11001_001, B11001_002, B11001_003, B11001_004, B11001_005, B11001_006, B11001_007, B11001_008, B11001_009), states 06,36,48,17,56, releases 2019, 2023. +Sum of all counties (aggregate.aggregate_geographies, approximate MOE) vs the published state estimate for additive counts (B01003_001, B25001_001, B11001_001, B11001_002, B11001_003, B11001_004, B11001_005, B11001_006, B11001_007, B11001_008, B11001_009), states 06,36,48,17,56,26, releases 2019, 2023. - Tolerance: estimate exact; MOE ratio (approximate sum MOE / published state MOE) reported, not asserted -- Compared: 110; failed: 0; max abs error: 0; max rel error: -; time: 2.28 s +- Compared: 132; failed: 0; max abs error: 0; max rel error: -; time: 2.51 s - B01003_001 is controlled (published MOE '*****') at county and state level, so no MOE ratio is formed for it. The approximate sum MOE ignores covariance between counties (Handbook 2020 ch. 8), so the ratio scatters around 1; it is reported, not asserted, as the plan specifies.
Details @@ -1486,103 +1486,103 @@ Sum of all counties (aggregate.aggregate_geographies, approximate MOE) vs the pu { "moe_ratio_sum_approx_over_state_published": { "B25001_001": { - "n": 10, - "min": 0.9200765829347392, - "p5": 0.9702394078119005, - "p25": 1.376113237503102, + "n": 12, + "min": 0.8221877095170256, + "p5": 0.8760265898967681, + "p25": 1.2676735772392067, "median": 2.0520658986589773, - "p75": 2.497099099175031, - "p95": 3.2782897272583877, + "p75": 2.517283311110397, + "p95": 3.2695165690838586, "max": 3.3177689390437686 }, "B11001_001": { - "n": 10, + "n": 12, "min": 0.4904288084347447, - "p5": 0.49170432173210277, - "p25": 0.5180181980138052, + "p5": 0.49198776913151565, + "p25": 0.5206201731224245, "median": 0.5839980208520118, - "p75": 0.6883133246348668, - "p95": 1.0711316438856664, + "p75": 0.6275524245060846, + "p95": 1.0567856764168537, "max": 1.1356884974953243 }, "B11001_002": { - "n": 10, + "n": 12, "min": 0.533780401313172, - "p5": 0.5721635686135307, - "p25": 0.6292462134947351, + "p5": 0.5806931613469438, + "p25": 0.6294049227653183, "median": 0.7032767886762725, - "p75": 0.9109901178233026, - "p95": 1.0501421401593645, + "p75": 0.8641937561250256, + "p95": 1.0430654234605417, "max": 1.0819873653040675 }, "B11001_003": { - "n": 10, + "n": 12, "min": 0.46528240950424143, - "p5": 0.48385216156572847, - "p25": 0.5314522014696266, + "p5": 0.48797877313494786, + "p25": 0.5534631693048354, "median": 0.6217339720149735, - "p75": 0.7938373754606243, - "p95": 1.0150855579716258, + "p75": 0.7414718507922212, + "p95": 1.0107738701548448, "max": 1.0344881531471406 }, "B11001_004": { - "n": 10, + "n": 12, "min": 0.8596575885724728, - "p5": 0.8749576459107514, - "p25": 0.905182754190873, - "median": 0.9273229249160981, - "p75": 0.9628670519610404, - "p95": 1.0222514778347334, + "p5": 0.8783576586525912, + "p25": 0.8997092650435321, + "median": 0.9252882830790294, + "p75": 0.9576767116631979, + "p95": 1.0158690350395374, "max": 1.050972470413115 }, "B11001_005": { - "n": 10, + "n": 12, "min": 0.8105428352930195, - "p5": 0.8728236338560468, - "p25": 0.9714868053551331, - "median": 1.001570136611617, - "p75": 1.0432243849624605, - "p95": 1.0946760150295458, + "p5": 0.8614994918107594, + "p25": 0.9648362532205813, + "median": 0.9906652131583422, + "p75": 1.0421326463241056, + "p95": 1.0856472021508765, "max": 1.1353056729835578 }, "B11001_006": { - "n": 10, + "n": 12, "min": 0.8997271046769949, - "p5": 0.9005037475606822, - "p25": 0.9305906371650302, + "p5": 0.9006763348681682, + "p25": 0.9365119096497926, "median": 0.9542296283215247, - "p75": 0.9797943499486612, - "p95": 1.0295970755556445, + "p75": 0.9804248178274029, + "p95": 1.0252041252463, "max": 1.0493653519476953 }, "B11001_007": { - "n": 10, + "n": 12, "min": 0.8036290107590621, - "p5": 0.8734460689673557, - "p25": 0.966270164453756, - "median": 1.0152523560486246, - "p75": 1.0817042990472965, - "p95": 1.1055862828350502, + "p5": 0.866743298945988, + "p25": 0.9635052362954257, + "median": 0.9989317189619551, + "p75": 1.075198054771135, + "p95": 1.1045422107777148, "max": 1.11028460709306 }, "B11001_008": { - "n": 10, - "min": 0.894953290664733, - "p5": 0.9341284324260899, - "p25": 0.9912930176720269, - "median": 1.0352289844645162, - "p75": 1.0724867215601919, - "p95": 1.1475252611230733, + "n": 12, + "min": 0.8165772484883481, + "p5": 0.8596840716853598, + "p25": 0.9712384920561632, + "median": 1.0219268811229107, + "p75": 1.0544642643338071, + "p95": 1.1398033264210572, "max": 1.182273967282146 }, "B11001_009": { - "n": 10, - "min": 0.9022493372059337, - "p5": 0.9195040596699223, - "p25": 0.9615270005344628, - "median": 1.0165662850562542, - "p75": 1.0331031707050693, - "p95": 1.07798386716504, + "n": 12, + "min": 0.8566024005642212, + "p5": 0.8817082157171631, + "p25": 0.9505326361537468, + "median": 1.007390193064066, + "p75": 1.0282877042562046, + "p95": 1.0764420369011085, "max": 1.0849221033527314 } } @@ -1600,7 +1600,7 @@ Sources: Round 6 national continuity check (census.continuity) for every multi-vintage concept of the F1 extraction (B01001, 2019, 2023). - Tolerance: as Round 6: no concept flagged as both significantly (z-test, 90%) and implausibly (>25% relative or >10 points) different at the national level -- Compared: 48; failed: 0; max abs error: -; max rel error: -; time: 3.03 s +- Compared: 48; failed: 0; max abs error: -; max rel error: -; time: 2.44 s - A pass shows no national value jumps implausibly between vintages; it does not prove the codes measure the same thing (census/continuity.py docstring). - Informational (not asserted): the Round 6 check flags concepts of for 2019-2023 although every concept keeps its code and label. The flagged series show real change over four years (income brackets in nominal dollars, poverty and insurance by age as the population ages). census/continuity.py's thresholds (25% relative, 10 points) are documented for adjacent, overlapping releases but are applied to any requested pair, so such extractions block in the default concordance mode. A false positive of the Round 6 check, reported for the module owner; it is not a calculation error. @@ -1649,10 +1649,10 @@ Sources: ## F10: PASSED -ACS 2019-2023 table-based Summary File cells (bulk delivery on www2.census.gov) vs API cells for tables B01003, B25003, B17001, B19001, the nation and states 06,36,48,17,56. +ACS 2019-2023 table-based Summary File cells (bulk delivery on www2.census.gov) vs API cells for tables B01003, B25003, B17001, B19001, the nation and states 06,36,48,17,56,26. - Tolerance: exact (numeric equality of every estimate and MOE cell) -- Compared: 960; failed: 0; max abs error: 0; max rel error: -; time: 9.12 s +- Compared: 1120; failed: 0; max abs error: 0; max rel error: -; time: 9.25 s - Only the first bytes of each national per-table file are requested (HTTP Range); its rows are sorted by GEO_ID, so the nation and state rows precede the county, tract and block-group rows. The table-based Summary File exists from the 2021 releases on (summary_file/2021/table-based-SF/); summary_file/2019/ holds only data/ (the sequence-based files, one archive per state holding every table), documentation/ and prototype/ (checked 2026-11-02), so 2019 is not compared here.
Details @@ -1661,27 +1661,27 @@ ACS 2019-2023 table-based Summary File cells (bulk delivery on www2.census.gov) { "tables": { "B01003": { - "cells": 12, + "cells": 14, "mismatches": 0, - "rows": 6, + "rows": 7, "byte_range": "bytes=0-262143 of 18311997" }, "B25003": { - "cells": 36, + "cells": 42, "mismatches": 0, - "rows": 6, + "rows": 7, "byte_range": "bytes=0-262143 of 26878591" }, "B17001": { - "cells": 708, + "cells": 826, "mismatches": 0, - "rows": 6, + "rows": 7, "byte_range": "bytes=0-262143 of 118402375" }, "B19001": { - "cells": 204, + "cells": 238, "mismatches": 0, - "rows": 6, + "rows": 7, "byte_range": "bytes=0-262143 of 68859942" } } diff --git a/docs/case_study_mi_ny.md b/docs/case_study_mi_ny.md index 9a32e45..f9101a2 100644 --- a/docs/case_study_mi_ny.md +++ b/docs/case_study_mi_ny.md @@ -10,6 +10,16 @@ frozen inputs, this one downloads real data from the Census Data API, the Census replicate (VRE) files and Census cartographic boundary files. Results are real ACS estimates and can change if the Census Bureau revises the release. +The study runs two ways (PLAN_ROUND_14 A4): + +1. **Confirmatory** (section 1): one pre-registered manifest, + [`examples/manifests/mi_ny_income_education.json`](../examples/manifests/mi_ny_income_education.json), + run with `run_manifest`. It fixes the states, variables, derivation, price adjustment, + model, hypothesis and correction before any data are pulled. This is the result to report. +2. **Exploratory, as sensitivity analysis** (sections 2-7): the same analysis built step by + step (Round 13), plus what the manifest does not cover: maps, a nominal-dollar model and + replicate SEs for the covariate. + ## Purpose and research question How does county median household income compare across Michigan and New York counties once @@ -55,7 +65,89 @@ Run the commands from the repository root. All outputs go under `case_study_outp (gitignored; never committed); paths are written out in full so every command can be pasted as is into a POSIX shell (bash, zsh). -## 1. Extraction with MOEs and provenance +## 1. Confirmatory run: the pre-registered manifest + +```bash +python -m census tool create_manifest --arg manifest_path=examples/manifests/mi_ny_income_education.json --json +python -m census tool run_manifest --arg manifest_path=examples/manifests/mi_ny_income_education.json --arg output_dir=case_study_output/confirmatory --json +``` + +`create_manifest` validates the manifest offline. Expected: `"valid": true` and +`"hash": "d8905f57625e1d1862a63ca0f6deeb364a7643385fc4a700b6691e855e3bfcb0"`. A different +hash means a different pre-registration. The MCP tool `run_manifest` takes the same +arguments and gives the same result. + +What the manifest fixes: + +| Manifest entry | Value | +| :--- | :--- | +| `geography` | `{"level": "county", "states": ["26", "36"]}`: each table is extracted per state and the two states are stacked (145 counties) | +| `variables` | `median_hh_income` = `2024:B19013_001`; `ed_total` = `2024:B15002_001`, and the eight bachelor's-or-higher cells `B15002_015`..`_018` and `_032`..`_035` | +| `derivations` | `pct_bachelors`: proportion of the eight cells over `ed_total`, as a percent | +| `adjustments` | `{"type": "rpp", "variables": ["median_hh_income"], "rpp_year": 2024, "areas": "county_metro"}` | +| `exclusions` | CV screen at 30% on `median_hh_income` and `pct_bachelors`, flag only | +| `analyses` | m1: `fit_replicate`, OLS `median_hh_income ~ pct_bachelors + C(GEOID.str[:2])`, `population` target, 90% intervals | +| `hypotheses` | H1: coefficient of `pct_bachelors` > 0 (one-sided); Holm correction (one hypothesis) | + +The `adjustments` step uses the same code as the `place_adjust_frame` tool (section 3): +each county gets its MSA's 2024 RPP, or the state's nonmetropolitan-portion RPP outside a +metropolitan CBSA, and the estimate and SE are divided by RPP/100 (replicates too, when +replicates are supplied). Multi-state manifests get no automatic state effect: the formula +names the state term `C(GEOID.str[:2])` explicitly. `rpp_year` is required (when it is +omitted, `create_manifest` fills in the latest year in the RPP table before hashing, so the +hash fixes it); a year other than the release end year is recorded as a note, not a +deviation. Exclusions run before the adjustment (the CV screen flags the same counties +either way, since the RPP factor leaves CVs unchanged). + +Expected in the JSON output: `"analysis_status": "confirmatory"`, `"deviations": []`, +`data_source` `{"synthetic": false, ...}`, and for H1: + +| Term | Estimate | se_model | 90% CI (from se_model) | One-sided p (Holm) | +| :--- | ---: | ---: | :--- | ---: | +| pct_bachelors | 811.00 | 77.03 | [684.29, 937.71] | 3.2e-26 | + +`confirmatory/bundle/results/m1.json` also gives New York vs Michigan 1,643.83 ([-965.49, +4,253.15]) and the intercept 51,698.07. `confirmatory/bundle/results/run.json` records +`n_units` 145, no county flagged by the CV screen, and under `adjustments`: the RPP year +(2024, `rpp_year_source` `manifest`), the 31 RPP areas used (29 MSAs and the two +nonmetropolitan portions) with their RPP values and county counts, the CBSA boundary file, +and the SHA-256 of the bundled RPP table +(`3e726a9d274636a2cce1301bac17000b66c71cb37090b9ac001c5c1082fd2dd9`; a table from +`CENSUS_PRICE_PARITY_DIR` would be recorded with its path and hash as `custom`). +`bundle.json` carries the same RPP provenance and `analysis_status: confirmatory`, and +`methods.md` describes the adjustment. + +**Comparison with the exploratory result (section 4).** The confirmatory coefficient, +`se_model` and interval are identical to the Round 13 exploratory result (811.00, 90% CI +[684.29, 937.71]), and so are the state and intercept terms. They have to be: both routes use +the same API responses, the same county-to-RPP assignment, the same derived share and the +same OLS fit, and with the `population` target the interval uses the model SE. There is one +difference. The confirmatory run has **no `se_sampling`**. No replicates are supplied, and +they could not be complete: `B19013` has no Census replicates, and a confirmatory run uses +`replicate_policy="strict"`, so supplying only the `B15002` replicates would stop m1 with a +blocking `replicates_incomplete` deviation. The covariate-only `se_sampling` (11.98) is +reported by the exploratory route below as a sensitivity result. It does not enter the +stated interval in either route. In the manifest frame `pct_bachelors_se` is the ACS +approximation rather than the exact replicate SE; this affects only the CV screen, which +flags no county in either route. + +**What "confirmatory" means here.** The manifest was written after the Round 13 +exploratory analysis of the same data, so this run confirms that the analysis reproduces +exactly under a fixed protocol. It is not an independent test of the hypothesis. An +independent confirmation would apply the same manifest, with only `states` or `releases` +changed, to data not yet analysed, for example other states or the next ACS release. + +The offline test `tests/test_manifest_multistate.py` runs these two commands on frozen +copies of the four API responses (trimmed to the manifest's variables) with network access +refused, and checks the numbers above for the CLI and MCP routes. The opt-in live test +(`CENSUS_LIVE_TESTS=1`) runs them against the Census API and the boundary files. + +## Sensitivity analysis: the exploratory route + +Sections 2-7 build the same model step by step with separate tools. Results from this +route are labelled `exploratory`. + +## 2. Extraction with MOEs and provenance ```bash python -m census extract --table B19013 --geo county --state 26 --years 2024 --variables B19013_001 --reliability --codebook --slug-style code --out "case_study_output/extract/26/B19013" --json @@ -69,9 +161,9 @@ with status `stable` in `*_concordance_issues.json` (`status_counts`). The progr `concordance policy: fail` on stderr is the policy (stop on blocking issues), not a result. Each folder holds the panel (`*_county_panel_2024.csv`: estimate, MOE, SE = MOE / 1.645, CV, reliability), the codebook, and `*_extraction_metadata.json` (endpoint `https://api.census.gov/data/{year}/acs/acs5`, variables, concept IDs, metadata vintage -hashes, package version and git commit), which `python -m census replay` can re-run (step 6). +hashes, package version and git commit), which `python -m census replay` can re-run (step 7). -## 2. Price adjustment and maps, one state at a time +## 3. Price adjustment and maps, one state at a time ```bash python -m census tool build_analysis_frame --arg input_path=case_study_output/extract/26/B19013/B19013_county_panel_2024.csv --arg 'variables={"median_hh_income": "B19013_001"}' --arg year=2024 --arg output_path=case_study_output/frames/income_26.parquet --json @@ -130,7 +222,7 @@ Small copies of the two choropleth PNGs are kept in the repository: [`docs/case_study/choropleth_26.png`](case_study/choropleth_26.png) and [`docs/case_study/choropleth_36.png`](case_study/choropleth_36.png). -## 3. Regression with replicate standard errors +## 4. Regression with replicate standard errors ```bash python -m census tool build_analysis_frame --arg 'input_path=["case_study_output/extract/26/B19013/B19013_county_panel_2024.csv", "case_study_output/extract/36/B19013/B19013_county_panel_2024.csv", "case_study_output/extract/26/B15002/B15002_county_panel_2024.csv", "case_study_output/extract/36/B15002/B15002_county_panel_2024.csv"]' --arg 'variables={"median_hh_income": "B19013_001"}' --arg 'derive=[{"name": "pct_bachelors", "op": "proportion", "num": ["B15002_015E", "B15002_016E", "B15002_017E", "B15002_018E", "B15002_032E", "B15002_033E", "B15002_034E", "B15002_035E"], "den": "B15002_001E", "as_percent": true}]' --arg use_replicates=true --arg table_code=B15002 --arg year=2024 --arg output_path=case_study_output/frames/model_frame.parquet --json @@ -197,7 +289,7 @@ association between areas, not a statement about individuals and not a causal ef education on income. Spatial correlation among counties is not modelled, so the model SE may also be too small (see Limitations). -## 4. Results bundle and run provenance +## 5. Results bundle and run provenance ```bash python -m census tool make_results_bundle --arg output_dir=case_study_output/bundle --arg 'results={"regression": "case_study_output/results/regression.json", "regression_nominal": "case_study_output/results/regression_nominal.json", "rpp_model": "case_study_output/results/rpp_model.json", "rpp_26": "case_study_output/results/rpp_26.json", "rpp_36": "case_study_output/results/rpp_36.json", "map_26": "case_study_output/results/map_26.json", "map_36": "case_study_output/results/map_36.json", "cv_map_26": "case_study_output/results/cv_map_26.json", "cv_map_36": "case_study_output/results/cv_map_36.json"}' --arg 'figure_paths=["case_study_output/maps/26/choropleth.pdf", "case_study_output/maps/26/choropleth.png", "case_study_output/maps/26/uncertainty.pdf", "case_study_output/maps/36/choropleth.pdf", "case_study_output/maps/36/choropleth.png", "case_study_output/maps/36/uncertainty.pdf", "case_study_output/figures/coefficients.pdf"]' --arg 'inputs=["case_study_output/extract/26/B19013/B19013_extraction_metadata.json", "case_study_output/extract/36/B19013/B19013_extraction_metadata.json", "case_study_output/extract/26/B15002/B15002_extraction_metadata.json", "case_study_output/extract/36/B15002/B15002_extraction_metadata.json", "case_study_output/frames/model_frame_rpp.parquet", "case_study_output/frames/model_frame_replicates_rpp.parquet"]' --arg "title=Price-adjusted county median household income, Michigan and New York, ACS 2020-2024" --arg 'notes=["Outcome B19013 has no Census variance replicates; se_sampling covers the covariate only.", "RPPs are BEA estimates with their own error, not included in any SE, MOE or interval here.", "ACS 5-year dollars are 2024 dollars; the 2024 RPP is applied to the whole 2020-2024 period."]' --json @@ -222,10 +314,10 @@ no source give `unknown`; synthetic and real inputs together give `mixed` with a SOURCES` warning. A saved result with `"success": false` is refused (a failed step is not a result) unless `allow_failed_results=true`, which lists it under "Failed steps" in `methods.md`. -This analysis is **exploratory**: it was not pre-registered with `run_manifest`, whose -manifests take one state per run and have no place-adjustment step (see "Toolkit gaps"). +This route is **exploratory**: its steps were not pre-registered. Section 1 is the +pre-registered, confirmatory run of the same analysis. -## 5. Spot check against the live API +## 6. Spot check against the live API For five counties per state, the extracted `B19013_001` estimate and MOE were compared with the values the Census Data API returns directly @@ -256,7 +348,7 @@ CENSUS_LIVE_TESTS=1 python -m pytest -q tests/test_case_study_live.py No values were taken from data.census.gov. -## 6. Re-running +## 7. Re-running ```bash python -m census replay "case_study_output/extract/26/B19013/B19013_extraction_metadata.json" --out "case_study_output/replay_26_B19013" --json @@ -281,7 +373,7 @@ fields: timestamps (`generated_at`), absolute paths, the git commit, and figure counties of a state share the nonmetropolitan-portion RPP; within-area price differences (e.g. Manhattan vs the rest of the New York MSA) are not captured. - **Outcome replicates.** `B19013` has no published variance replicates, so the replicate SE - covers the covariate only (see section 3). The model-SE interval is unaffected. + covers the covariate only (see section 4). The model-SE interval is unaffected. - **Measurement error in the covariate.** Replicate refits capture the coefficient's sampling variability; they do not correct attenuation from a noisy covariate. With covariate CVs of about 3.5% (median) the bias is likely small; `fit_model method=regression_calibration` @@ -289,11 +381,13 @@ fields: timestamps (`generated_at`), absolute paths, the git commit, and figure - **Pooling two states.** The state indicator removes a level difference only; the slope is assumed common to both states. Counties are not independent draws (spatial correlation is not modelled; `spatial_autocorrelation` is the check). -- **Exploratory, not confirmatory.** Not pre-registered (see section 4). +- **Confirmatory, not independent.** The manifest (section 1) was registered after the + exploratory analysis of the same data. It fixes the protocol but does not replicate the + finding on new data (see section 1). ## Toolkit gaps found while writing this workflow -Closed in this change (with offline tests): +Closed in Round 13 (with offline tests): - `build_analysis_frame` took one input file and one replicate table; it now takes a list of files (other tables joined on GEOID/Year, other states stacked) and a list of tables. @@ -306,9 +400,14 @@ Closed in this change (with offline tests): the right edge for some area shapes (New York counties): legend titles are now wrapped and axes are shrunk until the legend fits the fixed journal width. +Closed in Round 14 (PLAN_ROUND_14 A1-A4, offline tests in `tests/test_manifest_multistate.py`): + +- `run_manifest` took a single state and had no place-adjustment step. Manifests now take + `geography.states` (extracted per state and stacked) and an `adjustments` step + (`{"type": "rpp", ...}`) that reuses the `place_adjust_frame` code and records RPP + provenance. Derivations are evaluated on supplied replicates. Section 1 is the result. + Open (documented, not closed): -- `run_manifest` geography takes a single state and has no place-adjustment step, so this - analysis cannot be pre-registered as one manifest. - Census publishes no replicates for `B19013` (and other published medians not on the VRE list); exact replicate SEs for a model with such an outcome are not available. diff --git a/docs/recipes/preregistered.md b/docs/recipes/preregistered.md index 527a397..bc43811 100644 --- a/docs/recipes/preregistered.md +++ b/docs/recipes/preregistered.md @@ -3,9 +3,13 @@ Goal: a confirmatory analysis fixed before the data are pulled. 1. Write the manifest (JSON or YAML): research question, table, variables (codes or `YEAR:CODE`), geography, releases, derivations, tests or model formula, multiple-comparison correction, and reliability exclusions. + - Several states: `"geography": {"level": "county", "states": ["26", "36"]}` (instead of `"state"`; no `codes`; exactly 2-digit FIPS strings, `"06"` not `"6"`; not for `zcta`, whose GEOIDs are not state-prefixed). States are stacked; add a state term to the formula yourself, e.g. `C(GEOID.str[:2])`. + - Price adjustment: `"adjustments": [{"type": "rpp", "variables": ["income"], "rpp_year": 2024}]` divides dollar variables (and their SEs and replicates) by the BEA Regional Price Parity of each county's metro area or the state's nonmetro portion. `rpp_year` is required so the hash fixes it; when it is omitted, `create_manifest` fills in the latest RPP year, says so in its result, and writes the completed manifest to a new `.completed.json` (the input file is never overwritten); commit and run that file. Exclusions run before the adjustment, so a pre-registered `geoid_exclude` can drop a unit without an RPP area; any remaining unit without one blocks the run (`adjustment_failed`). One release only. 2. Validate it with `create_manifest` (full profile); it checks every variable resolves and records the manifest's SHA-256. Commit the manifest to the repository before running. 3. Run it with `run_manifest` and the manifest path. It runs `plan_extraction`, the download, derivations and models exactly as written. 4. Do not change the manifest after seeing results. Anything you run outside it on the same data is exploratory; label it so. 5. Report the manifest hash with every result, plus estimates with 90% MOEs or model SEs with their uncertainty sources. +Worked example on real data (two states, RPP-adjusted): docs/case_study_mi_ny.md section 1 runs examples/manifests/mi_ny_income_education.json. + Worked example (synthetic, offline, executable): docs/reference_workflow.md runs examples/manifests/income_population_tracts.json end to end, including a rerun check and a blocked incomplete-replicates case. diff --git a/docs/reference_workflow.md b/docs/reference_workflow.md index 0d14ba0..1778298 100644 --- a/docs/reference_workflow.md +++ b/docs/reference_workflow.md @@ -107,6 +107,8 @@ label every later result from that cache synthetic, real data included: set `CENSUS_CACHE_PATH` as in step 0 (or pass `--allow-default-cache` deliberately). The marker covers the whole cache and is removed only by `python -m census cache clear`, which deletes every response and the marker together (its output reports `"synthetic_marker_cleared"`). +The MCP tool `cache_import` (full profile) follows the same rules: it validates every file +first, and a synthetic import into the default cache needs `allow_default_cache=true`. Expected: `"imported": 2`, `"synthetic": true`, and in `sources` the tables `B01003`, `B19013` with `"sha256": "0f5725d460904f5c1ee6e74dfa5fda2f1ef3dd5a8d910515d1495dde6aa44a94"` @@ -284,7 +286,7 @@ re-execution byte-for-byte input-identical. | Step | Python | CLI | MCP | | :--- | :--- | :--- | :--- | | Validate manifest and hash | `census.manifest.create_manifest` | `tool create_manifest` | `create_manifest` | -| Freeze source into cache | `census.cache.import_frozen` | `cache import ` | not available (use the CLI) | +| Freeze source into cache | `census.cache.import_frozen` | `cache import ` (or `tool cache_import`) | `cache_import` (full profile; `source`, `allow_default_cache`) | | Plan | `census.pipeline.plan_extraction` | `plan` | `plan_extraction` | | Execute manifest | `census.manifest.run_manifest` (`replicates=read_replicates(path)`) | `tool run_manifest` (`replicates_path`) | `run_manifest` (`replicates_path`) | | Interval meaning | `census.results.interval_description` | `interval` in `results/m1.json` | same | diff --git a/examples/manifests/mi_ny_income_education.json b/examples/manifests/mi_ny_income_education.json new file mode 100644 index 0000000..7a4f770 --- /dev/null +++ b/examples/manifests/mi_ny_income_education.json @@ -0,0 +1,42 @@ +{ + "schema_version": 1, + "title": "Price-adjusted county median household income and bachelor's degree attainment, Michigan and New York, ACS 2020-2024", + "authors": ["human:example-researcher", "agent:claude"], + "created": "2026-11-08T00:00:00Z", + "releases": [2024], + "geography": {"level": "county", "states": ["26", "36"]}, + "variables": [ + {"name": "median_hh_income", "ref": "2024:B19013_001"}, + {"name": "ed_total", "ref": "2024:B15002_001"}, + {"name": "ba_male", "ref": "2024:B15002_015"}, + {"name": "ma_male", "ref": "2024:B15002_016"}, + {"name": "prof_male", "ref": "2024:B15002_017"}, + {"name": "phd_male", "ref": "2024:B15002_018"}, + {"name": "ba_female", "ref": "2024:B15002_032"}, + {"name": "ma_female", "ref": "2024:B15002_033"}, + {"name": "prof_female", "ref": "2024:B15002_034"}, + {"name": "phd_female", "ref": "2024:B15002_035"} + ], + "derivations": [ + {"name": "pct_bachelors", "op": "proportion", "as_percent": true, + "num": ["ba_male", "ma_male", "prof_male", "phd_male", "ba_female", "ma_female", "prof_female", "phd_female"], + "den": "ed_total"} + ], + "adjustments": [ + {"type": "rpp", "variables": ["median_hh_income"], "rpp_year": 2024, "areas": "county_metro"} + ], + "exclusions": [ + {"rule": "cv_screen", "threshold": 30, "variables": ["median_hh_income", "pct_bachelors"], "action": "flag"} + ], + "missing": "complete_case", + "confidence_level": 0.90, + "correction": {"method": "holm", "family": "all"}, + "analyses": [ + {"id": "m1", "method": "fit_replicate", "formula": "median_hh_income ~ pct_bachelors + C(GEOID.str[:2])", + "args": {"model": "ols", "target": "population"}} + ], + "hypotheses": [ + {"id": "H1", "statement": "Across Michigan and New York counties, a higher share of adults 25+ with a bachelor's degree or higher is associated with higher RPP-adjusted median household income, holding the state level fixed.", + "analysis": "m1", "statistic": "coefficient", "term": "pct_bachelors", "direction": "greater"} + ] +} diff --git a/mcp_server.py b/mcp_server.py index 035ea99..7cdee75 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -1047,6 +1047,7 @@ def generate_codebook( workflows.record_rule_review, workflows.run_validation, workflows.download_table, + workflows.cache_import, workflows_inference.build_analysis_frame, workflows_inference.fit_model, workflows_inference.small_area_estimates, @@ -1088,6 +1089,7 @@ def generate_codebook( record_rule_review = workflows.record_rule_review run_validation = workflows.run_validation download_table = workflows.download_table +cache_import = workflows.cache_import build_analysis_frame = workflows_inference.build_analysis_frame fit_model = workflows_inference.fit_model small_area_estimates = workflows_inference.small_area_estimates diff --git a/scripts/pums_memory_benchmark.py b/scripts/pums_memory_benchmark.py new file mode 100644 index 0000000..ae6b104 --- /dev/null +++ b/scripts/pums_memory_benchmark.py @@ -0,0 +1,590 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""PUMS peak-memory benchmark (PLAN_ROUND_14 workstream C; PLAN_ROUND_11 R2/F3). + +Bounded synthetic data only: no download. The script writes gzip CSVs shaped +like a 5-year state person file (RT, SERIALNO, DIVISION, SPORDER, PUMA, +REGION, STATE, ADJINC, PWGTP, AGEP, SEX, SCHL, WAGP, PINCP, optional filler +columns, PWGTP1-PWGTP80) and measures each phase in a fresh subprocess: + + baseline interpreter plus numpy, pandas and census.pums imported + read census.pums.read_pums_csv(columns=AGEP, PINCP, WAGP) at each chunk size + weights census.pums._weight_matrix on the frame read (81 float64 columns) + tabulate census.pums.tabulate: median PINCP (AGEP >= 15), mean WAGP + (AGEP >= 16), mean WAGP by PUMA; all with 80-replicate SEs + +Peak RSS is the kernel's high-water mark (VmHWM; resource.getrusage +ru_maxrss reports the same on Linux). In the weights and tabulate +subprocesses the file is read first and the high-water mark is then reset +(writing 5 to /proc/self/clear_refs, Linux 4.0+), so their peak covers only +that phase on top of the frame already resident. Where the reset is not +available the peak includes the read and the report says so. + +Chunk sizes: every tabulated estimate and SE, and a hash of the frame read, +must be identical across chunk sizes; the report records the comparison and +the command exits 1 when they differ. + +Usage: + python scripts/pums_memory_benchmark.py # 100k, 500k, 1.9M rows -> docs/audits/pums_memory.md + python scripts/pums_memory_benchmark.py --workdir DIR # generated CSVs in DIR (deleted afterwards unless --keep) + python scripts/pums_memory_benchmark.py --small # 2,000 rows into a temporary directory (the unit test) + +The 1.9M-row file (California's 5-year person file has about 1.9 million +records) is about 0.5 GB gzip-compressed; the full run takes about 10 minutes +and peaks at a few GB. +""" + +import argparse +import gc +import gzip +import json +import os +import platform +import shutil +import subprocess +import sys +import tempfile +import time +from datetime import datetime, timezone + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.dirname(HERE) +OUT_MD = os.path.join(ROOT, "docs", "audits", "pums_memory.md") + +SIZES = (100_000, 500_000, 1_900_000) +CHUNKSIZES = (50_000, 200_000, 1_000_000) # 200,000 is read_pums_csv's default +SMALL_SIZES = (2_000,) +SMALL_CHUNKSIZES = (500, 2_000) +ANALYSIS_COLUMNS = ["AGEP", "PINCP", "WAGP"] +ROWS_PER_PUMA = 6_800 # California: about 1.9 million person records in 281 PUMAs +BLOCK_ROWS = 100_000 # rows generated and written at a time (bounds the generator's memory) +SEED = 20261108 +GIB = 1024 ** 3 +MIB = 1024 ** 2 +OS_RESERVE_GIB = 2.0 # left for the OS and other processes when computing what fits + + +# --------------------------------------------------------------------------- synthetic files + +def generate(path, n_rows, extra_columns=0, seed=SEED): + """Write a synthetic PUMS-shaped person CSV (gzip) of ``n_rows`` records. + + Returns {path, rows, columns, gzip_bytes, csv_bytes}. Values are seeded and + plausible (zero-padded PUMA and STATE, 13-character SERIALNO, integer + weights, blank PINCP below age 15 and blank WAGP below age 16), so the + reader does the same work as on a real file. Recent files call the state + column STATE (older ones ST); census.pums keeps either as text. + """ + import numpy as np + import pandas as pd + + rng = np.random.default_rng(seed) + n_puma = max(1, round(n_rows / ROWS_PER_PUMA)) + csv_bytes, written, serial = 0, 0, 0 + with gzip.open(path, "wt", encoding="ascii", newline="", compresslevel=1) as f: + while written < n_rows: + n = min(BLOCK_ROWS, n_rows - written) + sporder = rng.integers(1, 5, n) + serial_ids = serial + np.cumsum(sporder == 1) + serial = int(serial_ids[-1]) + 1 + agep = rng.integers(0, 96, n) + pwgtp = rng.integers(1, 250, n) + pincp = np.round(rng.lognormal(10.3, 1.1, n)).astype(np.int64) - rng.integers(0, 2, n) * 5_000 + wagp = np.where(rng.random(n) < 0.35, 0, np.round(rng.lognormal(10.4, 0.9, n)).astype(np.int64)) + cols = { + "RT": "P", + "SERIALNO": [f"2022HU{s:07d}" for s in serial_ids], + "DIVISION": "9", + "SPORDER": sporder, + "PUMA": [f"{p:05d}" for p in rng.integers(1, n_puma + 1, n) * 100 + 1], + "REGION": "4", + "STATE": "06", + "ADJINC": "1042311", + "PWGTP": pwgtp, + "AGEP": agep, + "SEX": rng.integers(1, 3, n), + "SCHL": pd.array(np.where(agep >= 3, rng.integers(1, 25, n), 0), dtype="Int64"), + "WAGP": pd.array(wagp, dtype="Int64"), + "PINCP": pd.array(pincp, dtype="Int64"), + } + cols["SCHL"][agep < 3] = pd.NA + cols["WAGP"][agep < 16] = pd.NA + cols["PINCP"][agep < 15] = pd.NA + for j in range(extra_columns): + cols[f"FILL{j + 1:03d}"] = rng.integers(0, 10, n) + factors = rng.choice([0.29, 0.5, 1.0, 1.5, 1.71], size=(n, 80)) + reps = np.maximum(np.round(pwgtp[:, None] * factors), 0).astype(np.int64) + for r in range(80): + cols[f"PWGTP{r + 1}"] = reps[:, r] + text = pd.DataFrame(cols).to_csv(index=False, header=written == 0, lineterminator="\n") + csv_bytes += len(text) + f.write(text) + written += n + n_columns = len(cols) + return {"path": path, "rows": n_rows, "columns": n_columns, "gzip_bytes": os.path.getsize(path), + "csv_bytes": csv_bytes, "pumas": n_puma} + + +# --------------------------------------------------------------------------- measurement (worker side) + +def _status_kib(field): + try: + with open("/proc/self/status", encoding="ascii") as f: + for line in f: + if line.startswith(field + ":"): + return int(line.split()[1]) + except OSError: + pass + return None + + +def _windows_memory(): + """(working set, peak working set) in bytes on Windows (no resource module there).""" + import ctypes + from ctypes import wintypes + + class Counters(ctypes.Structure): + _fields_ = [("cb", wintypes.DWORD), ("PageFaultCount", wintypes.DWORD)] + [ + (name, ctypes.c_size_t) for name in ( + "PeakWorkingSetSize", "WorkingSetSize", "QuotaPeakPagedPoolUsage", "QuotaPagedPoolUsage", + "QuotaPeakNonPagedPoolUsage", "QuotaNonPagedPoolUsage", "PagefileUsage", "PeakPagefileUsage")] + + counters = Counters() + counters.cb = ctypes.sizeof(Counters) + kernel32 = ctypes.WinDLL("kernel32") + kernel32.GetCurrentProcess.restype = wintypes.HANDLE + get_info = getattr(kernel32, "K32GetProcessMemoryInfo") + get_info.argtypes = [wintypes.HANDLE, ctypes.POINTER(Counters), wintypes.DWORD] + if not get_info(kernel32.GetCurrentProcess(), ctypes.byref(counters), counters.cb): + raise OSError("GetProcessMemoryInfo failed") + return int(counters.WorkingSetSize), int(counters.PeakWorkingSetSize) + + +def _rss(): + """Current RSS in bytes (None where neither /proc nor the Windows API is available).""" + kib = _status_kib("VmRSS") + if kib is not None: + return kib * 1024 + return _windows_memory()[0] if sys.platform == "win32" else None + + +def _peak(): + """Peak RSS in bytes: VmHWM, else ru_maxrss (KiB on Linux, bytes on macOS), else + the Windows peak working set.""" + kib = _status_kib("VmHWM") + if kib is not None: + return kib * 1024 + if sys.platform == "win32": + return _windows_memory()[1] + import resource + + maxrss = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss + return maxrss if sys.platform == "darwin" else maxrss * 1024 + + +def _reset_peak(): + """Reset the high-water mark to the current RSS; False where unsupported.""" + try: + with open("/proc/self/clear_refs", "w", encoding="ascii") as f: + f.write("5") + return True + except OSError: + return False + + +def _floats(values): + """Exact float values as JSON-safe strings (repr round-trips; NaN kept).""" + return [repr(float(v)) for v in values] + + +def run_phase(phase, path, chunksize): + """Measure one phase in this (fresh) process; returns a JSON-able dict.""" + import pandas as pd + + from census import pums + + out = {"phase": phase, "chunksize": chunksize, "rss_after_imports": _rss(), "peak_reset": None} + if phase == "baseline": + out["peak"] = _peak() + return out + + def read(): + return pums.read_pums_csv(path, "person", ANALYSIS_COLUMNS, chunksize=chunksize) + + if phase == "read": + out["peak_reset"] = _reset_peak() + start, t0 = _rss(), time.perf_counter() + df = read() + out.update(seconds=time.perf_counter() - t0, peak=_peak(), rss_start=start, rss_end=_rss(), + rows=len(df), columns_read=len(df.columns), + frame_bytes=int(df.memory_usage(deep=True).sum()), + frame_hash=str(int(pd.util.hash_pandas_object(df, index=False).sum())), + dtypes={c: str(t) for c, t in df.dtypes.items()}) + return out + + df = read() + gc.collect() + out["peak_reset"] = _reset_peak() + start, t0 = _rss(), time.perf_counter() + out["rss_start"] = start + if phase == "weights": + _, W = pums._weight_matrix(df, "person") + out.update(seconds=time.perf_counter() - t0, peak=_peak(), matrix_bytes=int(W.nbytes), rows=len(df)) + return out + if phase != "tabulate": + raise ValueError(f"unknown phase {phase!r}") + stats = [("median_PINCP", dict(statistic="median", var="PINCP", where="AGEP >= 15")), + ("mean_WAGP", dict(statistic="mean", var="WAGP", where="AGEP >= 16")), + ("mean_WAGP_by_PUMA", dict(statistic="mean", var="WAGP", by="PUMA", where="AGEP >= 16"))] + results, steps = {}, {} + for name, kw in stats: + gc.collect() + _reset_peak() + s0, t1 = _rss(), time.perf_counter() + t = pums.tabulate(df, **kw) + steps[name] = {"seconds": time.perf_counter() - t1, "peak": _peak(), "rss_start": s0} + results[name] = {"estimate": _floats(t["estimate"]), "se": _floats(t["se"]), + "n_unweighted": [int(v) for v in t["n_unweighted"]]} + out.update(seconds=time.perf_counter() - t0, peak=max(s["peak"] for s in steps.values()), steps=steps, + results=results, rows=len(df), + first=dict(estimate=results["median_PINCP"]["estimate"][0], se=results["median_PINCP"]["se"][0])) + return out + + +# --------------------------------------------------------------------------- orchestration + +def _subprocess(phase, path, chunksize): + env = dict(os.environ) + env["PYTHONPATH"] = ROOT + (os.pathsep + env["PYTHONPATH"] if env.get("PYTHONPATH") else "") + cmd = [sys.executable, os.path.abspath(__file__), "--worker", phase, "--file", path or "", + "--chunksize", str(chunksize or 0)] + proc = subprocess.run(cmd, capture_output=True, text=True, env=env, cwd=ROOT) + if proc.returncode != 0: + raise RuntimeError(f"{phase} worker failed:\n{proc.stderr}") + return json.loads(proc.stdout.strip().splitlines()[-1]) + + +def environment(): + import numpy as np + import pandas as pd + + total = None + try: + with open("/proc/meminfo", encoding="ascii") as f: + total = int(next(line for line in f if line.startswith("MemTotal:")).split()[1]) * 1024 + except (OSError, StopIteration): + pass + return {"date": datetime.now(timezone.utc).strftime("%Y-%m-%d"), "platform": platform.platform(), + "machine": platform.machine(), "python": platform.python_version(), "numpy": np.__version__, + "pandas": pd.__version__, "cpus": os.cpu_count(), "mem_total_bytes": total} + + +def equivalence(runs): + """{size: {"tabulate": bool, "read_hash": bool}} comparing every chunk size.""" + out = {} + for rows in sorted({r["rows"] for r in runs if "rows" in r}): + tabs = [r["results"] for r in runs if r["phase"] == "tabulate" and r["rows"] == rows] + hashes = {r["frame_hash"] for r in runs if r["phase"] == "read" and r["rows"] == rows} + out[rows] = {"tabulate": len(tabs) > 1 and all(t == tabs[0] for t in tabs[1:]), + "read_hash": len(hashes) == 1, "chunksizes": len(tabs)} + return out + + +def _fit(points, baseline): + """Least-squares line peak = a + b * rows over (rows, peak) points + (through the baseline when there is only one point).""" + n = len(points) + if n < 2: + x, y = points[0] + return float(baseline), (y - baseline) / x + mx = sum(x for x, _ in points) / n + my = sum(y for _, y in points) / n + b = sum((x - mx) * (y - my) for x, y in points) / sum((x - mx) ** 2 for x, _ in points) + return my - b * mx, b + + +def _increment(run): + """Phase peak minus RSS at its start (None where the current RSS is unknown).""" + return None if run.get("rss_start") is None else run["peak"] - run["rss_start"] + + +def summarize(files, runs): + """Per size: end-to-end peak, per-phase peaks and increments, bytes per row.""" + base = next(r for r in runs if r["phase"] == "baseline")["peak"] + rows_list = sorted(files) + table = [] + for rows in rows_list: + reads = [r for r in runs if r["phase"] == "read" and r["rows"] == rows] + tab = [r for r in runs if r["phase"] == "tabulate" and r["rows"] == rows] + w = next(r for r in runs if r["phase"] == "weights" and r["rows"] == rows) + default = next((r for r in reads if r["chunksize"] == 200_000), reads[-1]) + row = {"rows": rows, "gzip_bytes": files[rows]["gzip_bytes"], "csv_bytes": files[rows]["csv_bytes"], + "columns": files[rows]["columns"], "columns_read": default["columns_read"], + "frame_bytes": default["frame_bytes"], + "read": {r["chunksize"]: {"peak": r["peak"], "seconds": r["seconds"]} for r in reads}, + "weights": {"peak": w["peak"], "increment": _increment(w), + "matrix_bytes": w["matrix_bytes"], "seconds": w["seconds"]}, + "tabulate": {r["chunksize"]: {"peak": r["peak"], "increment": _increment(r), + "seconds": r["seconds"], "steps": r["steps"]} for r in tab}} + row["read_peak"] = max(v["peak"] for v in row["read"].values()) + row["tab_peak"] = max(v["peak"] for v in row["tabulate"].values()) + row["end_to_end_peak"] = max(row["read_peak"], row["tab_peak"], w["peak"]) + row["bytes_per_row"] = (row["end_to_end_peak"] - base) / rows + table.append(row) + intercept, slope = _fit([(t["rows"], t["end_to_end_peak"]) for t in table], base) + fits = {} + for gib in (16, 128): + budget = (gib - OS_RESERVE_GIB) * GIB + fits[gib] = int((budget - intercept) / slope) if slope > 0 else None + return {"baseline": base, "table": table, "intercept": intercept, "slope": slope, "fits": fits} + + +def _mib(b): + return "n/a" if b is None else f"{b / MIB:,.0f}" + + +def _gib(b): + return f"{b / GIB:.2f}" + + +def render(env, files, runs, summary, equiv, peak_reset_ok, small, compare=None): + lines = [ + "# PUMS memory benchmark", + "", + f"Generated by `python scripts/pums_memory_benchmark.py{' --small' if small else ''}` on {env['date']} " + "(PLAN_ROUND_14 workstream C; PLAN_ROUND_11 R2/F3). Synthetic PUMS-shaped person files only; nothing " + "was downloaded.", + "", + "## Environment", + "", + f"- Platform: {env['platform']} ({env['machine']}), {env['cpus']} CPUs, " + + (f"{env['mem_total_bytes'] / GIB:.0f} GiB RAM" if env["mem_total_bytes"] else "RAM unknown"), + f"- Python {env['python']}, numpy {env['numpy']}, pandas {env['pandas']}", + "- Peak RSS: kernel high-water mark (VmHWM, the value `resource.getrusage().ru_maxrss` reports), one " + "fresh subprocess per phase and chunk size. " + + ("The weights and tabulate subprocesses read the file, then reset the high-water mark " + "(`/proc/self/clear_refs`), so their peak is that phase on top of the resident frame." + if peak_reset_ok else "The high-water mark could not be reset here: weights and tabulate peaks " + "include the read."), + f"- Baseline (interpreter, numpy, pandas, census.pums imported): {_mib(summary['baseline'])} MiB", + "", + "## Inputs", + "", + "| Rows | Columns in file | Columns read | gzip size (MiB) | CSV size (MiB) | PUMAs | Frame in memory (MiB) |", + "| ---: | ---: | ---: | ---: | ---: | ---: | ---: |", + ] + for t in summary["table"]: + lines.append(f"| {t['rows']:,} | {t['columns']} | {t['columns_read']} | {_mib(t['gzip_bytes'])} | " + f"{_mib(t['csv_bytes'])} | {files[t['rows']]['pumas']} | {_mib(t['frame_bytes'])} |") + lines += [ + "", + "Columns read: SERIALNO, SPORDER, STATE, PUMA, AGEP, PINCP, WAGP, PWGTP and PWGTP1-PWGTP80 " + "(`read_pums_csv(columns=['AGEP', 'PINCP', 'WAGP'])`; identifiers, PUMA and weights are always kept). " + "Weights are read as float32. A real 5-year person file has about 287 columns, not " + f"{summary['table'][0]['columns']}: the parser still tokenizes the columns it does not keep (longer " + "read times than here) but does not store them, so the stored frame, and the peaks below, depend " + "on the columns read (`--extra-columns` adds unread filler columns to check this).", + "", + "## Peak RSS by phase (MiB)", + "", + ] + chunks = sorted({c for t in summary["table"] for c in t["read"]}) + head = ["Rows"] + [f"read, chunk {c:,}" for c in chunks] + [ + "weight matrix (peak / increment)", "tabulate (peak / increment)", "end-to-end peak", "bytes per row"] + lines.append("| " + " | ".join(head) + " |") + lines.append("| " + " | ".join(["---:"] * len(head)) + " |") + for t in summary["table"]: + incs = [v["increment"] for v in t["tabulate"].values() if v["increment"] is not None] + tab_inc = max(incs) if incs else None + cells = [f"{t['rows']:,}"] + [_mib(t["read"][c]["peak"]) if c in t["read"] else "" for c in chunks] + [ + f"{_mib(t['weights']['peak'])} / {_mib(t['weights']['increment'])}", + f"{_mib(t['tab_peak'])} / {_mib(tab_inc)}", _mib(t["end_to_end_peak"]), f"{t['bytes_per_row']:,.0f}"] + lines.append("| " + " | ".join(cells) + " |") + lines += [ + "", + "Increment: peak minus RSS at the start of the phase (the frame already resident). End-to-end peak: " + "the largest of the read, weight-matrix and tabulate peaks, i.e. the peak of reading the file and " + "running these tabulations in one process. Bytes per row: (end-to-end peak - baseline) / rows. " + "The weight matrix alone is rows x 81 x 8 bytes " + f"({_mib(summary['table'][-1]['weights']['matrix_bytes'])} MiB at {summary['table'][-1]['rows']:,} rows).", + "", + "### Tabulation steps (MiB peak / seconds, default chunk size)", + "", + "| Rows | median PINCP (AGEP >= 15) | mean WAGP (AGEP >= 16) | mean WAGP by PUMA |", + "| ---: | ---: | ---: | ---: |", + ] + for t in summary["table"]: + tab = t["tabulate"].get(200_000) or next(iter(t["tabulate"].values())) + st = tab["steps"] + lines.append(f"| {t['rows']:,} | " + " | ".join( + f"{_mib(st[k]['peak'])} / {st[k]['seconds']:.1f}" for k in ("median_PINCP", "mean_WAGP", + "mean_WAGP_by_PUMA")) + " |") + lines += ["", "### Wall time (seconds)", "", + "| Rows | " + " | ".join(f"read, chunk {c:,}" for c in chunks) + " | weight matrix | tabulate |", + "| ---: | " + " | ".join(["---:"] * len(chunks)) + " | ---: | ---: |"] + for t in summary["table"]: + tab = t["tabulate"].get(200_000) or next(iter(t["tabulate"].values())) + lines.append(f"| {t['rows']:,} | " + " | ".join( + f"{t['read'][c]['seconds']:.1f}" if c in t["read"] else "" for c in chunks) + + f" | {t['weights']['seconds']:.2f} | {tab['seconds']:.1f} |") + lines += ["", "## Chunk-size equivalence", "", + "| Rows | Chunk sizes compared | Frame hash identical | Estimates and SEs identical |", + "| ---: | ---: | :--- | :--- |"] + for rows, e in sorted(equiv.items()): + lines.append(f"| {rows:,} | {e['chunksizes']} | {'yes' if e['read_hash'] else 'NO'} | " + f"{'yes' if e['tabulate'] else 'NO'} |") + lines += ["", "Identical means bit-for-bit: every estimate, SE and unweighted n of the three tabulations " + "(including all PUMA groups) compared as exact float values, and one hash over the frame read.", ""] + s = summary + big = s["table"][-1] + lines += [ + "## Scaling and practical limit", + "", + f"A least-squares line through the end-to-end peaks gives peak = {_mib(s['intercept'])} MiB + " + f"{s['slope']:,.0f} bytes x rows. With {OS_RESERVE_GIB:g} GiB left for the operating system, the " + "rows that fit with these columns are:", + "", + "| Machine RAM | Rows (person records) that fit | California-sized states (1.9M rows) |", + "| :--- | ---: | ---: |", + ] + for gib, n in s["fits"].items(): + lines.append(f"| {gib} GiB | {n:,} | {n / 1_900_000:.1f} |" if n else f"| {gib} GiB | n/a | n/a |") + lines += [ + "", + "These are for the needed columns plus the 80 replicate weights. Each extra numeric column read adds " + "8 bytes per row (float64 after conversion) and each extra text column more (pandas strings); " + "`columns=None` reads all 280+ columns of a real file and multiplies the frame several-fold (the " + "PumsMemoryWarning above 200 MB). The figures exclude the download step: `fetch_pums` holds the " + "compressed response in memory once while writing it to the cache (one zip, hundreds of MB for the " + "largest states).", + "", + ] + if compare: + before, label = compare + lines += [ + "## Before and after the Round 14 copy fixes", + "", + f"Before: {label}. The fixes (census/pums.py) remove avoidable copies of the n x 81 weight matrix: " + "`_group_sum` no longer copies it when every row counts and passes `copy=False` to DataFrame " + "(pandas 3 copies a 2-D array by default); `weighted_median` gathers the rows it uses once " + "instead of twice; `tabulate` no longer copies `W[rows]` per median group; `read_pums_csv` " + "drops the parsed chunks before conversion.", + "", + "| Rows | read peak before / after | weight matrix before / after | tabulate peak before / after " + "| end-to-end before / after | Estimates and SEs identical to before |", + "| ---: | ---: | ---: | ---: | ---: | :--- |", + ] + prev = {t["rows"]: t for t in before["table"]} + for t in summary["table"]: + p = prev.get(t["rows"]) or prev.get(str(t["rows"])) + if p is None: + continue + same = all(r["results"] == b["results"] for r in runs if r["phase"] == "tabulate" and r["rows"] == t["rows"] + for b in before["runs"] if b["phase"] == "tabulate" and b["rows"] == t["rows"] + and b["chunksize"] == r["chunksize"]) + lines.append(f"| {t['rows']:,} | {_mib(p['read_peak'])} / {_mib(t['read_peak'])} | " + f"{_mib(p['weights']['peak'])} / {_mib(t['weights']['peak'])} | " + f"{_mib(p['tab_peak'])} / {_mib(t['tab_peak'])} | " + f"{_mib(p['end_to_end_peak'])} / {_mib(t['end_to_end_peak'])} | {'yes' if same else 'NO'} |") + lines.append("") + if not small: + lines += [ + "## Findings and recommendation", + "", + f"- Memory scales linearly with rows, at about {s['slope']:,.0f} bytes per person record for this " + f"column set; the largest state (about {big['rows']:,} records) peaked at " + f"{_gib(big['end_to_end_peak'])} GiB.", + "- Chunk size does not bound memory (every chunk is kept until the frame is concatenated) and does " + "not change any result: estimates, SEs and the frame are bit-identical across chunk sizes. Smaller " + "chunks only lower the transient parser buffer.", + "- The peak comes from tabulation, which holds the frame, the 81-column float64 weight matrix and " + "per-statistic temporaries at once; reading alone peaks lower.", + f"- A 16 GiB machine handles any single state with the needed columns; the owner's 128 GiB machine " + f"holds about {s['fits'][128] / 1_900_000:.0f} California-sized files in one process (all states " + "together are about 16 million 5-year person records).", + "- Recommendation: keep the Round 10 design (full state files on demand, needed columns only); no " + "streaming rewrite is warranted. For many states, tabulate state by state and combine, or read " + "with `columns=[...]` and `filter=` to keep only the rows and variables needed.", + "", + ] + return "\n".join(lines) + + +def main(argv=None): + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--small", action="store_true", help="2,000 rows, chunk sizes 500 and 2,000 (fast test)") + ap.add_argument("--sizes", help="comma-separated row counts (default 100000,500000,1900000)") + ap.add_argument("--chunksizes", help="comma-separated chunk sizes (default 50000,200000,1000000)") + ap.add_argument("--extra-columns", type=int, default=0, help="filler columns written but not read") + ap.add_argument("--workdir", help="directory for the generated CSVs (default: a temporary directory)") + ap.add_argument("--keep", action="store_true", help="keep the generated CSVs") + ap.add_argument("--out", help="markdown report (default docs/audits/pums_memory.md; with --small, " + "pums_memory.md in the work directory)") + ap.add_argument("--json", help="also write the raw measurements as JSON") + ap.add_argument("--compare", help="raw JSON (--json) of an earlier run: add a before/after section") + ap.add_argument("--compare-label", help="how the report describes the earlier run") + ap.add_argument("--worker", help=argparse.SUPPRESS) + ap.add_argument("--file", help=argparse.SUPPRESS) + ap.add_argument("--chunksize", type=int, help=argparse.SUPPRESS) + args = ap.parse_args(argv) + + if args.worker: + sys.path.insert(0, ROOT) + print(json.dumps(run_phase(args.worker, args.file, args.chunksize))) + return 0 + + sizes = [int(s) for s in args.sizes.split(",")] if args.sizes else list(SMALL_SIZES if args.small else SIZES) + chunks = ([int(c) for c in args.chunksizes.split(",")] if args.chunksizes + else list(SMALL_CHUNKSIZES if args.small else CHUNKSIZES)) + own_dir = args.workdir is None + workdir = args.workdir or tempfile.mkdtemp(prefix="pums_memory_") + os.makedirs(workdir, exist_ok=True) + out_md = args.out or (os.path.join(workdir, "pums_memory.md") if args.small else OUT_MD) + + env = environment() + files, runs, made = {}, [_subprocess("baseline", None, None)], [] + try: + for rows in sizes: + path = os.path.join(workdir, f"psam_synthetic_{rows}.csv.gz") + t0 = time.perf_counter() + files[rows] = generate(path, rows, args.extra_columns) + made.append(path) + print(f"{rows:,} rows: generated {files[rows]['gzip_bytes'] / MIB:,.0f} MiB gzip in " + f"{time.perf_counter() - t0:.0f} s", file=sys.stderr) + for c in chunks: + runs.append(_subprocess("read", path, c)) + runs.append(_subprocess("weights", path, chunks[len(chunks) // 2])) + for c in chunks: + runs.append(_subprocess("tabulate", path, c)) + print(f"{rows:,} rows: measured", file=sys.stderr) + finally: + if not args.keep: + for p in made: + if os.path.exists(p): + os.remove(p) + if own_dir and not args.small: + shutil.rmtree(workdir, ignore_errors=True) + + equiv = equivalence(runs) + summary = summarize(files, runs) + peak_reset_ok = all(r["peak_reset"] for r in runs if r["phase"] in ("weights", "tabulate")) + compare = None + if args.compare: + with open(args.compare, encoding="utf-8") as f: + before = json.load(f) + compare = (before, args.compare_label or os.path.basename(args.compare)) + report = render(env, files, runs, summary, equiv, peak_reset_ok, args.small, compare) + os.makedirs(os.path.dirname(os.path.abspath(out_md)), exist_ok=True) + with open(out_md, "w", encoding="utf-8") as f: + f.write(report) + if args.json: + with open(args.json, "w", encoding="utf-8") as f: + json.dump({"environment": env, "files": files, "runs": runs, "equivalence": equiv, + "summary": {k: v for k, v in summary.items() if k != "table"}, + "table": summary["table"]}, f, indent=1, default=str) + ok = all(e["tabulate"] and e["read_hash"] for e in equiv.values()) + print(f"wrote {out_md}; chunk-size equivalence: {'identical' if ok else 'DIFFERENT'}", file=sys.stderr) + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/fixtures/mi_ny_manifest/cbsa_membership.csv b/tests/fixtures/mi_ny_manifest/cbsa_membership.csv new file mode 100644 index 0000000..5069c21 --- /dev/null +++ b/tests/fixtures/mi_ny_manifest/cbsa_membership.csv @@ -0,0 +1,146 @@ +GEOID,CBSA,CBSA_NAME,LSAD,share +26001,,,, +26003,,,, +26005,26090,"Holland, MI",M2,1.0 +26007,10980,"Alpena, MI",M2,1.0 +26009,,,, +26011,,,, +26013,,,, +26015,24340,"Grand Rapids-Wyoming-Kentwood, MI",M1,1.0 +26017,13020,"Bay City, MI",M1,1.0 +26019,45900,"Traverse City, MI",M1,1.0 +26021,35660,"Niles, MI",M1,1.0 +26023,17740,"Coldwater, MI",M2,1.0 +26025,12980,"Battle Creek, MI",M1,1.0 +26027,43780,"South Bend-Mishawaka, IN-MI",M1,1.0 +26029,,,, +26031,,,, +26033,42300,"Sault Ste. Marie, MI",M2,1.0 +26035,,,, +26037,29620,"Lansing-East Lansing, MI",M1,1.0 +26039,,,, +26041,21540,"Escanaba, MI",M2,1.0 +26043,27020,"Iron Mountain, MI-WI",M2,1.0 +26045,29620,"Lansing-East Lansing, MI",M1,1.0 +26047,37950,"Petoskey, MI",M2,1.0 +26049,22420,"Flint, MI",M1,1.0 +26051,,,, +26053,,,, +26055,45900,"Traverse City, MI",M1,1.0 +26057,10940,"Alma, MI",M2,1.0 +26059,25880,"Hillsdale, MI",M2,1.0 +26061,26340,"Houghton, MI",M2,1.0 +26063,,,, +26065,29620,"Lansing-East Lansing, MI",M1,1.0 +26067,24340,"Grand Rapids-Wyoming-Kentwood, MI",M1,1.0 +26069,,,, +26071,,,, +26073,34380,"Mount Pleasant, MI",M2,1.0 +26075,27100,"Jackson, MI",M1,1.0 +26077,28020,"Kalamazoo-Portage, MI",M1,1.0 +26079,45900,"Traverse City, MI",M1,1.0 +26081,24340,"Grand Rapids-Wyoming-Kentwood, MI",M1,1.0 +26083,26340,"Houghton, MI",M2,1.0 +26085,,,, +26087,19820,"Detroit-Warren-Dearborn, MI",M1,1.0 +26089,45900,"Traverse City, MI",M1,1.0 +26091,10300,"Adrian, MI",M2,1.0 +26093,19820,"Detroit-Warren-Dearborn, MI",M1,1.0 +26095,,,, +26097,,,, +26099,19820,"Detroit-Warren-Dearborn, MI",M1,1.0 +26101,,,, +26103,32100,"Marquette, MI",M2,1.0 +26105,31220,"Ludington, MI",M2,1.0 +26107,13660,"Big Rapids, MI",M2,1.0 +26109,31940,"Marinette, WI-MI",M2,1.0 +26111,33220,"Midland, MI",M1,1.0 +26113,15620,"Cadillac, MI",M2,1.0 +26115,33780,"Monroe, MI",M1,1.0 +26117,24340,"Grand Rapids-Wyoming-Kentwood, MI",M1,1.0 +26119,,,, +26121,34740,"Muskegon-Norton Shores, MI",M1,1.0 +26123,,,, +26125,19820,"Detroit-Warren-Dearborn, MI",M1,1.0 +26127,,,, +26129,,,, +26131,,,, +26133,,,, +26135,,,, +26137,,,, +26139,24340,"Grand Rapids-Wyoming-Kentwood, MI",M1,1.0 +26141,,,, +26143,,,, +26145,40980,"Saginaw, MI",M1,1.0 +26147,19820,"Detroit-Warren-Dearborn, MI",M1,1.0 +26149,44780,"Sturgis, MI",M2,1.0 +26151,,,, +26153,,,, +26155,37020,"Owosso, MI",M2,1.0 +26157,,,, +26159,,,, +26161,11460,"Ann Arbor, MI",M1,1.0 +26163,19820,"Detroit-Warren-Dearborn, MI",M1,1.0 +26165,15620,"Cadillac, MI",M2,1.0 +36001,10580,"Albany-Schenectady-Troy, NY",M1,1.0 +36003,,,, +36005,35620,"New York-Newark-Jersey City, NY-NJ",M1,1.0 +36007,13780,"Binghamton, NY",M1,1.0 +36009,36460,"Olean, NY",M2,1.0 +36011,12180,"Auburn, NY",M2,1.0 +36013,27460,"Jamestown-Dunkirk, NY",M2,1.0 +36015,21300,"Elmira, NY",M1,1.0 +36017,,,, +36019,38460,"Plattsburgh, NY",M2,1.0 +36021,26460,"Hudson, NY",M2,1.0 +36023,18660,"Cortland, NY",M2,1.0 +36025,,,, +36027,28880,"Kiryas Joel-Poughkeepsie-Newburgh, NY",M1,1.0 +36029,15380,"Buffalo-Cheektowaga, NY",M1,1.0 +36031,,,, +36033,,,, +36035,24100,"Gloversville, NY",M2,1.0 +36037,12860,"Batavia, NY",M2,1.0 +36039,,,, +36041,,,, +36043,46540,"Utica-Rome, NY",M1,1.0 +36045,48060,"Watertown-Fort Drum, NY",M1,1.0 +36047,35620,"New York-Newark-Jersey City, NY-NJ",M1,1.0 +36049,,,, +36051,40380,"Rochester, NY",M1,1.0 +36053,45060,"Syracuse, NY",M1,1.0 +36055,40380,"Rochester, NY",M1,1.0 +36057,11220,"Amsterdam, NY",M2,1.0 +36059,35620,"New York-Newark-Jersey City, NY-NJ",M1,1.0 +36061,35620,"New York-Newark-Jersey City, NY-NJ",M1,1.0 +36063,15380,"Buffalo-Cheektowaga, NY",M1,1.0 +36065,46540,"Utica-Rome, NY",M1,1.0 +36067,45060,"Syracuse, NY",M1,1.0 +36069,40380,"Rochester, NY",M1,1.0 +36071,28880,"Kiryas Joel-Poughkeepsie-Newburgh, NY",M1,1.0 +36073,40380,"Rochester, NY",M1,1.0 +36075,45060,"Syracuse, NY",M1,1.0 +36077,36580,"Oneonta, NY",M2,1.0 +36079,35620,"New York-Newark-Jersey City, NY-NJ",M1,1.0 +36081,35620,"New York-Newark-Jersey City, NY-NJ",M1,1.0 +36083,10580,"Albany-Schenectady-Troy, NY",M1,1.0 +36085,35620,"New York-Newark-Jersey City, NY-NJ",M1,1.0 +36087,35620,"New York-Newark-Jersey City, NY-NJ",M1,1.0 +36089,32390,"Massena-Ogdensburg, NY",M2,1.0 +36091,10580,"Albany-Schenectady-Troy, NY",M1,1.0 +36093,10580,"Albany-Schenectady-Troy, NY",M1,1.0 +36095,10580,"Albany-Schenectady-Troy, NY",M1,1.0 +36097,,,, +36099,42900,"Seneca Falls, NY",M2,1.0 +36101,18500,"Corning, NY",M2,1.0 +36103,35620,"New York-Newark-Jersey City, NY-NJ",M1,1.0 +36105,33910,"Monticello, NY",M2,1.0 +36107,13780,"Binghamton, NY",M1,1.0 +36109,27060,"Ithaca, NY",M1,1.0 +36111,28740,"Kingston, NY",M1,1.0 +36113,24020,"Glens Falls, NY",M1,1.0 +36115,24020,"Glens Falls, NY",M1,1.0 +36117,40380,"Rochester, NY",M1,1.0 +36119,35620,"New York-Newark-Jersey City, NY-NJ",M1,1.0 +36121,,,, +36123,,,, diff --git a/tests/fixtures/mi_ny_manifest/cbsa_membership_source.txt b/tests/fixtures/mi_ny_manifest/cbsa_membership_source.txt new file mode 100644 index 0000000..00d47c0 --- /dev/null +++ b/tests/fixtures/mi_ny_manifest/cbsa_membership_source.txt @@ -0,0 +1 @@ +https://www2.census.gov/geo/tiger/GENZ2024/shp/cb_2024_us_cbsa_500k.zip diff --git a/tests/fixtures/mi_ny_manifest/frozen_api_responses_26.json b/tests/fixtures/mi_ny_manifest/frozen_api_responses_26.json new file mode 100644 index 0000000..f59930f --- /dev/null +++ b/tests/fixtures/mi_ny_manifest/frozen_api_responses_26.json @@ -0,0 +1 @@ +{"_about":"REAL Census Data API responses (2024 acs5, all counties of state 26, get=NAME,group()), recorded by `python -m tests.test_manifest_multistate --record` for examples/manifests/mi_ny_income_education.json; columns trimmed to the manifest's variables (estimate, MOE and annotations) plus GEO_ID, NAME, state, county.","request":{"year":2024,"dataset":"acs5","geo_level":"county","state_code":"26","county_code":"*"},"requests":{"B19013":[["NAME","B19013_001E","B19013_001EA","B19013_001M","B19013_001MA","GEO_ID","NAME","state","county"],["Alcona County, Michigan","54993",null,"1572",null,"0500000US26001","Alcona County, Michigan","26","001"],["Alger County, Michigan","61286",null,"1930",null,"0500000US26003","Alger County, Michigan","26","003"],["Allegan County, Michigan","83283",null,"2480",null,"0500000US26005","Allegan County, Michigan","26","005"],["Alpena County, Michigan","53950",null,"2679",null,"0500000US26007","Alpena County, Michigan","26","007"],["Antrim County, Michigan","72054",null,"2793",null,"0500000US26009","Antrim County, Michigan","26","009"],["Arenac County, Michigan","58811",null,"1914",null,"0500000US26011","Arenac County, Michigan","26","011"],["Baraga County, Michigan","55327",null,"6240",null,"0500000US26013","Baraga County, Michigan","26","013"],["Barry County, Michigan","77500",null,"3772",null,"0500000US26015","Barry County, Michigan","26","015"],["Bay County, Michigan","61763",null,"2526",null,"0500000US26017","Bay County, Michigan","26","017"],["Benzie County, Michigan","74834",null,"3104",null,"0500000US26019","Benzie County, Michigan","26","019"],["Berrien County, Michigan","65425",null,"1108",null,"0500000US26021","Berrien County, Michigan","26","021"],["Branch County, Michigan","63724",null,"1967",null,"0500000US26023","Branch County, Michigan","26","023"],["Calhoun County, Michigan","63177",null,"2107",null,"0500000US26025","Calhoun County, Michigan","26","025"],["Cass County, Michigan","70443",null,"3549",null,"0500000US26027","Cass County, Michigan","26","027"],["Charlevoix County, Michigan","76531",null,"3028",null,"0500000US26029","Charlevoix County, Michigan","26","029"],["Cheboygan County, Michigan","62875",null,"3272",null,"0500000US26031","Cheboygan County, Michigan","26","031"],["Chippewa County, Michigan","62217",null,"2143",null,"0500000US26033","Chippewa County, Michigan","26","033"],["Clare County, Michigan","49384",null,"2661",null,"0500000US26035","Clare County, Michigan","26","035"],["Clinton County, Michigan","88210",null,"4115",null,"0500000US26037","Clinton County, Michigan","26","037"],["Crawford County, Michigan","60392",null,"4587",null,"0500000US26039","Crawford County, Michigan","26","039"],["Delta County, Michigan","57809",null,"3201",null,"0500000US26041","Delta County, Michigan","26","041"],["Dickinson County, Michigan","65156",null,"3928",null,"0500000US26043","Dickinson County, Michigan","26","043"],["Eaton County, Michigan","79597",null,"2067",null,"0500000US26045","Eaton County, Michigan","26","045"],["Emmet County, Michigan","78112",null,"4802",null,"0500000US26047","Emmet County, Michigan","26","047"],["Genesee County, Michigan","62281",null,"1425",null,"0500000US26049","Genesee County, Michigan","26","049"],["Gladwin County, Michigan","55349",null,"2210",null,"0500000US26051","Gladwin County, Michigan","26","051"],["Gogebic County, Michigan","52732",null,"3913",null,"0500000US26053","Gogebic County, Michigan","26","053"],["Grand Traverse County, Michigan","81647",null,"3008",null,"0500000US26055","Grand Traverse County, Michigan","26","055"],["Gratiot County, Michigan","63556",null,"2453",null,"0500000US26057","Gratiot County, Michigan","26","057"],["Hillsdale County, Michigan","64242",null,"2663",null,"0500000US26059","Hillsdale County, Michigan","26","059"],["Houghton County, Michigan","58899",null,"3371",null,"0500000US26061","Houghton County, Michigan","26","061"],["Huron County, Michigan","58870",null,"2129",null,"0500000US26063","Huron County, Michigan","26","063"],["Ingham County, Michigan","65526",null,"1674",null,"0500000US26065","Ingham County, Michigan","26","065"],["Ionia County, Michigan","75611",null,"2186",null,"0500000US26067","Ionia County, Michigan","26","067"],["Iosco County, Michigan","50066",null,"2086",null,"0500000US26069","Iosco County, Michigan","26","069"],["Iron County, Michigan","55940",null,"3026",null,"0500000US26071","Iron County, Michigan","26","071"],["Isabella County, Michigan","55237",null,"2195",null,"0500000US26073","Isabella County, Michigan","26","073"],["Jackson County, Michigan","66073",null,"1893",null,"0500000US26075","Jackson County, Michigan","26","075"],["Kalamazoo County, Michigan","72532",null,"1537",null,"0500000US26077","Kalamazoo County, Michigan","26","077"],["Kalkaska County, Michigan","68578",null,"3573",null,"0500000US26079","Kalkaska County, Michigan","26","079"],["Kent County, Michigan","82631",null,"1165",null,"0500000US26081","Kent County, Michigan","26","081"],["Keweenaw County, Michigan","52583",null,"5516",null,"0500000US26083","Keweenaw County, Michigan","26","083"],["Lake County, Michigan","50805",null,"2404",null,"0500000US26085","Lake County, Michigan","26","085"],["Lapeer County, Michigan","77306",null,"2688",null,"0500000US26087","Lapeer County, Michigan","26","087"],["Leelanau County, Michigan","99422",null,"6699",null,"0500000US26089","Leelanau County, Michigan","26","089"],["Lenawee County, Michigan","70518",null,"1768",null,"0500000US26091","Lenawee County, Michigan","26","091"],["Livingston County, Michigan","103039",null,"1720",null,"0500000US26093","Livingston County, Michigan","26","093"],["Luce County, Michigan","54893",null,"2148",null,"0500000US26095","Luce County, Michigan","26","095"],["Mackinac County, Michigan","63909",null,"3854",null,"0500000US26097","Mackinac County, Michigan","26","097"],["Macomb County, Michigan","77837",null,"1037",null,"0500000US26099","Macomb County, Michigan","26","099"],["Manistee County, Michigan","63516",null,"2576",null,"0500000US26101","Manistee County, Michigan","26","101"],["Marquette County, Michigan","65429",null,"2954",null,"0500000US26103","Marquette County, Michigan","26","103"],["Mason County, Michigan","64748",null,"3281",null,"0500000US26105","Mason County, Michigan","26","105"],["Mecosta County, Michigan","60951",null,"2088",null,"0500000US26107","Mecosta County, Michigan","26","107"],["Menominee County, Michigan","56435",null,"3732",null,"0500000US26109","Menominee County, Michigan","26","109"],["Midland County, Michigan","77705",null,"2331",null,"0500000US26111","Midland County, Michigan","26","111"],["Missaukee County, Michigan","66194",null,"5534",null,"0500000US26113","Missaukee County, Michigan","26","113"],["Monroe County, Michigan","77335",null,"2293",null,"0500000US26115","Monroe County, Michigan","26","115"],["Montcalm County, Michigan","67344",null,"2180",null,"0500000US26117","Montcalm County, Michigan","26","117"],["Montmorency County, Michigan","49756",null,"3273",null,"0500000US26119","Montmorency County, Michigan","26","119"],["Muskegon County, Michigan","65024",null,"1543",null,"0500000US26121","Muskegon County, Michigan","26","121"],["Newaygo County, Michigan","63304",null,"2643",null,"0500000US26123","Newaygo County, Michigan","26","123"],["Oakland County, Michigan","97760",null,"1268",null,"0500000US26125","Oakland County, Michigan","26","125"],["Oceana County, Michigan","62417",null,"3332",null,"0500000US26127","Oceana County, Michigan","26","127"],["Ogemaw County, Michigan","54666",null,"2368",null,"0500000US26129","Ogemaw County, Michigan","26","129"],["Ontonagon County, Michigan","54398",null,"5453",null,"0500000US26131","Ontonagon County, Michigan","26","131"],["Osceola County, Michigan","58267",null,"1942",null,"0500000US26133","Osceola County, Michigan","26","133"],["Oscoda County, Michigan","49515",null,"3496",null,"0500000US26135","Oscoda County, Michigan","26","135"],["Otsego County, Michigan","68183",null,"4808",null,"0500000US26137","Otsego County, Michigan","26","137"],["Ottawa County, Michigan","90502",null,"1860",null,"0500000US26139","Ottawa County, Michigan","26","139"],["Presque Isle County, Michigan","59321",null,"3539",null,"0500000US26141","Presque Isle County, Michigan","26","141"],["Roscommon County, Michigan","55246",null,"2903",null,"0500000US26143","Roscommon County, Michigan","26","143"],["Saginaw County, Michigan","60622",null,"1588",null,"0500000US26145","Saginaw County, Michigan","26","145"],["St. Clair County, Michigan","71270",null,"1351",null,"0500000US26147","St. Clair County, Michigan","26","147"],["St. Joseph County, Michigan","66425",null,"2763",null,"0500000US26149","St. Joseph County, Michigan","26","149"],["Sanilac County, Michigan","58863",null,"1842",null,"0500000US26151","Sanilac County, Michigan","26","151"],["Schoolcraft County, Michigan","56782",null,"4076",null,"0500000US26153","Schoolcraft County, Michigan","26","153"],["Shiawassee County, Michigan","67404",null,"2587",null,"0500000US26155","Shiawassee County, Michigan","26","155"],["Tuscola County, Michigan","63811",null,"1903",null,"0500000US26157","Tuscola County, Michigan","26","157"],["Van Buren County, Michigan","68114",null,"2018",null,"0500000US26159","Van Buren County, Michigan","26","159"],["Washtenaw County, Michigan","89180",null,"2487",null,"0500000US26161","Washtenaw County, Michigan","26","161"],["Wayne County, Michigan","60539",null,"602",null,"0500000US26163","Wayne County, Michigan","26","163"],["Wexford County, Michigan","61085",null,"2988",null,"0500000US26165","Wexford County, Michigan","26","165"]],"B15002":[["NAME","B15002_001E","B15002_001EA","B15002_001M","B15002_001MA","B15002_015E","B15002_015EA","B15002_015M","B15002_015MA","B15002_016E","B15002_016EA","B15002_016M","B15002_016MA","B15002_017E","B15002_017EA","B15002_017M","B15002_017MA","B15002_018E","B15002_018EA","B15002_018M","B15002_018MA","B15002_032E","B15002_032EA","B15002_032M","B15002_032MA","B15002_033E","B15002_033EA","B15002_033M","B15002_033MA","B15002_034E","B15002_034EA","B15002_034M","B15002_034MA","B15002_035E","B15002_035EA","B15002_035M","B15002_035MA","GEO_ID","NAME","state","county"],["Alcona County, Michigan","8649",null,"56",null,"582",null,"109",null,"264",null,"55",null,"65",null,"25",null,"14",null,"9",null,"510",null,"72",null,"363",null,"83",null,"36",null,"19",null,"18",null,"11",null,"0500000US26001","Alcona County, Michigan","26","001"],["Alger County, Michigan","6825",null,"77",null,"507",null,"89",null,"140",null,"38",null,"18",null,"13",null,"40",null,"23",null,"509",null,"79",null,"184",null,"42",null,"23",null,"15",null,"13",null,"17",null,"0500000US26003","Alger County, Michigan","26","003"],["Allegan County, Michigan","84097",null,"85",null,"7499",null,"453",null,"2481",null,"346",null,"487",null,"112",null,"512",null,"149",null,"7696",null,"534",null,"3427",null,"287",null,"512",null,"161",null,"210",null,"77",null,"0500000US26005","Allegan County, Michigan","26","005"],["Alpena County, Michigan","21702",null,"35",null,"1135",null,"188",null,"533",null,"161",null,"230",null,"96",null,"55",null,"37",null,"1349",null,"207",null,"533",null,"114",null,"89",null,"52",null,"53",null,"38",null,"0500000US26007","Alpena County, Michigan","26","007"],["Antrim County, Michigan","18527",null,"44",null,"1771",null,"193",null,"861",null,"129",null,"261",null,"97",null,"118",null,"43",null,"2157",null,"198",null,"1013",null,"132",null,"103",null,"46",null,"72",null,"37",null,"0500000US26009","Antrim County, Michigan","26","009"],["Arenac County, Michigan","11376",null,"40",null,"507",null,"78",null,"185",null,"41",null,"33",null,"17",null,"47",null,"25",null,"633",null,"93",null,"359",null,"69",null,"28",null,"17",null,"23",null,"15",null,"0500000US26011","Arenac County, Michigan","26","011"],["Baraga County, Michigan","6269",null,"46",null,"293",null,"56",null,"119",null,"50",null,"50",null,"31",null,"3",null,"4",null,"298",null,"61",null,"136",null,"46",null,"18",null,"18",null,"9",null,"11",null,"0500000US26013","Baraga County, Michigan","26","013"],["Barry County, Michigan","44987",null,"120",null,"3517",null,"343",null,"1069",null,"223",null,"284",null,"123",null,"108",null,"62",null,"4170",null,"427",null,"1733",null,"258",null,"116",null,"56",null,"217",null,"116",null,"0500000US26015","Barry County, Michigan","26","015"],["Bay County, Michigan","75312",null,"95",null,"5317",null,"514",null,"1387",null,"226",null,"464",null,"153",null,"223",null,"99",null,"6041",null,"579",null,"2779",null,"335",null,"229",null,"84",null,"219",null,"88",null,"0500000US26017","Bay County, Michigan","26","017"],["Benzie County, Michigan","14033",null,"56",null,"1442",null,"170",null,"586",null,"97",null,"173",null,"50",null,"88",null,"35",null,"1756",null,"187",null,"898",null,"118",null,"133",null,"45",null,"99",null,"43",null,"0500000US26019","Benzie County, Michigan","26","019"],["Berrien County, Michigan","108207",null,"80",null,"9492",null,"583",null,"4331",null,"391",null,"1246",null,"246",null,"841",null,"173",null,"10619",null,"605",null,"5515",null,"445",null,"785",null,"170",null,"653",null,"142",null,"0500000US26021","Berrien County, Michigan","26","021"],["Branch County, Michigan","31177",null,"102",null,"1862",null,"201",null,"486",null,"99",null,"117",null,"58",null,"51",null,"26",null,"1991",null,"176",null,"750",null,"105",null,"98",null,"54",null,"34",null,"19",null,"0500000US26023","Branch County, Michigan","26","023"],["Calhoun County, Michigan","91914",null,"134",null,"6596",null,"624",null,"2020",null,"307",null,"481",null,"144",null,"391",null,"107",null,"7173",null,"527",null,"3613",null,"416",null,"445",null,"155",null,"338",null,"171",null,"0500000US26025","Calhoun County, Michigan","26","025"],["Cass County, Michigan","37178",null,"121",null,"2455",null,"283",null,"895",null,"149",null,"247",null,"116",null,"89",null,"50",null,"2676",null,"296",null,"1443",null,"243",null,"192",null,"94",null,"115",null,"65",null,"0500000US26027","Cass County, Michigan","26","027"],["Charlevoix County, Michigan","19802",null,"67",null,"2117",null,"171",null,"1068",null,"146",null,"355",null,"81",null,"87",null,"31",null,"1968",null,"170",null,"1091",null,"105",null,"189",null,"54",null,"98",null,"37",null,"0500000US26029","Charlevoix County, Michigan","26","029"],["Cheboygan County, Michigan","20104",null,"80",null,"1122",null,"162",null,"557",null,"108",null,"227",null,"87",null,"140",null,"57",null,"1818",null,"233",null,"886",null,"163",null,"108",null,"57",null,"55",null,"35",null,"0500000US26031","Cheboygan County, Michigan","26","031"],["Chippewa County, Michigan","25680",null,"145",null,"1679",null,"219",null,"550",null,"138",null,"151",null,"53",null,"137",null,"75",null,"2215",null,"244",null,"1017",null,"173",null,"99",null,"45",null,"134",null,"48",null,"0500000US26033","Chippewa County, Michigan","26","033"],["Clare County, Michigan","23203",null,"59",null,"801",null,"124",null,"345",null,"93",null,"86",null,"43",null,"67",null,"38",null,"951",null,"138",null,"618",null,"160",null,"33",null,"29",null,"88",null,"44",null,"0500000US26035","Clare County, Michigan","26","035"],["Clinton County, Michigan","56125",null,"78",null,"6055",null,"472",null,"2240",null,"275",null,"831",null,"223",null,"472",null,"126",null,"5973",null,"464",null,"3266",null,"357",null,"633",null,"132",null,"384",null,"116",null,"0500000US26037","Clinton County, Michigan","26","037"],["Crawford County, Michigan","10316",null,"64",null,"488",null,"89",null,"250",null,"70",null,"60",null,"36",null,"44",null,"37",null,"730",null,"152",null,"428",null,"97",null,"80",null,"40",null,"13",null,"10",null,"0500000US26039","Crawford County, Michigan","26","039"],["Delta County, Michigan","27317",null,"75",null,"2032",null,"247",null,"577",null,"133",null,"144",null,"102",null,"110",null,"55",null,"2050",null,"274",null,"593",null,"150",null,"88",null,"54",null,"42",null,"33",null,"0500000US26041","Delta County, Michigan","26","041"],["Dickinson County, Michigan","19085",null,"110",null,"1653",null,"299",null,"505",null,"150",null,"139",null,"60",null,"120",null,"93",null,"1702",null,"239",null,"617",null,"153",null,"67",null,"39",null,"28",null,"27",null,"0500000US26043","Dickinson County, Michigan","26","043"],["Eaton County, Michigan","78050",null,"160",null,"7591",null,"576",null,"2672",null,"357",null,"616",null,"171",null,"469",null,"210",null,"8508",null,"529",null,"3528",null,"317",null,"358",null,"117",null,"319",null,"109",null,"0500000US26045","Eaton County, Michigan","26","045"],["Emmet County, Michigan","25406",null,"85",null,"3132",null,"307",null,"1290",null,"215",null,"601",null,"142",null,"160",null,"58",null,"2936",null,"276",null,"2000",null,"226",null,"265",null,"84",null,"177",null,"81",null,"0500000US26047","Emmet County, Michigan","26","047"],["Genesee County, Michigan","280648",null,"152",null,"19573",null,"1021",null,"6521",null,"564",null,"2220",null,"499",null,"822",null,"203",null,"23329",null,"1325",null,"10469",null,"735",null,"1615",null,"295",null,"784",null,"206",null,"0500000US26049","Genesee County, Michigan","26","049"],["Gladwin County, Michigan","19280",null,"62",null,"982",null,"142",null,"372",null,"79",null,"59",null,"27",null,"32",null,"27",null,"1156",null,"141",null,"553",null,"112",null,"89",null,"43",null,"10",null,"9",null,"0500000US26051","Gladwin County, Michigan","26","051"],["Gogebic County, Michigan","11204",null,"47",null,"917",null,"165",null,"324",null,"93",null,"91",null,"47",null,"44",null,"36",null,"760",null,"128",null,"271",null,"70",null,"66",null,"56",null,"86",null,"54",null,"0500000US26053","Gogebic County, Michigan","26","053"],["Grand Traverse County, Michigan","70229",null,"213",null,"8045",null,"557",null,"3804",null,"436",null,"1220",null,"232",null,"603",null,"191",null,"8666",null,"586",null,"5057",null,"465",null,"678",null,"183",null,"431",null,"149",null,"0500000US26055","Grand Traverse County, Michigan","26","055"],["Gratiot County, Michigan","28638",null,"129",null,"1615",null,"188",null,"549",null,"125",null,"68",null,"41",null,"93",null,"39",null,"2067",null,"257",null,"787",null,"174",null,"44",null,"21",null,"134",null,"76",null,"0500000US26057","Gratiot County, Michigan","26","057"],["Hillsdale County, Michigan","31627",null,"63",null,"1665",null,"174",null,"689",null,"140",null,"141",null,"51",null,"355",null,"129",null,"2457",null,"237",null,"1221",null,"212",null,"73",null,"28",null,"94",null,"46",null,"0500000US26059","Hillsdale County, Michigan","26","059"],["Houghton County, Michigan","21930",null,"112",null,"2222",null,"217",null,"1282",null,"192",null,"99",null,"24",null,"667",null,"224",null,"2306",null,"256",null,"1071",null,"207",null,"157",null,"64",null,"363",null,"126",null,"0500000US26061","Houghton County, Michigan","26","061"],["Huron County, Michigan","23141",null,"56",null,"1131",null,"116",null,"418",null,"83",null,"140",null,"41",null,"33",null,"24",null,"1764",null,"172",null,"588",null,"89",null,"141",null,"61",null,"71",null,"36",null,"0500000US26063","Huron County, Michigan","26","063"],["Ingham County, Michigan","176028",null,"79",null,"19476",null,"1037",null,"7437",null,"620",null,"2999",null,"441",null,"4028",null,"440",null,"21219",null,"953",null,"12068",null,"722",null,"2409",null,"403",null,"2886",null,"366",null,"0500000US26065","Ingham County, Michigan","26","065"],["Ionia County, Michigan","46711",null,"291",null,"2962",null,"346",null,"816",null,"195",null,"176",null,"72",null,"46",null,"28",null,"3228",null,"315",null,"1212",null,"250",null,"90",null,"40",null,"129",null,"61",null,"0500000US26067","Ionia County, Michigan","26","067"],["Iosco County, Michigan","19566",null,"83",null,"1160",null,"134",null,"437",null,"107",null,"86",null,"38",null,"73",null,"43",null,"1121",null,"168",null,"709",null,"145",null,"153",null,"64",null,"46",null,"33",null,"0500000US26069","Iosco County, Michigan","26","069"],["Iron County, Michigan","9009",null,"62",null,"701",null,"94",null,"255",null,"68",null,"39",null,"17",null,"48",null,"24",null,"680",null,"87",null,"254",null,"61",null,"40",null,"23",null,"11",null,"9",null,"0500000US26071","Iron County, Michigan","26","071"],["Isabella County, Michigan","36489",null,"154",null,"3030",null,"241",null,"1196",null,"186",null,"317",null,"100",null,"705",null,"185",null,"3457",null,"291",null,"2227",null,"243",null,"227",null,"66",null,"268",null,"77",null,"0500000US26073","Isabella County, Michigan","26","073"],["Jackson County, Michigan","112708",null,"91",null,"8132",null,"606",null,"3025",null,"463",null,"486",null,"138",null,"394",null,"111",null,"9406",null,"693",null,"4037",null,"425",null,"514",null,"157",null,"387",null,"192",null,"0500000US26075","Jackson County, Michigan","26","075"],["Kalamazoo County, Michigan","167183",null,"51",null,"20340",null,"947",null,"7815",null,"588",null,"2367",null,"404",null,"2027",null,"354",null,"21235",null,"1045",null,"12018",null,"735",null,"1522",null,"351",null,"1673",null,"305",null,"0500000US26077","Kalamazoo County, Michigan","26","077"],["Kalkaska County, Michigan","13436",null,"69",null,"633",null,"120",null,"282",null,"79",null,"104",null,"53",null,"76",null,"42",null,"996",null,"154",null,"388",null,"88",null,"45",null,"32",null,"27",null,"14",null,"0500000US26079","Kalkaska County, Michigan","26","079"],["Kent County, Michigan","445698",null,"67",null,"57074",null,"1665",null,"21466",null,"1360",null,"5446",null,"580",null,"3968",null,"397",null,"59371",null,"1756",null,"26522",null,"1339",null,"4747",null,"549",null,"3261",null,"440",null,"0500000US26081","Kent County, Michigan","26","081"],["Keweenaw County, Michigan","1760",null,"54",null,"159",null,"39",null,"95",null,"31",null,"33",null,"15",null,"40",null,"14",null,"247",null,"45",null,"108",null,"25",null,"11",null,"6",null,"32",null,"18",null,"0500000US26083","Keweenaw County, Michigan","26","083"],["Lake County, Michigan","9685",null,"142",null,"367",null,"57",null,"117",null,"43",null,"24",null,"13",null,"27",null,"14",null,"519",null,"102",null,"146",null,"39",null,"13",null,"10",null,"20",null,"19",null,"0500000US26085","Lake County, Michigan","26","085"],["Lapeer County, Michigan","64531",null,"140",null,"3699",null,"371",null,"1330",null,"245",null,"359",null,"104",null,"137",null,"51",null,"5262",null,"502",null,"2005",null,"338",null,"259",null,"88",null,"139",null,"75",null,"0500000US26087","Lapeer County, Michigan","26","087"],["Leelanau County, Michigan","18008",null,"77",null,"2667",null,"237",null,"1328",null,"167",null,"364",null,"84",null,"187",null,"58",null,"2805",null,"205",null,"1539",null,"169",null,"327",null,"123",null,"120",null,"53",null,"0500000US26089","Leelanau County, Michigan","26","089"],["Lenawee County, Michigan","69411",null,"137",null,"4332",null,"418",null,"1694",null,"272",null,"301",null,"103",null,"308",null,"130",null,"5942",null,"463",null,"2761",null,"307",null,"237",null,"75",null,"160",null,"67",null,"0500000US26091","Lenawee County, Michigan","26","091"],["Livingston County, Michigan","141138",null,"135",null,"18552",null,"736",null,"7101",null,"561",null,"1166",null,"209",null,"700",null,"142",null,"19010",null,"772",null,"8249",null,"491",null,"1173",null,"173",null,"731",null,"146",null,"0500000US26093","Livingston County, Michigan","26","093"],["Luce County, Michigan","4716",null,"82",null,"258",null,"66",null,"85",null,"39",null,"58",null,"56",null,"24",null,"42",null,"180",null,"42",null,"204",null,"67",null,"9",null,"9",null,"0",null,"16",null,"0500000US26095","Luce County, Michigan","26","095"],["Mackinac County, Michigan","8686",null,"83",null,"713",null,"122",null,"240",null,"58",null,"90",null,"33",null,"62",null,"34",null,"653",null,"94",null,"316",null,"63",null,"60",null,"32",null,"38",null,"32",null,"0500000US26097","Mackinac County, Michigan","26","097"],["Macomb County, Michigan","629501",null,"118",null,"53347",null,"1795",null,"20940",null,"1092",null,"4581",null,"476",null,"2003",null,"381",null,"60427",null,"1732",null,"26677",null,"1258",null,"4395",null,"707",null,"2134",null,"338",null,"0500000US26099","Macomb County, Michigan","26","099"],["Manistee County, Michigan","19477",null,"74",null,"1590",null,"177",null,"577",null,"108",null,"169",null,"57",null,"69",null,"50",null,"1645",null,"195",null,"654",null,"111",null,"90",null,"38",null,"60",null,"36",null,"0500000US26101","Manistee County, Michigan","26","101"],["Marquette County, Michigan","44729",null,"139",null,"5208",null,"381",null,"1596",null,"250",null,"498",null,"136",null,"413",null,"110",null,"5826",null,"448",null,"2258",null,"280",null,"296",null,"90",null,"367",null,"101",null,"0500000US26103","Marquette County, Michigan","26","103"],["Mason County, Michigan","21582",null,"56",null,"1608",null,"169",null,"752",null,"135",null,"170",null,"58",null,"69",null,"33",null,"1919",null,"206",null,"948",null,"184",null,"181",null,"84",null,"102",null,"43",null,"0500000US26105","Mason County, Michigan","26","105"],["Mecosta County, Michigan","27114",null,"122",null,"1775",null,"170",null,"645",null,"122",null,"142",null,"61",null,"203",null,"58",null,"2226",null,"228",null,"814",null,"138",null,"159",null,"66",null,"124",null,"50",null,"0500000US26107","Mecosta County, Michigan","26","107"],["Menominee County, Michigan","17491",null,"76",null,"1122",null,"150",null,"345",null,"85",null,"44",null,"25",null,"27",null,"21",null,"1132",null,"184",null,"427",null,"113",null,"61",null,"40",null,"80",null,"71",null,"0500000US26109","Menominee County, Michigan","26","109"],["Midland County, Michigan","59935",null,"134",null,"6220",null,"444",null,"2737",null,"297",null,"502",null,"151",null,"950",null,"181",null,"6897",null,"409",null,"3629",null,"355",null,"504",null,"171",null,"469",null,"131",null,"0500000US26111","Midland County, Michigan","26","111"],["Missaukee County, Michigan","10855",null,"40",null,"603",null,"93",null,"180",null,"60",null,"57",null,"41",null,"23",null,"14",null,"783",null,"100",null,"349",null,"82",null,"3",null,"5",null,"39",null,"29",null,"0500000US26113","Missaukee County, Michigan","26","113"],["Monroe County, Michigan","111168",null,"185",null,"7598",null,"563",null,"3287",null,"456",null,"715",null,"205",null,"443",null,"198",null,"8780",null,"673",null,"4228",null,"420",null,"453",null,"169",null,"348",null,"131",null,"0500000US26115","Monroe County, Michigan","26","115"],["Montcalm County, Michigan","48259",null,"316",null,"2287",null,"276",null,"735",null,"156",null,"209",null,"87",null,"80",null,"62",null,"2999",null,"350",null,"1251",null,"200",null,"119",null,"71",null,"52",null,"30",null,"0500000US26117","Montcalm County, Michigan","26","117"],["Montmorency County, Michigan","7610",null,"56",null,"379",null,"81",null,"149",null,"50",null,"26",null,"18",null,"9",null,"8",null,"396",null,"96",null,"182",null,"64",null,"18",null,"20",null,"25",null,"16",null,"0500000US26119","Montmorency County, Michigan","26","119"],["Muskegon County, Michigan","122392",null,"67",null,"8380",null,"573",null,"2431",null,"310",null,"718",null,"154",null,"319",null,"131",null,"9881",null,"738",null,"3658",null,"382",null,"424",null,"113",null,"315",null,"103",null,"0500000US26121","Muskegon County, Michigan","26","121"],["Newaygo County, Michigan","36099",null,"60",null,"2031",null,"254",null,"737",null,"166",null,"236",null,"100",null,"74",null,"35",null,"2421",null,"232",null,"894",null,"140",null,"68",null,"38",null,"82",null,"43",null,"0500000US26123","Newaygo County, Michigan","26","123"],["Oakland County, Michigan","920268",null,"31",null,"129067",null,"2742",null,"72504",null,"1776",null,"20518",null,"907",null,"10300",null,"810",null,"136119",null,"2431",null,"79143",null,"2046",null,"14649",null,"867",null,"7418",null,"692",null,"0500000US26125","Oakland County, Michigan","26","125"],["Oceana County, Michigan","19212",null,"73",null,"1159",null,"148",null,"404",null,"58",null,"118",null,"44",null,"91",null,"37",null,"1472",null,"147",null,"696",null,"116",null,"33",null,"16",null,"158",null,"70",null,"0500000US26127","Oceana County, Michigan","26","127"],["Ogemaw County, Michigan","15905",null,"44",null,"751",null,"125",null,"242",null,"56",null,"63",null,"35",null,"40",null,"27",null,"1052",null,"133",null,"434",null,"78",null,"120",null,"58",null,"34",null,"24",null,"0500000US26129","Ogemaw County, Michigan","26","129"],["Ontonagon County, Michigan","4895",null,"40",null,"344",null,"57",null,"99",null,"29",null,"33",null,"18",null,"4",null,"6",null,"241",null,"50",null,"170",null,"48",null,"8",null,"6",null,"4",null,"7",null,"0500000US26131","Ontonagon County, Michigan","26","131"],["Osceola County, Michigan","16476",null,"56",null,"777",null,"97",null,"335",null,"76",null,"73",null,"27",null,"43",null,"22",null,"1061",null,"120",null,"378",null,"61",null,"24",null,"15",null,"44",null,"28",null,"0500000US26133","Osceola County, Michigan","26","133"],["Oscoda County, Michigan","6331",null,"14",null,"317",null,"68",null,"100",null,"56",null,"18",null,"13",null,"6",null,"9",null,"396",null,"95",null,"140",null,"57",null,"10",null,"16",null,"3",null,"4",null,"0500000US26135","Oscoda County, Michigan","26","135"],["Otsego County, Michigan","18471",null,"86",null,"1569",null,"231",null,"574",null,"123",null,"150",null,"75",null,"60",null,"50",null,"1515",null,"220",null,"870",null,"180",null,"82",null,"49",null,"45",null,"27",null,"0500000US26137","Otsego County, Michigan","26","137"],["Ottawa County, Michigan","192975",null,"68",null,"24521",null,"1085",null,"9081",null,"678",null,"1892",null,"397",null,"2062",null,"459",null,"24370",null,"1019",null,"11024",null,"765",null,"1127",null,"237",null,"1007",null,"238",null,"0500000US26139","Ottawa County, Michigan","26","139"],["Presque Isle County, Michigan","10492",null,"46",null,"714",null,"109",null,"216",null,"68",null,"64",null,"25",null,"26",null,"13",null,"834",null,"122",null,"339",null,"83",null,"42",null,"25",null,"15",null,"10",null,"0500000US26141","Presque Isle County, Michigan","26","141"],["Roscommon County, Michigan","19141",null,"70",null,"1163",null,"164",null,"382",null,"98",null,"246",null,"123",null,"58",null,"35",null,"1147",null,"177",null,"686",null,"146",null,"188",null,"111",null,"0",null,"21",null,"0500000US26143","Roscommon County, Michigan","26","143"],["Saginaw County, Michigan","131086",null,"74",null,"9621",null,"583",null,"3768",null,"414",null,"1001",null,"211",null,"417",null,"127",null,"10854",null,"690",null,"5229",null,"513",null,"665",null,"181",null,"427",null,"142",null,"0500000US26145","Saginaw County, Michigan","26","145"],["St. Clair County, Michigan","115640",null,"52",null,"6540",null,"494",null,"2739",null,"387",null,"698",null,"173",null,"282",null,"98",null,"8291",null,"663",null,"4294",null,"433",null,"453",null,"123",null,"191",null,"83",null,"0500000US26147","St. Clair County, Michigan","26","147"],["St. Joseph County, Michigan","41475",null,"58",null,"2255",null,"293",null,"886",null,"206",null,"180",null,"68",null,"57",null,"40",null,"2239",null,"248",null,"1126",null,"177",null,"212",null,"89",null,"66",null,"32",null,"0500000US26149","St. Joseph County, Michigan","26","149"],["Sanilac County, Michigan","29043",null,"57",null,"1389",null,"201",null,"511",null,"117",null,"175",null,"62",null,"42",null,"22",null,"1583",null,"165",null,"873",null,"142",null,"152",null,"90",null,"59",null,"42",null,"0500000US26151","Sanilac County, Michigan","26","151"],["Schoolcraft County, Michigan","6220",null,"47",null,"363",null,"89",null,"116",null,"43",null,"55",null,"29",null,"11",null,"8",null,"512",null,"95",null,"198",null,"53",null,"44",null,"24",null,"13",null,"10",null,"0500000US26153","Schoolcraft County, Michigan","26","153"],["Shiawassee County, Michigan","48650",null,"95",null,"3101",null,"334",null,"869",null,"173",null,"186",null,"99",null,"245",null,"122",null,"3622",null,"335",null,"1519",null,"209",null,"74",null,"33",null,"113",null,"50",null,"0500000US26155","Shiawassee County, Michigan","26","155"],["Tuscola County, Michigan","38632",null,"92",null,"1975",null,"245",null,"603",null,"137",null,"192",null,"60",null,"65",null,"27",null,"2046",null,"204",null,"1177",null,"142",null,"184",null,"111",null,"67",null,"34",null,"0500000US26157","Tuscola County, Michigan","26","157"],["Van Buren County, Michigan","52835",null,"105",null,"3705",null,"364",null,"1329",null,"237",null,"353",null,"122",null,"253",null,"91",null,"3989",null,"351",null,"2031",null,"257",null,"306",null,"119",null,"183",null,"73",null,"0500000US26159","Van Buren County, Michigan","26","159"],["Washtenaw County, Michigan","237324",null,"52",null,"30858",null,"1067",null,"21532",null,"951",null,"6558",null,"534",null,"8951",null,"677",null,"33816",null,"1217",null,"25436",null,"982",null,"5768",null,"567",null,"6422",null,"514",null,"0500000US26161","Washtenaw County, Michigan","26","161"],["Wayne County, Michigan","1206499",null,"64",null,"92496",null,"2488",null,"41338",null,"1524",null,"10359",null,"834",null,"7108",null,"682",null,"109421",null,"2206",null,"57276",null,"1545",null,"9774",null,"788",null,"5012",null,"508",null,"0500000US26163","Wayne County, Michigan","26","163"],["Wexford County, Michigan","23924",null,"68",null,"1431",null,"199",null,"589",null,"121",null,"135",null,"55",null,"84",null,"47",null,"1861",null,"195",null,"784",null,"127",null,"137",null,"66",null,"77",null,"51",null,"0500000US26165","Wexford County, Michigan","26","165"]]}} diff --git a/tests/fixtures/mi_ny_manifest/frozen_api_responses_36.json b/tests/fixtures/mi_ny_manifest/frozen_api_responses_36.json new file mode 100644 index 0000000..bef9178 --- /dev/null +++ b/tests/fixtures/mi_ny_manifest/frozen_api_responses_36.json @@ -0,0 +1 @@ +{"_about":"REAL Census Data API responses (2024 acs5, all counties of state 36, get=NAME,group(
)), recorded by `python -m tests.test_manifest_multistate --record` for examples/manifests/mi_ny_income_education.json; columns trimmed to the manifest's variables (estimate, MOE and annotations) plus GEO_ID, NAME, state, county.","request":{"year":2024,"dataset":"acs5","geo_level":"county","state_code":"36","county_code":"*"},"requests":{"B19013":[["NAME","B19013_001E","B19013_001EA","B19013_001M","B19013_001MA","GEO_ID","NAME","state","county"],["Albany County, New York","85333",null,"2280",null,"0500000US36001","Albany County, New York","36","001"],["Allegany County, New York","62869",null,"2132",null,"0500000US36003","Allegany County, New York","36","003"],["Bronx County, New York","48676",null,"895",null,"0500000US36005","Bronx County, New York","36","005"],["Broome County, New York","62616",null,"1920",null,"0500000US36007","Broome County, New York","36","007"],["Cattaraugus County, New York","59540",null,"2028",null,"0500000US36009","Cattaraugus County, New York","36","009"],["Cayuga County, New York","67904",null,"2566",null,"0500000US36011","Cayuga County, New York","36","011"],["Chautauqua County, New York","58351",null,"1736",null,"0500000US36013","Chautauqua County, New York","36","013"],["Chemung County, New York","63716",null,"2911",null,"0500000US36015","Chemung County, New York","36","015"],["Chenango County, New York","62948",null,"2655",null,"0500000US36017","Chenango County, New York","36","017"],["Clinton County, New York","71224",null,"2611",null,"0500000US36019","Clinton County, New York","36","019"],["Columbia County, New York","81528",null,"5033",null,"0500000US36021","Columbia County, New York","36","021"],["Cortland County, New York","70418",null,"2912",null,"0500000US36023","Cortland County, New York","36","023"],["Delaware County, New York","63337",null,"3063",null,"0500000US36025","Delaware County, New York","36","025"],["Dutchess County, New York","99478",null,"2151",null,"0500000US36027","Dutchess County, New York","36","027"],["Erie County, New York","72839",null,"1077",null,"0500000US36029","Erie County, New York","36","029"],["Essex County, New York","71661",null,"4292",null,"0500000US36031","Essex County, New York","36","031"],["Franklin County, New York","65151",null,"3697",null,"0500000US36033","Franklin County, New York","36","033"],["Fulton County, New York","66533",null,"2501",null,"0500000US36035","Fulton County, New York","36","035"],["Genesee County, New York","73314",null,"2536",null,"0500000US36037","Genesee County, New York","36","037"],["Greene County, New York","77945",null,"5126",null,"0500000US36039","Greene County, New York","36","039"],["Hamilton County, New York","68835",null,"5478",null,"0500000US36041","Hamilton County, New York","36","041"],["Herkimer County, New York","68515",null,"3647",null,"0500000US36043","Herkimer County, New York","36","043"],["Jefferson County, New York","66301",null,"2730",null,"0500000US36045","Jefferson County, New York","36","045"],["Kings County, New York","80263",null,"946",null,"0500000US36047","Kings County, New York","36","047"],["Lewis County, New York","68182",null,"3903",null,"0500000US36049","Lewis County, New York","36","049"],["Livingston County, New York","74001",null,"2171",null,"0500000US36051","Livingston County, New York","36","051"],["Madison County, New York","75499",null,"3230",null,"0500000US36053","Madison County, New York","36","053"],["Monroe County, New York","76382",null,"1188",null,"0500000US36055","Monroe County, New York","36","055"],["Montgomery County, New York","64943",null,"3486",null,"0500000US36057","Montgomery County, New York","36","057"],["Nassau County, New York","146202",null,"1472",null,"0500000US36059","Nassau County, New York","36","059"],["New York County, New York","103931",null,"1490",null,"0500000US36061","New York County, New York","36","061"],["Niagara County, New York","69633",null,"2124",null,"0500000US36063","Niagara County, New York","36","063"],["Oneida County, New York","70154",null,"2335",null,"0500000US36065","Oneida County, New York","36","065"],["Onondaga County, New York","76945",null,"1228",null,"0500000US36067","Onondaga County, New York","36","067"],["Ontario County, New York","82324",null,"3039",null,"0500000US36069","Ontario County, New York","36","069"],["Orange County, New York","97178",null,"2147",null,"0500000US36071","Orange County, New York","36","071"],["Orleans County, New York","65969",null,"3199",null,"0500000US36073","Orleans County, New York","36","073"],["Oswego County, New York","69183",null,"2355",null,"0500000US36075","Oswego County, New York","36","075"],["Otsego County, New York","68885",null,"3568",null,"0500000US36077","Otsego County, New York","36","077"],["Putnam County, New York","126257",null,"5160",null,"0500000US36079","Putnam County, New York","36","079"],["Queens County, New York","86136",null,"875",null,"0500000US36081","Queens County, New York","36","081"],["Rensselaer County, New York","87915",null,"2391",null,"0500000US36083","Rensselaer County, New York","36","083"],["Richmond County, New York","98333",null,"2070",null,"0500000US36085","Richmond County, New York","36","085"],["Rockland County, New York","109959",null,"2755",null,"0500000US36087","Rockland County, New York","36","087"],["St. Lawrence County, New York","62850",null,"2870",null,"0500000US36089","St. Lawrence County, New York","36","089"],["Saratoga County, New York","100787",null,"3028",null,"0500000US36091","Saratoga County, New York","36","091"],["Schenectady County, New York","79623",null,"2743",null,"0500000US36093","Schenectady County, New York","36","093"],["Schoharie County, New York","70133",null,"3990",null,"0500000US36095","Schoharie County, New York","36","095"],["Schuyler County, New York","67663",null,"4782",null,"0500000US36097","Schuyler County, New York","36","097"],["Seneca County, New York","68089",null,"2709",null,"0500000US36099","Seneca County, New York","36","099"],["Steuben County, New York","65887",null,"2505",null,"0500000US36101","Steuben County, New York","36","101"],["Suffolk County, New York","130686",null,"1707",null,"0500000US36103","Suffolk County, New York","36","103"],["Sullivan County, New York","72382",null,"5140",null,"0500000US36105","Sullivan County, New York","36","105"],["Tioga County, New York","72739",null,"3285",null,"0500000US36107","Tioga County, New York","36","107"],["Tompkins County, New York","74024",null,"4332",null,"0500000US36109","Tompkins County, New York","36","109"],["Ulster County, New York","86271",null,"2871",null,"0500000US36111","Ulster County, New York","36","111"],["Warren County, New York","78442",null,"3623",null,"0500000US36113","Warren County, New York","36","113"],["Washington County, New York","73495",null,"2497",null,"0500000US36115","Washington County, New York","36","115"],["Wayne County, New York","74506",null,"2240",null,"0500000US36117","Wayne County, New York","36","117"],["Westchester County, New York","118976",null,"2567",null,"0500000US36119","Westchester County, New York","36","119"],["Wyoming County, New York","69110",null,"3387",null,"0500000US36121","Wyoming County, New York","36","121"],["Yates County, New York","69701",null,"3930",null,"0500000US36123","Yates County, New York","36","123"]],"B15002":[["NAME","B15002_001E","B15002_001EA","B15002_001M","B15002_001MA","B15002_015E","B15002_015EA","B15002_015M","B15002_015MA","B15002_016E","B15002_016EA","B15002_016M","B15002_016MA","B15002_017E","B15002_017EA","B15002_017M","B15002_017MA","B15002_018E","B15002_018EA","B15002_018M","B15002_018MA","B15002_032E","B15002_032EA","B15002_032M","B15002_032MA","B15002_033E","B15002_033EA","B15002_033M","B15002_033MA","B15002_034E","B15002_034EA","B15002_034M","B15002_034MA","B15002_035E","B15002_035EA","B15002_035M","B15002_035MA","GEO_ID","NAME","state","county"],["Albany County, New York","214344",null,"66",null,"25631",null,"1132",null,"13020",null,"836",null,"4479",null,"472",null,"3450",null,"459",null,"25037",null,"1020",null,"20847",null,"1034",null,"4219",null,"562",null,"2819",null,"361",null,"0500000US36001","Albany County, New York","36","001"],["Allegany County, New York","29722",null,"140",null,"1797",null,"211",null,"1000",null,"142",null,"188",null,"67",null,"330",null,"75",null,"2037",null,"207",null,"1774",null,"183",null,"90",null,"33",null,"172",null,"51",null,"0500000US36003","Allegany County, New York","36","003"],["Bronx County, New York","930631",null,"155",null,"55414",null,"2180",null,"21937",null,"1303",null,"5141",null,"626",null,"2783",null,"462",null,"75563",null,"2327",null,"38410",null,"1742",null,"5606",null,"635",null,"3500",null,"559",null,"0500000US36005","Bronx County, New York","36","005"],["Broome County, New York","130653",null,"41",null,"10735",null,"681",null,"5686",null,"461",null,"1103",null,"218",null,"1626",null,"332",null,"11029",null,"720",null,"8374",null,"638",null,"1014",null,"200",null,"992",null,"232",null,"0500000US36007","Broome County, New York","36","007"],["Cattaraugus County, New York","52793",null,"106",null,"2908",null,"319",null,"1510",null,"200",null,"281",null,"69",null,"278",null,"91",null,"3095",null,"231",null,"2779",null,"273",null,"156",null,"45",null,"147",null,"66",null,"0500000US36009","Cattaraugus County, New York","36","009"],["Cayuga County, New York","54962",null,"155",null,"3503",null,"312",null,"1603",null,"261",null,"282",null,"103",null,"334",null,"139",null,"3838",null,"353",null,"3390",null,"330",null,"314",null,"125",null,"265",null,"82",null,"0500000US36011","Cayuga County, New York","36","011"],["Chautauqua County, New York","88242",null,"146",null,"5642",null,"471",null,"2417",null,"280",null,"602",null,"159",null,"498",null,"130",null,"6551",null,"582",null,"5523",null,"566",null,"379",null,"131",null,"555",null,"199",null,"0500000US36013","Chautauqua County, New York","36","013"],["Chemung County, New York","58412",null,"180",null,"4529",null,"405",null,"1898",null,"325",null,"426",null,"138",null,"371",null,"136",null,"4336",null,"350",null,"3802",null,"362",null,"305",null,"108",null,"234",null,"84",null,"0500000US36015","Chemung County, New York","36","015"],["Chenango County, New York","33596",null,"75",null,"1541",null,"232",null,"882",null,"179",null,"203",null,"68",null,"94",null,"48",null,"2129",null,"262",null,"1359",null,"185",null,"229",null,"90",null,"134",null,"82",null,"0500000US36017","Chenango County, New York","36","017"],["Clinton County, New York","54834",null,"251",null,"4057",null,"378",null,"1464",null,"221",null,"402",null,"122",null,"442",null,"136",null,"4625",null,"424",null,"3567",null,"398",null,"259",null,"95",null,"293",null,"105",null,"0500000US36019","Clinton County, New York","36","019"],["Columbia County, New York","47481",null,"144",null,"4402",null,"383",null,"2903",null,"356",null,"1150",null,"251",null,"491",null,"145",null,"4671",null,"407",null,"3927",null,"425",null,"727",null,"142",null,"423",null,"107",null,"0500000US36021","Columbia County, New York","36","021"],["Cortland County, New York","29317",null,"143",null,"2022",null,"291",null,"1222",null,"204",null,"209",null,"117",null,"330",null,"136",null,"2473",null,"262",null,"1856",null,"261",null,"210",null,"95",null,"220",null,"77",null,"0500000US36023","Cortland County, New York","36","023"],["Delaware County, New York","33206",null,"105",null,"2437",null,"251",null,"1111",null,"182",null,"248",null,"73",null,"237",null,"93",null,"2670",null,"280",null,"1978",null,"232",null,"216",null,"73",null,"265",null,"108",null,"0500000US36025","Delaware County, New York","36","025"],["Dutchess County, New York","212816",null,"44",null,"23187",null,"875",null,"12128",null,"773",null,"3036",null,"375",null,"2307",null,"371",null,"24231",null,"1026",null,"18986",null,"1031",null,"2582",null,"314",null,"2111",null,"418",null,"0500000US36027","Dutchess County, New York","36","027"],["Erie County, New York","674034",null,"46",null,"70770",null,"2053",null,"33405",null,"1341",null,"8907",null,"741",null,"7082",null,"703",null,"70496",null,"1863",null,"51808",null,"1684",null,"7685",null,"653",null,"6296",null,"754",null,"0500000US36029","Erie County, New York","36","029"],["Essex County, New York","29098",null,"80",null,"2379",null,"297",null,"1145",null,"178",null,"420",null,"140",null,"247",null,"109",null,"2889",null,"350",null,"1860",null,"259",null,"427",null,"145",null,"176",null,"77",null,"0500000US36031","Essex County, New York","36","031"],["Franklin County, New York","33658",null,"160",null,"1921",null,"298",null,"1151",null,"266",null,"200",null,"82",null,"239",null,"103",null,"2251",null,"222",null,"1896",null,"256",null,"222",null,"95",null,"133",null,"82",null,"0500000US36033","Franklin County, New York","36","033"],["Fulton County, New York","38073",null,"95",null,"2564",null,"351",null,"819",null,"192",null,"136",null,"76",null,"104",null,"61",null,"2449",null,"266",null,"1641",null,"286",null,"90",null,"53",null,"157",null,"88",null,"0500000US36035","Fulton County, New York","36","035"],["Genesee County, New York","41686",null,"153",null,"2778",null,"291",null,"1133",null,"206",null,"290",null,"137",null,"57",null,"31",null,"2806",null,"309",null,"2550",null,"280",null,"237",null,"99",null,"80",null,"65",null,"0500000US36037","Genesee County, New York","36","037"],["Greene County, New York","36522",null,"195",null,"3035",null,"337",null,"1357",null,"250",null,"231",null,"72",null,"171",null,"67",null,"3540",null,"377",null,"2012",null,"322",null,"401",null,"185",null,"189",null,"100",null,"0500000US36039","Greene County, New York","36","039"],["Hamilton County, New York","4184",null,"30",null,"279",null,"52",null,"191",null,"52",null,"34",null,"22",null,"48",null,"40",null,"447",null,"69",null,"336",null,"83",null,"30",null,"24",null,"25",null,"13",null,"0500000US36041","Hamilton County, New York","36","041"],["Herkimer County, New York","42910",null,"81",null,"2952",null,"370",null,"1132",null,"226",null,"141",null,"55",null,"239",null,"99",null,"2722",null,"364",null,"2201",null,"290",null,"175",null,"71",null,"171",null,"106",null,"0500000US36043","Herkimer County, New York","36","043"],["Jefferson County, New York","74083",null,"227",null,"5537",null,"557",null,"2381",null,"273",null,"675",null,"179",null,"315",null,"114",null,"5838",null,"518",null,"4151",null,"389",null,"446",null,"124",null,"254",null,"82",null,"0500000US36045","Jefferson County, New York","36","045"],["Kings County, New York","1846499",null,"213",null,"217411",null,"3224",null,"94657",null,"2367",null,"26653",null,"1386",null,"12492",null,"921",null,"244061",null,"3352",null,"143337",null,"2819",null,"27074",null,"1477",null,"11831",null,"824",null,"0500000US36047","Kings County, New York","36","047"],["Lewis County, New York","18594",null,"53",null,"888",null,"155",null,"391",null,"98",null,"80",null,"46",null,"57",null,"40",null,"1057",null,"160",null,"839",null,"116",null,"85",null,"39",null,"53",null,"31",null,"0500000US36049","Lewis County, New York","36","049"],["Livingston County, New York","41720",null,"173",null,"3187",null,"339",null,"1571",null,"239",null,"396",null,"141",null,"421",null,"141",null,"3749",null,"293",null,"2721",null,"262",null,"368",null,"135",null,"334",null,"156",null,"0500000US36051","Livingston County, New York","36","051"],["Madison County, New York","46025",null,"160",null,"3377",null,"317",null,"1938",null,"197",null,"461",null,"113",null,"567",null,"115",null,"3810",null,"297",null,"2758",null,"252",null,"373",null,"73",null,"357",null,"89",null,"0500000US36053","Madison County, New York","36","053"],["Monroe County, New York","527287",null,"100",null,"60114",null,"1634",null,"28933",null,"1237",null,"8265",null,"811",null,"6533",null,"563",null,"60637",null,"1882",null,"42940",null,"1470",null,"6279",null,"621",null,"4939",null,"434",null,"0500000US36055","Monroe County, New York","36","055"],["Montgomery County, New York","34301",null,"57",null,"2321",null,"319",null,"772",null,"180",null,"126",null,"64",null,"130",null,"51",null,"2427",null,"302",null,"1733",null,"243",null,"193",null,"78",null,"161",null,"74",null,"0500000US36057","Montgomery County, New York","36","057"],["Nassau County, New York","975442",null,"116",null,"132889",null,"2526",null,"58983",null,"1996",null,"28165",null,"1258",null,"9331",null,"681",null,"128846",null,"2850",null,"96246",null,"2284",null,"20725",null,"1177",null,"9369",null,"858",null,"0500000US36059","Nassau County, New York","36","059"],["New York County, New York","1261727",null,"173",null,"198230",null,"4105",null,"111688",null,"3715",null,"48520",null,"2323",null,"25939",null,"1745",null,"216267",null,"3786",null,"140913",null,"3396",null,"43994",null,"2258",null,"24351",null,"1838",null,"0500000US36061","New York County, New York","36","061"],["Niagara County, New York","152398",null,"116",null,"12138",null,"774",null,"5296",null,"551",null,"998",null,"222",null,"662",null,"200",null,"12667",null,"844",null,"9089",null,"636",null,"951",null,"205",null,"804",null,"255",null,"0500000US36063","Niagara County, New York","36","063"],["Oneida County, New York","159063",null,"67",null,"12234",null,"646",null,"5974",null,"582",null,"1495",null,"231",null,"1179",null,"230",null,"12773",null,"787",null,"8451",null,"655",null,"1192",null,"229",null,"996",null,"255",null,"0500000US36065","Oneida County, New York","36","065"],["Onondaga County, New York","325131",null,"24",null,"34512",null,"1237",null,"16399",null,"931",null,"4455",null,"422",null,"3488",null,"382",null,"35842",null,"1319",null,"25727",null,"1100",null,"4121",null,"426",null,"2960",null,"348",null,"0500000US36067","Onondaga County, New York","36","067"],["Ontario County, New York","81153",null,"158",null,"8565",null,"553",null,"4111",null,"401",null,"908",null,"171",null,"816",null,"207",null,"8342",null,"464",null,"6340",null,"493",null,"728",null,"170",null,"1005",null,"296",null,"0500000US36069","Ontario County, New York","36","069"],["Orange County, New York","260987",null,"33",null,"23199",null,"1143",null,"10426",null,"844",null,"2274",null,"340",null,"1814",null,"306",null,"25381",null,"1161",null,"17975",null,"859",null,"2269",null,"381",null,"1605",null,"328",null,"0500000US36071","Orange County, New York","36","071"],["Orleans County, New York","29318",null,"242",null,"1455",null,"230",null,"581",null,"126",null,"97",null,"55",null,"117",null,"56",null,"1564",null,"253",null,"1228",null,"184",null,"127",null,"60",null,"86",null,"42",null,"0500000US36073","Orleans County, New York","36","073"],["Oswego County, New York","80607",null,"137",null,"4965",null,"365",null,"1487",null,"216",null,"671",null,"284",null,"338",null,"116",null,"5782",null,"441",null,"3550",null,"375",null,"365",null,"160",null,"284",null,"102",null,"0500000US36075","Oswego County, New York","36","075"],["Otsego County, New York","39271",null,"175",null,"3330",null,"374",null,"1546",null,"241",null,"392",null,"97",null,"470",null,"150",null,"4089",null,"332",null,"2872",null,"330",null,"528",null,"156",null,"277",null,"83",null,"0500000US36077","Otsego County, New York","36","077"],["Putnam County, New York","71435",null,"164",null,"8731",null,"553",null,"4787",null,"483",null,"819",null,"173",null,"547",null,"162",null,"9184",null,"751",null,"7023",null,"466",null,"917",null,"183",null,"490",null,"158",null,"0500000US36079","Putnam County, New York","36","079"],["Queens County, New York","1698409",null,"273",null,"175708",null,"3471",null,"71843",null,"2189",null,"20640",null,"1156",null,"12296",null,"1141",null,"199746",null,"3435",null,"106154",null,"2999",null,"17903",null,"1260",null,"9435",null,"786",null,"0500000US36081","Queens County, New York","36","081"],["Rensselaer County, New York","113756",null,"55",null,"12738",null,"891",null,"5276",null,"614",null,"1038",null,"208",null,"1210",null,"229",null,"12376",null,"780",null,"8243",null,"636",null,"983",null,"268",null,"856",null,"186",null,"0500000US36083","Rensselaer County, New York","36","083"],["Richmond County, New York","347481",null,"55",null,"36541",null,"1735",null,"13665",null,"974",null,"3624",null,"412",null,"1957",null,"313",null,"38597",null,"1464",null,"23855",null,"1370",null,"3945",null,"522",null,"1880",null,"292",null,"0500000US36085","Richmond County, New York","36","085"],["Rockland County, New York","209276",null,"27",null,"24454",null,"1184",null,"10837",null,"762",null,"4185",null,"496",null,"1699",null,"265",null,"24978",null,"935",null,"18256",null,"798",null,"3242",null,"430",null,"1728",null,"261",null,"0500000US36087","Rockland County, New York","36","087"],["St. Lawrence County, New York","71171",null,"237",null,"4365",null,"406",null,"2362",null,"285",null,"639",null,"147",null,"696",null,"172",null,"4896",null,"395",null,"4119",null,"424",null,"460",null,"134",null,"477",null,"129",null,"0500000US36089","St. Lawrence County, New York","36","089"],["Saratoga County, New York","174204",null,"82",null,"23122",null,"963",null,"10647",null,"788",null,"2445",null,"405",null,"2221",null,"424",null,"20333",null,"1011",null,"16527",null,"907",null,"1973",null,"372",null,"1807",null,"407",null,"0500000US36091","Saratoga County, New York","36","091"],["Schenectady County, New York","110701",null,"69",null,"10466",null,"701",null,"5493",null,"456",null,"1586",null,"300",null,"1378",null,"294",null,"10884",null,"766",null,"8518",null,"632",null,"973",null,"252",null,"668",null,"222",null,"0500000US36093","Schenectady County, New York","36","093"],["Schoharie County, New York","21699",null,"94",null,"1355",null,"195",null,"782",null,"171",null,"234",null,"95",null,"138",null,"60",null,"1626",null,"227",null,"1313",null,"204",null,"103",null,"44",null,"133",null,"43",null,"0500000US36095","Schoharie County, New York","36","095"],["Schuyler County, New York","13099",null,"48",null,"705",null,"114",null,"505",null,"123",null,"122",null,"71",null,"164",null,"79",null,"903",null,"155",null,"802",null,"156",null,"169",null,"83",null,"86",null,"62",null,"0500000US36097","Schuyler County, New York","36","097"],["Seneca County, New York","23742",null,"105",null,"1615",null,"238",null,"689",null,"117",null,"157",null,"78",null,"108",null,"58",null,"2125",null,"245",null,"995",null,"141",null,"215",null,"80",null,"89",null,"41",null,"0500000US36099","Seneca County, New York","36","099"],["Steuben County, New York","65996",null,"126",null,"4447",null,"359",null,"2333",null,"248",null,"588",null,"166",null,"690",null,"167",null,"4637",null,"347",null,"3831",null,"307",null,"480",null,"171",null,"379",null,"117",null,"0500000US36101","Steuben County, New York","36","101"],["Suffolk County, New York","1075649",null,"44",null,"116312",null,"2365",null,"56938",null,"1592",null,"19305",null,"1152",null,"10151",null,"789",null,"116426",null,"2625",null,"90114",null,"2258",null,"15547",null,"1021",null,"7807",null,"622",null,"0500000US36103","Suffolk County, New York","36","103"],["Sullivan County, New York","56129",null,"304",null,"4440",null,"488",null,"2197",null,"324",null,"632",null,"139",null,"407",null,"160",null,"5201",null,"470",null,"3251",null,"458",null,"442",null,"131",null,"327",null,"133",null,"0500000US36105","Sullivan County, New York","36","105"],["Tioga County, New York","34648",null,"110",null,"2600",null,"287",null,"1181",null,"247",null,"217",null,"84",null,"86",null,"51",null,"2685",null,"323",null,"1720",null,"236",null,"103",null,"49",null,"162",null,"129",null,"0500000US36107","Tioga County, New York","36","107"],["Tompkins County, New York","61195",null,"308",null,"6766",null,"491",null,"4816",null,"532",null,"1300",null,"308",null,"4106",null,"436",null,"7886",null,"527",null,"6927",null,"544",null,"1054",null,"208",null,"3103",null,"368",null,"0500000US36109","Tompkins County, New York","36","109"],["Ulster County, New York","134979",null,"96",null,"13476",null,"770",null,"6244",null,"638",null,"1767",null,"313",null,"1156",null,"268",null,"14505",null,"924",null,"11826",null,"681",null,"1521",null,"343",null,"1096",null,"239",null,"0500000US36111","Ulster County, New York","36","111"],["Warren County, New York","49635",null,"90",null,"4729",null,"406",null,"2194",null,"333",null,"1103",null,"253",null,"295",null,"103",null,"5116",null,"419",null,"4009",null,"437",null,"405",null,"124",null,"234",null,"88",null,"0500000US36113","Warren County, New York","36","113"],["Washington County, New York","45378",null,"106",null,"2975",null,"363",null,"916",null,"164",null,"428",null,"145",null,"213",null,"69",null,"3113",null,"374",null,"2082",null,"290",null,"282",null,"106",null,"143",null,"79",null,"0500000US36115","Washington County, New York","36","115"],["Wayne County, New York","65509",null,"166",null,"5095",null,"453",null,"1931",null,"292",null,"322",null,"106",null,"170",null,"94",null,"4986",null,"441",null,"3830",null,"405",null,"321",null,"125",null,"115",null,"71",null,"0500000US36117","Wayne County, New York","36","117"],["Westchester County, New York","701750",null,"117",null,"86903",null,"2135",null,"51705",null,"1851",null,"24041",null,"1219",null,"9761",null,"719",null,"98698",null,"2268",null,"75544",null,"2091",null,"17605",null,"1008",null,"8490",null,"711",null,"0500000US36119","Westchester County, New York","36","119"],["Wyoming County, New York","29707",null,"104",null,"1441",null,"191",null,"430",null,"98",null,"136",null,"70",null,"117",null,"56",null,"1710",null,"192",null,"1308",null,"207",null,"166",null,"77",null,"86",null,"90",null,"0500000US36121","Wyoming County, New York","36","121"],["Yates County, New York","16856",null,"75",null,"1150",null,"164",null,"461",null,"107",null,"203",null,"75",null,"88",null,"39",null,"1235",null,"172",null,"926",null,"144",null,"161",null,"59",null,"73",null,"38",null,"0500000US36123","Yates County, New York","36","123"]]}} diff --git a/tests/fixtures/validation/api_2014_acs_acs5_profile_7ea50200fdd2.json.gz b/tests/fixtures/validation/api_2014_acs_acs5_profile_7ea50200fdd2.json.gz deleted file mode 100644 index 1a5481e..0000000 Binary files a/tests/fixtures/validation/api_2014_acs_acs5_profile_7ea50200fdd2.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2014_acs_acs5_profile_7ea50200fdd2.json.gz.json b/tests/fixtures/validation/api_2014_acs_acs5_profile_7ea50200fdd2.json.gz.json deleted file mode 100644 index 5b3bf98..0000000 --- a/tests/fixtures/validation/api_2014_acs_acs5_profile_7ea50200fdd2.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "7ed86cf2192be839984c5be5048e61d6c817d6389a4ccab641e273d9f0c1e1ec", - "size_bytes": 9284, - "source_url": "https://api.census.gov/data/2014/acs/acs5/profile?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28DP03%29", - "uncompressed_sha256": "dea31456ca9392a71ec06a5d1324af32eb39f3e5216d15876e218638e88548af" -} diff --git a/tests/fixtures/validation/api_2014_acs_acs5_profile_9d2406ee4ced.json.gz b/tests/fixtures/validation/api_2014_acs_acs5_profile_9d2406ee4ced.json.gz new file mode 100644 index 0000000..cb2b619 Binary files /dev/null and b/tests/fixtures/validation/api_2014_acs_acs5_profile_9d2406ee4ced.json.gz differ diff --git a/tests/fixtures/validation/api_2014_acs_acs5_profile_9d2406ee4ced.json.gz.json b/tests/fixtures/validation/api_2014_acs_acs5_profile_9d2406ee4ced.json.gz.json new file mode 100644 index 0000000..6449214 --- /dev/null +++ b/tests/fixtures/validation/api_2014_acs_acs5_profile_9d2406ee4ced.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "c81653666e19f030691020a40f5301b29d384ce6f1e28294e50b52ed9b6ac9dd", + "size_bytes": 10524, + "source_url": "https://api.census.gov/data/2014/acs/acs5/profile?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28DP03%29", + "uncompressed_sha256": "40db811ae5c38f67c2bb1dbe1eaa0b0f90cabc59cca49fffe4b01d67687e400d" +} diff --git a/tests/fixtures/validation/api_2018_acs_acs5_profile_345896260046.json.gz b/tests/fixtures/validation/api_2018_acs_acs5_profile_345896260046.json.gz deleted file mode 100644 index 2614aeb..0000000 Binary files a/tests/fixtures/validation/api_2018_acs_acs5_profile_345896260046.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2018_acs_acs5_profile_345896260046.json.gz.json b/tests/fixtures/validation/api_2018_acs_acs5_profile_345896260046.json.gz.json deleted file mode 100644 index d430f36..0000000 --- a/tests/fixtures/validation/api_2018_acs_acs5_profile_345896260046.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "6bb2595cce0cf0eb7610173930d9862538ba48452d99726a639d9e787b300efe", - "size_bytes": 9087, - "source_url": "https://api.census.gov/data/2018/acs/acs5/profile?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28DP03%29", - "uncompressed_sha256": "38b1bb464b71254fdee23560e82dd7117ac71cfedde39a8c2b63fb192bd0951c" -} diff --git a/tests/fixtures/validation/api_2018_acs_acs5_profile_d4b279913788.json.gz b/tests/fixtures/validation/api_2018_acs_acs5_profile_d4b279913788.json.gz new file mode 100644 index 0000000..a048b79 Binary files /dev/null and b/tests/fixtures/validation/api_2018_acs_acs5_profile_d4b279913788.json.gz differ diff --git a/tests/fixtures/validation/api_2018_acs_acs5_profile_d4b279913788.json.gz.json b/tests/fixtures/validation/api_2018_acs_acs5_profile_d4b279913788.json.gz.json new file mode 100644 index 0000000..24e2c59 --- /dev/null +++ b/tests/fixtures/validation/api_2018_acs_acs5_profile_d4b279913788.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "f4649a8a16e696f815a27e35af63ae14f7e6f5603691079ede36cd4aad716c8c", + "size_bytes": 10329, + "source_url": "https://api.census.gov/data/2018/acs/acs5/profile?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28DP03%29", + "uncompressed_sha256": "8ac78a354a1ff22293129ed10d82a4ee606bdf2ee51859ec672d4115009456d3" +} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_04877cf897e4.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_04877cf897e4.json.gz index 8d3b5c2..3b7f04c 100644 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_04877cf897e4.json.gz and b/tests/fixtures/validation/api_2019_acs_acs5_04877cf897e4.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_04877cf897e4.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_04877cf897e4.json.gz.json index 7efc8d8..2dacf22 100644 --- a/tests/fixtures/validation/api_2019_acs_acs5_04877cf897e4.json.gz.json +++ b/tests/fixtures/validation/api_2019_acs_acs5_04877cf897e4.json.gz.json @@ -1,8 +1,8 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "79e3fd5fa3b239632f4f984d29f94d50fe00fe6fa2deafd5ca4e6842a1930121", + "retrieved": "2026-11-07", + "sha256": "98186f9902a01d500f6c2d288173c58146da6eb1ec664c391191e7f3758fbbe4", "size_bytes": 5540, "source_url": "https://api.census.gov/data/2019/acs/acs5?for=county%3A%2A&get=NAME%2Cgroup%28B01001%29&in=state%3A56", "uncompressed_sha256": "3bb8ab939382f1db1c760c52928ad6eec2fbb35d93076299d8e13ae1d7a16728" diff --git a/tests/fixtures/validation/api_2019_acs_acs5_0af368306180.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_0af368306180.json.gz new file mode 100644 index 0000000..4a57e70 Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_0af368306180.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_0af368306180.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_0af368306180.json.gz.json new file mode 100644 index 0000000..7d0a353 --- /dev/null +++ b/tests/fixtures/validation/api_2019_acs_acs5_0af368306180.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "c47fc3563fb6a8910f00b1b1690a47504ee76d0db1522862221ceaf10c1b49a1", + "size_bytes": 3333, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B27010%29", + "uncompressed_sha256": "bbf71e6f3618ff8411230ccb324c493231d0f8e7e37635f8eddc57325a327b74" +} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_0c2b46d2c90c.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_0c2b46d2c90c.json.gz deleted file mode 100644 index 1faaf58..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_0c2b46d2c90c.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_0c2b46d2c90c.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_0c2b46d2c90c.json.gz.json deleted file mode 100644 index d81e990..0000000 --- a/tests/fixtures/validation/api_2019_acs_acs5_0c2b46d2c90c.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "0284e87f342fa4603efe29c14a490d3211c9a2eb52438d8038ae8300dbe40483", - "size_bytes": 218, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B01003%29", - "uncompressed_sha256": "383108c9e09e0ab786fd72525dc675c26d97814d20f867b42cec0a9c0bee02c2" -} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_157bc77dcd45.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_157bc77dcd45.json.gz new file mode 100644 index 0000000..1cf9e48 Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_157bc77dcd45.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_157bc77dcd45.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_157bc77dcd45.json.gz.json new file mode 100644 index 0000000..100c3f9 --- /dev/null +++ b/tests/fixtures/validation/api_2019_acs_acs5_157bc77dcd45.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "98c5d9b44fd4e4ec45d465904d0cf63d8628e763f04fba5ff2f696c4efc12085", + "size_bytes": 217, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=B15003_024E%2CB15003_024M%2CB15003_025E%2CB15003_025M%2CB15003_001E%2CB15003_001M", + "uncompressed_sha256": "bd4012bfa448dd4e31940c50213947f9c2f4d3bf8b4b25b115514440b3ad6a04" +} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_21bd38a8650a.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_21bd38a8650a.json.gz index 0fb78d0..80ac817 100644 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_21bd38a8650a.json.gz and b/tests/fixtures/validation/api_2019_acs_acs5_21bd38a8650a.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_21bd38a8650a.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_21bd38a8650a.json.gz.json index 14df46d..04a426e 100644 --- a/tests/fixtures/validation/api_2019_acs_acs5_21bd38a8650a.json.gz.json +++ b/tests/fixtures/validation/api_2019_acs_acs5_21bd38a8650a.json.gz.json @@ -1,8 +1,8 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "a7c459c7c538eb53334bf5dce188f2d25895115d2014127b4426c04cec6493ec", + "retrieved": "2026-11-07", + "sha256": "1aa6d7c055a7fb8b0488e64895a168a97ca40d47bb0871b0c24f0197f13acda3", "size_bytes": 1205, "source_url": "https://api.census.gov/data/2019/acs/acs5?for=county%3A001%2C037&get=NAME%2Cgroup%28B01001%29&in=state%3A06", "uncompressed_sha256": "c34439beb1dcf995b97bbabc219df74cb8dd8afef2ae6804f14e8d132f5ef41a" diff --git a/tests/fixtures/validation/api_2019_acs_acs5_33f28cf8a208.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_33f28cf8a208.json.gz deleted file mode 100644 index f0ecd0f..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_33f28cf8a208.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_3920163b5dcf.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_3920163b5dcf.json.gz new file mode 100644 index 0000000..22c7350 Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_3920163b5dcf.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_7fcd8ab21138.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_3920163b5dcf.json.gz.json similarity index 61% rename from tests/fixtures/validation/api_2019_acs_acs5_7fcd8ab21138.json.gz.json rename to tests/fixtures/validation/api_2019_acs_acs5_3920163b5dcf.json.gz.json index 200ad1c..a3eb9a0 100644 --- a/tests/fixtures/validation/api_2019_acs_acs5_7fcd8ab21138.json.gz.json +++ b/tests/fixtures/validation/api_2019_acs_acs5_3920163b5dcf.json.gz.json @@ -1,9 +1,9 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "44b7721379476767facdf69e75dafbaf964bc3b64614c61d5ce4f876fed0f982", - "size_bytes": 7673, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=county%3A%2A&get=B15003_024E%2CB15003_024M%2CB15003_025E%2CB15003_025M%2CB15003_001E%2CB15003_001M&in=state%3A06%2C36%2C48%2C17%2C56", - "uncompressed_sha256": "b4943004eaf144bb4899b58d11a6218005c70f290134aef03c3c9a56fcb75c0e" + "retrieved": "2026-11-07", + "sha256": "c80d9850d45b609eb948c2d7a53f4d2a706bbe2d41136dcae9681a8b24028ae5", + "size_bytes": 8879, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=county%3A%2A&get=B15003_024E%2CB15003_024M%2CB15003_025E%2CB15003_025M%2CB15003_001E%2CB15003_001M&in=state%3A06%2C36%2C48%2C17%2C56%2C26", + "uncompressed_sha256": "08e6466ea3426f18fc68b6bea91c2bc3c719633905643de2b385336563e702f3" } diff --git a/tests/fixtures/validation/api_2019_acs_acs5_434fbaed0ab1.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_434fbaed0ab1.json.gz new file mode 100644 index 0000000..e754e81 Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_434fbaed0ab1.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_434fbaed0ab1.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_434fbaed0ab1.json.gz.json new file mode 100644 index 0000000..a6c7650 --- /dev/null +++ b/tests/fixtures/validation/api_2019_acs_acs5_434fbaed0ab1.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "b8206a3c630120199ddfcc49015127980dc615c06adb0e56fc259f438c9105a0", + "size_bytes": 3086, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B17001%29", + "uncompressed_sha256": "6921f941789f6306b07d77ee1d10f3aff7ab13c9ad8789133dabb3ce09033b25" +} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_468e7c5c2473.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_468e7c5c2473.json.gz new file mode 100644 index 0000000..4637e3f Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_468e7c5c2473.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_468e7c5c2473.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_468e7c5c2473.json.gz.json new file mode 100644 index 0000000..8ea107e --- /dev/null +++ b/tests/fixtures/validation/api_2019_acs_acs5_468e7c5c2473.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "97dfe6aa87e24f4758b855ce07f0535cd6980c6720748858638a3d48b738cdaa", + "size_bytes": 2631, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B01001%29", + "uncompressed_sha256": "b3ef924a97ea3c29878bb08fb92d0e8f2ced21174ba68d5b76b8705ff09033ed" +} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_53eb4a9b63b5.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_53eb4a9b63b5.json.gz index cae1c4e..6452245 100644 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_53eb4a9b63b5.json.gz and b/tests/fixtures/validation/api_2019_acs_acs5_53eb4a9b63b5.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_53eb4a9b63b5.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_53eb4a9b63b5.json.gz.json index eb16290..9c8bc6c 100644 --- a/tests/fixtures/validation/api_2019_acs_acs5_53eb4a9b63b5.json.gz.json +++ b/tests/fixtures/validation/api_2019_acs_acs5_53eb4a9b63b5.json.gz.json @@ -1,8 +1,8 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "e941b877a0ebf81b34f3b5b784e11d53d266aad959f1014fbab362e98a4e5af5", + "retrieved": "2026-11-07", + "sha256": "e964e00e59452f945c04e1ca411d97828d658aeaa7bbd736026a7b5bae2e7095", "size_bytes": 1115, "source_url": "https://api.census.gov/data/2019/acs/acs5?for=us%3A1&get=NAME%2Cgroup%28B17001%29", "uncompressed_sha256": "0b906e4f1d557451e72cc496418cc0cb14c2a5194e7a064246cc68609a9711ca" diff --git a/tests/fixtures/validation/api_2019_acs_acs5_5ae175ecc6e9.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_5ae175ecc6e9.json.gz deleted file mode 100644 index 58c5afe..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_5ae175ecc6e9.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_5ae175ecc6e9.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_5ae175ecc6e9.json.gz.json deleted file mode 100644 index bea123a..0000000 --- a/tests/fixtures/validation/api_2019_acs_acs5_5ae175ecc6e9.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "adccf9f5d36859bc29a1342f809976f55a8c6ff760d204f81c12ffb965b868a7", - "size_bytes": 1055, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2CB17001_002E%2CB17001_002M%2CB17001_001E%2CB17001_001M%2CB27010_017E%2CB27010_017M%2CB27010_033E%2CB27010_033M%2CB27010_050E%2CB27010_050M%2CB27010_066E%2CB27010_066M%2CB27010_001E%2CB27010_001M%2CB25003_002E%2CB25003_002M%2CB25003_001E%2CB25003_001M%2CB01001_020E%2CB01001_020M%2CB01001_021E%2CB01001_021M%2CB01001_022E%2CB01001_022M%2CB01001_023E%2CB01001_023M%2CB01001_024E%2CB01001_024M%2CB01001_025E%2CB01001_025M%2CB01001_044E%2CB01001_044M%2CB01001_045E%2CB01001_045M%2CB01001_046E%2CB01001_046M%2CB01001_047E%2CB01001_047M%2CB01001_048E%2CB01001_048M%2CB01001_049E%2CB01001_049M%2CB01001_001E%2CB01001_001M%2CB15003_022E%2CB15003_022M%2CB15003_023E%2CB15003_023M", - "uncompressed_sha256": "0e8ddafa780f6e9e42a7912405335d088b51b50bc94caba46bb4e0f988302d19" -} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_5c81b6067fcb.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_5c81b6067fcb.json.gz new file mode 100644 index 0000000..7cd92fe Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_5c81b6067fcb.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_60f8058dd4d4.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_5c81b6067fcb.json.gz.json similarity index 75% rename from tests/fixtures/validation/api_2019_acs_acs5_60f8058dd4d4.json.gz.json rename to tests/fixtures/validation/api_2019_acs_acs5_5c81b6067fcb.json.gz.json index 1f70ecc..f5d346d 100644 --- a/tests/fixtures/validation/api_2019_acs_acs5_60f8058dd4d4.json.gz.json +++ b/tests/fixtures/validation/api_2019_acs_acs5_5c81b6067fcb.json.gz.json @@ -1,9 +1,9 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "b9a270f6a50811e0d0ba401980fb9e58df99363da667d33b8b9260c3a43874ba", - "size_bytes": 39194, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=county%3A%2A&get=NAME%2CB19001_002E%2CB19001_003E%2CB19001_004E%2CB19001_005E%2CB19001_006E%2CB19001_007E%2CB19001_008E%2CB19001_009E%2CB19001_010E%2CB19001_011E%2CB19001_012E%2CB19001_013E%2CB19001_014E%2CB19001_015E%2CB19001_016E%2CB19001_017E%2CB19001_002M%2CB19001_003M%2CB19001_004M%2CB19001_005M%2CB19001_006M%2CB19001_007M%2CB19001_008M%2CB19001_009M%2CB19001_010M%2CB19001_011M%2CB19001_012M%2CB19001_013M%2CB19001_014M%2CB19001_015M%2CB19001_016M%2CB19001_017M%2CB19013_001E%2CB19013_001M&in=state%3A06%2C36%2C48%2C17%2C56", - "uncompressed_sha256": "180c85ef61215a3e79a21121b910fc450dcdb251c61b4a41fc91e59653c266a1" + "retrieved": "2026-11-07", + "sha256": "1016bb8d6655b4283fcb01294f8700ea3cd5d3711b9bf231462c87993f222668", + "size_bytes": 45618, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=county%3A%2A&get=NAME%2CB19001_002E%2CB19001_003E%2CB19001_004E%2CB19001_005E%2CB19001_006E%2CB19001_007E%2CB19001_008E%2CB19001_009E%2CB19001_010E%2CB19001_011E%2CB19001_012E%2CB19001_013E%2CB19001_014E%2CB19001_015E%2CB19001_016E%2CB19001_017E%2CB19001_002M%2CB19001_003M%2CB19001_004M%2CB19001_005M%2CB19001_006M%2CB19001_007M%2CB19001_008M%2CB19001_009M%2CB19001_010M%2CB19001_011M%2CB19001_012M%2CB19001_013M%2CB19001_014M%2CB19001_015M%2CB19001_016M%2CB19001_017M%2CB19013_001E%2CB19013_001M&in=state%3A06%2C36%2C48%2C17%2C56%2C26", + "uncompressed_sha256": "b75f19613b60024d53349720f2913e4d2d44f9dfbe0ba32c7201b482f0aedf71" } diff --git a/tests/fixtures/validation/api_2019_acs_acs5_60f8058dd4d4.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_60f8058dd4d4.json.gz deleted file mode 100644 index 36ae6be..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_60f8058dd4d4.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_64d8bf506b0f.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_64d8bf506b0f.json.gz index 9ef6e9e..d669d48 100644 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_64d8bf506b0f.json.gz and b/tests/fixtures/validation/api_2019_acs_acs5_64d8bf506b0f.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_64d8bf506b0f.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_64d8bf506b0f.json.gz.json index d50d98f..fd16178 100644 --- a/tests/fixtures/validation/api_2019_acs_acs5_64d8bf506b0f.json.gz.json +++ b/tests/fixtures/validation/api_2019_acs_acs5_64d8bf506b0f.json.gz.json @@ -1,8 +1,8 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "8b5ecb3efbf730dfad476bcfab8ea58917954303c039d9200935f4a72dd0306e", + "retrieved": "2026-11-07", + "sha256": "a9454a7f90766da9849c6499cc3b8cb664347899d0013ddc4c203e132809c115", "size_bytes": 397, "source_url": "https://api.census.gov/data/2019/acs/acs5?for=us%3A1&get=NAME%2Cgroup%28B19001%29", "uncompressed_sha256": "485aa25f49b72b496eed0521212237f14b17930bec1b1f803648d2892ac848e5" diff --git a/tests/fixtures/validation/api_2019_acs_acs5_6d57849f0011.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_6d57849f0011.json.gz deleted file mode 100644 index 9d92bf5..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_6d57849f0011.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_713966e74be2.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_713966e74be2.json.gz new file mode 100644 index 0000000..503326d Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_713966e74be2.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_713966e74be2.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_713966e74be2.json.gz.json new file mode 100644 index 0000000..aaab6a6 --- /dev/null +++ b/tests/fixtures/validation/api_2019_acs_acs5_713966e74be2.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "9203f36bea35dbaea6b4dc6ff496af4df4964f074bbc3da3c694386c892cc306", + "size_bytes": 242, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B01003%29", + "uncompressed_sha256": "c3aa42f762eebad6e50d373faf13fd893e1bdf99d38715edc9fc517e5d4c7077" +} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_7fcd8ab21138.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_7fcd8ab21138.json.gz deleted file mode 100644 index c5fcf7a..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_7fcd8ab21138.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_80059a578bb2.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_80059a578bb2.json.gz new file mode 100644 index 0000000..8fb22e6 Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_80059a578bb2.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_80059a578bb2.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_80059a578bb2.json.gz.json new file mode 100644 index 0000000..51543ca --- /dev/null +++ b/tests/fixtures/validation/api_2019_acs_acs5_80059a578bb2.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "5a740f284d14d09fdb159688d7d40abc3c0a910c039c0f7b0bfd13bc78292f08", + "size_bytes": 672, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2CB01003_001E%2CB01003_001M%2CB25001_001E%2CB25001_001M%2CB11001_001E%2CB11001_001M%2CB11001_002E%2CB11001_002M%2CB11001_003E%2CB11001_003M%2CB11001_004E%2CB11001_004M%2CB11001_005E%2CB11001_005M%2CB11001_006E%2CB11001_006M%2CB11001_007E%2CB11001_007M%2CB11001_008E%2CB11001_008M%2CB11001_009E%2CB11001_009M", + "uncompressed_sha256": "89d0cbe6a3b1876d3586e607fe70ebb85eac8a145d3d3b13984fa9b3291d5b33" +} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_8d376341f74c.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_8d376341f74c.json.gz new file mode 100644 index 0000000..bb1c6f0 Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_8d376341f74c.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_8d376341f74c.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_8d376341f74c.json.gz.json new file mode 100644 index 0000000..df8f433 --- /dev/null +++ b/tests/fixtures/validation/api_2019_acs_acs5_8d376341f74c.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "45ab2b83baefb26afe5c5722d7734bbb91c192a51a21d8cacce0d68ef85ff60b", + "size_bytes": 1462, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B25063%29", + "uncompressed_sha256": "f3ceab4f542ed06cfd047a66eb96a4d388a08288ea72aacf4e7db1412cd75f4b" +} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_97c1397a94b1.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_97c1397a94b1.json.gz deleted file mode 100644 index adb363a..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_97c1397a94b1.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_98a8e9fdc913.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_98a8e9fdc913.json.gz deleted file mode 100644 index 68d7beb..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_98a8e9fdc913.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_98a8e9fdc913.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_98a8e9fdc913.json.gz.json deleted file mode 100644 index 1b28f00..0000000 --- a/tests/fixtures/validation/api_2019_acs_acs5_98a8e9fdc913.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "58afa7850ea38f55c7a65321e4b2f753d72e6db4af0c42bdf4d15812c1baf4f1", - "size_bytes": 928, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B19001%29", - "uncompressed_sha256": "dfa99084d1cc323350b9be9663f2255bf710802d5b5c393ef7679c4bc0a42126" -} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_9b6f2242a9ff.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_9b6f2242a9ff.json.gz new file mode 100644 index 0000000..25124bf Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_9b6f2242a9ff.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_97c1397a94b1.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_9b6f2242a9ff.json.gz.json similarity index 56% rename from tests/fixtures/validation/api_2019_acs_acs5_97c1397a94b1.json.gz.json rename to tests/fixtures/validation/api_2019_acs_acs5_9b6f2242a9ff.json.gz.json index ad22fca..fb5c052 100644 --- a/tests/fixtures/validation/api_2019_acs_acs5_97c1397a94b1.json.gz.json +++ b/tests/fixtures/validation/api_2019_acs_acs5_9b6f2242a9ff.json.gz.json @@ -1,9 +1,9 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "e8317847949f43dbd5e5a8a8cf2ef468b2743b74fee97e73c1997889b6b0b7b8", - "size_bytes": 3286, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=county%3A%2A&get=B25064_001E%2CB25064_001M&in=state%3A06%2C36%2C48%2C17%2C56", - "uncompressed_sha256": "91e0ba8ff9db8a35fb18c73b46df93cfbb82cdf0689978985054de9f6c365560" + "retrieved": "2026-11-07", + "sha256": "6621efde8eaf028ded0d22a96c3182017b1875b4a4a57b48590ff7c13fff753d", + "size_bytes": 3788, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=county%3A%2A&get=B25064_001E%2CB25064_001M&in=state%3A06%2C36%2C48%2C17%2C56%2C26", + "uncompressed_sha256": "d0d5b080feb5ee6552a667a347af880706d8b8bfcf1bbdfd859f3fa5b9cf10c3" } diff --git a/tests/fixtures/validation/api_2019_acs_acs5_a117b41f6948.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_a117b41f6948.json.gz deleted file mode 100644 index dbe33c3..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_a117b41f6948.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_a117b41f6948.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_a117b41f6948.json.gz.json deleted file mode 100644 index 6317a7f..0000000 --- a/tests/fixtures/validation/api_2019_acs_acs5_a117b41f6948.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "3313b145fdd5e6f6deba9d51c3b73fa7f90bc12ce27b7272d0ffde4f95b83dcd", - "size_bytes": 608, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B11001%29", - "uncompressed_sha256": "cb43303557ca813d3cfada4d6f0f132ec09140af1bc5d083ef7743fcf5488cc9" -} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_a32df48d60d7.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_a32df48d60d7.json.gz deleted file mode 100644 index 28ce774..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_a32df48d60d7.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_a32df48d60d7.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_a32df48d60d7.json.gz.json deleted file mode 100644 index af66070..0000000 --- a/tests/fixtures/validation/api_2019_acs_acs5_a32df48d60d7.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "87ef6f5c7fd5b26e61fb0288c970eaf74563ace956a9e6b0cf0dddf6094a72fc", - "size_bytes": 1277, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B25063%29", - "uncompressed_sha256": "c2489be7581e033e3d6b0c65f0ea2044eadbe45183e9f956b95f9dea856d7b91" -} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_a48b77cebe4d.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_a48b77cebe4d.json.gz index dc472a4..05f4534 100644 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_a48b77cebe4d.json.gz and b/tests/fixtures/validation/api_2019_acs_acs5_a48b77cebe4d.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_a48b77cebe4d.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_a48b77cebe4d.json.gz.json index 9173440..076d306 100644 --- a/tests/fixtures/validation/api_2019_acs_acs5_a48b77cebe4d.json.gz.json +++ b/tests/fixtures/validation/api_2019_acs_acs5_a48b77cebe4d.json.gz.json @@ -1,8 +1,8 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "18255b15ceaa717541f9e863365e62ac77d7f109d2ce222aa87359005c780597", + "retrieved": "2026-11-07", + "sha256": "dd668a413e9986fdbc60d39b690a4dc97278d60a0d881ee2595a160224078d97", "size_bytes": 952, "source_url": "https://api.census.gov/data/2019/acs/acs5?for=us%3A1&get=NAME%2Cgroup%28B01001%29", "uncompressed_sha256": "bf31cb91d49577115a1b7caf5bbcb618a711b282b121ef59abb4ecd364fcdba6" diff --git a/tests/fixtures/validation/api_2019_acs_acs5_a6246da77b80.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_a6246da77b80.json.gz new file mode 100644 index 0000000..68e35c0 Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_a6246da77b80.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_a6246da77b80.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_a6246da77b80.json.gz.json new file mode 100644 index 0000000..316e52a --- /dev/null +++ b/tests/fixtures/validation/api_2019_acs_acs5_a6246da77b80.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "449b06eecaf7e98c909681bd3bbc050defaea38f11b1e2ecdd2decd79299bff2", + "size_bytes": 371, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B25003%29", + "uncompressed_sha256": "6d25e356cb264cdea316f67d17b49065e1f9120f949887da94904ff93687e2bd" +} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_a8832ca8ea97.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_a8832ca8ea97.json.gz index 3ccfc5a..43c668e 100644 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_a8832ca8ea97.json.gz and b/tests/fixtures/validation/api_2019_acs_acs5_a8832ca8ea97.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_a8832ca8ea97.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_a8832ca8ea97.json.gz.json index cc9e54c..36aa352 100644 --- a/tests/fixtures/validation/api_2019_acs_acs5_a8832ca8ea97.json.gz.json +++ b/tests/fixtures/validation/api_2019_acs_acs5_a8832ca8ea97.json.gz.json @@ -1,8 +1,8 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "58b0b3e8294e4039a9c36a5f0e6d309c6f87071607394442fa9a99126d854d0f", + "retrieved": "2026-11-07", + "sha256": "ad555713d246c1b2027f44cd732405e0d2816edcdda9a1a7f29b3ebb485137f1", "size_bytes": 1208, "source_url": "https://api.census.gov/data/2019/acs/acs5?for=us%3A1&get=NAME%2Cgroup%28B27010%29", "uncompressed_sha256": "c05f9949fe9e3ed81fd3feac79b1b31ab3cb11589a17cf79dd3f09c4ff59c033" diff --git a/tests/fixtures/validation/api_2019_acs_acs5_aefd730b1003.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_aefd730b1003.json.gz new file mode 100644 index 0000000..200cb04 Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_aefd730b1003.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_33f28cf8a208.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_aefd730b1003.json.gz.json similarity index 67% rename from tests/fixtures/validation/api_2019_acs_acs5_33f28cf8a208.json.gz.json rename to tests/fixtures/validation/api_2019_acs_acs5_aefd730b1003.json.gz.json index 641ed9b..6a969d7 100644 --- a/tests/fixtures/validation/api_2019_acs_acs5_33f28cf8a208.json.gz.json +++ b/tests/fixtures/validation/api_2019_acs_acs5_aefd730b1003.json.gz.json @@ -1,9 +1,9 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "26c3cacc654ebe9916caae8f6e3e6dea75c896ea182277b5665b54696c9a3404", - "size_bytes": 30291, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=county%3A%2A&get=NAME%2CB01003_001E%2CB01003_001M%2CB25001_001E%2CB25001_001M%2CB11001_001E%2CB11001_001M%2CB11001_002E%2CB11001_002M%2CB11001_003E%2CB11001_003M%2CB11001_004E%2CB11001_004M%2CB11001_005E%2CB11001_005M%2CB11001_006E%2CB11001_006M%2CB11001_007E%2CB11001_007M%2CB11001_008E%2CB11001_008M%2CB11001_009E%2CB11001_009M&in=state%3A06%2C36%2C48%2C17%2C56", - "uncompressed_sha256": "e9450499c8b7279d31c2660c3427f333437a974c4335fed7382370f3fe72f105" + "retrieved": "2026-11-07", + "sha256": "3a34ac384d792dd32808dc8e83839ea1957147746d60f726e7d53a07722d85a7", + "size_bytes": 35285, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=county%3A%2A&get=NAME%2CB01003_001E%2CB01003_001M%2CB25001_001E%2CB25001_001M%2CB11001_001E%2CB11001_001M%2CB11001_002E%2CB11001_002M%2CB11001_003E%2CB11001_003M%2CB11001_004E%2CB11001_004M%2CB11001_005E%2CB11001_005M%2CB11001_006E%2CB11001_006M%2CB11001_007E%2CB11001_007M%2CB11001_008E%2CB11001_008M%2CB11001_009E%2CB11001_009M&in=state%3A06%2C36%2C48%2C17%2C56%2C26", + "uncompressed_sha256": "8662f28e2e2f14eef3c610b15e39daf8932709b779940a7b36bf56c81094c615" } diff --git a/tests/fixtures/validation/api_2019_acs_acs5_b114f06e3490.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_b114f06e3490.json.gz new file mode 100644 index 0000000..a59d8aa Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_b114f06e3490.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_b114f06e3490.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_b114f06e3490.json.gz.json new file mode 100644 index 0000000..eb941e7 --- /dev/null +++ b/tests/fixtures/validation/api_2019_acs_acs5_b114f06e3490.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "07dc199c209404255c354bfe25a5d9b472db814aecc0a334a490523a7e7bc8e7", + "size_bytes": 1226, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2CB17001_002E%2CB17001_002M%2CB17001_001E%2CB17001_001M%2CB27010_017E%2CB27010_017M%2CB27010_033E%2CB27010_033M%2CB27010_050E%2CB27010_050M%2CB27010_066E%2CB27010_066M%2CB27010_001E%2CB27010_001M%2CB25003_002E%2CB25003_002M%2CB25003_001E%2CB25003_001M%2CB01001_020E%2CB01001_020M%2CB01001_021E%2CB01001_021M%2CB01001_022E%2CB01001_022M%2CB01001_023E%2CB01001_023M%2CB01001_024E%2CB01001_024M%2CB01001_025E%2CB01001_025M%2CB01001_044E%2CB01001_044M%2CB01001_045E%2CB01001_045M%2CB01001_046E%2CB01001_046M%2CB01001_047E%2CB01001_047M%2CB01001_048E%2CB01001_048M%2CB01001_049E%2CB01001_049M%2CB01001_001E%2CB01001_001M%2CB15003_022E%2CB15003_022M%2CB15003_023E%2CB15003_023M", + "uncompressed_sha256": "1f00f30f4851f6858c9962213c2a51e7f015caf0022fc69f4fd4bcbab9962bc9" +} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_bd718b6659de.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_bd718b6659de.json.gz deleted file mode 100644 index 29a7367..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_bd718b6659de.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_bd718b6659de.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_bd718b6659de.json.gz.json deleted file mode 100644 index 8aa87d8..0000000 --- a/tests/fixtures/validation/api_2019_acs_acs5_bd718b6659de.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "d60c38c482bb08942003cb5f004a13f0497a828799f8f34d768104aae03ac0a9", - "size_bytes": 194, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=B15003_024E%2CB15003_024M%2CB15003_025E%2CB15003_025M%2CB15003_001E%2CB15003_001M", - "uncompressed_sha256": "fd01c54f788088288ca90f9f5be93021ecccfe4e6af867a64958ddc651a4e57f" -} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_c7ff2ad13cb5.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_c7ff2ad13cb5.json.gz new file mode 100644 index 0000000..5049dc3 Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_c7ff2ad13cb5.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_6d57849f0011.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_c7ff2ad13cb5.json.gz.json similarity index 78% rename from tests/fixtures/validation/api_2019_acs_acs5_6d57849f0011.json.gz.json rename to tests/fixtures/validation/api_2019_acs_acs5_c7ff2ad13cb5.json.gz.json index 11ebf59..f68ad88 100644 --- a/tests/fixtures/validation/api_2019_acs_acs5_6d57849f0011.json.gz.json +++ b/tests/fixtures/validation/api_2019_acs_acs5_c7ff2ad13cb5.json.gz.json @@ -1,9 +1,9 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "e9f4c2ce5d8f7c4900001b2b421c7191edf13fc0a282a98f4e9652ef70c179ea", - "size_bytes": 39847, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=county%3A%2A&get=NAME%2CB25063_003E%2CB25063_004E%2CB25063_005E%2CB25063_006E%2CB25063_007E%2CB25063_008E%2CB25063_009E%2CB25063_010E%2CB25063_011E%2CB25063_012E%2CB25063_013E%2CB25063_014E%2CB25063_015E%2CB25063_016E%2CB25063_017E%2CB25063_018E%2CB25063_019E%2CB25063_020E%2CB25063_021E%2CB25063_022E%2CB25063_023E%2CB25063_024E%2CB25063_025E%2CB25063_026E%2CB25063_003M%2CB25063_004M%2CB25063_005M%2CB25063_006M%2CB25063_007M%2CB25063_008M%2CB25063_009M%2CB25063_010M%2CB25063_011M%2CB25063_012M%2CB25063_013M%2CB25063_014M%2CB25063_015M%2CB25063_016M%2CB25063_017M%2CB25063_018M%2CB25063_019M%2CB25063_020M%2CB25063_021M%2CB25063_022M%2CB25063_023M%2CB25063_024M%2CB25063_025M%2CB25063_026M&in=state%3A06%2C36%2C48%2C17%2C56", - "uncompressed_sha256": "928fb5c32ac15b98b1142506faf63f4bd1cdd6b281ab715f82a00907fb072a10" + "retrieved": "2026-11-07", + "sha256": "ce1ae10713614fd8ae8518e08fea5c55a3aab80c6fb6f85fb29024b68dd3b89f", + "size_bytes": 46463, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=county%3A%2A&get=NAME%2CB25063_003E%2CB25063_004E%2CB25063_005E%2CB25063_006E%2CB25063_007E%2CB25063_008E%2CB25063_009E%2CB25063_010E%2CB25063_011E%2CB25063_012E%2CB25063_013E%2CB25063_014E%2CB25063_015E%2CB25063_016E%2CB25063_017E%2CB25063_018E%2CB25063_019E%2CB25063_020E%2CB25063_021E%2CB25063_022E%2CB25063_023E%2CB25063_024E%2CB25063_025E%2CB25063_026E%2CB25063_003M%2CB25063_004M%2CB25063_005M%2CB25063_006M%2CB25063_007M%2CB25063_008M%2CB25063_009M%2CB25063_010M%2CB25063_011M%2CB25063_012M%2CB25063_013M%2CB25063_014M%2CB25063_015M%2CB25063_016M%2CB25063_017M%2CB25063_018M%2CB25063_019M%2CB25063_020M%2CB25063_021M%2CB25063_022M%2CB25063_023M%2CB25063_024M%2CB25063_025M%2CB25063_026M&in=state%3A06%2C36%2C48%2C17%2C56%2C26", + "uncompressed_sha256": "f7485dc6c6a861eb805c577be89c69ca51784993941950f708160b49b00d8539" } diff --git a/tests/fixtures/validation/api_2019_acs_acs5_c8bfd1d69026.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_c8bfd1d69026.json.gz deleted file mode 100644 index 195349f..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_c8bfd1d69026.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_cc8d0b7e7a9e.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_cc8d0b7e7a9e.json.gz deleted file mode 100644 index 557a276..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_cc8d0b7e7a9e.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_cc8d0b7e7a9e.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_cc8d0b7e7a9e.json.gz.json deleted file mode 100644 index 7f460a6..0000000 --- a/tests/fixtures/validation/api_2019_acs_acs5_cc8d0b7e7a9e.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "ecf568cda7a95fcae6d67bda8e5e0f359a0020ca65edafa90824f2536896012c", - "size_bytes": 2902, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B27010%29", - "uncompressed_sha256": "ccd63dc03e2c9770538f8efada7527c0b9c0042f47ea031a4dd449257042b4d8" -} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_cprofile_228e079bcf8e.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_cprofile_228e079bcf8e.json.gz deleted file mode 100644 index 97c9857..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_cprofile_228e079bcf8e.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_cprofile_228e079bcf8e.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_cprofile_228e079bcf8e.json.gz.json deleted file mode 100644 index d12377b..0000000 --- a/tests/fixtures/validation/api_2019_acs_acs5_cprofile_228e079bcf8e.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "fb60720c12fb6178b1dc872bdffd04be411c12364b510e529b1a3de9d30d92bf", - "size_bytes": 5975, - "source_url": "https://api.census.gov/data/2019/acs/acs5/cprofile?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28CP03%29", - "uncompressed_sha256": "eeae778675901e01d76868c18daf22b9258ce79d90ce75a0ff800449e4ef2938" -} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_cprofile_fcf4a1a49209.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_cprofile_fcf4a1a49209.json.gz new file mode 100644 index 0000000..2314d52 Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_cprofile_fcf4a1a49209.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_cprofile_fcf4a1a49209.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_cprofile_fcf4a1a49209.json.gz.json new file mode 100644 index 0000000..3bbd03a --- /dev/null +++ b/tests/fixtures/validation/api_2019_acs_acs5_cprofile_fcf4a1a49209.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "ab50e7f18e3e2fb51eee2bed738a095d12b7bdc4c3f9351ae49ff01f5a3768e9", + "size_bytes": 6754, + "source_url": "https://api.census.gov/data/2019/acs/acs5/cprofile?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28CP03%29", + "uncompressed_sha256": "e80ce6fc23f093ea1a64ea765a944cab5ccb6c56d330cc9c9a5b6f733969c6a8" +} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_d68113830411.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_d68113830411.json.gz deleted file mode 100644 index 1f0648f..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_d68113830411.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_d68113830411.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_d68113830411.json.gz.json deleted file mode 100644 index 82ed482..0000000 --- a/tests/fixtures/validation/api_2019_acs_acs5_d68113830411.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "3027550bc203a8fffc47edfa453f85632a4eb9b9ba21f0077d637d6cb40bfe7b", - "size_bytes": 586, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2CB01003_001E%2CB01003_001M%2CB25001_001E%2CB25001_001M%2CB11001_001E%2CB11001_001M%2CB11001_002E%2CB11001_002M%2CB11001_003E%2CB11001_003M%2CB11001_004E%2CB11001_004M%2CB11001_005E%2CB11001_005M%2CB11001_006E%2CB11001_006M%2CB11001_007E%2CB11001_007M%2CB11001_008E%2CB11001_008M%2CB11001_009E%2CB11001_009M", - "uncompressed_sha256": "df6778f4d515fff1b12249bcb3f6ebd4786d8b8554572038ee77570527b69f77" -} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_daeffc1dc3d4.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_daeffc1dc3d4.json.gz deleted file mode 100644 index 6e84ec3..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_daeffc1dc3d4.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_daeffc1dc3d4.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_daeffc1dc3d4.json.gz.json deleted file mode 100644 index 6e685ea..0000000 --- a/tests/fixtures/validation/api_2019_acs_acs5_daeffc1dc3d4.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "146559f2f7e7416273bfa2dd6d3303b92b31a9a1a91c00ee8ea68278f72f4d93", - "size_bytes": 2284, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B01001%29", - "uncompressed_sha256": "5b2f7419585dc140b93c80a038c63adc52507322f4bdc45af254b0048e679a8b" -} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_e5a717683448.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_e5a717683448.json.gz deleted file mode 100644 index 1ab05ed..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_e5a717683448.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_e5a717683448.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_e5a717683448.json.gz.json deleted file mode 100644 index 952caaf..0000000 --- a/tests/fixtures/validation/api_2019_acs_acs5_e5a717683448.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "6aec513ea59c59a4a4de7b29780f18820a5936467069093d96575106f08ceffd", - "size_bytes": 329, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B25003%29", - "uncompressed_sha256": "d30391d46c0cca39d39534f4d9f63dc4fdf79110d79dd96e16777e496531259f" -} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_e5d103b20f97.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_e5d103b20f97.json.gz new file mode 100644 index 0000000..71d75a4 Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_e5d103b20f97.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_e5d103b20f97.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_e5d103b20f97.json.gz.json new file mode 100644 index 0000000..3f762f2 --- /dev/null +++ b/tests/fixtures/validation/api_2019_acs_acs5_e5d103b20f97.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "6cb44aab41f54a682bcdc8fee83598efac9a3ae8e9e42ff252364c2556e90cab", + "size_bytes": 1067, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B19001%29", + "uncompressed_sha256": "4fe91a1fc8b2d31a4fa68b93f3083352b598df7a9c699eeea92bf1d340a146a7" +} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_f0405753c6d6.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_f0405753c6d6.json.gz new file mode 100644 index 0000000..cfce44f Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_f0405753c6d6.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_f0405753c6d6.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_f0405753c6d6.json.gz.json new file mode 100644 index 0000000..3de8995 --- /dev/null +++ b/tests/fixtures/validation/api_2019_acs_acs5_f0405753c6d6.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "ca3c37661c773b6f0767a21b10e9076586726291a44b9763b180e48c14c55a2a", + "size_bytes": 693, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B11001%29", + "uncompressed_sha256": "68ce421c407bbd10d4b19d26f0630f56ce6e2dcabd5b8fa81bba683dfb14aee1" +} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_f8ef5810051a.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_f8ef5810051a.json.gz new file mode 100644 index 0000000..f471ec8 Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_f8ef5810051a.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_c8bfd1d69026.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_f8ef5810051a.json.gz.json similarity index 78% rename from tests/fixtures/validation/api_2019_acs_acs5_c8bfd1d69026.json.gz.json rename to tests/fixtures/validation/api_2019_acs_acs5_f8ef5810051a.json.gz.json index a2d1b42..260f352 100644 --- a/tests/fixtures/validation/api_2019_acs_acs5_c8bfd1d69026.json.gz.json +++ b/tests/fixtures/validation/api_2019_acs_acs5_f8ef5810051a.json.gz.json @@ -1,9 +1,9 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "1db3c4936ac1a75c33b87d1c6eba03ac73d21c5f8b9ba29036dbc77b4ca9b306", - "size_bytes": 53605, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=county%3A%2A&get=NAME%2CB17001_002E%2CB17001_002M%2CB17001_001E%2CB17001_001M%2CB27010_017E%2CB27010_017M%2CB27010_033E%2CB27010_033M%2CB27010_050E%2CB27010_050M%2CB27010_066E%2CB27010_066M%2CB27010_001E%2CB27010_001M%2CB25003_002E%2CB25003_002M%2CB25003_001E%2CB25003_001M%2CB01001_020E%2CB01001_020M%2CB01001_021E%2CB01001_021M%2CB01001_022E%2CB01001_022M%2CB01001_023E%2CB01001_023M%2CB01001_024E%2CB01001_024M%2CB01001_025E%2CB01001_025M%2CB01001_044E%2CB01001_044M%2CB01001_045E%2CB01001_045M%2CB01001_046E%2CB01001_046M%2CB01001_047E%2CB01001_047M%2CB01001_048E%2CB01001_048M%2CB01001_049E%2CB01001_049M%2CB01001_001E%2CB01001_001M%2CB15003_022E%2CB15003_022M%2CB15003_023E%2CB15003_023M&in=state%3A06%2C36%2C48%2C17%2C56", - "uncompressed_sha256": "e04b9e361d6d6a77cc79f634de6a17d867a27541eb9a51be43f58f861a20bd06" + "retrieved": "2026-11-07", + "sha256": "5e78378a4b7ffbfc7bbf49fc49b8d37687a85d84e1f143c4c81513aa5105aef2", + "size_bytes": 62500, + "source_url": "https://api.census.gov/data/2019/acs/acs5?for=county%3A%2A&get=NAME%2CB17001_002E%2CB17001_002M%2CB17001_001E%2CB17001_001M%2CB27010_017E%2CB27010_017M%2CB27010_033E%2CB27010_033M%2CB27010_050E%2CB27010_050M%2CB27010_066E%2CB27010_066M%2CB27010_001E%2CB27010_001M%2CB25003_002E%2CB25003_002M%2CB25003_001E%2CB25003_001M%2CB01001_020E%2CB01001_020M%2CB01001_021E%2CB01001_021M%2CB01001_022E%2CB01001_022M%2CB01001_023E%2CB01001_023M%2CB01001_024E%2CB01001_024M%2CB01001_025E%2CB01001_025M%2CB01001_044E%2CB01001_044M%2CB01001_045E%2CB01001_045M%2CB01001_046E%2CB01001_046M%2CB01001_047E%2CB01001_047M%2CB01001_048E%2CB01001_048M%2CB01001_049E%2CB01001_049M%2CB01001_001E%2CB01001_001M%2CB15003_022E%2CB15003_022M%2CB15003_023E%2CB15003_023M&in=state%3A06%2C36%2C48%2C17%2C56%2C26", + "uncompressed_sha256": "ccb5f2bf088661ebc358c37e35945fbed7d636ff4a2077a6b91e74898bffaa2c" } diff --git a/tests/fixtures/validation/api_2019_acs_acs5_fa3022d6d73c.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_fa3022d6d73c.json.gz deleted file mode 100644 index b756451..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_fa3022d6d73c.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_fa3022d6d73c.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_fa3022d6d73c.json.gz.json deleted file mode 100644 index 0ca10dc..0000000 --- a/tests/fixtures/validation/api_2019_acs_acs5_fa3022d6d73c.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "0ae273140e9ed245590f2eac94e99161e7701f623ca252c65a60ea743461feb5", - "size_bytes": 2696, - "source_url": "https://api.census.gov/data/2019/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B17001%29", - "uncompressed_sha256": "1c0b428bbc4f0c270fd23e26be1f3f661388beb0f9048a16ba59eaf34348e454" -} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_profile_932f4cef5bd6.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_profile_932f4cef5bd6.json.gz new file mode 100644 index 0000000..c1b5dfb Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_profile_932f4cef5bd6.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_profile_932f4cef5bd6.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_profile_932f4cef5bd6.json.gz.json new file mode 100644 index 0000000..9a27f2b --- /dev/null +++ b/tests/fixtures/validation/api_2019_acs_acs5_profile_932f4cef5bd6.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "f0f3fbda229e9a7621fe03c2df1d8264aa14f6496d67000d6009e47464c38286", + "size_bytes": 280, + "source_url": "https://api.census.gov/data/2019/acs/acs5/profile?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2CDP03_0128PE%2CDP03_0128PM%2CDP03_0099PE%2CDP03_0099PM%2CDP04_0046PE%2CDP04_0046PM%2CDP05_0024PE%2CDP05_0024PM%2CDP02_0068PE%2CDP02_0068PM", + "uncompressed_sha256": "343bfbb40f3cbea6fc2570d95e92d28289679f2c05b86f7eda22a5a7babadbe3" +} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_profile_99cd887a712f.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_profile_99cd887a712f.json.gz new file mode 100644 index 0000000..62a3c27 Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_profile_99cd887a712f.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_profile_d3d176522267.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_profile_99cd887a712f.json.gz.json similarity index 64% rename from tests/fixtures/validation/api_2019_acs_acs5_profile_d3d176522267.json.gz.json rename to tests/fixtures/validation/api_2019_acs_acs5_profile_99cd887a712f.json.gz.json index 1f98137..5da37af 100644 --- a/tests/fixtures/validation/api_2019_acs_acs5_profile_d3d176522267.json.gz.json +++ b/tests/fixtures/validation/api_2019_acs_acs5_profile_99cd887a712f.json.gz.json @@ -1,9 +1,9 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "4d441c68676104a63c3a3d0056ddbe847f4bd0f179e51526ad6439e4b95998ce", - "size_bytes": 13241, - "source_url": "https://api.census.gov/data/2019/acs/acs5/profile?for=county%3A%2A&get=NAME%2CDP03_0128PE%2CDP03_0128PM%2CDP03_0099PE%2CDP03_0099PM%2CDP04_0046PE%2CDP04_0046PM%2CDP05_0024PE%2CDP05_0024PM%2CDP02_0068PE%2CDP02_0068PM&in=state%3A06%2C36%2C48%2C17%2C56", - "uncompressed_sha256": "b696f4d3389054524dd926145db6f26de00cef2e8cf2c3e22729903830ab16c6" + "retrieved": "2026-11-07", + "sha256": "c971f5689b9bb41e09f275f5a2e29e6236f2706b08427b96d945cc1fb27729ae", + "size_bytes": 15324, + "source_url": "https://api.census.gov/data/2019/acs/acs5/profile?for=county%3A%2A&get=NAME%2CDP03_0128PE%2CDP03_0128PM%2CDP03_0099PE%2CDP03_0099PM%2CDP04_0046PE%2CDP04_0046PM%2CDP05_0024PE%2CDP05_0024PM%2CDP02_0068PE%2CDP02_0068PM&in=state%3A06%2C36%2C48%2C17%2C56%2C26", + "uncompressed_sha256": "e36137df52e001824a2adc13a35f1ed60b7aaba5e887a802c50d883915732a48" } diff --git a/tests/fixtures/validation/api_2019_acs_acs5_profile_a9980d3d4ba9.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_profile_a9980d3d4ba9.json.gz new file mode 100644 index 0000000..72c52b2 Binary files /dev/null and b/tests/fixtures/validation/api_2019_acs_acs5_profile_a9980d3d4ba9.json.gz differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_profile_a9980d3d4ba9.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_profile_a9980d3d4ba9.json.gz.json new file mode 100644 index 0000000..989b2ae --- /dev/null +++ b/tests/fixtures/validation/api_2019_acs_acs5_profile_a9980d3d4ba9.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "85b2e2a00b0c4081d299a487cc1d01e2c23d1cd047cb7e4196a99645205a5c0e", + "size_bytes": 10514, + "source_url": "https://api.census.gov/data/2019/acs/acs5/profile?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28DP03%29", + "uncompressed_sha256": "e7350bf98973accedd828c4c32b8126629595c88292e89596fc471ecd0d3ebdf" +} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_profile_c128d325b647.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_profile_c128d325b647.json.gz deleted file mode 100644 index f2cba6e..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_profile_c128d325b647.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_profile_c128d325b647.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_profile_c128d325b647.json.gz.json deleted file mode 100644 index 552fedc..0000000 --- a/tests/fixtures/validation/api_2019_acs_acs5_profile_c128d325b647.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "58b7e743dcfc8f30767bcfb8bc0bad6ba94374dd35ae072f8e772a7363a0a932", - "size_bytes": 257, - "source_url": "https://api.census.gov/data/2019/acs/acs5/profile?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2CDP03_0128PE%2CDP03_0128PM%2CDP03_0099PE%2CDP03_0099PM%2CDP04_0046PE%2CDP04_0046PM%2CDP05_0024PE%2CDP05_0024PM%2CDP02_0068PE%2CDP02_0068PM", - "uncompressed_sha256": "dc5c79315305ef672a8a145b0d02215777e6501aef738135d5a695e1632f49e4" -} diff --git a/tests/fixtures/validation/api_2019_acs_acs5_profile_d3d176522267.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_profile_d3d176522267.json.gz deleted file mode 100644 index 95a3b1b..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_profile_d3d176522267.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_profile_ea90c1144b4a.json.gz b/tests/fixtures/validation/api_2019_acs_acs5_profile_ea90c1144b4a.json.gz deleted file mode 100644 index a047e0a..0000000 Binary files a/tests/fixtures/validation/api_2019_acs_acs5_profile_ea90c1144b4a.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2019_acs_acs5_profile_ea90c1144b4a.json.gz.json b/tests/fixtures/validation/api_2019_acs_acs5_profile_ea90c1144b4a.json.gz.json deleted file mode 100644 index 0e97d86..0000000 --- a/tests/fixtures/validation/api_2019_acs_acs5_profile_ea90c1144b4a.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "aa678346c4ed4b1eb73c45528bbaef1a4d8984cb48784ac8eb684321b2584458", - "size_bytes": 9278, - "source_url": "https://api.census.gov/data/2019/acs/acs5/profile?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28DP03%29", - "uncompressed_sha256": "5ce87f8dcdc3af9aa4629a34a8995abf8b8a731b9668f6e4a42f0e54a6c62482" -} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_030a7b2af5c4.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_030a7b2af5c4.json.gz deleted file mode 100644 index 6a791cb..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_030a7b2af5c4.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_14e476f3c0ec.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_14e476f3c0ec.json.gz deleted file mode 100644 index b0bde59..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_14e476f3c0ec.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_1cb9a6e37c85.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_1cb9a6e37c85.json.gz new file mode 100644 index 0000000..f97a8de Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_1cb9a6e37c85.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_1cb9a6e37c85.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_1cb9a6e37c85.json.gz.json new file mode 100644 index 0000000..e2a081b --- /dev/null +++ b/tests/fixtures/validation/api_2023_acs_acs5_1cb9a6e37c85.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "f38017c91d368ce0a3c40d23eadca9116bc612b98165730e551309ec7dca5d81", + "size_bytes": 670, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2CB01003_001E%2CB01003_001M%2CB25001_001E%2CB25001_001M%2CB11001_001E%2CB11001_001M%2CB11001_002E%2CB11001_002M%2CB11001_003E%2CB11001_003M%2CB11001_004E%2CB11001_004M%2CB11001_005E%2CB11001_005M%2CB11001_006E%2CB11001_006M%2CB11001_007E%2CB11001_007M%2CB11001_008E%2CB11001_008M%2CB11001_009E%2CB11001_009M", + "uncompressed_sha256": "0b15dd02b63644e8e300c8e998f3f0c6af16334a7c4cd8a7384427e73b375d20" +} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_1e31f121cce0.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_1e31f121cce0.json.gz index 960b947..6ec3f47 100644 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_1e31f121cce0.json.gz and b/tests/fixtures/validation/api_2023_acs_acs5_1e31f121cce0.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_1e31f121cce0.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_1e31f121cce0.json.gz.json index 3589e01..9b90b58 100644 --- a/tests/fixtures/validation/api_2023_acs_acs5_1e31f121cce0.json.gz.json +++ b/tests/fixtures/validation/api_2023_acs_acs5_1e31f121cce0.json.gz.json @@ -1,8 +1,8 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "d200511276b1bccf300d2872518b992f81f1a30bd3c49f9f69789ea8f2f890a0", + "retrieved": "2026-11-07", + "sha256": "e08d44e8f7451ad0e4d342c6b7f2636cdf4db1f3300a242f931b121eca309662", "size_bytes": 121, "source_url": "https://api.census.gov/data/2023/acs/acs5?for=us%3A1&get=NAME%2Cgroup%28B01003%29", "uncompressed_sha256": "1b79691f70571d8c8e450531996cc1b6273368f8bac619b08f3482e7e44fb5f5" diff --git a/tests/fixtures/validation/api_2023_acs_acs5_2d4f2ba5f10e.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_2d4f2ba5f10e.json.gz deleted file mode 100644 index acf3a7e..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_2d4f2ba5f10e.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_2d4f2ba5f10e.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_2d4f2ba5f10e.json.gz.json deleted file mode 100644 index 32b3868..0000000 --- a/tests/fixtures/validation/api_2023_acs_acs5_2d4f2ba5f10e.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "ca48042b2df9e71649ba00b92c91e2319620101577a4b42205d23dc8ba6c66c5", - "size_bytes": 2288, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B01001%29", - "uncompressed_sha256": "901c55432fd9e1253f3418f813fe72ecc83fd0638469f86914491261875cd545" -} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_2da90149c21f.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_2da90149c21f.json.gz deleted file mode 100644 index 07acd2e..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_2da90149c21f.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_383f1aac7e1b.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_383f1aac7e1b.json.gz new file mode 100644 index 0000000..007e6fb Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_383f1aac7e1b.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_ffefb214408d.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_383f1aac7e1b.json.gz.json similarity index 61% rename from tests/fixtures/validation/api_2023_acs_acs5_ffefb214408d.json.gz.json rename to tests/fixtures/validation/api_2023_acs_acs5_383f1aac7e1b.json.gz.json index a11443f..9eeaccd 100644 --- a/tests/fixtures/validation/api_2023_acs_acs5_ffefb214408d.json.gz.json +++ b/tests/fixtures/validation/api_2023_acs_acs5_383f1aac7e1b.json.gz.json @@ -1,9 +1,9 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "6fce19200e5a4fd57be026187e7f5c3bf4f2000c0ece7c684b05d6a88f5bde64", - "size_bytes": 7647, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=county%3A%2A&get=B15003_024E%2CB15003_024M%2CB15003_025E%2CB15003_025M%2CB15003_001E%2CB15003_001M&in=state%3A06%2C36%2C48%2C17%2C56", - "uncompressed_sha256": "43b0a46e5ca52fed8bafef314cdf1ef0fbe73ac3dd68a7c91ca07526189f2312" + "retrieved": "2026-11-07", + "sha256": "2782a99a36cb5b20a3b43ab06eccd3500c5350f9b688763dc4b8108e6e1a5059", + "size_bytes": 8879, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=county%3A%2A&get=B15003_024E%2CB15003_024M%2CB15003_025E%2CB15003_025M%2CB15003_001E%2CB15003_001M&in=state%3A06%2C36%2C48%2C17%2C56%2C26", + "uncompressed_sha256": "2c9e507b9de9964f06773d0ce8b30f2cdb0d92d3d725fd1904ff01755b8f9185" } diff --git a/tests/fixtures/validation/api_2023_acs_acs5_3ad38417cf0c.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_3ad38417cf0c.json.gz new file mode 100644 index 0000000..86f189a Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_3ad38417cf0c.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_3ad38417cf0c.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_3ad38417cf0c.json.gz.json new file mode 100644 index 0000000..d41fa33 --- /dev/null +++ b/tests/fixtures/validation/api_2023_acs_acs5_3ad38417cf0c.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "1be91068cd62dc6c32abdb142130c0ac5cf38b605774d36850d96628fe7aee9b", + "size_bytes": 1244, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2CB17001_002E%2CB17001_002M%2CB17001_001E%2CB17001_001M%2CB27010_017E%2CB27010_017M%2CB27010_033E%2CB27010_033M%2CB27010_050E%2CB27010_050M%2CB27010_066E%2CB27010_066M%2CB27010_001E%2CB27010_001M%2CB25003_002E%2CB25003_002M%2CB25003_001E%2CB25003_001M%2CB01001_020E%2CB01001_020M%2CB01001_021E%2CB01001_021M%2CB01001_022E%2CB01001_022M%2CB01001_023E%2CB01001_023M%2CB01001_024E%2CB01001_024M%2CB01001_025E%2CB01001_025M%2CB01001_044E%2CB01001_044M%2CB01001_045E%2CB01001_045M%2CB01001_046E%2CB01001_046M%2CB01001_047E%2CB01001_047M%2CB01001_048E%2CB01001_048M%2CB01001_049E%2CB01001_049M%2CB01001_001E%2CB01001_001M%2CB15003_022E%2CB15003_022M%2CB15003_023E%2CB15003_023M", + "uncompressed_sha256": "f2b15a7128be5255137f4b05e98c2a5781db75b7d22e13962fde1e4a10e1c5cc" +} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_3dbbec9fe7a0.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_3dbbec9fe7a0.json.gz new file mode 100644 index 0000000..18af51f Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_3dbbec9fe7a0.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_3dbbec9fe7a0.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_3dbbec9fe7a0.json.gz.json new file mode 100644 index 0000000..e8392ae --- /dev/null +++ b/tests/fixtures/validation/api_2023_acs_acs5_3dbbec9fe7a0.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "b23840516295316edcb2a66d0396c940398e64e5b22b90a1d88dd455277418bd", + "size_bytes": 241, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B01003%29", + "uncompressed_sha256": "c983d41ad8583600beaa3aad1c60418fd7fa0ade2c57efdb3c15575ba29e56e5" +} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_4624595b2e37.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_4624595b2e37.json.gz index 26119ab..a301744 100644 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_4624595b2e37.json.gz and b/tests/fixtures/validation/api_2023_acs_acs5_4624595b2e37.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_4624595b2e37.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_4624595b2e37.json.gz.json index 8e653e0..2504ea9 100644 --- a/tests/fixtures/validation/api_2023_acs_acs5_4624595b2e37.json.gz.json +++ b/tests/fixtures/validation/api_2023_acs_acs5_4624595b2e37.json.gz.json @@ -1,8 +1,8 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "568af51ad002b1157df74f66b1f97eb54afd5dbc61086d8e9974efb45692f425", + "retrieved": "2026-11-07", + "sha256": "6aa18dd5615c72b67b8a0d9107d136c017f047c4d612b4c8b8440e7811e8aa8f", "size_bytes": 1208, "source_url": "https://api.census.gov/data/2023/acs/acs5?for=us%3A1&get=NAME%2Cgroup%28B27010%29", "uncompressed_sha256": "29503801c8fa4f7c765bf20fe7b941984d83e9cb35aabd55206c66098bcc3183" diff --git a/tests/fixtures/validation/api_2023_acs_acs5_4f40a2a2603f.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_4f40a2a2603f.json.gz deleted file mode 100644 index feedf2f..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_4f40a2a2603f.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_4f40a2a2603f.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_4f40a2a2603f.json.gz.json deleted file mode 100644 index 9e95910..0000000 --- a/tests/fixtures/validation/api_2023_acs_acs5_4f40a2a2603f.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "e9d9eb70af194194e8b88510dcf3ad659dc78944fbf8d077682c01351dd5f0b5", - "size_bytes": 2703, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B17001%29", - "uncompressed_sha256": "7db3e6d71a8c8f6416575c5a07d89de8c2b8e939860eb0912f839dcd81ba42b8" -} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_5ca02edbe844.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_5ca02edbe844.json.gz new file mode 100644 index 0000000..e812b1b Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_5ca02edbe844.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_14e476f3c0ec.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_5ca02edbe844.json.gz.json similarity index 78% rename from tests/fixtures/validation/api_2023_acs_acs5_14e476f3c0ec.json.gz.json rename to tests/fixtures/validation/api_2023_acs_acs5_5ca02edbe844.json.gz.json index 9a2fcc7..bd77957 100644 --- a/tests/fixtures/validation/api_2023_acs_acs5_14e476f3c0ec.json.gz.json +++ b/tests/fixtures/validation/api_2023_acs_acs5_5ca02edbe844.json.gz.json @@ -1,9 +1,9 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "74a9dfda481f67a2a93f0fd9534b8d29261544cf71a2bd16572bd48336955e5b", - "size_bytes": 39134, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=county%3A%2A&get=NAME%2CB25063_003E%2CB25063_004E%2CB25063_005E%2CB25063_006E%2CB25063_007E%2CB25063_008E%2CB25063_009E%2CB25063_010E%2CB25063_011E%2CB25063_012E%2CB25063_013E%2CB25063_014E%2CB25063_015E%2CB25063_016E%2CB25063_017E%2CB25063_018E%2CB25063_019E%2CB25063_020E%2CB25063_021E%2CB25063_022E%2CB25063_023E%2CB25063_024E%2CB25063_025E%2CB25063_026E%2CB25063_003M%2CB25063_004M%2CB25063_005M%2CB25063_006M%2CB25063_007M%2CB25063_008M%2CB25063_009M%2CB25063_010M%2CB25063_011M%2CB25063_012M%2CB25063_013M%2CB25063_014M%2CB25063_015M%2CB25063_016M%2CB25063_017M%2CB25063_018M%2CB25063_019M%2CB25063_020M%2CB25063_021M%2CB25063_022M%2CB25063_023M%2CB25063_024M%2CB25063_025M%2CB25063_026M&in=state%3A06%2C36%2C48%2C17%2C56", - "uncompressed_sha256": "7bc65ed550ec916e08cdd5dff018cf5c7c2fc8f7fce028a3169a1bb3b610db1a" + "retrieved": "2026-11-07", + "sha256": "0295c8131e2f87ae3edb1e2a7cb4c993eceebf21339bc9a66f3c7374bc42bd16", + "size_bytes": 45710, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=county%3A%2A&get=NAME%2CB25063_003E%2CB25063_004E%2CB25063_005E%2CB25063_006E%2CB25063_007E%2CB25063_008E%2CB25063_009E%2CB25063_010E%2CB25063_011E%2CB25063_012E%2CB25063_013E%2CB25063_014E%2CB25063_015E%2CB25063_016E%2CB25063_017E%2CB25063_018E%2CB25063_019E%2CB25063_020E%2CB25063_021E%2CB25063_022E%2CB25063_023E%2CB25063_024E%2CB25063_025E%2CB25063_026E%2CB25063_003M%2CB25063_004M%2CB25063_005M%2CB25063_006M%2CB25063_007M%2CB25063_008M%2CB25063_009M%2CB25063_010M%2CB25063_011M%2CB25063_012M%2CB25063_013M%2CB25063_014M%2CB25063_015M%2CB25063_016M%2CB25063_017M%2CB25063_018M%2CB25063_019M%2CB25063_020M%2CB25063_021M%2CB25063_022M%2CB25063_023M%2CB25063_024M%2CB25063_025M%2CB25063_026M&in=state%3A06%2C36%2C48%2C17%2C56%2C26", + "uncompressed_sha256": "559766b93a702be5ab41cc0bc2a50844069ec8aeecd2330382583570e8165390" } diff --git a/tests/fixtures/validation/api_2023_acs_acs5_60e7c44e97ce.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_60e7c44e97ce.json.gz deleted file mode 100644 index 7bfd364..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_60e7c44e97ce.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_60e7c44e97ce.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_60e7c44e97ce.json.gz.json deleted file mode 100644 index baf35f6..0000000 --- a/tests/fixtures/validation/api_2023_acs_acs5_60e7c44e97ce.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "fd050f36bf9e6ef3be22c0fe5451da0d11324cd1e5756ba8601125bc1905e40b", - "size_bytes": 605, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B11001%29", - "uncompressed_sha256": "0289a5a014015c63fb9e0c8c6ca50fd85a43480da42f75d0c01485948451bf86" -} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_6f18fa5a8326.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_6f18fa5a8326.json.gz new file mode 100644 index 0000000..d033e68 Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_6f18fa5a8326.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_6f18fa5a8326.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_6f18fa5a8326.json.gz.json new file mode 100644 index 0000000..9094547 --- /dev/null +++ b/tests/fixtures/validation/api_2023_acs_acs5_6f18fa5a8326.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "31ae99246079f690873c2aa3b521b9b5bbe5baaccd682269d24cbc72edb61833", + "size_bytes": 374, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B25003%29", + "uncompressed_sha256": "6db268f907a00f864383abb336d8edec6b25772caef9d13f339b943b23639c56" +} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_70f8e86f8fd6.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_70f8e86f8fd6.json.gz index 3f9de6d..fb093fd 100644 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_70f8e86f8fd6.json.gz and b/tests/fixtures/validation/api_2023_acs_acs5_70f8e86f8fd6.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_70f8e86f8fd6.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_70f8e86f8fd6.json.gz.json index cd8a95a..98c2a0c 100644 --- a/tests/fixtures/validation/api_2023_acs_acs5_70f8e86f8fd6.json.gz.json +++ b/tests/fixtures/validation/api_2023_acs_acs5_70f8e86f8fd6.json.gz.json @@ -1,8 +1,8 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "167f81279bea4227a1d7542fed5916edc1d527740b33463c5c6f145a24238420", + "retrieved": "2026-11-07", + "sha256": "16d9a6385de6e36731e15a49feed235df89b89d675d5a37005a5ddcdbd7a9f76", "size_bytes": 1201, "source_url": "https://api.census.gov/data/2023/acs/acs5?for=county%3A001%2C037&get=NAME%2Cgroup%28B01001%29&in=state%3A06", "uncompressed_sha256": "7e1e86cdda0d2d6a79bd09d62851dee3f4c06bd1fa4372ce45a354926eee8877" diff --git a/tests/fixtures/validation/api_2023_acs_acs5_7503d557a9c9.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_7503d557a9c9.json.gz deleted file mode 100644 index 870bf96..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_7503d557a9c9.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_7503d557a9c9.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_7503d557a9c9.json.gz.json deleted file mode 100644 index 0e35723..0000000 --- a/tests/fixtures/validation/api_2023_acs_acs5_7503d557a9c9.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "23d5371cc864e32e8332c1365b3bddab1f9e6da5a0e0128e08c1a46b8614636d", - "size_bytes": 942, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B19001%29", - "uncompressed_sha256": "89dec52c0092352191780b7b88893bc8bbcef0a7c2a1608c31db4a70633f166d" -} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_774f25c4224c.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_774f25c4224c.json.gz new file mode 100644 index 0000000..85138bd Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_774f25c4224c.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_774f25c4224c.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_774f25c4224c.json.gz.json new file mode 100644 index 0000000..dcd73ca --- /dev/null +++ b/tests/fixtures/validation/api_2023_acs_acs5_774f25c4224c.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "934eb0527938c9da7f8c40241c1b7321d963ff613d67d694c29bc5215faa0088", + "size_bytes": 216, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=B15003_024E%2CB15003_024M%2CB15003_025E%2CB15003_025M%2CB15003_001E%2CB15003_001M", + "uncompressed_sha256": "c8e411bef99d7ab9adc98ce1d0f83aadc8625d061ea7bd759c13c263de359d45" +} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_77e17240413d.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_77e17240413d.json.gz new file mode 100644 index 0000000..6ed413f Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_77e17240413d.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_77e17240413d.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_77e17240413d.json.gz.json new file mode 100644 index 0000000..3253dcb --- /dev/null +++ b/tests/fixtures/validation/api_2023_acs_acs5_77e17240413d.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "1c1fd0e2d9969ae41fa98a6903c85628bbfbe8b60bc0d34b7c9d2bb0c444c7d7", + "size_bytes": 2634, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B01001%29", + "uncompressed_sha256": "33be046198b80e8e85d7e81994544a6bd040bdef4abd660c481f9cf409390657" +} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_780aa33b35ef.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_780aa33b35ef.json.gz index 10361fb..9f70dd6 100644 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_780aa33b35ef.json.gz and b/tests/fixtures/validation/api_2023_acs_acs5_780aa33b35ef.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_780aa33b35ef.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_780aa33b35ef.json.gz.json index 34924a8..3b7db1a 100644 --- a/tests/fixtures/validation/api_2023_acs_acs5_780aa33b35ef.json.gz.json +++ b/tests/fixtures/validation/api_2023_acs_acs5_780aa33b35ef.json.gz.json @@ -1,8 +1,8 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "0fa68902012169c2c7cdb1f493dc39db7295b66c7d71ea0cc953cec0bba06e0c", + "retrieved": "2026-11-07", + "sha256": "ec2593a888f3de34aa129dbc2e3e6145eeffc238d70438e66b9b1ddef1cf74b0", "size_bytes": 163, "source_url": "https://api.census.gov/data/2023/acs/acs5?for=us%3A1&get=NAME%2Cgroup%28B25003%29", "uncompressed_sha256": "839c651b498146a105d7a9987b4d132a32088294c2ece5d6f6cea4b6434a9ad6" diff --git a/tests/fixtures/validation/api_2023_acs_acs5_79320aed9eb6.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_79320aed9eb6.json.gz deleted file mode 100644 index 3e77f1e..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_79320aed9eb6.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_7a140b509747.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_7a140b509747.json.gz index 76f0e70..a4b3281 100644 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_7a140b509747.json.gz and b/tests/fixtures/validation/api_2023_acs_acs5_7a140b509747.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_7a140b509747.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_7a140b509747.json.gz.json index 160e67d..b009e2f 100644 --- a/tests/fixtures/validation/api_2023_acs_acs5_7a140b509747.json.gz.json +++ b/tests/fixtures/validation/api_2023_acs_acs5_7a140b509747.json.gz.json @@ -1,8 +1,8 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "5579faa8fb62461da8c526ca1e313107485581ea098175b38ef56b0af5981e9b", + "retrieved": "2026-11-07", + "sha256": "76d4df66842a6ec56b21bdba9749622aa8769babe74d650684b09a64f1ec276b", "size_bytes": 950, "source_url": "https://api.census.gov/data/2023/acs/acs5?for=us%3A1&get=NAME%2Cgroup%28B01001%29", "uncompressed_sha256": "bcc7270a55454a15fadf695a57aa407801192e226a024c7c9fddc60a55abf20c" diff --git a/tests/fixtures/validation/api_2023_acs_acs5_7f3e6d3c3489.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_7f3e6d3c3489.json.gz index bf4da83..3e75019 100644 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_7f3e6d3c3489.json.gz and b/tests/fixtures/validation/api_2023_acs_acs5_7f3e6d3c3489.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_7f3e6d3c3489.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_7f3e6d3c3489.json.gz.json index c4a978d..2ada9a9 100644 --- a/tests/fixtures/validation/api_2023_acs_acs5_7f3e6d3c3489.json.gz.json +++ b/tests/fixtures/validation/api_2023_acs_acs5_7f3e6d3c3489.json.gz.json @@ -1,8 +1,8 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "3f3f71268f614bb8c79a6bad9ece6af0f428990e4d3eef1278c0d833262664af", + "retrieved": "2026-11-07", + "sha256": "95cae7c113dfd7435dc4dfd85cf41424afeab8c4c2b002efde88cd5b3240a4eb", "size_bytes": 1113, "source_url": "https://api.census.gov/data/2023/acs/acs5?for=us%3A1&get=NAME%2Cgroup%28B17001%29", "uncompressed_sha256": "e85963588184681607d647bfe1925218b6b1a4522baa8f24f56d84e69292dc96" diff --git a/tests/fixtures/validation/api_2023_acs_acs5_967d754c4c47.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_967d754c4c47.json.gz new file mode 100644 index 0000000..12c7191 Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_967d754c4c47.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_aac14f18cf45.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_967d754c4c47.json.gz.json similarity index 67% rename from tests/fixtures/validation/api_2023_acs_acs5_aac14f18cf45.json.gz.json rename to tests/fixtures/validation/api_2023_acs_acs5_967d754c4c47.json.gz.json index 07a4c7f..1f8db97 100644 --- a/tests/fixtures/validation/api_2023_acs_acs5_aac14f18cf45.json.gz.json +++ b/tests/fixtures/validation/api_2023_acs_acs5_967d754c4c47.json.gz.json @@ -1,9 +1,9 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "d593aed0a1fc1c019dabd11542314c862dd5c5f9d892fe11128fc04f14306cb3", - "size_bytes": 30235, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=county%3A%2A&get=NAME%2CB01003_001E%2CB01003_001M%2CB25001_001E%2CB25001_001M%2CB11001_001E%2CB11001_001M%2CB11001_002E%2CB11001_002M%2CB11001_003E%2CB11001_003M%2CB11001_004E%2CB11001_004M%2CB11001_005E%2CB11001_005M%2CB11001_006E%2CB11001_006M%2CB11001_007E%2CB11001_007M%2CB11001_008E%2CB11001_008M%2CB11001_009E%2CB11001_009M&in=state%3A06%2C36%2C48%2C17%2C56", - "uncompressed_sha256": "04334885f762a23b12417c226b2666b4b82ff32d6bba784ab82a08fb9e3a57da" + "retrieved": "2026-11-07", + "sha256": "fc550b106ec75e66fd13991725198239cd673baebb8c8482bf2edc3add5997c1", + "size_bytes": 35190, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=county%3A%2A&get=NAME%2CB01003_001E%2CB01003_001M%2CB25001_001E%2CB25001_001M%2CB11001_001E%2CB11001_001M%2CB11001_002E%2CB11001_002M%2CB11001_003E%2CB11001_003M%2CB11001_004E%2CB11001_004M%2CB11001_005E%2CB11001_005M%2CB11001_006E%2CB11001_006M%2CB11001_007E%2CB11001_007M%2CB11001_008E%2CB11001_008M%2CB11001_009E%2CB11001_009M&in=state%3A06%2C36%2C48%2C17%2C56%2C26", + "uncompressed_sha256": "8e910de02147b77d7926ac63f2f0b62b26158df776c8e9f81e803be92b739f22" } diff --git a/tests/fixtures/validation/api_2023_acs_acs5_971c5cfbed8b.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_971c5cfbed8b.json.gz deleted file mode 100644 index 50e5ff3..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_971c5cfbed8b.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_971c5cfbed8b.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_971c5cfbed8b.json.gz.json deleted file mode 100644 index 1f27489..0000000 --- a/tests/fixtures/validation/api_2023_acs_acs5_971c5cfbed8b.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "db0ce5f9117c36e5a44829c43f42ab078a0bef2f624b803c3985845f8d38ef0c", - "size_bytes": 2905, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B27010%29", - "uncompressed_sha256": "adf752c0cbaf3821510f9b05069c662369e26875734f1b4b89b743c05ba41bb7" -} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_98e66e89592e.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_98e66e89592e.json.gz new file mode 100644 index 0000000..9948bac Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_98e66e89592e.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_2da90149c21f.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_98e66e89592e.json.gz.json similarity index 75% rename from tests/fixtures/validation/api_2023_acs_acs5_2da90149c21f.json.gz.json rename to tests/fixtures/validation/api_2023_acs_acs5_98e66e89592e.json.gz.json index 6cd145f..f79eab9 100644 --- a/tests/fixtures/validation/api_2023_acs_acs5_2da90149c21f.json.gz.json +++ b/tests/fixtures/validation/api_2023_acs_acs5_98e66e89592e.json.gz.json @@ -1,9 +1,9 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "ba2b9450d73d9fb95b402de310d32f78bdf48131f85a7af7de80ad45b51bf411", - "size_bytes": 39319, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=county%3A%2A&get=NAME%2CB19001_002E%2CB19001_003E%2CB19001_004E%2CB19001_005E%2CB19001_006E%2CB19001_007E%2CB19001_008E%2CB19001_009E%2CB19001_010E%2CB19001_011E%2CB19001_012E%2CB19001_013E%2CB19001_014E%2CB19001_015E%2CB19001_016E%2CB19001_017E%2CB19001_002M%2CB19001_003M%2CB19001_004M%2CB19001_005M%2CB19001_006M%2CB19001_007M%2CB19001_008M%2CB19001_009M%2CB19001_010M%2CB19001_011M%2CB19001_012M%2CB19001_013M%2CB19001_014M%2CB19001_015M%2CB19001_016M%2CB19001_017M%2CB19013_001E%2CB19013_001M&in=state%3A06%2C36%2C48%2C17%2C56", - "uncompressed_sha256": "0a221b8971431d250db04b0b4974130391ea39ff12d7dfcd2ab14fab4955ac04" + "retrieved": "2026-11-07", + "sha256": "d4f4d1c4beab8aa93afeb684609fa141a090ebf90d53fb6ec2f285d7641e50fb", + "size_bytes": 45772, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=county%3A%2A&get=NAME%2CB19001_002E%2CB19001_003E%2CB19001_004E%2CB19001_005E%2CB19001_006E%2CB19001_007E%2CB19001_008E%2CB19001_009E%2CB19001_010E%2CB19001_011E%2CB19001_012E%2CB19001_013E%2CB19001_014E%2CB19001_015E%2CB19001_016E%2CB19001_017E%2CB19001_002M%2CB19001_003M%2CB19001_004M%2CB19001_005M%2CB19001_006M%2CB19001_007M%2CB19001_008M%2CB19001_009M%2CB19001_010M%2CB19001_011M%2CB19001_012M%2CB19001_013M%2CB19001_014M%2CB19001_015M%2CB19001_016M%2CB19001_017M%2CB19013_001E%2CB19013_001M&in=state%3A06%2C36%2C48%2C17%2C56%2C26", + "uncompressed_sha256": "c35b019464591d1ae6d1ca9caa645e34c704f68ac5dad5d9db3701edb8965f09" } diff --git a/tests/fixtures/validation/api_2023_acs_acs5_9a706ba9345b.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_9a706ba9345b.json.gz deleted file mode 100644 index 990db03..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_9a706ba9345b.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_9a706ba9345b.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_9a706ba9345b.json.gz.json deleted file mode 100644 index 340f8df..0000000 --- a/tests/fixtures/validation/api_2023_acs_acs5_9a706ba9345b.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "f804ec13a952236d4c56dd15f48233bfc3aad35cb3b8cd0b7b3d2ce10c2128c1", - "size_bytes": 197, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=B15003_024E%2CB15003_024M%2CB15003_025E%2CB15003_025M%2CB15003_001E%2CB15003_001M", - "uncompressed_sha256": "b678608c7382a366af03c5969617e5d51f315bfc9880de1ad667ab4a79a2e890" -} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_a67c66bab9eb.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_a67c66bab9eb.json.gz deleted file mode 100644 index cc381c7..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_a67c66bab9eb.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_a67c66bab9eb.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_a67c66bab9eb.json.gz.json deleted file mode 100644 index ce8b0f6..0000000 --- a/tests/fixtures/validation/api_2023_acs_acs5_a67c66bab9eb.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "c02fa68f2cb00e29f5a6806ed312053201088821119ae3a6141558ddcdaa4434", - "size_bytes": 1276, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B25063%29", - "uncompressed_sha256": "90e737d426177521ce4b4222b28a209012092517b6801e67efe5c763124ded68" -} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_aac14f18cf45.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_aac14f18cf45.json.gz deleted file mode 100644 index d7400fd..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_aac14f18cf45.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_b78143b986b2.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_b78143b986b2.json.gz deleted file mode 100644 index 9b6aa4a..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_b78143b986b2.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_b78143b986b2.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_b78143b986b2.json.gz.json deleted file mode 100644 index f8af8fa..0000000 --- a/tests/fixtures/validation/api_2023_acs_acs5_b78143b986b2.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "444b4a2de7886191829f2fdea0bdb8750b373b3cf3c5be0f41acecf12c4c6708", - "size_bytes": 1072, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2CB17001_002E%2CB17001_002M%2CB17001_001E%2CB17001_001M%2CB27010_017E%2CB27010_017M%2CB27010_033E%2CB27010_033M%2CB27010_050E%2CB27010_050M%2CB27010_066E%2CB27010_066M%2CB27010_001E%2CB27010_001M%2CB25003_002E%2CB25003_002M%2CB25003_001E%2CB25003_001M%2CB01001_020E%2CB01001_020M%2CB01001_021E%2CB01001_021M%2CB01001_022E%2CB01001_022M%2CB01001_023E%2CB01001_023M%2CB01001_024E%2CB01001_024M%2CB01001_025E%2CB01001_025M%2CB01001_044E%2CB01001_044M%2CB01001_045E%2CB01001_045M%2CB01001_046E%2CB01001_046M%2CB01001_047E%2CB01001_047M%2CB01001_048E%2CB01001_048M%2CB01001_049E%2CB01001_049M%2CB01001_001E%2CB01001_001M%2CB15003_022E%2CB15003_022M%2CB15003_023E%2CB15003_023M", - "uncompressed_sha256": "5759510f2ed787854e7b5fd8365f21a7ec19c38a61428896906565de383a1d2b" -} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_c0aa09f6ee7b.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_c0aa09f6ee7b.json.gz new file mode 100644 index 0000000..2d8ede3 Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_c0aa09f6ee7b.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_c0aa09f6ee7b.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_c0aa09f6ee7b.json.gz.json new file mode 100644 index 0000000..2b8c8a8 --- /dev/null +++ b/tests/fixtures/validation/api_2023_acs_acs5_c0aa09f6ee7b.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "4cde966e99fa0deee68ea1b5986bcf46f95f716d765fdbfcef235c194c0427f1", + "size_bytes": 1464, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B25063%29", + "uncompressed_sha256": "9473849f071857bfefc9aea0d0a619c30c1311f643b808720405be34ddcbdfa6" +} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_c18230916e36.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_c18230916e36.json.gz new file mode 100644 index 0000000..f844d66 Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_c18230916e36.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_030a7b2af5c4.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_c18230916e36.json.gz.json similarity index 78% rename from tests/fixtures/validation/api_2023_acs_acs5_030a7b2af5c4.json.gz.json rename to tests/fixtures/validation/api_2023_acs_acs5_c18230916e36.json.gz.json index dbf594c..d05fb29 100644 --- a/tests/fixtures/validation/api_2023_acs_acs5_030a7b2af5c4.json.gz.json +++ b/tests/fixtures/validation/api_2023_acs_acs5_c18230916e36.json.gz.json @@ -1,9 +1,9 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "6f90bd662ad29f33cf7ba3f5f26460f9658c8e9c6fce91bfcd6f334da41d4b90", - "size_bytes": 53530, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=county%3A%2A&get=NAME%2CB17001_002E%2CB17001_002M%2CB17001_001E%2CB17001_001M%2CB27010_017E%2CB27010_017M%2CB27010_033E%2CB27010_033M%2CB27010_050E%2CB27010_050M%2CB27010_066E%2CB27010_066M%2CB27010_001E%2CB27010_001M%2CB25003_002E%2CB25003_002M%2CB25003_001E%2CB25003_001M%2CB01001_020E%2CB01001_020M%2CB01001_021E%2CB01001_021M%2CB01001_022E%2CB01001_022M%2CB01001_023E%2CB01001_023M%2CB01001_024E%2CB01001_024M%2CB01001_025E%2CB01001_025M%2CB01001_044E%2CB01001_044M%2CB01001_045E%2CB01001_045M%2CB01001_046E%2CB01001_046M%2CB01001_047E%2CB01001_047M%2CB01001_048E%2CB01001_048M%2CB01001_049E%2CB01001_049M%2CB01001_001E%2CB01001_001M%2CB15003_022E%2CB15003_022M%2CB15003_023E%2CB15003_023M&in=state%3A06%2C36%2C48%2C17%2C56", - "uncompressed_sha256": "d6f891d4e6948a930e4e65d10458149ef8b99bae0c10b61049e7a827e7e6338f" + "retrieved": "2026-11-07", + "sha256": "e219a3e412955363489811977f751334ebe44e2058bf2328996b1f07e3262fcb", + "size_bytes": 62496, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=county%3A%2A&get=NAME%2CB17001_002E%2CB17001_002M%2CB17001_001E%2CB17001_001M%2CB27010_017E%2CB27010_017M%2CB27010_033E%2CB27010_033M%2CB27010_050E%2CB27010_050M%2CB27010_066E%2CB27010_066M%2CB27010_001E%2CB27010_001M%2CB25003_002E%2CB25003_002M%2CB25003_001E%2CB25003_001M%2CB01001_020E%2CB01001_020M%2CB01001_021E%2CB01001_021M%2CB01001_022E%2CB01001_022M%2CB01001_023E%2CB01001_023M%2CB01001_024E%2CB01001_024M%2CB01001_025E%2CB01001_025M%2CB01001_044E%2CB01001_044M%2CB01001_045E%2CB01001_045M%2CB01001_046E%2CB01001_046M%2CB01001_047E%2CB01001_047M%2CB01001_048E%2CB01001_048M%2CB01001_049E%2CB01001_049M%2CB01001_001E%2CB01001_001M%2CB15003_022E%2CB15003_022M%2CB15003_023E%2CB15003_023M&in=state%3A06%2C36%2C48%2C17%2C56%2C26", + "uncompressed_sha256": "601235ea2058015ffd80c5eee4c44949642f66da4665025650c0c27191784257" } diff --git a/tests/fixtures/validation/api_2023_acs_acs5_c3a1c69e7609.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_c3a1c69e7609.json.gz deleted file mode 100644 index 7483608..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_c3a1c69e7609.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_c3a1c69e7609.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_c3a1c69e7609.json.gz.json deleted file mode 100644 index 1fad3b5..0000000 --- a/tests/fixtures/validation/api_2023_acs_acs5_c3a1c69e7609.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "4b3e873b613c1021dfcec95d8c140a163c1657ec953487da0c4cfdbf8efea79e", - "size_bytes": 217, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B01003%29", - "uncompressed_sha256": "94565940b8386068b9845cb6c39ba06aacd88e8bbc7a9c00a9aa7d1b719cbd9a" -} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_cf59bdf95e4c.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_cf59bdf95e4c.json.gz deleted file mode 100644 index 9665368..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_cf59bdf95e4c.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_cf59bdf95e4c.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_cf59bdf95e4c.json.gz.json deleted file mode 100644 index 763821c..0000000 --- a/tests/fixtures/validation/api_2023_acs_acs5_cf59bdf95e4c.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "1dadf22c8431dab732b512048327b7b0502407fef393f4420c442b8ab11bb6c4", - "size_bytes": 331, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28B25003%29", - "uncompressed_sha256": "add5f00d4b81dbf33d8bd7a347889f0512da411cdc4c2a73e1ba5a9780e5fdbc" -} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_cprofile_306a53d9533e.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_cprofile_306a53d9533e.json.gz new file mode 100644 index 0000000..37492e7 Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_cprofile_306a53d9533e.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_cprofile_306a53d9533e.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_cprofile_306a53d9533e.json.gz.json new file mode 100644 index 0000000..52d0a26 --- /dev/null +++ b/tests/fixtures/validation/api_2023_acs_acs5_cprofile_306a53d9533e.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "ecb1aa7c8ee309f387a76b9b37f774b40a29f975a544cc2e3248d04f7c7fcef0", + "size_bytes": 6786, + "source_url": "https://api.census.gov/data/2023/acs/acs5/cprofile?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28CP03%29", + "uncompressed_sha256": "5ffcda6b8fc96e9826d039c96bac410d2f340482235eb75eb158edced9d30ab9" +} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_cprofile_51b39566a20d.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_cprofile_51b39566a20d.json.gz deleted file mode 100644 index 9d55969..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_cprofile_51b39566a20d.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_cprofile_51b39566a20d.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_cprofile_51b39566a20d.json.gz.json deleted file mode 100644 index 9f74063..0000000 --- a/tests/fixtures/validation/api_2023_acs_acs5_cprofile_51b39566a20d.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "62712cf817f1f53982cb2ceb7551b52cdd7a276076f9244db02a67f6a7af33f2", - "size_bytes": 5973, - "source_url": "https://api.census.gov/data/2023/acs/acs5/cprofile?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28CP03%29", - "uncompressed_sha256": "1dfaeb7eab806125dcc4d801a8b75f74837c403774594d95ad6e5203e449a51c" -} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_d4f0e840822e.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_d4f0e840822e.json.gz new file mode 100644 index 0000000..462227a Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_d4f0e840822e.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_d4f0e840822e.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_d4f0e840822e.json.gz.json new file mode 100644 index 0000000..ce3a655 --- /dev/null +++ b/tests/fixtures/validation/api_2023_acs_acs5_d4f0e840822e.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "16a7329b5e4cf27130d582f47c2f529921824858add508b95536e1a78b132e55", + "size_bytes": 3106, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B17001%29", + "uncompressed_sha256": "6e8b16fa877bffbd32fc59fda4f509ee075027372d1d735b60b7963049697a8b" +} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_d6dc3b2d81e5.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_d6dc3b2d81e5.json.gz index 0af2111..acd936e 100644 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_d6dc3b2d81e5.json.gz and b/tests/fixtures/validation/api_2023_acs_acs5_d6dc3b2d81e5.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_d6dc3b2d81e5.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_d6dc3b2d81e5.json.gz.json index 01d77a9..392d204 100644 --- a/tests/fixtures/validation/api_2023_acs_acs5_d6dc3b2d81e5.json.gz.json +++ b/tests/fixtures/validation/api_2023_acs_acs5_d6dc3b2d81e5.json.gz.json @@ -1,8 +1,8 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "3ebea6b02330f3df467929cd21bca9e231fc961e39fad6a236f890b89e08f9f2", + "retrieved": "2026-11-07", + "sha256": "6efd16661895891c1c27b58dec252548184d4f8ac67c733f245090863db9780c", "size_bytes": 399, "source_url": "https://api.census.gov/data/2023/acs/acs5?for=us%3A1&get=NAME%2Cgroup%28B19001%29", "uncompressed_sha256": "ef5a7e82a77f0000f94fd83b953ecfd94c18e3a7ab54c2908438856af146fec2" diff --git a/tests/fixtures/validation/api_2023_acs_acs5_dde0d0177348.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_dde0d0177348.json.gz new file mode 100644 index 0000000..b4dd82a Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_dde0d0177348.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_dde0d0177348.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_dde0d0177348.json.gz.json new file mode 100644 index 0000000..1347625 --- /dev/null +++ b/tests/fixtures/validation/api_2023_acs_acs5_dde0d0177348.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "1dc35c260e4604570ce93c23842aed349d9522eb59b9fe8a2ea66e942df66d7c", + "size_bytes": 694, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B11001%29", + "uncompressed_sha256": "ca240b91a49676eec4a36ac706eebb7eebbb00c25a8c171b1c1a661cd4a12091" +} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_eae21ad9d69e.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_eae21ad9d69e.json.gz new file mode 100644 index 0000000..d866360 Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_eae21ad9d69e.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_eae21ad9d69e.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_eae21ad9d69e.json.gz.json new file mode 100644 index 0000000..f22b649 --- /dev/null +++ b/tests/fixtures/validation/api_2023_acs_acs5_eae21ad9d69e.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "98ca4fd230eea715396b95828971355998d874999ec1c43b3f4c00d7681b7071", + "size_bytes": 3323, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B27010%29", + "uncompressed_sha256": "9cdada8abb8d790c9b5f3d671ed68a0b416ee72bf9d44dc1ccdbe2ba7a2455f7" +} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_f0c6714d1de9.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_f0c6714d1de9.json.gz new file mode 100644 index 0000000..d1a5da3 Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_f0c6714d1de9.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_79320aed9eb6.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_f0c6714d1de9.json.gz.json similarity index 56% rename from tests/fixtures/validation/api_2023_acs_acs5_79320aed9eb6.json.gz.json rename to tests/fixtures/validation/api_2023_acs_acs5_f0c6714d1de9.json.gz.json index 648d156..63e76aa 100644 --- a/tests/fixtures/validation/api_2023_acs_acs5_79320aed9eb6.json.gz.json +++ b/tests/fixtures/validation/api_2023_acs_acs5_f0c6714d1de9.json.gz.json @@ -1,9 +1,9 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "4ced3e0ca72e94b45f568ed40cd3f162e1b7453c4155cb1615a221dbdc6fcb0e", - "size_bytes": 3300, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=county%3A%2A&get=B25064_001E%2CB25064_001M&in=state%3A06%2C36%2C48%2C17%2C56", - "uncompressed_sha256": "d83e0fe9bd45cebb2afda75644e18ab58f4b9dccb81e90373a4364a537ffa039" + "retrieved": "2026-11-07", + "sha256": "74a5b1e2f532c43a46c00c9a6f780b16c9f01e32b4dfedc047162a22847b2e62", + "size_bytes": 3815, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=county%3A%2A&get=B25064_001E%2CB25064_001M&in=state%3A06%2C36%2C48%2C17%2C56%2C26", + "uncompressed_sha256": "672890f029716e4b374aa510005cd201cb2fa98ad12e86b3943cd8cc06236568" } diff --git a/tests/fixtures/validation/api_2023_acs_acs5_f11dc210c4a5.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_f11dc210c4a5.json.gz deleted file mode 100644 index baf8953..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_f11dc210c4a5.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_f11dc210c4a5.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_f11dc210c4a5.json.gz.json deleted file mode 100644 index 091450f..0000000 --- a/tests/fixtures/validation/api_2023_acs_acs5_f11dc210c4a5.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "b832b9a8b815b6f37d5b39b2787278e1dce7c22054591b578a3a62b12707b5d7", - "size_bytes": 581, - "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2CB01003_001E%2CB01003_001M%2CB25001_001E%2CB25001_001M%2CB11001_001E%2CB11001_001M%2CB11001_002E%2CB11001_002M%2CB11001_003E%2CB11001_003M%2CB11001_004E%2CB11001_004M%2CB11001_005E%2CB11001_005M%2CB11001_006E%2CB11001_006M%2CB11001_007E%2CB11001_007M%2CB11001_008E%2CB11001_008M%2CB11001_009E%2CB11001_009M", - "uncompressed_sha256": "e1728a03f0538c03f4861e6f932559931f0f770f6adff3c0887c3a333c3b734a" -} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_f148567ad740.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_f148567ad740.json.gz index 038923a..a23ba3a 100644 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_f148567ad740.json.gz and b/tests/fixtures/validation/api_2023_acs_acs5_f148567ad740.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_f148567ad740.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_f148567ad740.json.gz.json index c8caab3..36156ab 100644 --- a/tests/fixtures/validation/api_2023_acs_acs5_f148567ad740.json.gz.json +++ b/tests/fixtures/validation/api_2023_acs_acs5_f148567ad740.json.gz.json @@ -1,8 +1,8 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "1c466d4ae993f4daf8574ecf7c785bf2607691b637f05aa9af7df5744b79c6b1", + "retrieved": "2026-11-07", + "sha256": "ebc71b7748454cd9b3306e7ee3cfa699107f263f54a9b5e5e331edd37d9b6f00", "size_bytes": 5565, "source_url": "https://api.census.gov/data/2023/acs/acs5?for=county%3A%2A&get=NAME%2Cgroup%28B01001%29&in=state%3A56", "uncompressed_sha256": "d2229b1ad292f0552ef9d08a69347d83c783c11f4cb5bb64b4d34bcfbc938897" diff --git a/tests/fixtures/validation/api_2023_acs_acs5_f9abbc0fc806.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_f9abbc0fc806.json.gz new file mode 100644 index 0000000..5a15d8f Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_f9abbc0fc806.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_f9abbc0fc806.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_f9abbc0fc806.json.gz.json new file mode 100644 index 0000000..942c04e --- /dev/null +++ b/tests/fixtures/validation/api_2023_acs_acs5_f9abbc0fc806.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "4d56462ea9d8b05ba1ae074e77f96417c9da1fe3abd536f64a594c63d9bbecf4", + "size_bytes": 1078, + "source_url": "https://api.census.gov/data/2023/acs/acs5?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28B19001%29", + "uncompressed_sha256": "9999293e64fbe7e4801c413609fd23681fb3443d6ecb5e4968065350cce9f778" +} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_ffefb214408d.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_ffefb214408d.json.gz deleted file mode 100644 index e1b8d1c..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_ffefb214408d.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_profile_001e31c9b596.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_profile_001e31c9b596.json.gz deleted file mode 100644 index a0f99e7..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_profile_001e31c9b596.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_profile_14d5f5ba26ed.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_profile_14d5f5ba26ed.json.gz new file mode 100644 index 0000000..6784767 Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_profile_14d5f5ba26ed.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_profile_14d5f5ba26ed.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_profile_14d5f5ba26ed.json.gz.json new file mode 100644 index 0000000..019f44a --- /dev/null +++ b/tests/fixtures/validation/api_2023_acs_acs5_profile_14d5f5ba26ed.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "31435432e25851063d634f752c2910796ecb20642de7ec4d4321fbd40f21b1a0", + "size_bytes": 10590, + "source_url": "https://api.census.gov/data/2023/acs/acs5/profile?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2Cgroup%28DP03%29", + "uncompressed_sha256": "9c010c49ecdc9959d1090ab04dfe0699647d11805790d368ff7a1bfa3461c15b" +} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_profile_945df3e4a9ef.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_profile_945df3e4a9ef.json.gz deleted file mode 100644 index 0226543..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_profile_945df3e4a9ef.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_profile_945df3e4a9ef.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_profile_945df3e4a9ef.json.gz.json deleted file mode 100644 index d13a893..0000000 --- a/tests/fixtures/validation/api_2023_acs_acs5_profile_945df3e4a9ef.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "09bf1c814544fe1dbe9fbce599dbb4a91e7de0990693f4bff1a612b6aa81a648", - "size_bytes": 262, - "source_url": "https://api.census.gov/data/2023/acs/acs5/profile?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2CDP03_0128PE%2CDP03_0128PM%2CDP03_0099PE%2CDP03_0099PM%2CDP04_0046PE%2CDP04_0046PM%2CDP05_0024PE%2CDP05_0024PM%2CDP02_0068PE%2CDP02_0068PM", - "uncompressed_sha256": "0aace686da1c8b77096348ec36414cacaf2e67ed4cde51ff74f0f2bdbdfcf34d" -} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_profile_cd9d50f11b2e.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_profile_cd9d50f11b2e.json.gz new file mode 100644 index 0000000..657a990 Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_profile_cd9d50f11b2e.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_profile_001e31c9b596.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_profile_cd9d50f11b2e.json.gz.json similarity index 64% rename from tests/fixtures/validation/api_2023_acs_acs5_profile_001e31c9b596.json.gz.json rename to tests/fixtures/validation/api_2023_acs_acs5_profile_cd9d50f11b2e.json.gz.json index 5e3dbe0..562446c 100644 --- a/tests/fixtures/validation/api_2023_acs_acs5_profile_001e31c9b596.json.gz.json +++ b/tests/fixtures/validation/api_2023_acs_acs5_profile_cd9d50f11b2e.json.gz.json @@ -1,9 +1,9 @@ { "kind": "census_api_response", "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "cc243466a2ee5d52a1c937dd46cc8cc3990f2d8d4c8bb74b6b209546f1b78c58", - "size_bytes": 13175, - "source_url": "https://api.census.gov/data/2023/acs/acs5/profile?for=county%3A%2A&get=NAME%2CDP03_0128PE%2CDP03_0128PM%2CDP03_0099PE%2CDP03_0099PM%2CDP04_0046PE%2CDP04_0046PM%2CDP05_0024PE%2CDP05_0024PM%2CDP02_0068PE%2CDP02_0068PM&in=state%3A06%2C36%2C48%2C17%2C56", - "uncompressed_sha256": "7ed2b7147b95b58461fb922456ebe3e0060798704d5c039bb6abbcef2acc9756" + "retrieved": "2026-11-07", + "sha256": "dc508188bb4cd531a3226980917a3566d8f0c52b030a0e1570e761f5e83d6c9c", + "size_bytes": 15271, + "source_url": "https://api.census.gov/data/2023/acs/acs5/profile?for=county%3A%2A&get=NAME%2CDP03_0128PE%2CDP03_0128PM%2CDP03_0099PE%2CDP03_0099PM%2CDP04_0046PE%2CDP04_0046PM%2CDP05_0024PE%2CDP05_0024PM%2CDP02_0068PE%2CDP02_0068PM&in=state%3A06%2C36%2C48%2C17%2C56%2C26", + "uncompressed_sha256": "4476eaa27502f7f06eb0ca656013f91564e74f62424d863664033f693eaa74cc" } diff --git a/tests/fixtures/validation/api_2023_acs_acs5_profile_f20ad5fd71a4.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_profile_f20ad5fd71a4.json.gz deleted file mode 100644 index 3d4e8cd..0000000 Binary files a/tests/fixtures/validation/api_2023_acs_acs5_profile_f20ad5fd71a4.json.gz and /dev/null differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_profile_f20ad5fd71a4.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_profile_f20ad5fd71a4.json.gz.json deleted file mode 100644 index 8d3e5e3..0000000 --- a/tests/fixtures/validation/api_2023_acs_acs5_profile_f20ad5fd71a4.json.gz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kind": "census_api_response", - "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", - "retrieved": "2026-11-02", - "sha256": "5b2361d092612449f1f599b21e0d570eb0e2f8506be09965cd20b207d7addf79", - "size_bytes": 9362, - "source_url": "https://api.census.gov/data/2023/acs/acs5/profile?for=state%3A06%2C36%2C48%2C17%2C56&get=NAME%2Cgroup%28DP03%29", - "uncompressed_sha256": "705465e865538ff9d45b0ae1dc5bc0486adc9ad4ee7416d0405293c46df5ee25" -} diff --git a/tests/fixtures/validation/api_2023_acs_acs5_profile_ffe684b0a63d.json.gz b/tests/fixtures/validation/api_2023_acs_acs5_profile_ffe684b0a63d.json.gz new file mode 100644 index 0000000..b097b69 Binary files /dev/null and b/tests/fixtures/validation/api_2023_acs_acs5_profile_ffe684b0a63d.json.gz differ diff --git a/tests/fixtures/validation/api_2023_acs_acs5_profile_ffe684b0a63d.json.gz.json b/tests/fixtures/validation/api_2023_acs_acs5_profile_ffe684b0a63d.json.gz.json new file mode 100644 index 0000000..b269964 --- /dev/null +++ b/tests/fixtures/validation/api_2023_acs_acs5_profile_ffe684b0a63d.json.gz.json @@ -0,0 +1,9 @@ +{ + "kind": "census_api_response", + "note": "Parsed API JSON re-serialized compactly (content identical) and gzip-compressed with mtime=0. The API key is not part of the URL.", + "retrieved": "2026-11-07", + "sha256": "3013015d7fa629dd97bf2cd31a2645ebf4afdd5daa149c17c0a420b1f3fb8fde", + "size_bytes": 287, + "source_url": "https://api.census.gov/data/2023/acs/acs5/profile?for=state%3A06%2C36%2C48%2C17%2C56%2C26&get=NAME%2CDP03_0128PE%2CDP03_0128PM%2CDP03_0099PE%2CDP03_0099PM%2CDP04_0046PE%2CDP04_0046PM%2CDP05_0024PE%2CDP05_0024PM%2CDP02_0068PE%2CDP02_0068PM", + "uncompressed_sha256": "889023918a8b63aa2de3d4b5cafdec377591462b96a0560c9393b70f585db85e" +} diff --git a/tests/fixtures/validation/sf_2023_b01003_rows.txt.gz b/tests/fixtures/validation/sf_2023_b01003_rows.txt.gz index 6e74407..dd1c5c6 100644 Binary files a/tests/fixtures/validation/sf_2023_b01003_rows.txt.gz and b/tests/fixtures/validation/sf_2023_b01003_rows.txt.gz differ diff --git a/tests/fixtures/validation/sf_2023_b01003_rows.txt.gz.json b/tests/fixtures/validation/sf_2023_b01003_rows.txt.gz.json index d337d71..b6e3ec1 100644 --- a/tests/fixtures/validation/sf_2023_b01003_rows.txt.gz.json +++ b/tests/fixtures/validation/sf_2023_b01003_rows.txt.gz.json @@ -2,10 +2,10 @@ "byte_range": "bytes=0-262143 of 18311997", "fetched_prefix_sha256": "6304ab6afa67dfe3cb41185b8c33a86146c8362d8526da96ed56fe831be680e9", "kind": "summary_file_rows", - "retrieved": "2026-11-02", - "selection": "header line and the rows whose GEO_ID is one of 0100000US,0400000US06,0400000US36,0400000US48,0400000US17,0400000US56", - "sha256": "88e8cf6a1396706af84dbf98c89e05e692b4ebca5412fc016c2d42fed673a72f", - "size_bytes": 118, + "retrieved": "2026-11-07", + "selection": "header line and the rows whose GEO_ID is one of 0100000US,0400000US06,0400000US36,0400000US48,0400000US17,0400000US56,0400000US26", + "sha256": "7d63b842f0af49c31d5729fca27e548ff17758dd823b7a10491615f27858352c", + "size_bytes": 124, "source_url": "https://www2.census.gov/programs-surveys/acs/summary_file/2023/table-based-SF/data/5YRData/acsdt5y2023-b01003.dat", "transformation": "lines copied verbatim from the fetched prefix; gzip mtime=0" } diff --git a/tests/fixtures/validation/sf_2023_b17001_rows.txt.gz b/tests/fixtures/validation/sf_2023_b17001_rows.txt.gz index 5c9659a..82e9e4a 100644 Binary files a/tests/fixtures/validation/sf_2023_b17001_rows.txt.gz and b/tests/fixtures/validation/sf_2023_b17001_rows.txt.gz differ diff --git a/tests/fixtures/validation/sf_2023_b17001_rows.txt.gz.json b/tests/fixtures/validation/sf_2023_b17001_rows.txt.gz.json index d69c00b..5a09250 100644 --- a/tests/fixtures/validation/sf_2023_b17001_rows.txt.gz.json +++ b/tests/fixtures/validation/sf_2023_b17001_rows.txt.gz.json @@ -2,10 +2,10 @@ "byte_range": "bytes=0-262143 of 118402375", "fetched_prefix_sha256": "a64ba9e2c8c8cfe91411fd7d662167fe3fa9fac99f1d640bfd5b42d6e38d3edf", "kind": "summary_file_rows", - "retrieved": "2026-11-02", - "selection": "header line and the rows whose GEO_ID is one of 0100000US,0400000US06,0400000US36,0400000US48,0400000US17,0400000US56", - "sha256": "46ed0e0239c5348bb49d905281096e5b9cec91a672652d50775c7242ff770464", - "size_bytes": 2508, + "retrieved": "2026-11-07", + "selection": "header line and the rows whose GEO_ID is one of 0100000US,0400000US06,0400000US36,0400000US48,0400000US17,0400000US56,0400000US26", + "sha256": "1431a555124cc777bd51acafbc54473f22da450c37321d3aa0bdc24de6b22de8", + "size_bytes": 2839, "source_url": "https://www2.census.gov/programs-surveys/acs/summary_file/2023/table-based-SF/data/5YRData/acsdt5y2023-b17001.dat", "transformation": "lines copied verbatim from the fetched prefix; gzip mtime=0" } diff --git a/tests/fixtures/validation/sf_2023_b19001_rows.txt.gz b/tests/fixtures/validation/sf_2023_b19001_rows.txt.gz index ebcdac2..b54fb49 100644 Binary files a/tests/fixtures/validation/sf_2023_b19001_rows.txt.gz and b/tests/fixtures/validation/sf_2023_b19001_rows.txt.gz differ diff --git a/tests/fixtures/validation/sf_2023_b19001_rows.txt.gz.json b/tests/fixtures/validation/sf_2023_b19001_rows.txt.gz.json index 10c9767..be4ad9b 100644 --- a/tests/fixtures/validation/sf_2023_b19001_rows.txt.gz.json +++ b/tests/fixtures/validation/sf_2023_b19001_rows.txt.gz.json @@ -2,10 +2,10 @@ "byte_range": "bytes=0-262143 of 68859942", "fetched_prefix_sha256": "2779080ea2b2685fd2d1c3562176f2b16e52f02972e6c9881ef1ad9e97708de6", "kind": "summary_file_rows", - "retrieved": "2026-11-02", - "selection": "header line and the rows whose GEO_ID is one of 0100000US,0400000US06,0400000US36,0400000US48,0400000US17,0400000US56", - "sha256": "d515cec225085f671aea2cc4321c76c384f8fe9b97801e60301e45214e4f3438", - "size_bytes": 788, + "retrieved": "2026-11-07", + "selection": "header line and the rows whose GEO_ID is one of 0100000US,0400000US06,0400000US36,0400000US48,0400000US17,0400000US56,0400000US26", + "sha256": "fca9a49c1b3ddbacdae2a117713e3dc3cc305a0d29519a2fe65786c0d9fa6c37", + "size_bytes": 891, "source_url": "https://www2.census.gov/programs-surveys/acs/summary_file/2023/table-based-SF/data/5YRData/acsdt5y2023-b19001.dat", "transformation": "lines copied verbatim from the fetched prefix; gzip mtime=0" } diff --git a/tests/fixtures/validation/sf_2023_b25003_rows.txt.gz b/tests/fixtures/validation/sf_2023_b25003_rows.txt.gz index 434fdbc..058fc27 100644 Binary files a/tests/fixtures/validation/sf_2023_b25003_rows.txt.gz and b/tests/fixtures/validation/sf_2023_b25003_rows.txt.gz differ diff --git a/tests/fixtures/validation/sf_2023_b25003_rows.txt.gz.json b/tests/fixtures/validation/sf_2023_b25003_rows.txt.gz.json index 443908d..d422a34 100644 --- a/tests/fixtures/validation/sf_2023_b25003_rows.txt.gz.json +++ b/tests/fixtures/validation/sf_2023_b25003_rows.txt.gz.json @@ -2,10 +2,10 @@ "byte_range": "bytes=0-262143 of 26878591", "fetched_prefix_sha256": "6ccd78431901d223ce1fb560363b329c4b54b4cae67891f20d2c2ab585bb5805", "kind": "summary_file_rows", - "retrieved": "2026-11-02", - "selection": "header line and the rows whose GEO_ID is one of 0100000US,0400000US06,0400000US36,0400000US48,0400000US17,0400000US56", - "sha256": "d94f25798e68b4a7fa4a6bd53ef72e43261eb04cea7223b4efaddc28bd859311", - "size_bytes": 218, + "retrieved": "2026-11-07", + "selection": "header line and the rows whose GEO_ID is one of 0100000US,0400000US06,0400000US36,0400000US48,0400000US17,0400000US56,0400000US26", + "sha256": "ab706655f02cfb7dc1428c44ae82fc3e18b3cae3f9bd58dd0f25f5168c972b37", + "size_bytes": 241, "source_url": "https://www2.census.gov/programs-surveys/acs/summary_file/2023/table-based-SF/data/5YRData/acsdt5y2023-b25003.dat", "transformation": "lines copied verbatim from the fetched prefix; gzip mtime=0" } diff --git a/tests/fixtures/validation/vre_2019_040_B01001.csv.gz b/tests/fixtures/validation/vre_2019_040_B01001.csv.gz index 65be2a1..b26a368 100644 Binary files a/tests/fixtures/validation/vre_2019_040_B01001.csv.gz and b/tests/fixtures/validation/vre_2019_040_B01001.csv.gz differ diff --git a/tests/fixtures/validation/vre_2019_040_B01001.csv.gz.json b/tests/fixtures/validation/vre_2019_040_B01001.csv.gz.json index ee5428b..22994f9 100644 --- a/tests/fixtures/validation/vre_2019_040_B01001.csv.gz.json +++ b/tests/fixtures/validation/vre_2019_040_B01001.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 247, + "data_rows": 296, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "bbe2554a17e54d7699edaa325a9831271d65a82a4879cf9faf8d22e028ac37ad", - "size_bytes": 58747, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "26f8863d60fbbffda11962dc0c1a6fba6dfee21d95363ee45ddcc1e73237d75a", + "size_bytes": 70225, "source_sha256": "70fa54516fe9dc727c9a9d8400278fad2d4911918690da61000d507e725d9104", "source_size_bytes": 576776, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2019/data/5-year/040/B01001.csv.zip", diff --git a/tests/fixtures/validation/vre_2019_040_B01003.csv.gz b/tests/fixtures/validation/vre_2019_040_B01003.csv.gz index 6b25509..f6a3035 100644 Binary files a/tests/fixtures/validation/vre_2019_040_B01003.csv.gz and b/tests/fixtures/validation/vre_2019_040_B01003.csv.gz differ diff --git a/tests/fixtures/validation/vre_2019_040_B01003.csv.gz.json b/tests/fixtures/validation/vre_2019_040_B01003.csv.gz.json index 20b0968..7820426 100644 --- a/tests/fixtures/validation/vre_2019_040_B01003.csv.gz.json +++ b/tests/fixtures/validation/vre_2019_040_B01003.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 7, + "data_rows": 8, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "85e1e90d5dc6ec620ec30f3eb5521edc62cd21cbfa5e7c33938c1dd5c9cf8224", - "size_bytes": 458, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "6074182509d91262004cef376c4201bd858126dfc4c4e38452fd093d7208a180", + "size_bytes": 490, "source_sha256": "1ae2d9175ef9181317c3f9da608487fea3884db1f0074c253f193b669ec796db", "source_size_bytes": 1722, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2019/data/5-year/040/B01003.csv.zip", diff --git a/tests/fixtures/validation/vre_2019_040_B11001.csv.gz b/tests/fixtures/validation/vre_2019_040_B11001.csv.gz index f839f24..0228b7a 100644 Binary files a/tests/fixtures/validation/vre_2019_040_B11001.csv.gz and b/tests/fixtures/validation/vre_2019_040_B11001.csv.gz differ diff --git a/tests/fixtures/validation/vre_2019_040_B11001.csv.gz.json b/tests/fixtures/validation/vre_2019_040_B11001.csv.gz.json index b651982..701c3e5 100644 --- a/tests/fixtures/validation/vre_2019_040_B11001.csv.gz.json +++ b/tests/fixtures/validation/vre_2019_040_B11001.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 47, + "data_rows": 56, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "c50c575f1f43938fdf78fdd306d5900aa4502e34fa464b157338d72c679bf34f", - "size_bytes": 12744, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "76e33ed0c9536e01a4389da9d48d3460f7b3cbd18646fb701d45f6ba7523f4c9", + "size_bytes": 15240, "source_sha256": "28d53b05a13f410cdda884a0b5c48b12158c4f8e659efb2c55add119f82fe140", "source_size_bytes": 121859, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2019/data/5-year/040/B11001.csv.zip", diff --git a/tests/fixtures/validation/vre_2019_040_B17001.csv.gz b/tests/fixtures/validation/vre_2019_040_B17001.csv.gz index b71be56..b0f9a77 100644 Binary files a/tests/fixtures/validation/vre_2019_040_B17001.csv.gz and b/tests/fixtures/validation/vre_2019_040_B17001.csv.gz differ diff --git a/tests/fixtures/validation/vre_2019_040_B17001.csv.gz.json b/tests/fixtures/validation/vre_2019_040_B17001.csv.gz.json index d22e5d0..1635e75 100644 --- a/tests/fixtures/validation/vre_2019_040_B17001.csv.gz.json +++ b/tests/fixtures/validation/vre_2019_040_B17001.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 297, + "data_rows": 356, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "d799d8a4c8640b8e6dc042a7d9880d07062f632e0b0f0e18be7fa2d5f87e843c", - "size_bytes": 73581, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "0681614b696463e566fb3925c17230d950ee1e1e337c61c6752a29d4e1a880f8", + "size_bytes": 87877, "source_sha256": "1491b30c992019b9a0b1f0302f211d697b55723dd05bab81672efd31d5ac49cf", "source_size_bytes": 712509, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2019/data/5-year/040/B17001.csv.zip", diff --git a/tests/fixtures/validation/vre_2019_040_B19001.csv.gz b/tests/fixtures/validation/vre_2019_040_B19001.csv.gz index b080d70..ba97966 100644 Binary files a/tests/fixtures/validation/vre_2019_040_B19001.csv.gz and b/tests/fixtures/validation/vre_2019_040_B19001.csv.gz differ diff --git a/tests/fixtures/validation/vre_2019_040_B19001.csv.gz.json b/tests/fixtures/validation/vre_2019_040_B19001.csv.gz.json index 08a4dea..30c0093 100644 --- a/tests/fixtures/validation/vre_2019_040_B19001.csv.gz.json +++ b/tests/fixtures/validation/vre_2019_040_B19001.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 87, + "data_rows": 104, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "6673002ed8f6e278db2958e5930061b7b45fb8d55d8118478e0c404c1feb9d19", - "size_bytes": 22321, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "99392b69c5bea263a8ead543ca992185c877c4f097ece874e53d226c563f0176", + "size_bytes": 26702, "source_sha256": "99f5783bddc7fff1e11559022e5ce75de4aafd8864ab7bc60c6b5d1faf5ddb95", "source_size_bytes": 215728, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2019/data/5-year/040/B19001.csv.zip", diff --git a/tests/fixtures/validation/vre_2019_040_B25003.csv.gz b/tests/fixtures/validation/vre_2019_040_B25003.csv.gz index b1c1341..fa69e80 100644 Binary files a/tests/fixtures/validation/vre_2019_040_B25003.csv.gz and b/tests/fixtures/validation/vre_2019_040_B25003.csv.gz differ diff --git a/tests/fixtures/validation/vre_2019_040_B25003.csv.gz.json b/tests/fixtures/validation/vre_2019_040_B25003.csv.gz.json index d96c4fb..d2dc642 100644 --- a/tests/fixtures/validation/vre_2019_040_B25003.csv.gz.json +++ b/tests/fixtures/validation/vre_2019_040_B25003.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 17, + "data_rows": 20, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "c6375a9dfe0ec1c70f1502380f130d3b627e098b226dea259adae320e9a9bc25", - "size_bytes": 4484, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "fbc4675f423b2dfcf38d6170f53df194554ce0a8cc12228c225a9dde3f0cfd28", + "size_bytes": 5305, "source_sha256": "67e15223ac776465691533a73ac2f4693344eda9554959b236264d9be8b7d52a", "source_size_bytes": 42297, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2019/data/5-year/040/B25003.csv.zip", diff --git a/tests/fixtures/validation/vre_2019_040_B25063.csv.gz b/tests/fixtures/validation/vre_2019_040_B25063.csv.gz index 0a2691a..4a6a312 100644 Binary files a/tests/fixtures/validation/vre_2019_040_B25063.csv.gz and b/tests/fixtures/validation/vre_2019_040_B25063.csv.gz differ diff --git a/tests/fixtures/validation/vre_2019_040_B25063.csv.gz.json b/tests/fixtures/validation/vre_2019_040_B25063.csv.gz.json index 2654b8e..d11fab7 100644 --- a/tests/fixtures/validation/vre_2019_040_B25063.csv.gz.json +++ b/tests/fixtures/validation/vre_2019_040_B25063.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 137, + "data_rows": 164, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "4b0b3969eca2b4a4eb098b1227c50d5ed7d772fabe06f8997d7be9f9abc356b1", - "size_bytes": 31908, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "511a4f9b095efbfbe82eee6546590883cb0a106887f04c87a56d7e4d68b88b12", + "size_bytes": 38144, "source_sha256": "a930a11cbb44d110c5170af1eca80fb6d2be4c5a8c9e2f7e06d0bfaf6fbdee4d", "source_size_bytes": 301927, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2019/data/5-year/040/B25063.csv.zip", diff --git a/tests/fixtures/validation/vre_2019_040_B27010.csv.gz b/tests/fixtures/validation/vre_2019_040_B27010.csv.gz index 1eb17e1..c5d2b01 100644 Binary files a/tests/fixtures/validation/vre_2019_040_B27010.csv.gz and b/tests/fixtures/validation/vre_2019_040_B27010.csv.gz differ diff --git a/tests/fixtures/validation/vre_2019_040_B27010.csv.gz.json b/tests/fixtures/validation/vre_2019_040_B27010.csv.gz.json index 9fe796e..173b12f 100644 --- a/tests/fixtures/validation/vre_2019_040_B27010.csv.gz.json +++ b/tests/fixtures/validation/vre_2019_040_B27010.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 332, + "data_rows": 398, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "67d738d778ce0b1d7becd334797258e2a490e611b9be767c37621ada561df5c2", - "size_bytes": 78652, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "09219f43a7fb356f1d188e7b21b25072d43494afbddef2545246f2e81eb5a1c5", + "size_bytes": 93911, "source_sha256": "9f54846cd0dad60746cecdf04956d4bd6a85b06f10db78d730060124e8822119", "source_size_bytes": 763185, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2019/data/5-year/040/B27010.csv.zip", diff --git a/tests/fixtures/validation/vre_2019_050_B25003.csv.gz b/tests/fixtures/validation/vre_2019_050_B25003.csv.gz index 10ebba4..6503e39 100644 Binary files a/tests/fixtures/validation/vre_2019_050_B25003.csv.gz and b/tests/fixtures/validation/vre_2019_050_B25003.csv.gz differ diff --git a/tests/fixtures/validation/vre_2019_050_B25003.csv.gz.json b/tests/fixtures/validation/vre_2019_050_B25003.csv.gz.json index 02a36e3..f667e28 100644 --- a/tests/fixtures/validation/vre_2019_050_B25003.csv.gz.json +++ b/tests/fixtures/validation/vre_2019_050_B25003.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 1000, + "data_rows": 1166, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 050: header, the table title/universe rows, and the rows of states 06,36,48,17,56 for variables B25003_001,B25003_002", - "sha256": "0cf936e2201e1df0f04e3f0ecf6e45c24845718aeb7e3b574802335bd0ac6a2e", - "size_bytes": 205857, + "retrieved": "2026-11-07", + "selection": "summary level 050: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26 for variables B25003_001,B25003_002", + "sha256": "b3b238890bf7fda338258e89d82c8292eae23276c2718371e34603e50b4c0154", + "size_bytes": 240752, "source_sha256": "b0cbdab927b427839e5d6a9bda4afa5fc36dd8bd243f38d7101a39effe6170c6", "source_size_bytes": 1914276, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2019/data/5-year/050/B25003.csv.zip", diff --git a/tests/fixtures/validation/vre_2023_040_B01001.csv.gz b/tests/fixtures/validation/vre_2023_040_B01001.csv.gz index 28667d9..f3f3cbd 100644 Binary files a/tests/fixtures/validation/vre_2023_040_B01001.csv.gz and b/tests/fixtures/validation/vre_2023_040_B01001.csv.gz differ diff --git a/tests/fixtures/validation/vre_2023_040_B01001.csv.gz.json b/tests/fixtures/validation/vre_2023_040_B01001.csv.gz.json index 141aa63..68f01f4 100644 --- a/tests/fixtures/validation/vre_2023_040_B01001.csv.gz.json +++ b/tests/fixtures/validation/vre_2023_040_B01001.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 247, + "data_rows": 296, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "883480cfdf9ce60fe7194eab0736782ab63f8e3f9f7756ef9d2bbf27f7cd8c73", - "size_bytes": 59341, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "35d493d1ece71c97b63523f2fe5cb58620b55f864b88939a043e377cbe7a7f7c", + "size_bytes": 71029, "source_sha256": "55c09e98dce63074b64a37a8d81b871928f4172f667f8e52f62afc893a75a347", "source_size_bytes": 583494, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2023/data/5-year/040/B01001.csv.zip", diff --git a/tests/fixtures/validation/vre_2023_040_B01003.csv.gz b/tests/fixtures/validation/vre_2023_040_B01003.csv.gz index 5e34355..2dbe996 100644 Binary files a/tests/fixtures/validation/vre_2023_040_B01003.csv.gz and b/tests/fixtures/validation/vre_2023_040_B01003.csv.gz differ diff --git a/tests/fixtures/validation/vre_2023_040_B01003.csv.gz.json b/tests/fixtures/validation/vre_2023_040_B01003.csv.gz.json index 9a3aa76..4a106bd 100644 --- a/tests/fixtures/validation/vre_2023_040_B01003.csv.gz.json +++ b/tests/fixtures/validation/vre_2023_040_B01003.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 7, + "data_rows": 8, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "8c7cc902dcf1dbe13d4927592752ab671b1944cf1b567e834d7e324ee1d65b73", - "size_bytes": 449, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "8f0c958c359f450a3b378a6135eadb9726931f11ec840830a4421e6d82de174d", + "size_bytes": 478, "source_sha256": "ac1a9b84894d7dfb2f43c1abba36ca2747d8b0b06362ccd48424914d2b203f37", "source_size_bytes": 1753, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2023/data/5-year/040/B01003.csv.zip", diff --git a/tests/fixtures/validation/vre_2023_040_B11001.csv.gz b/tests/fixtures/validation/vre_2023_040_B11001.csv.gz index c09a1c7..1956888 100644 Binary files a/tests/fixtures/validation/vre_2023_040_B11001.csv.gz and b/tests/fixtures/validation/vre_2023_040_B11001.csv.gz differ diff --git a/tests/fixtures/validation/vre_2023_040_B11001.csv.gz.json b/tests/fixtures/validation/vre_2023_040_B11001.csv.gz.json index 7512b2a..0edb317 100644 --- a/tests/fixtures/validation/vre_2023_040_B11001.csv.gz.json +++ b/tests/fixtures/validation/vre_2023_040_B11001.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 47, + "data_rows": 56, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "11d69257bae68dbc2d4500a11caad2c250404af65061ef3f4dc1f60c020ce4f1", - "size_bytes": 12840, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "057d4437aa4737fbe4ea4fbaf002752a7e979d845896071ed0937f24b1428627", + "size_bytes": 15325, "source_sha256": "a0b8987c45b05e8c0dfc6e593197aaefb4f5ecc24af8a6b27bcef174c0e42c3a", "source_size_bytes": 123546, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2023/data/5-year/040/B11001.csv.zip", diff --git a/tests/fixtures/validation/vre_2023_040_B17001.csv.gz b/tests/fixtures/validation/vre_2023_040_B17001.csv.gz index 3dea84a..030ee21 100644 Binary files a/tests/fixtures/validation/vre_2023_040_B17001.csv.gz and b/tests/fixtures/validation/vre_2023_040_B17001.csv.gz differ diff --git a/tests/fixtures/validation/vre_2023_040_B17001.csv.gz.json b/tests/fixtures/validation/vre_2023_040_B17001.csv.gz.json index 2cd86c8..49ad7fe 100644 --- a/tests/fixtures/validation/vre_2023_040_B17001.csv.gz.json +++ b/tests/fixtures/validation/vre_2023_040_B17001.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 297, + "data_rows": 356, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "abf463c70e5a44a1bda9d6d0588f6e2311bf80ce81d4d7ecd82ae45192c00f00", - "size_bytes": 74472, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "0912631f66e718f25dd9682ad08fbc1a86969340f0c5c5d4300b7aa5f091d2a9", + "size_bytes": 88955, "source_sha256": "ea10dff0d6f47d14a87d15a2a12ac38bf31a27ec8df93f209d575aaa71f18024", "source_size_bytes": 717717, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2023/data/5-year/040/B17001.csv.zip", diff --git a/tests/fixtures/validation/vre_2023_040_B19001.csv.gz b/tests/fixtures/validation/vre_2023_040_B19001.csv.gz index e25d671..0f4765c 100644 Binary files a/tests/fixtures/validation/vre_2023_040_B19001.csv.gz and b/tests/fixtures/validation/vre_2023_040_B19001.csv.gz differ diff --git a/tests/fixtures/validation/vre_2023_040_B19001.csv.gz.json b/tests/fixtures/validation/vre_2023_040_B19001.csv.gz.json index b436c06..71916bb 100644 --- a/tests/fixtures/validation/vre_2023_040_B19001.csv.gz.json +++ b/tests/fixtures/validation/vre_2023_040_B19001.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 87, + "data_rows": 104, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "ba1c7fb77461e969539b44e25ceb71629b3ac8e94bc47146649cab6c4fa2a35e", - "size_bytes": 22544, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "214210b8a4e5e7b81aa94aab5b45a60ff8086466b78b4bff60ee82999a4dfb7a", + "size_bytes": 26950, "source_sha256": "aa9c9438345aadfcdc59f3e0967d9878c2d07a833b1313103e661415e588f8f6", "source_size_bytes": 217317, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2023/data/5-year/040/B19001.csv.zip", diff --git a/tests/fixtures/validation/vre_2023_040_B25003.csv.gz b/tests/fixtures/validation/vre_2023_040_B25003.csv.gz index 71d8f63..21703b1 100644 Binary files a/tests/fixtures/validation/vre_2023_040_B25003.csv.gz and b/tests/fixtures/validation/vre_2023_040_B25003.csv.gz differ diff --git a/tests/fixtures/validation/vre_2023_040_B25003.csv.gz.json b/tests/fixtures/validation/vre_2023_040_B25003.csv.gz.json index a79937f..fa74a7b 100644 --- a/tests/fixtures/validation/vre_2023_040_B25003.csv.gz.json +++ b/tests/fixtures/validation/vre_2023_040_B25003.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 17, + "data_rows": 20, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "bfc37d07611141b07ef88b0bfe32bed3096e2e87a5ff4e45e5781114f6428997", - "size_bytes": 4508, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "a384ab644c23c487bb188fc98ae4b4008eccf0e31961994842b9e73c617446a5", + "size_bytes": 5331, "source_sha256": "46054db9a8c0c1b8562884075713a949c26de2748f870cb5f8044d536a00d26e", "source_size_bytes": 42610, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2023/data/5-year/040/B25003.csv.zip", diff --git a/tests/fixtures/validation/vre_2023_040_B25063.csv.gz b/tests/fixtures/validation/vre_2023_040_B25063.csv.gz index 9745543..2f4ccaf 100644 Binary files a/tests/fixtures/validation/vre_2023_040_B25063.csv.gz and b/tests/fixtures/validation/vre_2023_040_B25063.csv.gz differ diff --git a/tests/fixtures/validation/vre_2023_040_B25063.csv.gz.json b/tests/fixtures/validation/vre_2023_040_B25063.csv.gz.json index 9be2c6d..0675bae 100644 --- a/tests/fixtures/validation/vre_2023_040_B25063.csv.gz.json +++ b/tests/fixtures/validation/vre_2023_040_B25063.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 137, + "data_rows": 164, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "a18080e12a9d38c79cf02e4e404b90f0feb9fb26ac4df52b4f27625dda2a5eeb", - "size_bytes": 32060, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "0f219c094ac9eafea41bc7132ce805f3d7f26db4121e0f172c4ec26cdf816451", + "size_bytes": 38276, "source_sha256": "2817b6bf3f1f235d7b82a6c0e71b1d034f508b5220c743a12de3912370729ddb", "source_size_bytes": 301581, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2023/data/5-year/040/B25063.csv.zip", diff --git a/tests/fixtures/validation/vre_2023_040_B27010.csv.gz b/tests/fixtures/validation/vre_2023_040_B27010.csv.gz index cb5f51a..276d21a 100644 Binary files a/tests/fixtures/validation/vre_2023_040_B27010.csv.gz and b/tests/fixtures/validation/vre_2023_040_B27010.csv.gz differ diff --git a/tests/fixtures/validation/vre_2023_040_B27010.csv.gz.json b/tests/fixtures/validation/vre_2023_040_B27010.csv.gz.json index f22a468..b139d99 100644 --- a/tests/fixtures/validation/vre_2023_040_B27010.csv.gz.json +++ b/tests/fixtures/validation/vre_2023_040_B27010.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 332, + "data_rows": 398, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56", - "sha256": "c09a8b411f784c17861c3a9808ba54d49385f21ec87c6b4bc13e970863794d6e", - "size_bytes": 79418, + "retrieved": "2026-11-07", + "selection": "summary level 040: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26", + "sha256": "dc35f0d993baf97954cfecf19e650057817f696db4af432379ca7e2957bc569e", + "size_bytes": 95084, "source_sha256": "bab2c1640213dc0b542bcf8cb0f728493418767087e7323036ed4838462b0c87", "source_size_bytes": 771664, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2023/data/5-year/040/B27010.csv.zip", diff --git a/tests/fixtures/validation/vre_2023_050_B25003.csv.gz b/tests/fixtures/validation/vre_2023_050_B25003.csv.gz index b280609..e3b091f 100644 Binary files a/tests/fixtures/validation/vre_2023_050_B25003.csv.gz and b/tests/fixtures/validation/vre_2023_050_B25003.csv.gz differ diff --git a/tests/fixtures/validation/vre_2023_050_B25003.csv.gz.json b/tests/fixtures/validation/vre_2023_050_B25003.csv.gz.json index 07a97d6..7892dbb 100644 --- a/tests/fixtures/validation/vre_2023_050_B25003.csv.gz.json +++ b/tests/fixtures/validation/vre_2023_050_B25003.csv.gz.json @@ -1,10 +1,10 @@ { - "data_rows": 1000, + "data_rows": 1166, "kind": "vre_subset", - "retrieved": "2026-11-02", - "selection": "summary level 050: header, the table title/universe rows, and the rows of states 06,36,48,17,56 for variables B25003_001,B25003_002", - "sha256": "784c135df5515aed4aa11ce2b952f48db3adca556aee29fa06d8285a5f67bb71", - "size_bytes": 206432, + "retrieved": "2026-11-07", + "selection": "summary level 050: header, the table title/universe rows, and the rows of states 06,36,48,17,56,26 for variables B25003_001,B25003_002", + "sha256": "c6d50df7f347970c624e84cecdcf88b2167c7cfc8e9a8e6035beca6d28f443da", + "size_bytes": 241578, "source_sha256": "ab6620ab8a43e32154bc00e054e70866b771ebccd0df5b7c3221797ec6099e0a", "source_size_bytes": 1922726, "source_url": "https://www2.census.gov/programs-surveys/acs/replicate_estimates/2023/data/5-year/050/B25003.csv.zip", diff --git a/tests/test_case_study.py b/tests/test_case_study.py index 57c6ce0..8278ed7 100644 --- a/tests/test_case_study.py +++ b/tests/test_case_study.py @@ -42,6 +42,10 @@ def run_document(workdir): (outputs land in workdir/case_study_output). Returns (command name, JSON output).""" from census.__main__ import main + # the confirmatory step reads examples/manifests/ relative to the working directory + manifests = os.path.join(workdir, "examples", "manifests") + if not os.path.exists(manifests): + shutil.copytree(os.path.join(ROOT, "examples", "manifests"), manifests) outputs = [] cwd = os.getcwd() os.chdir(workdir) diff --git a/tests/test_case_study_live.py b/tests/test_case_study_live.py index ac34a46..c44e8bf 100644 --- a/tests/test_case_study_live.py +++ b/tests/test_case_study_live.py @@ -100,6 +100,23 @@ def test_bundle_and_regression(self): replay = [o for n, o in self.outputs if n == "replay"][0] self.assertTrue(replay["verified"]) + def test_confirmatory_manifest_matches_exploratory(self): + """PLAN_ROUND_14 A4: the pre-registered run reproduces the exploratory coefficient and interval.""" + run = [o for n, o in self.outputs if n == "run_manifest"][0] + self.assertEqual(run["analysis_status"], "confirmatory") + self.assertEqual(run["deviations"], []) + h1 = run["hypotheses"][0] + with open(self.path("results", "regression.json"), encoding="utf-8") as f: + reg = json.load(f) + coef = {c["term"]: c for c in reg["coefficients"]}["pct_bachelors"] + for k in ("estimate", "ci_lower", "ci_upper"): + self.assertAlmostEqual(h1[k], coef[k], places=6) + with open(self.path("confirmatory", "bundle", "bundle.json"), encoding="utf-8") as f: + prov = json.load(f)["provenance"] + self.assertEqual(prov["analysis_status"], "confirmatory") + self.assertEqual(prov["adjustments"][0]["rpp_year"], 2024) + self.assertEqual(len(prov["adjustments"][0]["rpp_areas"]), 31) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_export.py b/tests/test_export.py index 7b6d90d..0fb6172 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -4,6 +4,8 @@ import shutil import tempfile import unittest +import warnings + import pandas as pd from census.export import ( enhanced_melt, @@ -38,6 +40,18 @@ def test_format_dataframe(self): self.assertIn("Total Population", formatted.columns) self.assertNotIn("B01001_001E", formatted.columns) + def test_format_dataframe_in_place_like_rename(self): + # PLAN_ROUND_14 D1: no inplace=True (pandas 3.1 Pandas4Warning), same labels, same object + df = pd.DataFrame([[1, 2, 3, 4]], columns=["NAME", "B01001_001E", "B01001_001E", "B01001_002E"]) + expected = df.rename(columns={"B01001_001E": "Total", "B99999_001E": "unused"}).columns + with warnings.catch_warnings(): + warnings.simplefilter("error") + out = format_dataframe(df, "B01001", variable_descriptions={"B01001_001E": "Total", + "B99999_001E": "unused"}) + self.assertIs(out, df) + self.assertEqual(list(df.columns), list(expected)) + self.assertEqual(df.iloc[0].tolist(), [1, 2, 3, 4]) + def test_enhanced_melt(self): df = pd.DataFrame( { diff --git a/tests/test_manifest_multistate.py b/tests/test_manifest_multistate.py new file mode 100644 index 0000000..3f83509 --- /dev/null +++ b/tests/test_manifest_multistate.py @@ -0,0 +1,498 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""Multi-state manifests with a declared RPP adjustment (PLAN_ROUND_14 workstream A). + +- Validation of ``geography.states`` and ``adjustments`` (A1, A2). +- Derivations evaluated on supplied replicates (A3) and the adjustment of + estimates, SEs and replicates, on a small synthetic two-state fake client. +- The whole confirmatory route of examples/manifests/mi_ny_income_education.json + offline (A4): ``cache import`` of frozen real Census API responses + (tests/fixtures/mi_ny_manifest/, trimmed to the manifest's variables) into an + isolated cache, then ``python -m census tool run_manifest`` and the MCP tool, + with network access refused. County-to-CBSA membership comes from the frozen + ``cbsa_membership.csv`` (recorded from the Census 2024 county and CBSA + boundary overlay) instead of the boundary files, which are too large to + freeze; the live test exercises the overlay itself. + +Re-record the fixtures (live; needs CENSUS_API_KEY; never prints the key):: + + python -m tests.test_manifest_multistate --record +""" + +import contextlib +import io +import json +import os +import shutil +import sys +import tempfile +import unittest +from unittest import mock + +import numpy as np +import pandas as pd + +from census.api import CensusAPIClient +from census.manifest import load_manifest, manifest_hash, methods_section, run_manifest, validate_manifest + +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +MANIFEST = os.path.join(ROOT, "examples", "manifests", "mi_ny_income_education.json") +MANIFEST_HASH = "d8905f57625e1d1862a63ca0f6deeb364a7643385fc4a700b6691e855e3bfcb0" +FIXTURES = os.path.join(ROOT, "tests", "fixtures", "mi_ny_manifest") +MEMBERSHIP = os.path.join(FIXTURES, "cbsa_membership.csv") +MEMBERSHIP_SOURCE = os.path.join(FIXTURES, "cbsa_membership_source.txt") +STATES = ("26", "36") +REP_COLS = [f"rep_{i}" for i in range(1, 81)] +LIVE = os.getenv("CENSUS_LIVE_TESTS") == "1" +# Round 13 exploratory result (docs/case_study_mi_ny.md, section 3), RPP-adjusted, population target +ROUND13 = {"estimate": 811.00, "ci_lower": 684.29, "ci_upper": 937.71} + + +def frozen_membership(): + """(membership indexed by GEOID, boundary source) as census.workflows_microdata._county_rpp_areas returns.""" + df = pd.read_csv(MEMBERSHIP, dtype=str) + df["share"] = pd.to_numeric(df["share"]) + with open(MEMBERSHIP_SOURCE, encoding="utf-8") as f: + source = f.read().strip() + return df.set_index("GEOID"), source + + +def patched_membership(): + from census import workflows_microdata + + membership, source = frozen_membership() + + def lookup(geoids, year): + missing = sorted(set(geoids) - set(membership.index)) + if missing: + raise ValueError(f"no frozen CBSA membership for {missing[:5]}") + return membership.loc[sorted(set(geoids))], source + + return mock.patch.object(workflows_microdata, "_county_rpp_areas", side_effect=lookup) + + +# --------------------------------------------------------------------------- +# synthetic two-state client (no fixtures) +# --------------------------------------------------------------------------- + +def _payload(table_code, state, n=12, seed=3): + """County payload for state ``state``: B19013 income rises with the B15002 college share.""" + rng = np.random.default_rng(seed + int(state)) + total = rng.uniform(5000, 50000, n).round() + share = rng.uniform(0.15, 0.45, n) + college = (total * share).round() + income = 40000 + 900 * 100 * share + rng.normal(0, 3000, n) + (3000 if state == "36" else 0) + if table_code == "B19013": + cols = {"B19013_001": (income, income * 0.04)} + else: + cols = {"B15002_001": (total, total * 0.01), "B15002_015": (college, college * 0.05)} + header = ["GEO_ID", "NAME"] + [f"{c}{s}" for c in cols for s in ("E", "EA", "M", "MA")] + ["state", "county"] + rows = [header] + for i in range(n): + county = f"{2 * i + 1:03d}" + vals = [] + for est, moe in cols.values(): + vals += [f"{est[i]:.0f}", None, f"{moe[i]:.0f}", None] + rows.append([f"0500000US{state}{county}", f"County {i} {state}"] + vals + [state, county]) + return rows + + +class TwoStateClient(CensusAPIClient): + def __init__(self, fail_states=()): + super().__init__(api_key="", cache=None) + self.fail_states = set(fail_states) + self.calls = [] + + def fetch(self, year, state_code, table_code, geo_level="place", **kwargs): # type: ignore[override] + self.calls.append((table_code, state_code)) + if state_code in self.fail_states: + self.last_error = "HTTP 500" + return None + self.last_error = None + return _payload(table_code, state_code) + + def fetch_variable_metadata(self, year, table_code, dataset=None): # type: ignore[override] + return {} + + +def two_state_manifest(**changes): + m = { + "schema_version": 1, "title": "Two-state synthetic test", "authors": ["agent:test"], + "created": "2026-11-08T00:00:00Z", "releases": [2024], + "geography": {"level": "county", "states": ["26", "36"]}, + "variables": [{"name": "income", "ref": "2024:B19013_001"}, {"name": "total", "ref": "2024:B15002_001"}, + {"name": "college", "ref": "2024:B15002_015"}], + "derivations": [{"name": "pct", "op": "proportion", "num": "college", "den": "total", "as_percent": True}], + "adjustments": [{"type": "rpp", "variables": ["income"], "rpp_year": 2023, "areas": "state"}], + "analyses": [{"id": "m1", "method": "fit_replicate", "formula": "income ~ pct + C(GEOID.str[:2])"}], + "hypotheses": [{"id": "H1", "analysis": "m1", "statistic": "coefficient", "term": "pct", + "direction": "greater"}], + } + m.update(changes) + return m + + +class TestValidation(unittest.TestCase): + def test_example_valid_and_hash(self): + m = load_manifest(MANIFEST) + self.assertEqual(validate_manifest(m), []) + self.assertEqual(manifest_hash(m), MANIFEST_HASH) + + def test_single_state_example_hash_unchanged(self): + from tests.test_reference_workflow import MANIFEST as SINGLE, MANIFEST_HASH as SINGLE_HASH + + self.assertEqual(manifest_hash(load_manifest(SINGLE)), SINGLE_HASH) + + def check(self, m, fragment): + errs = validate_manifest(m, resolve=False) + self.assertTrue(any(fragment in e for e in errs), errs) + + def test_geography_errors(self): + self.assertEqual(validate_manifest(two_state_manifest(), resolve=False), []) + self.check(two_state_manifest(geography={"level": "county", "state": "26", "states": ["36"]}), + "not both") + self.check(two_state_manifest(geography={"level": "county", "states": []}), "non-empty list") + self.check(two_state_manifest(geography={"level": "county", "states": "26,36"}), "non-empty list") + self.check(two_state_manifest(geography={"level": "county", "states": ["26", "26"]}), "more than once") + self.check(two_state_manifest(geography={"level": "county", "states": ["26", "all"]}), "one 2-digit") + self.check(two_state_manifest(geography={"level": "county", "states": ["26", "99"]}), "geography.states") + self.check(two_state_manifest(geography={"level": "county", "states": ["26", "36"], "codes": ["001"]}), + "cannot be combined") + self.check(two_state_manifest(geography={"level": "msa", "states": ["26", "36"]}, + adjustments=None), "does not apply") + self.check(two_state_manifest(geography={"level": "county"}), "state_code is required") + + def test_adjustment_errors(self): + def adj(**kw): + return two_state_manifest(adjustments=[{"type": "rpp", "variables": ["income"], **kw}]) + + self.assertEqual(validate_manifest(adj(rpp_year=2023), resolve=False), []) # areas defaulted + self.check(adj(), "rpp_year is required") # create_manifest fills it in (test_review_fixes_r14) + self.check(adj(type="cpi"), "type must be") + self.check(two_state_manifest(adjustments=[{"type": "rpp", "variables": []}]), "non-empty list") + self.check(two_state_manifest(adjustments=[{"type": "rpp", "variables": ["rent"]}]), "unknown variable") + self.check(adj(rpp_year=1990), "rpp_year must be") + self.check(adj(rpp_year="2023"), "rpp_year must be") + self.check(adj(areas="msa"), "areas must be") + self.check(adj(colour="red"), "unknown key") + self.check(two_state_manifest(adjustments=[{"type": "rpp", "variables": ["income"]}, + {"type": "rpp", "variables": ["income"]}]), "adjusted twice") + self.check(two_state_manifest(releases=[2023, 2024], variables=[ + {"name": "income", "ref": "2024:B19013_001"}, {"name": "total", "ref": "2024:B15002_001"}, + {"name": "college", "ref": "2024:B15002_015"}]), "exactly one release") + self.check(two_state_manifest(aggregation={"mapping": {"26001": "a"}}), "aggregation") + tracts = two_state_manifest(geography={"level": "tract", "state": "26"}, + adjustments=[{"type": "rpp", "variables": ["income"], "areas": "county_metro"}]) + self.check(tracts, "needs geography level 'county'") + zcta = two_state_manifest(geography={"level": "zcta", "state": "26"}, + adjustments=[{"type": "rpp", "variables": ["income"]}]) + self.check(zcta, "state-based geography") + self.check(two_state_manifest(adjustments={"type": "rpp"}), "must be a list") + + +class TestTwoStateRun(unittest.TestCase): + def setUp(self): + self.dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.dir, True) + + def run_m(self, m, client=None, **kw): + return run_manifest(m, os.path.join(self.dir, "run"), client=client or TwoStateClient(), use_cache=False, **kw) + + def test_states_stacked_and_state_rpp(self): + from census import rpp + + client = TwoStateClient() + res = self.run_m(two_state_manifest(), client) + self.assertEqual(res["analysis_status"], "confirmatory", res["deviations"]) + self.assertEqual(sorted(set(client.calls)), [("B15002", "26"), ("B15002", "36"), ("B19013", "26"), + ("B19013", "36")]) + frame = res["frame"] + self.assertEqual(len(frame), 24) + self.assertEqual(sorted(frame["GEOID"].str[:2].unique()), list(STATES)) + raw = pd.DataFrame(_payload("B19013", "36")[1:], columns=_payload("B19013", "36")[0]) + row = frame[frame["GEOID"] == "36001"].iloc[0] + factor = 100.0 / rpp.place_parity("36", 2023)["rpp"] + self.assertAlmostEqual(row["income"], float(raw.loc[0, "B19013_001E"]) * factor) + self.assertAlmostEqual(row["income_se"], float(raw.loc[0, "B19013_001M"]) / 1.645 * factor) + adj = res["adjustments"][0] + self.assertEqual((adj["rpp_year"], adj["rpp_year_source"], adj["dollar_year"]), (2023, "manifest", 2024)) + self.assertFalse(adj["rpp_year_matches_release"]) + self.assertTrue(any(n.startswith("RPP YEAR:") for n in adj["notes"])) + self.assertEqual(adj["rpp_table"]["source"], "bundled") + self.assertEqual(adj["rpp_table"]["sha256"], rpp.price_parity_info()["csv_sha256"]["rpp.csv"]) + self.assertEqual(sorted(a["rpp_area"] for a in adj["rpp_areas"]), ["26000", "36000"]) + self.assertFalse(any(d["kind"] == "adjustment_failed" for d in res["deviations"])) # mismatch is a note + with open(os.path.join(self.dir, "run", "bundle", "bundle.json"), encoding="utf-8") as f: + prov = json.load(f)["provenance"] + self.assertEqual(prov["adjustments"][0]["rpp_year"], 2023) + self.assertEqual(prov["adjustments"][0]["rpp_table"]["source"], "bundled") + with open(os.path.join(self.dir, "run", "bundle", "results", "run.json"), encoding="utf-8") as f: + run = json.load(f) + self.assertEqual(run["extraction"]["B19013"]["states"], list(STATES)) + text = methods_section(two_state_manifest(), run) + self.assertIn("states 26, 36", text) + self.assertIn("2023 RPP was applied to dollars of 2024", text) + self.assertIn("no automatic state effects", text) + + def test_omitted_rpp_year_is_invalid_at_run(self): + m = two_state_manifest(adjustments=[{"type": "rpp", "variables": ["income"]}]) + res = self.run_m(m) + self.assertFalse(res["success"]) + self.assertTrue(any("rpp_year is required" in e for e in res["errors"]), res["errors"]) + + def test_default_areas_recorded(self): + from census.manifest import latest_rpp_year + + m = two_state_manifest(adjustments=[{"type": "rpp", "variables": ["income"], "rpp_year": latest_rpp_year()}]) + with patched_membership(): + res = self.run_m(m) + adj = res["adjustments"][0] + self.assertEqual(adj["rpp_year_source"], "manifest") + self.assertEqual(adj["areas"], "county_metro") # county default + + def test_one_state_failing_is_blocking(self): + res = self.run_m(two_state_manifest(), TwoStateClient(fail_states={"36"})) + self.assertEqual(res["analysis_status"], "confirmatory_incomplete") + self.assertIn("failed_calls", {d["kind"] for d in res["deviations"]}) + + def test_adjustment_failure_is_blocking(self): + from census import workflows_microdata + + with mock.patch.object(workflows_microdata, "_county_rpp_areas", side_effect=OSError("no boundaries")): + res = self.run_m(two_state_manifest(adjustments=[{"type": "rpp", "variables": ["income"], + "rpp_year": 2024}])) + self.assertEqual(res["analysis_status"], "confirmatory_incomplete") + kinds = [d["kind"] for d in res["deviations"]] + self.assertIn("adjustment_failed", kinds) + self.assertIn("analysis_not_run", kinds) # never run on unadjusted dollars + + def _replicates(self, frame, names_by_code=False): + """Synthetic replicates for income, total and college (by manifest name or ACS code).""" + rng = np.random.default_rng(11) + parts = [] + for name, code in (("income", "B19013_001"), ("total", "B15002_001"), ("college", "B15002_015")): + est = frame[name].to_numpy(float) + part = pd.DataFrame({"GEOID": frame["GEOID"], "variable": code if names_by_code else name, + "estimate": est}) + reps = est[:, None] * (1 + rng.normal(0, 0.02, (len(est), 80))) + parts.append(pd.concat([part, pd.DataFrame(reps, columns=REP_COLS)], axis=1)) + return pd.concat(parts, ignore_index=True) + + def test_derivation_on_replicates_and_scaling(self): + from census import rpp + + probe = self.run_m(two_state_manifest(adjustments=None))["frame"] # unadjusted frame + for by_code in (False, True): + reps = self._replicates(probe, names_by_code=by_code) + res = self.run_m(two_state_manifest(), replicates=reps) + self.assertEqual(res["analysis_status"], "confirmatory", res["deviations"]) + self.assertEqual(res["replicates"]["derived_on_replicates"], ["pct"]) + self.assertEqual(res["replicates"]["scaled_by_adjustments"], ["income"]) + frame = res["frame"].set_index("GEOID") + self.assertTrue((frame["pct_method"] == "replicate").all()) + # exact replicate SE of the derived share, evaluated from the input replicates + g = "26003" + sub = reps[reps["GEOID"] == g].set_index("variable") + num = sub.loc["B15002_015" if by_code else "college", REP_COLS].to_numpy(float) + den = sub.loc["B15002_001" if by_code else "total", REP_COLS].to_numpy(float) + est = frame.loc[g, "pct"] + se = np.sqrt(4 / 80 * ((100 * num / den - est) ** 2).sum()) + self.assertAlmostEqual(frame.loc[g, "pct_se"], se, places=6) + # income kept its published SE (only derived variables take replicate SEs) + factor = 100.0 / rpp.place_parity("26", 2023)["rpp"] + self.assertAlmostEqual(frame.loc[g, "income_se"], probe.set_index("GEOID").loc[g, "income_se"] * factor) + m1 = res["results"]["m1"] + self.assertEqual(m1.method, "replicate_refit") + self.assertEqual(sorted(m1.provenance["replicate_variables"]), ["income", "pct"]) + self.assertTrue(np.isfinite(m1.coefficients.set_index("term").loc["pct", "se_sampling"])) + # the caller-supplied replicate hash is of the input, not of the derived/scaled frame + from census.manifest import replicates_sha256 + + self.assertEqual(res["replicates"]["sha256"], replicates_sha256(reps)) + + def test_scaled_replicates_equal_scaled_estimates(self): + """Replicates of an adjusted variable scale by the row's RPP factor (refit uses them).""" + from census import manifest, rpp + + probe = self.run_m(two_state_manifest(adjustments=None))["frame"] + reps = self._replicates(probe) + seen = {} + real = manifest._run_analysis + + def spy(a, frame, level, replicates, correction): + seen["reps"] = replicates + return real(a, frame, level, replicates, correction) + + with mock.patch.object(manifest, "_run_analysis", side_effect=spy): + self.run_m(two_state_manifest(), replicates=reps) + got = seen["reps"] + r = got[(got["variable"] == "income") & (got["GEOID"] == "36005")].iloc[0] + r0 = reps[(reps["variable"] == "income") & (reps["GEOID"] == "36005")].iloc[0] + factor = 100.0 / rpp.place_parity("36", 2023)["rpp"] + np.testing.assert_allclose(r[["estimate"] + REP_COLS].to_numpy(float), + r0[["estimate"] + REP_COLS].to_numpy(float) * factor) + + def test_derivation_without_input_replicates_blocks_strict(self): + probe = self.run_m(two_state_manifest(adjustments=None))["frame"] + reps = self._replicates(probe) + reps = reps[reps["variable"] != "college"] # pct cannot be evaluated on replicates + res = self.run_m(two_state_manifest(), replicates=reps) + self.assertEqual(res["analysis_status"], "confirmatory_incomplete") + self.assertIn("replicates_incomplete", {d["kind"] for d in res["deviations"]}) + self.assertNotIn("derived_on_replicates", res["replicates"]) + + +# --------------------------------------------------------------------------- +# frozen real-data route (examples/manifests/mi_ny_income_education.json) +# --------------------------------------------------------------------------- + +def run_cli(argv): + from census.__main__ import main + + buf = io.StringIO() + with contextlib.redirect_stdout(buf), contextlib.redirect_stderr(io.StringIO()): + code = main(argv) + return code, json.loads(buf.getvalue()) + + +class TestFrozenMiNyRoute(unittest.TestCase): + @classmethod + def setUpClass(cls): + from tests.test_reference_workflow import offline + + cls.dir = tempfile.mkdtemp(prefix="mi_ny_manifest_") + cls.env = mock.patch.dict(os.environ, {"CENSUS_CACHE_PATH": os.path.join(cls.dir, "cache.db")}) + cls.env.start() + with offline(), patched_membership(): + code, cls.imported = run_cli(["cache", "import", FIXTURES, "--json"]) + assert code == 0, cls.imported + code, cls.cli = run_cli(["tool", "run_manifest", "--arg", f"manifest_path={MANIFEST}", + "--arg", f"output_dir={os.path.join(cls.dir, 'cli')}", "--json"]) + cls.cli_code = code + + @classmethod + def tearDownClass(cls): + cls.env.stop() + shutil.rmtree(cls.dir, ignore_errors=True) + + def test_import_is_real_data(self): + self.assertEqual(self.imported["imported"], 4) + self.assertFalse(self.imported["synthetic"]) + + def test_confirmatory_result_reproduces_round13(self): + out = self.cli + self.assertEqual(self.cli_code, 0, out.get("error")) + self.assertTrue(out["success"]) + self.assertEqual(out["analysis_status"], "confirmatory") + self.assertEqual(out["deviations"], []) + self.assertEqual(out["manifest_hash"], MANIFEST_HASH) + self.assertEqual(out["data_source"]["synthetic"], False) + h1 = out["hypotheses"][0] + self.assertEqual((h1["id"], h1["status"], h1["se_basis"]), ("H1", "tested", "se_model")) + for k, v in ROUND13.items(): + self.assertAlmostEqual(h1[k], v, places=2) + self.assertTrue(h1["significant"]) + with open(os.path.join(self.dir, "cli", "bundle", "results", "run.json"), encoding="utf-8") as f: + run = json.load(f) + self.assertEqual(run["n_units"], 145) + adj = run["adjustments"][0] + self.assertEqual(len(adj["rpp_areas"]), 31) # 16 + 13 MSAs and two nonmetro portions + rows = {a["rpp_area"]: a for a in adj["rpp_areas"]} + self.assertEqual((rows["26999"]["rows"], rows["36999"]["rows"]), (53, 25)) + self.assertAlmostEqual(rows["35620"]["rpp"], 112.563) + with open(os.path.join(self.dir, "cli", "bundle", "bundle.json"), encoding="utf-8") as f: + bundle = json.load(f) + self.assertEqual(bundle["provenance"]["analysis_status"], "confirmatory") + self.assertEqual(bundle["provenance"]["adjustments"][0]["rpp_year"], 2024) + self.assertIn("Retrieved via the Census Data API", bundle["citation"]) + + def test_mcp_route_agrees(self): + from tests.test_reference_workflow import _mcp_call, offline + + import mcp_server + + with offline(), patched_membership(): + out = _mcp_call(mcp_server, "run_manifest", {"manifest_path": MANIFEST, + "output_dir": os.path.join(self.dir, "mcp")}) + self.assertEqual(out["analysis_status"], "confirmatory", out.get("error")) + self.assertEqual(out["manifest_hash"], self.cli["manifest_hash"]) + a, b = out["hypotheses"][0], self.cli["hypotheses"][0] + for k in ("estimate", "se", "ci_lower", "ci_upper", "p_value"): + self.assertAlmostEqual(a[k], b[k], places=9) + + +@unittest.skipUnless(LIVE, "set CENSUS_LIVE_TESTS=1 to run live Census API tests") +class TestLiveMiNyRoute(unittest.TestCase): + """The same route on the live Census API and boundary files (default cache).""" + + def test_live_confirmatory_run(self): + d = tempfile.mkdtemp(prefix="mi_ny_live_") + self.addCleanup(shutil.rmtree, d, True) + code, out = run_cli(["tool", "run_manifest", "--arg", f"manifest_path={MANIFEST}", + "--arg", f"output_dir={d}", "--json"]) + self.assertEqual(code, 0, out.get("error")) + self.assertEqual(out["analysis_status"], "confirmatory") + self.assertEqual(out["data_source"]["synthetic"], False) + h1 = out["hypotheses"][0] + for k, v in ROUND13.items(): + self.assertAlmostEqual(h1[k], v, places=2) + # the live CBSA overlay agrees with the frozen membership used offline + from census import workflows_microdata + + frozen, _ = frozen_membership() + live, _ = workflows_microdata._county_rpp_areas(sorted(frozen.index), 2024) + pd.testing.assert_series_equal(live["CBSA"].fillna("").astype(str).sort_index(), + frozen["CBSA"].fillna("").astype(str).sort_index(), check_names=False) + + +# --------------------------------------------------------------------------- +# recording (live) +# --------------------------------------------------------------------------- + +def record(): + """Fetch the four responses (2024 acs5, all counties of 26 and 36; B19013 and B15002) + through the toolkit client, trim each to the manifest's variables, and write the + frozen files and the CBSA membership. Never writes or prints the API key.""" + from census import workflows_microdata + from census.manifest import tables_and_refs + + m = load_manifest(MANIFEST) + keep = {t: [r.split(":")[1] for r in refs] for t, refs in tables_and_refs(m).items()} + client = CensusAPIClient() + os.makedirs(FIXTURES, exist_ok=True) + geoids = [] + for st in STATES: + request = {"year": 2024, "dataset": "acs5", "geo_level": "county", "state_code": st, "county_code": "*"} + doc = {"_about": (f"REAL Census Data API responses (2024 acs5, all counties of state {st}, get=NAME," + "group(
)), recorded by `python -m tests.test_manifest_multistate --record` for " + "examples/manifests/mi_ny_income_education.json; columns trimmed to the manifest's " + "variables (estimate, MOE and annotations) plus GEO_ID, NAME, state, county."), + "request": request, "requests": {}} + for table, codes in keep.items(): + rows = client.fetch(table_code=table, **request) + if rows is None: + raise SystemExit(f"fetch failed for {table} state {st}") + header = rows[0] + cols = [i for i, h in enumerate(header) + if h in ("GEO_ID", "NAME", "state", "county") + or any(h == f"{c}{s}" for c in codes for s in ("E", "EA", "M", "MA"))] + doc["requests"][table] = [[r[i] for i in cols] for r in rows] + if table == "B19013": + geoids += [r[header.index("state")] + r[header.index("county")] for r in rows[1:]] + with open(os.path.join(FIXTURES, f"frozen_api_responses_{st}.json"), "w", encoding="utf-8", + newline="\n") as f: + json.dump(doc, f, separators=(",", ":")) + f.write("\n") + membership, source = workflows_microdata._county_rpp_areas(sorted(geoids), 2024) + out = membership.reset_index()[["GEOID", "CBSA", "CBSA_NAME", "LSAD", "share"]].sort_values("GEOID") + out["share"] = out["share"].round(6) + out.to_csv(MEMBERSHIP, index=False, lineterminator="\n") + with open(MEMBERSHIP_SOURCE, "w", encoding="utf-8", newline="\n") as f: + f.write(f"{source}\n") + print(f"recorded {len(geoids)} counties into {FIXTURES}") + + +if __name__ == "__main__": + if "--record" in sys.argv: + record() + else: + unittest.main() diff --git a/tests/test_mcp_cache_import.py b/tests/test_mcp_cache_import.py new file mode 100644 index 0000000..0149e37 --- /dev/null +++ b/tests/test_mcp_cache_import.py @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""MCP ``cache_import`` (PLAN_ROUND_14 D2): the CLI ``cache import`` rules +(tests/test_review_fixes_r13.py TestCacheImportSafety, tests/test_reference_workflow.py +TestCacheImport) through the MCP tool and the CLI ``tool`` runner.""" + +import contextlib +import io +import json +import os +import shutil +import tempfile +import unittest +from unittest import mock + +import mcp_server as srv +from census import mcp_profiles, workflows +from census.cache import QueryCache + +HERE = os.path.dirname(os.path.abspath(__file__)) +FIXTURE = os.path.join(os.path.dirname(HERE), "tests", "fixtures", "reference_workflow", "frozen_api_responses.json") + + +class TestMcpCacheImport(unittest.TestCase): + def setUp(self): + self.dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.dir, True) + self.db = os.path.join(self.dir, "cache.db") + + def call(self, *args, env=None, remove=(), **kwargs): + with mock.patch.dict(os.environ, {"CENSUS_CACHE_PATH": self.db} if env is None else env): + for key in remove: + os.environ.pop(key, None) + return srv.cache_import(*args, **kwargs) + + def real_source(self): + with open(FIXTURE, encoding="utf-8") as f: + doc = json.load(f) + del doc["synthetic"] + path = os.path.join(self.dir, "real.json") + with open(path, "w", encoding="utf-8") as f: + json.dump(doc, f) + return path + + def test_registered_in_full_profile_only(self): + self.assertIn("cache_import", {t.name for t in srv.mcp._tool_manager.list_tools()}) + for profile in ("core", "review"): + server = mcp_profiles.build_profile_server(srv.mcp, profile) + self.assertNotIn("cache_import", {t.name for t in server._tool_manager.list_tools()}) + + def test_real_then_synthetic(self): + out = self.call(self.real_source()) + self.assertTrue(out["success"], out) + self.assertEqual(out["imported"], 2) + self.assertFalse(out["synthetic"]) + self.assertIsNone(QueryCache(self.db).synthetic_marker()) + out = self.call(FIXTURE) + self.assertTrue(out["synthetic"]) + self.assertEqual(out["entries"], 2) # same request keys: replaced, not duplicated + self.assertEqual(out["path"], self.db) + + def test_import_is_atomic(self): + src = os.path.join(self.dir, "src") + os.makedirs(src) + shutil.copyfile(FIXTURE, os.path.join(src, "a_good.json")) + with open(os.path.join(src, "b_bad.json"), "w", encoding="utf-8") as f: + json.dump({"request": {"year": 2019, "colour": "red"}, "requests": {"B19013": [["GEO_ID"]]}}, f) + out = self.call(src) + self.assertFalse(out["success"]) + self.assertIn("b_bad.json", out["error"]) + cache = QueryCache(self.db) + self.assertEqual(cache.stats()["entries"], 0) + self.assertIsNone(cache.synthetic_marker()) + + def test_synthetic_into_default_cache_needs_flag(self): + default = QueryCache(os.path.join(self.dir, "default.db")) + with mock.patch.object(workflows, "get_default_cache", return_value=default): + out = self.call(FIXTURE, env={}, remove=("CENSUS_CACHE_PATH",)) + self.assertFalse(out["success"]) + self.assertIn("CENSUS_CACHE_PATH", out["error"]) + self.assertIn("allow_default_cache=true", out["error"]) + self.assertEqual(default.stats()["entries"], 0) + self.assertIsNone(default.synthetic_marker()) + out = self.call(self.real_source(), env={}, remove=("CENSUS_CACHE_PATH",)) # real: no flag needed + self.assertTrue(out["success"], out) + self.assertFalse(out["synthetic"]) + out = self.call(FIXTURE, allow_default_cache=True, env={}, remove=("CENSUS_CACHE_PATH",)) + self.assertTrue(out["success"], out) + self.assertTrue(default.synthetic_marker()) + + def test_errors(self): + self.assertFalse(self.call("")["success"]) + self.assertFalse(self.call(os.path.join(self.dir, "missing.json"))["success"]) + out = self.call(self.dir) # a directory without frozen files + self.assertFalse(out["success"]) + self.assertIn("No frozen response files", out["error"]) + out = self.call(FIXTURE, env={"CENSUS_CACHE_DISABLED": "1"}) + self.assertFalse(out["success"]) + self.assertIn("disabled", out["error"]) + + def test_cli_tool_runner(self): + from census.__main__ import main + + buf = io.StringIO() + with mock.patch.dict(os.environ, {"CENSUS_CACHE_PATH": self.db}), contextlib.redirect_stdout(buf): + code = main(["tool", "cache_import", "--arg", f"source={FIXTURE}", "--json"]) + out = json.loads(buf.getvalue()) + self.assertEqual(code, 0, out) + self.assertTrue(out["success"] and out["synthetic"]) + self.assertEqual(out["imported"], 2) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_pums.py b/tests/test_pums.py index 50398c4..16b4179 100644 --- a/tests/test_pums.py +++ b/tests/test_pums.py @@ -284,3 +284,68 @@ def test_puma_boundary_level(): B.check_boundary_vintage(2023, 2020, "puma") with pytest.raises(B.BoundaryVintageError): B.check_boundary_vintage(2023, 2019, "puma") + + +# --------------------------------------------------------------------------- memory fixes (PLAN_ROUND_14 C) + +def _weighted_median_v0_12(values, weights): + """weighted_median as of v0.12.0 (two gathers of the weight matrix).""" + x = np.asarray(values, float) + w = np.asarray(weights, float) + if w.ndim == 1: + w = w[:, None] + ok = np.isfinite(x) + x, w = x[ok], w[ok] + if not len(x): + return np.full(w.shape[1], np.nan) + order = np.argsort(x, kind="mergesort") + x, w = x[order], w[order] + cum = np.cumsum(w, axis=0) + half = cum[-1] / 2.0 + reached = cum >= half - 1e-9 * np.abs(half) + idx = reached.argmax(axis=0) + out = x[idx].astype(float) + exact = np.isclose(cum[idx, np.arange(w.shape[1])], half, rtol=1e-12, atol=1e-9) + for r in np.flatnonzero(exact): + nz = np.flatnonzero(w[:, r] != 0) + pos = np.searchsorted(nz, idx[r], side="right") + if pos < len(nz): + out[r] = (x[idx[r]] + x[nz[pos]]) / 2.0 + return np.where(half > 0, out, np.nan) + + +def _group_sum_v0_12(ids, mat, n_groups): + ok = ids >= 0 + out = np.zeros((n_groups, mat.shape[1])) + if ok.any(): + sums = pd.DataFrame(mat[ok]).groupby(ids[ok]).sum() + out[sums.index.to_numpy()] = sums.to_numpy() + return out + + +def test_memory_fixes_give_identical_results(): + rng = np.random.default_rng(7) + for n in (1, 2, 9, 500): + x = np.round(rng.normal(50, 20, n)) # ties + x[rng.random(n) < 0.2] = np.nan + w = rng.integers(0, 4, (n, 81)).astype(float) + assert np.array_equal(P.weighted_median(x, w), _weighted_median_v0_12(x, w), equal_nan=True) + ids = rng.integers(-1, 5, n) + assert np.array_equal(P._group_sum(ids, w, 5), _group_sum_v0_12(ids, w, 5)) + ids[:] = 0 + assert np.array_equal(P._group_sum(ids, w, 1), _group_sum_v0_12(ids, w, 1)) + assert np.array_equal(P.weighted_median(np.array([1.0, 2, 3, 4]), np.ones(4)), [2.5]) + + +def test_tabulate_median_groups_unchanged(): + df = synthetic(n=600) + df.loc[:40, "x"] = np.nan + for bins in (None, [-np.inf, 5, 8, 10, 12, 15, np.inf]): + t = P.tabulate(df, "median", "x", by="g", where="flag == 1", bins=bins, return_replicates=True) + for _, row in t.iterrows(): + sub = df[(df.g == row.g) & (df.flag == 1) & df.x.notna()] + W = sub[["PWGTP"] + REPS].to_numpy(np.float64) + ref = (_weighted_median_v0_12(sub.x.to_numpy(), W) if bins is None + else P.binned_median(sub.x.to_numpy(), W, bins)) + got = np.array([row.estimate] + [row[f"rep_{i}"] for i in range(1, 81)]) + assert np.array_equal(got, ref) diff --git a/tests/test_pums_memory_benchmark.py b/tests/test_pums_memory_benchmark.py new file mode 100644 index 0000000..5fbd63e --- /dev/null +++ b/tests/test_pums_memory_benchmark.py @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""scripts/pums_memory_benchmark.py in --small mode (2,000 synthetic rows; PLAN_ROUND_14 C). + +The full benchmark (100k-1.9M rows) is run by hand; docs/audits/pums_memory.md +holds its results. +""" + +import importlib.util +import json +import os +import shutil +import tempfile +import unittest + +import numpy as np + +from census import pums + +HERE = os.path.dirname(os.path.abspath(__file__)) +SCRIPT = os.path.join(os.path.dirname(HERE), "scripts", "pums_memory_benchmark.py") +_spec = importlib.util.spec_from_file_location("pums_memory_benchmark", SCRIPT) +bench = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(bench) + + +class TestPumsMemoryBenchmark(unittest.TestCase): + def setUp(self): + self.dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.dir, True) + + def test_synthetic_file_reads_like_pums(self): + path = os.path.join(self.dir, "p.csv.gz") + info = bench.generate(path, 1_500) + self.assertEqual((info["rows"], info["columns"]), (1_500, 94)) + df = pums.read_pums_csv(path, "person", bench.ANALYSIS_COLUMNS, chunksize=400) + self.assertEqual(len(df), 1_500) + self.assertTrue(df["PUMA"].str.len().eq(5).all() and df["STATE"].eq("06").all()) + self.assertEqual(df["PWGTP80"].dtype, np.float32) + self.assertTrue(df.loc[df["AGEP"] < 15, "PINCP"].isna().all()) + self.assertTrue(df.loc[df["AGEP"] >= 15, "PINCP"].notna().all()) + + def test_small_mode(self): + out, raw = os.path.join(self.dir, "report.md"), os.path.join(self.dir, "raw.json") + code = bench.main(["--small", "--workdir", self.dir, "--out", out, "--json", raw]) + self.assertEqual(code, 0) + with open(raw, encoding="utf-8") as f: + res = json.load(f) + self.assertEqual(res["equivalence"], {"2000": {"tabulate": True, "read_hash": True, "chunksizes": 2}}) + phases = sorted(r["phase"] for r in res["runs"]) + self.assertEqual(phases, ["baseline", "read", "read", "tabulate", "tabulate", "weights"]) + weights = next(r for r in res["runs"] if r["phase"] == "weights") + self.assertEqual(weights["matrix_bytes"], 2_000 * 81 * 8) + for r in res["runs"]: + self.assertGreater(r["peak"], 0) + with open(out, encoding="utf-8") as f: + report = f.read() + self.assertIn("## Chunk-size equivalence", report) + self.assertIn("| 2,000 | 2 | yes | yes |", report) + self.assertFalse(any(name.endswith(".csv.gz") for name in os.listdir(self.dir))) # deleted afterwards + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_review_fixes_r14.py b/tests/test_review_fixes_r14.py new file mode 100644 index 0000000..e191ccc --- /dev/null +++ b/tests/test_review_fixes_r14.py @@ -0,0 +1,341 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""Regression tests for the Round 14 code-review findings (branch round14/review-fixes).""" + +import json +import os +import shutil +import tempfile +import unittest +import warnings +from unittest import mock + +import numpy as np +import pandas as pd + +from census import manifest as M +from tests.test_manifest_multistate import REP_COLS, TwoStateClient, two_state_manifest + + +class _Run(unittest.TestCase): + def setUp(self): + self.dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.dir, True) + + def run_m(self, m, client=None, **kw): + return M.run_manifest(m, os.path.join(self.dir, "run"), client=client or TwoStateClient(), + use_cache=False, **kw) + + def probe(self): + return self.run_m(two_state_manifest(adjustments=None))["frame"] + + @staticmethod + def replicates(frame, codes=None, seed=11): + """Replicates of income, total and college; ``codes`` maps name -> replicate variable label.""" + rng = np.random.default_rng(seed) + parts = [] + for name in ("income", "total", "college"): + est = frame[name].to_numpy(float) + part = pd.DataFrame({"GEOID": frame["GEOID"], "variable": (codes or {}).get(name, name), + "estimate": est}) + reps = est[:, None] * (1 + rng.normal(0, 0.02, (len(est), 80))) + parts.append(pd.concat([part, pd.DataFrame(reps, columns=REP_COLS)], axis=1)) + return pd.concat(parts, ignore_index=True) + + +# 1. replicate derivation errors are deviations, not exceptions +class TestDeriveReplicatesErrors(_Run): + def test_duplicate_replicate_rows_recorded_not_raised(self): + reps = self.replicates(self.probe()) + reps = pd.concat([reps, reps[reps["variable"] == "college"].head(1)], ignore_index=True) + res = self.run_m(two_state_manifest(), replicates=reps) # _rep_cube raises ValueError + self.assertEqual(res["analysis_status"], "confirmatory_incomplete") + dev = [d for d in res["deviations"] if d["kind"] == "replicates_incomplete" + and "derivations on replicates" in str(d.get("detail"))] + self.assertTrue(dev, res["deviations"]) + self.assertIn("duplicate rows", dev[0]["detail"]) + self.assertTrue(os.path.exists(os.path.join(self.dir, "run", "bundle", "bundle.json"))) + + def test_key_error_recorded_not_raised(self): + reps = self.replicates(self.probe()) + with mock.patch("census.analysis.attach_replicates", side_effect=KeyError("rep_7")): + res = self.run_m(two_state_manifest(), replicates=reps) + self.assertIn("replicates_incomplete", {d["kind"] for d in res["deviations"]}) + self.assertEqual(res["analysis_status"], "confirmatory_incomplete") + + def test_mcp_wrapper_survives_key_error(self): + from census import workflows_inference as W + + path = os.path.join(self.dir, "m.json") + with open(path, "w", encoding="utf-8") as f: + json.dump(two_state_manifest(), f) + with mock.patch("census.manifest.run_manifest", side_effect=KeyError("boom")): + out = W.run_manifest(path, os.path.join(self.dir, "out")) + self.assertFalse(out["success"]) + self.assertIn("boom", out["error"]) + + +# 2. inputs given by ACS code with an E/PE suffix +class TestDeriveByCodeSuffix(_Run): + def test_suffixed_codes_match_normalized_replicate_names(self): + frame = self.probe() + reps = self.replicates(frame, codes={"income": "B19013_001E", "total": "B15002_001E", + "college": "B15002_015E"}) + m = two_state_manifest(variables=[{"name": "income", "ref": "2024:B19013_001E"}, + {"name": "total", "ref": "2024:B15002_001E"}, + {"name": "college", "ref": "2024:B15002_015E"}]) + _, _, derived = M._derive_replicates(frame, reps, m) + self.assertEqual(list(derived), ["pct"]) + + def test_percent_code(self): + frame = self.probe() + reps = self.replicates(frame, codes={"total": "DP02_0001E", "college": "DP02_0068PE"}) + m = two_state_manifest(variables=[{"name": "income", "ref": "2024:B19013_001"}, + {"name": "total", "ref": "2024:DP02_0001E"}, + {"name": "college", "ref": "2024:DP02_0068PE"}]) + _, _, derived = M._derive_replicates(frame, reps, m) + self.assertEqual(list(derived), ["pct"]) + + +# 3. derived_on_replicates lists only derivations that received replicate SEs, with counts +class TestDerivedOnReplicatesCounts(_Run): + def test_no_rows_with_replicates_not_listed(self): + frame = self.probe() + reps = self.replicates(frame) + sel = reps["variable"] == "college" + reps.loc[sel, "GEOID"] = [f"99{i:03d}" for i in range(int(sel.sum()))] # no unit has college replicates + _, _, derived = M._derive_replicates(frame, reps, two_state_manifest()) + self.assertEqual(dict(derived), {}) + + def test_partial_counts_and_methods_text(self): + frame = self.probe() + reps = self.replicates(frame) + drop = reps["GEOID"].isin(["26001", "26003", "36001"]) & (reps["variable"] == "college") + res = self.run_m(two_state_manifest(), replicates=reps[~drop].reset_index(drop=True)) + info = res["replicates"] + self.assertEqual(info["derived_on_replicates"], ["pct"]) + self.assertEqual(info["derived_on_replicates_rows"], {"pct": {"rows_replicate": 21, "rows": 24}}) + with open(os.path.join(self.dir, "run", "bundle", "results", "run.json"), encoding="utf-8") as f: + run = json.load(f) + text = M.methods_section(two_state_manifest(), run) + self.assertIn("replicate SE for 21 of 24 units; approximate SE (ACS formulas, Handbook ch. 8) elsewhere", + text) + + def test_full_coverage_text(self): + frame = self.probe() + res = self.run_m(two_state_manifest(), replicates=self.replicates(frame)) + self.assertEqual(res["replicates"]["derived_on_replicates_rows"], {"pct": {"rows_replicate": 24, + "rows": 24}}) + with open(os.path.join(self.dir, "run", "bundle", "results", "run.json"), encoding="utf-8") as f: + run = json.load(f) + self.assertIn("SE from the derivation evaluated on each replicate (all 24 units)", + M.methods_section(two_state_manifest(), run)) + + +def _errs(m): + return M.validate_manifest(m, resolve=False) + + +# 4-5. geography.states validation +class TestStatesValidation(unittest.TestCase): + def test_one_digit_state_rejected(self): + for states in (["6", "06"], ["6", "36"], ["026", "36"], [" 26", "36"]): + errs = _errs(two_state_manifest(geography={"level": "county", "states": states})) + self.assertTrue(any("exactly 2 digits" in e for e in errs), (states, errs)) + + def test_one_digit_single_state_rejected(self): + errs = _errs(two_state_manifest(geography={"level": "county", "state": "6"}, adjustments=None)) + self.assertTrue(any("exactly 2 digits" in e for e in errs), errs) + self.assertEqual(_errs(two_state_manifest(geography={"level": "county", "state": "all"}, + adjustments=None)), []) + + def test_zcta_with_states_rejected(self): + errs = _errs(two_state_manifest(geography={"level": "zcta", "states": ["26", "36"]}, adjustments=None)) + self.assertTrue(any("zcta" in e.lower() and "cross state lines" in e for e in errs), errs) + + def test_example_hashes_unchanged(self): + from tests.test_manifest_multistate import MANIFEST, MANIFEST_HASH + from tests.test_reference_workflow import MANIFEST as SINGLE, MANIFEST_HASH as SINGLE_HASH + + for path, h in ((MANIFEST, MANIFEST_HASH), (SINGLE, SINGLE_HASH)): + m = M.load_manifest(path) + self.assertEqual(M.validate_manifest(m), []) + self.assertEqual(M.manifest_hash(m), h) + + +# 6. rpp_year must be explicit; create_manifest fills it before hashing +class TestRppYearExplicit(unittest.TestCase): + def setUp(self): + self.dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.dir, True) + + def test_validation_requires_rpp_year(self): + m = two_state_manifest(adjustments=[{"type": "rpp", "variables": ["income"]}]) + errs = _errs(m) + self.assertTrue(any("rpp_year is required" in e for e in errs), errs) + + def test_create_fills_latest_year_before_hash(self): + m = two_state_manifest(adjustments=[{"type": "rpp", "variables": ["income"]}]) + path = os.path.join(self.dir, "m.json") + with open(path, "w", encoding="utf-8") as f: + json.dump(m, f) + res = M.create_manifest(path, resolve=False) + self.assertTrue(res["valid"], res["errors"]) + year = M.latest_rpp_year() + self.assertEqual(res["filled"], [{"adjustment": 0, "rpp_year": year}]) + self.assertTrue(any("rpp_year" in n and str(year) in n for n in res["notes"])) + with open(path, encoding="utf-8") as f: # the input file is never overwritten + self.assertEqual(json.load(f), m) + completed = os.path.join(self.dir, "m.completed.json") + self.assertEqual(res["path"], completed) + written = M.load_manifest(completed) + self.assertEqual(written["adjustments"][0]["rpp_year"], year) + self.assertEqual(M.manifest_hash(written), res["hash"]) + self.assertEqual(_errs(written), []) + + def test_create_dict_returns_filled_manifest(self): + m = two_state_manifest(adjustments=[{"type": "rpp", "variables": ["income"]}]) + res = M.create_manifest(m, resolve=False) + self.assertTrue(res["valid"], res["errors"]) + self.assertEqual(res["manifest"]["adjustments"][0]["rpp_year"], M.latest_rpp_year()) + self.assertEqual(M.manifest_hash(res["manifest"]), res["hash"]) + self.assertNotIn("rpp_year", m["adjustments"][0]) # caller's dict untouched + + +# 7. exclusions before adjustments +class TestExclusionsBeforeAdjustments(_Run): + @staticmethod + def membership(skip=()): + from census import workflows_microdata + + def lookup(geoids, year): + missing = sorted(set(geoids) & set(skip)) + if missing: + raise ValueError(f"no county boundaries for {missing}") + idx = sorted(set(geoids)) + return pd.DataFrame({"CBSA": [np.nan] * len(idx), "LSAD": [np.nan] * len(idx)}, + index=pd.Index(idx, name="GEOID")), "test" + + return mock.patch.object(workflows_microdata, "_county_rpp_areas", side_effect=lookup) + + def manifest(self, exclude=True): + return two_state_manifest( + adjustments=[{"type": "rpp", "variables": ["income"], "rpp_year": 2023, "areas": "county_metro"}], + exclusions=[{"rule": "geoid_exclude", "geoids": ["26001"]}] if exclude else []) + + def test_excluded_unit_needs_no_rpp_area(self): + with self.membership(skip=("26001",)): + res = self.run_m(self.manifest()) + self.assertEqual(res["analysis_status"], "confirmatory", res["deviations"]) + self.assertNotIn("26001", set(res["frame"]["GEOID"])) + self.assertEqual(res["adjustments"][0]["rows"], 23) + + def test_unit_without_rpp_area_still_blocks(self): + with self.membership(skip=("26001",)): + res = self.run_m(self.manifest(exclude=False)) + self.assertEqual(res["analysis_status"], "confirmatory_incomplete") + self.assertIn("adjustment_failed", {d["kind"] for d in res["deviations"]}) + + def test_cv_screen_unchanged_by_order(self): + thr = float(np.median(self.probe()["income_cv"])) # CV is scale invariant: same rows either way + m = two_state_manifest(exclusions=[{"rule": "cv_screen", "threshold": thr, "variables": ["income"], + "action": "exclude"}]) + res = self.run_m(m) + unadj = self.run_m(two_state_manifest(adjustments=None, exclusions=m["exclusions"])) + self.assertEqual(sorted(res["frame"]["GEOID"]), sorted(unadj["frame"]["GEOID"])) + self.assertLess(len(res["frame"]), 24) + + def test_methods_text_order(self): + text = M.methods_section(two_state_manifest(exclusions=[{"rule": "geoid_exclude", "geoids": ["26001"]}])) + self.assertLess(text.index("pre-specified GEOIDs were excluded"), text.index("Price adjustment")) + + +# 8. PUMS medians: one sort of the group ids, one gather per group +class TestPumsMedianGroups(unittest.TestCase): + def frame(self, n=3000, groups=150, seed=5): + from tests.test_pums import synthetic + + df = synthetic(n=n, seed=seed) + rng = np.random.default_rng(seed) + df["g"] = rng.integers(0, groups, n) + df["x"] = np.round(df["x"]) # ties + df.loc[rng.random(n) < 0.1, "x"] = np.nan + return df + + def test_many_groups_identical_and_gathered_once(self): + from census import pums as P + from tests.test_pums import REPS, _weighted_median_v0_12 + + df = self.frame() + seen = [] + real = P.weighted_median + + def spy(values, weights): + seen.append(len(values)) + return real(values, weights) + + with mock.patch.object(P, "weighted_median", side_effect=spy): + t = P.tabulate(df, "median", "x", by="g", return_replicates=True) + defined = int(df["x"].notna().sum()) + self.assertEqual(sum(seen), defined) # each group's rows only, not G x N + for _, row in t.iterrows(): + sub = df[(df.g == row.g) & df.x.notna()] + ref = _weighted_median_v0_12(sub.x.to_numpy(), sub[["PWGTP"] + REPS].to_numpy(np.float64)) + got = np.array([row.estimate] + [row[f"rep_{i}"] for i in range(1, 81)]) + self.assertTrue(np.array_equal(got, ref, equal_nan=True), row.g) + + def test_binned_many_groups_identical(self): + from census import pums as P + from tests.test_pums import REPS + + df = self.frame(n=1500, groups=60, seed=9) + bins = [-np.inf, 5, 8, 10, 12, 15, np.inf] + t = P.tabulate(df, "median", "x", by="g", bins=bins, return_replicates=True) + for _, row in t.iterrows(): + sub = df[(df.g == row.g) & df.x.notna()] + ref = P.binned_median(sub.x.to_numpy(), sub[["PWGTP"] + REPS].to_numpy(np.float64), bins) + got = np.array([row.estimate] + [row[f"rep_{i}"] for i in range(1, 81)]) + self.assertTrue(np.array_equal(got, ref, equal_nan=True), row.g) + + +# 9. one owner of the default RPP areas rule +class TestDefaultRppAreas(unittest.TestCase): + def test_helper(self): + self.assertEqual(M.default_rpp_areas("county"), "county_metro") + self.assertEqual(M.default_rpp_areas("tract"), "state") + + def test_validation_and_execution_use_helper(self): + m = two_state_manifest(adjustments=[{"type": "rpp", "variables": ["income"], "rpp_year": 2023}]) + with mock.patch.object(M, "default_rpp_areas", return_value="bogus") as helper: + errs = _errs(m) + self.assertTrue(helper.called) + self.assertTrue(any("areas must be" in e for e in errs), errs) + + def test_place_adjust_frame_bad_areas_reported_once(self): + from census import workflows_microdata as WM + + d = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, d, True) + path = os.path.join(d, "frame.csv") + pd.DataFrame({"GEOID": ["26001"], "income": [1.0], "income_se": [0.1]}).to_csv(path, index=False) + out = WM.place_adjust_frame(path, ["income"], 2024, areas="msa") + self.assertFalse(out["success"]) + self.assertIn("areas must be one of", out["error"]) + + +# 10. export relabelling without a zero-row slice +class TestFormatDataframeNoSlice(unittest.TestCase): + def test_no_slice_same_object(self): + from census.export import format_dataframe + + df = pd.DataFrame([[1, 2, 3]], columns=["NAME", "B01001_001E", "B01001_002E"]) + with warnings.catch_warnings(), \ + mock.patch.object(pd.DataFrame, "iloc", new_callable=mock.PropertyMock, + side_effect=AssertionError("data sliced")): + warnings.simplefilter("error") + out = format_dataframe(df, "B01001", variable_descriptions={"B01001_001E": "Total"}) + self.assertIs(out, df) + self.assertEqual(list(df.columns), ["NAME", "Total", "B01001_002E"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_validation.py b/tests/test_validation.py index d59d0cc..138d746 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -88,7 +88,7 @@ def test_f5_details(self): self.assertLess(inc["median_rel_error"], 0.01) self.assertGreaterEqual(inc["share_within_published_moe"], 0.98) # Reported, not asserted: B19001 is coarser than Census's internal distribution. - self.assertAlmostEqual(inc23["share_within_2pct"], 0.8414, places=3) + self.assertAlmostEqual(inc23["share_within_2pct"], 0.8589, places=3) self.assertEqual(r.n_failed, 0) def test_f6_cp_inflation(self):