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())
|