diff --git a/.circleci/config.yml b/.circleci/config.yml index 55e450c49..789fbc4e9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ description: Datapackr Test Suite jobs: build: docker: - - image: rocker/verse:4.1.1 + - image: rocker/verse:4.2.1 steps: - checkout - restore_cache: @@ -12,7 +12,7 @@ jobs: name: Install curl sytem deps command: | sudo apt-get update - sudo apt-get -qq -y install libcurl4-openssl-dev libxml2-dev libsodium-dev libgit2-dev libreoffice-calc-nogui + sudo apt-get -qq -y install libcurl4-openssl-dev libxml2-dev libsodium-dev libgit2-dev libreoffice-calc-nogui libicu-dev - run: name: Install package dependencies command: R -e "install.packages(c('renv','rlang'))" @@ -30,7 +30,7 @@ jobs: name: Test package no_output_timeout: 30m command: | - R -e "devtools::check(error_on='error')" + R -e "devtools::test(stop_on_failure = TRUE, error_on='error')" - run: name: Lint packages command: | diff --git a/DESCRIPTION b/DESCRIPTION index c01b0ecf5..4e7eb82e4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Package: datapackr Type: Package Title: A Package that Packs and Unpacks all Data Packs and Target Setting Tools -Version: 6.1.1 -Date: 2023-02-14 +Version: 6.2.0 +Date: 2023-03-08 Authors@R: c( person("Scott", "Jackson", email = "sjackson@baosystems.com", role = c("aut", "cre")), @@ -55,7 +55,8 @@ Suggests: gdtools, flextable, officer, - lintr (>= 3.0.0) + lintr (>= 3.0.0), + stringi Remotes: pepfar-datim/datimutils, pepfar-datim/datim-validation diff --git a/NAMESPACE b/NAMESPACE index 584c25cb9..431ff3cf3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -158,6 +158,7 @@ export(unPackSNUxIM) export(unPackSchema) export(unPackSheets) export(unPackTool) +export(unpackYear2Sheet) export(updateExistingPrioritization) export(validationSummary) export(writeHomeTab) diff --git a/NEWS.md b/NEWS.md index 27b1cbfe9..095e6bbc4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,28 @@ +# datapackr 6.2.0 + +## Breaking changes +* Removed support for COP21 + +## New features +* Added initial support for parsing COP23 Datapacks +* Added functionality to export COP23 data to export formats +* Added function writeSpectrumData which can be used to populate the DataPack Spectrum tab during testing. +* Added testing helper functions to deal with peculiarities of the CI testing environment. +* Added various tests and testing files for COP23 tools. +* Adds initial parsing methods for COP23 Year2 tabs + + +## Minor improvements and fixes +* Upgraded CI testing environment to R 4.2.1 +* Fixed bug in unPackSchemarelated to detecting invalid column and value types. +* Fixed create schema unit test. +* Removed superfluous warning related to missing PSNUs. +* Fixed issue in unPackingChecks related to the lack of the SNU1 column in COP23 tools. +* Fixed a testing issue related to choosing the correct template to use for testing. +* Updated several unit tests to favor COP23 over COP21. +* Disabled two unit tests for COP21. +* Altered test method from devtools::check to devtools::test, which skips CRAN package checks. + # datapackr 6.1.1 ## Breaking changes diff --git a/R/adorn_import_file.R b/R/adorn_import_file.R index 3ee3eef68..717d8b2eb 100644 --- a/R/adorn_import_file.R +++ b/R/adorn_import_file.R @@ -134,7 +134,9 @@ adorn_import_file <- function(psnu_import_file, unique() if (length(unknown_psnu) > 0) { - psnus <- getPSNUInfo(unknown_psnu, d2_session = d2_session) %>% + psnus <- getPSNUInfo(snu_uids = unknown_psnu, + cop_year = cop_year, + d2_session = d2_session) %>% dplyr::select(-name) psnu_import_file %<>% diff --git a/R/checkAnalytics.R b/R/checkAnalytics.R index a6e7a215f..2595ff7dc 100644 --- a/R/checkAnalytics.R +++ b/R/checkAnalytics.R @@ -9,10 +9,11 @@ HTS_POS_Modalities <- function(cop_year) { # a reference to the cop year. Since the modalities # differ from year to year though, this list needs # to be determined based on the year we are dealing with. + # TODO: datapackr::getMapDataPack_DATIM_DEs_COCs(cop_year) %>% dplyr::select(indicator_code, hts_modality, resultstatus) %>% - dplyr::filter(!is.na(hts_modality)) %>% + tidyr::drop_na() %>% dplyr::filter(resultstatus %in% c("Newly Tested Positives", "Positive")) %>% dplyr::distinct() %>% dplyr::pull(indicator_code) @@ -29,8 +30,19 @@ HTS_POS_Modalities <- function(cop_year) { #' @return a #' analyze_eid_2mo <- function(data) { + a <- NULL + required_names <- c("PMTCT_EID.N.12.T", + "PMTCT_EID.N.2.T") + + if (any(!(required_names %in% names(data)))) { + a$test_results <- data.frame(msg = "Missing data.") + attr(a$test_results, "test_name") <- "PMTCT_EID coverage by 2 months issues" + a$msg <- "Could not analyze PMTCT EID due to missing data." + return(a) + } + analysis <- data %>% dplyr::mutate( PMTCT_EID.T = PMTCT_EID.N.12.T + PMTCT_EID.N.2.T, @@ -100,7 +112,18 @@ analyze_eid_2mo <- function(data) { #' @return a #' analyze_vmmc_indeterminate <- function(data) { + a <- NULL + required_names <- c("VMMC_CIRC.Pos.T", + "VMMC_CIRC.Neg.T", + "VMMC_CIRC.Unk.T") + + if (any(!(required_names %in% names(data)))) { + a$test_results <- data.frame(msg = "Missing data.") + attr(a$test_results, "test_name") <- "VMMC Indeterminate rate issues" + a$msg <- "Could not analyze VMMC_CIRC Indeterminate Rate due to missing data." + return(a) + } issues <- data %>% dplyr::mutate( @@ -185,6 +208,17 @@ analyze_vmmc_indeterminate <- function(data) { analyze_pmtctknownpos <- function(data) { a <- NULL + required_names <- c("PMTCT_STAT.N.New.Pos.T", + "PMTCT_STAT.N.KnownPos.T", + "PMTCT_STAT.N.New.Neg.T") + + if (any(!(required_names %in% names(data)))) { + a$test_results <- data.frame(msg = "Missing data.") + attr(a$test_results, "test_name") <- "PMTCT Known Pos issues" + a$msg <- "Could not analyze PMTCT Known Pos issues due to missing data." + return(a) + } + issues <- data %>% dplyr::filter(is.na(key_population)) %>% dplyr::mutate( @@ -243,6 +277,17 @@ analyze_pmtctknownpos <- function(data) { analyze_tbknownpos <- function(data) { a <- NULL + required_names <- c("TB_STAT.N.New.Pos.T", + "TB_STAT.N.KnownPos.T", + "TB_STAT.N.New.Neg.T") + + if (any(!(required_names %in% names(data)))) { + a$test_results <- data.frame(msg = "Missing data.") + attr(a$test_results, "test_name") <- "TB Known Pos issues" + a$msg <- "Could not analyze TB Known Pos issues due to missing data." + return(a) + } + issues <- data %>% dplyr::mutate( TB_STAT.N.Total = @@ -297,6 +342,18 @@ analyze_tbknownpos <- function(data) { analyze_retention <- function(data) { a <- NULL + + required_names <- c("TX_CURR.T", + "TX_CURR.T_1", + "TX_NEW.T") + + if (any(!(required_names %in% names(data)))) { + a$test_results <- data.frame(msg = "Missing data.") + attr(a$test_results, "test_name") <- "Retention rate issues" + a$msg <- "Could not analyze Retention rate issues due to missing data." + return(a) + } + analysis <- data %>% #For COP22, we need to collapse the finer 50+ age bands back to 50+ # since TX_NEW is not allocated at these finer age bands @@ -377,8 +434,20 @@ analyze_retention <- function(data) { analyze_linkage <- function(data) { a <- NULL + hts_modalities <- HTS_POS_Modalities(data$cop_year[1]) + required_names <- c("TX_NEW.T", "TX_NEW.KP.T") + + if (any(!(required_names %in% names(data)))) { + a$test_results <- data.frame(msg = "Missing data.") + attr(a$test_results, "test_name") <- "Linkage rate issues" + a$msg <- "Could not analyze Linkage rate issues due to missing data." + return(a) + } + + + analysis <- data %>% dplyr::mutate(age = dplyr::case_when(age %in% c("50-54", "55-59", "60-64", "65+") ~ "50+", TRUE ~ age)) %>% @@ -473,7 +542,19 @@ analyze_linkage <- function(data) { #' @return a #' analyze_indexpos_ratio <- function(data) { + a <- NULL + required_names <- c("HTS_INDEX_COM.New.Pos.T", + "HTS_INDEX_FAC.New.Pos.T", + "PLHIV.T_1", + "TX_CURR_SUBNAT.T_1") + + if (any(!(required_names %in% names(data)))) { + a$test_results <- data.frame(msg = "Missing data.") + attr(a$test_results, "test_name") <- "HTS_INDEX_POS Rate Issues" + a$msg <- "Could not analyze HTS_INDEX_POS Rate Issues due to missing data." + return(a) + } hts_modalities <- HTS_POS_Modalities(data$cop_year[1]) @@ -481,8 +562,7 @@ analyze_indexpos_ratio <- function(data) { dplyr::filter(is.na(key_population)) %>% dplyr::select(-age, -sex, -key_population) %>% dplyr::group_by(psnu, psnu_uid) %>% - dplyr::summarise(dplyr::across(dplyr::everything(), sum)) %>% - dplyr::ungroup() %>% + dplyr::summarise(dplyr::across(dplyr::everything(), sum), .groups = "drop") %>% dplyr::mutate( HTS_TST_POS.T = rowSums(dplyr::select(., tidyselect::any_of(hts_modalities))), HTS_INDEX.total = @@ -541,7 +621,7 @@ analyze_indexpos_ratio <- function(data) { "\n") } - return(a) + a } @@ -629,14 +709,13 @@ checkAnalytics <- function(d, addcols((d$info$schema %>% dplyr::filter(col_type %in% c("target", "past"), sheet_name != "PSNUxIM") %>% - dplyr::pull(indicator_code)), + dplyr::pull(indicator_code) %>% + unique(.)), type = "numeric") %>% dplyr::mutate(dplyr::across(c(-psnu, -psnu_uid, -age, -sex, -key_population), ~tidyr::replace_na(.x, 0))) %>% dplyr::mutate(cop_year = d$info$cop_year) - - #Apply the list of analytics checks functions funs <- list( retention = analyze_retention, diff --git a/R/getCOPDataFromDATIM.R b/R/getCOPDataFromDATIM.R index bbfc35ce7..b53bc30bc 100644 --- a/R/getCOPDataFromDATIM.R +++ b/R/getCOPDataFromDATIM.R @@ -18,7 +18,7 @@ getCOPDataFromDATIM <- function(country_uids, inherits = TRUE)) { - if (!cop_year %in% c(2020:2022)) { + if (!cop_year %in% c(2020:2023)) { stop("The COP year provided is not supported by the internal function getCOPDataFromDATIM") ### NOTE for COP23 some special handling of SUBNAT data for FY23 like the code below may be @@ -61,7 +61,7 @@ getCOPDataFromDATIM <- function(country_uids, return(NULL) }) - if (is.null(datim_data)) { + if (is.null(datim_data) || NROW(datim_data) == 0) { return(NULL) } else { datim_data %>% diff --git a/R/getMemoIndicators.R b/R/getMemoIndicators.R index 8034ec93d..3dde41437 100644 --- a/R/getMemoIndicators.R +++ b/R/getMemoIndicators.R @@ -41,6 +41,7 @@ getMemoIndicators <- function(cop_year, ind_group <- switch(as.character(cop_year), "2021" = "TslxbFe3VUZ", "2022" = "zRApVEi7qjo", + "2023" = "ZTGhB3qIPsi", NULL) #Bail out early if don't have a group if (is.null(ind_group)) { diff --git a/R/memoStructure.R b/R/memoStructure.R index a7df8a495..d5e749a8f 100644 --- a/R/memoStructure.R +++ b/R/memoStructure.R @@ -8,7 +8,7 @@ memoStructure <- function(d, d2_session = dynGet("d2_default_session", inherits = TRUE)) { - if (!(d$info$cop_year %in% c("2021", "2022"))) { + if (!(d$info$cop_year %in% supportedCOPYears())) { warning("COP Memo structure unknown for given COP year") return(d) } @@ -77,7 +77,7 @@ memoStructure <- function(d, d2_session = dynGet("d2_default_session", "AGYW_PREV", "Total", NA) } - if (d$info$cop_year == "2022") { + if (d$info$cop_year %in% c("2022", "2023")) { row_order <- tibble::tribble( ~ind, ~options, ~partner_chunk, "TX_NEW", "<15", 1, diff --git a/R/packDataPack.R b/R/packDataPack.R index 43345e73a..4056583d4 100644 --- a/R/packDataPack.R +++ b/R/packDataPack.R @@ -11,6 +11,7 @@ #' packDataPack <- function(d, model_data = NULL, + spectrum_data = NULL, d2_session = dynGet("d2_default_session", inherits = TRUE)) { @@ -53,6 +54,12 @@ packDataPack <- function(d, sheets = NULL, cop_year = d$info$cop_year) + if (!is.null(spectrum_data)) { + interactive_print("Writing Spectrum data...") + d$tool$wb <- writeSpectrumData(wb = d$tool$wb, + spectrum_data = spectrum_data) + } + # Hide unneeded sheets #### sheets_to_hide <- which(stringr::str_detect(names(d$tool$wb), "PSNUxIM")) openxlsx::sheetVisibility(d$tool$wb)[sheets_to_hide] <- "hidden" diff --git a/R/packTool.R b/R/packTool.R index 6201b2d38..e2394d081 100644 --- a/R/packTool.R +++ b/R/packTool.R @@ -23,6 +23,7 @@ packTool <- function(model_data = NULL, output_folder, results_archive = TRUE, expand_formulas = FALSE, + spectrum_data = NULL, d2_session = dynGet("d2_default_session", inherits = TRUE)) { @@ -68,6 +69,7 @@ packTool <- function(model_data = NULL, if (d$info$tool == "Data Pack") { d <- packDataPack(d, model_data = model_data, + spectrum_data = spectrum_data, d2_session = d2_session) } else if (d$info$tool == "OPU Data Pack") { diff --git a/R/unPackCountryUIDs.R b/R/unPackCountryUIDs.R index 04e0d2782..3dd3e1194 100644 --- a/R/unPackCountryUIDs.R +++ b/R/unPackCountryUIDs.R @@ -136,11 +136,9 @@ unPackCountryUIDs <- function(submission_path, ~ .x %in% country_uids))) { warning("Deduced or provided Country UIDs do no match Country UIDs observed in submission.") } - } else { - warning("No PSNUs were detected.") } - return(country_uids) + country_uids } diff --git a/R/unPackDataPack.R b/R/unPackDataPack.R index 71c670867..766609314 100644 --- a/R/unPackDataPack.R +++ b/R/unPackDataPack.R @@ -51,8 +51,10 @@ unPackDataPack <- function(d, d <- unPackSheets(d) # Unpack the SNU x IM sheet #### - interactive_print("Unpacking the PSNUxIM tab...") - d <- unPackSNUxIM(d) + if (d$info$cop_year < 2023) { + interactive_print("Unpacking the PSNUxIM tab...") + d <- unPackSNUxIM(d) + } # Prepare undistributed import file for use in analytics if necessary #### # TODO: Allow packForDATIM to auto-detect what is present and what should be packed. diff --git a/R/unPackSchema.R b/R/unPackSchema.R index 90e9b4cf9..6792502d0 100644 --- a/R/unPackSchema.R +++ b/R/unPackSchema.R @@ -145,11 +145,12 @@ checkSchema_InvalidColType <- function(schema, tool, cop_year) { skip_sheets <- getSkipSheets(schema, tool, cop_year) col_type_invalid <- schema %>% + dplyr::filter(!(sheet_name %in% skip_sheets$names)) %>% + dplyr::filter(!is.na(col_type)) %>% dplyr::mutate( invalid_col_type = (!col_type %in% c("target", "reference", "assumption", "calculation", "past", - "row_header", "allocation", "result")) - & (sheet_name %in% skip_sheets$names & !is.na(col_type))) %>% + "row_header", "allocation", "result"))) %>% dplyr::filter(invalid_col_type == TRUE) %>% dplyr::select(sheet_name, col, indicator_code, data_structure, col_type) @@ -162,10 +163,10 @@ checkSchema_InvalidValueType <- function(schema, tool, cop_year) { skip_sheets <- getSkipSheets(schema, tool, cop_year) value_type_invalid <- schema %>% - dplyr::mutate( - invalid_value_type = - (!value_type %in% c("integer", "percentage", "string")) - & (sheet_name %in% skip_sheets$names & !is.na(value_type))) %>% + dplyr::filter(!(sheet_name %in% skip_sheets$names)) %>% + dplyr::filter(!is.na(value_type)) %>% + dplyr::mutate( + invalid_value_type = !(value_type %in% c("integer", "percentage", "string"))) %>% dplyr::filter(invalid_value_type == TRUE) %>% dplyr::select(sheet_name, col, indicator_code, value_type) diff --git a/R/unPackSheets.R b/R/unPackSheets.R index 962d7e4e3..fd628cb20 100644 --- a/R/unPackSheets.R +++ b/R/unPackSheets.R @@ -28,7 +28,10 @@ unPackSheets <- function(d, d <- loadSheets(d) } - sheets <- sheets %||% grep("PSNUxIM", names(d$sheets), value = TRUE, invert = TRUE) + #TODO: Fix this + sheet_names <- names(d$sheets)[names(d$sheets) != "Year 2"] + + sheets <- sheets %||% grep("PSNUxIM", sheet_names, value = TRUE, invert = TRUE) sheets <- checkSheets(sheets = sheets, cop_year = d$info$cop_year, diff --git a/R/unPackTool.R b/R/unPackTool.R index 616e9cd8d..49007b829 100644 --- a/R/unPackTool.R +++ b/R/unPackTool.R @@ -32,6 +32,11 @@ unPackTool <- function(submission_path = NULL, if (d$info$tool == "Data Pack") { d <- unPackDataPack(d, d2_session = d2_session) + + if (d$info$cop_year == "2023") { + d <- unpackYear2Sheet(d) + } + } else if (d$info$tool == "OPU Data Pack") { d <- unPackOPUDataPack(d, d2_session = d2_session) diff --git a/R/unPackYear2Sheet.R b/R/unPackYear2Sheet.R new file mode 100644 index 000000000..88e704035 --- /dev/null +++ b/R/unPackYear2Sheet.R @@ -0,0 +1,547 @@ +y2ExtractDuplicateRows <- function(d) { + duplicated_rows <- d$data$Year2 %>% + dplyr::select(-value) %>% + duplicated() + + if (any(duplicated_rows)) { + warning_msg <- + paste0( + "WARNING! Duplicated rows were detected in your Year 2 tab.", + "These will be dropped. Consult the validation test report ", + "for specific details. \n" + ) + + d$tests$year2_duplicated_rows <- d$data$Year2 %>% + dplyr::filter(duplicated_rows) + attr(d$tests$year2_duplicated_rows, "test_name") <- + "Duplicated Year 2 rows" + d$info$messages <- + appendMessage(d$info$messages, warning_msg, "WARNING") + } + + d$data$Year2 %<>% + dplyr::filter(!duplicated_rows) + + d +} + +y2ExtractInvalidDisaggs <- function(d) { + + year2_invalid_disaggs <- + is.na(d$data$Year2$dataelementuid) | + is.na(d$data$Year2$categoryoptioncombouid) + + + if (any(year2_invalid_disaggs)) { + warning_msg <- + paste0( + "WARNING! Invalid disaggregate combinations were found in the Year 2 tab.", + "These will be dropped. Consult the validation test report ", + "for specific details.", + "\n" + ) + + d$info$messages <- + appendMessage(d$info$messages, warning_msg, "WARNING") + d$tests$year2_invalid_disaggs <- + d$data$Year2[year2_invalid_disaggs, ] + attr(d$tests$year2_invalid_disaggs, "test_name") <- "Invalid Year 2 disaggs" + #TODO: Re-enable this once the data element /COC map is fixed + #d$data$Year2 <- d$data$Year2[!year2_invalid_disaggs, ] + } + + d + +} + +y2TestColumnStructure <- function(d) { + expected_cols <- + d$info$schema %>% + dplyr::filter(sheet_name == "Year 2") %>% + dplyr::select(col, indicator_code) + + actual_cols <- + data.frame(indicator_code = names(d$data$Year2)) %>% + dplyr::mutate(actual_col = dplyr::row_number()) + + cols_compare <- + actual_cols %>% + dplyr::full_join(expected_cols, by = "indicator_code") %>% + dplyr::mutate(is_equal = identical(actual_col, col)) + + if (!any(cols_compare$is_equal)) { + warning_msg <- + paste0( + "WARNING! Columns in the Year 2 tab are missing or out of order.", + "We will attempt to proceed with validation, however this must be fixed", + " prior to final submission.", + "Consult the validation report for details.", + "\n" + ) + + d$tests$year2_cols_out_of_order <- + cols_compare %>% dplyr::filter(!is_equal) + attr(d$tests$year2_cols_out_of_order, "test_name") <- + "Invalid Year 2 column order" + + } + + d + +} + +#' Title pickUIDFromType is a utility function used to obtain a +#' particular UID from a supplied list based on the type of +#' value we are dealing with. In the Year2 tab, values disaggregated +#' by AgeSex, KP and EID are co-mingled and must be separated based +#' on available disaggregates. +#' +#' @param type Disaggregate type (e.g. AgeSex, KP, EID) +#' @param de_uid_list A list of possible UIDs (e.g. c("UID1","{KP}UID2" ) +#' +#' @return A UID based on the supplied type. Returns NA if no UID is matched. +#' +pickUIDFromType <- function(type, de_uid_list) { + uid_regex <- "[A-Za-z][A-Za-z0-9]{10}" + pick <- NA + + if (is.na(type) || length(de_uid_list) == 0) { + return(NA) + } + + if (length(de_uid_list) == 1) { + pick <- + stringr::str_extract(de_uid_list[1], paste0("^", uid_regex, "$")) + } else { + if (type == "AgeSex") { + idx <- which(grepl(paste0("^", uid_regex, "$"), de_uid_list)) + pick <- de_uid_list[idx] + } + + if (type == "KP") { + idx <- which(grepl("\\{KP\\}", de_uid_list)) + pick <- stringr::str_extract(de_uid_list[idx], uid_regex) + } + + if (type == "EID") { + idx <- which(grepl("\\{EID\\}", de_uid_list)) + pick <- stringr::str_extract(de_uid_list[idx], uid_regex) + } + + } + + if (is.na(pick)) { + return(NA) + } + + if (!is_uidish(pick)) { + return(NA) + } + + pick + +} + +generateY2IndicatorCodeDataElementMap <- function(cols_to_keep, cop_year) { + + #TODO: Fix this in the main map + hts_recent_kps <- + datapackr::getMapDataPack_DATIM_DEs_COCs(cop_year) %>% + dplyr::filter(support_type == "DSD") %>% + dplyr::filter(indicator_code == "HTS_RECENT.KP.T") %>% + dplyr::select( + indicator_code, + valid_ages.name, + valid_sexes.name, + valid_kps.name, + dataelementuid, + resultstatus, + disagg_type + ) %>% + dplyr::mutate(indicator_code = "HTS_RECENT.T2", + dataelementuid = "SsqfZeIs1Va.{KP}y42bZItNsea") + + tx_new_kps <- + datapackr::getMapDataPack_DATIM_DEs_COCs(cop_year) %>% + dplyr::filter(support_type == "DSD") %>% + dplyr::filter(indicator_code == "TX_NEW.KP.T") %>% + dplyr::select( + indicator_code, + valid_ages.name, + valid_sexes.name, + valid_kps.name, + dataelementuid, + resultstatus, + disagg_type + ) %>% + dplyr::mutate(indicator_code = "TX_NEW.T2", + dataelementuid = "vmfKLKi1NBA.{KP}ktZYUSS0Zjo") + + tx_curr_kps <- + datapackr::getMapDataPack_DATIM_DEs_COCs(cop_year) %>% + dplyr::filter(support_type == "DSD") %>% + dplyr::filter(indicator_code == "TX_CURR.KP.T") %>% + dplyr::select( + indicator_code, + valid_ages.name, + valid_sexes.name, + valid_kps.name, + dataelementuid, + resultstatus, + disagg_type + ) %>% + dplyr::mutate(indicator_code = "TX_CURR.T2", + dataelementuid = "di4b6joXm84.{KP}OLbhrUez4dP") + + tx_curr_subnat_kps <- + datapackr::getMapDataPack_DATIM_DEs_COCs(cop_year) %>% + dplyr::filter(support_type == "DSD") %>% + dplyr::filter(indicator_code == "TX_CURR_SUBNAT.KP.T") %>% + dplyr::select( + indicator_code, + valid_ages.name, + valid_sexes.name, + valid_kps.name, + dataelementuid, + resultstatus, + disagg_type + ) %>% + dplyr::mutate(indicator_code = "TX_CURR.T2", + dataelementuid = "di4b6joXm84.{KP}OLbhrUez4dP") + + tx_pvls_d_kps <- + datapackr::getMapDataPack_DATIM_DEs_COCs(cop_year) %>% + dplyr::filter(support_type == "DSD") %>% + dplyr::filter(indicator_code == "TX_PVLS.D.KP.T") %>% + dplyr::select( + indicator_code, + valid_ages.name, + valid_sexes.name, + valid_kps.name, + dataelementuid, + resultstatus, + disagg_type + ) %>% + dplyr::mutate(indicator_code = "TX_PVLS.D.Routine.T2", + dataelementuid = "WpZwPieQ060.{KP}P9OjdVVMHMW") + + tx_pvls_n_kps <- + datapackr::getMapDataPack_DATIM_DEs_COCs(cop_year) %>% + dplyr::filter(support_type == "DSD") %>% + dplyr::filter(indicator_code == "TX_PVLS.N.KP.T") %>% + dplyr::select( + indicator_code, + valid_ages.name, + valid_sexes.name, + valid_kps.name, + dataelementuid, + resultstatus, + disagg_type + ) %>% + dplyr::mutate(indicator_code = "TX_PVLS.N.Routine.T2", + dataelementuid = "N55pM5ZuWcI.{KP}tGtB1nLnQjC") + + prep_new_kps <- + datapackr::getMapDataPack_DATIM_DEs_COCs(cop_year) %>% + dplyr::filter(support_type == "DSD") %>% + dplyr::filter(indicator_code == "PrEP_NEW.KP.T") %>% + dplyr::select( + indicator_code, + valid_ages.name, + valid_sexes.name, + valid_kps.name, + dataelementuid, + resultstatus, + disagg_type + ) %>% + dplyr::mutate(indicator_code = "PrEP_NEW.T2", + dataelementuid = "rXV784LlUQ4.{KP}lXqlw8UxoqF") + + + prep_ct_kps <- + datapackr::getMapDataPack_DATIM_DEs_COCs(cop_year) %>% + dplyr::filter(support_type == "DSD") %>% + dplyr::filter(indicator_code == "PrEP_CT.KP.T") %>% + dplyr::select( + indicator_code, + valid_ages.name, + valid_sexes.name, + valid_kps.name, + dataelementuid, + resultstatus, + disagg_type + ) %>% + dplyr::mutate(indicator_code = "PrEP_CT.T2", + dataelementuid = "agoURWZyPpn.{KP}bwlf1Jfww0L") + + + #TB_STAT.KnownPos.T2 seems to be missing entirely from the map + tb_stat_known_pos_t2 <- + datapackr::getMapDataPack_DATIM_DEs_COCs(cop_year) %>% + dplyr::filter(support_type == "DSD") %>% + dplyr::filter(indicator_code == "TB_STAT.N.KnownPos.T") %>% + dplyr::select( + indicator_code, + valid_ages.name, + valid_sexes.name, + valid_kps.name, + dataelementuid, + resultstatus, + disagg_type + ) %>% + dplyr::mutate(indicator_code = "TB_STAT.N.KnownPos.T2") + + pmtct_eid <- + datapackr::getMapDataPack_DATIM_DEs_COCs(cop_year) %>% + dplyr::filter(dataelementuid == "euzbW4INAqn" & + indicator_code == "PMTCT_EID.N.2.T") %>% + dplyr::select( + indicator_code, + valid_ages.name, + valid_sexes.name, + valid_kps.name, + dataelementuid, + resultstatus, + disagg_type + ) + + + is_positive <- + c("PLHIV|TX_CURR|TX_NEW|TX_PVLS|HTS_TST|HTS_RECENT|CXCA_SCRN|HTS_RECENT") + + + map_ind_code_des <- + datapackr::getMapDataPack_DATIM_DEs_COCs(cop_year) %>% + dplyr::filter(indicator_code %in% cols_to_keep$indicator_code) %>% + dplyr::select( + indicator_code, + valid_ages.name, + valid_sexes.name, + valid_kps.name, + dataelementuid, + resultstatus, + disagg_type + ) %>% + dplyr::mutate(resultstatus = dplyr::case_when( + grepl(is_positive, indicator_code) ~ "Positive", + TRUE ~ resultstatus + )) %>% + #TODO: This should be fixed in the main map I think. + + dplyr::mutate( + resultstatus = dplyr::case_when( + indicator_code == "VMMC_CIRC.Neg.T2" ~ "Negative", + indicator_code == "VMMC_CIRC.Pos.T2" ~ "Positive", + indicator_code == "VMMC_CIRC.Unk.T2" ~ "Status Unknown", + indicator_code == "HTS_TST.Index.Pos.Share.T2" ~ "Newly Tested Positives", + indicator_code == "HTS_TST.TB.Pos.Share.T2" ~ "Newly Tested Positives", + indicator_code == "HTS_TST.PMTCT.Pos.Share.T2" ~ "Newly Tested Positives", + indicator_code == "TB_STAT.N.New.Pos.T2" ~ "Newly Tested Positives", + indicator_code == "TB_STAT.N.New.Neg.T2" ~ "New Negatives", + indicator_code == "TB_STAT.N.KnownPos.T" ~ "Known Positives", + indicator_code == "PMTCT_STAT.N.KnownPos.T2" ~ "Known Positives", + indicator_code == "PMTCT_STAT.N.New.Neg.T2" ~ "New Negatives", + indicator_code == "PMTCT_STAT.N.New.Pos.T2" ~ "Newly Tested Positives", + indicator_code == "PMTCT_ART.Already.T2" ~ "Known Positives", + indicator_code == "PMTCT_ART.New.T2" ~ "Newly Tested Positives", + indicator_code == "TB_PREV.D.New.T2" ~ "Newly Tested Positives", + indicator_code == "TB_PREV.D.Already.T2" ~ "Known Positives", + indicator_code == "TB_PREV.N.New.T2" ~ "Newly Tested Positives", + indicator_code == "TB_PREV.N.Already.T2" ~ "Known Positives", + indicator_code == "TB_ART.New.T2" ~ "Newly Tested Positives", + indicator_code == "TB_ART.Already.T2" ~ "Known Positives", + indicator_code == "TB_STAT.N.New.Pos.T2" ~ "Newly Tested Positives", + indicator_code == "TB_STAT.N.New.Neg.T2" ~ "New Negatives", + indicator_code == "TB_STAT.N.KnownPos.T2" ~ "Known Positives", + indicator_code == "TX_TB.D.Already.Neg.T2" ~ "TB Screen - Negative, Life-long ART, Already, Positive", + indicator_code == "TX_TB.D.Already.Pos.T2" ~ "TB Screen - Positive, Life-long ART, Already, Positive", + indicator_code == "TX_TB.D.New.Neg.T2" ~ "TB Screen - Negative, Life-long ART, New, Positive", + indicator_code == "TX_TB.D.New.Pos.T2" ~ "TB Screen - Positive, Life-long ART, New, Positive", + indicator_code == "OVC_SERV.Active.T2" & + dataelementuid == "HVzzfyVVIs1" ~ "Active, Beneficiary", + indicator_code == "OVC_SERV.Active.T2" & + dataelementuid == "cx8hxarh4Ke" ~ "Active, Caregiver", + indicator_code == "OVC_SERV.Grad.T2" & + dataelementuid == "HVzzfyVVIs1" ~ "Graduated, Beneficiary", + indicator_code == "OVC_SERV.Grad.T2" & + dataelementuid == "cx8hxarh4Ke" ~ "Graduated, Caregiver", + indicator_code == "GEND_GBV.PE.T2" ~ "Physical and/or Emotional Violence", + indicator_code == "GEND_GBV.S.T2" ~ "Sexual Violence (Post-Rape Care)", + TRUE ~ resultstatus + ) + ) %>% + dplyr::mutate( + disagg_type = dplyr::case_when( + indicator_code == "PLHIV.T2" ~ "Age/Sex/HIVStatus", + indicator_code == "TX_PVLS.D.Routine.T2" ~ "Age/Sex/Indication/HIVStatus", + indicator_code == "TX_PVLS.N.Routine.T2" ~ "Age/Sex/Indication/HIVStatus", + indicator_code == "TX_CURR.T2" ~ "Age/Sex/HIVStatus", + indicator_code == "TX_NEW.T2" ~ "Age/Sex/HIVStatus", + indicator_code == "HTS_RECENT.T2" ~ "Age/Sex/HIVStatus", + indicator_code == "HTS_SELF.T2" ~ "Age/Sex", + indicator_code == "PrEP_NEW.T2" ~ "Age/Sex", + indicator_code == "PrEP_CT.T2" ~ "Age/Sex", + TRUE ~ disagg_type + ) + ) %>% + #Deal with OVC_HIVSTAT. In the DP the data is disaggregated by age + #However, in DATIM there is no disaggregation + dplyr::mutate( + valid_sexes.name = dplyr::case_when( + indicator_code == "OVC_HIVSTAT.T2" ~ NA_character_, + TRUE ~ valid_sexes.name + ) + ) %>% + dplyr::mutate( + valid_ages.name = dplyr::case_when( + indicator_code == "OVC_HIVSTAT.T2" ~ NA_character_, + TRUE ~ valid_ages.name + ) + ) %>% + dplyr::bind_rows( + hts_recent_kps, + tx_new_kps, + tx_curr_kps, + tx_pvls_d_kps, + tx_pvls_n_kps, + tb_stat_known_pos_t2, + prep_new_kps, + prep_ct_kps, + pmtct_eid + ) %>% + dplyr::distinct() +} + +#' Title unpackYear2Sheet +#' +#' @param d +#' +#' @return d +#' @export +#' +unpackYear2Sheet <- function(d) { + #We will not process any Year2 data for regional data packs + #Or any datapacks which have more than one country UID. + if (length(d$info$country_uids) != 1) { + return(d) + } + + sheet <- "Year 2" + + header_row <- + headerRow(tool = d$info$tool, + cop_year = d$info$cop_year) + + if (is.null(d$sheets$`Year 2`)) { + d$sheets$`Year 2` <- + readxl::read_excel( + path = d$keychain$submission_path, + sheet = sheet, + range = readxl::cell_limits(c(header_row, 1), c(NA, NA)), + col_types = "text", + .name_repair = "minimal" + ) + } + d$data$Year2 <- d$sheets$`Year 2` + d$data$Year2 <- d$data$Year2[!(names(d$data$Year2) %in% c(""))] + + #Test column structure before any restructuring. + d <- y2TestColumnStructure(d) + + cols_to_keep <- datapackr:::getColumnsToKeep(d, sheet) + header_cols <- datapackr:::getHeaderColumns(cols_to_keep, sheet) + + Year2_KP_stacked <- pick_schema(d$info$cop_year, d$info$tool) %>% + dplyr::filter(sheet_name == "Year 2", + stringr::str_detect(dataelement_dsd, "\\.\\{KP\\}")) + + d$data$Year2 %<>% + dplyr::select(tidyselect::any_of(cols_to_keep$indicator_code)) %>% + #Deal with HTS_TST + dplyr::mutate(dplyr::across(!header_cols$indicator_code, as.numeric)) %>% + dplyr::mutate(dplyr::across( + tidyselect::contains("Share"), + ~ .x * HTS_TST.Pos.Total_With_HEI.T2 + )) %>% + #Pivot longer + tidyr::pivot_longer( + cols = !tidyselect::any_of(header_cols$indicator_code), + names_to = "indicator_code", + values_to = "value", + values_drop_na = TRUE + ) %>% + #Drop HTS_TST.POS but keep the under 1s and map it to PMTCT_EID <2 months + # nolint start + dplyr::mutate( + indicator_code = dplyr::case_when( + indicator_code == "HTS_TST.Pos.Total_With_HEI.T2" + & Age == "<01" + ~ paste0(indicator_code, ".EID.2"), + indicator_code %in% Year2_KP_stacked$indicator_code + & !is.na(KeyPop) + ~ paste0(indicator_code, (".KP")), + TRUE ~ indicator_code)) %>% + # nolint end + # Allow mapping of EID + # OVC_HIVSTAT needs to be aggregated + dplyr::mutate( + Age = dplyr::case_when( + indicator_code %in% c("HTS_TST.Pos.Total_With_HEI.T2.EID.2", "OVC_HIVSTAT.T2") + ~ NA_character_, + TRUE ~ Age), + Sex = dplyr::case_when( + indicator_code %in% c("HTS_TST.Pos.Total_With_HEI.T2.EID.2", "OVC_HIVSTAT.T2") + ~ NA_character_, + TRUE ~ Sex)) %>% + #Get rid of the HTS_TST Pos at this point + dplyr::filter(indicator_code != "HTS_TST.Pos.Total_With_HEI.T2") %>% + #Drop TX_CURR_SUBNAT data for KPs. It should not be there. + dplyr::filter(!(indicator_code == "TX_CURR_SUBNAT.T2" & + !is.na(KeyPop))) %>% + dplyr::select( + -`Indicator Group`, + valid_sexes.name = "Sex", + valid_ages.name = "Age", + valid_kps.name = "KeyPop" + ) %>% + dplyr::group_by(valid_sexes.name, + valid_ages.name, + valid_kps.name, + indicator_code) %>% + #TODO: This feels a bit risky. Should we only limit to OVC_HIVSTAT & PMTCT_EID? + dplyr::summarise(value = sum(value, na.rm = TRUE), .groups = "drop") + + # Get the raw data element codes from the map ---- + d$data$Year2 %<>% + dplyr::left_join( + getMapDataPack_DATIM_DEs_COCs(d$info$cop_year), + by = c( + "indicator_code", + "valid_ages.name", + "valid_sexes.name", + "valid_kps.name")) + + #No data should have any missing data element uids or category option combo + #uids at this poinbt + d <- y2ExtractInvalidDisaggs(d) + + # Create the DATIM export file ---- + d$datim$year2 <- d$data$Year2 %>% + dplyr::mutate( + orgUnit = d$info$country_uids, + attributeOptionCombo = default_catOptCombo() + ) %>% + dplyr::select( + dataElement = dataelementuid, + period, + orgUnit, + categoryOptionCombo = categoryoptioncombouid, + attributeOptionCombo, + value = value + ) %>% + tidyr::drop_na() %>% #TODO: Remove this. We should not have any NAs at this point + dplyr::distinct() #TODO: Remove this. We need to be sure we have no duplicates from the join + + d + +} diff --git a/R/unPackingChecks.R b/R/unPackingChecks.R index 78af2227e..b466be5fc 100644 --- a/R/unPackingChecks.R +++ b/R/unPackingChecks.R @@ -778,9 +778,15 @@ checkInvalidOrgUnits <- function(sheets, d, quiet = TRUE) { valid_orgunits_local <- getValidOrgUnits(d$info$cop_year) + #There may be some variation in the columns between cop years + cols_to_filter <- switch(as.character(d$info$cop_year), + "2021" = c("SNU1", "PSNU", "Age", "Sex"), + "2022" = c("SNU1", "PSNU", "Age", "Sex"), + "2023" = c("PSNU", "Age", "Sex")) + invalid_orgunits <- d$sheets[sheets] %>% dplyr::bind_rows(.id = "sheet_name") %>% - dplyr::filter(dplyr::if_any(c("SNU1", "PSNU", "Age", "Sex"), ~!is.na(.))) %>% + dplyr::filter(dplyr::if_any(cols_to_filter, ~ !is.na(.))) %>% dplyr::select(sheet_name, PSNU) %>% dplyr::distinct() %>% dplyr::mutate(snu_uid = extract_uid(PSNU)) %>% diff --git a/R/utilities.R b/R/utilities.R index 06b4b1d40..a8a7bb1d3 100644 --- a/R/utilities.R +++ b/R/utilities.R @@ -471,22 +471,20 @@ getMapDataPack_DATIM_DEs_COCs <- function(cop_year, datasource = NULL) { #' @param cop_year The COP Year #' #' @return A data frame of organisation units along with their attributes. -getValidOrgUnits <- function(cop_year = NA) { +getValidOrgUnits <- function(cop_year = NULL) { cop_year <- cop_year %missing% NULL - if (length(cop_year) != 1L) { + stop("You must specify a single COP Year!") } - if (is.na(cop_year) || is.null(cop_year)) { stop(paste("COP Year was not specified")) } - if (!(cop_year %in% supportedCOPYears())) { stop(paste("COP Year", cop_year, "has no valid orgunits.")) } diff --git a/R/writeSpectrumData.R b/R/writeSpectrumData.R new file mode 100644 index 000000000..1573e03fd --- /dev/null +++ b/R/writeSpectrumData.R @@ -0,0 +1,68 @@ +#' Title writeSpectrumData +#' +#' @description Utility function mostly for testing which will +#' write Spectrum data in a defined format to the Spectrum tab. +#' +#' @param wb Openxlsx worbook object +#' @param spectrum_data Spectrum data +#' +#' @return Modified openxlsx worbook object with Spectrum data written to the +#' Spectrum tab. +writeSpectrumData <- function(wb, spectrum_data) { + + + if (!inherits(spectrum_data, "data.frame")) { + warning("Spectrum data must be a data frame. Proceeding further without writing this data.") + return(wb) + } + + expected_names <- c("psnu", "psnu_uid", "area_id", "indicator_code", "dataelement_uid", + "age", "age_uid", "sex", "sex_uid", "calendar_quarter", "value", "age_sex_rse", "district_rse") + + if (!identical(names(spectrum_data), expected_names)) { + warning("Spectrum data does not appear to be in the correct format.Proceeding further without writing this data.") + return(wb) + + } + # Write data to sheet #### + openxlsx::writeData(wb = wb, + sheet = "Spectrum", + x = spectrum_data, # Object to be written. + xy = c(4, 2), + colNames = FALSE, + rowNames = FALSE, + withFilter = FALSE) # Filters are not applied to column name row + + rows_to_write <- seq_len(NROW(spectrum_data)) + 1 + #Write parse age formulas to sheet + parse_age_fomula <- paste0("=SUBSTITUTE(SUBSTITUTE($I", + rows_to_write, + ",CHAR(61),\"\"),CHAR(34),\"\")") + + openxlsx::writeFormula( + wb = wb, + sheet = "Spectrum", + x = parse_age_fomula, + startCol = cellranger::letter_to_num("Q"), + startRow = 2 + ) + # #Write IDs to columns + id_formulas <- paste0("=IF($D", rows_to_write, + "<>\"\",\"[\"&$E", rows_to_write + , "&\"]\"&\"|\"&$Q", rows_to_write, + "&\"|\"&$K", rows_to_write, + "&\"|\"&$G", rows_to_write, + "&\"|\"&IF($G", rows_to_write, + "=\"TX_CURR_SUBNAT.R\",\"CY2022Q4\",$M", rows_to_write, + "),\"\")") + + openxlsx::writeFormula( + wb = wb, + sheet = "Spectrum", + x = id_formulas, + startCol = cellranger::letter_to_num("R"), + startRow = 2 + ) + + wb +} diff --git a/data-raw/COP22/update_cop22_datapack_schema.R b/data-raw/COP22/update_cop22_datapack_schema.R index 9c363799f..9037d2be0 100644 --- a/data-raw/COP22/update_cop22_datapack_schema.R +++ b/data-raw/COP22/update_cop22_datapack_schema.R @@ -8,20 +8,14 @@ library(magrittr) secrets <- Sys.getenv("SECRETS_FOLDER") %>% paste0(., "datim.json") datimutils::loginToDATIM(secrets) -datapack_template_filepath <- system.file("extdata", - "COP22_Data_Pack_Template.xlsx", - package = "datapackr", - mustWork = TRUE) +template_file <- rprojroot::find_package_root_file("inst/extdata/COP22_Data_Pack_Template.xlsx") cop22_data_pack_schema <- unPackSchema( - template_path = datapack_template_filepath, - skip = skip_tabs(tool = "Data Pack Template", cop_year = 2022), + template_path = template_file, cop_year = 2022) waldo::compare(cop22_data_pack_schema, datapackr::cop22_data_pack_schema) -save(cop22_data_pack_schema, - file = "./data/cop22_data_pack_schema.rda", - compress = "xz") +usethis::use_data(cop22_data_pack_schema, overwrite = TRUE, compress = "xz") ## Rebuild package again. diff --git a/data-raw/COP23/cop23_validation_rules.json b/data-raw/COP23/cop23_validation_rules.json new file mode 100644 index 000000000..07b888102 --- /dev/null +++ b/data-raw/COP23/cop23_validation_rules.json @@ -0,0 +1 @@ +{"validationRules":[{"name":"AGYW_PREV (N, NoApp, Age/Sex/DREAMSComp) TARGET <= AGYW_PREV (D, NoApp, Age/Sex/DREAMSBegun) TARGET","id":"vgrMba6LesO","periodType":"FinancialOct","description":"AGYW_PREV (N, NoApp, Age/Sex/DREAMSComp) TARGET <= AGYW_PREV (D, NoApp, Age/Sex/DREAMSBegun) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{tSYnSvSK200}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{HO5wSDKttn3}","missingValueStrategy":"NEVER_SKIP"}},{"name":"DIAGNOSED_NAT (N, NAT, Age Agg/S/HIV) TARGET :OR: DIAGNOSED_NAT (N, NAT, HIV/Sex) TARGET","id":"Cp8ooC5b4qt","periodType":"FinancialOct","description":"DIAGNOSED_NAT (N, NAT, Age Agg/S/HIV) TARGET :OR: DIAGNOSED_NAT (N, NAT, HIV/Sex) TARGET","operator":"exclusive_pair","leftSide":{"expression":"#{kwGogP27E2B}","missingValueStrategy":"SKIP_IF_ALL_VALUES_MISSING"},"rightSide":{"expression":"#{D4gH8ugpzng}","missingValueStrategy":"SKIP_IF_ALL_VALUES_MISSING"}},{"name":"DIAGNOSED_NAT (N, NAT, HIVStatus) TARGET >= DIAGNOSED_NAT (N, NAT, Age Agg/S/HIV) TARGET + DIAGNOSED_NAT (N, NAT, HIV/Sex) TARGET","id":"DNKjkyXJL4V","periodType":"FinancialOct","description":"DIAGNOSED_NAT (N, NAT, HIVStatus) TARGET >= DIAGNOSED_NAT (N, NAT, Age Agg/S/HIV) TARGET + DIAGNOSED_NAT (N, NAT, HIV/Sex) TARGET","operator":"greater_than_or_equal_to","leftSide":{"expression":"#{npKP6tZdT88}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{kwGogP27E2B}+#{D4gH8ugpzng}","missingValueStrategy":"NEVER_SKIP"}},{"name":"DIAGNOSED_SUBNAT (N, SUBNAT, Age/Sex/HIV) TARGET :OR: DIAGNOSED_SUBNAT (N, SUBNAT, HIV/Sex) TARGET","id":"x6gkF7cgDvo","periodType":"FinancialOct","description":"DIAGNOSED_SUBNAT (N, SUBNAT, Age/Sex/HIV) TARGET :OR: DIAGNOSED_SUBNAT (N, SUBNAT, HIV/Sex) TARGET","operator":"exclusive_pair","leftSide":{"expression":"#{nF19GOjcnoD}","missingValueStrategy":"SKIP_IF_ALL_VALUES_MISSING"},"rightSide":{"expression":"#{xVPaflQHwEP}","missingValueStrategy":"SKIP_IF_ALL_VALUES_MISSING"}},{"name":"HTS_RECENT (N, DSD, KeyPop/HIVStatus) TARGET <= HTS_RECENT (N, DSD, Age/Sex/RTRI/HIV) TARGET","id":"YzmqatlGR19","periodType":"FinancialOct","description":"HTS_RECENT (N, DSD, KeyPop/HIVStatus) TARGET <= HTS_RECENT (N, DSD, Age/Sex/RTRI/HIV) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{y42bZItNsea}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{SsqfZeIs1Va}","missingValueStrategy":"NEVER_SKIP"}},{"name":"HTS_RECENT (N, TA, KeyPop/HIVStatus) TARGET <= HTS_RECENT (N, TA, Age/Sex/RTRI/HIV) TARGET","id":"SXSCPTSlkJU","periodType":"FinancialOct","description":"HTS_RECENT (N, TA, KeyPop/HIVStatus) TARGET <= HTS_RECENT (N, TA, Age/Sex/RTRI/HIV) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{uZ2UTVh2wkJ}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{p17Oj2I1PVH}","missingValueStrategy":"NEVER_SKIP"}},{"name":"HTS_SELF (N, DSD, KeyPop) TARGET <= HTS_SELF (N, DSD, Age/Sex) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","id":"kykRc2Taeuy","periodType":"FinancialOct","description":"HTS_SELF (N, DSD, KeyPop) TARGET <= HTS_SELF (N, DSD, Age/Sex) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","operator":"less_than_or_equal_to","leftSide":{"expression":"#{qL1qysWLZoq}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{r9N46a1q942.QoTOWtrqplq}+#{r9N46a1q942.fb1obLTVGJP}+#{r9N46a1q942.OvNoo8adPWg}+#{r9N46a1q942.K5qkqZ7MyVH}+#{r9N46a1q942.b525dwmnqCT}+#{r9N46a1q942.yIRSK1trrSw}+#{r9N46a1q942.KAUx9elzgpv}+#{r9N46a1q942.Cjvs14BxI4s}+#{r9N46a1q942.ydUveu7bUpz}+#{r9N46a1q942.TYauWugQYce}","missingValueStrategy":"NEVER_SKIP"}},{"name":"HTS_SELF (N, TA, KeyPop) TARGET <= HTS_SELF (N, TA, Age/Sex) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","id":"TFy6VAUwfTv","periodType":"FinancialOct","description":"HTS_SELF (N, TA, KeyPop) TARGET <= HTS_SELF (N, TA, Age/Sex) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","operator":"less_than_or_equal_to","leftSide":{"expression":"#{Hx5jjGAdVsT}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{NcMMbbymtij.QoTOWtrqplq}+#{NcMMbbymtij.fb1obLTVGJP}+#{NcMMbbymtij.OvNoo8adPWg}+#{NcMMbbymtij.K5qkqZ7MyVH}+#{NcMMbbymtij.b525dwmnqCT}+#{NcMMbbymtij.yIRSK1trrSw}+#{NcMMbbymtij.KAUx9elzgpv}+#{NcMMbbymtij.Cjvs14BxI4s}+#{NcMMbbymtij.ydUveu7bUpz}+#{NcMMbbymtij.TYauWugQYce}","missingValueStrategy":"NEVER_SKIP"}},{"name":"HTS_TST (N, DSD, KeyPop/Result) TARGET <= HTS_TST (N, DSD, PMTCT P/Age/Sex/Result) TARGET + HTS_TST (N, DSD, STI/Age/Sex/Result) TARGET options 10-14, 15-24, 25-34, 35-49, 50+ + TB_STAT (N, DSD, Age/Sex/KnownNewPosNeg) TARGET opti","id":"j5ALC9dqhkH","periodType":"FinancialOct","description":"HTS_TST (N, DSD, KeyPop/Result) TARGET <= HTS_TST (N, DSD, PMTCT P/Age/Sex/Result) TARGET + HTS_TST (N, DSD, STI/Age/Sex/Result) TARGET options 10-14, 15-24, 25-34, 35-49, 50+ + TB_STAT (N, DSD, Age/Sex/KnownNewPosNeg) TARGET options Newly Identified Positive, Newly Identified Negative, 10-14, 15-24, 25-34, 35-49, 50+ + PMTCT_STAT (N, DSD, Age/Sex/KnownNewResult) TARGET options Newly Identified Positive, Newly Identified Negative + VMMC_CIRC (N, DSD, Age/Sex/HIVStatus) TARGET options Positive, Negative + HTS_INDEX (N, DSD, ActiveIndex/A/S/R) TARGET options 10-14, 15-24, 25-34, 35-49, 50+, Newly Identified Negative, Newly Identified Positive + HTS_TST (N, DSD, ActSNS/Age/Sex/Result) TARGET options 10-14, 15-24, 25-34, 35-49, 50+ + HTS_TST (N, DSD, ActOther/Age/Sex/Result) TARGET options 10-14, 15-24, 25-34, 35-49, 50+ + HTS_TST (N, DSD, OtherF/Age/Sex/Result) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","operator":"less_than_or_equal_to","leftSide":{"expression":"#{ef1o9r1kDIH}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{dd2WtJJxFYj}+#{JDdf06ZTwyz.UpJDTuLdPAy}+#{JDdf06ZTwyz.TF5Q5RAyWme}+#{JDdf06ZTwyz.MrxuaYOnlWh}+#{JDdf06ZTwyz.ZKkeOLADBwy}+#{JDdf06ZTwyz.SISQbQHInlT}+#{JDdf06ZTwyz.uFF2JbAGZy7}+#{JDdf06ZTwyz.QcFdRzqeXXp}+#{JDdf06ZTwyz.CSCd4SnOaQH}+#{JDdf06ZTwyz.hUQndtnqiX0}+#{JDdf06ZTwyz.d7TgqF2Cp02}+#{JDdf06ZTwyz.U5D7oXnxzQ7}+#{JDdf06ZTwyz.OiYlXuTHdPy}+#{JDdf06ZTwyz.DmnGVhc0p5Q}+#{JDdf06ZTwyz.LrC3Cm5o6Sc}+#{JDdf06ZTwyz.O45ThFCR08x}+#{JDdf06ZTwyz.CPO3ELLJveR}+#{JDdf06ZTwyz.fXl4WztEwxV}+#{JDdf06ZTwyz.cwzvmC8JFjD}+#{JDdf06ZTwyz.WuK4Pajy3TI}+#{JDdf06ZTwyz.rWNPEQCETeT}+#{tqP00nubcdo.nLfUtOIjCE6}+#{tqP00nubcdo.b22pTOqGk5J}+#{tqP00nubcdo.ImhaXBHVX8B}+#{tqP00nubcdo.z08ZaV4mCxA}+#{tqP00nubcdo.CNiltuv6rgA}+#{tqP00nubcdo.kIvykribSKv}+#{tqP00nubcdo.t5YSqitkhoS}+#{tqP00nubcdo.AHVkV0ioej1}+#{tqP00nubcdo.aadhTvNEimS}+#{tqP00nubcdo.oVdkzlFcuTE}+#{tqP00nubcdo.sJAkGRqmtRX}+#{tqP00nubcdo.qVwX8yRlaQj}+#{tqP00nubcdo.yFdFOVRP6nZ}+#{tqP00nubcdo.hyZUcUIrg0b}+#{tqP00nubcdo.GD2R7Zu2JcU}+#{tqP00nubcdo.HDbP7ZMvCq0}+#{tqP00nubcdo.aM2bz3GMWYL}+#{tqP00nubcdo.o4yVL1rqnVb}+#{tqP00nubcdo.ib1wZ4GEWUo}+#{tqP00nubcdo.HyGl3CKz9n0}+#{tqP00nubcdo.ChmaqCVVSba}+#{tqP00nubcdo.YAIqpUJ4wFj}+#{tqP00nubcdo.gZNo9TYxWUf}+#{tqP00nubcdo.zUOGAfbDZGo}+#{tqP00nubcdo.xfA04UUlddv}+#{tqP00nubcdo.Fb0kidq2ufJ}+#{tqP00nubcdo.qTiUlMAg1X7}+#{tqP00nubcdo.uzaJ04qaB9U}+#{tqP00nubcdo.UbRNFuQrSJU}+#{tqP00nubcdo.LBdaPfkzvWi}+#{jCfFnI5DZST.k5phJc80j99}+#{jCfFnI5DZST.gquIA7jhsP9}+#{jCfFnI5DZST.K1y6gfJS29m}+#{jCfFnI5DZST.TfUmQbJs4MQ}+#{jCfFnI5DZST.kZHCmGWZ6Vk}+#{jCfFnI5DZST.OP7jS18h6C3}+#{jCfFnI5DZST.fkmAo2UinN6}+#{jCfFnI5DZST.o1laeJsekhb}+#{U0WZIWZ2oLm.nLfUtOIjCE6}+#{U0WZIWZ2oLm.b22pTOqGk5J}+#{U0WZIWZ2oLm.ImhaXBHVX8B}+#{U0WZIWZ2oLm.z08ZaV4mCxA}+#{U0WZIWZ2oLm.CNiltuv6rgA}+#{U0WZIWZ2oLm.kIvykribSKv}+#{U0WZIWZ2oLm.t5YSqitkhoS}+#{U0WZIWZ2oLm.AHVkV0ioej1}+#{U0WZIWZ2oLm.aadhTvNEimS}+#{U0WZIWZ2oLm.oVdkzlFcuTE}+#{U0WZIWZ2oLm.sJAkGRqmtRX}+#{U0WZIWZ2oLm.qVwX8yRlaQj}+#{U0WZIWZ2oLm.yFdFOVRP6nZ}+#{U0WZIWZ2oLm.hyZUcUIrg0b}+#{U0WZIWZ2oLm.GD2R7Zu2JcU}+#{U0WZIWZ2oLm.HDbP7ZMvCq0}+#{U0WZIWZ2oLm.aM2bz3GMWYL}+#{U0WZIWZ2oLm.o4yVL1rqnVb}+#{U0WZIWZ2oLm.ib1wZ4GEWUo}+#{U0WZIWZ2oLm.HyGl3CKz9n0}+#{U0WZIWZ2oLm.ChmaqCVVSba}+#{U0WZIWZ2oLm.YAIqpUJ4wFj}+#{U0WZIWZ2oLm.gZNo9TYxWUf}+#{U0WZIWZ2oLm.zUOGAfbDZGo}+#{U0WZIWZ2oLm.xfA04UUlddv}+#{U0WZIWZ2oLm.Fb0kidq2ufJ}+#{U0WZIWZ2oLm.qTiUlMAg1X7}+#{U0WZIWZ2oLm.uzaJ04qaB9U}+#{U0WZIWZ2oLm.UbRNFuQrSJU}+#{U0WZIWZ2oLm.LBdaPfkzvWi}+#{QPXR0jhU2zo.UpJDTuLdPAy}+#{QPXR0jhU2zo.TF5Q5RAyWme}+#{QPXR0jhU2zo.MrxuaYOnlWh}+#{QPXR0jhU2zo.ZKkeOLADBwy}+#{QPXR0jhU2zo.SISQbQHInlT}+#{QPXR0jhU2zo.uFF2JbAGZy7}+#{QPXR0jhU2zo.QcFdRzqeXXp}+#{QPXR0jhU2zo.CSCd4SnOaQH}+#{QPXR0jhU2zo.hUQndtnqiX0}+#{QPXR0jhU2zo.d7TgqF2Cp02}+#{QPXR0jhU2zo.U5D7oXnxzQ7}+#{QPXR0jhU2zo.OiYlXuTHdPy}+#{QPXR0jhU2zo.DmnGVhc0p5Q}+#{QPXR0jhU2zo.LrC3Cm5o6Sc}+#{QPXR0jhU2zo.O45ThFCR08x}+#{QPXR0jhU2zo.CPO3ELLJveR}+#{QPXR0jhU2zo.fXl4WztEwxV}+#{QPXR0jhU2zo.cwzvmC8JFjD}+#{QPXR0jhU2zo.WuK4Pajy3TI}+#{QPXR0jhU2zo.rWNPEQCETeT}+#{E8v5iyap5SV.UpJDTuLdPAy}+#{E8v5iyap5SV.TF5Q5RAyWme}+#{E8v5iyap5SV.MrxuaYOnlWh}+#{E8v5iyap5SV.ZKkeOLADBwy}+#{E8v5iyap5SV.SISQbQHInlT}+#{E8v5iyap5SV.uFF2JbAGZy7}+#{E8v5iyap5SV.QcFdRzqeXXp}+#{E8v5iyap5SV.CSCd4SnOaQH}+#{E8v5iyap5SV.hUQndtnqiX0}+#{E8v5iyap5SV.d7TgqF2Cp02}+#{E8v5iyap5SV.U5D7oXnxzQ7}+#{E8v5iyap5SV.OiYlXuTHdPy}+#{E8v5iyap5SV.DmnGVhc0p5Q}+#{E8v5iyap5SV.LrC3Cm5o6Sc}+#{E8v5iyap5SV.O45ThFCR08x}+#{E8v5iyap5SV.CPO3ELLJveR}+#{E8v5iyap5SV.fXl4WztEwxV}+#{E8v5iyap5SV.cwzvmC8JFjD}+#{E8v5iyap5SV.WuK4Pajy3TI}+#{E8v5iyap5SV.rWNPEQCETeT}+#{X1sIFjE7Zf5.UpJDTuLdPAy}+#{X1sIFjE7Zf5.TF5Q5RAyWme}+#{X1sIFjE7Zf5.MrxuaYOnlWh}+#{X1sIFjE7Zf5.ZKkeOLADBwy}+#{X1sIFjE7Zf5.SISQbQHInlT}+#{X1sIFjE7Zf5.uFF2JbAGZy7}+#{X1sIFjE7Zf5.QcFdRzqeXXp}+#{X1sIFjE7Zf5.CSCd4SnOaQH}+#{X1sIFjE7Zf5.hUQndtnqiX0}+#{X1sIFjE7Zf5.d7TgqF2Cp02}+#{X1sIFjE7Zf5.U5D7oXnxzQ7}+#{X1sIFjE7Zf5.OiYlXuTHdPy}+#{X1sIFjE7Zf5.DmnGVhc0p5Q}+#{X1sIFjE7Zf5.LrC3Cm5o6Sc}+#{X1sIFjE7Zf5.O45ThFCR08x}+#{X1sIFjE7Zf5.CPO3ELLJveR}+#{X1sIFjE7Zf5.fXl4WztEwxV}+#{X1sIFjE7Zf5.cwzvmC8JFjD}+#{X1sIFjE7Zf5.WuK4Pajy3TI}+#{X1sIFjE7Zf5.rWNPEQCETeT}","missingValueStrategy":"NEVER_SKIP"}},{"name":"HTS_TST (N, TA, KeyPop/Result) TARGET <= HTS_TST (N, TA, PMTCT P/Age/Sex/Result) TARGET + HTS_TST (N, TA, STI/Age/Sex/Result) TARGET options 10-14, 15-24, 25-34, 35-49, 50+ + TB_STAT (N, TA, Age/Sex/KnownNewPosNeg) TARGET options ","id":"haQUFiooR5i","periodType":"FinancialOct","description":"HTS_TST (N, TA, KeyPop/Result) TARGET <= HTS_TST (N, TA, PMTCT P/Age/Sex/Result) TARGET + HTS_TST (N, TA, STI/Age/Sex/Result) TARGET options 10-14, 15-24, 25-34, 35-49, 50+ + TB_STAT (N, TA, Age/Sex/KnownNewPosNeg) TARGET options Newly Identified Positive, Newly Identified Negative, 10-14, 15-24, 25-34, 35-49, 50+ + PMTCT_STAT (N, TA, Age/Sex/KnownNewResult) TARGET options Newly Identified Positive, Newly Identified Negative + VMMC_CIRC (N, TA, Age/Sex/HIVStatus) TARGET options Positive, Negative + HTS_INDEX (N, TA, ActiveIndex/A/S/R) TARGET options 10-14, 15-24, 25-34, 35-49, 50+, Newly Identified Negative, Newly Identified Positive + HTS_TST (N, TA, ActSNS/Age/Sex/Result) TARGET options 10-14, 15-24, 25-34, 35-49, 50+ + HTS_TST (N, TA, ActOther/Age/Sex/Result) TARGET options 10-14, 15-24, 25-34, 35-49, 50+ + HTS_TST (N, TA, OtherF/Age/Sex/Result) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","operator":"less_than_or_equal_to","leftSide":{"expression":"#{MA4lEpgzAxS}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{CrhtWrSnb2D}+#{Jky7p2XOCNk.UpJDTuLdPAy}+#{Jky7p2XOCNk.TF5Q5RAyWme}+#{Jky7p2XOCNk.MrxuaYOnlWh}+#{Jky7p2XOCNk.ZKkeOLADBwy}+#{Jky7p2XOCNk.SISQbQHInlT}+#{Jky7p2XOCNk.uFF2JbAGZy7}+#{Jky7p2XOCNk.QcFdRzqeXXp}+#{Jky7p2XOCNk.CSCd4SnOaQH}+#{Jky7p2XOCNk.hUQndtnqiX0}+#{Jky7p2XOCNk.d7TgqF2Cp02}+#{Jky7p2XOCNk.U5D7oXnxzQ7}+#{Jky7p2XOCNk.OiYlXuTHdPy}+#{Jky7p2XOCNk.DmnGVhc0p5Q}+#{Jky7p2XOCNk.LrC3Cm5o6Sc}+#{Jky7p2XOCNk.O45ThFCR08x}+#{Jky7p2XOCNk.CPO3ELLJveR}+#{Jky7p2XOCNk.fXl4WztEwxV}+#{Jky7p2XOCNk.cwzvmC8JFjD}+#{Jky7p2XOCNk.WuK4Pajy3TI}+#{Jky7p2XOCNk.rWNPEQCETeT}+#{VkrrexUefpS.X9oQCOXFLpS}+#{VkrrexUefpS.T7F0DwyrbBV}+#{VkrrexUefpS.vUUk6jQrXdb}+#{VkrrexUefpS.tNnfZGycqoK}+#{VkrrexUefpS.nr8KgqTWYe8}+#{VkrrexUefpS.FsaFnYgYYiE}+#{VkrrexUefpS.mN07ApGjAKh}+#{VkrrexUefpS.rL9fEh5MSHf}+#{VkrrexUefpS.fpnwXTQGmD5}+#{VkrrexUefpS.rGASCBRaR2U}+#{VkrrexUefpS.W2jt0eaDKcD}+#{VkrrexUefpS.hjgWcKahM96}+#{xlSW2W3YGtk.k5phJc80j99}+#{xlSW2W3YGtk.gquIA7jhsP9}+#{xlSW2W3YGtk.K1y6gfJS29m}+#{xlSW2W3YGtk.TfUmQbJs4MQ}+#{xlSW2W3YGtk.kZHCmGWZ6Vk}+#{xlSW2W3YGtk.OP7jS18h6C3}+#{xlSW2W3YGtk.fkmAo2UinN6}+#{xlSW2W3YGtk.o1laeJsekhb}+#{hAX7DTwvdQS.nLfUtOIjCE6}+#{hAX7DTwvdQS.b22pTOqGk5J}+#{hAX7DTwvdQS.ImhaXBHVX8B}+#{hAX7DTwvdQS.z08ZaV4mCxA}+#{hAX7DTwvdQS.CNiltuv6rgA}+#{hAX7DTwvdQS.kIvykribSKv}+#{hAX7DTwvdQS.t5YSqitkhoS}+#{hAX7DTwvdQS.AHVkV0ioej1}+#{hAX7DTwvdQS.aadhTvNEimS}+#{hAX7DTwvdQS.oVdkzlFcuTE}+#{hAX7DTwvdQS.sJAkGRqmtRX}+#{hAX7DTwvdQS.qVwX8yRlaQj}+#{hAX7DTwvdQS.yFdFOVRP6nZ}+#{hAX7DTwvdQS.hyZUcUIrg0b}+#{hAX7DTwvdQS.GD2R7Zu2JcU}+#{hAX7DTwvdQS.HDbP7ZMvCq0}+#{hAX7DTwvdQS.aM2bz3GMWYL}+#{hAX7DTwvdQS.o4yVL1rqnVb}+#{hAX7DTwvdQS.ib1wZ4GEWUo}+#{hAX7DTwvdQS.HyGl3CKz9n0}+#{hAX7DTwvdQS.ChmaqCVVSba}+#{hAX7DTwvdQS.YAIqpUJ4wFj}+#{hAX7DTwvdQS.gZNo9TYxWUf}+#{hAX7DTwvdQS.zUOGAfbDZGo}+#{hAX7DTwvdQS.xfA04UUlddv}+#{hAX7DTwvdQS.Fb0kidq2ufJ}+#{hAX7DTwvdQS.qTiUlMAg1X7}+#{hAX7DTwvdQS.uzaJ04qaB9U}+#{hAX7DTwvdQS.UbRNFuQrSJU}+#{hAX7DTwvdQS.LBdaPfkzvWi}+#{OeY8AsZ6vWL.UpJDTuLdPAy}+#{OeY8AsZ6vWL.TF5Q5RAyWme}+#{OeY8AsZ6vWL.MrxuaYOnlWh}+#{OeY8AsZ6vWL.ZKkeOLADBwy}+#{OeY8AsZ6vWL.SISQbQHInlT}+#{OeY8AsZ6vWL.uFF2JbAGZy7}+#{OeY8AsZ6vWL.QcFdRzqeXXp}+#{OeY8AsZ6vWL.CSCd4SnOaQH}+#{OeY8AsZ6vWL.hUQndtnqiX0}+#{OeY8AsZ6vWL.d7TgqF2Cp02}+#{OeY8AsZ6vWL.U5D7oXnxzQ7}+#{OeY8AsZ6vWL.OiYlXuTHdPy}+#{OeY8AsZ6vWL.DmnGVhc0p5Q}+#{OeY8AsZ6vWL.LrC3Cm5o6Sc}+#{OeY8AsZ6vWL.O45ThFCR08x}+#{OeY8AsZ6vWL.CPO3ELLJveR}+#{OeY8AsZ6vWL.fXl4WztEwxV}+#{OeY8AsZ6vWL.cwzvmC8JFjD}+#{OeY8AsZ6vWL.WuK4Pajy3TI}+#{OeY8AsZ6vWL.rWNPEQCETeT}+#{jeNpjmy1XaX.UpJDTuLdPAy}+#{jeNpjmy1XaX.TF5Q5RAyWme}+#{jeNpjmy1XaX.MrxuaYOnlWh}+#{jeNpjmy1XaX.ZKkeOLADBwy}+#{jeNpjmy1XaX.SISQbQHInlT}+#{jeNpjmy1XaX.uFF2JbAGZy7}+#{jeNpjmy1XaX.QcFdRzqeXXp}+#{jeNpjmy1XaX.CSCd4SnOaQH}+#{jeNpjmy1XaX.hUQndtnqiX0}+#{jeNpjmy1XaX.d7TgqF2Cp02}+#{jeNpjmy1XaX.U5D7oXnxzQ7}+#{jeNpjmy1XaX.OiYlXuTHdPy}+#{jeNpjmy1XaX.DmnGVhc0p5Q}+#{jeNpjmy1XaX.LrC3Cm5o6Sc}+#{jeNpjmy1XaX.O45ThFCR08x}+#{jeNpjmy1XaX.CPO3ELLJveR}+#{jeNpjmy1XaX.fXl4WztEwxV}+#{jeNpjmy1XaX.cwzvmC8JFjD}+#{jeNpjmy1XaX.WuK4Pajy3TI}+#{jeNpjmy1XaX.rWNPEQCETeT}+#{b1643s4qxZA.UpJDTuLdPAy}+#{b1643s4qxZA.TF5Q5RAyWme}+#{b1643s4qxZA.MrxuaYOnlWh}+#{b1643s4qxZA.ZKkeOLADBwy}+#{b1643s4qxZA.SISQbQHInlT}+#{b1643s4qxZA.uFF2JbAGZy7}+#{b1643s4qxZA.QcFdRzqeXXp}+#{b1643s4qxZA.CSCd4SnOaQH}+#{b1643s4qxZA.hUQndtnqiX0}+#{b1643s4qxZA.d7TgqF2Cp02}+#{b1643s4qxZA.U5D7oXnxzQ7}+#{b1643s4qxZA.OiYlXuTHdPy}+#{b1643s4qxZA.DmnGVhc0p5Q}+#{b1643s4qxZA.LrC3Cm5o6Sc}+#{b1643s4qxZA.O45ThFCR08x}+#{b1643s4qxZA.CPO3ELLJveR}+#{b1643s4qxZA.fXl4WztEwxV}+#{b1643s4qxZA.cwzvmC8JFjD}+#{b1643s4qxZA.WuK4Pajy3TI}+#{b1643s4qxZA.rWNPEQCETeT}","missingValueStrategy":"NEVER_SKIP"}},{"name":"OVC_HIVSTAT (N, DSD) TARGET <= OVC_SERV (N, DSD, Age/Sex/ProgramStatus) TARGET options <1, 1-4, 5-9, 10-14, 15-17 + OVC_SERV (N, DSD, Age/Sex/PSCaregiver) TARGET options <1, 1-4, 5-9, 10-14, 15-17","id":"JoKXq9knuI6","periodType":"FinancialOct","description":"OVC_HIVSTAT (N, DSD) TARGET <= OVC_SERV (N, DSD, Age/Sex/ProgramStatus) TARGET options <1, 1-4, 5-9, 10-14, 15-17 + OVC_SERV (N, DSD, Age/Sex/PSCaregiver) TARGET options <1, 1-4, 5-9, 10-14, 15-17","operator":"less_than_or_equal_to","leftSide":{"expression":"#{TqpIEl7LfV7}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{HVzzfyVVIs1.rTico1He4D7}+#{HVzzfyVVIs1.O0kP2eiwXr0}+#{HVzzfyVVIs1.S8dARTBMJy8}+#{HVzzfyVVIs1.NVkxJFn3sBm}+#{HVzzfyVVIs1.rSB0QMg1KYT}+#{HVzzfyVVIs1.aZRN9kD6ocJ}+#{HVzzfyVVIs1.yJNgCqRK9RH}+#{HVzzfyVVIs1.sk6Jos2G0Qt}+#{HVzzfyVVIs1.zozAnZkqv7o}+#{HVzzfyVVIs1.k3txRv5EtKf}+#{HVzzfyVVIs1.xQ4kQEvWoxO}+#{HVzzfyVVIs1.ZcN9mCvsjBd}+#{HVzzfyVVIs1.jr005I3fIgL}+#{HVzzfyVVIs1.AXsTYP110IT}+#{HVzzfyVVIs1.aqENQmOScjV}+#{HVzzfyVVIs1.iJdeK2OzRMv}+#{HVzzfyVVIs1.L5Td16LsrwX}+#{HVzzfyVVIs1.dy4xY8uaXc2}+#{HVzzfyVVIs1.pAREPN3TOau}+#{HVzzfyVVIs1.FBiIJPaJ2dN}","missingValueStrategy":"NEVER_SKIP"}},{"name":"OVC_HIVSTAT (N, TA) TARGET <= OVC_SERV (N, TA, Age/Sex/ProgramStatus) TARGET options <1, 1-4, 5-9, 10-14, 15-17 + OVC_SERV (N, TA, Age/Sex/PSCaregiver) TARGET options <1, 1-4, 5-9, 10-14, 15-17","id":"ApCcAi965Ud","periodType":"FinancialOct","description":"OVC_HIVSTAT (N, TA) TARGET <= OVC_SERV (N, TA, Age/Sex/ProgramStatus) TARGET options <1, 1-4, 5-9, 10-14, 15-17 + OVC_SERV (N, TA, Age/Sex/PSCaregiver) TARGET options <1, 1-4, 5-9, 10-14, 15-17","operator":"less_than_or_equal_to","leftSide":{"expression":"#{Tp792ZiaQed}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{Qk8HjZeOBom.rTico1He4D7}+#{Qk8HjZeOBom.O0kP2eiwXr0}+#{Qk8HjZeOBom.S8dARTBMJy8}+#{Qk8HjZeOBom.NVkxJFn3sBm}+#{Qk8HjZeOBom.rSB0QMg1KYT}+#{Qk8HjZeOBom.aZRN9kD6ocJ}+#{Qk8HjZeOBom.yJNgCqRK9RH}+#{Qk8HjZeOBom.sk6Jos2G0Qt}+#{Qk8HjZeOBom.zozAnZkqv7o}+#{Qk8HjZeOBom.k3txRv5EtKf}+#{Qk8HjZeOBom.xQ4kQEvWoxO}+#{Qk8HjZeOBom.ZcN9mCvsjBd}+#{Qk8HjZeOBom.jr005I3fIgL}+#{Qk8HjZeOBom.AXsTYP110IT}+#{Qk8HjZeOBom.aqENQmOScjV}+#{Qk8HjZeOBom.iJdeK2OzRMv}+#{Qk8HjZeOBom.L5Td16LsrwX}+#{Qk8HjZeOBom.dy4xY8uaXc2}+#{Qk8HjZeOBom.pAREPN3TOau}+#{Qk8HjZeOBom.FBiIJPaJ2dN}","missingValueStrategy":"NEVER_SKIP"}},{"name":"PMTCT_ART_NAT (N, NAT, NewExistArt) TARGET <= PMTCT_ART_NAT (D, NAT) TARGET","id":"rSEzD9VW9M3","periodType":"FinancialOct","description":"PMTCT_ART_NAT (N, NAT, NewExistArt) TARGET <= PMTCT_ART_NAT (D, NAT) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{JGdLIsOJBgT}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{OGIOaxtwtu7}","missingValueStrategy":"NEVER_SKIP"}},{"name":"PMTCT_STAT_NAT (N, NAT) TARGET <= PMTCT_STAT_NAT (D, NAT) TARGET","id":"sNgjXtv40WI","periodType":"FinancialOct","description":"PMTCT_STAT_NAT (N, NAT) TARGET <= PMTCT_STAT_NAT (D, NAT) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{N2kFiN4J7Y9}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{wWCPwnSeheY}","missingValueStrategy":"NEVER_SKIP"}},{"name":"PMTCT_STAT (N, DSD, Age/Sex/KnownNewResult) TARGET <= PMTCT_STAT (D, DSD, Age/Sex) TARGET","id":"kkgHOAYhbXk","periodType":"FinancialOct","description":"PMTCT_STAT (N, DSD, Age/Sex/KnownNewResult) TARGET <= PMTCT_STAT (D, DSD, Age/Sex) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{Qdn0vmNSflO}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{f10j57Ifd7k}","missingValueStrategy":"NEVER_SKIP"}},{"name":"PMTCT_STAT (N, TA, Age/Sex/KnownNewResult) TARGET <= PMTCT_STAT (D, TA, Age/Sex) TARGET","id":"wOMDGChKe9g","periodType":"FinancialOct","description":"PMTCT_STAT (N, TA, Age/Sex/KnownNewResult) TARGET <= PMTCT_STAT (D, TA, Age/Sex) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{mfYq3HGUIX3}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{bu3sosk4HWn}","missingValueStrategy":"NEVER_SKIP"}},{"name":"PrEP_CT (N, DSD, Age/Sex/HIVStatus) TARGET >= PrEP_NEW (N, DSD, Age/Sex) TARGET v2","id":"LSaavhSefjK","periodType":"FinancialOct","description":"PrEP_CT (N, DSD, Age/Sex/HIVStatus) TARGET >= PrEP_NEW (N, DSD, Age/Sex) TARGET v2","operator":"greater_than_or_equal_to","leftSide":{"expression":"#{DNuQYS2nIPf}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{rXV784LlUQ4}","missingValueStrategy":"NEVER_SKIP"}},{"name":"PrEP_CT (N, DSD, KeyPop) TARGET <= PrEP_CT (N, DSD, Age/Sex/HIVStatus) TARGET","id":"iwpM8hqSk1c","periodType":"FinancialOct","description":"PrEP_CT (N, DSD, KeyPop) TARGET <= PrEP_CT (N, DSD, Age/Sex/HIVStatus) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{bwlf1Jfww0L}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{DNuQYS2nIPf}","missingValueStrategy":"NEVER_SKIP"}},{"name":"PrEP_CT (N, TA, Age/Sex/HIVStatus) TARGET >= PrEP_NEW (N, TA, Age/Sex) TARGET v2","id":"F34zhxAJ7ap","periodType":"FinancialOct","description":"PrEP_CT (N, TA, Age/Sex/HIVStatus) TARGET >= PrEP_NEW (N, TA, Age/Sex) TARGET v2","operator":"greater_than_or_equal_to","leftSide":{"expression":"#{ovFwBkjVLZA}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{DjW93zhi4zZ}","missingValueStrategy":"NEVER_SKIP"}},{"name":"PrEP_CT (N, TA, KeyPop) TARGET <= PrEP_CT (N, TA, Age/Sex/HIVStatus) TARGET","id":"wDBU8zbm1Ua","periodType":"FinancialOct","description":"PrEP_CT (N, TA, KeyPop) TARGET <= PrEP_CT (N, TA, Age/Sex/HIVStatus) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{GlyNHRiJTOu}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{ovFwBkjVLZA}","missingValueStrategy":"NEVER_SKIP"}},{"name":"PrEP_NEW (N, DSD, KeyPop) TARGET <= PrEP_NEW (N, DSD, Age/Sex) TARGET v2","id":"hTf6zPMJ0KA","periodType":"FinancialOct","description":"PrEP_NEW (N, DSD, KeyPop) TARGET <= PrEP_NEW (N, DSD, Age/Sex) TARGET v2","operator":"less_than_or_equal_to","leftSide":{"expression":"#{lXqlw8UxoqF}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{rXV784LlUQ4}","missingValueStrategy":"NEVER_SKIP"}},{"name":"PrEP_NEW (N, TA, KeyPop) TARGET <= PrEP_NEW (N, TA, Age/Sex) TARGET v2","id":"uPYxGDxeQ3m","periodType":"FinancialOct","description":"PrEP_NEW (N, TA, KeyPop) TARGET <= PrEP_NEW (N, TA, Age/Sex) TARGET v2","operator":"less_than_or_equal_to","leftSide":{"expression":"#{gdHE7Kk9Q1k}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{DjW93zhi4zZ}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TB_PREV (N, DSD, Age/Sex/NewExArt/HIV) TARGET <= TB_PREV (D, DSD, Age/Sex/NewExArt/HIV) TARGET","id":"SpG8ZoIkRW2","periodType":"FinancialOct","description":"TB_PREV (N, DSD, Age/Sex/NewExArt/HIV) TARGET <= TB_PREV (D, DSD, Age/Sex/NewExArt/HIV) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{DA7uTAScxxi}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{L3qltmjNtSr}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TB_PREV (N, TA, Age/Sex/NewExArt/HIV) TARGET <= TB_PREV (D, TA, Age/Sex/NewExArt/HIV) TARGET","id":"fFmcyva5sjB","periodType":"FinancialOct","description":"TB_PREV (N, TA, Age/Sex/NewExArt/HIV) TARGET <= TB_PREV (D, TA, Age/Sex/NewExArt/HIV) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{MdqPezsikDr}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{HUgQMmRVHMS}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TB_STAT (N, DSD, Age/Sex/KnownNewPosNeg) TARGET <= TB_STAT (D, DSD, Age/Sex) TARGET","id":"E5p1OpvZi9t","periodType":"FinancialOct","description":"TB_STAT (N, DSD, Age/Sex/KnownNewPosNeg) TARGET <= TB_STAT (D, DSD, Age/Sex) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{tqP00nubcdo}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{eeYmLTvWk8k}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TB_STAT (N, TA, Age/Sex/KnownNewPosNeg) TARGET <= TB_STAT (D, TA, Age/Sex) TARGET","id":"cMlGLd9QPmD","periodType":"FinancialOct","description":"TB_STAT (N, TA, Age/Sex/KnownNewPosNeg) TARGET <= TB_STAT (D, TA, Age/Sex) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{VkrrexUefpS}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{OLbVI34nrpu}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TX_CURR_NAT (N, NAT, Age Agg/Sex) TARGET :OR: TX_CURR_NAT (N, NAT, Sex) TARGET","id":"SaGnjyARR10","periodType":"FinancialOct","description":"TX_CURR_NAT (N, NAT, Age Agg/Sex) TARGET :OR: TX_CURR_NAT (N, NAT, Sex) TARGET","operator":"exclusive_pair","leftSide":{"expression":"#{MCya9ihz5Yh}","missingValueStrategy":"SKIP_IF_ALL_VALUES_MISSING"},"rightSide":{"expression":"#{Kx6zjHMU71y}","missingValueStrategy":"SKIP_IF_ALL_VALUES_MISSING"}},{"name":"TX_CURR_NAT (N, NAT) TARGET >= TX_CURR_NAT (N, NAT, Age Agg/Sex) TARGET + TX_CURR_NAT (N, NAT, Sex) TARGET","id":"sJ1yAdfZBSb","periodType":"FinancialOct","description":"TX_CURR_NAT (N, NAT) TARGET >= TX_CURR_NAT (N, NAT, Age Agg/Sex) TARGET + TX_CURR_NAT (N, NAT, Sex) TARGET","operator":"greater_than_or_equal_to","leftSide":{"expression":"#{mxAZxVqiyHR}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{MCya9ihz5Yh}+#{Kx6zjHMU71y}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TX_CURR (N, DSD, KeyPop/HIVStatus) TARGET <= TX_CURR (N, DSD, Age/Sex/HIVStatus) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","id":"DgATgmOrAYy","periodType":"FinancialOct","description":"TX_CURR (N, DSD, KeyPop/HIVStatus) TARGET <= TX_CURR (N, DSD, Age/Sex/HIVStatus) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","operator":"less_than_or_equal_to","leftSide":{"expression":"#{OLbhrUez4dP}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{di4b6joXm84.RLGOV7MCVrF}+#{di4b6joXm84.SGNvThVISKr}+#{di4b6joXm84.DeBA55gXcy3}+#{di4b6joXm84.iJuMQQ1wnUf}+#{di4b6joXm84.CeuDnHicKrF}+#{di4b6joXm84.G5xBaxUTA1Q}+#{di4b6joXm84.SJyG6PfeF6l}+#{di4b6joXm84.IsCSXOSWFSn}+#{di4b6joXm84.LOtNQ2NFIgT}+#{di4b6joXm84.JgmjEkK0toj}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TX_CURR (N, TA, KeyPop/HIVStatus) TARGET <= TX_CURR (N, TA, Age/Sex/HIVStatus) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","id":"WUZT1rSBnji","periodType":"FinancialOct","description":"TX_CURR (N, TA, KeyPop/HIVStatus) TARGET <= TX_CURR (N, TA, Age/Sex/HIVStatus) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","operator":"less_than_or_equal_to","leftSide":{"expression":"#{uIqAr4ev05I}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{HGZY9RNZjRd.tEMe0224zlP}+#{HGZY9RNZjRd.AG0milXShQM}+#{HGZY9RNZjRd.lR2zeQ9VfB8}+#{HGZY9RNZjRd.QwUdNwRA8Uc}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TX_CURR_SUBNAT (N, SUBNAT, Age/Sex/HIV) TARGET :OR: TX_CURR_SUBNAT (N, SUBNAT, Sex/HIV) TARGET","id":"LwzdyhJGAkm","periodType":"FinancialOct","description":"TX_CURR_SUBNAT (N, SUBNAT, Age/Sex/HIV) TARGET :OR: TX_CURR_SUBNAT (N, SUBNAT, Sex/HIV) TARGET","operator":"exclusive_pair","leftSide":{"expression":"#{xghQXueYJxu}","missingValueStrategy":"SKIP_IF_ALL_VALUES_MISSING"},"rightSide":{"expression":"#{IwYoTvC2tof}","missingValueStrategy":"SKIP_IF_ALL_VALUES_MISSING"}},{"name":"TX_NEW (N, DSD, Age/Sex/HIVStatus) TARGET <= TX_CURR (N, DSD, Age/Sex/HIVStatus) TARGET","id":"iESaQHWhmnz","periodType":"FinancialOct","description":"TX_NEW (N, DSD, Age/Sex/HIVStatus) TARGET <= TX_CURR (N, DSD, Age/Sex/HIVStatus) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{vmfKLKi1NBA}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{di4b6joXm84}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TX_NEW (N, DSD, KeyPop/HIVStatus) TARGET <= TX_NEW (N, DSD, Age/Sex/HIVStatus) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","id":"OutnbQ55w2l","periodType":"FinancialOct","description":"TX_NEW (N, DSD, KeyPop/HIVStatus) TARGET <= TX_NEW (N, DSD, Age/Sex/HIVStatus) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","operator":"less_than_or_equal_to","leftSide":{"expression":"#{ktZYUSS0Zjo}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{vmfKLKi1NBA.RLGOV7MCVrF}+#{vmfKLKi1NBA.SGNvThVISKr}+#{vmfKLKi1NBA.DeBA55gXcy3}+#{vmfKLKi1NBA.iJuMQQ1wnUf}+#{vmfKLKi1NBA.CeuDnHicKrF}+#{vmfKLKi1NBA.G5xBaxUTA1Q}+#{vmfKLKi1NBA.SJyG6PfeF6l}+#{vmfKLKi1NBA.IsCSXOSWFSn}+#{vmfKLKi1NBA.LOtNQ2NFIgT}+#{vmfKLKi1NBA.JgmjEkK0toj}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TX_NEW (N, TA, Age/Sex/HIVStatus) TARGET <= TX_CURR (N, TA, Age/Sex/HIVStatus) TARGET","id":"DiJIqX0Om20","periodType":"FinancialOct","description":"TX_NEW (N, TA, Age/Sex/HIVStatus) TARGET <= TX_CURR (N, TA, Age/Sex/HIVStatus) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{DhrLCUBm3bK}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{HGZY9RNZjRd}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TX_NEW (N, TA, KeyPop/HIVStatus) TARGET <= TX_NEW (N, TA, Age/Sex/HIVStatus) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","id":"pbwVVDj7rbv","periodType":"FinancialOct","description":"TX_NEW (N, TA, KeyPop/HIVStatus) TARGET <= TX_NEW (N, TA, Age/Sex/HIVStatus) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","operator":"less_than_or_equal_to","leftSide":{"expression":"#{SyQHN3Jpcww}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{DhrLCUBm3bK.RLGOV7MCVrF}+#{DhrLCUBm3bK.SGNvThVISKr}+#{DhrLCUBm3bK.DeBA55gXcy3}+#{DhrLCUBm3bK.iJuMQQ1wnUf}+#{DhrLCUBm3bK.CeuDnHicKrF}+#{DhrLCUBm3bK.G5xBaxUTA1Q}+#{DhrLCUBm3bK.SJyG6PfeF6l}+#{DhrLCUBm3bK.IsCSXOSWFSn}+#{DhrLCUBm3bK.LOtNQ2NFIgT}+#{DhrLCUBm3bK.JgmjEkK0toj}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TX_PVLS (D, DSD, KeyPop/HIVStatus) TARGET <= TX_PVLS (D, DSD, Age/Sex/Ind/HIV) TARGET v2 options 10-14, 15-24, 25-34, 35-49, 50+","id":"OtyZkXEwW6D","periodType":"FinancialOct","description":"TX_PVLS (D, DSD, KeyPop/HIVStatus) TARGET <= TX_PVLS (D, DSD, Age/Sex/Ind/HIV) TARGET v2 options 10-14, 15-24, 25-34, 35-49, 50+","operator":"less_than_or_equal_to","leftSide":{"expression":"#{P9OjdVVMHMW}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{WpZwPieQ060.Y0NhdTlPb51}+#{WpZwPieQ060.Zi54FPun6Vk}+#{WpZwPieQ060.TipzA6GZfZT}+#{WpZwPieQ060.nZPSnZ1yH2j}+#{WpZwPieQ060.UlSacH1D2fk}+#{WpZwPieQ060.bwFCHybAwol}+#{WpZwPieQ060.y5UHmDx4xaD}+#{WpZwPieQ060.gvBt3MwoI7T}+#{WpZwPieQ060.Xq52sj6kjCI}+#{WpZwPieQ060.T3VBaUubXKd}+#{WpZwPieQ060.o2JVE9N2Xox}+#{WpZwPieQ060.XdIdVCk0VCu}+#{WpZwPieQ060.VnpUnKEEYFa}+#{WpZwPieQ060.wsoNVIAcEFC}+#{WpZwPieQ060.PmGMM8uFjo7}+#{WpZwPieQ060.DtzCrbJXIZh}+#{WpZwPieQ060.AHktV3Xvvft}+#{WpZwPieQ060.fhILECF10oc}+#{WpZwPieQ060.zfuuqk4jRUc}+#{WpZwPieQ060.PtCAvXLn3aI}+#{WpZwPieQ060.CFrMdkc5Wcj}+#{WpZwPieQ060.jaLUDPHAIWW}+#{WpZwPieQ060.skyEmXcTmEW}+#{WpZwPieQ060.Y2sOpOmAsEH}+#{WpZwPieQ060.hU5Fl68fklc}+#{WpZwPieQ060.O5ebnsmhLnJ}+#{WpZwPieQ060.OZHrqW9GrcQ}+#{WpZwPieQ060.oakCqqjkuqs}+#{WpZwPieQ060.IuuLdSjHSpd}+#{WpZwPieQ060.UHZOTJlEKQe}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TX_PVLS (D, TA, KeyPop/HIVStatus) TARGET <= TX_PVLS (D, TA, Age/Sex/Ind/HIV) TARGET v2 options 10-14, 15-24, 25-34, 35-49, 50+","id":"sL3KAYB5lLr","periodType":"FinancialOct","description":"TX_PVLS (D, TA, KeyPop/HIVStatus) TARGET <= TX_PVLS (D, TA, Age/Sex/Ind/HIV) TARGET v2 options 10-14, 15-24, 25-34, 35-49, 50+","operator":"less_than_or_equal_to","leftSide":{"expression":"#{ZYB5AHSw7Tm}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{KlAFsRMw17y.Y0NhdTlPb51}+#{KlAFsRMw17y.Zi54FPun6Vk}+#{KlAFsRMw17y.TipzA6GZfZT}+#{KlAFsRMw17y.nZPSnZ1yH2j}+#{KlAFsRMw17y.UlSacH1D2fk}+#{KlAFsRMw17y.bwFCHybAwol}+#{KlAFsRMw17y.y5UHmDx4xaD}+#{KlAFsRMw17y.gvBt3MwoI7T}+#{KlAFsRMw17y.Xq52sj6kjCI}+#{KlAFsRMw17y.T3VBaUubXKd}+#{KlAFsRMw17y.o2JVE9N2Xox}+#{KlAFsRMw17y.XdIdVCk0VCu}+#{KlAFsRMw17y.VnpUnKEEYFa}+#{KlAFsRMw17y.wsoNVIAcEFC}+#{KlAFsRMw17y.PmGMM8uFjo7}+#{KlAFsRMw17y.DtzCrbJXIZh}+#{KlAFsRMw17y.AHktV3Xvvft}+#{KlAFsRMw17y.fhILECF10oc}+#{KlAFsRMw17y.zfuuqk4jRUc}+#{KlAFsRMw17y.PtCAvXLn3aI}+#{KlAFsRMw17y.CFrMdkc5Wcj}+#{KlAFsRMw17y.jaLUDPHAIWW}+#{KlAFsRMw17y.skyEmXcTmEW}+#{KlAFsRMw17y.Y2sOpOmAsEH}+#{KlAFsRMw17y.hU5Fl68fklc}+#{KlAFsRMw17y.O5ebnsmhLnJ}+#{KlAFsRMw17y.OZHrqW9GrcQ}+#{KlAFsRMw17y.oakCqqjkuqs}+#{KlAFsRMw17y.IuuLdSjHSpd}+#{KlAFsRMw17y.UHZOTJlEKQe}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TX_PVLS (N, DSD, Age/Sex/Ind/HIV) TARGET <= TX_PVLS (D, DSD, Age/Sex/Ind/HIV) TARGET v2","id":"RG5MppiflCp","periodType":"FinancialOct","description":"TX_PVLS (N, DSD, Age/Sex/Ind/HIV) TARGET <= TX_PVLS (D, DSD, Age/Sex/Ind/HIV) TARGET v2","operator":"less_than_or_equal_to","leftSide":{"expression":"#{N55pM5ZuWcI}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{WpZwPieQ060}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TX_PVLS (N, DSD, KeyPop/HIVStatus) TARGET <= TX_PVLS (N, DSD, Age/Sex/Ind/HIV) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","id":"p0F57p1XwAS","periodType":"FinancialOct","description":"TX_PVLS (N, DSD, KeyPop/HIVStatus) TARGET <= TX_PVLS (N, DSD, Age/Sex/Ind/HIV) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","operator":"less_than_or_equal_to","leftSide":{"expression":"#{tGtB1nLnQjC}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{N55pM5ZuWcI.Y0NhdTlPb51}+#{N55pM5ZuWcI.Zi54FPun6Vk}+#{N55pM5ZuWcI.TipzA6GZfZT}+#{N55pM5ZuWcI.nZPSnZ1yH2j}+#{N55pM5ZuWcI.UlSacH1D2fk}+#{N55pM5ZuWcI.bwFCHybAwol}+#{N55pM5ZuWcI.y5UHmDx4xaD}+#{N55pM5ZuWcI.gvBt3MwoI7T}+#{N55pM5ZuWcI.Xq52sj6kjCI}+#{N55pM5ZuWcI.T3VBaUubXKd}+#{N55pM5ZuWcI.o2JVE9N2Xox}+#{N55pM5ZuWcI.XdIdVCk0VCu}+#{N55pM5ZuWcI.VnpUnKEEYFa}+#{N55pM5ZuWcI.wsoNVIAcEFC}+#{N55pM5ZuWcI.PmGMM8uFjo7}+#{N55pM5ZuWcI.DtzCrbJXIZh}+#{N55pM5ZuWcI.AHktV3Xvvft}+#{N55pM5ZuWcI.fhILECF10oc}+#{N55pM5ZuWcI.zfuuqk4jRUc}+#{N55pM5ZuWcI.PtCAvXLn3aI}+#{N55pM5ZuWcI.CFrMdkc5Wcj}+#{N55pM5ZuWcI.jaLUDPHAIWW}+#{N55pM5ZuWcI.skyEmXcTmEW}+#{N55pM5ZuWcI.Y2sOpOmAsEH}+#{N55pM5ZuWcI.hU5Fl68fklc}+#{N55pM5ZuWcI.O5ebnsmhLnJ}+#{N55pM5ZuWcI.OZHrqW9GrcQ}+#{N55pM5ZuWcI.oakCqqjkuqs}+#{N55pM5ZuWcI.IuuLdSjHSpd}+#{N55pM5ZuWcI.UHZOTJlEKQe}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TX_PVLS (N, TA, Age/Sex/Ind/HIV) TARGET <= TX_PVLS (D, TA, Age/Sex/Ind/HIV) TARGET v2","id":"K7zn5vWmQrg","periodType":"FinancialOct","description":"TX_PVLS (N, TA, Age/Sex/Ind/HIV) TARGET <= TX_PVLS (D, TA, Age/Sex/Ind/HIV) TARGET v2","operator":"less_than_or_equal_to","leftSide":{"expression":"#{LCrR2BK3wmA}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{KlAFsRMw17y}","missingValueStrategy":"NEVER_SKIP"}},{"name":"TX_PVLS (N, TA, KeyPop/HIVStatus) TARGET <= TX_PVLS (N, TA, Age/Sex/Ind/HIV) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","id":"zGkTFCzcX5X","periodType":"FinancialOct","description":"TX_PVLS (N, TA, KeyPop/HIVStatus) TARGET <= TX_PVLS (N, TA, Age/Sex/Ind/HIV) TARGET options 10-14, 15-24, 25-34, 35-49, 50+","operator":"less_than_or_equal_to","leftSide":{"expression":"#{Qshwh8ZOmYD}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{LCrR2BK3wmA.Y0NhdTlPb51}+#{LCrR2BK3wmA.Zi54FPun6Vk}+#{LCrR2BK3wmA.TipzA6GZfZT}+#{LCrR2BK3wmA.nZPSnZ1yH2j}+#{LCrR2BK3wmA.UlSacH1D2fk}+#{LCrR2BK3wmA.bwFCHybAwol}+#{LCrR2BK3wmA.y5UHmDx4xaD}+#{LCrR2BK3wmA.gvBt3MwoI7T}+#{LCrR2BK3wmA.Xq52sj6kjCI}+#{LCrR2BK3wmA.T3VBaUubXKd}+#{LCrR2BK3wmA.o2JVE9N2Xox}+#{LCrR2BK3wmA.XdIdVCk0VCu}+#{LCrR2BK3wmA.VnpUnKEEYFa}+#{LCrR2BK3wmA.wsoNVIAcEFC}+#{LCrR2BK3wmA.PmGMM8uFjo7}+#{LCrR2BK3wmA.DtzCrbJXIZh}+#{LCrR2BK3wmA.AHktV3Xvvft}+#{LCrR2BK3wmA.fhILECF10oc}+#{LCrR2BK3wmA.zfuuqk4jRUc}+#{LCrR2BK3wmA.PtCAvXLn3aI}+#{LCrR2BK3wmA.CFrMdkc5Wcj}+#{LCrR2BK3wmA.jaLUDPHAIWW}+#{LCrR2BK3wmA.skyEmXcTmEW}+#{LCrR2BK3wmA.Y2sOpOmAsEH}+#{LCrR2BK3wmA.hU5Fl68fklc}+#{LCrR2BK3wmA.O5ebnsmhLnJ}+#{LCrR2BK3wmA.OZHrqW9GrcQ}+#{LCrR2BK3wmA.oakCqqjkuqs}+#{LCrR2BK3wmA.IuuLdSjHSpd}+#{LCrR2BK3wmA.UHZOTJlEKQe}","missingValueStrategy":"NEVER_SKIP"}},{"name":"VL_SUPPRESSION_NAT (N, NAT, AgeA/S/H) TARGET :OR: VL_SUPPRESSION_NAT (N, NAT, HIV/Sex) TARGET","id":"IUjy9GtHGUe","periodType":"FinancialOct","description":"VL_SUPPRESSION_NAT (N, NAT, AgeA/S/H) TARGET :OR: VL_SUPPRESSION_NAT (N, NAT, HIV/Sex) TARGET","operator":"exclusive_pair","leftSide":{"expression":"#{vw62pqpTa87}","missingValueStrategy":"SKIP_IF_ALL_VALUES_MISSING"},"rightSide":{"expression":"#{KDCBYIUcV4p}","missingValueStrategy":"SKIP_IF_ALL_VALUES_MISSING"}},{"name":"VL_SUPPRESSION_NAT (N, NAT, HIVStatus) TARGET >= VL_SUPPRESSION_NAT (N, NAT, AgeA/S/H) TARGET + VL_SUPPRESSION_NAT (N, NAT, HIV/Sex) TARGET","id":"FDxnOcXlVLY","periodType":"FinancialOct","description":"VL_SUPPRESSION_NAT (N, NAT, HIVStatus) TARGET >= VL_SUPPRESSION_NAT (N, NAT, AgeA/S/H) TARGET + VL_SUPPRESSION_NAT (N, NAT, HIV/Sex) TARGET","operator":"greater_than_or_equal_to","leftSide":{"expression":"#{RG2vkB2BEb6}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{vw62pqpTa87}+#{KDCBYIUcV4p}","missingValueStrategy":"NEVER_SKIP"}},{"name":"VL_SUPPRESSION_NAT (N, NAT, RT/HIV) TARGET <= VL_SUPPRESSION_NAT (N, NAT, HIVStatus) TARGET","id":"onST7zEZL2n","periodType":"FinancialOct","description":"VL_SUPPRESSION_NAT (N, NAT, RT/HIV) TARGET <= VL_SUPPRESSION_NAT (N, NAT, HIVStatus) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{JF6iC9sV2pV}","missingValueStrategy":"NEVER_SKIP"},"rightSide":{"expression":"#{RG2vkB2BEb6}","missingValueStrategy":"NEVER_SKIP"}},{"name":"VL_SUPPRESSION_SUBNAT (N, SUBNAT, Age/S/H) TARGET :OR: VL_SUPPRESSION_SUBNAT (N, SUBNAT, HIV/Sex) TARGET","id":"CuyUeBJLYNR","periodType":"FinancialOct","description":"VL_SUPPRESSION_SUBNAT (N, SUBNAT, Age/S/H) TARGET :OR: VL_SUPPRESSION_SUBNAT (N, SUBNAT, HIV/Sex) TARGET","operator":"exclusive_pair","leftSide":{"expression":"#{zoKiMGRucOY}","missingValueStrategy":"SKIP_IF_ALL_VALUES_MISSING"},"rightSide":{"expression":"#{NgQE08Vyuxd}","missingValueStrategy":"SKIP_IF_ALL_VALUES_MISSING"}},{"name":"VMMC_TOTALCIRC_NAT (N, NAT, Age/Sex) TARGET <= VMMC_TOTALCIRC_NAT (N, NAT, Sex) TARGET","id":"u6cPqE9pCf5","periodType":"FinancialOct","description":"VMMC_TOTALCIRC_NAT (N, NAT, Age/Sex) TARGET <= VMMC_TOTALCIRC_NAT (N, NAT, Sex) TARGET","operator":"less_than_or_equal_to","leftSide":{"expression":"#{AppEXdI1dhS}","missingValueStrategy":"SKIP_IF_ALL_VALUES_MISSING"},"rightSide":{"expression":"#{eTV3FznMU6C}","missingValueStrategy":"SKIP_IF_ALL_VALUES_MISSING"}}]} \ No newline at end of file diff --git a/data-raw/COP23/update_cop23_datapack_schema.R b/data-raw/COP23/update_cop23_datapack_schema.R index d7c6ef04e..85d36a114 100644 --- a/data-raw/COP23/update_cop23_datapack_schema.R +++ b/data-raw/COP23/update_cop23_datapack_schema.R @@ -3,10 +3,8 @@ library(datapackr) -datapack_template_filepath <- system.file("extdata", - "COP23_Data_Pack_Template.xlsx", - package = "datapackr", - mustWork = TRUE) +datapack_template_filepath <- rprojroot::find_package_root_file("inst/extdata/COP23_Data_Pack_Template.xlsx") + cop23_data_pack_schema <- unPackSchema( template_path = datapack_template_filepath, @@ -14,13 +12,10 @@ cop23_data_pack_schema <- waldo::compare(datapackr::cop23_data_pack_schema, cop23_data_pack_schema) -checkSchema(schema = datapackr::cop23_data_pack_schema, +checkSchema(schema = cop23_data_pack_schema, template_path = datapack_template_filepath, cop_year = 2023, tool = "Data Pack") - -save(cop23_data_pack_schema, - file = "./data/cop23_data_pack_schema.rda", - compress = "xz") +usethis::use_data(cop23_data_pack_schema, overwrite = TRUE, compress = "xz") ## Rebuild package again. (Cmd+Shift+B) diff --git a/data-raw/COP23/update_cop23_de_coc_co_map.R b/data-raw/COP23/update_cop23_de_coc_co_map.R index 333dc6302..f1f9c9f53 100644 --- a/data-raw/COP23/update_cop23_de_coc_co_map.R +++ b/data-raw/COP23/update_cop23_de_coc_co_map.R @@ -2,6 +2,21 @@ # metadata, then combines this with the Data Pack schema to create a full map between # Data Packs and DATIM for the purpose of generating import and analytics tables. +# For new `Year 2` data, this file assumes: +# +# - All positives within modalities have already been multiplied against total +# positives to make integers. +# +# - EID is already split into separate rows for each of <2 & 2-12 months +# +# - EID & KP indicator_codes first use their overarching Age/Sex indicator code +# equivalents, then are suffixed with `.EID.2`, `.EID.12`, or `.KP`. +# +# - OVC_SERV have already been tagged with correct dataElements (Caregivers, vs. OVC) +# +# - OVC_HIVSTAT has already been aggregated to have no age/sex disaggregation. + + # Point to DATIM login secrets #### secrets <- Sys.getenv("SECRETS_FOLDER") %>% paste0(., "datim.json") datimutils::loginToDATIM(secrets) @@ -11,14 +26,22 @@ cop_year <- 2023 datasets_to_pull <- tibble::tribble( ~dataset_uid, ~dataset_name, ~FY, ~targets_results, ~datastream, ~org_unit, "dA9C5bL44NX", "FY24 MER Targets", 2024, "targets", "mer", "psnu", + "dA9C5bL44NX", "FY25 MER Targets", 2025, "targets", "mer", "psnu", "vpDd67HlZcT", "FY24 DREAMS Targets", 2024, "targets", "dreams", "dsnu", + "vpDd67HlZcT", "FY25 DREAMS Targets", 2025, "targets", "dreams", "dsnu", + # For all FY25 SUBNAT/IMPATT, mimic FY24 "kWKJQYP1uT7", "FY25 IMPATT", 2025, "targets", "impatt", "psnu", "kWKJQYP1uT7", "FY24 IMPATT", 2024, "targets", "impatt", "psnu", - "CxMsvlKepvE", "FY23 IMPATT", 2023, "targets", "impatt", "psnu", + # For all FY23 SUBNAT/IMPATT, remap to FY24 disaggs, as these won't go to + # DATIM, but must go to PAW alongside FY24. + "kWKJQYP1uT7", "FY23 IMPATT", 2023, "targets", "impatt", "psnu", "bKSmkDP5YTc", "FY25 SUBNAT Targets", 2025, "targets", "subnat", "psnu", "bKSmkDP5YTc", "FY24 SUBNAT Targets", 2024, "targets", "subnat", "psnu", - "J4tdiDEi08O", "FY23 SUBNAT Targets", 2023, "targets", "subnat", "psnu", - "IXiORiVFqIv", "FY22 SUBNAT Results", 2022, "results", "subnat", "psnu") + "bKSmkDP5YTc", "FY23 SUBNAT Targets", 2023, "targets", "subnat", "psnu", + # May not be able to map FY22 SUBNAT Results, since these are results instead + # of Targets + #"IXiORiVFqIv", "FY22 SUBNAT Results", 2022, "results", "subnat", "psnu") +) # Test that all dataSet uids are valid ---- dataSetUIDs <- datimutils::getMetadata(dataSets, d2_session = d2_session) @@ -89,14 +112,107 @@ fullCodeList %<>% # Prep Data Pack schema for mapping #### -dp_map <- pick_schema(2023, "Data Pack") %>% +dp_map <- pick_schema(cop_year, "Data Pack") %>% dplyr::filter((col_type == "target" & dataset %in% c("mer", "subnat", "impatt")) | dataset == "subnat" & col_type == "result", !is.na(FY)) %>% dplyr::select(sheet_name, indicator_code, dataset, col_type, value_type, dataelement_dsd, dataelement_ta, categoryoption_specified, valid_ages, valid_sexes, valid_kps, - FY, period) %>% + FY, period) + +# Prep Year 2 data ---- +Year2 <- dp_map %>% + dplyr::filter(sheet_name == "Year 2") %>% + +## Handle unique KP & EID data ---- + # KP + tidyr::separate_wider_regex( + dataelement_dsd, + c(dataelement_dsd = ".*", + "\\.\\{KP\\}", + dataelement_dsd_KP = ".*"), + too_few = "align_start") %>% + tidyr::separate_wider_regex( + categoryoption_specified, + c(categoryoption_specified = ".*", + "\\.\\{KP\\}", + categoryoption_specified_KP = ".*"), + too_few = "align_start") %>% + + # EID + tidyr::separate_wider_regex( + dataelement_dsd, + c(dataelement_dsd = ".*", + "\\{EID\\}", + dataelement_dsd_EID = ".*"), + too_few = "align_start") %>% + tidyr::separate_wider_regex( + categoryoption_specified, + c(categoryoption_specified = ".*", + "\\{EID\\}", + categoryoption_specified_EID = ".*"), + too_few = "align_start") + + # Remove empty string written where nothing left after removing KP & EID +Year2$dataelement_dsd[Year2$dataelement_dsd == ""] <- NA +Year2$categoryoption_specified[Year2$categoryoption_specified == ""] <- NA + + # Split KP, Age/Sex, EID into separate rows + +empty <- list(tibble::tribble( + ~name, ~id, + NA_character_, NA_character_)) + + ## KP +Year2_KP <- Year2 %>% + dplyr::filter(!is.na(dataelement_dsd_KP)) %>% + dplyr::select(-dataelement_dsd, -dataelement_dsd_EID, + -categoryoption_specified, -categoryoption_specified_EID) %>% + dplyr::rename(dataelement_dsd = dataelement_dsd_KP, + categoryoption_specified = categoryoption_specified_KP) %>% + dplyr::mutate(valid_ages = empty, + valid_sexes = empty, + indicator_code = paste0(indicator_code, ".KP")) + + ## EID +Year2_EID <- Year2 %>% + dplyr::filter(!is.na(dataelement_dsd_EID)) %>% + dplyr::select(-dataelement_dsd, -dataelement_dsd_KP, + -categoryoption_specified, -categoryoption_specified_KP) %>% + dplyr::rename(dataelement_dsd = dataelement_dsd_EID, + categoryoption_specified = categoryoption_specified_EID) %>% + dplyr::mutate(valid_ages = empty, + valid_sexes = empty, + valid_kps = empty, + indicator_code = paste0(indicator_code, ".EID")) %>% + tidyr::separate_longer_delim(categoryoption_specified, ".") %>% + dplyr::mutate( + indicator_code = + dplyr::case_when( + categoryoption_specified == "J4SQd7SnDi2" ~ paste0(indicator_code, ".2"), + categoryoption_specified == "pbXCUjm50XK" ~ paste0(indicator_code, ".12"), + TRUE ~ categoryoption_specified)) + + ## Recombine +Year2 %<>% + dplyr::select(-dataelement_dsd_EID, -dataelement_dsd_KP, + -categoryoption_specified_EID, + -categoryoption_specified_KP) %>% + dplyr::filter(!is.na(dataelement_dsd)) %>% + dplyr::mutate( + valid_kps = dplyr::case_when( + indicator_code == "KP_PREV.T2" ~ valid_kps, + TRUE ~ empty)) %>% + dplyr::bind_rows(Year2_KP, Year2_EID) + +dp_map %<>% + dplyr::filter(sheet_name != "Year 2") %>% + dplyr::bind_rows(Year2) + + +## Unfold Age, Sex, KP disaggs ---- +dp_map %<>% tidyr::unnest(cols = valid_ages, names_sep = ".") %>% tidyr::unnest(cols = valid_sexes, names_sep = ".") %>% tidyr::unnest(cols = valid_kps, names_sep = ".") %>% @@ -128,10 +244,11 @@ dp_map %<>% ## Allow aggregation of OVC_HIVSTAT #### dp_map %<>% - dplyr::mutate_at(c("valid_ages.name", "valid_ages.id", "valid_sexes.name", "valid_sexes.id"), - ~dplyr::case_when(indicator_code == "OVC_HIVSTAT.T" - ~ NA_character_, - TRUE ~ .)) %>% + dplyr::mutate_at( + c("valid_ages.name", "valid_ages.id", "valid_sexes.name", "valid_sexes.id"), + ~dplyr::case_when(indicator_code %in% c("OVC_HIVSTAT.T", "OVC_HIVSTAT.T2") + ~ NA_character_, + TRUE ~ .)) %>% dplyr::distinct() ## Combine all categoryOptions into a joinable list. #### @@ -171,7 +288,7 @@ dp_map %<>% # Join Full Code List with Schema #### dp_map %<>% dplyr::select(-dataset) %>% - dplyr::full_join(fullCodeList, + dplyr::inner_join(fullCodeList, by = c("dataelementuid" = "dataelementuid", "categoryOptions.ids" = "categoryOptions.ids", "period" = "period", @@ -339,14 +456,7 @@ compare_diffs <- datapackr::cop23_map_DataPack_DATIM_DEs_COCs %>% waldo::compare(datapackr::cop23_map_DataPack_DATIM_DEs_COCs, dp_map) -# Expected changes from COP21: -# - finer age bands on DATIM side for TX_CURR only -# - finer age bands on DP side, mapped to 50+ on DATIM side -# - PrEP_CT instead of PrEP_CURR -# - New SNS modality in HTS_TST and HTS_RECENT -# - No need to remap PMTCT_STAT_SUBNAT in weird ways -# - AGYW_PREV listed as "dreams" dataset instead of "mer" -# - Updates to DEGS for HTS modalities and top level to reflect FY23 targets changes + cop23_map_DataPack_DATIM_DEs_COCs <- dp_map save(cop23_map_DataPack_DATIM_DEs_COCs, file = "./data/cop23_map_DataPack_DATIM_DEs_COCs.rda", compress = "xz") diff --git a/data-raw/cop_validation_rules.R b/data-raw/cop_validation_rules.R index 72fb801f2..7997f2b93 100644 --- a/data-raw/cop_validation_rules.R +++ b/data-raw/cop_validation_rules.R @@ -4,7 +4,7 @@ require(jsonlite) require(purrr) require(dplyr) require(magrittr) - +# # paste0("https://www.datim.org/api/validationRules.json? # filter=name:like:TARGET&fields=id,name,periodType,description,operator, # leftSide[expression,missingValueStrategy], @@ -47,11 +47,11 @@ processValidationRules <- function(r) { } -cop21 <- processValidationRules("./data-raw/cop21_validation_rules.json") -cop22 <- processValidationRules("./data-raw/cop22_validation_rules.json") %>% +cop21 <- processValidationRules("./data-raw/COP21/cop21_validation_rules.json") +cop22 <- processValidationRules("./data-raw/COP22/cop22_validation_rules.json") %>% dplyr::filter(id != "h6ACV56qnvz") # Patch for DP-552 +cop23 <- processValidationRules("./data-raw/COP23/cop23_validation_rules.json") - -cop_validation_rules <- list("2021" = cop21, "2022" = cop22) +cop_validation_rules <- list("2021" = cop21, "2022" = cop22, "2023" = cop23) usethis::use_data(cop_validation_rules, overwrite = TRUE) diff --git a/data/cop22_data_pack_schema.rda b/data/cop22_data_pack_schema.rda index 2fe15f074..8486534c2 100644 Binary files a/data/cop22_data_pack_schema.rda and b/data/cop22_data_pack_schema.rda differ diff --git a/data/cop23_data_pack_schema.rda b/data/cop23_data_pack_schema.rda index c0f126a47..b41109bd1 100644 Binary files a/data/cop23_data_pack_schema.rda and b/data/cop23_data_pack_schema.rda differ diff --git a/data/cop23_map_DataPack_DATIM_DEs_COCs.rda b/data/cop23_map_DataPack_DATIM_DEs_COCs.rda index 5ef201ea5..755e6c841 100644 Binary files a/data/cop23_map_DataPack_DATIM_DEs_COCs.rda and b/data/cop23_map_DataPack_DATIM_DEs_COCs.rda differ diff --git a/data/cop_validation_rules.rda b/data/cop_validation_rules.rda index 76b340e19..c4f10aa4b 100644 Binary files a/data/cop_validation_rules.rda and b/data/cop_validation_rules.rda differ diff --git a/inst/extdata/COP23_Data_Pack_Template.xlsx b/inst/extdata/COP23_Data_Pack_Template.xlsx index ae6b49cdb..8d6f317ab 100644 Binary files a/inst/extdata/COP23_Data_Pack_Template.xlsx and b/inst/extdata/COP23_Data_Pack_Template.xlsx differ diff --git a/man/add_dp_label.Rd b/man/add_dp_label.Rd index 6ee8b1b9e..a0177c738 100644 --- a/man/add_dp_label.Rd +++ b/man/add_dp_label.Rd @@ -4,10 +4,13 @@ \alias{add_dp_label} \title{Modify Data Pack Org Unit list to add datapackr IDs.} \usage{ -add_dp_label(orgunits) +add_dp_label(orgunits, cop_year) } \arguments{ \item{orgunits}{Data frame of Data Pack org units produced by \code{\link{getDataPackOrgUnits}}.} + +\item{cop_year}{COP Year. For COP years less than 2023, +the organisation unit type will be added to tbe DP label} } \value{ Data frame of Data Pack Org units with added Data Pack label, \code{dp_label}. diff --git a/man/checkPSNUxIMDisaggs.Rd b/man/checkPSNUxIMDisaggs.Rd index 7a0e69b0f..7ea54590d 100644 --- a/man/checkPSNUxIMDisaggs.Rd +++ b/man/checkPSNUxIMDisaggs.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/unPackingChecks.R +% Please edit documentation in R/unPackSNUxIM.R \name{checkPSNUxIMDisaggs} \alias{checkPSNUxIMDisaggs} \title{Title checkPSNUxIMDisaggs} diff --git a/man/getMapDataPack_DATIM_DEs_COCs.Rd b/man/getMapDataPack_DATIM_DEs_COCs.Rd index 318cc7e60..4c03e0b3b 100644 --- a/man/getMapDataPack_DATIM_DEs_COCs.Rd +++ b/man/getMapDataPack_DATIM_DEs_COCs.Rd @@ -12,7 +12,7 @@ getMapDataPack_DATIM_DEs_COCs(cop_year, datasource = NULL) \item{datasource}{Type of datasource (Data Pack, OPU Data Pack, DATIM)} } \value{ -{cop21, cop22}_map_DataPack_DATIM_DEs_COCs +{cop21, cop22, cop23}_map_DataPack_DATIM_DEs_COCs } \description{ get_Map_DataPack_DATIM_DEs_COCs diff --git a/man/getValidOrgUnits.Rd b/man/getValidOrgUnits.Rd index ddb79eb3f..feb4c0c27 100644 --- a/man/getValidOrgUnits.Rd +++ b/man/getValidOrgUnits.Rd @@ -5,7 +5,7 @@ \title{Return a data frame of valid organisation units based on the COP year} \usage{ -getValidOrgUnits(cop_year = NA) +getValidOrgUnits(cop_year = NULL) } \arguments{ \item{cop_year}{The COP Year} diff --git a/man/packDataPack.Rd b/man/packDataPack.Rd index cb3c5c7cc..97dff5a99 100644 --- a/man/packDataPack.Rd +++ b/man/packDataPack.Rd @@ -7,6 +7,7 @@ packDataPack( d, model_data = NULL, + spectrum_data = NULL, d2_session = dynGet("d2_default_session", inherits = TRUE) ) } diff --git a/man/packTool.Rd b/man/packTool.Rd index 695a954a5..17fd658a7 100644 --- a/man/packTool.Rd +++ b/man/packTool.Rd @@ -17,6 +17,7 @@ packTool( output_folder, results_archive = TRUE, expand_formulas = FALSE, + spectrum_data = NULL, d2_session = dynGet("d2_default_session", inherits = TRUE) ) } diff --git a/man/unpackYear2Sheet.Rd b/man/unpackYear2Sheet.Rd new file mode 100644 index 000000000..4a5e03a3b --- /dev/null +++ b/man/unpackYear2Sheet.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/unPackYear2Sheet.R +\name{unpackYear2Sheet} +\alias{unpackYear2Sheet} +\title{Title unpackYear2Sheet} +\usage{ +unpackYear2Sheet(d) +} +\arguments{ +\item{d}{} +} +\value{ +d +} +\description{ +Title unpackYear2Sheet +} diff --git a/man/writeSpectrumData.Rd b/man/writeSpectrumData.Rd new file mode 100644 index 000000000..be1366599 --- /dev/null +++ b/man/writeSpectrumData.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/writeSpectrumData.R +\name{writeSpectrumData} +\alias{writeSpectrumData} +\title{Title writeSpectrumData} +\usage{ +writeSpectrumData(wb, spectrum_data) +} +\arguments{ +\item{wb}{Openxlsx worbook object} + +\item{spectrum_data}{Spectrum data} +} +\value{ +Modified openxlsx worbook object with Spectrum data written to the +Spectrum tab. +} +\description{ +Utility function mostly for testing which will +write Spectrum data in a defined format to the Spectrum tab. +} diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index b0298c080..8f07dae88 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -1,6 +1,25 @@ test_sheet <- function(fname) testthat::test_path("sheets", fname) -test_config <- function(fname) rprojroot::find_testthat_root_file("config", fname) +test_config <- function(fname) { + rprojroot::find_testthat_root_file("config", fname) +} + +onCI <- isTRUE(as.logical(Sys.getenv("CI"))) + +getTemplate <- function(path) { + ifelse( + onCI, + paste0("/root/project/inst/extdata/", path), + rprojroot::find_package_root_file(paste0("inst/extdata/", path))) +} + +getRDA <- function(object_name) { + ifelse( + onCI, + paste0("/root/project/data/", object_name, ".rda"), + rprojroot::find_package_root_file(paste0("data/", object_name, ".rda"))) + +} # login object stubs sufficient for use in mocked api calls # one needed for each server with mock calls diff --git a/tests/testthat/sheets/COP21_DP_random_no_psnuxim.xlsx b/tests/testthat/sheets/COP21_DP_random_no_psnuxim.xlsx deleted file mode 100644 index f0a7803d6..000000000 Binary files a/tests/testthat/sheets/COP21_DP_random_no_psnuxim.xlsx and /dev/null differ diff --git a/tests/testthat/sheets/COP21_Data_Pack_Template.xlsx b/tests/testthat/sheets/COP21_Data_Pack_Template.xlsx deleted file mode 100644 index 90395a369..000000000 Binary files a/tests/testthat/sheets/COP21_Data_Pack_Template.xlsx and /dev/null differ diff --git a/tests/testthat/sheets/COP21_OPU_Data_Pack_Template.xlsx b/tests/testthat/sheets/COP21_OPU_Data_Pack_Template.xlsx deleted file mode 100644 index fcdfea1a9..000000000 Binary files a/tests/testthat/sheets/COP21_OPU_Data_Pack_Template.xlsx and /dev/null differ diff --git a/tests/testthat/sheets/COP23_Data_Pack_Template.xlsx b/tests/testthat/sheets/COP23_Data_Pack_Template.xlsx deleted file mode 100644 index f6050c0f6..000000000 Binary files a/tests/testthat/sheets/COP23_Data_Pack_Template.xlsx and /dev/null differ diff --git a/tests/testthat/sheets/COP23_Data_Pack_Template_v1.xlsx b/tests/testthat/sheets/COP23_Data_Pack_Template_v1.xlsx new file mode 100644 index 000000000..7599d0925 Binary files /dev/null and b/tests/testthat/sheets/COP23_Data_Pack_Template_v1.xlsx differ diff --git a/tests/testthat/sheets/COP23_datapack_model_data_random_MW.rds b/tests/testthat/sheets/COP23_datapack_model_data_random_MW.rds new file mode 100644 index 000000000..1f762f4e8 Binary files /dev/null and b/tests/testthat/sheets/COP23_datapack_model_data_random_MW.rds differ diff --git a/tests/testthat/sheets/COP23_sample_DataPack_Malawi.xlsx b/tests/testthat/sheets/COP23_sample_DataPack_Malawi.xlsx new file mode 100644 index 000000000..4144ce463 Binary files /dev/null and b/tests/testthat/sheets/COP23_sample_DataPack_Malawi.xlsx differ diff --git a/tests/testthat/sheets/COP23_spectrum_data_random_MW.rds b/tests/testthat/sheets/COP23_spectrum_data_random_MW.rds new file mode 100644 index 000000000..5fb356f7a Binary files /dev/null and b/tests/testthat/sheets/COP23_spectrum_data_random_MW.rds differ diff --git a/tests/testthat/sheets/datapack_model_data.rds b/tests/testthat/sheets/datapack_model_data.rds deleted file mode 100644 index 5d744e2fd..000000000 Binary files a/tests/testthat/sheets/datapack_model_data.rds and /dev/null differ diff --git a/tests/testthat/test-checkAnalytics.R b/tests/testthat/test-checkAnalytics.R index e3b7fa4b5..ede01b5b3 100644 --- a/tests/testthat/test-checkAnalytics.R +++ b/tests/testthat/test-checkAnalytics.R @@ -1,6 +1,7 @@ context("test-check-analytics") test_that("PMTCT_EID coverage by 2 months old < 90% expect message", { + data <- tribble( ~psnu, ~psnu_uid, ~age, ~sex, ~key_population, ~PMTCT_EID.N.2.T, ~PMTCT_EID.N.12.T, "a", 1, "<1", "F", NA, 1, 100, @@ -26,6 +27,20 @@ test_that("PMTCT_EID coverage by 2 months old < 90% all zeros expect NULL", { }) +test_that("PMTCT_EID coverage by 2 months missing data", { + data <- tribble( + ~psnu, ~psnu_uid, ~age, ~sex, ~key_population, ~PMTCT_EID.N.2.T, + "a", 1, "<1", "F", NA, 0 + ) + + foo <- analyze_eid_2mo(data) + testthat::expect_equal(class(foo), "list") + testthat::expect_setequal(names(foo), c("test_results", "msg")) + testthat::expect_equal(NROW(foo$test_results), 1) + expect_equal(foo$test_results$msg, "Missing data.") + +}) + test_that("PMTCT_EID coverage by 2 months old > 90% expect NULL", { data <- tribble( ~psnu, ~psnu_uid, ~age, ~sex, ~key_population, ~PMTCT_EID.N.2.T, ~PMTCT_EID.N.12.T, @@ -52,6 +67,21 @@ test_that("VMMC_CIRC Indeterminate Rate < 5% expect message", { }) +test_that("VMMC_CIRC Indeterminate Rate > 5% missing data expect NULL", { + data <- tribble( + ~psnu, ~psnu_uid, ~age, ~sex, ~key_population, ~VMMC_CIRC.Pos.T, ~VMMC_CIRC.Neg.T, + "a", 1, "<1", "M", NA, 1, 100, + "b", 2, "<1", "M", NA, 0, 0 + ) + + foo <- analyze_vmmc_indeterminate(data) + testthat::expect_equal(class(foo), "list") + testthat::expect_setequal(names(foo), c("test_results", "msg")) + testthat::expect_equal(NROW(foo$test_results), 1) + expect_equal(foo$test_results$msg, "Missing data.") + +}) + test_that("VMMC_CIRC Indeterminate Rate > 5% expect NULL", { data <- tribble( ~psnu, ~psnu_uid, ~age, ~sex, ~key_population, ~VMMC_CIRC.Pos.T, ~VMMC_CIRC.Neg.T, ~VMMC_CIRC.Unk.T, @@ -101,6 +131,22 @@ test_that("PMTCT Known Pos/PMTCT Total > 0.75 expect message", { testthat::expect_equal(NROW(foo$test_results), 1) expect_equal(foo$test_results$knownpos_ratio, 0.833, tolerance = 1e-3) +}) + +test_that("PMTCT Known Pos/PMTCT Total > 0.75 missing data", { + data <- tribble( + ~psnu, ~psnu_uid, ~age, ~sex, ~key_population, + ~PMTCT_STAT.N.New.Pos.T, ~PMTCT_STAT.N.KnownPos.T, + "a", 1, "<1", "M", NA, 10, 100, + "b", 2, "<1", "M", NA, 0, 0 + ) + + foo <- analyze_pmtctknownpos(data) + testthat::expect_equal(class(foo), "list") + testthat::expect_setequal(names(foo), c("test_results", "msg")) + testthat::expect_equal(NROW(foo$test_results), 1) + expect_equal(foo$test_results$msg, "Missing data.") + }) @@ -145,6 +191,21 @@ test_that("TB Known Pos ratio > 75% expect message", { }) +test_that("TB Known Pos ratio > 75% expect message", { + data <- tribble( + ~psnu, ~psnu_uid, ~age, ~sex, ~key_population, ~TB_STAT.N.New.Pos.T, ~TB_STAT.N.KnownPos.T, + "a", 1, "<1", "M", NA, 25, 151, + "b", 2, "<1", "M", NA, 0, 0 + ) + + foo <- analyze_tbknownpos(data) + testthat::expect_equal(class(foo), "list") + testthat::expect_setequal(names(foo), c("test_results", "msg")) + testthat::expect_equal(NROW(foo$test_results), 1) + expect_equal(foo$test_results$msg, "Missing data.") + +}) + test_that("TB Known Pos ratio < 75% expect message expect null", { data <- tribble( ~psnu, ~psnu_uid, ~age, ~sex, ~key_population, ~TB_STAT.N.New.Pos.T, ~TB_STAT.N.KnownPos.T, ~TB_STAT.N.New.Neg.T, @@ -182,6 +243,21 @@ test_that(" Test retention < 98% expect message", { }) +test_that(" Test retention < 98% missing required data", { + data <- tribble( + ~psnu, ~psnu_uid, ~age, ~sex, ~key_population, ~TX_CURR.T, ~TX_CURR.T_1, + "a", 1, "<1", "F", NA, 97, 97, + "b", 2, "<1", "M", NA, 0, 0 + ) + + foo <- analyze_retention(data) + testthat::expect_equal(class(foo), "list") + testthat::expect_setequal(names(foo), c("test_results", "msg")) + testthat::expect_equal(NROW(foo$test_results), 1) + expect_equal(foo$test_results$msg, "Missing data.") + +}) + test_that(" Test retention > 100% expect message", { data <- tribble( ~psnu, ~psnu_uid, ~age, ~sex, ~key_population, ~TX_CURR.T, ~TX_CURR.T_1, ~TX_NEW.T, ~cop_year, @@ -234,6 +310,23 @@ test_that(" Test linkage < 95% expect message", { expect_equal(foo$test_results$HTS_TST.Linkage.T, 0.94, tolerance = 1e-3) }) + +test_that(" Test linkage < 95% missing data", { + data <- tribble( + ~psnu, ~psnu_uid, ~age, ~sex, ~key_population, ~HTS_INDEX_COM.New.Pos.T, + ~HTS_INDEX_FAC.New.Pos.T, ~TX_NEW.T, ~HTS_TST.KP.Pos.T, ~cop_year, + "a", 1, "25-49", "F", NA, 95, 5, 94, 0, 2021, + "b", 2, "25-49", "M", NA, 95, 5, 95, 0, 2021 + ) + + foo <- analyze_linkage(data) + testthat::expect_equal(class(foo), "list") + testthat::expect_setequal(names(foo), c("test_results", "msg")) + testthat::expect_equal(NROW(foo$test_results), 1) + expect_equal(foo$test_results$msg, "Missing data.") +}) + + test_that(" Test KP linkage < 95% expect message", { data <- tribble( ~psnu, ~psnu_uid, ~age, ~sex, ~key_population, ~HTS_INDEX_COM.New.Pos.T, @@ -315,7 +408,24 @@ test_that(" Test linkage all zeros expect NULL", { }) +test_that(" Test index pos ratio missing data", { + + data <- tribble( + ~psnu, ~psnu_uid, ~age, ~sex, ~key_population, ~HTS_INDEX_COM.New.Pos.T, + ~HTS_INDEX_FAC.New.Pos.T, ~HTS_TST.PostANC1.Pos.T, ~TX_CURR_SUBNAT.T_1, ~cop_year, + "a", 1, "25-49", "F", NA, 5, 5, 100, 5, 2022 + + ) + + foo <- analyze_indexpos_ratio(data) + testthat::expect_equal(class(foo), "list") + testthat::expect_setequal(names(foo), c("test_results", "msg")) + testthat::expect_equal(NROW(foo$test_results), 1) + expect_equal(foo$test_results$msg, "Missing data.") +}) + test_that(" Test index pos ratio", { + data <- tribble( ~psnu, ~psnu_uid, ~age, ~sex, ~key_population, ~HTS_INDEX_COM.New.Pos.T, ~HTS_INDEX_FAC.New.Pos.T, ~HTS_TST.PostANC1.Pos.T, ~TX_CURR_SUBNAT.T_1, ~PLHIV.T_1, ~cop_year, diff --git a/tests/testthat/test-create-analytics.R b/tests/testthat/test-create-analytics.R index 163849310..8db676f8f 100644 --- a/tests/testthat/test-create-analytics.R +++ b/tests/testthat/test-create-analytics.R @@ -2,11 +2,11 @@ context("Analytics creation tests") with_mock_api({ - test_that("We can create analytics", { + test_that("We can create analytics for a COP23 Data Pack", { d <- loadDataPack( - submission_path = test_sheet("COP21_DP_random_with_psnuxim.xlsx"), + submission_path = test_sheet("COP23_sample_DataPack_Malawi.xlsx"), tool = "Data Pack", country_uids = NULL, cop_year = NULL, @@ -15,13 +15,11 @@ with_mock_api({ d %<>% unPackSheets(., check_sheets = FALSE) %>% - unPackSNUxIM(.) %>% packForDATIM(., type = "Undistributed MER") %>% - packForDATIM(., type = "SUBNAT_IMPATT") %>% - packForDATIM(., type = "PSNUxIM") + packForDATIM(., type = "SUBNAT_IMPATT") expect_named(d, - c("keychain", "info", "tests", "sheets", "data", "datim"), + c("keychain", "info", "sheets", "data", "datim"), ignore.order = TRUE) d %<>% createAnalytics(d2_session = training) diff --git a/tests/testthat/test-create-schema.R b/tests/testthat/test-create-schema.R index 0d5951f7e..bdf615e6e 100644 --- a/tests/testthat/test-create-schema.R +++ b/tests/testthat/test-create-schema.R @@ -8,7 +8,7 @@ Sys.setlocale(category = "LC_COLLATE", locale = "en_US.UTF-8") with_mock_api({ test_that("We can create a COP21 OPU Data Pack Schema", { - template_file <- file.path(system.file("extdata", package = "datapackr"), "COP21_OPU_Data_Pack_Template.xlsx") + template_file <- getTemplate("COP21_OPU_Data_Pack_Template.xlsx") expect_true(file.exists(template_file)) test_dataset <- unPackSchema( template_path = template_file, @@ -56,7 +56,6 @@ with_mock_api({ expect_type(test_dataset$formula, "character") expect_type(test_dataset$FY, "double") expect_type(test_dataset$period, "character") - #skip("Why does the COP21 OPU template not match the template?") expect_identical(test_dataset, cop21OPU_data_pack_schema) @@ -68,9 +67,9 @@ with_mock_api({ with_mock_api({ test_that("We can create a COP22 OPU DataPack Schema", { - template_file <- file.path(system.file("extdata", package = "datapackr"), "COP22_OPU_Data_Pack_Template.xlsx") + template_file <- getTemplate("COP22_OPU_Data_Pack_Template.xlsx") expect_true(file.exists(template_file)) - test_dataset <- test_dataset <- unPackSchema( + test_dataset <- unPackSchema( template_path = template_file, skip = skip_tabs(tool = "OPU Data Pack Template", cop_year = 2022), tool = "OPU Data Pack Template", @@ -127,12 +126,11 @@ with_mock_api({ with_mock_api({ test_that("We can create a COP22 DataPack Schema", { - skip("Schema creation test failing for unknown reasons") - template_file <- file.path(system.file("extdata", package = "datapackr"), "COP22_Data_Pack_Template.xlsx") + + template_file <- getTemplate("COP22_Data_Pack_Template.xlsx") expect_true(file.exists(template_file)) - test_dataset <- unPackSchema( + test_dataset <- unPackSchema( template_path = template_file, - skip = skip_tabs(tool = "Data Pack Template", cop_year = 2022), cop_year = 2022) expect_type(test_dataset, "list") expect_named( @@ -175,8 +173,126 @@ with_mock_api({ expect_type(test_dataset$formula, "character") expect_type(test_dataset$FY, "double") expect_type(test_dataset$period, "character") + load(getRDA("cop22_data_pack_schema")) expect_identical(test_dataset, cop22_data_pack_schema) + }) + +}) + + + +with_mock_api({ + test_that("We can create a COP23 DataPack Schema", { + template_file <- getTemplate("COP23_Data_Pack_Template.xlsx") + expect_true(file.exists(template_file)) + test_dataset <- unPackSchema( + template_path = template_file, + skip = skip_tabs(tool = "Data Pack Template", cop_year = 2023), + cop_year = 2023) + expect_type(test_dataset, "list") + expect_named( + test_dataset, + c( + "sheet_num", + "sheet_name", + "data_structure", + "col", + "indicator_code", + "dataset", + "col_type", + "value_type", + "dataelement_dsd", + "dataelement_ta", + "categoryoption_specified", + "valid_ages", + "valid_sexes", + "valid_kps", + "formula", + "FY", + "period" + ), + ignore.order = TRUE + ) + expect_type(test_dataset$sheet_num, "integer") + expect_type(test_dataset$sheet_name, "character") + expect_type(test_dataset$data_structure, "character") + expect_type(test_dataset$col, "integer") + expect_type(test_dataset$indicator_code, "character") + expect_type(test_dataset$dataset, "character") + expect_type(test_dataset$col_type, "character") + expect_type(test_dataset$value_type, "character") + expect_type(test_dataset$dataelement_dsd, "character") + expect_type(test_dataset$dataelement_ta, "character") + expect_type(test_dataset$categoryoption_specified, "character") + expect_type(test_dataset$valid_ages, "list") + expect_type(test_dataset$valid_sexes, "list") + expect_type(test_dataset$valid_kps, "list") + expect_type(test_dataset$formula, "character") + expect_type(test_dataset$FY, "double") + expect_type(test_dataset$period, "character") + load(getRDA("cop23_data_pack_schema")) + expect_identical(test_dataset, cop23_data_pack_schema) + + + }) + +}) + + + +with_mock_api({ + test_that("Can detect when template and schema do not match", { + template_file <- test_sheet("COP23_Data_Pack_Template_v1.xlsx") + expect_true(file.exists(template_file)) + test_dataset <- unPackSchema( + template_path = template_file, + skip = skip_tabs(tool = "Data Pack Template", cop_year = 2023), + cop_year = 2023) + expect_type(test_dataset, "list") + expect_named( + test_dataset, + c( + "sheet_num", + "sheet_name", + "data_structure", + "col", + "indicator_code", + "dataset", + "col_type", + "value_type", + "dataelement_dsd", + "dataelement_ta", + "categoryoption_specified", + "valid_ages", + "valid_sexes", + "valid_kps", + "formula", + "FY", + "period" + ), + ignore.order = TRUE + ) + expect_type(test_dataset$sheet_num, "integer") + expect_type(test_dataset$sheet_name, "character") + expect_type(test_dataset$data_structure, "character") + expect_type(test_dataset$col, "integer") + expect_type(test_dataset$indicator_code, "character") + expect_type(test_dataset$dataset, "character") + expect_type(test_dataset$col_type, "character") + expect_type(test_dataset$value_type, "character") + expect_type(test_dataset$dataelement_dsd, "character") + expect_type(test_dataset$dataelement_ta, "character") + expect_type(test_dataset$categoryoption_specified, "character") + expect_type(test_dataset$valid_ages, "list") + expect_type(test_dataset$valid_sexes, "list") + expect_type(test_dataset$valid_kps, "list") + expect_type(test_dataset$formula, "character") + expect_type(test_dataset$FY, "double") + expect_type(test_dataset$period, "character") + load(getRDA("cop23_data_pack_schema")) + expect_false(identical(test_dataset, cop23_data_pack_schema)) + }) diff --git a/tests/testthat/test-createCOP21OPU.R b/tests/testthat/test-createCOP21OPU.R index c7cddefa5..7385a38a1 100644 --- a/tests/testthat/test-createCOP21OPU.R +++ b/tests/testthat/test-createCOP21OPU.R @@ -2,7 +2,7 @@ context("Create a COP21 OPU") with_mock_api({ test_that("We can write an COP21 OPU tool", { - + skip("COP21 OPUs are deprecated.") # For Generating Individual Data Packs #### generation_list <- c("Burundi") diff --git a/tests/testthat/test-generateCOP23DataPack.R b/tests/testthat/test-generateCOP23DataPack.R index a555aaa10..fe5a980e1 100644 --- a/tests/testthat/test-generateCOP23DataPack.R +++ b/tests/testthat/test-generateCOP23DataPack.R @@ -3,8 +3,12 @@ context("Create a COP23 Target Setting Tool") with_mock_api({ test_that("We can write an COP23 Target Setting tool", { + template_path <- getTemplate("COP23_Data_Pack_Template.xlsx") + + expect_true(file.exists(template_path)) + # For Generating Individual Data Packs #### - generation_list <- c("Eswatini") + generation_list <- c("Malawi") pick <- datapackr::COP21_datapacks_countries %>% dplyr::filter(datapack_name %in% generation_list) %>% @@ -15,24 +19,57 @@ with_mock_api({ #Suppress console output - d <- packTool(model_data_path = test_sheet("COP23_model_data_random.rds"), + spectrum_data <- readRDS(test_sheet("COP23_spectrum_data_random_MW.rds")) + + d <- packTool(model_data_path = test_sheet("COP23_datapack_model_data_random_MW.rds"), tool = "Data Pack", datapack_name = pick$datapack_name[1], country_uids = unlist(pick$country_uids[1]), - template_path = test_sheet("COP23_Data_Pack_Template.xlsx"), + template_path = template_path, cop_year = 2023, output_folder = output_folder, results_archive = FALSE, expand_formulas = TRUE, + spectrum_data = spectrum_data, d2_session = training) expect_setequal(names(d), c("keychain", "info", "tool", "data")) - expect_equal("Eswatini", d$info$datapack_name) + expect_equal("Malawi", d$info$datapack_name) + + #Open the generated tool in libreoffice to kick off the formulas + #Do not even try and do this on Windows + skip_if(Sys.info()["sysname"] == "Windows") + + #MacOS users will need to install LibreOffice + lo_path <- ifelse(Sys.info()["sysname"] == "Darwin", + "/Applications/LibreOffice.app/Contents/MacOS/soffice", + #Needs to be relative, but can't figure out terminal command + #got to ls /Applications/ | grep -i libre + system("which libreoffice", intern = TRUE)) + + #Skip this if we cannot execute libreoffice + skip_if(file.access(lo_path, 1) != 0) + + out_dir <- paste0(output_folder, "/out") + dir.create(out_dir) + + Sys.setenv(LD_LIBRARY_PATH = ifelse(Sys.info()["sysname"] == "Darwin", + "/Applications/LibreOffice.app/Contents/MacOS/soffice", + "/usr/lib/libreoffice/program/")) + sys_command <- paste0(ifelse(Sys.info()["sysname"] == "Darwin", + "/Applications/LibreOffice.app/Contents/MacOS/soffice", + "libreoffice"), + " --headless --convert-to xlsx --outdir ", out_dir, " '", d$info$output_file, "'") + system(sys_command) - skip("Need to add Spectrum data perhaps?") - #Be sure we can unpack what we just wrote to a file - d_out <- unPackTool(submission_path = d$info$output_file, d2_session = training) + out_file <- paste0(out_dir, "/", basename(d$info$output_file)) + #Unpack this tool which has been "opened" in libreoffice + d_opened <- unPackTool(submission_path = out_file, d2_session = training) + expect_identical(d$info$datapack_name, d_opened$info$datapack_name) + expect_setequal(names(d_opened), c("keychain", "info", "data", "tests", "datim", "sheets")) + expect_true(NROW(d_opened$data$analytics) > 0) + expect_true(all(d_opened$data$analytics$mechanism_desc == "default")) }) }) diff --git a/tests/testthat/test-get-datapack-name.R b/tests/testthat/test-get-datapack-name.R index 18cfb67d1..842bb152f 100644 --- a/tests/testthat/test-get-datapack-name.R +++ b/tests/testthat/test-get-datapack-name.R @@ -2,7 +2,7 @@ context("test-get-datapack-name") test_that("Can read a Datapack Name and UIDs", { template_copy <- paste0(tempfile(), ".xlsx") - file.copy(from = test_sheet("COP21_Data_Pack_Template.xlsx"), to = template_copy) + file.copy(from = getTemplate("COP23_Data_Pack_Template.xlsx"), to = template_copy) foo <- unPackDataPackName(submission_path = template_copy, "Data Pack") expect_equal(foo, "Lesotho") @@ -14,7 +14,7 @@ test_that("Can read a Datapack Name and UIDs", { test_that("Can error on an invalid regional DataPack UID", { template_copy <- paste0(tempfile(), ".xlsx") - file.copy(from = test_sheet("COP21_Data_Pack_Template.xlsx"), to = template_copy) + file.copy(from = getTemplate("COP23_Data_Pack_Template.xlsx"), to = template_copy) wb <- openxlsx::loadWorkbook(template_copy) home_address <- cellranger::as.cell_addr(countryUIDs_homeCell(), strict = FALSE) @@ -24,13 +24,13 @@ test_that("Can error on an invalid regional DataPack UID", { openxlsx::saveWorkbook(wb = wb, file = template_copy, overwrite = TRUE) expect_error(unPackCountryUIDs(submission_path = template_copy, tool = "Data Pack", - cop_year = 2021)) + cop_year = 2023)) unlink(template_copy) }) -test_that("Can read a COP21 OPU DataPack name and country_uid", { +test_that("Can read a COP22 OPU DataPack name and country_uid", { template_copy <- paste0(tempfile(), ".xlsx") - file.copy(from = test_sheet("COP21_OPU_Data_Pack_Template.xlsx"), to = template_copy) + file.copy(from = getTemplate("COP22_OPU_Data_Pack_Template.xlsx"), to = template_copy) foo <- unPackDataPackName(submission_path = template_copy, "OPU Data Pack") @@ -42,21 +42,21 @@ test_that("Can read a COP21 OPU DataPack name and country_uid", { unlink(template_copy) }) -test_that("Can parse valid PSNU UIDs for COP21 OPU Datapack", { +test_that("Can parse valid PSNU UIDs for COP23 OPU Datapack", { template_copy <- paste0(tempfile(), ".xlsx") - file.copy(from = test_sheet("COP21_OPU_Data_Pack_Template.xlsx"), to = template_copy) + file.copy(from = getTemplate("COP22_OPU_Data_Pack_Template.xlsx"), to = template_copy) wb <- openxlsx::loadWorkbook(template_copy) openxlsx::writeData(wb = wb, sheet = "PSNUxIM", - x = "Lesotho >Berea [#SNU] [wpg5evyl1OL]", + x = "Lesotho > Berea [#SNU] [wpg5evyl1OL]", startCol = 1, startRow = 15) openxlsx::saveWorkbook(wb = wb, file = template_copy, overwrite = TRUE) - foo <- parsePSNUs(template_copy, "OPU Data Pack", "2021") + foo <- parsePSNUs(template_copy, "OPU Data Pack", "2022") expect_equal(typeof(foo), "list") expect_setequal(names(foo), c("PSNU", "psnu_uid", "country_name", "country_uid")) - expect_equal(foo$PSNU, "Lesotho >Berea [#SNU] [wpg5evyl1OL]") + expect_equal(foo$PSNU, "Lesotho > Berea [#SNU] [wpg5evyl1OL]") expect_equal(foo$psnu_uid, "wpg5evyl1OL") expect_equal(foo$country_name, "Lesotho") expect_equal(foo$country_uid, "qllxzIjjurr") @@ -64,9 +64,9 @@ test_that("Can parse valid PSNU UIDs for COP21 OPU Datapack", { }) -test_that("Can error on invlid PSNU UIDs for COP21 OPU Datapack", { +test_that("Can error on invlid PSNU UIDs for COP22 OPU Datapack", { template_copy <- paste0(tempfile(), ".xlsx") - file.copy(from = test_sheet("COP21_OPU_Data_Pack_Template.xlsx"), to = template_copy) + file.copy(from = getTemplate("COP22_OPU_Data_Pack_Template.xlsx"), to = template_copy) wb <- openxlsx::loadWorkbook(template_copy) openxlsx::writeData(wb = wb, sheet = "PSNUxIM", @@ -75,7 +75,7 @@ test_that("Can error on invlid PSNU UIDs for COP21 OPU Datapack", { startRow = 15) openxlsx::saveWorkbook(wb = wb, file = template_copy, overwrite = TRUE) - expect_error(parsePSNUs(template_copy, "OPU Data Pack", "2021")) + expect_error(parsePSNUs(template_copy, "OPU Data Pack", "2022")) unlink(template_copy) }) diff --git a/tests/testthat/test-handshake.R b/tests/testthat/test-handshake.R index 2b882a228..28c164dcd 100644 --- a/tests/testthat/test-handshake.R +++ b/tests/testthat/test-handshake.R @@ -2,14 +2,14 @@ context("test-handshake") test_that("Can handshake template", { - d <- datapackr:::handshakeFile(path = test_sheet("COP21_Data_Pack_Template.xlsx"), + d <- datapackr:::handshakeFile(path = getTemplate("COP23_Data_Pack_Template.xlsx"), tool = "Data Pack Template") expect_true(file.exists(d)) }) test_that("Can error on bad type", { - expect_error(datapackr:::handshakeFile(test_sheet("COP21_Data_Pack_Template.xlsx"), + expect_error(datapackr:::handshakeFile(getTemplate("COP23_Data_Pack_Template.xlsx"), "Foo Template"), "Please specify correct file type: Data Pack, Data Pack Template, OPU Data Pack Template.") diff --git a/tests/testthat/test-indicators.R b/tests/testthat/test-indicators.R index 3ecf6a688..2591692bb 100644 --- a/tests/testthat/test-indicators.R +++ b/tests/testthat/test-indicators.R @@ -21,7 +21,7 @@ test_that("Can calculate an indicator with totals", { expect_equal(class(test_values), "data.frame") expect_setequal(names(test_values), c("id", "name", "numerator", "denominator", "value")) expect_equal(NROW(test_values), 1) - expect_equal(test_values$value, 0.33333, tolerance = 0.0001) + expect_equal(0.333333, test_values$value, tolerance = 1e-3) expect_equal(test_values$numerator, 10) expect_equal(test_values$denominator, 30) } diff --git a/tests/testthat/test-packDataPack.R b/tests/testthat/test-packDataPack.R index 39ce3c067..2bfd22aef 100644 --- a/tests/testthat/test-packDataPack.R +++ b/tests/testthat/test-packDataPack.R @@ -1,4 +1,4 @@ -context("test-packDataPack") +context("Test Datapack model paramaters") test_that("Can test different combinations of model param passing", { @@ -12,7 +12,7 @@ test_that("Can test different combinations of model param passing", { # have both model_path and model data d <- list() - d$keychain$model_data_path <- test_sheet("datapack_model_data.rds") + d$keychain$model_data_path <- test_sheet("COP23_model_data_random.rds") model_data <- data.frame() testthat::expect_error( packDataPack(d, model_data = model_data), diff --git a/tests/testthat/test-prepareMemoData.R b/tests/testthat/test-prepareMemoData.R index d04316885..763f03280 100644 --- a/tests/testthat/test-prepareMemoData.R +++ b/tests/testthat/test-prepareMemoData.R @@ -30,6 +30,7 @@ with_mock_api({ with_mock_api({ test_that("We can create Datapack memo data", { + testthat_print("Update this to COP23 when possible") d <- loadDataPack( submission_path = test_sheet("COP21_DP_random_with_psnuxim.xlsx"), diff --git a/tests/testthat/test-signature.R b/tests/testthat/test-signature.R index 77db53fd6..7c14b332b 100644 --- a/tests/testthat/test-signature.R +++ b/tests/testthat/test-signature.R @@ -1,8 +1,8 @@ context("test-signature") -test_that("Can generate a key chain", { - d <- createKeychainInfo(submission_path = test_sheet("COP21_Data_Pack_Template.xlsx"), +test_that("Can generate a key chain from a Data Pack Template", { + d <- createKeychainInfo(submission_path = getTemplate("COP23_Data_Pack_Template.xlsx"), tool = "Data Pack", country_uids = NULL, cop_year = NULL, @@ -28,7 +28,7 @@ test_that("Can generate a key chain", { "messages" ) ) - expect_equal(d$keychain$submission_path, test_sheet("COP21_Data_Pack_Template.xlsx")) + expect_equal(d$keychain$submission_path, getTemplate("COP23_Data_Pack_Template.xlsx")) expect_setequal(class(d$info$messages), c("MessageQueue")) expect_false(d$info$has_error) expect_false(d$info$newSNUxIM) @@ -43,14 +43,14 @@ test_that("Can generate a key chain", { expect_false(d$info$missing_DSNUs) expect_false(d$info$missing_psnuxim_combos) expect_false(d$info$unallocatedIMs) - expect_equal(d$info$tool, "Data Pack") - expect_equal(d$info$cop_year, 2021) + expect_equal(d$info$tool, "Data Pack Template") + expect_equal(d$info$cop_year, 2023) expect_false(d$info$needs_psnuxim) }) -test_that("Can get the type and COP year of tool of a COP21 Data Pack", { +test_that("Can get the type and COP year of tool of a COP22 Data Pack", { - d <- datapackr::createKeychainInfo(submission_path = test_sheet("COP21_Data_Pack_Template.xlsx")) + d <- datapackr::createKeychainInfo(submission_path = test_sheet("COP22_DataPack_unPackingChecks.xlsx")) expect_equal(d$info$tool, "Data Pack") - expect_equal(d$info$cop_year, 2021) + expect_equal(d$info$cop_year, 2022) }) diff --git a/tests/testthat/test-unPackSheets.R b/tests/testthat/test-unPackSheets.R index afc414564..ec573c7ac 100644 --- a/tests/testthat/test-unPackSheets.R +++ b/tests/testthat/test-unPackSheets.R @@ -3,7 +3,7 @@ context("test-unPackSheets") test_that("Can load sheets if empty ...", { - d <- loadDataPack(submission_path = test_sheet("COP21_Data_Pack_Template.xlsx"), + d <- loadDataPack(submission_path = test_sheet("COP22_Data_Pack_Template_minimal.xlsx"), tool = "Data Pack", country_uids = NULL, cop_year = NULL, @@ -14,7 +14,7 @@ test_that("Can load sheets if empty ...", { d <- unPackSheets(d) # when d$sheets is explicitly NULL, unPackSheets should call # loadSheets and therefore fix the NULL value - testthat::expect_equal(length(d$sheets), 18) + testthat::expect_equal(length(d$sheets), 17) }) @@ -43,9 +43,9 @@ test_that("Can test sheets are valid...", { "Lilongwe District [#SNU] [ScR9iFKAasW]", "20" ) - # TODO: need to figure out right warning equivalent - # test that interactive warning is produced - # couldn't match warning perfectly so expectation is some warning - testthat::expect_warning(unPackSheets(d, check_sheets = FALSE, sheets = c("Prioritization", "Cascade"))) + #We need to pretend we are interactive to get this warning, otherwise, its + #silent + options(rlang_interactive = TRUE) + expect_warning(unPackSheets(d, check_sheets = FALSE, sheets = c("Prioritization", "Cascade"))) }) diff --git a/tests/testthat/test-unPackingChecks.R b/tests/testthat/test-unPackingChecks.R index 2d9b27952..4e2916c10 100644 --- a/tests/testthat/test-unPackingChecks.R +++ b/tests/testthat/test-unPackingChecks.R @@ -35,7 +35,7 @@ test_that("Can detect invalid comment types ...", { expect_true(d$info$has_error) - d <- loadDataPack(submission_path = test_sheet("COP21_Data_Pack_Template.xlsx"), + d <- loadDataPack(submission_path = getTemplate("COP23_Data_Pack_Template.xlsx"), tool = "Data Pack", country_uids = NULL, cop_year = NULL, @@ -63,7 +63,7 @@ test_that("Can detect external links in a file ...", { }) test_that("Can check Tool structure...", { - d <- loadDataPack(submission_path = test_sheet("COP21_Data_Pack_Template.xlsx"), + d <- loadDataPack(submission_path = getTemplate("COP23_Data_Pack_Template.xlsx"), tool = "Data Pack", country_uids = NULL, cop_year = NULL, @@ -72,7 +72,7 @@ test_that("Can check Tool structure...", { expect_equal(NROW(d$tests$missing_sheets), 0L) template_copy <- paste0(tempfile(), ".xlsx") - file.copy(from = test_sheet("COP21_Data_Pack_Template.xlsx"), to = template_copy) + file.copy(from = getTemplate("COP23_Data_Pack_Template.xlsx"), to = template_copy) wb <- openxlsx::loadWorkbook(template_copy) openxlsx::removeWorksheet(wb, "PMTCT") openxlsx::saveWorkbook(wb, file = template_copy, overwrite = TRUE) diff --git a/tests/testthat/test-unpack-COP21-datapack.R b/tests/testthat/test-unpack-COP21-datapack.R index 7a6a0804d..8e30bcf7f 100644 --- a/tests/testthat/test-unpack-COP21-datapack.R +++ b/tests/testthat/test-unpack-COP21-datapack.R @@ -1,92 +1,91 @@ -context("can-unpack-COP21-datapack") - -d_data_targets_names <- c("PSNU", "psnuid", "sheet_name", "indicator_code", "Age", "Sex", "KeyPop", "value") -d_data_tests_types <- c("tbl_df", "tbl", "data.frame") - -d <- - datapackr::loadDataPack( - submission_path = test_sheet("COP21_DP_random_no_psnuxim.xlsx"), - tool = "Data Pack", - country_uids = NULL, - cop_year = 2021, - load_sheets = TRUE, - d2_session = training) - -d <- unPackSheets(d, check_sheets = TRUE) - -with_mock_api({ - test_that("Can unpack all Data Pack sheets", { - - expect_true(!is.null(d$data$MER)) - expect_setequal(class(d$data$MER), c("tbl_df", "tbl", "data.frame")) - expect_identical(unname(sapply(d$data$MER, typeof)), c(rep("character", 7), "double")) - expect_setequal(names(d$data$MER), d_data_targets_names) - expect_true((NROW(d$data$MER) > 0)) - - expect_true(!is.null(d$data$SUBNAT_IMPATT)) - expect_setequal(class(d$data$SUBNAT_IMPATT), c("tbl_df", "tbl", "data.frame")) - expect_identical(unname(sapply(d$data$SUBNAT_IMPATT, typeof)), c(rep("character", 7), "double")) - expect_setequal(names(d$data$SUBNAT_IMPATT), d_data_targets_names) - expect_true((NROW(d$data$SUBNAT_IMPATT) > 0)) - - # Expect there to be test information - # The test_name attribute should not be null - expect_true(!is.null(d$tests)) - expect_true(all(unlist(lapply(d$tests, function(x) (setequal(class(x), d_data_tests_types)))))) - all(unlist(lapply(d$tests, function(x) !is.null(attr(x, "test_name"))))) - validation_summary <- validationSummary(d) - expect_named(validation_summary, - c("count", "country_name", "country_uid", - "ou", "ou_id", "test_name", "validation_issue_category"), - ignore.order = TRUE) - - # Should throw an error if the tool is an unknown type - d$info$tool <- "FooPack" - expect_error(d <- unPackSheets(d, check_sheets = FALSE)) - }) -}) - -with_mock_api({ - test_that("Can pack Undistributed data for DATIM.", { - - # Package the undistributed data for DATIM - d <- packForDATIM(d, type = "Undistributed MER") - expect_true(!is.null(d$datim$UndistributedMER)) - expect_true(NROW(d$datim$UndistributedMER) > 0) - expect_true(all(unlist( - lapply(d$datim$UndistributedMER$dataElement, is_uidish) - ))) - expect_true(all(unlist( - lapply(d$datim$UndistributedMER$categoryOptionCombo, is_uidish) - ))) - expect_true(all(unlist( - lapply(d$datim$UndistributedMER$period, function(x) { - grepl("^\\d{4}Oct$", x) - }) - ))) - - expect_type(d$datim$UndistributedMER$attributeOptionCombo, "character") - expect_type(d$datim$UndistributedMER$value, "double") - }) -}) - -with_mock_api({ - test_that("Can unpack a COP21 Datapack with PSNUxIM", { - - d <- datapackr::loadDataPack( - submission_path = test_sheet("COP21_DP_random_no_psnuxim.xlsx"), - tool = "Data Pack", - country_uids = NULL, - cop_year = NULL, - load_sheets = TRUE, - d2_session = training) - - d <- unPackDataPack(d, - d2_session = training) - - #Most of this is tested elsewhere, so only test the structure here - expect_named(d, c("keychain", "info", "sheets", "tests", "data", "datim"), ignore.order = TRUE) - - - - })}) +# context("can-unpack-COP21-datapack") +# +# d_data_targets_names <- c("PSNU", "psnuid", "sheet_name", "indicator_code", "Age", "Sex", "KeyPop", "value") +# d_data_tests_types <- c("tbl_df", "tbl", "data.frame") +# +# d <- +# datapackr::loadDataPack( +# submission_path = test_sheet("COP21_DP_random_no_psnuxim.xlsx"), +# tool = "Data Pack", +# country_uids = NULL, +# cop_year = 2021, +# load_sheets = TRUE, +# d2_session = training) +# +# d <- unPackSheets(d, check_sheets = TRUE) +# +# with_mock_api({ +# test_that("Can unpack all Data Pack sheets", { +# expect_true(!is.null(d$data$MER)) +# expect_setequal(class(d$data$MER), c("tbl_df", "tbl", "data.frame")) +# expect_identical(unname(sapply(d$data$MER, typeof)), c(rep("character", 7), "double")) +# expect_setequal(names(d$data$MER), d_data_targets_names) +# expect_true((NROW(d$data$MER) > 0)) +# +# expect_true(!is.null(d$data$SUBNAT_IMPATT)) +# expect_setequal(class(d$data$SUBNAT_IMPATT), c("tbl_df", "tbl", "data.frame")) +# expect_identical(unname(sapply(d$data$SUBNAT_IMPATT, typeof)), c(rep("character", 7), "double")) +# expect_setequal(names(d$data$SUBNAT_IMPATT), d_data_targets_names) +# expect_true((NROW(d$data$SUBNAT_IMPATT) > 0)) +# +# # Expect there to be test information +# # The test_name attribute should not be null +# expect_true(!is.null(d$tests)) +# expect_true(all(unlist(lapply(d$tests, function(x) (setequal(class(x), d_data_tests_types)))))) +# all(unlist(lapply(d$tests, function(x) !is.null(attr(x, "test_name"))))) +# validation_summary <- validationSummary(d) +# expect_named(validation_summary, +# c("count", "country_name", "country_uid", +# "ou", "ou_id", "test_name", "validation_issue_category"), +# ignore.order = TRUE) +# +# # Should throw an error if the tool is an unknown type +# d$info$tool <- "FooPack" +# expect_error(d <- unPackSheets(d, check_sheets = FALSE)) +# }) +# }) +# +# with_mock_api({ +# test_that("Can pack Undistributed data for DATIM.", { +# +# # Package the undistributed data for DATIM +# d <- packForDATIM(d, type = "Undistributed MER") +# expect_true(!is.null(d$datim$UndistributedMER)) +# expect_true(NROW(d$datim$UndistributedMER) > 0) +# expect_true(all(unlist( +# lapply(d$datim$UndistributedMER$dataElement, is_uidish) +# ))) +# expect_true(all(unlist( +# lapply(d$datim$UndistributedMER$categoryOptionCombo, is_uidish) +# ))) +# expect_true(all(unlist( +# lapply(d$datim$UndistributedMER$period, function(x) { +# grepl("^\\d{4}Oct$", x) +# }) +# ))) +# +# expect_type(d$datim$UndistributedMER$attributeOptionCombo, "character") +# expect_type(d$datim$UndistributedMER$value, "double") +# }) +# }) +# +# with_mock_api({ +# test_that("Can unpack a COP21 Datapack with PSNUxIM", { +# +# d <- datapackr::loadDataPack( +# submission_path = test_sheet("COP21_DP_random_no_psnuxim.xlsx"), +# tool = "Data Pack", +# country_uids = NULL, +# cop_year = NULL, +# load_sheets = TRUE, +# d2_session = training) +# +# d <- unPackDataPack(d, +# d2_session = training) +# +# #Most of this is tested elsewhere, so only test the structure here +# expect_named(d, c("keychain", "info", "sheets", "tests", "data", "datim"), ignore.order = TRUE) +# +# +# +# })}) diff --git a/tests/testthat/test-write-home-tab.R b/tests/testthat/test-write-home-tab.R index 9f17a7d5c..41a3839f8 100644 --- a/tests/testthat/test-write-home-tab.R +++ b/tests/testthat/test-write-home-tab.R @@ -1,11 +1,11 @@ -context("test-write-home-tab") +context("Write a DataPack Home tab") test_that("Can write a home tab", { template_copy <- paste0(tempfile(), ".xlsx") - file.copy(from = test_sheet("COP21_Data_Pack_Template.xlsx"), to = template_copy) + file.copy(from = getTemplate("COP23_Data_Pack_Template.xlsx"), to = template_copy) wb <- openxlsx::loadWorkbook(template_copy) openxlsx::removeWorksheet(wb, "Home") - datapackr::writeHomeTab(wb, datapack_name = "Lesotho", country_uids = "qllxzIjjurr", cop_year = 2021) + datapackr::writeHomeTab(wb, datapack_name = "Lesotho", country_uids = "qllxzIjjurr", cop_year = 2023) openxlsx::saveWorkbook(wb, file = template_copy, overwrite = TRUE) d <- datapackr::createKeychainInfo(template_copy) testthat::expect_setequal(names(d), c("info", "keychain")) @@ -44,8 +44,8 @@ test_that("Can write a home tab", { expect_false(d$info$missing_DSNUs) expect_false(d$info$missing_psnuxim_combos) expect_false(d$info$unallocatedIMs) - expect_equal(d$info$tool, "Data Pack") - expect_equal(d$info$cop_year, 2021) + expect_equal(d$info$tool, "Data Pack Template") + expect_equal(d$info$cop_year, 2023) expect_false(d$info$needs_psnuxim) unlink(template_copy) })