Adds a reserved category = "All Categories" that returns one summed row
per (year, govid, subtype) across every category in the requested concept's
subtype scope. The concept boundary in this package is subtype, not category,
so the sum is defined by the subtype allowlist rather than by category
membership — which is what makes it a guarantee rather than a convenience.
Named 'All Categories' rather than 'Total' because category = "Total"
would sit one argument away from expenditure_concept = "total" and mean
something different.
Also documents that n_units_reporting is category-conditional and is not a
response rate (uscogdata#36).
Verified on the full 1967-2024 corpus across FY1972/2002/2022/2024, spanning
both the census-id and FIPS-id vintages:
FY1972 by_category 196,747,000 all_categories 196,747,000 MATCH
FY2002 by_category 1,471,819,000 all_categories 1,471,819,000 MATCH
FY2022 by_category 1,763,918,000 all_categories 1,763,918,000 MATCH
FY2024 by_category 2,708,239,000 all_categories 2,708,239,000 MATCH
Revenue verified too (Atlanta FY2022, expenditure_concept = "general"):
1,627,842,000 both ways.
Full test suite: 880 PASS / 0 FAIL / 0 SKIP / 0 WARN.
Version bumped to 0.2.0 (minor — adds public surface without breaking any
existing call). This is load-bearing: cog-api installs this package with install_local(), which no-ops when the version already matches, so Phase 1
needs the bump to actually exercise the new reader instead of silently
testing against 0.1.0.
Closes cog-api#37 (reader half) and uscogdata#36.
Adds a reserved `category = "All Categories"` that returns one summed row
per (year, govid, subtype) across every category in the requested concept's
subtype scope. The concept boundary in this package is subtype, not category,
so the sum is defined by the subtype allowlist rather than by category
membership — which is what makes it a guarantee rather than a convenience.
Named 'All Categories' rather than 'Total' because `category = "Total"`
would sit one argument away from `expenditure_concept = "total"` and mean
something different.
Also documents that `n_units_reporting` is category-conditional and is not a
response rate (uscogdata#36).
Verified on the full 1967-2024 corpus across FY1972/2002/2022/2024, spanning
both the census-id and FIPS-id vintages:
```
FY1972 by_category 196,747,000 all_categories 196,747,000 MATCH
FY2002 by_category 1,471,819,000 all_categories 1,471,819,000 MATCH
FY2022 by_category 1,763,918,000 all_categories 1,763,918,000 MATCH
FY2024 by_category 2,708,239,000 all_categories 2,708,239,000 MATCH
```
Revenue verified too (Atlanta FY2022, `expenditure_concept = "general"`):
1,627,842,000 both ways.
Full test suite: 880 PASS / 0 FAIL / 0 SKIP / 0 WARN.
Version bumped to 0.2.0 (minor — adds public surface without breaking any
existing call). This is load-bearing: `cog-api` installs this package with
`install_local()`, which no-ops when the version already matches, so Phase 1
needs the bump to actually exercise the new reader instead of silently
testing against 0.1.0.
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.
Returns one summed row per (year, govid, subtype) across every
category in the requested concept's subtype scope, so a caller never
sums categories client-side and cannot sum the wrong scope.
Combining it with other category names is an error rather than a
silent partial sum.
The @param category text on cog_spending()/cog_revenue() told users to
"Combine with subtype = ..." but neither verb has a subtype argument.
Replace with accurate guidance: filter the returned frame's
spend_subtype/revenue_subtype column.
A reserved value nobody can discover is a trap, and this is the view
the API's /categories endpoint is built from. Emitted for the two flow
vocabularies only -- cog_balances() returns a stock and has no concept
to sum within.
Also fix test-categories.R to exclude pseudo-category rows from
crosswalk-specific assertions (one row per (category, subtype) pair,
non-empty item_codes, valid subtypes).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Geographic totals are the expensive case cog-api#37 was filed about --
without this a caller issues one rollup per category and sums them.
The pass-through was expected to work by construction; this asserts it
rather than assuming it, including under per_capita and inflation
adjustment.
Closes uscogdata#36. It counts governments with rows for the requested
category, so a surveyed government that genuinely spends nothing there
is indistinguishable from one never surveyed. In FY2022, a complete
census year, Georgia reports 393 of 567 cities for Police -- the gap is
cities that contract to the sheriff.
Documents the comparison that IS valid: same category, census year vs
sample year.
Bumps the minor version because 'All Categories' adds public surface
without breaking any existing call.
The bump is load-bearing, not cosmetic: cog-api installs this package
with install_local(), which no-ops when the version already matches.
Without it, Phase 1 would silently test against the 0.1.0 reader and
pass while proving nothing.
- .detect_direct_suppressed() keys on (year, canonical_govid, category);
all-categories mode collapses category to one literal value, so the key
collides and the detector silently reports FALSE instead of "unknown".
Report NA there instead, and stop isTRUE() in .build_provenance() from
collapsing that NA back to FALSE. Schema widened to allow null.
- Refuse complete = TRUE + category = "All Categories": the completion grid
has no per-category cells left to fill once categories are collapsed,
so the prior silent 0-rows-filled result was never actually checked.
- cog_balances(category = "All Categories") returned zero rows with no
error. .validate_verb_inputs() gains allow_all_categories (default
FALSE); .verb_spendrev() passes TRUE, cog_balances() does not, so the
three verbs share one place to reject it instead of drifting again.
- Fix the false `subtype = "operations"` argument claim (no such argument
exists) in NEWS.md and an internal spending.R comment.
Adds three covering tests to test-all-categories.R for the three
behaviour changes above.
.build_suggestions()'s recipe-candidate sub-select was keyed on
`WHERE category IN (<category>)`. The reserved pseudo-category
"All Categories" is never itself a row in summary_categories.category, so
in all-categories mode `candidates` always came back empty and coverage
signposting (uscogdata#9) was structurally impossible for the one mode
whose entire premise is "you cannot sum the wrong scope" -- measured on
Los Angeles County FY2011: category = "Public Welfare" reports 2
suggestions (incl. $271,589,000 excluded E68), category = "All Categories"
reported 0, silently losing that same signal.
Apply the branch's own design principle: the concept boundary is subtype,
not category. .build_suggestions() now accepts all_categories/subtype_col/
subtype_scope (all optional, default off, so no other caller's behaviour
changes) and, when all-categories mode is active, scopes the candidate
sub-select by `<subtype_col> IN (<subtype_scope>)` instead -- symmetric
with .build_verb_sql()'s own WHERE predicate. The M/L recipe exclusion and
the is.null(category) early return are unchanged.
After the fix, LA County FY2011 "All Categories" reports 5 suggestions,
including welfare_cash_e68_wide for the exact $271,589,000 gap.
Adds two covering tests to test-all-categories.R using the bundled fixture
(AL state gov, FY2011, "Corrections"): one end-to-end (per-category and
all-categories both signpost the same recipe) and one direct on
.build_suggestions() proving the subtype-vs-category branch is what
changes the query. Updates the 0.2.0 NEWS entry.
jared
merged commit e3ab26c3e6 into main2026-08-05 12:50:46 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes cog-api#37 (reader half) and uscogdata#36.
Adds a reserved
category = "All Categories"that returns one summed rowper (year, govid, subtype) across every category in the requested concept's
subtype scope. The concept boundary in this package is subtype, not category,
so the sum is defined by the subtype allowlist rather than by category
membership — which is what makes it a guarantee rather than a convenience.
Named 'All Categories' rather than 'Total' because
category = "Total"would sit one argument away from
expenditure_concept = "total"and meansomething different.
Also documents that
n_units_reportingis category-conditional and is not aresponse rate (uscogdata#36).
Verified on the full 1967-2024 corpus across FY1972/2002/2022/2024, spanning
both the census-id and FIPS-id vintages:
Revenue verified too (Atlanta FY2022,
expenditure_concept = "general"):1,627,842,000 both ways.
Full test suite: 880 PASS / 0 FAIL / 0 SKIP / 0 WARN.
Version bumped to 0.2.0 (minor — adds public surface without breaking any
existing call). This is load-bearing:
cog-apiinstalls this package withinstall_local(), which no-ops when the version already matches, so Phase 1needs the bump to actually exercise the new reader instead of silently
testing against 0.1.0.