From 5e22e940e7c23938fffbafdec21c689b3e90be8b Mon Sep 17 00:00:00 2001 From: Jared Knowles Date: Wed, 5 Aug 2026 11:21:35 -0400 Subject: [PATCH] feat: all-categories mode in .build_verb_sql() The concept boundary in this package is subtype, not category, so a total is the existing query with the category dimension collapsed and no category predicate applied. subtype is deliberately kept in the grouping: subtype=operations plus all-categories is 'operating expenditure', which is the measure a fiscal comparison wants. Named 'All Categories' rather than 'Total' because category='Total' would sit one argument from expenditure_concept='total' and mean something different. --- R/spending.R | 38 ++++++++++++++++++++++----- tests/testthat/test-all-categories.R | 39 ++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 tests/testthat/test-all-categories.R diff --git a/R/spending.R b/R/spending.R index 29393c8..6aa82ec 100644 --- a/R/spending.R +++ b/R/spending.R @@ -19,6 +19,14 @@ .spend_subtypes_primary <- c("operations", "capital", "assistance") .spend_subtypes_direct <- c(.spend_subtypes_primary, "interest", "insurance_benefits") +# The reserved pseudo-category. Deliberately NOT "Total": `category = "Total"` +# would sit one argument away from `expenditure_concept = "total"` and mean +# something different -- the concept selects WHICH SUBTYPES are in scope, this +# selects whether the rows inside that scope are broken out by category or +# summed. "All Categories" states the operation and cannot be misread as the +# concept. +.ALL_CATEGORIES <- "All Categories" + #' @noRd .expenditure_concept_subtypes <- function(concept) { switch(concept, @@ -570,10 +578,16 @@ cog_spending <- function(govid, years, category = NULL, #' @noRd .build_verb_sql <- function(view, subtype_col, govid, years, category, - ig_view = NULL, subtype_scope = NULL) { + ig_view = NULL, subtype_scope = NULL, + all_categories = FALSE) { govid_lit <- .sql_lit_chr(govid) years_lit <- paste(as.integer(years), collapse = ",") - category_pred <- if (is.null(category)) { + # In all-categories mode there is no category filter: the sum is defined by + # the concept's SUBTYPE allowlist (subtype_pred below), which is the real + # concept boundary. Filtering by category as well would be a no-op at best + # and, if the crosswalk ever gained an uncategorized code, a silent + # under-count of the very total this mode exists to guarantee. + category_pred <- if (all_categories || is.null(category)) { "" } else { sprintf("AND category IN (%s)", .sql_lit_chr(category)) @@ -612,13 +626,24 @@ cog_spending <- function(govid, years, category = NULL, # though its dollars came entirely from an aggregate row, silently # suppressing the "Aggregate fallback applied" note on exactly the rows # this feature exists to surface. + + # Collapse the category dimension. subtype is deliberately KEPT: it is what + # makes `subtype = "operations"` + all-categories mean "operating + # expenditure", the measure a fiscal comparison actually wants. + category_select <- if (all_categories) { + sprintf("%s AS category", .sql_lit_chr(.ALL_CATEGORIES)) + } else { + "category" + } + category_group <- if (all_categories) "" else ", category" + sprintf( "SELECT year, canonical_govid, COALESCE(xwalk_gov_name, gov_name) AS gov_name, %1$s, - category, + %7$s, SUM(amt) * 1000.0 AS amt_nominal, string_agg(DISTINCT item_code, ',' ORDER BY item_code) AS codes_included, bool_or(is_aggregate) AS aggregate_fallback @@ -627,9 +652,10 @@ cog_spending <- function(govid, years, category = NULL, AND year IN (%4$s) %5$s %6$s - GROUP BY year, canonical_govid, gov_name, xwalk_gov_name, %1$s, category - ORDER BY year, canonical_govid, %1$s, category", - subtype_col, source_expr, govid_lit, years_lit, category_pred, subtype_pred + GROUP BY year, canonical_govid, gov_name, xwalk_gov_name, %1$s%8$s + ORDER BY year, canonical_govid, %1$s%8$s", + subtype_col, source_expr, govid_lit, years_lit, category_pred, subtype_pred, + category_select, category_group ) } diff --git a/tests/testthat/test-all-categories.R b/tests/testthat/test-all-categories.R new file mode 100644 index 0000000..d19dd3c --- /dev/null +++ b/tests/testthat/test-all-categories.R @@ -0,0 +1,39 @@ +# Baseline at branch point: 843 PASS / 0 FAIL / 0 SKIP / 0 WARN (2026-08-05, origin/main 2fc9e75) + +test_that(".build_verb_sql emits a literal category and no category filter in all-categories mode", { + sql <- uscogdata:::.build_verb_sql( + view = "spending_annotated", + subtype_col = "spend_subtype", + govid = "552025209777", + years = 2019L, + category = NULL, + subtype_scope = c("operations", "capital"), + all_categories = TRUE + ) + + expect_match(sql, "'All Categories' AS category", fixed = TRUE) + # no category filter of any kind + expect_false(grepl("AND category IN", sql, fixed = TRUE)) + # category is not a grouping key + expect_false(grepl("GROUP BY year, canonical_govid, gov_name, xwalk_gov_name, spend_subtype, category", + sql, fixed = TRUE)) + # the subtype allowlist still applies -- this is what makes the sum a concept + expect_match(sql, "AND spend_subtype IN ('operations','capital')", fixed = TRUE) +}) + +test_that(".build_verb_sql is unchanged when all_categories is FALSE", { + args <- list( + view = "spending_annotated", subtype_col = "spend_subtype", + govid = "552025209777", years = 2019L, category = NULL, + subtype_scope = c("operations", "capital") + ) + old <- do.call(uscogdata:::.build_verb_sql, args) + new <- do.call(uscogdata:::.build_verb_sql, c(args, list(all_categories = FALSE))) + expect_identical(old, new) + expect_match(new, "GROUP BY year, canonical_govid, gov_name, xwalk_gov_name, spend_subtype, category", + fixed = TRUE) +}) + +test_that(".ALL_CATEGORIES is the exact reserved string", { + expect_identical(uscogdata:::.ALL_CATEGORIES, "All Categories") +})