-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsources.py
More file actions
120 lines (101 loc) · 4.69 KB
/
Copy pathsources.py
File metadata and controls
120 lines (101 loc) · 4.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""
Data source connectors for the Knowledge Graph Explorer.
------------------------------------------------------------
Three free, no-auth-required APIs, each answering a different question:
- PubMed (via Entrez) -> "what does the research literature say?"
- ClinicalTrials.gov -> "what active trials exist right now?"
- Wikipedia REST summary -> "what's the plain-language context?"
Pulling from more than one source is what turns this from a single-query
NLP demo into something that actually helps someone orient themselves on
a disease: the literature graph, the trials that are currently recruiting,
and a layperson summary side by side.
"""
import urllib.parse
import requests
from Bio import Entrez, Medline
CLINICAL_TRIALS_API = "https://clinicaltrials.gov/api/v2/studies"
WIKIPEDIA_SUMMARY_API = "https://en.wikipedia.org/api/rest_v1/page/summary/{}"
# Wikimedia's robot policy rejects requests with no User-Agent (403), so every
# request identifies this app per https://w.wiki/4wJS.
_HEADERS = {"User-Agent": "NeuroKnowledgeGraphExplorer/1.0 (https://github.com/lmarshall-boop/Knowledge-Graph)"}
def fetch_pubmed_abstracts(query, max_results=15, email="your_email@example.com"):
"""Search PubMed and return {"abstracts": [...], "total_count": int}.
`total_count` is PubMed's count of ALL matching records, not just the
ones pulled -- it's what lets the UI honestly say "searching across
12,482 PubMed records" instead of just "10 abstracts", since esearch
reports the full match count regardless of retmax.
Uses MEDLINE format (not a blank-line-split hack) so each record's PMID
is parsed reliably -- that PMID is what lets the UI link every extracted
relationship back to its actual source citation.
"""
Entrez.email = email
handle = Entrez.esearch(db="pubmed", term=query, retmax=max_results)
record = Entrez.read(handle)
handle.close()
ids = record["IdList"]
total_count = int(record.get("Count", 0))
if not ids:
return {"abstracts": [], "total_count": total_count}
handle = Entrez.efetch(db="pubmed", id=ids, rettype="medline", retmode="text")
records = list(Medline.parse(handle))
handle.close()
abstracts = []
for rec in records:
abstract = rec.get("AB", "").strip()
if len(abstract) < 200:
continue
abstracts.append({
"pmid": rec.get("PMID", ""),
"title": rec.get("TI", "(untitled)"),
"abstract": abstract,
})
return {"abstracts": abstracts, "total_count": total_count}
def fetch_clinical_trials(query, max_results=10):
"""Return {"trials": [...], "total_count": int} of matching ClinicalTrials.gov studies.
`total_count` (via countTotal=true) is the full count of registered
studies matching the term, not just the page pulled here.
"""
params = {
"query.term": query,
"pageSize": max_results,
"countTotal": "true",
"fields": "NCTId,BriefTitle,OverallStatus,Phase,LeadSponsorName",
}
try:
resp = requests.get(CLINICAL_TRIALS_API, params=params, headers=_HEADERS, timeout=10)
resp.raise_for_status()
except requests.RequestException:
return {"trials": [], "total_count": 0}
payload = resp.json()
trials = []
for study in payload.get("studies", []):
protocol = study.get("protocolSection", {})
ident = protocol.get("identificationModule", {})
status = protocol.get("statusModule", {})
design = protocol.get("designModule", {})
sponsor = protocol.get("sponsorCollaboratorsModule", {}).get("leadSponsor", {})
nct_id = ident.get("nctId", "")
trials.append({
"nct_id": nct_id,
"title": ident.get("briefTitle", "(untitled)"),
"status": status.get("overallStatus", "UNKNOWN"),
"phase": ", ".join(design.get("phases", [])) or "N/A",
"sponsor": sponsor.get("name", "N/A"),
"url": f"https://clinicaltrials.gov/study/{nct_id}" if nct_id else "",
})
return {"trials": trials, "total_count": payload.get("totalCount", len(trials))}
def fetch_wikipedia_summary(term):
"""Return a plain-language summary string for `term`, or None."""
title = urllib.parse.quote(term.replace(" ", "_"))
try:
resp = requests.get(WIKIPEDIA_SUMMARY_API.format(title), headers=_HEADERS, timeout=10)
if resp.status_code != 200:
return None
data = resp.json()
return {
"title": data.get("title", term),
"extract": data.get("extract", ""),
"url": data.get("content_urls", {}).get("desktop", {}).get("page", ""),
}
except requests.RequestException:
return None