Skip to content

Commit 0c7b949

Browse files
authored
🚀 Switch project status from "dev" to "prod" (#18)
2 parents 040a8e4 + 40421ec commit 0c7b949

20 files changed

Lines changed: 1677 additions & 98 deletions

‎CHANGELOG.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## 1.6.1
9+
10+
### Added
11+
12+
- `no alerts triggered` set for potential alerts that did completed without triggering
13+
14+
## 1.6.0
15+
16+
### Changed
17+
18+
- REDCap status changed from dev to prod
19+
820
## 1.5.0
921

1022
### Added

‎VERSION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.5.0
1+
1.6.1

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hbnmigration",
3-
"version": "1.5.0",
3+
"version": "1.6.1",
44
"private": true,
55
"description": "HBN data migration monitoring infrastructure with Python and Node.js services",
66
"workspaces": [

‎python_jobs/VERSION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.5.0
1+
1.6.1
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
"""Custom exceptions."""
22

3+
from .logging import TSVLoggedError
4+
35

46
class NoData(Exception): # noqa: N818
57
"""No data to process."""
68

7-
...
9+
10+
__all__ = ["NoData", "TSVLoggedError"]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Logging exceptions."""
2+
3+
from typing import Optional
4+
5+
from ..utility_functions.logging import setup_tsv_logger
6+
7+
8+
class TSVLoggedError(Exception):
9+
"""Exception that logs to TSV."""
10+
11+
logger = setup_tsv_logger("mrn_error_log", "mrn_error_log.tsv")
12+
13+
def __init__(self, mrn: int | str, error_message: Optional[str], attempt: str):
14+
"""Initialize TSV logging exception."""
15+
self.mrn = mrn
16+
self.error_message = error_message or ""
17+
self.attempt = attempt
18+
19+
# Log using logging library
20+
self.logger.error(error_message, extra={"mrn": mrn, "attempt": attempt})
21+
22+
super().__init__(f"MRN {mrn}: {error_message} (Attempt {attempt})")

‎python_jobs/src/hbnmigration/from_curious/alerts_to_redcap.py‎

Lines changed: 9 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,25 @@
2222
from ..from_redcap.from_redcap import fetch_data, response_index_reverse_lookup
2323
from ..utility_functions import (
2424
CuriousAlert,
25-
fetch_api_data,
2625
initialize_logging,
2726
redcap_api_push,
2827
setup_tsv_logger,
2928
)
3029
from .config import curious_authenticate
30+
from .utils import (
31+
fetch_alerts_metadata,
32+
REDCAP_TOKEN,
33+
)
3134

3235
initialize_logging()
3336
logger = logging.getLogger(__name__)
3437
REDCAP_ENDPOINTS = redcap_variables.Endpoints()
38+
3539
# ============================================================================
3640
# Constants
3741
# ============================================================================
38-
ALERTS_INSTRUMENT_FORM = "ra_alerts_child,ra_alerts_parent"
3942
ALERT_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
4346
WS_RECONNECT_DELAY = 5 # seconds
@@ -46,20 +49,6 @@
4649
WS_PING_TIMEOUT = 10 # seconds
4750
WS_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"""\ndoes 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-
162136
def _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

Comments
 (0)