Skip to content

Commit 617148c

Browse files
committed
Consume cchs_missing_data.yaml: schema-driven fallback pattern and priority rules
The CCHS missing-data pattern schema was shipped but dormant. It now has two thin consumers reading only its normative blocks: - clean_variables()'s fallback pattern (used when a variable has no worksheet metadata) is built from the schema's pattern families instead of hardcoded values. Net behavior gain: the early-cycle decimal missing codes (999.6-999.9), declared in the schema but absent from the old hardcoded fallback, are now handled. - load_priority_rules() resolves the not-applicable-over-missing priority from the schema's declared hierarchy, replacing the built-in fallback and its once-per-session 'YAML files not found' warning. The priority decision is now read from a versioned, reviewable file. The schema gains an applies_to: source_codes declaration (codes are in the raw StatCan domain, not the harmonized domain) and a consumer note marking variable_assignments as reference-only - variable_details.csv remains the per-variable source of truth. Also adds .DS_Store to .Rbuildignore so Finder droppings stay out of the built package. Suite: 842 passing, 0 failures. Relates to CEP-017 Track 3.
1 parent 638de98 commit 617148c

9 files changed

Lines changed: 174 additions & 38 deletions

File tree

.Rbuildignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313
^papers$
1414
^\.claude$
1515
^ceps$
16+
^.*\.DS_Store$

NAMESPACE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export(has_cached_pattern)
110110
export(if_else2)
111111
export(is_equal)
112112
export(list_subjects)
113-
export(load_database_registry)
113+
export(load_cchs_missing_data)
114114
export(load_schema)
115115
export(load_worksheet_metadata)
116116
export(load_worksheet_schemas)

