Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
748ca4a56e | ||
|
|
fa40266d07 | ||
|
|
3583c05852
|
||
|
|
bd53230ae7 | ||
|
|
e813ffd3aa | ||
|
|
b0df1ec668 |
+24
-1
@@ -21,7 +21,30 @@
|
|||||||
.uscogdata_defaults[[key]]
|
.uscogdata_defaults[[key]]
|
||||||
}
|
}
|
||||||
|
|
||||||
.resolve_url <- function() .cfg("url")
|
#' Resolve the corpus URL, guaranteeing the trailing slash the package assumes.
|
||||||
|
#'
|
||||||
|
#' Every consumer builds locations by CONCATENATION -- `paste0(url,
|
||||||
|
#' "manifest.json")` in manifest.R, `paste0(url, e$path)` in mirror.R, and the
|
||||||
|
#' parquet glob in views.R -- and mirror.R:104 documents the invariant outright
|
||||||
|
#' ('url ends in "/"'). Nothing enforced it, so a URL entered without the slash
|
||||||
|
#' failed silently and misleadingly:
|
||||||
|
#'
|
||||||
|
#' HTTPS -> ".../downloadmanifest.json"; the host answers with an HTML 404
|
||||||
|
#' page, which lands in the JSON parser as the lexical error
|
||||||
|
#' reported in issue #3 -- pointing the user at "login page / wrong
|
||||||
|
#' share" when the real cause was one missing character.
|
||||||
|
#' local -> ".../corpusdata/long/**/*.parquet" and a DuckDB "No files found".
|
||||||
|
#'
|
||||||
|
#' Normalizing here fixes every consumer at once, rather than each call site
|
||||||
|
#' re-deriving the same invariant. An empty setting is passed through
|
||||||
|
#' untouched so manifest.R's "not configured" guard still fires instead of the
|
||||||
|
#' value degrading into a bare "/" filesystem root.
|
||||||
|
#' @noRd
|
||||||
|
.resolve_url <- function() {
|
||||||
|
url <- .cfg("url")
|
||||||
|
if (is.null(url) || !nzchar(url) || grepl("/$", url)) return(url)
|
||||||
|
paste0(url, "/")
|
||||||
|
}
|
||||||
|
|
||||||
.resolve_cache_dir <- function() {
|
.resolve_cache_dir <- function() {
|
||||||
v <- .cfg("cache_dir")
|
v <- .cfg("cache_dir")
|
||||||
|
|||||||
+11
-1
@@ -128,7 +128,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#' @noRd
|
#' @noRd
|
||||||
.validate_schema <- function(manifest, supported = c(4L, 5L)) {
|
#' Schema v6 (FIPS geography harmonization, 2026-07-22) is accepted alongside
|
||||||
|
#' 4/5. v6 renamed the long table's fips_state_code/fips_county_code to
|
||||||
|
#' fips_state_asof/fips_county_asof and added cog_legacy_state/
|
||||||
|
#' cog_legacy_county (26 -> 28 cols); this package references NONE of those
|
||||||
|
#' columns, so no code change was needed. NOTE the SILENT semantic change for
|
||||||
|
#' any consumer of the raw long table: long fips_state/fips_county are now
|
||||||
|
#' PRESENT/harmonized geography (current county identity carried back to every
|
||||||
|
#' year, matching canonical_fips_xwalk) rather than as-of-year; as-of-year
|
||||||
|
#' moved to the *_asof columns. This package's own geography always came from
|
||||||
|
#' the xwalk (already present-based), so behaviour is unchanged.
|
||||||
|
.validate_schema <- function(manifest, supported = c(4L, 5L, 6L)) {
|
||||||
if (!manifest$schema_version %in% supported) {
|
if (!manifest$schema_version %in% supported) {
|
||||||
cli::cli_abort(c(
|
cli::cli_abort(c(
|
||||||
"Corpus schema version mismatch.",
|
"Corpus schema version mismatch.",
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ cog_open <- function(url = .resolve_url(),
|
|||||||
DBI::dbExecute(con, "INSTALL httpfs; LOAD httpfs;")
|
DBI::dbExecute(con, "INSTALL httpfs; LOAD httpfs;")
|
||||||
|
|
||||||
manifest <- .fetch_or_cache_manifest(url, cache_dir)
|
manifest <- .fetch_or_cache_manifest(url, cache_dir)
|
||||||
.validate_schema(manifest, supported = c(4L, 5L))
|
.validate_schema(manifest, supported = c(4L, 5L, 6L))
|
||||||
.validate_scope(manifest)
|
.validate_scope(manifest)
|
||||||
|
|
||||||
.register_views(con, url, manifest)
|
.register_views(con, url, manifest)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+33
-20
@@ -1,11 +1,24 @@
|
|||||||
{
|
{
|
||||||
"schema_version": 5,
|
"schema_version": 6,
|
||||||
"built_at": "2026-07-19T03:08:41Z",
|
"built_at": "2026-07-23T16:14:30Z",
|
||||||
"pipeline_commit": "ece9b32",
|
"pipeline_commit": "4f992a0",
|
||||||
"fixture_note": "Four-year (2011, 2012, 2019, 2020) fixture for uscogdata tests. Full corpus available via USCOGDATA_URL. Regenerated for Phase R2 (schema_version 5, harmonization_map/harmonization_recipes/ series_breaks parquet tables added). 2011/2012 straddle the wide-aggregate -> modern-leaf format boundary exercised by basis= \"harmonized\" and recipe= queries; 2019/2020 retain the prior per-capita/CPI regression anchors. Full canonical_fips_xwalk master and canonical_alias lookup table included via data-raw/regenerate_fixture_corpus.R.",
|
"fixture_note": "Four-year (2011, 2012, 2019, 2020) fixture for uscogdata tests. Full corpus available via USCOGDATA_URL. Regenerated for Phase R2 (schema_version 5, harmonization_map/harmonization_recipes/ series_breaks parquet tables added). 2011/2012 straddle the wide-aggregate -> modern-leaf format boundary exercised by basis= \"harmonized\" and recipe= queries; 2019/2020 retain the prior per-capita/CPI regression anchors. Full canonical_fips_xwalk master and canonical_alias lookup table included via data-raw/regenerate_fixture_corpus.R.",
|
||||||
"data_vintage": {
|
"data_vintage": {
|
||||||
"census_source_downloaded": "unknown",
|
"source_vintages": {
|
||||||
"cpi_vintage": "FRED CPIAUCSL",
|
"2012": "10162019",
|
||||||
|
"2013": "10162019",
|
||||||
|
"2014": "10162019",
|
||||||
|
"2015": "10162019",
|
||||||
|
"2016": "10162019",
|
||||||
|
"2017": "06102021",
|
||||||
|
"2018": "06102021",
|
||||||
|
"2019": "06102021",
|
||||||
|
"2020": "06122023",
|
||||||
|
"2021": "06122023",
|
||||||
|
"2022": "06052025",
|
||||||
|
"2023": "06052025"
|
||||||
|
},
|
||||||
|
"registry_rows": 148,
|
||||||
"acs_vintage": "ACS 2018-2022 5-year"
|
"acs_vintage": "ACS 2018-2022 5-year"
|
||||||
},
|
},
|
||||||
"scope": {
|
"scope": {
|
||||||
@@ -14,8 +27,8 @@
|
|||||||
"scope_note": "v0.1 covers state, county, city/municipality, and township governments. Special districts (type 4) and school districts (type 5) are excluded pending validation in a future cycle."
|
"scope_note": "v0.1 covers state, county, city/municipality, and township governments. Special districts (type 4) and school districts (type 5) are excluded pending validation in a future cycle."
|
||||||
},
|
},
|
||||||
"schema": {
|
"schema": {
|
||||||
"long_column_count": 26,
|
"long_column_count": 28,
|
||||||
"long_columns": ["fips_state", "type", "fips_county", "govid", "gov_blank", "gov_name", "county_name", "fips_state_code", "fips_county_code", "fips_place_code", "population", "popyear", "enrollment", "enrollyear", "function_code", "sch_level_code", "fiscal_year_end", "srvy_year", "item_code", "amt", "srv_data", "impute_flag", "is_aggregate", "canonical_govid", "harmonized_code", "survey_weight"],
|
"long_columns": ["fips_state", "type", "fips_county", "govid", "gov_blank", "gov_name", "county_name", "fips_state_asof", "fips_county_asof", "cog_legacy_state", "cog_legacy_county", "fips_place_code", "population", "popyear", "enrollment", "enrollyear", "function_code", "sch_level_code", "fiscal_year_end", "srvy_year", "item_code", "amt", "srv_data", "impute_flag", "is_aggregate", "canonical_govid", "harmonized_code", "survey_weight"],
|
||||||
"data_dictionary": "docs/data_dictionary.md"
|
"data_dictionary": "docs/data_dictionary.md"
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
@@ -23,41 +36,41 @@
|
|||||||
{
|
{
|
||||||
"year": 2011,
|
"year": 2011,
|
||||||
"path": "data/long/year=2011/part-0.parquet",
|
"path": "data/long/year=2011/part-0.parquet",
|
||||||
"sha256": "76c2153ef0a94c3551a24751aa08225fc937fafb579f10fd0750848d781dc471",
|
"sha256": "84302ab364dc9fc3b3fbbc3c3f8b826e3508b4d73ff7c42d094d3863cd1e37b5",
|
||||||
"row_count": 3037606,
|
"row_count": 2864212,
|
||||||
"size_bytes": 4985667
|
"size_bytes": 3845911
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"year": 2012,
|
"year": 2012,
|
||||||
"path": "data/long/year=2012/part-0.parquet",
|
"path": "data/long/year=2012/part-0.parquet",
|
||||||
"sha256": "a9bb10d04887490376a4b030a03a4ca1e0f1f9fbca5f7d99ea7ca7434c42b2bc",
|
"sha256": "b82ac82d5e35f844b26c887445601f3748438c52c998ba4e403b025941a6f170",
|
||||||
"row_count": 1163338,
|
"row_count": 1163338,
|
||||||
"size_bytes": 5900191
|
"size_bytes": 5929917
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"year": 2019,
|
"year": 2019,
|
||||||
"path": "data/long/year=2019/part-0.parquet",
|
"path": "data/long/year=2019/part-0.parquet",
|
||||||
"sha256": "475481fcfbb030f96cae457cbda824512a2da6f97f4c6d12a75130979e4331f9",
|
"sha256": "5cbd4726dcc7d0dab5c2a05a64702e979533ae119ed0587073cd31c089e0d737",
|
||||||
"row_count": 318139,
|
"row_count": 318139,
|
||||||
"size_bytes": 1717345
|
"size_bytes": 1719548
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"year": 2020,
|
"year": 2020,
|
||||||
"path": "data/long/year=2020/part-0.parquet",
|
"path": "data/long/year=2020/part-0.parquet",
|
||||||
"sha256": "7cdf3eb1a73c34e6a3d22befdf7ccdd0b44b1e6b1cf4491159a38a5a24a1b005",
|
"sha256": "ee548fec80bf1beda844fe03916ac145f10dd34c45968407cc330ec260935f00",
|
||||||
"row_count": 317500,
|
"row_count": 317500,
|
||||||
"size_bytes": 1720794
|
"size_bytes": 1722918
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"metadata": [
|
"metadata": [
|
||||||
{
|
{
|
||||||
"path": "data/canonical_alias.parquet",
|
"path": "data/canonical_alias.parquet",
|
||||||
"sha256": "fa27ce4b59e286ace05fbcb9f59a910b8e483fd6c58ddf01875a210b966809cb",
|
"sha256": "3f617051c23a99bea322889857f7106df0c92954564afeec181df7083ee6698e",
|
||||||
"description": "canonical_alias.parquet"
|
"description": "canonical_alias.parquet"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "data/canonical_fips_xwalk.parquet",
|
"path": "data/canonical_fips_xwalk.parquet",
|
||||||
"sha256": "b6e2c4cb748141f6f2e324d6704c13830d2a68ad57bbb73d04ac54b881d1285b",
|
"sha256": "f98742f941269dacf8f7de5c273aa4dd4e75017a5bb70c054da35852a95a8d46",
|
||||||
"description": "canonical_fips_xwalk.parquet"
|
"description": "canonical_fips_xwalk.parquet"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -67,7 +80,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "data/harmonization_map.parquet",
|
"path": "data/harmonization_map.parquet",
|
||||||
"sha256": "52b4f3f94e65231aa869445946df7a0cfebf8fef1940f800e08287b53d09ab42",
|
"sha256": "4cf32d0f817079ba4f28dc0ce65450d3247ebbf08d94c0c26c0d02af597bf812",
|
||||||
"description": "harmonization_map.parquet"
|
"description": "harmonization_map.parquet"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -77,7 +90,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "data/series_breaks.parquet",
|
"path": "data/series_breaks.parquet",
|
||||||
"sha256": "049bd7365dae14e357f6e57765dc5a069440bab46facce237d7ba2ec94b0b113",
|
"sha256": "b0b6794b6887a4f300079adfa10029c2a77109faa4952fbff1c5a270793cc02b",
|
||||||
"description": "series_breaks.parquet"
|
"description": "series_breaks.parquet"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -26,3 +26,41 @@ test_that(".resolve_cache_dir falls back to R_user_dir", {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Trailing-slash normalization (uscogdata #3 follow-up).
|
||||||
|
#
|
||||||
|
# EVERY consumer builds paths by concatenation: paste0(url, "manifest.json")
|
||||||
|
# (manifest.R), paste0(url, e$path) (mirror.R), and the parquet glob in
|
||||||
|
# views.R. mirror.R:104 even comments 'url ends in "/"' -- an assumption the
|
||||||
|
# package documents and relies on but never enforced.
|
||||||
|
#
|
||||||
|
# A URL missing its trailing slash therefore fails SILENTLY and confusingly:
|
||||||
|
# HTTPS -> ".../downloadmanifest.json" -> the host answers with an HTML 404
|
||||||
|
# page -> the jsonlite lexical error that issue #3 reported;
|
||||||
|
# local -> ".../corpusdata/long/**/*.parquet" -> DuckDB "No files found".
|
||||||
|
# Neither message points at the real cause. Normalize once, at resolution.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
test_that(".resolve_url appends a missing trailing slash", {
|
||||||
|
withr::local_envvar(USCOGDATA_URL = "https://example.org/s/TOKEN/download")
|
||||||
|
expect_equal(.resolve_url(), "https://example.org/s/TOKEN/download/")
|
||||||
|
})
|
||||||
|
|
||||||
|
test_that(".resolve_url leaves an existing trailing slash alone", {
|
||||||
|
withr::local_envvar(USCOGDATA_URL = "https://example.org/s/TOKEN/download/")
|
||||||
|
expect_equal(.resolve_url(), "https://example.org/s/TOKEN/download/")
|
||||||
|
})
|
||||||
|
|
||||||
|
test_that(".resolve_url normalizes a local path without a trailing slash", {
|
||||||
|
withr::local_envvar(USCOGDATA_URL = "/tmp/corpus")
|
||||||
|
expect_equal(.resolve_url(), "/tmp/corpus/")
|
||||||
|
})
|
||||||
|
|
||||||
|
test_that(".resolve_url does not invent a slash for an empty setting", {
|
||||||
|
# An unset/empty URL must stay empty so the "not configured" guard in
|
||||||
|
# manifest.R still fires, rather than degrading into a bare "/" root.
|
||||||
|
withr::local_envvar(USCOGDATA_URL = "")
|
||||||
|
withr::local_options(uscogdata.url = "")
|
||||||
|
expect_equal(.resolve_url(), "")
|
||||||
|
})
|
||||||
|
|||||||
@@ -111,15 +111,20 @@ test_that("cog_manifest returns the active session's parsed manifest", {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test_that(".validate_schema accepts schema_version 4 and 5, rejects others", {
|
test_that(".validate_schema accepts schema_version 4, 5 and 6, rejects others", {
|
||||||
expect_silent(uscogdata:::.validate_schema(list(schema_version = 4L)))
|
expect_silent(uscogdata:::.validate_schema(list(schema_version = 4L)))
|
||||||
expect_silent(uscogdata:::.validate_schema(list(schema_version = 5L)))
|
expect_silent(uscogdata:::.validate_schema(list(schema_version = 5L)))
|
||||||
|
# v6 = FIPS geography harmonization (2026-07-22): _code -> _asof rename +
|
||||||
|
# cog_legacy_* columns (26 -> 28 cols). This package references none of the
|
||||||
|
# renamed columns and its geography comes from the xwalk, so v6 is accepted
|
||||||
|
# without behavioural change -- see .validate_schema()'s note.
|
||||||
|
expect_silent(uscogdata:::.validate_schema(list(schema_version = 6L)))
|
||||||
expect_error(
|
expect_error(
|
||||||
uscogdata:::.validate_schema(list(schema_version = 3L)),
|
uscogdata:::.validate_schema(list(schema_version = 3L)),
|
||||||
"schema_version"
|
"schema_version"
|
||||||
)
|
)
|
||||||
expect_error(
|
expect_error(
|
||||||
uscogdata:::.validate_schema(list(schema_version = 6L)),
|
uscogdata:::.validate_schema(list(schema_version = 7L)),
|
||||||
"schema_version"
|
"schema_version"
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -250,12 +250,17 @@ test_that("provenance carries basis + harmonization block with na_rows_excluded"
|
|||||||
expect_true(prov$harmonization$applied)
|
expect_true(prov$harmonization$applied)
|
||||||
expect_true(prov$harmonization$na_rows_excluded >= 0L)
|
expect_true(prov$harmonization$na_rows_excluded >= 0L)
|
||||||
expect_true(prov$harmonization$na_amount_excluded >= 0)
|
expect_true(prov$harmonization$na_amount_excluded >= 0)
|
||||||
# Data-verified for this fixture: none of the discontinued_na rulings
|
# Data-verified for the v6 fixture (corpus 2026-07-22). The Task 18 map
|
||||||
# (S74, Z61, X04, X06, the debt-detail family, L24) fall inside the
|
# extension added E/F/G-prefix discontinued_na rulings the earlier pin's
|
||||||
# E/F/G/K spending prefixes, so the exclusion count is exactly zero for
|
# comment predated: E21/F21/G21 (Education NEC local, SB184-186,
|
||||||
# every year in the bundled window -- see
|
# "trivial; explicit-NA, full wide-era window"). Broward's 2011 legacy
|
||||||
# docs/phase_r_harmonization_review.md § 1.3/1.4.
|
# partition zero-pads exactly those three codes, so this query now
|
||||||
expect_equal(prov$harmonization$na_rows_excluded, 0L)
|
# excludes 3 NA-harmonized rows -- all with amt = 0, hence the excluded
|
||||||
|
# AMOUNT stays exactly zero. (The other discontinued_na rulings -- S74,
|
||||||
|
# Z61, X04, X06, the debt-detail family, L24 -- remain outside the
|
||||||
|
# E/F/G/K prefixes.) See docs/phase_r_harmonization_review.md § 1.3/1.4
|
||||||
|
# and cog_pipeline data/harmonization_map.csv E21/F21/G21 rows.
|
||||||
|
expect_equal(prov$harmonization$na_rows_excluded, 3L)
|
||||||
expect_equal(prov$harmonization$na_amount_excluded, 0)
|
expect_equal(prov$harmonization$na_amount_excluded, 0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user