fix: regenerate the bundled fixture against the sparsified corpus (#18)
The fixture predated three shipped corpus changes at once: no J rows in summary_categories (it was built before the crosswalk completion), no representation.parquet or code_set.parquet, and a still-dense wide era. Every test in this package and in cog-api runs against it, so both suites were green against a corpus that no longer exists. This is #18's stated prerequisite; it proves nothing about production until it lands. Regenerated from the publish tree at pipeline_commit 83f9715 (schema v6, built 2026-07-29). FY2011 goes from 2,864,212 rows to 496,004 -- 82.7% of the old partition was explicit zeros -- and the fixture now ships all ten publish-tree metadata tables rather than six. The generator's file list is a single constant now, so the copy step and the manifest step cannot drift. Three test repairs, each a real consequence of sparsification rather than a number to bump: test-categories.R "assistance" joined the spending subtype vocabulary with the J-prefix codes. test-spending.R The harmonization block counts rows that exist. Broward's E21/F21/G21 were zero-pads and are gone, so the anchor moves to FL state, whose three NA-mapped rows carry $2.83B -- the amount accounting was previously asserted only against 0 and could not have caught a bug. Broward keeps a test of its own, now asserting the zero-pads are absent. test-expenditure-concept.R Coverage-gap suggestions are presence-based. AL state's only FY2011 B47 cell was an explicit zero, so ig_federal_b47_wide stopped being a candidate there; FL state carries a real amount, so the counterpart guard is exercised against a suggestion that fires. test-fixture-vintage.R pins the structural facts that separate this vintage from its predecessor -- the ten metadata tables, the dense/sparse representation contract, zero explicit zeros in FY2011, code_set coverage, and J19's category. Checked against the old fixture: FY2011 carried 2,368,208 explicit zeros, so the assertion discriminates rather than merely passing. Suites: uscogdata 594 pass / 0 fail / 6 skip (was 576/0/6). cog-api 357 pass / 0 fail / 8 skip against the regenerated fixture, unchanged from its baseline.
This commit is contained in:
@@ -15,7 +15,11 @@ test_that("cog_categories(type = 'spending') returns only expenditure rows", {
|
||||
skip_if_no_corpus()
|
||||
r <- cog_categories(type = "spending")
|
||||
expect_true(all(r$category_type == "expenditure"))
|
||||
expect_true(all(r$subtype %in% c("operations", "capital", "intergovernmental")))
|
||||
# "assistance" (the J-prefix aid/benefit codes) joined the vocabulary with
|
||||
# the crosswalk completion in cog_pipeline#60/#65 -- every flow code
|
||||
# carrying dollars now maps to a category.
|
||||
expect_true(all(r$subtype %in%
|
||||
c("operations", "capital", "intergovernmental", "assistance")))
|
||||
})
|
||||
|
||||
test_that("cog_categories surfaces the intergovernmental spending subtype", {
|
||||
|
||||
@@ -298,8 +298,16 @@ test_that("a mis-scoped cog_spending() call never attaches an M/L counterpart to
|
||||
# (M47/M94, same suffixes) -- a coincidence of reused digits, not a real
|
||||
# Direct/Total pairing. The flow-family gate in
|
||||
# .attach_ig_counterparts() must keep ig_recipe_id NULL here.
|
||||
#
|
||||
# Anchored on FL state government, not AL. Coverage is presence-based: a
|
||||
# recipe is only suggested when its component codes have rows for the
|
||||
# requested government-year. AL state's only FY2011 B47 cell was an
|
||||
# explicit zero, which the corpus no longer stores after sparsification
|
||||
# (SB194, cog_pipeline#64), so the recipe stopped being a candidate there.
|
||||
# FL state carries a real FY2011 B47 amount, so this exercises the guard
|
||||
# against a suggestion that genuinely fires.
|
||||
r <- suppressMessages(
|
||||
cog_spending("010000226085", years = c(2005, 2011), category = "IG Federal")
|
||||
cog_spending("120000226351", years = c(2005, 2011), category = "IG Federal")
|
||||
)
|
||||
sugg <- attr(r, "provenance")$suggestions
|
||||
expect_gt(length(sugg), 0L)
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
# tests/testthat/test-fixture-vintage.R
|
||||
#
|
||||
# The bundled fixture is a slice of a real cog_pipeline publish tree, and
|
||||
# every test in this package -- plus the whole cog-api suite -- runs against
|
||||
# it. When the published corpus changes shape and the fixture does not, both
|
||||
# suites stay green against a corpus that no longer exists (uscogdata#18).
|
||||
#
|
||||
# These tests pin the structural facts that distinguish the current published
|
||||
# vintage from its predecessor, so a stale fixture fails loudly instead of
|
||||
# passing quietly. They assert shape, never dollar values: re-running
|
||||
# data-raw/regenerate_fixture_corpus.R against a newer publish tree should
|
||||
# keep them green.
|
||||
|
||||
# Open a bare DuckDB connection on the fixture's parquet files. Deliberately
|
||||
# not the package session: these assertions are about what the fixture
|
||||
# CONTAINS, and routing them through the reader's own views would let a
|
||||
# filter hide the very absence being checked.
|
||||
fixture_query <- function(sql, ...) {
|
||||
con <- DBI::dbConnect(duckdb::duckdb())
|
||||
on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)
|
||||
path <- function(rel) {
|
||||
sprintf("read_parquet(%s)",
|
||||
DBI::dbQuoteString(con, file.path(fixture_corpus_path(), rel)))
|
||||
}
|
||||
DBI::dbGetQuery(con, do.call(sprintf, c(list(sql), lapply(c(...), path))))
|
||||
}
|
||||
|
||||
test_that("fixture ships every metadata table the publish tree does", {
|
||||
skip_if_no_corpus()
|
||||
# representation/code_set are what make a sparse corpus interpretable; a
|
||||
# fixture without them predates sparsification (cog_pipeline#64).
|
||||
expected <- c(
|
||||
"canonical_alias.parquet", "canonical_fips_xwalk.parquet",
|
||||
"census_collection_coverage.parquet", "code_set.parquet",
|
||||
"harmonization_map.parquet", "harmonization_recipes.parquet",
|
||||
"lineage_events.parquet", "representation.parquet",
|
||||
"series_breaks.parquet", "summary_categories.parquet"
|
||||
)
|
||||
on_disk <- basename(list.files(
|
||||
file.path(fixture_corpus_path(), "data"), pattern = "\\.parquet$"
|
||||
))
|
||||
expect_true(all(expected %in% on_disk))
|
||||
|
||||
# The manifest must list them too -- consumers read the manifest, not ls().
|
||||
in_manifest <- with_fixture_corpus(
|
||||
basename(vapply(cog_manifest()$files$metadata, function(f) f$path, character(1)))
|
||||
)
|
||||
expect_true(all(expected %in% in_manifest))
|
||||
})
|
||||
|
||||
test_that("fixture carries the dense/sparse representation contract", {
|
||||
skip_if_no_corpus()
|
||||
rep <- fixture_query(
|
||||
"SELECT year, representation, absence_means FROM %s
|
||||
WHERE year IN (2011, 2012, 2019, 2020) ORDER BY year",
|
||||
"data/representation.parquet"
|
||||
)
|
||||
expect_equal(nrow(rep), 4L)
|
||||
expect_equal(rep$representation, c("dense_source", rep("sparse_source", 3L)))
|
||||
expect_equal(rep$absence_means, c("census_zero", rep("not_reported", 3L)))
|
||||
})
|
||||
|
||||
test_that("the fixture's wide era is sparse, not zero-padded", {
|
||||
skip_if_no_corpus()
|
||||
# FY2011 is a dense_source year: the corpus publishes only the cells Census
|
||||
# reported non-zero, and an absent cell means Census published $0. Before
|
||||
# sparsification this partition was 2,864,212 rows, ~83% of them explicit
|
||||
# zeros. A single explicit zero here means the fixture predates the change.
|
||||
zeros_2011 <- fixture_query(
|
||||
"SELECT COUNT(*) AS n FROM %s WHERE amt = 0",
|
||||
"data/long/year=2011/part-0.parquet"
|
||||
)$n
|
||||
expect_equal(zeros_2011, 0L)
|
||||
|
||||
# The modern era is a different regime: a reported zero there is real data
|
||||
# (the government filed $0), so zeros legitimately survive and must not be
|
||||
# asserted away.
|
||||
expect_gt(
|
||||
fixture_query("SELECT COUNT(*) AS n FROM %s", "data/long/year=2012/part-0.parquet")$n,
|
||||
0L
|
||||
)
|
||||
})
|
||||
|
||||
test_that("code_set covers every fixture year with the reader-spec columns", {
|
||||
skip_if_no_corpus()
|
||||
cs <- fixture_query(
|
||||
"SELECT * FROM %s WHERE year IN (2011, 2012, 2019, 2020)",
|
||||
"data/code_set.parquet"
|
||||
)
|
||||
expect_true(all(
|
||||
c("code_set_id", "year", "type", "item_code", "is_aggregate", "n_units")
|
||||
%in% names(cs)
|
||||
))
|
||||
expect_setequal(unique(cs$year), c(2011L, 2012L, 2019L, 2020L))
|
||||
})
|
||||
|
||||
test_that("every flow code carrying dollars has a category, J-prefix included", {
|
||||
skip_if_no_corpus()
|
||||
# The J (assistance/benefit) codes were uncategorised until the crosswalk
|
||||
# completion shipped (cog_pipeline#60/#65, J19 held back until #64's
|
||||
# duplication fix landed). Their absence is how a pre-crosswalk fixture
|
||||
# gives itself away.
|
||||
j <- fixture_query(
|
||||
"SELECT item_code, category, category_type, spend_subtype FROM %s
|
||||
WHERE LEFT(item_code, 1) = 'J' ORDER BY item_code",
|
||||
"data/summary_categories.parquet"
|
||||
)
|
||||
expect_true("J19" %in% j$item_code)
|
||||
expect_true(all(j$category_type == "expenditure"))
|
||||
expect_true(all(j$spend_subtype == "assistance"))
|
||||
expect_false(any(is.na(j$category)))
|
||||
})
|
||||
@@ -244,24 +244,42 @@ test_that("basis defaults to 'harmonized' when not passed", {
|
||||
test_that("provenance carries basis + harmonization block with na_rows_excluded", {
|
||||
skip_if_no_corpus()
|
||||
with_fixture_corpus({
|
||||
r <- cog_spending("121011212191", 2011:2012, "Corrections")
|
||||
# FL state government. The harmonization block is scoped by government,
|
||||
# year and flow prefix -- NOT by category -- so a Corrections query still
|
||||
# counts every E/F/G-prefixed row the harmonized basis drops for having
|
||||
# no harmonized_code. The three that apply here are E21/F21/G21
|
||||
# (Education NEC, SB184-186, "discontinued_na", wide-era window ending
|
||||
# FY2011); the other discontinued_na rulings live outside E/F/G.
|
||||
# See docs/phase_r_harmonization_review.md § 1.3/1.4 and cog_pipeline
|
||||
# data/harmonization_map.csv.
|
||||
r <- cog_spending("120000226351", 2011:2012, "Corrections")
|
||||
prov <- attr(r, "provenance")
|
||||
expect_equal(prov$basis, "harmonized")
|
||||
expect_true(prov$harmonization$applied)
|
||||
expect_true(prov$harmonization$na_rows_excluded >= 0L)
|
||||
expect_true(prov$harmonization$na_amount_excluded >= 0)
|
||||
# Data-verified for the v6 fixture (corpus 2026-07-22). The Task 18 map
|
||||
# extension added E/F/G-prefix discontinued_na rulings the earlier pin's
|
||||
# comment predated: E21/F21/G21 (Education NEC local, SB184-186,
|
||||
# "trivial; explicit-NA, full wide-era window"). Broward's 2011 legacy
|
||||
# partition zero-pads exactly those three codes, so this query now
|
||||
# excludes 3 NA-harmonized rows -- all with amt = 0, hence the excluded
|
||||
# AMOUNT stays exactly zero. (The other discontinued_na rulings -- S74,
|
||||
# Z61, X04, X06, the debt-detail family, L24 -- remain outside the
|
||||
# E/F/G/K prefixes.) See docs/phase_r_harmonization_review.md § 1.3/1.4
|
||||
# and cog_pipeline data/harmonization_map.csv E21/F21/G21 rows.
|
||||
expect_equal(prov$harmonization$na_rows_excluded, 3L)
|
||||
expect_equal(prov$harmonization$na_amount_excluded, 0)
|
||||
# $2,825,439 thousands of FY2011 E21 + F21 + G21, reported in full USD.
|
||||
# Pinning a non-zero amount is the point: the earlier Broward anchor's
|
||||
# three rows were all explicit zeros, so the AMOUNT accounting was
|
||||
# asserted only against 0 and could not have caught a bug.
|
||||
expect_equal(prov$harmonization$na_amount_excluded, 2825439 * 1000)
|
||||
})
|
||||
})
|
||||
|
||||
test_that("sparsification removed the wide era's zero-pads from the exclusion count", {
|
||||
skip_if_no_corpus()
|
||||
with_fixture_corpus({
|
||||
# Broward County FY2011 used to carry E21/F21/G21 rows of exactly $0 --
|
||||
# the wide era stored every government x every code, zeros included. The
|
||||
# published corpus no longer does (SB194, cog_pipeline#64), so there is
|
||||
# now nothing for the harmonized basis to exclude. Absence in a
|
||||
# dense_source year means Census published $0; it does not mean the
|
||||
# exclusion machinery stopped working, which the FL state anchor above
|
||||
# proves independently.
|
||||
r <- cog_spending("121011212191", 2011:2012, "Corrections")
|
||||
h <- attr(r, "provenance")$harmonization
|
||||
expect_true(h$applied)
|
||||
expect_equal(h$na_rows_excluded, 0L)
|
||||
expect_equal(h$na_amount_excluded, 0)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user