*! version 1.0 2026-04-21 *! Author: m.chatfield@uq.edu.au *change isid bits? program define redcapsetup version 17.0 // otherwise get CSV UTF-8 BOM leakage problems syntax [, smart] // smart option not described in help file global smartvariable "`smart'" tempfile idlongdta formeventsdta idmixdta alertsdta asidta fdldta completevarsdta DataDictionarydta preserve qui { ***Instrument-Event Designations local idcsv : dir . files "*InstrumentDesignations*.csv", respectcase global wcidcsv : word count `idcsv' if $wcidcsv == 0 { noi di as err "redcapsetup requires a *InstrumentDesignations*.csv file in the working directory." exit } if $wcidcsv > 1 { noi di as err `"redcapsetup requires just one *InstrumentDesignations*.csv file in the working directory. You have: `idcsv'"' exit } noi di as txt `"Using `idcsv'"' import delimited `idcsv', clear drop arm_num save "`idlongdta'", replace // has 2 vars: unique_event_name form **this is one way of going wide tempname here postfile `here' str80 formname str800 formevents using "`formeventsdta'", replace levelsof form, clean local(forms) foreach form of local forms { qui levelsof unique_event_name if form=="`form'", clean local(events) post `here' ("`form'") ("`events'") } levelsof unique_event_name, clean local(events) post `here' ("_all") ("`events'") postclose `here' keep form rename form formname duplicates drop gen variablefieldname = formname + "_complete" save "`completevarsdta'", replace use "`formeventsdta'", clear split formevents, gen(fevent) save "`formeventsdta'", replace rename formname form gen unique_event_name = "" append using "`idlongdta'" save "`idmixdta'", replace ***Alerts *it is completely normal and expected for unique-form-name to be unspecified or blank in an Alerts CSV. *Alerts are project-level constructs *instrument linkage is implicit via fieldnames, not via a unique-form-name column. Hmm. I may not have processed this well enough. local alertscsv : dir . files "*Alerts_*.csv", respectcase global wcalertscsv : word count `alertscsv' if $wcalertscsv > 1 { noi di as err `"redcapsetup requires no more than one *Alerts_*.csv file in the working directory. You have: `alertscsv'"' exit } if $wcalertscsv == 1 { noi di as txt `"Using `alertscsv'"' import delimited `alertscsv', clear bindquote(strict) *tab1 * ds alertcondition - alertdeactivated, has(type string) egen combinedvars = concat(`r(varlist)') rename uniqueformname form rename uniqueeventname unique_event_name ds unique_event_name, has(type string) if "`r(varlist)'" == "" { drop unique_event_name gen unique_event_name = "" } gen row = _n gen variableorform = "#" + string(row) + " " + form + " (" + unique_event_name + ")" replace row = row + 1 drop if alertdeactivated=="Y" // ignore deactivated alerts merge m:1 form unique_event_name using "`idmixdta'" count if _merge==1 & form!="" if `=r(N)' >0 { noi di " " noi di as err "Below: (unique-form-name, unique-event-name) in Alerts is invalid" noi list form unique_event_name if _merge==1, noobs ab(30) } drop if _merge==2 replace unique_event_name = formevents if unique_event_name == "" drop formevents rename unique_event_name formevents keep variableorform combinedvars row form formevents gen source = "ALERT" save "`alertsdta'", replace } ***ASI - Automated Survey Invitations local asicsv : dir . files "asi_*.csv", respectcase global wcasicsv : word count `asicsv' if $wcasicsv > 1 { noi di as err `"redcapsetup requires no more than one asi_*.csv file in the working directory. You have: `asicsv'"' exit } if $wcasicsv == 1 { noi di as txt `"Using `asicsv'"' import delimited `asicsv', clear bindquote(strict) *tab1 * ds , has(type string) *condition_logic email_content condition_send_time_lag_field egen combinedvars = concat(`r(varlist)') gen row = _n + 1 rename form_name form rename event_name unique_event_name ds unique_event_name, has(type string) if "`r(varlist)'" == "" { drop unique_event_name gen unique_event_name = "" } drop if active==0 // ignore inactive gen variableorform = " " + form + " (" + unique_event_name + ")" // should be unique here, and when combined with FDL (hence space at start) isid form unique_event_name, missok // harsh? merge m:1 form unique_event_name using "`idmixdta'" count if _merge==1 if `=r(N)' >0 { noi di " " noi di as err "Below: (form_name, event_name) in Automated Survey Invitations is invalid" noi list form unique_event_name if _merge==1, noobs ab(30) } keep if _merge==3 replace unique_event_name = formevents if unique_event_name == "" drop formevents rename unique_event_name formevents keep variableorform combinedvars row form formevents gen source = "ASI" save "`asidta'", replace } ***FDL - Form Display Logic local fdlcsv : dir . files "fdl_*.csv", respectcase global wcfdlcsv : word count `fdlcsv' if $wcfdlcsv > 1 { noi di as err `"redcapsetup requires no more than one fdl_*.csv file in the working directory. You have: `fdlcsv'"' exit } if $wcfdlcsv == 1 { noi di as txt `"Using `fdlcsv'"' import delimited `fdlcsv', clear bindquote(strict) varnames(1) isid form_name event_name, missok // harsh? *tab1 * gen combinedvars = control_condition rename form_name form rename event_name unique_event_name ds unique_event_name, has(type string) if "`r(varlist)'" == "" { drop unique_event_name gen unique_event_name = "" } gen variableorform = form + " (" + unique_event_name + ")" gen row = _n + 1 merge m:1 form unique_event_name using "`idmixdta'" count if _merge==1 if `=r(N)' >0 { noi di " " noi di as err "Below: (form_name, event_name) in Form Display Logic is invalid" noi list form unique_event_name if _merge==1, noobs ab(30) } keep if _merge==3 replace unique_event_name = formevents if unique_event_name == "" drop formevents rename unique_event_name formevents keep variableorform combinedvars row form formevents gen source = "FDL" save "`fdldta'", replace } ***DataDictionary *REDCap will fail & give error if you upload a data dictionary that references a non-existent form local ddcsv : dir . files "*DataDictionary*.csv", respectcase global wcddcsv : word count `ddcsv' if $wcddcsv == 0 { noi di as err "redcapsetup requires a *DataDictionary*.csv file in the working directory." exit } if $wcddcsv > 1 { noi di as err `"redcapsetup requires just one *DataDictionary*.csv file in the working directory. You have: `ddcsv'"' exit } noi di as txt `"Using `ddcsv'"' import delimited `ddcsv', clear bindquote(strict) maxquotedrows(180) d replace formname = "_all" in 1 // for record_id (or whatever it has been renamed to) gen source = "DD" append using "`completevarsdta'" gen row = _n + 1 merge m:1 formname using "`formeventsdta'" drop _merge save "`DataDictionarydta'", replace *Combine information from columns with potential references to variable names and event names *codebook branchinglogicshowfieldonly fieldlabel choicescalculationsorslider fieldannotation sectionheader textvalidationmin textvalidationmax ds branchinglogicshowfieldonly fieldlabel choicescalculationsorslider fieldannotation sectionheader textvalidationmin textvalidationmax, has(type string) egen combinedvars = concat(`r(varlist)') rename variablefieldname variableorform if $wcalertscsv == 1 { append using "`alertsdta'" } if $wcasicsv == 1 { append using "`asidta'" } if $wcfdlcsv == 1 { append using "`fdldta'" } *Extract contents inside square brackets: "[contents1]...[contents2]...[contents3]..." quietly { *Create output columns called contents1, contents2, contents3 etc. gen nrightbrackets = length(combinedvars) - length(subinstr(combinedvars, "]", "", .)) su nrightbrackets local max = r(max) forvalues k = 1/`max' { gen contents`k' = "" } *Put text in the output columns, row-by-row forvalues i = 1/`=_N' { local cell = combinedvars[`i'] local k = 0 while ustrregexm(`"`cell'"', "\[([^\]]+)\]") { local ++k replace contents`k' = ustrregexs(1) in `i' *Now remove the matched "[...]" so the next match can be found local cell = usubinstr(`"`cell'"', ustrregexs(0), "", 1) } } } reshape long contents, i(variableorform) j(bracket_num) drop if contents =="" *tab contents *Store "eventname" on the same row as the "variablename" that follows gen isevent = 1 if regexm(contents, "_arm_[1-9]$") bysort variableorform (bracket_num): gen eventname = contents[_n-1] if isevent[_n-1]==1 *Delete some rows and adjust contents drop if isevent==1 // as that info is now on the next line *"Smart variables" (see https://kb.wisc.edu/smph/informatics/88571) are not listed in variablefieldname count if ustrregexm(contents, "\-") if `=r(N)' > 0 { if "$smartvariable" !="" { noi di " " noi di as txt "Dropping the following [contents]:" noi tab contents if ustrregexm(contents, "\-") } *count if ustrregexm(contents, "event-name") & row!=. *if `=r(N)' > 0 { * noi di " " * noi di as res "Recommendation: search Codebook for [event-name] and check what follows is OK, e.g. if([event-name]='baseline_arm_1'..." *} drop if ustrregexm(contents, "\-") // code may need tweaking if the table does not exclusively contain only "smart variables" } *When Field Type = checkbox, recover variable names by losing e.g. "(2)" from "bci_services(2)" etc. replace contents = regexreplaceall(contents, "\([^)]+\)", "") *field annotations (also called action tags in newer REDCap terminology) replace contents = regexreplaceall(contents, ":value", "") replace contents = regexreplaceall(contents, ":inline", "") replace contents = regexreplaceall(contents, ":label", "") replace contents = regexreplaceall(contents, ":hideunderscore", "") keep eventname contents formevents variableorform source row rename formevents completed_at rename contents variablefieldname gen source_row = source + " " + string(row) rename row zrow merge m:1 variablefieldname using "`DataDictionarydta'" rename variablefieldname refvar rename row refvar_row rename zrow row rename formevents refvar_events rename formname refvar_formname sort source row variableorform gen match = 0 foreach fevent of var fevent* { replace match = 1 if eventname == `fevent' } *completed_at is not a subset of refvar_events if s_formevents_wc>0 gen s_formevents = completed_at foreach fevent of var fevent* { replace s_formevents = subinword(s_formevents,`fevent',"",1) } gen s_formevents_wc = wordcount(s_formevents) *tab s_formevents_wc ****Obviously wrong eventname specified before referenced variable noi di " " count if _merge==3 & eventname!="" & match==0 if `=r(N)' ==0 noi di as res "Good news: No instances of [obviously-wrong-eventname][variablename] found." else { noi di as err "There are instances of [obviously-wrong-eventname][refvar]:" noi list source_row variableorform eventname refvar_events refvar refvar_row if _merge==3 & eventname!="" & match==0, noobs ab(30) noi di as err "Recommendation: Specify an event in refvar_events in place of eventname when variableorform references refvar." } noi di " " ****No eventname specified before referenced variable when there should be count if _merge==3 & eventname=="" & s_formevents_wc!=0 & refvar_row!=2 if `=r(N)' ==0 noi di as res "Good news: No instances of [variablename] which need to be [eventname][variablename] found." else { noi di as err "There are instances of [refvar] which perhaps need to be [eventname][refvar]:" noi list source_row variableorform completed_at refvar_events refvar refvar_formname if _merge==3 & eventname=="" & s_formevents_wc!=0 & refvar_row!=2, noobs ab(30) noi di as err "Recommendation: For variableorform, you might need to specify an event in refvar_events if still want to reference refvar." } *(unless completed_at is a subset of refvar_events) noi di " " ****References to variables that don't exist *(or what is in [] that is not a variable in the data dictionary?) count if _merge==1 if `=r(N)' ==0 noi di as res "Good news: No references to [non-existent variables] found." else { noi di as err "This list may include references to [non-existent variables] in the last column:" noi list source_row variableorform refvar if _merge==1, noobs ab(30) noi di as err "Recommendation: For variableorform, you may need to change [refvar]." } drop if _merge==2 } restore end