From 679005ce3aa5bcd74ca33c5074c3024a4834a163 Mon Sep 17 00:00:00 2001 From: Spencer Childress Date: Fri, 13 Jan 2023 16:07:12 -0500 Subject: [PATCH 1/4] add support for SDTM --- R/getDefaultData.R | 17 +++++++++++ R/getDefaultSettings.R | 63 +++++++++++++++++++++++++++++++++++++++ R/getTiming.R | 60 +++++++++++++++++++++++++++++++++++++ R/profileApp.R | 67 +++++++++++++++++++++++------------------- R/profile_server.R | 5 +--- 5 files changed, 177 insertions(+), 35 deletions(-) create mode 100644 R/getDefaultData.R create mode 100644 R/getDefaultSettings.R create mode 100644 R/getTiming.R diff --git a/R/getDefaultData.R b/R/getDefaultData.R new file mode 100644 index 0000000..421da19 --- /dev/null +++ b/R/getDefaultData.R @@ -0,0 +1,17 @@ +getDefaultData <- function(standard) { + data <- list() + + if (standard == 'sdtm') { + data$aes <- safetyData::sdtm_ae + data$dm <- safetyData::sdtm_dm + data$labs <- safetyData::sdtm_lb + } + + if (standard == 'adam') { + data$aes <- safetyData::adam_adae + data$dm <- safetyData::adam_adsl + data$labs <- safetyData::adam_adlbc + } + + data +} diff --git a/R/getDefaultSettings.R b/R/getDefaultSettings.R new file mode 100644 index 0000000..704ffda --- /dev/null +++ b/R/getDefaultSettings.R @@ -0,0 +1,63 @@ +getDefaultSettings <- function(standard) { + settings <- list() + + if (standard == 'sdtm') { + settings$aes <- list( + id_col = "USUBJID", + stdt_col = "AESTDT", + endt_col = "AEENDT", + stdy_col = NULL, + endy_col = NULL, + aeterm_col = "AETERM", + decod_col = 'AEDECOD', + bodsys_col = "AEBODSYS", + severity_col = "AESEV" + ) + + settings$dm <- list( + id_col = "USUBJID", + reference_date_col = 'RFSTDTC', + treatment_col = "ARM" + ) + + settings$labs <- list( + id_col = "USUBJID", + visit_col = 'VISIT', + visit_order_col = 'VISITNUM', + dt_col = 'LBDT', + dy_col = NULL, + result_col = 'LBSTRESN' + ) + } + + if (standard == 'adam') { + settings$aes <- list( + id_col = "USUBJID", + stdt_col = NULL, + endt_col = NULL, + stdy_col = "ASTDY", + endy_col = "AENDY", + aeterm_col = "AETERM", + term_col = 'AEDECOD', + bodsys_col = "AEBODSYS", + severity_col = "AESEV" + ) + + settings$dm <- list( + id_col = "USUBJID", + reference_date_col = 'TRTSDT', + treatment_col = "TRT01P" + ) + + settings$labs <- list( + id_col = "USUBJID", + visit_col = 'AVISIT', + visit_order_col = 'AVISITN', + dt_col = 'ADT', + dy_col = 'ADY', + result_col = 'AVAL' + ) + } + + settings +} diff --git a/R/getTiming.R b/R/getTiming.R new file mode 100644 index 0000000..9ccfb0a --- /dev/null +++ b/R/getTiming.R @@ -0,0 +1,60 @@ +#' Calculate reference timepoint. +#' +#' @param params `list` Reactive list of input data and mappings +#' @param domain `character` Name of data domain with which to calculate reference timepoint +#' @param domainDate `character` Name of date column with which to calculate reference timepoint +#' @param reference `character` Name of data reference with which to calculate reference timepoint +#' @param referenceDate `character` Name of date column with which to calculate reference timepoint + +getTiming <- function( + params, + domain, + domainDate = NULL, + refDomain = 'dm', + refDate = 'RFSTDTC', + domainStartDate = NULL, + domainEndDate = NULL +) { + # TODO: add stopifnot() logic + + id_col <- reactive({ + params()$settings[[ refDomain ]]$id_col + }) + + ref_data <- params()$data[[ refDomain ]] %>% + select( + id_col, refDate + ) %>% + mutate( + refDate = as.Date(.data$refDate) + ) + + domain_data <- params()$data[[ domain ]] %>% + left_join( + ref_data, + id_col + ) + + if (!is.null(domainDate)) { + domain_date[[ domainDate ]] <- as.Date(domain_data[[ domainDate ]]) + + domain_data[[ paste0(domainDate, '_dy') ]] <- as.numeric(domain_data[[ domainDate ]] - + ref_data[[ refDate ]]) + (domain_data[[ domainDate ]] >= ref_data[[ refDate ]]) + } + + if (!is.null(domainStartDate)) { + domain_date[[ domainStartDate ]] <- as.Date(domain_data[[ domainStartDate ]]) + + domain_data[[ paste0(domainDate, '_stdy') ]] <- as.numeric(domain_data[[ domainStartDate ]] - + ref_data[[ refDate ]]) + (domain_data[[ domainStartDate ]] >= ref_data[[ refDate ]]) + } + + if (!is.null(domainEndDate)) { + domain_date[[ domainEndDate ]] <- as.Date(domain_data[[ domainEndDate ]]) + + domain_data[[ paste0(domainDate, '_endy') ]] <- as.numeric(domain_data[[ domainEndDate ]] - + ref_data[[ refDate ]]) + (domain_data[[ domainEndDate ]] >= ref_data[[ refDate ]]) + } + + domain_data +} diff --git a/R/profileApp.R b/R/profileApp.R index 4e22f37..bf61366 100644 --- a/R/profileApp.R +++ b/R/profileApp.R @@ -1,53 +1,58 @@ #' Safety Profile App #' -#' @param dfAE AE Data -#' @param dfDemog demog data -#' @param settings safetyGraphics settings +#' @param data `list` Named list of data domains +#' @param settings `list` Named list of data mappings +#' @param standard `character` Name of data standard +#' @param runNow `logical` Run app? #' -#' @import shiny +#' @return `shiny.appobj` Shiny app +#' +#' @importFrom shiny shinyApp callModule #' #' @export profileApp <- function( - data = list( - aes=safetyData::adam_adae, - dm = safetyData::adam_adsl, - labs=safetyData::adam_adlbc - ), - settings=NULL, - runNow=TRUE -){ + data = NULL, + settings = NULL, + standard = 'adam', + runNow = TRUE +) { + stopifnot( + '[ standard ] must be a character-classed variable.' = + class(standard) == 'character', + '[ standard ] must be one of "sdtm", "adam", "custom".' = + tolower(standard) %in% c('sdtm', 'adam', 'custom') + ) - ## create default settings when settings is not defined by default - if(is.null(settings)){ - settings<-list( - labs=list(id_col="USUBJID"), - aes=list(id_col="USUBJID", siteid_col="SITEID", trarm_col="TRTA", - bodsys_col="AEBODSYS", term_col = 'AEDECOD', - aeterm_col="AETERM", severity_col="AESEV", - stdy_col="ASTDY", endy_col="AENDY"), - dm=list(id_col="USUBJID", treatment_col="ARM") - ) - } + standard <- tolower(standard) + + ## create list of default data when undefined + if (is.null(data)) + data <- getDefaultData(standard) - ## create object containing data and setting to pass to server + ## create list of default settings when undefined + if (is.null(settings)) + settings <- getDefaultSettings(standard) +browser() + ## create reactive list of data and settings to pass to server params <- reactive({ list( - data=data, - settings=settings + data = data, + settings = settings, + standard = standard ) }) - ## Create app with ui and server + ## Create app with UI and server app <- shinyApp( ui = profile_ui("profile"), - server = function(input,output,session){ + server = function(input,output,session) { callModule(profile_server, "profile", params) } ) - #if(runNow) + if (runNow) runApp(app) - #else - #app + else + app } diff --git a/R/profile_server.R b/R/profile_server.R index a03aaa5..b6845cd 100644 --- a/R/profile_server.R +++ b/R/profile_server.R @@ -23,7 +23,6 @@ profile_server <- function(input, output, session, params) { ## set up some basic reactives for convenience id_col<-reactive({ - params()$settings$dm$id_col }) @@ -78,9 +77,7 @@ profile_server <- function(input, output, session, params) { print(params()$data$aes %>% filter(!!sym(id_col()) == input$idSelect))# %in% input$idSelect]) }) - - - # TODO Make this dynamic for any domain provided (use a sub-module?) + # TODO: Make this dynamic for any domain provided (use a sub-module?) output$overview <- renderDT({domain_choice() %>% filter(!!sym(id_col()) == input$idSelect)}) output$AEplot <- renderPlot({ From 4d17e577da4dea5a3e17568b02f404b6768aa753 Mon Sep 17 00:00:00 2001 From: Spencer Childress Date: Fri, 27 Jan 2023 11:58:03 -0500 Subject: [PATCH 2/4] fix #5 --- .gitignore | 2 ++ R/getDefaultSettings.R | 12 ++++++------ R/getTiming.R | 41 ++++++++++++++++++++--------------------- R/profileApp.R | 35 +++++++++++++++++++++++++++++++---- 4 files changed, 59 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index cd67eac..aef76f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .Rproj.user +.Rhistory +.Rdata diff --git a/R/getDefaultSettings.R b/R/getDefaultSettings.R index 704ffda..69313a0 100644 --- a/R/getDefaultSettings.R +++ b/R/getDefaultSettings.R @@ -4,8 +4,8 @@ getDefaultSettings <- function(standard) { if (standard == 'sdtm') { settings$aes <- list( id_col = "USUBJID", - stdt_col = "AESTDT", - endt_col = "AEENDT", + stdt_col = "AESTDTC", + endt_col = "AEENDTC", stdy_col = NULL, endy_col = NULL, aeterm_col = "AETERM", @@ -24,7 +24,7 @@ getDefaultSettings <- function(standard) { id_col = "USUBJID", visit_col = 'VISIT', visit_order_col = 'VISITNUM', - dt_col = 'LBDT', + dt_col = 'LBDTC', dy_col = NULL, result_col = 'LBSTRESN' ) @@ -33,12 +33,12 @@ getDefaultSettings <- function(standard) { if (standard == 'adam') { settings$aes <- list( id_col = "USUBJID", - stdt_col = NULL, - endt_col = NULL, + stdt_col = 'ASTDT', + endt_col = 'AENDT', stdy_col = "ASTDY", endy_col = "AENDY", aeterm_col = "AETERM", - term_col = 'AEDECOD', + decod_col = 'AEDECOD', bodsys_col = "AEBODSYS", severity_col = "AESEV" ) diff --git a/R/getTiming.R b/R/getTiming.R index 9ccfb0a..894c67b 100644 --- a/R/getTiming.R +++ b/R/getTiming.R @@ -17,44 +17,43 @@ getTiming <- function( ) { # TODO: add stopifnot() logic - id_col <- reactive({ - params()$settings[[ refDomain ]]$id_col - }) + id_col <- params$settings[[ refDomain ]]$id_col - ref_data <- params()$data[[ refDomain ]] %>% + ref_data <- params$data[[ refDomain ]] %>% select( id_col, refDate ) %>% mutate( - refDate = as.Date(.data$refDate) + # TODO: add logic around date format... lubridate::ymd? + refDate = as.Date(.data[[ refDate ]]) ) - domain_data <- params()$data[[ domain ]] %>% + domain_data <- params$data[[ domain ]] %>% left_join( ref_data, id_col ) - if (!is.null(domainDate)) { - domain_date[[ domainDate ]] <- as.Date(domain_data[[ domainDate ]]) + getStudyDay <- function(data, date_col) { + data[[ date_col ]] <- as.Date(data[[ date_col ]]) - domain_data[[ paste0(domainDate, '_dy') ]] <- as.numeric(domain_data[[ domainDate ]] - - ref_data[[ refDate ]]) + (domain_data[[ domainDate ]] >= ref_data[[ refDate ]]) - } - - if (!is.null(domainStartDate)) { - domain_date[[ domainStartDate ]] <- as.Date(domain_data[[ domainStartDate ]]) + data[[ paste0(date_col, '_dy') ]] <- as.numeric( + data[[ date_col ]] - data$refDate + ) + ( + data[[ date_col ]] >= data$refDate + ) - domain_data[[ paste0(domainDate, '_stdy') ]] <- as.numeric(domain_data[[ domainStartDate ]] - - ref_data[[ refDate ]]) + (domain_data[[ domainStartDate ]] >= ref_data[[ refDate ]]) + data } - if (!is.null(domainEndDate)) { - domain_date[[ domainEndDate ]] <- as.Date(domain_data[[ domainEndDate ]]) + if (!is.null(domainDate)) + domain_data <- getStudyDay(domain_data, domainDate) - domain_data[[ paste0(domainDate, '_endy') ]] <- as.numeric(domain_data[[ domainEndDate ]] - - ref_data[[ refDate ]]) + (domain_data[[ domainEndDate ]] >= ref_data[[ refDate ]]) - } + if (!is.null(domainStartDate)) + domain_data <- getStudyDay(domain_data, domainStartDate) + + if (!is.null(domainEndDate)) + domain_data <- getStudyDay(domain_data, domainEndDate) domain_data } diff --git a/R/profileApp.R b/R/profileApp.R index bf61366..988d534 100644 --- a/R/profileApp.R +++ b/R/profileApp.R @@ -26,15 +26,42 @@ profileApp <- function( standard <- tolower(standard) - ## create list of default data when undefined + ## Create list of default data when undefined. if (is.null(data)) data <- getDefaultData(standard) - ## create list of default settings when undefined + ## Create list of default settings when undefined. if (is.null(settings)) settings <- getDefaultSettings(standard) -browser() - ## create reactive list of data and settings to pass to server + + ## Calculate study timing. + if (standard == 'sdtm') { + data$aes <- getTiming( + params = list( + data = data, + settings = settings, + standard = standard + ), + 'aes', + domainStartDate = settings$aes$stdt_col, + domainEndDate = settings$aes$endt_col + ) + settings$aes$stdy_col <- paste0(settings$aes$stdt_col, '_dy') + settings$aes$endy_col <- paste0(settings$aes$endt_col, '_dy') + + data$labs <- getTiming( + params = list( + data = data, + settings = settings, + standard = standard + ), + 'labs', + domainDate = settings$labs$dt_col + ) + settings$labs$dy_col <- paste0(settings$labs$dt_col, '_dy') + } + + ## Create reactive list of data and settings to pass to server params <- reactive({ list( data = data, From 841fc5856b8dc8395473dfb1562c46768a244db7 Mon Sep 17 00:00:00 2001 From: Spencer Childress Date: Thu, 23 Feb 2023 10:01:25 -0500 Subject: [PATCH 3/4] idk --- DESCRIPTION | 3 +++ R/profileApp.R | 40 ++++++++-------------------------------- 2 files changed, 11 insertions(+), 32 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 4c83b0b..69f341d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -14,3 +14,6 @@ License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) RoxygenNote: 7.2.1 +Depends: + R (>= 2.10) +LazyData: true diff --git a/R/profileApp.R b/R/profileApp.R index 988d534..21b1ae6 100644 --- a/R/profileApp.R +++ b/R/profileApp.R @@ -12,7 +12,11 @@ #' @export profileApp <- function( - data = NULL, + data = list( + dm = safetyData::adam_adsl, + aes = safetyData::adam_adae, + labs = safetyData::adam_adlbc + ), settings = NULL, standard = 'adam', runNow = TRUE @@ -26,40 +30,12 @@ profileApp <- function( standard <- tolower(standard) - ## Create list of default data when undefined. + # TODO: use default data given standard if (is.null(data)) - data <- getDefaultData(standard) + settings <- safetyProfile::example_data[[ standard ]] - ## Create list of default settings when undefined. if (is.null(settings)) - settings <- getDefaultSettings(standard) - - ## Calculate study timing. - if (standard == 'sdtm') { - data$aes <- getTiming( - params = list( - data = data, - settings = settings, - standard = standard - ), - 'aes', - domainStartDate = settings$aes$stdt_col, - domainEndDate = settings$aes$endt_col - ) - settings$aes$stdy_col <- paste0(settings$aes$stdt_col, '_dy') - settings$aes$endy_col <- paste0(settings$aes$endt_col, '_dy') - - data$labs <- getTiming( - params = list( - data = data, - settings = settings, - standard = standard - ), - 'labs', - domainDate = settings$labs$dt_col - ) - settings$labs$dy_col <- paste0(settings$labs$dt_col, '_dy') - } + settings <- safetyProfile::mapping[[ standard ]] ## Create reactive list of data and settings to pass to server params <- reactive({ From 98268adeae8e3e0fb393a2cb361a828b24825d1b Mon Sep 17 00:00:00 2001 From: Spencer Childress Date: Fri, 24 Feb 2023 11:11:25 -0500 Subject: [PATCH 4/4] git add --- R/getSDTMTiming.R | 45 ++++++++++++++++++++++++++++++++++++++ data-raw/mapping.R | 21 ++++++++++++++++++ data-raw/mapping_adam.csv | 19 ++++++++++++++++ data-raw/mapping_sdtm.csv | 16 ++++++++++++++ data/mapping.rda | Bin 0 -> 443 bytes data/mapping_adam.rda | Bin 0 -> 405 bytes data/mapping_sdtm.rda | Bin 0 -> 391 bytes 7 files changed, 101 insertions(+) create mode 100644 R/getSDTMTiming.R create mode 100644 data-raw/mapping.R create mode 100644 data-raw/mapping_adam.csv create mode 100644 data-raw/mapping_sdtm.csv create mode 100644 data/mapping.rda create mode 100644 data/mapping_adam.rda create mode 100644 data/mapping_sdtm.rda diff --git a/R/getSDTMTiming.R b/R/getSDTMTiming.R new file mode 100644 index 0000000..8114cd7 --- /dev/null +++ b/R/getSDTMTiming.R @@ -0,0 +1,45 @@ +getSDTMTiming <- function() { + stopifnot( + '[ standard ] must be a character-classed variable.' = + class(standard) == 'character', + '[ standard ] must be one of "sdtm", "adam", "custom".' = + tolower(standard) %in% c('sdtm', 'adam', 'custom') + ) + + standard <- tolower(standard) + + ## Create list of default data when undefined. + if (is.null(data)) + data <- getDefaultData(standard) + + ## Create list of default settings when undefined. + if (is.null(settings)) + settings <- getDefaultSettings(standard) + + ## Calculate study timing. + if (standard == 'sdtm') { + data$aes <- getTiming( + params = list( + data = data, + settings = settings, + standard = standard + ), + 'aes', + domainStartDate = settings$aes$stdt_col, + domainEndDate = settings$aes$endt_col + ) + settings$aes$stdy_col <- paste0(settings$aes$stdt_col, '_dy') + settings$aes$endy_col <- paste0(settings$aes$endt_col, '_dy') + + data$labs <- getTiming( + params = list( + data = data, + settings = settings, + standard = standard + ), + 'labs', + domainDate = settings$labs$dt_col + ) + settings$labs$dy_col <- paste0(settings$labs$dt_col, '_dy') + } +} diff --git a/data-raw/mapping.R b/data-raw/mapping.R new file mode 100644 index 0000000..ab11f80 --- /dev/null +++ b/data-raw/mapping.R @@ -0,0 +1,21 @@ +library(dplyr) +library(purrr) + +mapping_sdtm <- read.csv("data-raw/mapping_sdtm.csv") +mapping_adam <- read.csv("data-raw/mapping_adam.csv") + +usethis::use_data(mapping_sdtm, overwrite = TRUE) +usethis::use_data(mapping_adam, overwrite = TRUE) + +mapping <- bind_rows(mapping_sdtm, mapping_adam) %>% + split(.$standard) %>% + map(function(standard) { + standard %>% + split(.$domain) %>% + map(function(domain) { + as.list(domain$value) %>% + setNames(domain$key) + }) + }) + +usethis::use_data(mapping, overwrite = TRUE) diff --git a/data-raw/mapping_adam.csv b/data-raw/mapping_adam.csv new file mode 100644 index 0000000..6ca2a9a --- /dev/null +++ b/data-raw/mapping_adam.csv @@ -0,0 +1,19 @@ +standard,domain,key,value +adam,aes,id_col,USUBJID +adam,aes,stdt_col,ASTDT +adam,aes,endt_col,AENDT +adam,aes,stdy_col,ASTDY +adam,aes,endy_col,AENDY +adam,aes,aeterm_col,AETERM +adam,aes,decod_col,AEDECOD +adam,aes,bodsys_col,AEBODSYS +adam,aes,severity_col,AESEV +adam,dm,id_col,USUBJID +adam,dm,reference_date_col,TRTSDT +adam,dm,treatment_col,TRT01P +adam,labs,id_col,USUBJID +adam,labs,visit_col,AVISIT +adam,labs,visit_order_col,AVISITN +adam,labs,dt_col,ADT +adam,labs,dy_col,ADY +adam,labs,result_col,AVAL diff --git a/data-raw/mapping_sdtm.csv b/data-raw/mapping_sdtm.csv new file mode 100644 index 0000000..2688e30 --- /dev/null +++ b/data-raw/mapping_sdtm.csv @@ -0,0 +1,16 @@ +standard,domain,key,value +sdtm,aes,id_col,USUBJID +sdtm,aes,stdt_col,AESTDTC +sdtm,aes,endt_col,AEENDTC +sdtm,aes,aeterm_col,AETERM +sdtm,aes,decod_col,AEDECOD +sdtm,aes,bodsys_col,AEBODSYS +sdtm,aes,severity_col,AESEV +sdtm,dm,id_col,USUBJID +sdtm,dm,reference_date_col,RFSTDTC +sdtm,dm,treatment_col,ARM +sdtm,labs,id_col,USUBJID +sdtm,labs,visit_col,VISIT +sdtm,labs,visit_order_col,VISITNUM +sdtm,labs,dt_col,LBDTC +sdtm,labs,result_col,LBSTRESN diff --git a/data/mapping.rda b/data/mapping.rda new file mode 100644 index 0000000000000000000000000000000000000000..cd4566c648082f1eeb107ea7579e92c1a838e976 GIT binary patch literal 443 zcmV;s0Yv^nT4*^jL0KkKS@Ms&=>P(A?|}dI*Z^<U|V$FLnH zIB-^qQI_;UzxP<0Fj%OhBk4;cXLkPEt`J6-o-`?=R7xXMW4oVzWza4Wp`MTqq#&aV zAqkT(3?w+oBqhUBfF(Hv_+6$zS9F0a6TyizD5!RUWD05>NTM2A{zX*PR36G@5G$hy zK^+ll9Y8!L9SQAuS9H9JnCVZx2flz`}KzSb=4m2{~L>OBpfEk{gR l+URwq7MvrQa>;ouTD8{6v6?M$L)1Uw?ntK!5*|_aeIO?`!r1@- literal 0 HcmV?d00001 diff --git a/data/mapping_adam.rda b/data/mapping_adam.rda new file mode 100644 index 0000000000000000000000000000000000000000..3b46a9843c205de8abf0cd61a3adf09cad017a23 GIT binary patch literal 405 zcmV;G0c!q2T4*^jL0KkKS$YZiegFZWf5HFu*Z=?lZ~#6x-(bJ5-=IJM00962umO0p zVi_d$GD-T6Q#BJN)b%|;JwR!p>J1t_Jy0|uC!!e|000J>0QCSG0V0zjPs*E60B8o9 z0iZoiGy~K@T8 zorBE!u{J}V+tpQ;jFe%HD1r_ow!Hvz9V*x|8bluyiV_N+Ks>0z)=`rhTiOFVO)-fK zLk?Yrf3O9+T9ywn~-# zqLdxtd9cpq6p$R6cjv<{QrSFEF=~h)Rt$4uF$h2i=HRhGs+!`c6(1x72iJ}?J4+#x zZ1y0rI8q2eLM_cueTB7JoM;*wz)`8aT|S27lxB;|xxcoAANaeHDZ+$<&`-ADqd)^q15q_SN2Z8ofdfD>7!yqlKn5i>Hb$mXPt=-elSYF~LqN#& z13gg2)fk%*0|n?%2?pgr35M9V*KnNe2Gj*ek?o-|B#~Q(Lj%oo0OCy=^!4Ktt9BS5 z_dGMasCLSEP^}b_1BeMC2@W8D5hS7zEQtgI=b-=)!OE3OD9m!QY0GjU0TUM#8e}=A z;Sq)@4;aXa%MdU@8&+IE=}x3ZXHSl`lnYr<$s0L9WE{%?q>}?pQit!}w5>XmC9lD# zX8Kx)&Q17EI|JQZ5Uw4>KaJNCVD>0AzOHNDTHkBwwg42uuazM&5|c&7JRC&BgX^ek zN}0q!Rgy%V#7hlW1d0T~`|mjnm@>)9$}>cm<-aID8(U=WWtwf+R;