diff --git a/DESCRIPTION b/DESCRIPTION index 3b96a6f..5e7b8d9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: redcap.gpt Title: What the Package Does (One Line, Title Case) -Version: 0.1.1 +Version: 0.1.1.9000 Authors@R: c( person("Corrado", "Lanera", , "corrado.lanera@ubep.unipd.it", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-0520-7428")), diff --git a/NAMESPACE b/NAMESPACE index 1e6234e..4f5f510 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -6,8 +6,8 @@ export(get_redcap_pid) export(get_redcap_token) export(get_redcap_uri) export(gpt_to_tibble) +export(parse_checkbox) export(parse_gpt_fctr) -export(parse_sensazione) export(query_gpt_on_redcap_instrument) importFrom(rlang,"!!") importFrom(rlang,":=") diff --git a/NEWS.md b/NEWS.md index c0e048e..253e3d3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,12 @@ +# redcap.gpt (development version) + +* allow `query_gpt_on_redcap_instrument` to process the whole db setting `query_all_records` to `TRUE`. +* change `parse_sensazione` to a more general `parse_checkbox`. + # redcap.gpt 0.1.1 -* Added support of `targets` and `tarchetypes` -* Fix levels name for the trend question (fix issue #1) +* Added support of `targets` and `tarchetypes`. +* Fix levels name for the trend question (fix issue #1). # redcap.gpt 0.1.0 diff --git a/R/parse_answer.R b/R/parse_answer.R index 2c4cb5a..52306e7 100644 --- a/R/parse_answer.R +++ b/R/parse_answer.R @@ -1,4 +1,4 @@ -#' Parser for `answer` columns +#' Parser for checkboxes #' #' Converts GPT answers yes/no to logical. #' @@ -8,11 +8,11 @@ #' @export #' #' @examples -#' parse_sensazione("si") -#' parse_sensazione("no") -#' parse_sensazione("NA") -#' parse_sensazione("N/A") -parse_sensazione <- function(x) { +#' parse_checkbox("si") +#' parse_checkbox("no") +#' parse_checkbox("NA") +#' parse_checkbox("N/A") +parse_checkbox <- function(x) { res <- stringi::stri_enc_toascii(stringr::str_to_lower(x)) == "si" tidyr::replace_na(res, FALSE) |> as.integer() diff --git a/R/query_composers.R b/R/query_composers.R index da52793..fa102d4 100644 --- a/R/query_composers.R +++ b/R/query_composers.R @@ -12,13 +12,16 @@ compose_usr_task <- function() { compose_usr_instructions <- function() { "Dal testo fornito dal soggetto, riportato qui di seguito tra la coppia di delimitatori `#####`, estrai le seguenti informazioni: - - [sensazione_calmo]: il soggetto sembra calmo/tranquillo/sereno? - {si/no} - - [sensazione_irritato]: il soggetto sembra irritato/infastidito? - {si/no} - - [sensazione_ansioso]: il soggetto sembra ansioso/preoccupato/nervoso? - {si/no} - - [sensazione_ottimista]: il soggetto sembra ottimista/positivo verso il recupero? - {si/no} - - [sensazione_demotivato]: il soggetto sembra demotivato/negativo verso il recupero? - {si/no} - - [sensazione_stanco]: il soggetto sembra stanco/esaurito fisicamente o mentalmente? - {si/no} - - [momento]: in quali momenti della giornata sono collocate le sesazioni descritte? - {mattina (05-11) / pomeriggio (11-17) / sera (17-23) / notte (23-05)} + - [sensazione_calmo]: basandosi sul tono/stile e su quanto esplicitamente scritto, il soggetto sembra calmo/tranquillo/sereno? - {si/no} + - [sensazione_irritato]: basandosi sul tono/stile e su quanto esplicitamente scritto, il soggetto sembra irritato/infastidito? - {si/no} + - [sensazione_ansioso]: basandosi sul tono/stile e su quanto esplicitamente scritto, il soggetto sembra ansioso/preoccupato/nervoso? - {si/no} + - [sensazione_ottimista]: basandosi sul tono/stile e su quanto esplicitamente scritto, il soggetto sembra ottimista/positivo verso il recupero? - {si/no} + - [sensazione_demotivato]: basandosi sul tono/stile e su quanto esplicitamente scritto, il soggetto sembra demotivato/negativo verso il recupero? - {si/no} + - [sensazione_stanco]: basandosi sul tono/stile e su quanto esplicitamente scritto, il soggetto sembra stanco/esaurito fisicamente o mentalmente? - {si/no} + - [momento_mattina]: ci sono sensazioni che nella descrizione sono state collocate (esplicitamente) al mattino? - {si/no} + - [momento_pomeriggio]: ci sono sensazioni che nella descrizione sono state collocate (esplicitamente) al pomeriggio? - {si/no} + - [momento_sera]: ci sono sensazioni che nella descrizione sono state collocate (esplicitamente) alla sera? - {si/no} + - [momento_notte]: ci sono sensazioni che nella descrizione sono state collocate (esplicitamente) nella notte? - {si/no} - [andamento]: come pare stia procedendo il recupero? - {peggiore/migliore/costante/altalenante} - [impatto]: che impatto sulle attività si manifesta? - {nessuno (attività regolari) / leggero (disagio nel condurre le attività) / moderato (impedimenti nel condurre le attività) / grave (limitazioni nel condurre le attività) / critico (impossibilità di condurre le attività)} " @@ -60,7 +63,19 @@ compose_usr_example <- function() { "risposta": , "motivazione": }, - momento = { + momento_mattina = { + "risposta": , + "motivazione": + }, + momento_pomeriggio = { + "risposta": , + "motivazione": + }, + momento_sera = { + "risposta": , + "motivazione": + }, + momento_notte = { "risposta": , "motivazione": }, diff --git a/R/query_gpt_on_redcap_instrument.R b/R/query_gpt_on_redcap_instrument.R index 73956c9..0391931 100644 --- a/R/query_gpt_on_redcap_instrument.R +++ b/R/query_gpt_on_redcap_instrument.R @@ -4,6 +4,11 @@ #' @param instrument (chr) The REDCap instrument of the form to query. #' @param model (chr, default: "gpt-4o-mini") The GPT model to use. #' @param seed (int, default: 1234) The seed for the GPT model. +#' @param query_on_all_records (lgl, default: FALSE) If TRUE, the +#' content of the "text_processed_record" variable (which mark if a +#' record has already been processed) will be ignored. If FALSE +#' (default) only records not marked as already processed will be +#' considered. #' #' @return (tbl_df) The REDCap DB with GPT responses parsed. #' @export @@ -11,14 +16,15 @@ query_gpt_on_redcap_instrument <- function( db, instrument = c("note_fup", "comments_fup", "details_fup"), model = "gpt-4o-mini", - seed = 1234 + seed = 1234, + query_on_all_records = FALSE ) { instrument <- match.arg(instrument) checkmate::assert_subset(instrument, names(db)) stopifnot( sum(stringr::str_detect( names(db), stringr::str_glue("{instrument}_text") - )) == 19 + )) == 25 ) db_to_query <- db |> @@ -30,7 +36,7 @@ query_gpt_on_redcap_instrument <- function( ) |> dplyr::filter( !is.na(.data[[instrument]]), - !.data[[ + query_on_all_records | !.data[[ stringr::str_c(instrument, "_text_processed_record___1") ]] ) |> @@ -89,8 +95,15 @@ query_gpt_on_redcap_instrument <- function( stanco_response <- stringr::str_glue("{instrument}_text_feeling___6") stanco_motivation <- stringr::str_glue("{instrument}_text_feeling_6_motivation") - momento_response <- stringr::str_glue("{instrument}_text_daytime") - momento_motivation <- stringr::str_glue("{instrument}_text_daytime_motivation") + mattina_response <- stringr::str_glue("{instrument}_text_daytime___1") + mattina_motivation <- stringr::str_glue("{instrument}_text_daytime_1_motivation") + pomeriggio_response <- stringr::str_glue("{instrument}_text_daytime___2") + pomeriggio_motivation <- stringr::str_glue("{instrument}_text_daytime_2_motivation") + sera_response <- stringr::str_glue("{instrument}_text_daytime___3") + sera_motivation <- stringr::str_glue("{instrument}_text_daytime_3_motivation") + notte_response <- stringr::str_glue("{instrument}_text_daytime___4") + notte_motivation <- stringr::str_glue("{instrument}_text_daytime_4_motivation") + andamento_response <- stringr::str_glue("{instrument}_text_trend") andamento_motivation <- stringr::str_glue("{instrument}_text_trend_motivation") impatto_response <- stringr::str_glue("{instrument}_text_impact") @@ -104,11 +117,11 @@ query_gpt_on_redcap_instrument <- function( ), dplyr::across( dplyr::matches("^sensazione_.*_risposta"), - parse_sensazione + parse_checkbox ), - momento_risposta = parse_gpt_fctr( - from_str = .data[["momento_risposta"]], - to_fct = .data[[momento_response]] + dplyr::across( + dplyr::matches("^momento_.*_risposta"), + parse_checkbox ), andamento_risposta = parse_gpt_fctr( from_str = .data[["andamento_risposta"]], @@ -132,8 +145,16 @@ query_gpt_on_redcap_instrument <- function( !!demotivato_motivation := .data[["sensazione_demotivato_motivazione"]], !!stanco_response := .data[["sensazione_stanco_risposta"]], !!stanco_motivation := .data[["sensazione_stanco_motivazione"]], - !!momento_response := .data[["momento_risposta"]], - !!momento_motivation := .data[["momento_motivazione"]], + + !!mattina_response := .data[["momento_mattina_risposta"]], + !!mattina_motivation := .data[["momento_mattina_motivazione"]], + !!pomeriggio_response := .data[["momento_pomeriggio_risposta"]], + !!pomeriggio_motivation := .data[["momento_pomeriggio_motivazione"]], + !!sera_response := .data[["momento_sera_risposta"]], + !!sera_motivation := .data[["momento_sera_motivazione"]], + !!notte_response := .data[["momento_notte_risposta"]], + !!notte_motivation := .data[["momento_notte_motivazione"]], + !!andamento_response := .data[["andamento_risposta"]], !!andamento_motivation := .data[["andamento_motivazione"]], !!impatto_response := .data[["impatto_risposta"]], @@ -152,8 +173,14 @@ query_gpt_on_redcap_instrument <- function( "sensazione_demotivato_motivazione", "sensazione_stanco_risposta", "sensazione_stanco_motivazione", - "momento_risposta", - "momento_motivazione", + "momento_mattina_risposta", + "momento_mattina_motivazione", + "momento_pomeriggio_risposta", + "momento_pomeriggio_motivazione", + "momento_sera_risposta", + "momento_sera_motivazione", + "momento_notte_risposta", + "momento_notte_motivazione", "andamento_risposta", "andamento_motivazione", "impatto_risposta", @@ -169,30 +196,3 @@ query_gpt_on_redcap_instrument <- function( dplyr::across(dplyr::where(is.logical), \(x) as.integer(x)) ) } - - -var_to_map <- function(x) { - c( - feeling___1 = "calmo", - feeling___2 = "irritato", - feeling___3 = "ansioso", - feeling___4 = "ottimista", - feeling___5 = "demotivato", - daytime = "momento", - trend = "andamento", - impact = "impatto" - )[x] -} - -map_to_var <- function(x) { - c( - calmo = "feeling___1", - irritato = "feeling___2", - ansioso = "feeling___3", - ottimista = "feeling___4", - demotivato = "feeling___5", - momento = "daytime", - andamento = "trend", - impatto = "impact" - )[x] -} diff --git a/analyses/write.R b/analyses/write.R index 56ef743..f9a5d54 100644 --- a/analyses/write.R +++ b/analyses/write.R @@ -6,7 +6,7 @@ fup_143060 <- fetch_form("followup_postoperatorio_14_30_60_giorno_po") fup_90 <- fetch_form("visita_followup_postoperatorio_90_giorno_po") note_fup_to_be_pushed <- fup_143060 |> - query_gpt_on_redcap_instrument("note_fup") + query_gpt_on_redcap_instrument("note_fup") comments_fup_to_be_pushed <- fup_143060 |> query_gpt_on_redcap_instrument("comments_fup") details_fup_to_be_pushed <- fup_90 |> diff --git a/man/parse_sensazione.Rd b/man/parse_checkbox.Rd similarity index 57% rename from man/parse_sensazione.Rd rename to man/parse_checkbox.Rd index 2e09b7b..5af1ff7 100644 --- a/man/parse_sensazione.Rd +++ b/man/parse_checkbox.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/parse_answer.R -\name{parse_sensazione} -\alias{parse_sensazione} -\title{Parser for \code{answer} columns} +\name{parse_checkbox} +\alias{parse_checkbox} +\title{Parser for checkboxes} \usage{ -parse_sensazione(x) +parse_checkbox(x) } \arguments{ \item{x}{(chr) The character vector to parse.} @@ -16,8 +16,8 @@ parse_sensazione(x) Converts GPT answers yes/no to logical. } \examples{ -parse_sensazione("si") -parse_sensazione("no") -parse_sensazione("NA") -parse_sensazione("N/A") +parse_checkbox("si") +parse_checkbox("no") +parse_checkbox("NA") +parse_checkbox("N/A") } diff --git a/man/query_gpt_on_redcap_instrument.Rd b/man/query_gpt_on_redcap_instrument.Rd index f6cc2fb..da6a99a 100644 --- a/man/query_gpt_on_redcap_instrument.Rd +++ b/man/query_gpt_on_redcap_instrument.Rd @@ -8,7 +8,8 @@ query_gpt_on_redcap_instrument( db, instrument = c("note_fup", "comments_fup", "details_fup"), model = "gpt-4o-mini", - seed = 1234 + seed = 1234, + query_on_all_records = FALSE ) } \arguments{ @@ -19,6 +20,12 @@ query_gpt_on_redcap_instrument( \item{model}{(chr, default: "gpt-4o-mini") The GPT model to use.} \item{seed}{(int, default: 1234) The seed for the GPT model.} + +\item{query_on_all_records}{(lgl, default: FALSE) If TRUE, the +content of the "text_processed_record" variable (which mark if a +record has already been processed) will be ignored. If FALSE +(default) only records not marked as already processed will be +considered.} } \value{ (tbl_df) The REDCap DB with GPT responses parsed. diff --git a/tests/testthat/test-parse_answer.R b/tests/testthat/test-parse_answer.R index 6e76638..193fa62 100644 --- a/tests/testthat/test-parse_answer.R +++ b/tests/testthat/test-parse_answer.R @@ -1,6 +1,6 @@ -test_that("parse_sensazione works", { +test_that("parse_checkbox works", { # evaluate - res <- parse_sensazione(c("si", "no", "forse", NA)) + res <- parse_checkbox(c("si", "no", "forse", NA)) # test expect_equal(res, c(1, 0, 0, 0))