Sparsification (cog_pipeline#64, SB194) stopped the corpus storing the wide era's explicit zeros, which made absence ambiguous: <= FY2011 dense_source absent => Census published $0 >= FY2012 sparse_source absent => not reported, unknown A wide-era query whose cells were all $0 had begun returning nothing at all, with no way to get them back -- strictly less than the reader exposed before, which is why #64 filed this follow-on. complete = TRUE fills the requested grid from `code_set` and stamps every row with value_source: "reported", "census_zero" (amt 0), or "not_reported" (amt NA). The NA is the point. Filling a modern absence with 0 would invent data, which is exactly the error the representation contract exists to prevent -- and it makes this strictly MORE informative than the pre-sparsification corpus, which could not tell a published zero from an unreported cell either. Measured on the fixture, Broward County: FY2011 returns 28 reported + 16 census_zero; FY2019 returns 30 reported + 14 not_reported. The five categories that walkthrough finding F-006 read as "retired at FY2012" now report themselves correctly as census_zero before and not_reported after. Scoping decisions, each of which would invent rows if taken loosely: - The grid is per government TYPE (code_set.type). Filling against the union of all types would give a county cells like "state IG transfer to school districts", indistinguishable from real census zeros. - NOT is_aggregate, mirroring spending_long/revenue_long. Without it the grid offers cells those views never return, so each would fill as a phantom $0. - Filling happens BEFORE per_capita and inflation, so a census_zero stays 0 through both and a not_reported stays NA rather than becoming 0. Two new views (36-representation, 37-code_set) are gated on the manifest LISTING those tables, not on schema_version. Sparsification did not bump the version -- the fixture this package shipped against until 2026-07-30 was already v6 and carried neither table -- so a version gate would register a view over a missing file and fail at CREATE VIEW time on exactly the corpora the check exists to tolerate. with_corpus_missing_representation() models that corpus and asserts the abort. Refused where the fill would be guesswork, both classed uscogdata_complete_unsupported: a recipe defines its own component codes and never touches summary_categories; the intergovernmental leg deliberately keeps aggregate rows (inst/sql/24-ig_long.sql) so its cells are not the ones code_set describes. Expected cell sets in the tests are computed from the corpus parquet directly, never through the verb -- verifying what a filter does through that same filter proves nothing. Closes DoD 2, 3 and 4 of #18. DoD 5 (the cog-api follow-on) is filed separately. Suite: 658 pass / 0 fail / 3 skip (was 629/0/3). rcmdcheck clean.
189 lines
7.7 KiB
R
189 lines
7.7 KiB
R
# tests/testthat/test-complete.R
|
|
#
|
|
# uscogdata#18. The published corpus no longer stores the wide era's explicit
|
|
# zeros (cog_pipeline#64, series break SB194), so absence means two different
|
|
# things:
|
|
#
|
|
# <= FY2011 (dense_source) : cell absent => Census published $0
|
|
# >= FY2012 (sparse_source): cell absent => not reported, unknown
|
|
#
|
|
# `complete = TRUE` fills the requested grid from `code_set` and stamps every
|
|
# row's `value_source` so the two are distinguishable. Expected row sets here
|
|
# are built from the corpus parquet directly, never from the verb under test --
|
|
# verifying what a filter does through that same filter proves nothing.
|
|
|
|
# The (subtype, category) cells that SHOULD exist for one government-year:
|
|
# every code in force for that government's type, mapped through
|
|
# summary_categories, matching the verb's flow prefixes and excluding
|
|
# aggregate-flagged codes (which spending_long/revenue_long drop).
|
|
raw_expected_cells <- function(govid, year, prefixes, subtype_col) {
|
|
fx <- sub("/$", "", Sys.getenv("USCOGDATA_URL"))
|
|
q <- function(f) sprintf("read_parquet('%s/data/%s')", fx, f)
|
|
wt_raw_query(sprintf(
|
|
"SELECT DISTINCT c.%s AS subtype, c.category
|
|
FROM %s cs
|
|
JOIN %s x ON x.govs_type = cs.type
|
|
JOIN %s c ON c.item_code = cs.item_code
|
|
WHERE x.canonical_govid = '%s'
|
|
AND cs.year = %d
|
|
AND NOT cs.is_aggregate
|
|
AND LEFT(cs.item_code, 1) IN (%s)
|
|
AND c.category IS NOT NULL
|
|
AND c.%s IS NOT NULL",
|
|
subtype_col, q("code_set.parquet"), q("canonical_fips_xwalk.parquet"),
|
|
q("summary_categories.parquet"), govid, year,
|
|
paste0("'", prefixes, "'", collapse = ","), subtype_col
|
|
))
|
|
}
|
|
|
|
test_that("complete = FALSE is the default and changes nothing", {
|
|
skip_if_no_corpus()
|
|
with_fixture_corpus({
|
|
plain <- cog_spending("121011212191", 2011L)
|
|
explicit <- cog_spending("121011212191", 2011L, complete = FALSE)
|
|
expect_equal(nrow(plain), nrow(explicit))
|
|
expect_false("value_source" %in% names(plain))
|
|
})
|
|
})
|
|
|
|
test_that("complete = TRUE round-trips a dense-source year to the pre-sparsification cells", {
|
|
skip_if_no_corpus()
|
|
with_fixture_corpus({
|
|
# FY2011 is dense_source: before sparsification this government carried a
|
|
# row for every code in force, most of them $0. complete = TRUE must
|
|
# reproduce that cell set exactly.
|
|
r <- cog_spending("121011212191", 2011L, complete = TRUE)
|
|
expected <- raw_expected_cells("121011212191", 2011L,
|
|
c("E", "F", "G"), "spend_subtype")
|
|
|
|
key <- function(sub, cat) paste(sub, cat, sep = "|")
|
|
expect_setequal(key(r$spend_subtype, r$category),
|
|
key(expected$subtype, expected$category))
|
|
expect_gt(nrow(expected), 0L)
|
|
|
|
# Every filled cell in a dense-source year is a Census-published $0 --
|
|
# never "unknown", which is what the modern era's absences mean.
|
|
expect_setequal(unique(r$value_source), c("reported", "census_zero"))
|
|
expect_true(all(r$amt_nominal[r$value_source == "census_zero"] == 0))
|
|
expect_true(all(r$amt_nominal[r$value_source == "reported"] != 0))
|
|
})
|
|
})
|
|
|
|
test_that("complete = TRUE preserves the reported rows and their amounts exactly", {
|
|
skip_if_no_corpus()
|
|
with_fixture_corpus({
|
|
plain <- cog_spending("121011212191", 2011L)
|
|
full <- cog_spending("121011212191", 2011L, complete = TRUE)
|
|
|
|
# Filling adds rows; it must never alter or drop one.
|
|
expect_gt(nrow(full), nrow(plain))
|
|
reported <- full[full$value_source == "reported", ]
|
|
expect_equal(nrow(reported), nrow(plain))
|
|
expect_equal(sum(reported$amt_nominal), sum(plain$amt_nominal))
|
|
# ... and the total is unchanged, because every added cell is $0.
|
|
expect_equal(sum(full$amt_nominal, na.rm = TRUE), sum(plain$amt_nominal))
|
|
})
|
|
})
|
|
|
|
test_that("a sparse-source year's absences are unknown, not zero", {
|
|
skip_if_no_corpus()
|
|
with_fixture_corpus({
|
|
# FY2019 is sparse_source: an absent cell means the government did not
|
|
# report, which is NOT a zero. Filling those with 0 would invent data --
|
|
# the exact error the representation contract exists to prevent.
|
|
r <- cog_spending("121011212191", 2019L, complete = TRUE)
|
|
filled <- r[r$value_source != "reported", ]
|
|
expect_gt(nrow(filled), 0L)
|
|
expect_true(all(filled$value_source == "not_reported"))
|
|
expect_true(all(is.na(filled$amt_nominal)))
|
|
expect_false(any(r$value_source == "census_zero"))
|
|
})
|
|
})
|
|
|
|
test_that("the fill is scoped to each government's own type", {
|
|
skip_if_no_corpus()
|
|
with_fixture_corpus({
|
|
# Filling against the union of all types would invent cells for codes a
|
|
# county can never report. Every filled category must be one that
|
|
# code_set puts in force for type 1 (county) specifically.
|
|
r <- cog_spending("121011212191", 2011L, complete = TRUE)
|
|
county_cells <- raw_expected_cells("121011212191", 2011L,
|
|
c("E", "F", "G"), "spend_subtype")
|
|
expect_true(all(r$category %in% county_cells$category))
|
|
})
|
|
})
|
|
|
|
test_that("complete = TRUE respects the category filter", {
|
|
skip_if_no_corpus()
|
|
with_fixture_corpus({
|
|
r <- cog_spending("121011212191", 2011L, category = "Police",
|
|
complete = TRUE)
|
|
expect_true(all(r$category == "Police"))
|
|
expect_true("value_source" %in% names(r))
|
|
})
|
|
})
|
|
|
|
test_that("cog_revenue() completes on its own flow", {
|
|
skip_if_no_corpus()
|
|
with_fixture_corpus({
|
|
r <- cog_revenue("121011212191", 2011L, complete = TRUE)
|
|
expected <- raw_expected_cells("121011212191", 2011L,
|
|
c("T", "A", "U", "B", "C", "D"),
|
|
"revenue_subtype")
|
|
key <- function(sub, cat) paste(sub, cat, sep = "|")
|
|
expect_setequal(key(r$revenue_subtype, r$category),
|
|
key(expected$subtype, expected$category))
|
|
expect_setequal(unique(r$value_source), c("reported", "census_zero"))
|
|
})
|
|
})
|
|
|
|
test_that("provenance records the completion and its absence rule", {
|
|
skip_if_no_corpus()
|
|
with_fixture_corpus({
|
|
prov <- attr(cog_spending("121011212191", 2011L, complete = TRUE),
|
|
"provenance")
|
|
expect_true(prov$completion$applied)
|
|
expect_equal(prov$completion$absence_means$`2011`, "census_zero")
|
|
expect_gt(prov$completion$rows_filled, 0L)
|
|
|
|
off <- attr(cog_spending("121011212191", 2011L), "provenance")
|
|
expect_false(off$completion$applied)
|
|
expect_equal(off$completion$rows_filled, 0L)
|
|
})
|
|
})
|
|
|
|
test_that("complete = TRUE is refused where the fill would be guesswork", {
|
|
skip_if_no_corpus()
|
|
with_fixture_corpus({
|
|
# A recipe defines its own component codes and does not go through
|
|
# summary_categories at all, so there is no grid to fill from.
|
|
expect_error(
|
|
cog_spending("121011212191", 2011L, recipe = "corrections_combined",
|
|
complete = TRUE),
|
|
class = "uscogdata_complete_unsupported"
|
|
)
|
|
# The intergovernmental leg keeps aggregate rows by design
|
|
# (inst/sql/24-ig_long.sql), so its grid is not code_set's grid.
|
|
expect_error(
|
|
cog_spending("121011212191", 2011L, expenditure_concept = "total",
|
|
complete = TRUE),
|
|
class = "uscogdata_complete_unsupported"
|
|
)
|
|
})
|
|
})
|
|
|
|
test_that("complete = TRUE aborts on a corpus with no representation contract", {
|
|
skip_if_no_corpus()
|
|
# A corpus published before sparsification carries neither table, so there
|
|
# is nothing to fill from and no rule saying what an absence means. That
|
|
# must abort rather than guess.
|
|
with_corpus_missing_representation({
|
|
expect_error(
|
|
cog_spending("121011212191", 2011L, complete = TRUE),
|
|
class = "uscogdata_representation_unavailable"
|
|
)
|
|
# ... while an ordinary query on the same corpus still works.
|
|
expect_gt(nrow(cog_spending("121011212191", 2011L)), 0L)
|
|
})
|
|
})
|