R/clean-variables.R

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -127,22 +127,17 @@ clean_variables <- function(vars, output_format = "tagged_na", check_length = TR
127127
pattern <- tryCatch({
128128
get_complete_pattern(var_name)
129129
}, error = function(e) {
130-
# Fallback to default CCHS pattern when metadata lookup fails
131-
# This allows functions to work before worksheets are fully migrated
130+
# Fallback to the declared CCHS pattern schema when metadata lookup
131+
# fails. This allows functions to work before worksheets are fully
132+
# migrated.
132133
warning_key <- paste0("pattern_fallback_", var_name)
133134
cache <- .get_pattern_warnings_cache()
134135
if (!exists(warning_key, envir = cache)) {
135136
assign(warning_key, TRUE, envir = cache)
136137
warning("Using default CCHS pattern for '", var_name,
137138
"' (metadata lookup failed). ", call. = FALSE)
138139
}
139-
# Return default CCHS single-digit pattern (most common for categorical vars)
140-
list(
141-
na_a_codes = c(6, 96, 996), # Not applicable
142-
na_b_codes = c(7, 8, 9, 97, 98, 99, 997, 998, 999), # Not stated/don't know/refusal
143-
copy_mappings = list(list(min = 1, max = 95)), # Valid range for most vars
144-
else_mappings = list()
145-
)
140+
.default_cchs_pattern()
146141
})
147142

148143
# Process data using pattern and output format
@@ -534,6 +529,46 @@ apply_else_rule <- function(value, else_mappings) {
534529
# UTILITY FUNCTIONS
535530
# ==============================================================================
536531

532+
#' Default CCHS Missing Pattern from the Declared Schema
533+
#'
534+
#' Builds the fallback missing pattern from the CCHS missing-data schema
535+
#' (inst/metadata/schemas/cchs/cchs_missing_data.yaml): the union of all
536+
#' pattern families' codes by tag, including the early-cycle decimal
537+
#' variants. Used when a variable has no worksheet metadata. If the schema
538+
#' cannot be read, falls back to the equivalent hardcoded values so the
539+
#' cleaning path never fails on a schema problem.
540+
#'
541+
#' @return Pattern list with na_a_codes, na_b_codes, copy_mappings,
542+
#' else_mappings
543+
#' @noRd
544+
.default_cchs_pattern <- function() {
545+
na_a <- c(6, 96, 996)
546+
na_b <- c(7, 8, 9, 97, 98, 99, 997, 998, 999)
547+
548+
tryCatch({
549+
schema <- load_cchs_missing_data()
550+
a <- numeric(0)
551+
b <- numeric(0)
552+
for (fam in schema$pattern_definitions$patterns) {
553+
hier <- fam$priority_hierarchy
554+
a <- c(a, unlist(hier$not_applicable$original_codes),
555+
unlist(hier$not_applicable$decimal_codes))
556+
b <- c(b, unlist(hier$missing_data$original_codes),
557+
unlist(hier$missing_data$decimal_codes))
558+
}
559+
if (length(a) > 0) na_a <- sort(unique(as.numeric(a)))
560+
if (length(b) > 0) na_b <- sort(unique(as.numeric(b)))
561+
}, error = function(e) NULL)
562+
563+
list(
564+
na_a_codes = na_a,
565+
na_b_codes = na_b,
566+
# Valid range for most variables; not declared in the schema
567+
copy_mappings = list(list(min = 1, max = 95)),
568+
else_mappings = list()
569+
)
570+
}
571+
537572
#' Coerce CCHS Label Strings to Numeric with Tagged NAs
538573
#'
539574
#' Converts character input to numeric while mapping recognized CCHS

R/load-schema.R

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,43 @@ load_schema <- function(file_type) {
6060
#' \dontrun{
6161
#' load_database_registry()
6262
#' }
63+
#' Load the CCHS missing-data pattern schema
64+
#'
65+
#' @description Loads the machine-actionable CCHS missing-data pattern
66+
#' definitions (inst/metadata/schemas/cchs/cchs_missing_data.yaml): the
67+
#' single/double/triple-digit code families with their decimal-era
68+
#' variants and the not-applicable-over-missing priority hierarchy.
69+
#'
70+
#' Consumers read the normative blocks only (`pattern_definitions` and
71+
#' `transformation_rules$na_category_definitions`); the file's
72+
#' variable-level assignments are reference documentation -- the
73+
#' per-variable source of truth remains variable_details.csv.
74+
#'
75+
#' @return Named list parsed from the YAML schema.
76+
#'
77+
#' @export
78+
#'
79+
#' @examples
80+
#' \dontrun{
81+
#' schema <- load_cchs_missing_data()
82+
#' names(schema$pattern_definitions$patterns)
83+
#' }
84+
load_cchs_missing_data <- function() {
85+
schema_path <- system.file(
86+
"metadata", "schemas", "cchs", "cchs_missing_data.yaml",
87+
package = "cchsflow",
88+
mustWork = TRUE
89+
)
90+
91+
tryCatch(
92+
yaml::read_yaml(schema_path),
93+
error = function(e) {
94+
stop("Failed to load the CCHS missing-data schema at ", schema_path,
95+
": ", e$message)
96+
}
97+
)
98+
}
99+
63100
load_database_registry <- function(registry_file = "database_registry.yaml") {
64101
registry_path <- system.file(
65102
"metadata", "schemas", "core", registry_file,

R/missing-data-functions.R

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,21 @@ load_priority_rules <- function() {
190190
warning("Could not load core priority rules: ", e$message)
191191
})
192192
}
193-
193+
194+
# Read the declared priority hierarchy from the CCHS missing-data schema:
195+
# every pattern family declares not_applicable wins over missing_data
196+
# (inst/metadata/schemas/cchs/cchs_missing_data.yaml)
197+
if (is.null(rules)) {
198+
tryCatch({
199+
schema <- load_cchs_missing_data()
200+
fams <- schema$pattern_definitions$patterns
201+
if (length(fams) > 0 &&
202+
!is.null(fams[[1]]$priority_hierarchy$not_applicable)) {
203+
rules <- list(na_a = 1, na_b = 2)
204+
}
205+
}, error = function(e) NULL)
206+
}
207+
194208
# Built-in fallback (general data handling: "Not Applicable" > "Not Stated")
195209
if (is.null(rules)) {
196210
rules <- list(na_a = 1, na_b = 2) # na_a higher priority as general fallback

inst/metadata/schemas/cchs/cchs_missing_data.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ schema_version: "1.0.0"
22
schema_date: "2025-01-20"
33
description: "CCHS missing data patterns and transformation rules - machine-actionable metadata reference"
44

5+
# All codes and ranges in this schema are in the SOURCE coding domain (raw
6+
# StatCan codes), not the harmonized/derived domain. Consumers: the
7+
# clean_variables() fallback pattern and load_priority_rules() read the
8+
# normative blocks (pattern_definitions, na_category_definitions);
9+
# variable_assignments is reference documentation only - the per-variable
10+
# source of truth is variable_details.csv.
11+
applies_to: "source_codes"
12+
513
# Cross-reference with human-readable documentation
614
related_documentation:
715
user_guide: "vignettes/missing_value_conventions.qmd"
@@ -201,7 +209,7 @@ implementation:
201209
# ============================================================================
202210
metadata:
203211
schema_authority: "cchsflow development team"
204-
last_reviewed: "2025-01-20"
212+
last_reviewed: "2026-07-15"
205213
next_review_due: "2025-07-20"
206214
version_history:
207215
"1.0.0": "Initial comprehensive specification with standardized pattern names (single/double/triple_digit_missing)"

man/load_cchs_missing_data.Rd

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/load_database_registry.Rd

Lines changed: 0 additions & 26 deletions
This file was deleted.

tests/testthat/test-check-worksheet.R

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,3 +461,26 @@ test_that("check_cross_file_keys flags orphaned variable_details entries", {
461461
expect_equal(errs[[1]]$variable, "ZZZ_typo")
462462
expect_equal(errs[[1]]$error_type, "orphaned_variable_details")
463463
})
464+
465+
# ==============================================================================
466+
# CCHS missing-data schema consumption
467+
# ==============================================================================
468+
469+
test_that("the fallback pattern comes from the CCHS missing-data schema", {
470+
pat <- cchsflow:::.default_cchs_pattern()
471+
expect_true(all(c(6, 96, 996, 999.6) %in% pat$na_a_codes))
472+
expect_true(all(c(7, 8, 9, 97, 98, 99, 997, 998, 999, 999.7, 999.8, 999.9)
473+
%in% pat$na_b_codes))
474+
})
475+
476+
test_that("priority rules resolve from the schema without a fallback warning", {
477+
# Clear the session cache so load_priority_rules() re-resolves
478+
rm(list = ls(envir = cchsflow:::.priority_rules_cache),
479+
envir = cchsflow:::.priority_rules_cache)
480+
expect_no_warning(rules <- cchsflow:::load_priority_rules())
481+
expect_equal(rules$na_a, 1)
482+
expect_equal(rules$na_b, 2)
483+
# Behavioural check: not applicable wins over missing
484+
pri <- get_priority_missing(haven::tagged_na("a"), haven::tagged_na("b"))
485+
expect_true(haven::is_tagged_na(pri, "a"))
486+
})

0 commit comments

Comments
 (0)