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
Files changed (2) hide show
  1. app.py +7 -2
  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
- Outcomes include: death, disability, hospitalization, etc.
244
 
245
  Args:
246
  drug_name (str): The name of the drug to search for.
247
- limit (int): The maximum number of outcomes to return.
248
 
249
  Returns:
250
- dict: The JSON response from the API, or an error dictionary.
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
- cache_key = f"serious_outcomes_{drug_name_processed}_{limit}"
258
-
 
259
  if cache_key in cache:
260
  return cache[cache_key]
261
 
262
- query = (
263
- f'search=patient.drug.medicinalproduct:"{drug_name_processed}"'
264
- f'&count=reactionoutcome.exact&limit={limit}'
265
- )
266
-
267
- try:
268
- time.sleep(REQUEST_DELAY_SECONDS)
269
-
270
- response = requests.get(f"{API_BASE_URL}?{query}")
271
- response.raise_for_status()
272
-
273
- data = response.json()
274
-
275
- # Map outcome codes to human-readable names
276
- if "results" in data:
277
- for item in data["results"]:
278
- item["term"] = OUTCOME_MAPPING.get(item["term"], f"Unknown ({item['term']})")
279
-
280
- cache[cache_key] = data
281
- return data
282
-
283
- except requests.exceptions.HTTPError as http_err:
284
- if response.status_code == 404:
285
- return {"error": f"No data found for drug: '{drug_name}'. It might be misspelled or not in the database."}
286
- return {"error": f"HTTP error occurred: {http_err}"}
287
- except requests.exceptions.RequestException as req_err:
288
- return {"error": f"A network request error occurred: {req_err}"}
289
- except Exception as e:
290
- return {"error": f"An unexpected error occurred: {e}"}
 
 
 
 
 
 
 
 
 
 
 
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
  """