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.
This commit is contained in:
+32
-6
@@ -19,6 +19,14 @@
|
|||||||
.spend_subtypes_primary <- c("operations", "capital", "assistance")
|
.spend_subtypes_primary <- c("operations", "capital", "assistance")
|
||||||
.spend_subtypes_direct <- c(.spend_subtypes_primary, "interest", "insurance_benefits")
|
.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
|
#' @noRd
|
||||||
.expenditure_concept_subtypes <- function(concept) {
|
.expenditure_concept_subtypes <- function(concept) {
|
||||||
switch(concept,
|
switch(concept,
|
||||||
@@ -570,10 +578,16 @@ cog_spending <- function(govid, years, category = NULL,
|
|||||||
|
|
||||||
#' @noRd
|
#' @noRd
|
||||||
.build_verb_sql <- function(view, subtype_col, govid, years, category,
|
.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)
|
govid_lit <- .sql_lit_chr(govid)
|
||||||
years_lit <- paste(as.integer(years), collapse = ",")
|
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 {
|
} else {
|
||||||
sprintf("AND category IN (%s)", .sql_lit_chr(category))
|
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
|
# though its dollars came entirely from an aggregate row, silently
|
||||||
# suppressing the "Aggregate fallback applied" note on exactly the rows
|
# suppressing the "Aggregate fallback applied" note on exactly the rows
|
||||||
# this feature exists to surface.
|
# 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(
|
sprintf(
|
||||||
"SELECT
|
"SELECT
|
||||||
year,
|
year,
|
||||||
canonical_govid,
|
canonical_govid,
|
||||||
COALESCE(xwalk_gov_name, gov_name) AS gov_name,
|
COALESCE(xwalk_gov_name, gov_name) AS gov_name,
|
||||||
%1$s,
|
%1$s,
|
||||||
category,
|
%7$s,
|
||||||
SUM(amt) * 1000.0 AS amt_nominal,
|
SUM(amt) * 1000.0 AS amt_nominal,
|
||||||
string_agg(DISTINCT item_code, ',' ORDER BY item_code) AS codes_included,
|
string_agg(DISTINCT item_code, ',' ORDER BY item_code) AS codes_included,
|
||||||
bool_or(is_aggregate) AS aggregate_fallback
|
bool_or(is_aggregate) AS aggregate_fallback
|
||||||
@@ -627,9 +652,10 @@ cog_spending <- function(govid, years, category = NULL,
|
|||||||
AND year IN (%4$s)
|
AND year IN (%4$s)
|
||||||
%5$s
|
%5$s
|
||||||
%6$s
|
%6$s
|
||||||
GROUP BY year, canonical_govid, gov_name, xwalk_gov_name, %1$s, category
|
GROUP BY year, canonical_govid, gov_name, xwalk_gov_name, %1$s%8$s
|
||||||
ORDER BY year, canonical_govid, %1$s, category",
|
ORDER BY year, canonical_govid, %1$s%8$s",
|
||||||
subtype_col, source_expr, govid_lit, years_lit, category_pred, subtype_pred
|
subtype_col, source_expr, govid_lit, years_lit, category_pred, subtype_pred,
|
||||||
|
category_select, category_group
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user