feat: cog_spending + cog_revenue + cog_explain
Three core query verbs over the spending_annotated / revenue_annotated DuckDB views. Each verb accepts vector govid, vector years, optional category filter, per_capita flag, and adjust_to_year for CPI-U real-dollar conversion (bundled index). Amounts are returned in full USD (SUM(amt) * 1000) so callers can freely rescale to millions/billions. The $1,000s -> $USD conversion is recorded in provenance$transformations$units_conversion. Every result carries an attr(., "provenance") list matching inst/schemas/provenance-v1.json. cog_explain() prints the structured form via cli or returns the raw list for MCP/JSON consumers. Also: .fetch_or_cache_manifest() now handles local fixture paths so tests can point USCOGDATA_FIXTURE_URL at the pipeline publish_cache/ without a working HTTP server. Tests: 80 pass / 0 fail. devtools::check() 0E/0W/2N (both notes pre-existing / environmental).
This commit is contained in:
+100
@@ -0,0 +1,100 @@
|
||||
# R/explain.R
|
||||
|
||||
#' Explain a verb result's provenance
|
||||
#'
|
||||
#' Prints the structured provenance attached to a tibble returned by any
|
||||
#' `cog_*` verb, or returns it as a list for downstream use (MCP tools,
|
||||
#' dashboards, JSON export).
|
||||
#'
|
||||
#' @param result A tibble returned by a `cog_*` verb.
|
||||
#' @param format `"print"` (default) for a human-readable cli summary;
|
||||
#' returns `result` invisibly for chaining. `"list"` returns the raw
|
||||
#' provenance list (identical to `attr(result, "provenance")`).
|
||||
#' @return Either `result` (invisibly) or the provenance list.
|
||||
#' @export
|
||||
cog_explain <- function(result, format = c("print", "list")) {
|
||||
format <- match.arg(format)
|
||||
prov <- attr(result, "provenance")
|
||||
if (is.null(prov)) {
|
||||
cli::cli_abort(c(
|
||||
"No `provenance` attribute on result.",
|
||||
i = "Pass a tibble returned by a cog_* verb (e.g. cog_spending())."
|
||||
))
|
||||
}
|
||||
if (format == "list") return(prov)
|
||||
.print_provenance(prov)
|
||||
invisible(result)
|
||||
}
|
||||
|
||||
#' @noRd
|
||||
.print_provenance <- function(prov) {
|
||||
cli::cli_h1("{prov$verb}()")
|
||||
|
||||
tgt_ids <- paste(prov$target$canonical_govid, collapse = ", ")
|
||||
tgt_names <- if (length(prov$target$gov_name) == 0L) {
|
||||
"(no rows returned)"
|
||||
} else {
|
||||
paste(prov$target$gov_name, collapse = ", ")
|
||||
}
|
||||
cli::cli_text("Target: {tgt_names} [canonical_govid: {tgt_ids}]")
|
||||
|
||||
yrs <- prov$years
|
||||
cli::cli_text(if (length(yrs) == 1L) {
|
||||
"Year: {yrs}"
|
||||
} else {
|
||||
"Years: {min(yrs)}-{max(yrs)} ({length(yrs)} years)"
|
||||
})
|
||||
|
||||
if (!is.null(prov$category)) {
|
||||
cli::cli_text("Category: {paste(prov$category, collapse = ', ')}")
|
||||
} else {
|
||||
cli::cli_text("Category: (all)")
|
||||
}
|
||||
|
||||
cli::cli_h2("Codes observed")
|
||||
codes <- prov$codes_summed$observed
|
||||
if (length(codes) == 0L) {
|
||||
cli::cli_alert_info("No item codes matched.")
|
||||
} else {
|
||||
cli::cli_ul(codes)
|
||||
}
|
||||
|
||||
if (isTRUE(prov$aggregate_fallback$applied)) {
|
||||
cli::cli_h2("Aggregate fallback")
|
||||
cli::cli_alert_warning(
|
||||
"Aggregate fallback used for years: {paste(prov$aggregate_fallback$years, collapse = ', ')}"
|
||||
)
|
||||
}
|
||||
|
||||
cli::cli_h2("Transformations")
|
||||
uc <- prov$transformations$units_conversion
|
||||
if (isTRUE(uc$applied)) {
|
||||
cli::cli_text("Units: {uc$source_unit} -> {uc$target_unit} (x{uc$multiplier})")
|
||||
}
|
||||
pc <- prov$transformations$per_capita
|
||||
if (isTRUE(pc$applied)) {
|
||||
cli::cli_text("Per-capita denominator: {pc$denominator_source}")
|
||||
}
|
||||
infl <- prov$transformations$inflation
|
||||
if (isTRUE(infl$applied)) {
|
||||
cli::cli_text("Inflation: {infl$index}, base year {infl$base_year}")
|
||||
}
|
||||
|
||||
cli::cli_h2("Scope")
|
||||
cli::cli_text(
|
||||
"Included gov types: {paste(prov$scope$gov_types_included, collapse = ', ')}"
|
||||
)
|
||||
cli::cli_text(
|
||||
"Excluded gov types: {paste(prov$scope$gov_types_excluded, collapse = ', ')}"
|
||||
)
|
||||
if (nzchar(prov$scope$scope_note %||% "")) {
|
||||
cli::cli_text("Note: {prov$scope$scope_note}")
|
||||
}
|
||||
|
||||
cli::cli_h2("Data vintage")
|
||||
cli::cli_text(
|
||||
"Manifest schema v{prov$manifest$schema_version}, pipeline {prov$manifest$pipeline_commit}, built {prov$manifest$built_at}"
|
||||
)
|
||||
|
||||
invisible(NULL)
|
||||
}
|
||||
Reference in New Issue
Block a user