33from dataclasses import dataclass
44import logging
55from pathlib import Path
6- from typing import Literal
6+ from typing import Literal , Optional
77
88import numpy as np
99import pandas as pd
1010import requests
1111
1212from .._config_variables import redcap_variables , ripple_variables
1313from ..exceptions import NoData
14- from ..utility_functions import initialize_logging , yesterday
14+ from ..utility_functions import fetch_api_data , initialize_logging , yesterday
1515
1616initialize_logging ()
1717logger = logging .getLogger (__name__ )
@@ -125,8 +125,58 @@ def prepare_redcap_data(df: pd.DataFrame) -> None:
125125 """Prepare Ripple API returned data to be imported into REDCap."""
126126 copy_selected_redcap_df = set_redcap_columns (df )
127127
128- # Save the new dataframe to a CSV file
129- copy_selected_redcap_df .to_csv (redcap_variables .redcap_import_file , index = False )
128+ # Split into update and new
129+ to_update , new_subjects = get_redcap_subjects_to_update (copy_selected_redcap_df )
130+
131+ # Save the new dataframes to CSV files
132+ if not to_update .empty :
133+ to_update .to_csv (redcap_variables .redcap_update_file , index = False )
134+ if not new_subjects .empty :
135+ new_subjects .to_csv (redcap_variables .redcap_import_file , index = False )
136+
137+
138+ def get_redcap_subjects_to_update (
139+ df : pd .DataFrame ,
140+ ) -> tuple [pd .DataFrame , pd .DataFrame ]:
141+ """
142+ Get subjects from REDCap that need to be updated vs. new subjects.
143+
144+ Returns
145+ -------
146+ pd.DataFrame
147+ subjects to update
148+ pd.DataFrame
149+ new subjects
150+
151+ """
152+ redcap_participant_consent_data = {
153+ "token" : redcap_variables .Tokens .pid247 ,
154+ "content" : "record" ,
155+ "action" : "export" ,
156+ "format" : "csv" ,
157+ "type" : "flat" ,
158+ "csvDelimiter" : "" ,
159+ "fields" : "mrn,record_id" ,
160+ "rawOrLabel" : "raw" ,
161+ "rawOrLabelHeaders" : "raw" ,
162+ "exportCheckboxLabel" : "false" ,
163+ "exportSurveyFields" : "false" ,
164+ "exportDataAccessGroups" : "false" ,
165+ "returnFormat" : "csv" ,
166+ }
167+ df_redcap_consent_instruments = fetch_api_data (
168+ Endpoints .REDCap .base_url ,
169+ redcap_variables .headers ,
170+ redcap_participant_consent_data ,
171+ )
172+ to_update = df [df ["mrn" ].isin (df_redcap_consent_instruments ["mrn" ])].copy ()
173+ to_update = to_update .drop (columns = ["record_id" ]).merge (
174+ df_redcap_consent_instruments [["mrn" , "record_id" ]], on = "mrn" , how = "left"
175+ )
176+ to_update = to_update [["record_id" , "mrn" , "email_consent" ]]
177+ return to_update [to_update ["mrn" ].isin (df_redcap_consent_instruments ["mrn" ])], df [
178+ ~ df ["mrn" ].isin (df_redcap_consent_instruments ["mrn" ])
179+ ]
130180
131181
132182def prepare_ripple_to_ripple (df : pd .DataFrame ) -> dict [str , str ]:
@@ -160,17 +210,26 @@ def prepare_ripple_to_ripple(df: pd.DataFrame) -> dict[str, str]:
160210 return ripple_import_files
161211
162212
163- def push_to_redcap (project_token : str ) -> None :
213+ def push_to_redcap (project_token : str , update : Optional [ bool ] = None ) -> None :
164214 """Push the HBN Potential Participants MRN and Contact email to RedCap."""
165- # Read the content of the CSV file
215+ if update is None :
216+ # Try both
217+ for _update in [True , False ]:
218+ push_to_redcap (project_token , _update )
219+ return
220+ filepath = (
221+ redcap_variables .redcap_update_file
222+ if update
223+ else redcap_variables .redcap_import_file
224+ )
225+ if not filepath .exists ():
226+ return
166227 try :
167- with open (redcap_variables .redcap_import_file , "r" ) as file :
228+ # Read the content of the CSV file
229+ with open (filepath , "r" ) as file :
168230 csv_content = file .read ()
169231 except FileNotFoundError :
170- logger .exception (
171- "Error: The file '%s' was not found." , redcap_variables .redcap_import_file
172- )
173- # You might want to create a placeholder or exit
232+ logger .exception ("Error: The file '%s' was not found." , filepath )
174233 csv_content = "" # Default to empty string if file not found
175234 if csv_content :
176235 data = {
@@ -180,7 +239,7 @@ def push_to_redcap(project_token: str) -> None:
180239 "format" : "csv" ,
181240 "type" : "flat" ,
182241 "overwriteBehavior" : "normal" ,
183- "forceAutoNumber" : "true" ,
242+ "forceAutoNumber" : str ( not update ). lower ,
184243 "data" : csv_content ,
185244 "returnContent" : "count" ,
186245 "returnFormat" : "csv" ,
@@ -240,11 +299,12 @@ def set_status_in_ripple(ripple_study: str, ripple_import_file: str) -> None:
240299 raise
241300
242301
243- def cleanup () -> None :
302+ def cleanup (temp_files : list [ str | Path ] ) -> None :
244303 """Delete temporary files."""
245304 for filepath in [
246305 redcap_variables .redcap_import_file ,
247- ripple_variables .ripple_import_file ,
306+ redcap_variables .redcap_update_file ,
307+ * [Path (filepath ) for filepath in temp_files ],
248308 ]:
249309 try :
250310 filepath .unlink (missing_ok = True )
@@ -258,6 +318,7 @@ def main(project_status: Literal["dev", "prod"] = "prod") -> None:
258318 "dev" : {"token" : redcap_variables .Tokens .pid757 },
259319 "prod" : {"token" : redcap_variables .Tokens .pid247 },
260320 }
321+ ripple_import_files : dict [str , str ] = {}
261322 try :
262323 filtered_ripple_df = request_potential_participants ()
263324 prepare_redcap_data (filtered_ripple_df )
@@ -268,7 +329,7 @@ def main(project_status: Literal["dev", "prod"] = "prod") -> None:
268329 except NoData :
269330 pass
270331 finally :
271- cleanup ()
332+ cleanup (list ( ripple_import_files . values ()) )
272333
273334
274335if __name__ == "__main__" :
0 commit comments