diff --git a/NEWS.md b/NEWS.md index bd95835..30b605b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ * Use options to set `schema_version` and `branch` arguments in `download_tasks_schema()` and the `create_*()` family of functions for creating config files programmatically. This allows for setting the schema version and branch globally for the session (#85). * Make validation of `round_id` patterns explicit. This ties in with schema version v4.0.1 where the pattern the `round_id` property must match if `round_id_from_variable` is `false` is now specified as a regular expression in the schema. This check is also now implemented dynamically on values of the `round_id` variable if `round_id_from_variable` is `true` when validating tasks.json config files. Checks on `round_id` patterns are also now implemented in `create_round()` when creating rounds programmatically. +* As of schema version v5.0.0, only a single `target_keys` element is allowed when creating target metadata items programmatically (#89). # hubAdmin 1.4.0 diff --git a/R/create_target_metadata_item.R b/R/create_target_metadata_item.R index 9e2e290..ade3d03 100644 --- a/R/create_target_metadata_item.R +++ b/R/create_target_metadata_item.R @@ -11,13 +11,15 @@ #' @param target_name character string. A longer human readable target description #' that could be used, for example, as a visualisation axis label. #' @param target_units character string. Unit of observation of the target. -#' @param target_keys named list or `NULL`. Should be `NULL`, in the case -#' where the target is not specified as a task_id but is specified solely through -#' the `target_id` argument. Otherwise, should be a named list of one or more -#' character strings. The name of each element should match a task_id variable -#' within the same model_tasks object. Each element should be of length 1. -#' Each value, or the combination of values if multiple keys are specified, -#' define a single target value. +#' @param target_keys named list or `NULL`. The `target_keys` value defines a +#' single target. +#' Should be a named list containing a single character string element. +#' The name of the element should match a `task_id` variable name within the same +#' `model_tasks` object and the value should match a single value of that variable +#' as described in +#' [target metadata section of the official hubverse documentation](https://hubverse.io/en/latest/user-guide/tasks.html#target-metadata). # nolint: line_length_linter +#' Otherwise, `NULL` in the case where the target is not specified as a task_id +#' but is specified solely through the `target_id` argument. #' @param description character string (optional). An optional verbose description #' of the target that might include information such as definitions of a 'rate' or similar. #' @param target_type character string. Target statistical data type. Consult the @@ -117,7 +119,10 @@ create_target_metadata_item <- function(target_id, target_name, target_units, } ) - check_target_keys(target_keys, call = call) + check_target_keys(target_keys, + schema_version = schema$`$id`, + call = call + ) structure(mget(property_names), class = c("target_metadata_item", "list"), @@ -127,7 +132,8 @@ create_target_metadata_item <- function(target_id, target_name, target_units, ) } -check_target_keys <- function(target_keys, call = rlang::caller_env()) { +check_target_keys <- function(target_keys, schema_version, + call = rlang::caller_env()) { if (is.null(target_keys)) { return() } @@ -149,6 +155,20 @@ check_target_keys <- function(target_keys, call = rlang::caller_env()) { call = call ) } + is_gte_v5_0_0 <- hubUtils::version_gte( + "v5.0.0", + schema_version = schema_version + ) + target_key_n <- length(target_keys) + if (is_gte_v5_0_0 && target_key_n > 1L) { + cli::cli_abort( + c( + "!" = "{.arg target_keys} must be a named {.cls list} of + length {.val {1L}} not {.val {target_key_n}}." + ), + call = call + ) + } purrr::walk2( target_keys, diff --git a/hubAdmin.Rproj b/hubAdmin.Rproj index 97b9e3f..a7bcba2 100644 --- a/hubAdmin.Rproj +++ b/hubAdmin.Rproj @@ -1,4 +1,5 @@ Version: 1.0 +ProjectId: a853e49a-e7e8-463b-96b8-8c69e98c0382 RestoreWorkspace: No SaveWorkspace: No diff --git a/man/create_target_metadata_item.Rd b/man/create_target_metadata_item.Rd index 97e2387..cd21ee5 100644 --- a/man/create_target_metadata_item.Rd +++ b/man/create_target_metadata_item.Rd @@ -26,13 +26,15 @@ that could be used, for example, as a visualisation axis label.} \item{target_units}{character string. Unit of observation of the target.} -\item{target_keys}{named list or \code{NULL}. Should be \code{NULL}, in the case -where the target is not specified as a task_id but is specified solely through -the \code{target_id} argument. Otherwise, should be a named list of one or more -character strings. The name of each element should match a task_id variable -within the same model_tasks object. Each element should be of length 1. -Each value, or the combination of values if multiple keys are specified, -define a single target value.} +\item{target_keys}{named list or \code{NULL}. The \code{target_keys} value defines a +single target. +Should be a named list containing a single character string element. +The name of the element should match a \code{task_id} variable name within the same +\code{model_tasks} object and the value should match a single value of that variable +as described in +\href{https://hubverse.io/en/latest/user-guide/tasks.html#target-metadata}{target metadata section of the official hubverse documentation}. # nolint: line_length_linter +Otherwise, \code{NULL} in the case where the target is not specified as a task_id +but is specified solely through the \code{target_id} argument.} \item{description}{character string (optional). An optional verbose description of the target that might include information such as definitions of a 'rate' or similar.} diff --git a/tests/testthat/_snaps/create_target_metadata_item.md b/tests/testthat/_snaps/create_target_metadata_item.md index 2cae71e..859795b 100644 --- a/tests/testthat/_snaps/create_target_metadata_item.md +++ b/tests/testthat/_snaps/create_target_metadata_item.md @@ -162,3 +162,14 @@ - "https://raw.githubusercontent.com/hubverse-org/schemas/main/v3.0.1/tasks-schema.json" + "https://raw.githubusercontent.com/hubverse-org/schemas/main/v4.0.0/tasks-schema.json" +# Target_keys of length more than 1 are not allowed post v5.0.0 + + Code + create_target_metadata_item(target_id = "flu inc hosp", target_name = "Weekly incident influenza hospitalizations", + target_units = "rate per 100,000 population", target_keys = list(target = "flu", + target_metric = "inc hosp"), target_type = "discrete", is_step_ahead = TRUE, + time_unit = "week") + Condition + Error in `create_target_metadata_item()`: + ! `target_keys` must be a named of length 1 not 2. + diff --git a/tests/testthat/test-create_output_type_item.R b/tests/testthat/test-create_output_type_item.R index c26ba49..7c17214 100644 --- a/tests/testthat/test-create_output_type_item.R +++ b/tests/testthat/test-create_output_type_item.R @@ -452,7 +452,7 @@ test_that("schema version option works for create_output_type_mean", { withr::with_options( list( hubAdmin.schema_version = "v3.0.1", - hubAmin.branch = "main" + hubAdmin.branch = "main" ), { opt_version <- create_output_type_mean( @@ -493,7 +493,7 @@ test_that("schema version option works for create_output_type_quantile", { withr::with_options( list( hubAdmin.schema_version = "v3.0.1", - hubAmin.branch = "main" + hubAdmin.branch = "main" ), { opt_version <- create_output_type_quantile( @@ -543,7 +543,7 @@ test_that("schema version option works for create_output_type_cdf", { withr::with_options( list( hubAdmin.schema_version = "v3.0.1", - hubAmin.branch = "main" + hubAdmin.branch = "main" ), { opt_version <- create_output_type_cdf( @@ -590,7 +590,7 @@ test_that("schema version option works for create_output_type_pmf", { withr::with_options( list( hubAdmin.schema_version = "v3.0.1", - hubAmin.branch = "main" + hubAdmin.branch = "main" ), { opt_version <- create_output_type_pmf( diff --git a/tests/testthat/test-create_round.R b/tests/testthat/test-create_round.R index 30ce7e1..a4a6f1a 100644 --- a/tests/testthat/test-create_round.R +++ b/tests/testthat/test-create_round.R @@ -229,8 +229,8 @@ test_that("validating round_id patterns when round_id_from_var = TRUE works", { skip_if_offline() withr::with_options( list( - hubAdmin.schema_version = "v4.0.1", - hubAdmin.branch = "br-v4.0.1" + hubAdmin.schema_version = "v5.0.0", + hubAdmin.branch = "br-v5.0.0" ), { output_types <- create_output_type( @@ -372,8 +372,8 @@ test_that("validating round_id pattern when round_id_from_var = FALSE works", { skip_if_offline() withr::with_options( list( - hubAdmin.schema_version = "v4.0.1", - hubAdmin.branch = "br-v4.0.1" + hubAdmin.schema_version = "v5.0.0", + hubAdmin.branch = "br-v5.0.0" ), { output_types <- create_output_type( diff --git a/tests/testthat/test-create_target_metadata_item.R b/tests/testthat/test-create_target_metadata_item.R index 8231cbb..e91768b 100644 --- a/tests/testthat/test-create_target_metadata_item.R +++ b/tests/testthat/test-create_target_metadata_item.R @@ -131,7 +131,7 @@ test_that("schema version option works for create_target_metadata_item", { withr::with_options( list( hubAdmin.schema_version = "v3.0.1", - hubAmin.branch = "main" + hubAdmin.branch = "main" ), { opt_version <- create_target_metadata_item( @@ -148,3 +148,69 @@ test_that("schema version option works for create_target_metadata_item", { expect_equal(arg_version, opt_version) expect_snapshot(waldo::compare(opt_version, version_default)) }) + +test_that("Target_keys of length more than 1 are not allowed post v5.0.0", { + skip_if_offline() + withr::with_options( + list( + hubAdmin.schema_version = "v5.0.0", + # TDOD: remove branch argument when v5.0.0 is released. + hubAdmin.branch = "br-v5.0.0" + ), + { + # One target_key is allowed in v5.0.0 and later versions. + target_keys_n1 <- create_target_metadata_item( + target_id = "inc hosp", + target_name = "Weekly incident influenza hospitalizations", + target_units = "rate per 100,000 population", + target_keys = list(target = "inc hosp"), + target_type = "discrete", + is_step_ahead = TRUE, + time_unit = "week" + ) + expect_s3_class(target_keys_n1, "target_metadata_item") + expect_length(target_keys_n1$target_keys, 1L) + + # More than one target_key is NOT allowed in v5.0.0 and later versions + # and throws error. + expect_snapshot( + create_target_metadata_item( + target_id = "flu inc hosp", + target_name = "Weekly incident influenza hospitalizations", + target_units = "rate per 100,000 population", + target_keys = list( + target = "flu", + target_metric = "inc hosp" + ), + target_type = "discrete", + is_step_ahead = TRUE, + time_unit = "week" + ), + error = TRUE + ) + } + ) + # More than 1 target_keys still allowed in earlier versions to conform to + # schema. + withr::with_options( + list( + hubAdmin.schema_version = "v4.0.0" + ), + { + target_keys_n2 <- create_target_metadata_item( + target_id = "flu inc hosp", + target_name = "Weekly incident influenza hospitalizations", + target_units = "rate per 100,000 population", + target_keys = list( + target = "flu", + target_metric = "inc hosp" + ), + target_type = "discrete", + is_step_ahead = TRUE, + time_unit = "week" + ) + expect_s3_class(target_keys_n2, "target_metadata_item") + expect_length(target_keys_n2$target_keys, 2L) + } + ) +}) diff --git a/tests/testthat/test-create_task_id.R b/tests/testthat/test-create_task_id.R index 044cef8..a85eb52 100644 --- a/tests/testthat/test-create_task_id.R +++ b/tests/testthat/test-create_task_id.R @@ -114,7 +114,7 @@ test_that("schema version option works for create_task_id", { withr::with_options( list( hubAdmin.schema_version = "v3.0.1", - hubAmin.branch = "main" + hubAdmin.branch = "main" ), { opt_version <- create_task_id("horizon", diff --git a/tests/testthat/test-download_tasks_schema.R b/tests/testthat/test-download_tasks_schema.R index 236d6fd..0e2dd30 100644 --- a/tests/testthat/test-download_tasks_schema.R +++ b/tests/testthat/test-download_tasks_schema.R @@ -40,7 +40,7 @@ test_that("schema version option works for download_tasks_schema", { withr::with_options( list(hubAdmin.schema_version = "v3.0.1", - hubAmin.branch = "main"), + hubAdmin.branch = "main"), { opt_version <- download_tasks_schema() } diff --git a/tests/testthat/test-validate_config.R b/tests/testthat/test-validate_config.R index 760beec..95935b5 100644 --- a/tests/testthat/test-validate_config.R +++ b/tests/testthat/test-validate_config.R @@ -252,10 +252,43 @@ test_that("v4 validation works", { ) }) -test_that("v4.0.1 round_id pattern validation works", { + +test_that("v5.0.0 target keys with 2 properties throws error", { + skip_if_offline() + config_path <- testthat::test_path("testdata", "v5.0.0-tasks-2-target_keys.json") + out <- suppressMessages( + validate_config( + config_path = config_path, + # TDOD: remove branch argument when v5.0.0 is released. + branch = "br-v5.0.0" + ) + ) + expect_false(out) + + expect_equal( + unique(attr(out, "errors")$message), + "must NOT have more than 1 items" + ) + expect_equal(nrow(attr(out, "errors")), 2L) +}) + +test_that("v5.0.0 target keys with NULL properties passes", { + # Ensure NULL target keys are still allowed. + config_path <- testthat::test_path("testdata", "v5.0.0-tasks-null-target_keys.json") + out <- suppressMessages( + validate_config( + config_path = config_path, + # TDOD: remove branch argument when v5.0.0 is released. + branch = "br-v5.0.0" + ) + ) + expect_true(out) +}) + +test_that("v5.0.0 round_id pattern validation works", { skip_if_offline() - # TODO: remove branch argument when v4.0.1 is released. - schema <- download_tasks_schema("v4.0.1", branch = "br-v4.0.1") + # TODO: remove branch argument when v5.0.0 is released. + schema <- download_tasks_schema("v5.0.0", branch = "br-v5.0.0") # Test that regex pattern matching for round_id properties in jsonvalidate # identifies expected errors (when round_id_from_variable: false). @@ -264,9 +297,9 @@ test_that("v4.0.1 round_id pattern validation works", { validate_config( config_path = testthat::test_path( "testdata", - "v4.0.1-tasks-fail-round-id-pattern.json" + "v5.0.0-tasks-fail-round-id-pattern.json" ), - branch = "br-v4.0.1" + branch = "br-v5.0.0" ) ) ) @@ -292,9 +325,9 @@ test_that("v4.0.1 round_id pattern validation works", { validate_config( config_path = testthat::test_path( "testdata", - "v4.0.1-tasks-fail-round-id-val-pattern.json" + "v5.0.0-tasks-fail-round-id-val-pattern.json" ), - branch = "br-v4.0.1" + branch = "br-v5.0.0" ) ) ) diff --git a/tests/testthat/testdata/v5.0.0-tasks-2-target_keys.json b/tests/testthat/testdata/v5.0.0-tasks-2-target_keys.json new file mode 100644 index 0000000..182014c --- /dev/null +++ b/tests/testthat/testdata/v5.0.0-tasks-2-target_keys.json @@ -0,0 +1,164 @@ +{ + "schema_version": "https://raw.githubusercontent.com/hubverse-org/schemas/main/v5.0.0/tasks-schema.json", + "rounds": [{ + "round_id_from_variable": true, + "round_id": "forecast_date", + "model_tasks": [{ + "task_ids": { + "forecast_date": { + "required": null, + "optional": [ + "2022-12-12", "2022-12-19", "2022-12-26", "2023-01-02", "2023-01-09", + "2023-01-16", "2023-01-23", "2023-01-30", "2023-02-06", "2023-02-13", + "2023-02-20", "2023-02-27", "2023-03-06", "2023-03-13", "2023-03-20", + "2023-03-27", "2023-04-03", "2023-04-10", "2023-04-17", "2023-04-24", + "2023-05-01" + ] + }, + "target": { + "required": null, + "optional": ["wk ahead inc"] + }, + "target_metric": { + "required": null, + "optional": ["flu hosp"] + }, + "horizon": { + "required": [2], + "optional": [1] + }, + "location": { + "required": ["US"], + "optional": [ + "01", + "02" + ] + }, + "target_date": { + "required": null, + "optional": [ + "2022-12-19", "2022-12-26", "2023-01-02", "2023-01-09", + "2023-01-16", "2023-01-23", "2023-01-30", "2023-02-06", "2023-02-13", + "2023-02-20", "2023-02-27", "2023-03-06", "2023-03-13", "2023-03-20", + "2023-03-27", "2023-04-03", "2023-04-10", "2023-04-17", "2023-04-24", + "2023-05-01", "2023-05-08", "2023-05-15" + ] + } + }, + "output_type": { + "sample": { + "output_type_id_params": { + "type": "character", + "min_samples_per_task": 50, + "max_samples_per_task": 100, + "max_length": 10 + }, + "is_required": true, + "value": { + "type": "integer", + "minimum": 0 + } + }, + "mean": { + "output_type_id": { + "required": null + }, + "is_required": true, + "value": { + "type": "double", + "minimum": 0 + } + } + }, + "target_metadata": [{ + "target_id": "wk ahead inc flu hosp", + "target_name": "weekly influenza hospitalization incidence", + "target_units": "rate per 100,000 population", + "target_keys": { + "target": "wk ahead inc", + "target_metric": "flu hosp" + }, + "target_type": "discrete", + "description": "This target represents the counts of new hospitalizations per horizon week.", + "is_step_ahead": true, + "time_unit": "week" + }] + }, { + "task_ids": { + "forecast_date": { + "required": null, + "optional": [ + "2022-12-12", "2022-12-19", "2022-12-26", "2023-01-02", "2023-01-09", + "2023-01-16", "2023-01-23", "2023-01-30", "2023-02-06", "2023-02-13", + "2023-02-20", "2023-02-27", "2023-03-06", "2023-03-13", "2023-03-20", + "2023-03-27", "2023-04-03", "2023-04-10", "2023-04-17", "2023-04-24", + "2023-05-01" + ] + }, + "target": { + "required": null, + "optional": ["wk flu hosp"] + }, + "target_metric": { + "required": null, + "optional": ["rate change"] + }, + "horizon": { + "required": [2], + "optional": [1] + }, + "location": { + "required": ["US"], + "optional": [ + "01", + "02" + ] + }, + "target_date": { + "required": null, + "optional": [ + "2022-12-19", "2022-12-26", "2023-01-02", "2023-01-09", + "2023-01-16", "2023-01-23", "2023-01-30", "2023-02-06", "2023-02-13", + "2023-02-20", "2023-02-27", "2023-03-06", "2023-03-13", "2023-03-20", + "2023-03-27", "2023-04-03", "2023-04-10", "2023-04-17", "2023-04-24", + "2023-05-01", "2023-05-08", "2023-05-15" + ] + } + }, + "output_type": { + "pmf": { + "output_type_id": { + "required": ["large_decrease", "decrease", "stable", "increase", "large_increase"] + }, + "is_required": false, + "value": { + "type": "double", + "minimum": 0, + "maximum": 1 + } + } + }, + "target_metadata": [{ + "target_id": "wk flu hosp rate change", + "target_name": "weekly influenza hospitalization rate change", + "target_units": "rate per 100,000 population", + "target_keys": { + "target": "wk flu hosp", + "target_metric": "rate change" + }, + "target_type": "nominal", + "description": "This target represents the change in the rate of new hospitalizations per week comparing the week ending two days prior to the forecast_date to the week ending h weeks after the forecast_date.", + "is_step_ahead": true, + "time_unit": "week" + }] + }], + "submissions_due": { + "relative_to": "forecast_date", + "start": -6, + "end": 2 + }, + "derived_task_ids": ["target_date"] + } + + ] +} diff --git a/tests/testthat/testdata/v4.0.1-tasks-fail-round-id-pattern.json b/tests/testthat/testdata/v5.0.0-tasks-fail-round-id-pattern.json similarity index 99% rename from tests/testthat/testdata/v4.0.1-tasks-fail-round-id-pattern.json rename to tests/testthat/testdata/v5.0.0-tasks-fail-round-id-pattern.json index 0813782..24517c0 100644 --- a/tests/testthat/testdata/v4.0.1-tasks-fail-round-id-pattern.json +++ b/tests/testthat/testdata/v5.0.0-tasks-fail-round-id-pattern.json @@ -1,5 +1,5 @@ { - "schema_version": "https://raw.githubusercontent.com/hubverse-org/schemas/main/v4.0.1/tasks-schema.json", + "schema_version": "https://raw.githubusercontent.com/hubverse-org/schemas/main/v5.0.0/tasks-schema.json", "rounds": [{ "round_id_from_variable": true, "round_id": "round_id_var", diff --git a/tests/testthat/testdata/v4.0.1-tasks-fail-round-id-val-pattern.json b/tests/testthat/testdata/v5.0.0-tasks-fail-round-id-val-pattern.json similarity index 99% rename from tests/testthat/testdata/v4.0.1-tasks-fail-round-id-val-pattern.json rename to tests/testthat/testdata/v5.0.0-tasks-fail-round-id-val-pattern.json index ebcfd66..09a4217 100644 --- a/tests/testthat/testdata/v4.0.1-tasks-fail-round-id-val-pattern.json +++ b/tests/testthat/testdata/v5.0.0-tasks-fail-round-id-val-pattern.json @@ -1,5 +1,5 @@ { - "schema_version": "https://raw.githubusercontent.com/hubverse-org/schemas/main/v4.0.1/tasks-schema.json", + "schema_version": "https://raw.githubusercontent.com/hubverse-org/schemas/main/v5.0.0/tasks-schema.json", "rounds": [{ "round_id_from_variable": true, "round_id": "round_id_var", diff --git a/tests/testthat/testdata/v5.0.0-tasks-null-target_keys.json b/tests/testthat/testdata/v5.0.0-tasks-null-target_keys.json new file mode 100644 index 0000000..5dc10d0 --- /dev/null +++ b/tests/testthat/testdata/v5.0.0-tasks-null-target_keys.json @@ -0,0 +1,93 @@ +{ + "schema_version": "https://raw.githubusercontent.com/hubverse-org/schemas/main/v5.0.0/tasks-schema.json", + "rounds": [{ + "round_id_from_variable": true, + "round_id": "forecast_date", + "model_tasks": [{ + "task_ids": { + "forecast_date": { + "required": null, + "optional": [ + "2022-12-12", "2022-12-19", "2022-12-26", "2023-01-02", "2023-01-09", + "2023-01-16", "2023-01-23", "2023-01-30", "2023-02-06", "2023-02-13", + "2023-02-20", "2023-02-27", "2023-03-06", "2023-03-13", "2023-03-20", + "2023-03-27", "2023-04-03", "2023-04-10", "2023-04-17", "2023-04-24", + "2023-05-01" + ] + }, + "target": { + "required": null, + "optional": ["wk ahead inc"] + }, + "target_metric": { + "required": null, + "optional": ["flu hosp"] + }, + "horizon": { + "required": [2], + "optional": [1] + }, + "location": { + "required": ["US"], + "optional": [ + "01", + "02" + ] + }, + "target_date": { + "required": null, + "optional": [ + "2022-12-19", "2022-12-26", "2023-01-02", "2023-01-09", + "2023-01-16", "2023-01-23", "2023-01-30", "2023-02-06", "2023-02-13", + "2023-02-20", "2023-02-27", "2023-03-06", "2023-03-13", "2023-03-20", + "2023-03-27", "2023-04-03", "2023-04-10", "2023-04-17", "2023-04-24", + "2023-05-01", "2023-05-08", "2023-05-15" + ] + } + }, + "output_type": { + "sample": { + "output_type_id_params": { + "type": "character", + "min_samples_per_task": 50, + "max_samples_per_task": 100, + "max_length": 10 + }, + "is_required": true, + "value": { + "type": "integer", + "minimum": 0 + } + }, + "mean": { + "output_type_id": { + "required": null + }, + "is_required": true, + "value": { + "type": "double", + "minimum": 0 + } + } + }, + "target_metadata": [{ + "target_id": "wk ahead inc flu hosp", + "target_name": "weekly influenza hospitalization incidence", + "target_units": "rate per 100,000 population", + "target_keys": null, + "target_type": "discrete", + "description": "This target represents the counts of new hospitalizations per horizon week.", + "is_step_ahead": true, + "time_unit": "week" + }] + }], + "submissions_due": { + "relative_to": "forecast_date", + "start": -6, + "end": 2 + }, + "derived_task_ids": ["target_date"] + } + + ] +}