Jonas commited on
Commit
c2b5b0b
·
1 Parent(s): e382e80

final fixes to add top_n. fix presentation of about section. add relative even frequency.

Browse files
Files changed (2) hide show
  1. app.py +136 -126
  2. openfda_client.py +40 -13
app.py CHANGED
@@ -22,13 +22,23 @@ def format_pair_frequency_results(data: dict, drug_name: str, event_name: str) -
22
  if "error" in data:
23
  return f"An error occurred: {data['error']}"
24
 
25
- total_reports = data.get("meta", {}).get("results", {}).get("total", 0)
 
 
 
 
 
 
 
 
 
 
26
 
27
  result = (
28
- f"Found {total_reports:,} reports for the combination of "
29
- f"'{drug_name.title()}' and '{event_name.title()}'.\n\n"
30
- "Source: FDA FAERS via OpenFDA\n"
31
- "Disclaimer: Spontaneous reports do not prove causation. Consult a healthcare professional."
32
  )
33
  return result
34
 
@@ -48,6 +58,8 @@ def top_adverse_events_tool(drug_name: str, top_n: int = 10, patient_sex: str =
48
  Returns:
49
  tuple: A Plotly figure, a Pandas DataFrame, and a summary string.
50
  """
 
 
51
  if patient_sex is None:
52
  patient_sex = "all"
53
  if min_age is None:
@@ -105,6 +117,8 @@ def serious_outcomes_tool(drug_name: str, top_n: int = 6):
105
  Returns:
106
  tuple: A Plotly figure, a Pandas DataFrame, and a summary string.
107
  """
 
 
108
  data = get_serious_outcomes(drug_name, limit=top_n)
109
 
110
  if "error" in data:
@@ -181,6 +195,8 @@ def report_source_tool(drug_name: str, top_n: int = 5):
181
  Returns:
182
  tuple: A Plotly figure, a Pandas DataFrame, and a summary string.
183
  """
 
 
184
  data = get_report_source_data(drug_name, limit=top_n)
185
 
186
  if "error" in data:
@@ -213,127 +229,121 @@ def report_source_tool(drug_name: str, top_n: int = 5):
213
  with open("gradio_readme.md", "r") as f:
214
  readme_content = f.read()
215
 
216
- interface_about = gr.Interface(
217
- fn=lambda: readme_content,
218
- inputs=[],
219
- outputs=gr.Markdown(),
220
- title="About the Medication Adverse-Event Explorer",
221
- )
222
-
223
- interface1 = gr.Interface(
224
- fn=top_adverse_events_tool,
225
- inputs=[
226
- gr.Textbox(
227
- label="Drug Name",
228
- info="Enter a brand or generic drug name (e.g., 'Aspirin', 'Lisinopril')."
229
- ),
230
- gr.Slider(
231
- 5, 50,
232
- value=10,
233
- label="Number of Events to Show",
234
- step=1
235
- ),
236
- gr.Radio(
237
- ["All", "Male", "Female"],
238
- label="Patient Sex",
239
- value="All"
240
- ),
241
- gr.Slider(
242
- 0, 120,
243
- value=0,
244
- label="Minimum Age",
245
- step=1
246
- ),
247
- gr.Slider(
248
- 0, 120,
249
- value=120,
250
- label="Maximum Age",
251
- step=1
252
- ),
253
- ],
254
- outputs=[
255
- gr.Plot(label="Top Adverse Events Chart"),
256
- gr.DataFrame(label="Top Adverse Events", interactive=False),
257
- gr.Markdown()
258
- ],
259
- title="Top Adverse Events by Drug",
260
- description="Find the most frequently reported adverse events for a specific medication.",
261
- examples=[["Lisinopril"], ["Ozempic"], ["Metformin"]],
262
- #cache_examples=True,
263
- allow_flagging="never",
264
- )
265
-
266
- interface3 = gr.Interface(
267
- fn=serious_outcomes_tool,
268
- inputs=[
269
- gr.Textbox(
270
- label="Drug Name",
271
- info="Enter a brand or generic drug name (e.g., 'Aspirin', 'Lisinopril')."
272
- ),
273
- gr.Slider(1, 6, value=6, label="Number of Outcomes to Show", step=1),
274
- ],
275
- outputs=[
276
- gr.Plot(label="Top Serious Outcomes Chart"),
277
- gr.DataFrame(label="Top Serious Outcomes", interactive=False),
278
- gr.Markdown()
279
- ],
280
- title="Serious Outcome Analysis",
281
- description="Find the most frequently reported serious outcomes (e.g., hospitalization, death) for a specific medication.",
282
- examples=[["Lisinopril"], ["Ozempic"], ["Metformin"]],
283
- allow_flagging="never",
284
- #cache_examples=True,
285
- )
286
-
287
- interface2 = gr.Interface(
288
- fn=drug_event_stats_tool,
289
- inputs=[
290
- gr.Textbox(label="Drug Name", info="e.g., 'Ibuprofen'"),
291
- gr.Textbox(label="Adverse Event", info="e.g., 'Headache'")
292
- ],
293
- outputs=[gr.Textbox(label="Report Count", lines=5)],
294
- title="Drug/Event Pair Frequency",
295
- description="Get the total number of reports for a specific drug and adverse event combination.",
296
- examples=[["Lisinopril", "Cough"], ["Ozempic", "Nausea"]],
297
- #cache_examples=True,
298
- )
299
-
300
- interface4 = gr.Interface(
301
- fn=time_series_tool,
302
- inputs=[
303
- gr.Textbox(label="Drug Name", info="e.g., 'Ibuprofen'"),
304
- gr.Textbox(label="Adverse Event", info="e.g., 'Headache'"),
305
- gr.Radio(["Yearly", "Quarterly"], label="Aggregation", value="Yearly")
306
- ],
307
- outputs=[gr.Plot(label="Report Trends")],
308
- title="Time-Series Trend Plotting",
309
- description="Plot the number of adverse event reports over time for a specific drug-event pair.",
310
- examples=[["Lisinopril", "Cough", "Yearly"], ["Ozempic", "Nausea", "Quarterly"]],
311
- #cache_examples=True,
312
- )
313
-
314
- interface5 = gr.Interface(
315
- fn=report_source_tool,
316
- inputs=[
317
- gr.Textbox(label="Drug Name", info="e.g., 'Aspirin', 'Lisinopril'"),
318
- gr.Slider(1, 5, value=5, label="Number of Sources to Show", step=1),
319
- ],
320
- outputs=[
321
- gr.Plot(label="Report Source Breakdown"),
322
- gr.DataFrame(label="Report Source Data", interactive=False),
323
- gr.Markdown()
324
- ],
325
- title="Report Source Breakdown",
326
- description="Show a pie chart breaking down the source of the reports (e.g., Consumer, Physician).",
327
- examples=[["Lisinopril"], ["Ibuprofen"]],
328
- allow_flagging="never",
329
- #cache_examples=True,
330
- )
331
-
332
- demo = gr.TabbedInterface(
333
- [interface_about, interface1, interface3, interface2, interface4, interface5],
334
- ["About", "Top Events", "Serious Outcomes", "Event Frequency", "Time-Series Trends", "Report Sources"],
335
- title="Medication Adverse-Event Explorer"
336
- )
337
 
338
  if __name__ == "__main__":
339
  demo.launch(mcp_server=True, server_name="0.0.0.0")
 
22
  if "error" in data:
23
  return f"An error occurred: {data['error']}"
24
 
25
+ results = data.get("meta", {}).get("results", {})
26
+ total_reports = results.get("total", 0)
27
+ total_for_drug = results.get("total_for_drug", 0)
28
+
29
+ percentage_string = ""
30
+ if total_for_drug > 0:
31
+ percentage = (total_reports / total_for_drug) * 100
32
+ percentage_string = (
33
+ f"\n\nThis combination accounts for **{percentage:.2f}%** of the **{total_for_drug:,}** "
34
+ f"total adverse event reports for '{drug_name.title()}' in the database."
35
+ )
36
 
37
  result = (
38
+ f"Found **{total_reports:,}** reports for the combination of "
39
+ f"'{drug_name.title()}' and '{event_name.title()}'.{percentage_string}\n\n"
40
+ "**Source**: FDA FAERS via OpenFDA\n"
41
+ "**Disclaimer**: Spontaneous reports do not prove causation. Consult a healthcare professional."
42
  )
43
  return result
44
 
 
58
  Returns:
59
  tuple: A Plotly figure, a Pandas DataFrame, and a summary string.
60
  """
61
+ if top_n is None:
62
+ top_n = 10
63
  if patient_sex is None:
64
  patient_sex = "all"
65
  if min_age is None:
 
117
  Returns:
118
  tuple: A Plotly figure, a Pandas DataFrame, and a summary string.
119
  """
120
+ if top_n is None:
121
+ top_n = 6
122
  data = get_serious_outcomes(drug_name, limit=top_n)
123
 
124
  if "error" in data:
 
195
  Returns:
196
  tuple: A Plotly figure, a Pandas DataFrame, and a summary string.
197
  """
198
+ if top_n is None:
199
+ top_n = 5
200
  data = get_report_source_data(drug_name, limit=top_n)
201
 
202
  if "error" in data:
 
229
  with open("gradio_readme.md", "r") as f:
230
  readme_content = f.read()
231
 
232
+ with gr.Blocks(title="Medication Adverse-Event Explorer") as demo:
233
+ gr.Markdown("# Medication Adverse-Event Explorer")
234
+
235
+ with gr.Tabs():
236
+ with gr.TabItem("About"):
237
+ gr.Markdown(readme_content)
238
+
239
+ with gr.TabItem("Top Events"):
240
+ gr.Interface(
241
+ fn=top_adverse_events_tool,
242
+ inputs=[
243
+ gr.Textbox(
244
+ label="Drug Name",
245
+ info="Enter a brand or generic drug name (e.g., 'Aspirin', 'Lisinopril')."
246
+ ),
247
+ gr.Slider(
248
+ 5, 50,
249
+ value=10,
250
+ label="Number of Events to Show",
251
+ step=1
252
+ ),
253
+ gr.Radio(
254
+ ["All", "Male", "Female"],
255
+ label="Patient Sex",
256
+ value="All"
257
+ ),
258
+ gr.Slider(
259
+ 0, 120,
260
+ value=0,
261
+ label="Minimum Age",
262
+ step=1
263
+ ),
264
+ gr.Slider(
265
+ 0, 120,
266
+ value=120,
267
+ label="Maximum Age",
268
+ step=1
269
+ ),
270
+ ],
271
+ outputs=[
272
+ gr.Plot(label="Top Adverse Events Chart"),
273
+ gr.DataFrame(label="Top Adverse Events", interactive=False),
274
+ gr.Markdown()
275
+ ],
276
+ title="Top Adverse Events by Drug",
277
+ description="Find the most frequently reported adverse events for a specific medication.",
278
+ examples=[["Lisinopril"], ["Ozempic"], ["Metformin"]],
279
+ allow_flagging="never",
280
+ )
281
+
282
+ with gr.TabItem("Serious Outcomes"):
283
+ gr.Interface(
284
+ fn=serious_outcomes_tool,
285
+ inputs=[
286
+ gr.Textbox(
287
+ label="Drug Name",
288
+ info="Enter a brand or generic drug name (e.g., 'Aspirin', 'Lisinopril')."
289
+ ),
290
+ gr.Slider(1, 6, value=6, label="Number of Outcomes to Show", step=1),
291
+ ],
292
+ outputs=[
293
+ gr.Plot(label="Top Serious Outcomes Chart"),
294
+ gr.DataFrame(label="Top Serious Outcomes", interactive=False),
295
+ gr.Markdown()
296
+ ],
297
+ title="Serious Outcome Analysis",
298
+ description="Find the most frequently reported serious outcomes (e.g., hospitalization, death) for a specific medication.",
299
+ examples=[["Lisinopril"], ["Ozempic"], ["Metformin"]],
300
+ allow_flagging="never",
301
+ )
302
+
303
+ with gr.TabItem("Event Frequency"):
304
+ gr.Interface(
305
+ fn=drug_event_stats_tool,
306
+ inputs=[
307
+ gr.Textbox(label="Drug Name", info="e.g., 'Ibuprofen'"),
308
+ gr.Textbox(label="Adverse Event", info="e.g., 'Headache'")
309
+ ],
310
+ outputs=[gr.Textbox(label="Report Count", lines=5)],
311
+ title="Drug/Event Pair Frequency",
312
+ description="Get the total number of reports for a specific drug and adverse event combination.",
313
+ examples=[["Lisinopril", "Cough"], ["Ozempic", "Nausea"]],
314
+ )
315
+
316
+ with gr.TabItem("Time-Series Trends"):
317
+ gr.Interface(
318
+ fn=time_series_tool,
319
+ inputs=[
320
+ gr.Textbox(label="Drug Name", info="e.g., 'Ibuprofen'"),
321
+ gr.Textbox(label="Adverse Event", info="e.g., 'Headache'"),
322
+ gr.Radio(["Yearly", "Quarterly"], label="Aggregation", value="Yearly")
323
+ ],
324
+ outputs=[gr.Plot(label="Report Trends")],
325
+ title="Time-Series Trend Plotting",
326
+ description="Plot the number of adverse event reports over time for a specific drug-event pair.",
327
+ examples=[["Lisinopril", "Cough", "Yearly"], ["Ozempic", "Nausea", "Quarterly"]],
328
+ )
329
+
330
+ with gr.TabItem("Report Sources"):
331
+ gr.Interface(
332
+ fn=report_source_tool,
333
+ inputs=[
334
+ gr.Textbox(label="Drug Name", info="e.g., 'Aspirin', 'Lisinopril'"),
335
+ gr.Slider(1, 5, value=5, label="Number of Sources to Show", step=1),
336
+ ],
337
+ outputs=[
338
+ gr.Plot(label="Report Source Breakdown"),
339
+ gr.DataFrame(label="Report Source Data", interactive=False),
340
+ gr.Markdown()
341
+ ],
342
+ title="Report Source Breakdown",
343
+ description="Show a pie chart breaking down the source of the reports (e.g., Consumer, Physician).",
344
+ examples=[["Lisinopril"], ["Ibuprofen"]],
345
+ allow_flagging="never",
346
+ )
 
 
 
 
 
 
347
 
348
  if __name__ == "__main__":
349
  demo.launch(mcp_server=True, server_name="0.0.0.0")
openfda_client.py CHANGED
@@ -210,14 +210,15 @@ def get_top_adverse_events(drug_name: str, limit: int = 10, patient_sex: Optiona
210
  def get_drug_event_pair_frequency(drug_name: str, event_name: str) -> dict:
211
  """
212
  Query OpenFDA to get the total number of reports for a specific
213
- drug-adverse event pair.
214
 
215
  Args:
216
  drug_name (str): The name of the drug.
217
  event_name (str): The name of the adverse event.
218
 
219
  Returns:
220
- dict: The JSON response from the API, or an error dictionary.
 
221
  """
222
  if not drug_name or not event_name:
223
  return {"error": "Drug name and event name cannot be empty."}
@@ -229,25 +230,51 @@ def get_drug_event_pair_frequency(drug_name: str, event_name: str) -> dict:
229
  cache_key = f"pair_freq_{drug_name_processed}_{event_name_processed}"
230
  if cache_key in cache:
231
  return cache[cache_key]
232
-
233
- query = (
234
- f'search=patient.drug.medicinalproduct:"{drug_name_processed}"'
235
- f'+AND+patient.reaction.reactionmeddrapt:"{event_name_processed}"'
236
- )
237
-
238
  try:
 
 
239
  time.sleep(REQUEST_DELAY_SECONDS)
 
240
 
241
- response = requests.get(f"{API_BASE_URL}?{query}")
242
- response.raise_for_status()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
- data = response.json()
245
  cache[cache_key] = data
246
  return data
247
 
248
  except requests.exceptions.HTTPError as http_err:
249
- if response.status_code == 404:
250
- return {"error": f"No data found for drug '{drug_name}' and event '{event_name}'. They may be misspelled or not in the database."}
251
  return {"error": f"HTTP error occurred: {http_err}"}
252
  except requests.exceptions.RequestException as req_err:
253
  return {"error": f"A network request error occurred: {req_err}"}
 
210
  def get_drug_event_pair_frequency(drug_name: str, event_name: str) -> dict:
211
  """
212
  Query OpenFDA to get the total number of reports for a specific
213
+ drug-adverse event pair, and the total reports for the drug alone.
214
 
215
  Args:
216
  drug_name (str): The name of the drug.
217
  event_name (str): The name of the adverse event.
218
 
219
  Returns:
220
+ dict: A dictionary containing the results or an error message.
221
+ Includes `total` (for the pair) and `total_for_drug`.
222
  """
223
  if not drug_name or not event_name:
224
  return {"error": "Drug name and event name cannot be empty."}
 
230
  cache_key = f"pair_freq_{drug_name_processed}_{event_name_processed}"
231
  if cache_key in cache:
232
  return cache[cache_key]
233
+
 
 
 
 
 
234
  try:
235
+ # First, get total reports for the drug to see if it exists
236
+ drug_query = f'search=patient.drug.medicinalproduct:"{drug_name_processed}"'
237
  time.sleep(REQUEST_DELAY_SECONDS)
238
+ drug_response = requests.get(f"{API_BASE_URL}?{drug_query}")
239
 
240
+ # This is a critical failure if the drug isn't found
241
+ drug_response.raise_for_status()
242
+
243
+ drug_data = drug_response.json()
244
+ total_for_drug = drug_data.get("meta", {}).get("results", {}).get("total", 0)
245
+
246
+ # Second, get reports for the specific drug-event pair
247
+ pair_query = (
248
+ f'search=patient.drug.medicinalproduct:"{drug_name_processed}"'
249
+ f'+AND+patient.reaction.reactionmeddrapt:"{event_name_processed}"'
250
+ )
251
+ time.sleep(REQUEST_DELAY_SECONDS)
252
+ pair_response = requests.get(f"{API_BASE_URL}?{pair_query}")
253
+
254
+ total_for_pair = 0
255
+ if pair_response.status_code == 200:
256
+ pair_data = pair_response.json()
257
+ total_for_pair = pair_data.get("meta", {}).get("results", {}).get("total", 0)
258
+ # We don't raise for 404 on the pair, as it just means 0 results
259
+ elif pair_response.status_code != 404:
260
+ pair_response.raise_for_status()
261
+
262
+ # Construct a consistent response structure
263
+ data = {
264
+ "meta": {
265
+ "results": {
266
+ "total": total_for_pair,
267
+ "total_for_drug": total_for_drug
268
+ }
269
+ }
270
+ }
271
 
 
272
  cache[cache_key] = data
273
  return data
274
 
275
  except requests.exceptions.HTTPError as http_err:
276
+ if http_err.response.status_code == 404:
277
+ return {"error": f"No data found for drug '{drug_name}'. It may be misspelled or not in the database."}
278
  return {"error": f"HTTP error occurred: {http_err}"}
279
  except requests.exceptions.RequestException as req_err:
280
  return {"error": f"A network request error occurred: {req_err}"}