2222from ..from_redcap .from_redcap import fetch_data , response_index_reverse_lookup
2323from ..utility_functions import (
2424 CuriousAlert ,
25- fetch_api_data ,
2625 initialize_logging ,
2726 redcap_api_push ,
2827 setup_tsv_logger ,
2928)
3029from .config import curious_authenticate
30+ from .utils import (
31+ fetch_alerts_metadata ,
32+ REDCAP_TOKEN ,
33+ )
3134
3235initialize_logging ()
3336logger = logging .getLogger (__name__ )
3437REDCAP_ENDPOINTS = redcap_variables .Endpoints ()
38+
3539# ============================================================================
3640# Constants
3741# ============================================================================
38- ALERTS_INSTRUMENT_FORM = "ra_alerts_child,ra_alerts_parent"
3942ALERT_FIELD_PATTERN = r"alerts_([^_]+(?:_[^_]+)?)_\d+"
40- PID_625 = redcap_variables . Tokens . pid625
43+ PID_625 = REDCAP_TOKEN # Use token from alert_utils
4144
4245# WebSocket configuration constants
4346WS_RECONNECT_DELAY = 5 # seconds
4649WS_PING_TIMEOUT = 10 # seconds
4750WS_CLOSE_TIMEOUT = 5 # seconds
4851
49- # Standard REDCap metadata fetch parameters
50- METADATA_PARAMS = {
51- "content" : "metadata" ,
52- "action" : "export" ,
53- "format" : "csv" ,
54- "type" : "eav" ,
55- "csvDelimiter" : "" ,
56- "rawOrLabel" : "raw" ,
57- "rawOrLabelHeaders" : "raw" ,
58- "exportCheckboxLabel" : "false" ,
59- "exportSurveyFields" : "false" ,
60- "exportDataAccessGroups" : "false" ,
61- "returnFormat" : "csv" ,
62- }
6352# ============================================================================
6453# Type Definitions
6554# ============================================================================
@@ -124,14 +113,12 @@ def parse_alert(alert: CuriousAlert) -> pd.DataFrame:
124113 Note: 'record', 'value', and 'redcap_event_name' columns need further processing.
125114 """
126115 columns = ["record" , "field_name" , "value" , "redcap_event_name" ]
127-
128116 # Check for secretId FIRST
129117 if "secretId" not in alert :
130118 logger .info ('Response: \n """\n %s\n """\n does not include "secretId"' , alert )
131119 tsv_logger = setup_tsv_logger ("mrn_error_log" , "mrn_error_log.tsv" )
132120 tsv_logger .error (str (alert ), extra = {"mrn" : "" , "attempt" : "parse_alert" })
133121 return pd .DataFrame (columns = columns )
134-
135122 answer , item = _parse_alert_message (alert ["message" ])
136123 fields : list [tuple [str , Any ]] = [("mrn" , alert ["secretId" ]), (item , answer )]
137124 data : list [tuple [str , str , Any , Optional [str ]]] = [
@@ -146,19 +133,6 @@ def parse_alert(alert: CuriousAlert) -> pd.DataFrame:
146133# ============================================================================
147134
148135
149- def _fetch_alerts_metadata () -> pd .DataFrame :
150- """Fetch alerts instrument metadata from REDCap."""
151- return fetch_api_data (
152- REDCAP_ENDPOINTS .base_url ,
153- redcap_variables .headers ,
154- {
155- "token" : PID_625 ,
156- "forms" : ALERTS_INSTRUMENT_FORM ,
157- ** METADATA_PARAMS ,
158- },
159- )
160-
161-
162136def _create_choice_lookup (
163137 alerts_instrument : pd .DataFrame ,
164138) -> dict [tuple [str , str ], int | str ]:
@@ -185,10 +159,9 @@ def _map_mrns_to_records(
185159 Returns
186160 -------
187161 tuple
188- (processed_alerts, mrn_lookup, record_events )
189- - processed_alerts: Filtered alert DataFrame
162+ (processed_alerts, mrn_lookup)
163+ - processed_alerts: Filtered alert DataFrame with event names populated
190164 - mrn_lookup: Maps MRN string to record ID integer
191- - record_events: Maps field name to event name string
192165
193166 """
194167 # Prepare data types
@@ -243,7 +216,7 @@ def process_alerts_for_redcap(
243216 """
244217 alert_fields = redcap_alerts ["field_name" ].unique ()
245218 # Fetch metadata
246- alerts_instrument = _fetch_alerts_metadata ( )
219+ alerts_instrument = fetch_alerts_metadata ( REDCAP_ENDPOINTS . base_url )
247220 # Filter fields if partial landing
248221 if partial_redcap_landing :
249222 alert_fields = intersect1d (
@@ -384,7 +357,6 @@ async def main_with_reconnect(
384357 attempt ,
385358 f" of { max_attempts } " if max_attempts else "" ,
386359 )
387-
388360 async with connect_to_websocket (token , uri ) as websocket :
389361 # Reset attempt counter on successful connection
390362 if attempt > 0 :
@@ -393,18 +365,15 @@ async def main_with_reconnect(
393365 await websocket_listener (websocket , partial_redcap_landing )
394366 logger .info ("WebSocket listener completed normally" )
395367 break
396-
397368 except ConnectionClosedError :
398369 attempt += 1
399370 if max_attempts and attempt >= max_attempts :
400371 logger .exception ("Max reconnection attempts reached. Exiting." )
401372 raise
402-
403373 logger .warning (
404374 "Connection lost. Reconnecting in %d seconds..." , WS_RECONNECT_DELAY
405375 )
406376 await asyncio .sleep (WS_RECONNECT_DELAY )
407-
408377 except InvalidStatus as e :
409378 # Authentication or server errors
410379 logger .exception (
@@ -419,15 +388,12 @@ async def main_with_reconnect(
419388 logger .exception ("Max reconnection attempts reached. Exiting." )
420389 raise
421390 await asyncio .sleep (WS_RECONNECT_DELAY )
422-
423391 except asyncio .CancelledError :
424392 logger .info ("Operation cancelled" )
425393 raise
426-
427394 except KeyboardInterrupt :
428395 logger .info ("WebSocket listener cancelled manually" )
429396 break
430-
431397 except Exception :
432398 logger .exception ("Fatal error in main loop" )
433399 raise
@@ -456,7 +422,6 @@ async def main(
456422 """
457423 tokens = curious_authenticate ()
458424 endpoints = curious_variables .Endpoints (protocol = "wss" )
459-
460425 await main_with_reconnect (
461426 token = tokens .access ,
462427 uri = endpoints .alerts ,
@@ -503,7 +468,6 @@ def cli() -> None:
503468 parser .set_defaults (partial = False , synchronous = False )
504469 namespace = _SynchronousArgs ()
505470 args = parser .parse_args (namespace = namespace )
506-
507471 if args .synchronous :
508472 synchronous_main (args .partial )
509473 else :
0 commit comments