Spaces:
Running
Running
# api_clients/rxnorm_client.py | |
""" | |
Client for the NLM RxNorm API. | |
This module is essential for the Drug Interaction & Safety Analyzer. It provides | |
functionality to standardize drug names to RxCUIs and then checks for | |
potential interactions between a list of those drugs. | |
""" | |
import asyncio | |
import aiohttp | |
from .config import RXNORM_BASE_URL, REQUEST_HEADERS | |
async def get_rxcui(session: aiohttp.ClientSession, drug_name: str) -> str | None: | |
""" | |
Converts a drug name string into its corresponding RxNorm Concept Unique Identifier (RxCUI). | |
An RxCUI is a standardized code necessary for accurately checking interactions. | |
Args: | |
session (aiohttp.ClientSession): The active HTTP session. | |
drug_name (str): The name of the drug (e.g., "Lipitor", "atorvastatin"). | |
Returns: | |
str | None: The RxCUI as a string if found, otherwise None. | |
""" | |
if not drug_name: | |
return None | |
url = f"{RXNORM_BASE_URL}/rxcui.json" | |
params = {'name': drug_name, 'search': 1} # search=1 for exact match first | |
try: | |
async with session.get(url, params=params, headers=REQUEST_HEADERS, timeout=10) as resp: | |
resp.raise_for_status() | |
data = await resp.json() | |
# The RxCUI is nested within the 'idGroup' | |
id_group = data.get('idGroup', {}) | |
if id_group and 'rxnormId' in id_group: | |
return id_group['rxnormId'][0] # Return the first and most likely CUI | |
return None | |
except aiohttp.ClientError as e: | |
print(f"Error fetching RxCUI for '{drug_name}': {e}") | |
return None | |
async def get_interactions(session: aiohttp.ClientSession, list_of_rxcuis: list[str]) -> list[dict]: | |
""" | |
Checks for interactions among a list of RxCUIs. | |
Args: | |
session (aiohttp.ClientSession): The active HTTP session. | |
list_of_rxcuis (list[str]): A list of standardized drug RxCUI strings. | |
Returns: | |
list[dict]: A list of interaction pairs, each with a description and severity. | |
Returns an empty list if no interactions are found or an error occurs. | |
""" | |
if not list_of_rxcuis or len(list_of_rxcuis) < 2: | |
return [] # Interactions require at least two drugs | |
# The API expects a space-separated string of RxCUIs | |
rxcuis_str = " ".join(list_of_rxcuis) | |
url = f"{RXNORM_BASE_URL}/interaction/list.json" | |
params = {'rxcuis': rxcuis_str} | |
try: | |
async with session.get(url, params=params, headers=REQUEST_HEADERS, timeout=15) as resp: | |
resp.raise_for_status() | |
data = await resp.json() | |
interaction_groups = data.get('fullInteractionTypeGroup', []) | |
parsed_interactions = [] | |
# The API response is deeply nested, so we parse it carefully | |
for group in interaction_groups: | |
for interaction_type in group.get('fullInteractionType', []): | |
for pair in interaction_type.get('interactionPair', []): | |
# Extract the critical information into a clean format | |
description = pair.get('description', 'No description available.') | |
severity = pair.get('severity', 'Severity unknown') | |
# Identify the two drugs involved in this specific interaction | |
drug1_name = pair['interactionConcept'][0]['minConceptItem']['name'] | |
drug2_name = pair['interactionConcept'][1]['minConceptItem']['name'] | |
parsed_interactions.append({ | |
"pair": f"{drug1_name} / {drug2_name}", | |
"severity": severity, | |
"description": description | |
}) | |
return parsed_interactions | |
except aiohttp.ClientError as e: | |
print(f"Error fetching interactions for RxCUIs '{rxcuis_str}': {e}") | |
return [] | |
async def run_interaction_check(drug_names: list[str]) -> list[dict]: | |
""" | |
High-level orchestrator for a complete drug interaction check. | |
This function handles the full workflow: | |
1. Takes a list of human-readable drug names. | |
2. Concurrently converts them all to RxCUIs. | |
3. Feeds the valid RxCUIs into the interaction checker. | |
Args: | |
drug_names (list[str]): A list of drug names from user input. | |
Returns: | |
list[dict]: A list of found interactions, ready for display or AI synthesis. | |
""" | |
async with aiohttp.ClientSession() as session: | |
# Step 1: Concurrently get RxCUIs for all provided drug names | |
rxcui_tasks = [get_rxcui(session, name) for name in drug_names] | |
resolved_rxcuis = await asyncio.gather(*rxcui_tasks) | |
# Step 2: Filter out any drugs that were not found (returned None) | |
valid_rxcuis = [rxcui for rxcui in resolved_rxcuis if rxcui] | |
if len(valid_rxcuis) < 2: | |
print("Fewer than two valid drugs found, cannot check for interactions.") | |
return [] | |
# Step 3: Check for interactions using the list of valid RxCUIs | |
interactions = await get_interactions(session, valid_rxcuis) | |
return interactions |