Closes the last blocked test in the suite. Owner ruled both halves of the open question yes on 2026-07-30. `cog_revenue()` gains `revenue_concept`, mirroring `expenditure_concept`, with Census's two published concepts defined as crosswalk `revenue_subtype` sets rather than item-code prefixes: general = own_source + federal + state + local_aid (the default) total = general + utility + liquor_store + insurance_trust The manual defines the first by subtracting the other three from the second (4.3), so both are computable only once all four families are named -- which cog_pipeline#79 does. Insurance trust now includes the employee-retirement X codes (X01/X02/X05/X08) alongside the Y codes. - inst/sql: revenue_long / revenue_long_harmonized carry EVERY revenue subtype; the concept narrows in R via the existing subtype_scope machinery, exactly as expenditure_concept narrows spending_long. - cog_explain() now prints each verb's OWN concept. It previously printed `expenditure_concept` unconditionally, so a cog_revenue() caller was told "Concept: primary" -- a spending concept their result has nothing to do with. - Fixture regenerated at pipeline_commit aadb46b (330 crosswalk rows). Corrected two stale expectations in the blocked test while un-skipping it. It asserted X01+X04+X05+X08 and omitted X02, which applies to state governments and is nonzero for Wisconsin; X04 is an exhibit code for an INTRAgovernmental transfer that Census's own "Total Emp Ret Rev" excludes. Verified against that Census field: the right set is X01+X02+X05+X08 = $2,283,883k, exactly. And its expected `total` of $33,377,093k predated the Y codes being classified -- complete Total Revenue for WI FY2012 is $34,881,961k (general 31,338,293 + Y 1,259,785 + X 2,283,883). Behaviour change worth knowing: `general` is now STRICT Census General Revenue, so utility and liquor store revenue leave the default. Measured on the fixture that is 15.9% of what cog_revenue() returned for cities, vs 1.2% for states and 1.7% for counties. Suite: 716 pass / 0 fail / 0 skip -- the first time this package has had no skipped tests. Closes #12
96 lines
4.0 KiB
R
96 lines
4.0 KiB
R
test_that("cog_categories returns all categories grouped by subtype", {
|
|
skip_if_no_corpus()
|
|
r <- cog_categories()
|
|
expect_s3_class(r, "tbl_df")
|
|
expected <- c("category", "category_type", "subtype",
|
|
"n_codes", "item_codes")
|
|
expect_true(all(expected %in% names(r)))
|
|
expect_gt(nrow(r), 10L)
|
|
# corpus preserves Census-native "expenditure" vocabulary; the API takes
|
|
# "spending" as a friendlier alias.
|
|
#
|
|
# `balance` joined as a third category_type with the cash-and-security
|
|
# holding codes (pipeline#76). `cog_categories()` is a CATALOGUE verb, not a
|
|
# money verb, so it surfaces every category_type the corpus carries -- the
|
|
# stock/flow guard belongs on cog_spending()/cog_revenue(), which must never
|
|
# return a balance row.
|
|
expect_setequal(unique(r$category_type),
|
|
c("expenditure", "revenue", "balance"))
|
|
})
|
|
|
|
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"))
|
|
# "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.
|
|
# `interest` (I89, I91-I94) and `insurance_benefits` (Y05/Y06/Y14/Y53)
|
|
# joined with the I/Q/Y flow batch -- the last two characters of Census's
|
|
# expenditure taxonomy. `interest` is what makes the three-concept model
|
|
# computable: primary = direct minus debt service.
|
|
expect_true(all(r$subtype %in%
|
|
c("operations", "capital", "intergovernmental", "assistance",
|
|
"interest", "insurance_benefits")))
|
|
})
|
|
|
|
test_that("cog_categories surfaces the intergovernmental spending subtype", {
|
|
skip_if_no_corpus()
|
|
r <- cog_categories(type = "spending")
|
|
expect_true("intergovernmental" %in% r$subtype)
|
|
# IG rows reuse the existing functional categories -- they add a subtype,
|
|
# not new category values.
|
|
ig_cats <- sort(unique(r$category[r$subtype == "intergovernmental"]))
|
|
direct_cats <- sort(unique(r$category[r$subtype != "intergovernmental"]))
|
|
expect_true(all(ig_cats %in% c(direct_cats, "Other Education")))
|
|
})
|
|
|
|
test_that("cog_categories(type = 'revenue') returns only revenue rows", {
|
|
skip_if_no_corpus()
|
|
r <- cog_categories(type = "revenue")
|
|
expect_true(all(r$category_type == "revenue"))
|
|
# The four non-general subtypes are deliberately NOT own_source: Census's
|
|
# General Revenue excludes insurance trust (Y01 alone is $1.31T corpus-wide,
|
|
# plus the employee-retirement X codes), utility (A91-A94) and liquor store
|
|
# (A90) revenue by definition, which is what makes both of its published
|
|
# revenue concepts computable -- see `revenue_concept` in `?cog_revenue`.
|
|
expect_true(all(r$subtype %in%
|
|
c("own_source", "federal", "state", "local_aid",
|
|
"insurance_trust", "utility", "liquor_store")))
|
|
})
|
|
|
|
test_that("cog_categories(pattern = ...) filters case-insensitively", {
|
|
skip_if_no_corpus()
|
|
r <- cog_categories(pattern = "police")
|
|
expect_gt(nrow(r), 0L)
|
|
expect_true(all(grepl("Police", r$category, ignore.case = TRUE)))
|
|
})
|
|
|
|
test_that("cog_categories has one row per (category, subtype)", {
|
|
skip_if_no_corpus()
|
|
r <- cog_categories()
|
|
key <- paste(r$category, r$subtype, sep = "|")
|
|
expect_equal(length(key), length(unique(key)))
|
|
})
|
|
|
|
test_that("cog_categories item_codes is non-empty comma-separated string", {
|
|
skip_if_no_corpus()
|
|
r <- cog_categories()
|
|
expect_true(all(nzchar(r$item_codes)))
|
|
expect_true(all(r$n_codes >= 1L))
|
|
# n_codes should equal count of commas + 1
|
|
expect_equal(r$n_codes,
|
|
vapply(strsplit(r$item_codes, ","), length, integer(1)))
|
|
})
|
|
|
|
test_that("cog_categories sorted by category_type, category, subtype", {
|
|
skip_if_no_corpus()
|
|
r <- cog_categories()
|
|
sorted <- r[order(r$category_type, r$category, r$subtype), ]
|
|
expect_identical(r, sorted)
|
|
})
|
|
|
|
test_that("cog_categories rejects invalid type", {
|
|
expect_error(cog_categories(type = "both"), "type")
|
|
})
|