Spaces:
Sleeping
Sleeping
Jonas commited on
Commit ·
06651f3
1
Parent(s): fcabd7a
Enhance app.py interfaces with caching for examples and update openfda_client.py to aggregate serious outcome data from multiple API queries.
Browse files- app.py +7 -2
- openfda_client.py +55 -34
app.py
CHANGED
|
@@ -206,6 +206,7 @@ interface1 = gr.Interface(
|
|
| 206 |
title="Top Adverse Events by Drug",
|
| 207 |
description="Find the most frequently reported adverse events for a specific medication.",
|
| 208 |
examples=[["Lisinopril"], ["Ozempic"], ["Metformin"]],
|
|
|
|
| 209 |
)
|
| 210 |
|
| 211 |
interface3 = gr.Interface(
|
|
@@ -224,7 +225,8 @@ interface3 = gr.Interface(
|
|
| 224 |
title="Serious Outcome Analysis",
|
| 225 |
description="Find the most frequently reported serious outcomes (e.g., hospitalization, death) for a specific medication.",
|
| 226 |
examples=[["Lisinopril"], ["Ozempic"], ["Metformin"]],
|
| 227 |
-
allow_flagging="never"
|
|
|
|
| 228 |
)
|
| 229 |
|
| 230 |
interface2 = gr.Interface(
|
|
@@ -237,6 +239,7 @@ interface2 = gr.Interface(
|
|
| 237 |
title="Drug/Event Pair Frequency",
|
| 238 |
description="Get the total number of reports for a specific drug and adverse event combination.",
|
| 239 |
examples=[["Lisinopril", "Cough"], ["Ozempic", "Nausea"]],
|
|
|
|
| 240 |
)
|
| 241 |
|
| 242 |
interface4 = gr.Interface(
|
|
@@ -250,6 +253,7 @@ interface4 = gr.Interface(
|
|
| 250 |
title="Time-Series Trend Plotting",
|
| 251 |
description="Plot the number of adverse event reports over time for a specific drug-event pair.",
|
| 252 |
examples=[["Lisinopril", "Cough", "Yearly"], ["Ozempic", "Nausea", "Quarterly"]],
|
|
|
|
| 253 |
)
|
| 254 |
|
| 255 |
interface5 = gr.Interface(
|
|
@@ -261,7 +265,8 @@ interface5 = gr.Interface(
|
|
| 261 |
title="Report Source Breakdown",
|
| 262 |
description="Show a pie chart breaking down the source of the reports (e.g., Consumer, Physician).",
|
| 263 |
examples=[["Lisinopril"], ["Ibuprofen"]],
|
| 264 |
-
allow_flagging="never"
|
|
|
|
| 265 |
)
|
| 266 |
|
| 267 |
demo = gr.TabbedInterface(
|
|
|
|
| 206 |
title="Top Adverse Events by Drug",
|
| 207 |
description="Find the most frequently reported adverse events for a specific medication.",
|
| 208 |
examples=[["Lisinopril"], ["Ozempic"], ["Metformin"]],
|
| 209 |
+
cache_examples=True,
|
| 210 |
)
|
| 211 |
|
| 212 |
interface3 = gr.Interface(
|
|
|
|
| 225 |
title="Serious Outcome Analysis",
|
| 226 |
description="Find the most frequently reported serious outcomes (e.g., hospitalization, death) for a specific medication.",
|
| 227 |
examples=[["Lisinopril"], ["Ozempic"], ["Metformin"]],
|
| 228 |
+
allow_flagging="never",
|
| 229 |
+
cache_examples=True,
|
| 230 |
)
|
| 231 |
|
| 232 |
interface2 = gr.Interface(
|
|
|
|
| 239 |
title="Drug/Event Pair Frequency",
|
| 240 |
description="Get the total number of reports for a specific drug and adverse event combination.",
|
| 241 |
examples=[["Lisinopril", "Cough"], ["Ozempic", "Nausea"]],
|
| 242 |
+
cache_examples=True,
|
| 243 |
)
|
| 244 |
|
| 245 |
interface4 = gr.Interface(
|
|
|
|
| 253 |
title="Time-Series Trend Plotting",
|
| 254 |
description="Plot the number of adverse event reports over time for a specific drug-event pair.",
|
| 255 |
examples=[["Lisinopril", "Cough", "Yearly"], ["Ozempic", "Nausea", "Quarterly"]],
|
| 256 |
+
cache_examples=True,
|
| 257 |
)
|
| 258 |
|
| 259 |
interface5 = gr.Interface(
|
|
|
|
| 265 |
title="Report Source Breakdown",
|
| 266 |
description="Show a pie chart breaking down the source of the reports (e.g., Consumer, Physician).",
|
| 267 |
examples=[["Lisinopril"], ["Ibuprofen"]],
|
| 268 |
+
allow_flagging="never",
|
| 269 |
+
cache_examples=True,
|
| 270 |
)
|
| 271 |
|
| 272 |
demo = gr.TabbedInterface(
|
openfda_client.py
CHANGED
|
@@ -124,6 +124,15 @@ QUALIFICATION_MAPPING = {
|
|
| 124 |
"5": "Consumer or Non-Health Professional",
|
| 125 |
}
|
| 126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
def get_top_adverse_events(drug_name: str, limit: int = 10, patient_sex: Optional[str] = None, age_range: Optional[Tuple[int, int]] = None) -> dict:
|
| 128 |
"""
|
| 129 |
Query OpenFDA to get the top adverse events for a given drug.
|
|
@@ -240,54 +249,66 @@ def get_drug_event_pair_frequency(drug_name: str, event_name: str) -> dict:
|
|
| 240 |
def get_serious_outcomes(drug_name: str, limit: int = 10) -> dict:
|
| 241 |
"""
|
| 242 |
Query OpenFDA to get the most frequent serious outcomes for a given drug.
|
| 243 |
-
|
| 244 |
|
| 245 |
Args:
|
| 246 |
drug_name (str): The name of the drug to search for.
|
| 247 |
-
limit (int):
|
| 248 |
|
| 249 |
Returns:
|
| 250 |
-
dict:
|
| 251 |
"""
|
| 252 |
if not drug_name:
|
| 253 |
return {"error": "Drug name cannot be empty."}
|
| 254 |
|
| 255 |
drug_name_processed = drug_name.lower().strip()
|
| 256 |
drug_name_processed = DRUG_SYNONYM_MAPPING.get(drug_name_processed, drug_name_processed)
|
| 257 |
-
|
| 258 |
-
|
|
|
|
| 259 |
if cache_key in cache:
|
| 260 |
return cache[cache_key]
|
| 261 |
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
return {"error": f"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
|
| 292 |
def get_time_series_data(drug_name: str, event_name: str) -> dict:
|
| 293 |
"""
|
|
|
|
| 124 |
"5": "Consumer or Non-Health Professional",
|
| 125 |
}
|
| 126 |
|
| 127 |
+
SERIOUS_OUTCOME_FIELDS = [
|
| 128 |
+
"seriousnessdeath",
|
| 129 |
+
"seriousnesslifethreatening",
|
| 130 |
+
"seriousnesshospitalization",
|
| 131 |
+
"seriousnessdisabling",
|
| 132 |
+
"seriousnesscongenitalanomali",
|
| 133 |
+
"seriousnessother",
|
| 134 |
+
]
|
| 135 |
+
|
| 136 |
def get_top_adverse_events(drug_name: str, limit: int = 10, patient_sex: Optional[str] = None, age_range: Optional[Tuple[int, int]] = None) -> dict:
|
| 137 |
"""
|
| 138 |
Query OpenFDA to get the top adverse events for a given drug.
|
|
|
|
| 249 |
def get_serious_outcomes(drug_name: str, limit: int = 10) -> dict:
|
| 250 |
"""
|
| 251 |
Query OpenFDA to get the most frequent serious outcomes for a given drug.
|
| 252 |
+
This function makes multiple API calls to count different outcome fields.
|
| 253 |
|
| 254 |
Args:
|
| 255 |
drug_name (str): The name of the drug to search for.
|
| 256 |
+
limit (int): This argument is maintained for signature consistency but is not directly used in the multi-query logic.
|
| 257 |
|
| 258 |
Returns:
|
| 259 |
+
dict: A dictionary containing aggregated results or an error.
|
| 260 |
"""
|
| 261 |
if not drug_name:
|
| 262 |
return {"error": "Drug name cannot be empty."}
|
| 263 |
|
| 264 |
drug_name_processed = drug_name.lower().strip()
|
| 265 |
drug_name_processed = DRUG_SYNONYM_MAPPING.get(drug_name_processed, drug_name_processed)
|
| 266 |
+
|
| 267 |
+
# Use a cache key for the aggregated result
|
| 268 |
+
cache_key = f"serious_outcomes_aggregated_{drug_name_processed}"
|
| 269 |
if cache_key in cache:
|
| 270 |
return cache[cache_key]
|
| 271 |
|
| 272 |
+
aggregated_results = {}
|
| 273 |
+
|
| 274 |
+
# Base search for all serious reports
|
| 275 |
+
base_search_query = f'patient.drug.medicinalproduct:"{drug_name_processed}"+AND+serious:1'
|
| 276 |
+
|
| 277 |
+
for field in SERIOUS_OUTCOME_FIELDS:
|
| 278 |
+
try:
|
| 279 |
+
# Each query counts reports where the specific seriousness field exists
|
| 280 |
+
query = f"search={base_search_query}+AND+_exists_:{field}"
|
| 281 |
+
|
| 282 |
+
time.sleep(REQUEST_DELAY_SECONDS)
|
| 283 |
+
response = requests.get(f"{API_BASE_URL}?{query}")
|
| 284 |
+
|
| 285 |
+
if response.status_code == 200:
|
| 286 |
+
data = response.json()
|
| 287 |
+
total_count = data.get("meta", {}).get("results", {}).get("total", 0)
|
| 288 |
+
if total_count > 0:
|
| 289 |
+
# Use a more readable name for the outcome
|
| 290 |
+
outcome_name = field.replace("seriousness", "").replace("anomali", "anomaly").title()
|
| 291 |
+
aggregated_results[outcome_name] = total_count
|
| 292 |
+
# Ignore 404s, as they just mean no results for that specific outcome
|
| 293 |
+
elif response.status_code != 404:
|
| 294 |
+
response.raise_for_status()
|
| 295 |
+
|
| 296 |
+
except requests.exceptions.RequestException as e:
|
| 297 |
+
return {"error": f"A network request error occurred for field {field}: {e}"}
|
| 298 |
+
|
| 299 |
+
if not aggregated_results:
|
| 300 |
+
return {"error": f"No serious outcome data found for drug: '{drug_name}'."}
|
| 301 |
+
|
| 302 |
+
# Format the results to match the expected structure for plotting
|
| 303 |
+
final_data = {
|
| 304 |
+
"results": [{"term": k, "count": v} for k, v in aggregated_results.items()]
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
# Sort results by count, descending
|
| 308 |
+
final_data["results"] = sorted(final_data["results"], key=lambda x: x['count'], reverse=True)
|
| 309 |
+
|
| 310 |
+
cache[cache_key] = final_data
|
| 311 |
+
return final_data
|
| 312 |
|
| 313 |
def get_time_series_data(drug_name: str, event_name: str) -> dict:
|
| 314 |
"""
|