mgbam commited on
Commit
8f682c2
·
verified ·
1 Parent(s): a0d8a91

Update api_clients/rxnorm_client.py

Browse files
Files changed (1) hide show
  1. api_clients/rxnorm_client.py +124 -0
api_clients/rxnorm_client.py CHANGED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # api_clients/rxnorm_client.py
2
+ """
3
+ Client for the NLM RxNorm API.
4
+ This module is essential for the Drug Interaction & Safety Analyzer. It provides
5
+ functionality to standardize drug names to RxCUIs and then checks for
6
+ potential interactions between a list of those drugs.
7
+ """
8
+ import asyncio
9
+ import aiohttp
10
+ from .config import RXNORM_BASE_URL, REQUEST_HEADERS
11
+
12
+ async def get_rxcui(session: aiohttp.ClientSession, drug_name: str) -> str | None:
13
+ """
14
+ Converts a drug name string into its corresponding RxNorm Concept Unique Identifier (RxCUI).
15
+
16
+ An RxCUI is a standardized code necessary for accurately checking interactions.
17
+
18
+ Args:
19
+ session (aiohttp.ClientSession): The active HTTP session.
20
+ drug_name (str): The name of the drug (e.g., "Lipitor", "atorvastatin").
21
+
22
+ Returns:
23
+ str | None: The RxCUI as a string if found, otherwise None.
24
+ """
25
+ if not drug_name:
26
+ return None
27
+
28
+ url = f"{RXNORM_BASE_URL}/rxcui.json"
29
+ params = {'name': drug_name, 'search': 1} # search=1 for exact match first
30
+
31
+ try:
32
+ async with session.get(url, params=params, headers=REQUEST_HEADERS, timeout=10) as resp:
33
+ resp.raise_for_status()
34
+ data = await resp.json()
35
+ # The RxCUI is nested within the 'idGroup'
36
+ id_group = data.get('idGroup', {})
37
+ if id_group and 'rxnormId' in id_group:
38
+ return id_group['rxnormId'][0] # Return the first and most likely CUI
39
+ return None
40
+ except aiohttp.ClientError as e:
41
+ print(f"Error fetching RxCUI for '{drug_name}': {e}")
42
+ return None
43
+
44
+ async def get_interactions(session: aiohttp.ClientSession, list_of_rxcuis: list[str]) -> list[dict]:
45
+ """
46
+ Checks for interactions among a list of RxCUIs.
47
+
48
+ Args:
49
+ session (aiohttp.ClientSession): The active HTTP session.
50
+ list_of_rxcuis (list[str]): A list of standardized drug RxCUI strings.
51
+
52
+ Returns:
53
+ list[dict]: A list of interaction pairs, each with a description and severity.
54
+ Returns an empty list if no interactions are found or an error occurs.
55
+ """
56
+ if not list_of_rxcuis or len(list_of_rxcuis) < 2:
57
+ return [] # Interactions require at least two drugs
58
+
59
+ # The API expects a space-separated string of RxCUIs
60
+ rxcuis_str = " ".join(list_of_rxcuis)
61
+ url = f"{RXNORM_BASE_URL}/interaction/list.json"
62
+ params = {'rxcuis': rxcuis_str}
63
+
64
+ try:
65
+ async with session.get(url, params=params, headers=REQUEST_HEADERS, timeout=15) as resp:
66
+ resp.raise_for_status()
67
+ data = await resp.json()
68
+
69
+ interaction_groups = data.get('fullInteractionTypeGroup', [])
70
+ parsed_interactions = []
71
+
72
+ # The API response is deeply nested, so we parse it carefully
73
+ for group in interaction_groups:
74
+ for interaction_type in group.get('fullInteractionType', []):
75
+ for pair in interaction_type.get('interactionPair', []):
76
+ # Extract the critical information into a clean format
77
+ description = pair.get('description', 'No description available.')
78
+ severity = pair.get('severity', 'Severity unknown')
79
+
80
+ # Identify the two drugs involved in this specific interaction
81
+ drug1_name = pair['interactionConcept'][0]['minConceptItem']['name']
82
+ drug2_name = pair['interactionConcept'][1]['minConceptItem']['name']
83
+
84
+ parsed_interactions.append({
85
+ "pair": f"{drug1_name} / {drug2_name}",
86
+ "severity": severity,
87
+ "description": description
88
+ })
89
+ return parsed_interactions
90
+
91
+ except aiohttp.ClientError as e:
92
+ print(f"Error fetching interactions for RxCUIs '{rxcuis_str}': {e}")
93
+ return []
94
+
95
+ async def run_interaction_check(drug_names: list[str]) -> list[dict]:
96
+ """
97
+ High-level orchestrator for a complete drug interaction check.
98
+
99
+ This function handles the full workflow:
100
+ 1. Takes a list of human-readable drug names.
101
+ 2. Concurrently converts them all to RxCUIs.
102
+ 3. Feeds the valid RxCUIs into the interaction checker.
103
+
104
+ Args:
105
+ drug_names (list[str]): A list of drug names from user input.
106
+
107
+ Returns:
108
+ list[dict]: A list of found interactions, ready for display or AI synthesis.
109
+ """
110
+ async with aiohttp.ClientSession() as session:
111
+ # Step 1: Concurrently get RxCUIs for all provided drug names
112
+ rxcui_tasks = [get_rxcui(session, name) for name in drug_names]
113
+ resolved_rxcuis = await asyncio.gather(*rxcui_tasks)
114
+
115
+ # Step 2: Filter out any drugs that were not found (returned None)
116
+ valid_rxcuis = [rxcui for rxcui in resolved_rxcuis if rxcui]
117
+
118
+ if len(valid_rxcuis) < 2:
119
+ print("Fewer than two valid drugs found, cannot check for interactions.")
120
+ return []
121
+
122
+ # Step 3: Check for interactions using the list of valid RxCUIs
123
+ interactions = await get_interactions(session, valid_rxcuis)
124
+ return interactions