feat: narrow harmonization signposting to per-code gap detection
.build_suggestions() previously flagged a recipe only when the WHOLE category result had zero rows in a requested year, so a multi-code category where one recipe component was genuinely gapped never fired if any sibling code (same recipe or not) had data that year. Each recipe's own in-category component is now checked individually -- a component fires when it has no rows in a requested (in-scope) year the recipe's own generic join otherwise covers, even when the overall category result looks complete. Decomposes .build_suggestions() into .category_recipe_components/ .recipe_meta/.component_presence/.recipe_coverage/.recipe_component_gapped helpers, drops the now-unused `result` param, and rewrites the header comment to describe the new, deliberately wider scope plus the per-government `covered` guard that still filters recipes with no data at all (ordinary reporting variance vs. a real format-boundary gap). Tests pin the multi-code case the coarse check missed (Cleburne County FY2012: G05 gapped, G04 covers, masked because E04/E05 have data) next to the still-guarded no-recipe-coverage case (F04/F05 both absent), and update the Broward 2019-2020 case to its new, correct expectation (fires for corrections_combined/corrections_other_capital_combined, still silent for corrections_capital_combined) plus a fresh true-full-coverage negative case (Maricopa County).
This commit is contained in:
@@ -176,6 +176,16 @@ test_that("recipe = requires schema_version >= 5", {
|
||||
})
|
||||
|
||||
# --- signposting -------------------------------------------------------
|
||||
#
|
||||
# Phase R3 / Task 19c: .build_suggestions() was narrowed from a whole-result
|
||||
# gap check (R2: does the ENTIRE category result have zero rows in a
|
||||
# requested year) to per-code gap detection (does a specific recipe
|
||||
# component -- itself a member of the requested category -- have zero rows
|
||||
# in a year the recipe's own generic join otherwise covers). See
|
||||
# R/suggestions.R's header comment and docs/phase_r_harmonization_review.md
|
||||
# § 0.3. The R2 test below ("...across the 2011->2012 gap") is unaffected
|
||||
# by the refinement (it already passed under both the coarse and per-code
|
||||
# rule). The next few pin cases the coarse rule specifically could NOT see.
|
||||
|
||||
test_that("signposting suggests corrections_combined across the 2011->2012 gap", {
|
||||
skip_if_no_corpus()
|
||||
@@ -193,10 +203,77 @@ test_that("signposting suggests corrections_combined across the 2011->2012 gap",
|
||||
expect_equal(hit$available_years, c(1967L, 2023L))
|
||||
})
|
||||
|
||||
test_that("no signposting when the result already has full year coverage", {
|
||||
test_that("per-code gap fires even when a sibling code masks the whole-result check (Cleburne County, FY2012)", {
|
||||
skip_if_no_corpus()
|
||||
# Cleburne County, AL (canonical_govid 011029122489), FY2012: E04 ($854)
|
||||
# and E05 ($1) both report ("operations" subtype), and G04 ($14,000,
|
||||
# corrections_other_capital_combined's modern-only leg) also reports
|
||||
# ("capital" subtype) -- so the WHOLE category result is non-empty for
|
||||
# 2012 (2 rows) and the R2 whole-result check would never look further.
|
||||
# But G05 -- G04's OWN recipe sibling, the 1967-2023 wide leg -- has
|
||||
# ZERO rows at all that year: a genuine, per-code gap the recipe exists
|
||||
# to bridge, invisible at the category-result grain because it's masked
|
||||
# by G04's own data, let alone the unrelated E04/E05 pair.
|
||||
r <- cog_spending("011029122489", years = 2012L, category = "Corrections")
|
||||
expect_equal(nrow(r), 2L) # operations + capital rows: a non-empty result
|
||||
|
||||
prov <- attr(r, "provenance")
|
||||
ids <- vapply(prov$suggestions, function(s) s$recipe_id, character(1))
|
||||
expect_true("corrections_other_capital_combined" %in% ids)
|
||||
hit <- prov$suggestions[[which(ids == "corrections_other_capital_combined")]]
|
||||
expect_equal(hit$hint, "re-run with recipe = 'corrections_other_capital_combined'")
|
||||
expect_equal(hit$available_years, c(1967L, 2023L))
|
||||
|
||||
# corrections_combined must NOT fire: E04 AND E05 both have real 2012
|
||||
# data for this government, so neither of ITS OWN components is gapped.
|
||||
expect_false("corrections_combined" %in% ids)
|
||||
})
|
||||
|
||||
test_that("per-code gap does not fire when no recipe component has any data at all (ordinary reporting variance, not a format-boundary gap)", {
|
||||
skip_if_no_corpus()
|
||||
# Same government/year as above: F04 and F05 (corrections_capital_combined)
|
||||
# are BOTH completely absent -- Cleburne simply never reported capital
|
||||
# corrections spending under that code family in 2012, wide-era or
|
||||
# modern. The recipe's own generic join (aggregate-inclusive, either
|
||||
# component) has nothing to offer either, so this must stay silent --
|
||||
# the per-government `covered` guard the header comment describes is
|
||||
# unchanged and still does this filtering.
|
||||
r <- cog_spending("011029122489", years = 2012L, category = "Corrections")
|
||||
prov <- attr(r, "provenance")
|
||||
ids <- vapply(prov$suggestions, function(s) s$recipe_id, character(1))
|
||||
expect_false("corrections_capital_combined" %in% ids)
|
||||
})
|
||||
|
||||
test_that("per-code gap fires for Broward 2019-2020 even though the category result looks complete", {
|
||||
skip_if_no_corpus()
|
||||
# Broward reports E04 + G04 (modern leaf codes) in BOTH 2019 and 2020 but
|
||||
# never reports E05 or G05 (their own recipe siblings) in either year --
|
||||
# a real per-code gap in two of the three Corrections recipes, invisible
|
||||
# under the R2 coarse check because the category *result* is non-empty
|
||||
# both years (this replaces the old R2-era "full year coverage" test,
|
||||
# whose premise -- that a non-empty result implies nothing to signpost --
|
||||
# is exactly what this refinement narrows; see data-raw/
|
||||
# measure_signposting_rate.R for the measured rate change this causes).
|
||||
# corrections_capital_combined correctly stays silent: Broward reports
|
||||
# neither F04 nor F05 in 2019 or 2020, so that recipe's own join has
|
||||
# nothing to offer either (ordinary non-reporting, not a format-boundary
|
||||
# gap) -- the per-government `covered` guard still does its job here too.
|
||||
r <- cog_spending("121011212191", years = 2019:2020, category = "Corrections")
|
||||
prov <- attr(r, "provenance")
|
||||
ids <- vapply(prov$suggestions, function(s) s$recipe_id, character(1))
|
||||
expect_true("corrections_combined" %in% ids)
|
||||
expect_true("corrections_other_capital_combined" %in% ids)
|
||||
expect_false("corrections_capital_combined" %in% ids)
|
||||
})
|
||||
|
||||
test_that("no signposting when every recipe component genuinely has data (true full per-code coverage)", {
|
||||
skip_if_no_corpus()
|
||||
# Maricopa County, AZ (canonical_govid 041013160815): all six Corrections
|
||||
# codes (E04, E05, F04, F05, G04, G05) report real, nonzero, non-aggregate
|
||||
# amounts in BOTH 2019 and 2020 -- genuinely nothing for any recipe to
|
||||
# fill, even at the finer per-code grain this refinement now checks.
|
||||
r <- cog_spending("041013160815", years = 2019:2020, category = "Corrections")
|
||||
prov <- attr(r, "provenance")
|
||||
expect_length(prov$suggestions, 0L)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user