Update mcp/openfda.py
Browse files- mcp/openfda.py +74 -22
mcp/openfda.py
CHANGED
|
@@ -1,24 +1,76 @@
|
|
| 1 |
# mcp/openfda.py
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# mcp/openfda.py
|
| 2 |
|
| 3 |
+
#!/usr/bin/env python3
|
| 4 |
+
"""MedGenesis – **openFDA** drug‑event wrapper (async, cached).
|
| 5 |
+
|
| 6 |
+
Enhancements over the legacy helper
|
| 7 |
+
-----------------------------------
|
| 8 |
+
* Back‑off retry (2×, 4×, 8×) for intermittent 503s.
|
| 9 |
+
* `@lru_cache` (12 h, 512 keys) to spare quota.
|
| 10 |
+
* Returns empty list instead of raising on any service hiccup.
|
| 11 |
+
* Accepts optional `limit` kwarg (capped at 20).
|
| 12 |
+
* Normalises API response to `schemas.DrugSafety`‑compatible dicts.
|
| 13 |
+
|
| 14 |
+
API docs: https://open.fda.gov/apis/drug/event/
|
| 15 |
+
"""
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import asyncio, httpx
|
| 19 |
+
from functools import lru_cache
|
| 20 |
+
from typing import List, Dict
|
| 21 |
+
|
| 22 |
+
_URL = "https://api.fda.gov/drug/event.json"
|
| 23 |
+
_TIMEOUT = 15
|
| 24 |
+
_MAX_LIMIT = 20
|
| 25 |
+
|
| 26 |
+
# ---------------------------------------------------------------------
|
| 27 |
+
# Internal helper with retry
|
| 28 |
+
# ---------------------------------------------------------------------
|
| 29 |
+
async def _get(params: Dict, *, retries: int = 3) -> Dict:
|
| 30 |
+
delay = 2
|
| 31 |
+
last = None
|
| 32 |
+
for _ in range(retries):
|
| 33 |
+
async with httpx.AsyncClient(timeout=_TIMEOUT) as cli:
|
| 34 |
+
last = await cli.get(_URL, params=params)
|
| 35 |
+
if last.status_code == 200:
|
| 36 |
+
return last.json()
|
| 37 |
+
await asyncio.sleep(delay)
|
| 38 |
+
delay *= 2
|
| 39 |
+
return last.json() if last else {}
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
# ---------------------------------------------------------------------
|
| 43 |
+
# Public API – cached
|
| 44 |
+
# ---------------------------------------------------------------------
|
| 45 |
+
@lru_cache(maxsize=512)
|
| 46 |
+
async def fetch_drug_safety(drug: str, *, limit: int = 3) -> List[Dict]:
|
| 47 |
+
"""Return recent adverse‑event reports for *drug* (empty list on failure)."""
|
| 48 |
+
limit = max(1, min(limit, _MAX_LIMIT))
|
| 49 |
+
params = {
|
| 50 |
+
"search": f'patient.drug.medicinalproduct:"{drug}"',
|
| 51 |
+
"limit" : limit,
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
try:
|
| 55 |
+
data = await _get(params)
|
| 56 |
+
rows = data.get("results", [])
|
| 57 |
+
out: List[Dict] = []
|
| 58 |
+
for ev in rows:
|
| 59 |
+
out.append({
|
| 60 |
+
"safety_report_id": ev.get("safetyreportid"),
|
| 61 |
+
"serious" : ev.get("serious"),
|
| 62 |
+
"reactions" : [r.get("reactionmeddrapt") for r in ev.get("patient", {}).get("reaction", [])],
|
| 63 |
+
"receivedate" : ev.get("receivedate"),
|
| 64 |
+
})
|
| 65 |
+
return out
|
| 66 |
+
except Exception:
|
| 67 |
+
return []
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
# Quick test -----------------------------------------------------------------
|
| 71 |
+
if __name__ == "__main__":
|
| 72 |
+
import asyncio, json
|
| 73 |
+
async def _demo():
|
| 74 |
+
res = await fetch_drug_safety("temozolomide", limit=2)
|
| 75 |
+
print(json.dumps(res, indent=2))
|
| 76 |
+
asyncio.run(_demo())
|