Update app.py
Browse files
app.py
CHANGED
@@ -30,7 +30,7 @@ API_ENDPOINTS = {
|
|
30 |
"who_drugs": "https://health-products.canada.ca/api/drug/product",
|
31 |
"fda_drug_approval": "https://api.fda.gov/drug/label.json",
|
32 |
"faers_adverse_events": "https://api.fda.gov/drug/event.json",
|
33 |
-
# PharmGKB endpoints
|
34 |
"pharmgkb_variant_clinical_annotations": "https://api.pharmgkb.org/v1/data/variant/{}/clinicalAnnotations",
|
35 |
"pharmgkb_gene": "https://api.pharmgkb.org/v1/data/gene/{}", # expects PharmGKB accession
|
36 |
"pharmgkb_gene_variants": "https://api.pharmgkb.org/v1/data/gene/{}/variants",
|
@@ -43,7 +43,7 @@ API_ENDPOINTS = {
|
|
43 |
# -----------------------------------
|
44 |
OPENAI_API_KEY = st.secrets.get("OPENAI_API_KEY")
|
45 |
BIOPORTAL_API_KEY = st.secrets.get("BIOPORTAL_API_KEY")
|
46 |
-
PUB_EMAIL = st.secrets.get("PUB_EMAIL")
|
47 |
OPENFDA_KEY = st.secrets.get("OPENFDA_KEY")
|
48 |
|
49 |
if not PUB_EMAIL:
|
@@ -62,7 +62,7 @@ from openai import OpenAI
|
|
62 |
client = OpenAI(api_key=OPENAI_API_KEY)
|
63 |
|
64 |
def generate_content(prompt: str) -> str:
|
65 |
-
"""Generate content using GPT-4 via the
|
66 |
try:
|
67 |
completion = client.chat.completions.create(
|
68 |
model="gpt-4",
|
@@ -99,7 +99,7 @@ def _get_pubchem_smiles(drug_name: str) -> Optional[str]:
|
|
99 |
"""Retrieves a drug's SMILES string from PubChem."""
|
100 |
url = API_ENDPOINTS["pubchem"].format(drug_name)
|
101 |
data = _query_api(url)
|
102 |
-
if data and "PC_Compounds" in data and
|
103 |
for prop in data["PC_Compounds"][0].get("props", []):
|
104 |
if prop.get("name") == "Canonical SMILES":
|
105 |
return prop["value"]["sval"]
|
@@ -119,23 +119,21 @@ def _draw_molecule(smiles: str) -> Optional[Any]:
|
|
119 |
logging.error(f"Molecule drawing error: {e}")
|
120 |
return None
|
121 |
|
122 |
-
def _get_clinical_trials(query: str
|
123 |
-
"""Queries clinicaltrials.gov with a search term.
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
params = {"term": query, "retmax": 10, "retmode": "json"
|
131 |
-
|
|
|
132 |
|
133 |
-
def _get_pubmed(query: str
|
134 |
"""Queries PubMed using E-utilities."""
|
135 |
-
|
136 |
-
st.error("PubMed email not configured.")
|
137 |
-
return None
|
138 |
-
params = {"db": "pubmed", "term": query, "retmax": 10, "retmode": "json", "email": email}
|
139 |
return _query_api(API_ENDPOINTS["pubmed"], params)
|
140 |
|
141 |
def _get_fda_approval(drug_name: str, api_key: Optional[str] = OPENFDA_KEY) -> Optional[Dict]:
|
@@ -170,8 +168,8 @@ def _get_pharmgkb_clinical_annotations(variant_id: str) -> Optional[Dict]:
|
|
170 |
|
171 |
def _get_pharmgkb_variants_for_gene(pharmgkb_gene_id: str) -> Optional[List[str]]:
|
172 |
"""
|
173 |
-
Retrieves variant IDs for a gene using
|
174 |
-
|
175 |
"""
|
176 |
if not pharmgkb_gene_id.startswith("PA"):
|
177 |
st.warning("Please provide a valid PharmGKB accession ID for the gene (e.g., PA1234).")
|
@@ -184,10 +182,7 @@ def _get_pharmgkb_variants_for_gene(pharmgkb_gene_id: str) -> Optional[List[str]
|
|
184 |
return None
|
185 |
|
186 |
def get_pharmgkb_gene_data(pharmgkb_gene_id: str) -> Optional[Dict]:
|
187 |
-
"""
|
188 |
-
Retrieves PharmGKB gene data using a PharmGKB accession.
|
189 |
-
If the gene identifier does not start with "PA", a warning is issued.
|
190 |
-
"""
|
191 |
if not pharmgkb_gene_id.startswith("PA"):
|
192 |
st.warning("Please enter a valid PharmGKB gene accession ID (e.g., PA1234).")
|
193 |
return None
|
@@ -199,7 +194,7 @@ def get_pharmgkb_gene_data(pharmgkb_gene_id: str) -> Optional[Dict]:
|
|
199 |
return None
|
200 |
|
201 |
def scrape_ema_drug_info(drug_name: str) -> Optional[Dict]:
|
202 |
-
"""Scrapes EMA website for drug information using a browser-like header."""
|
203 |
try:
|
204 |
search_url = f"https://www.ema.europa.eu/en/search?text={drug_name.replace(' ', '+')}&type=Product"
|
205 |
headers = {
|
@@ -234,7 +229,7 @@ def scrape_ema_drug_info(drug_name: str) -> Optional[Dict]:
|
|
234 |
return None
|
235 |
|
236 |
def _get_dailymed_label(drug_name: str) -> Optional[Dict]:
|
237 |
-
"""Retrieves DailyMed label info;
|
238 |
try:
|
239 |
params = {"drug_name": drug_name, "page": 1, "pagesize": 1}
|
240 |
data = _query_api(API_ENDPOINTS["dailymed"], params)
|
@@ -474,7 +469,7 @@ with tabs[2]:
|
|
474 |
if st.button("Analyze Compound"):
|
475 |
with st.spinner("Querying PubChem..."):
|
476 |
smiles = None
|
477 |
-
# If the input is already a valid SMILES, use it; otherwise query PubChem
|
478 |
if Chem.MolFromSmiles(compound_input):
|
479 |
smiles = compound_input
|
480 |
else:
|
@@ -484,7 +479,7 @@ with tabs[2]:
|
|
484 |
if img:
|
485 |
st.image(img, caption="2D Structure")
|
486 |
else:
|
487 |
-
st.error("Compound structure not found in databases.")
|
488 |
|
489 |
pubchem_data = _query_api(API_ENDPOINTS["pubchem"].format(compound_input))
|
490 |
if pubchem_data and pubchem_data.get("PC_Compounds"):
|
@@ -515,7 +510,7 @@ with tabs[3]:
|
|
515 |
ema_info = scrape_ema_drug_info(drug_name)
|
516 |
ema_status = ema_info.get("EMA Approval Status") if ema_info else "Not Available"
|
517 |
|
518 |
-
# WHO Data from
|
519 |
who = _query_api(API_ENDPOINTS["who_drugs"], params={"name": drug_name})
|
520 |
who_status = "Yes" if who else "No"
|
521 |
|
@@ -630,7 +625,6 @@ with tabs[5]:
|
|
630 |
st.pyplot(fig)
|
631 |
|
632 |
st.subheader("Gene-Variant-Drug Network (Sample)")
|
633 |
-
# Sample network demonstration
|
634 |
sample_gene = "CYP2C19"
|
635 |
sample_variants = ["rs4244285", "rs12248560"]
|
636 |
sample_annotations = {
|
@@ -641,4 +635,17 @@ with tabs[5]:
|
|
641 |
network_fig = _create_variant_network(sample_gene, sample_variants, sample_annotations)
|
642 |
st.plotly_chart(network_fig, use_container_width=True)
|
643 |
except Exception as e:
|
644 |
-
st.error(f"Error generating network graph: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
"who_drugs": "https://health-products.canada.ca/api/drug/product",
|
31 |
"fda_drug_approval": "https://api.fda.gov/drug/label.json",
|
32 |
"faers_adverse_events": "https://api.fda.gov/drug/event.json",
|
33 |
+
# PharmGKB endpoints require a PharmGKB accession (e.g., PA1234)
|
34 |
"pharmgkb_variant_clinical_annotations": "https://api.pharmgkb.org/v1/data/variant/{}/clinicalAnnotations",
|
35 |
"pharmgkb_gene": "https://api.pharmgkb.org/v1/data/gene/{}", # expects PharmGKB accession
|
36 |
"pharmgkb_gene_variants": "https://api.pharmgkb.org/v1/data/gene/{}/variants",
|
|
|
43 |
# -----------------------------------
|
44 |
OPENAI_API_KEY = st.secrets.get("OPENAI_API_KEY")
|
45 |
BIOPORTAL_API_KEY = st.secrets.get("BIOPORTAL_API_KEY")
|
46 |
+
PUB_EMAIL = st.secrets.get("PUB_EMAIL") # Not used in clinical trials anymore.
|
47 |
OPENFDA_KEY = st.secrets.get("OPENFDA_KEY")
|
48 |
|
49 |
if not PUB_EMAIL:
|
|
|
62 |
client = OpenAI(api_key=OPENAI_API_KEY)
|
63 |
|
64 |
def generate_content(prompt: str) -> str:
|
65 |
+
"""Generate content using GPT-4 via the latest OpenAI SDK."""
|
66 |
try:
|
67 |
completion = client.chat.completions.create(
|
68 |
model="gpt-4",
|
|
|
99 |
"""Retrieves a drug's SMILES string from PubChem."""
|
100 |
url = API_ENDPOINTS["pubchem"].format(drug_name)
|
101 |
data = _query_api(url)
|
102 |
+
if data and "PC_Compounds" in data and data["PC_Compounds"]:
|
103 |
for prop in data["PC_Compounds"][0].get("props", []):
|
104 |
if prop.get("name") == "Canonical SMILES":
|
105 |
return prop["value"]["sval"]
|
|
|
119 |
logging.error(f"Molecule drawing error: {e}")
|
120 |
return None
|
121 |
|
122 |
+
def _get_clinical_trials(query: str) -> Optional[Dict]:
|
123 |
+
"""Queries clinicaltrials.gov with a search term.
|
124 |
+
Note: the email parameter has been removed as it is not required."""
|
125 |
+
# Try using 'term' and if that fails, try 'query.term'
|
126 |
+
params = {"term": query, "retmax": 10, "retmode": "json"}
|
127 |
+
data = _query_api(API_ENDPOINTS["clinical_trials"], params)
|
128 |
+
if not data:
|
129 |
+
# Fallback to alternative parameter name:
|
130 |
+
params = {"query.term": query, "retmax": 10, "retmode": "json"}
|
131 |
+
data = _query_api(API_ENDPOINTS["clinical_trials"], params)
|
132 |
+
return data
|
133 |
|
134 |
+
def _get_pubmed(query: str) -> Optional[Dict]:
|
135 |
"""Queries PubMed using E-utilities."""
|
136 |
+
params = {"db": "pubmed", "term": query, "retmax": 10, "retmode": "json", "email": PUB_EMAIL}
|
|
|
|
|
|
|
137 |
return _query_api(API_ENDPOINTS["pubmed"], params)
|
138 |
|
139 |
def _get_fda_approval(drug_name: str, api_key: Optional[str] = OPENFDA_KEY) -> Optional[Dict]:
|
|
|
168 |
|
169 |
def _get_pharmgkb_variants_for_gene(pharmgkb_gene_id: str) -> Optional[List[str]]:
|
170 |
"""
|
171 |
+
Retrieves variant IDs for a gene using its PharmGKB accession.
|
172 |
+
If an invalid accession is provided (not starting with "PA"), a warning is issued.
|
173 |
"""
|
174 |
if not pharmgkb_gene_id.startswith("PA"):
|
175 |
st.warning("Please provide a valid PharmGKB accession ID for the gene (e.g., PA1234).")
|
|
|
182 |
return None
|
183 |
|
184 |
def get_pharmgkb_gene_data(pharmgkb_gene_id: str) -> Optional[Dict]:
|
185 |
+
"""Retrieves PharmGKB gene data using a PharmGKB accession."""
|
|
|
|
|
|
|
186 |
if not pharmgkb_gene_id.startswith("PA"):
|
187 |
st.warning("Please enter a valid PharmGKB gene accession ID (e.g., PA1234).")
|
188 |
return None
|
|
|
194 |
return None
|
195 |
|
196 |
def scrape_ema_drug_info(drug_name: str) -> Optional[Dict]:
|
197 |
+
"""Scrapes the EMA website for drug information using a browser-like header."""
|
198 |
try:
|
199 |
search_url = f"https://www.ema.europa.eu/en/search?text={drug_name.replace(' ', '+')}&type=Product"
|
200 |
headers = {
|
|
|
229 |
return None
|
230 |
|
231 |
def _get_dailymed_label(drug_name: str) -> Optional[Dict]:
|
232 |
+
"""Retrieves DailyMed label info; if URL is missing, returns None."""
|
233 |
try:
|
234 |
params = {"drug_name": drug_name, "page": 1, "pagesize": 1}
|
235 |
data = _query_api(API_ENDPOINTS["dailymed"], params)
|
|
|
469 |
if st.button("Analyze Compound"):
|
470 |
with st.spinner("Querying PubChem..."):
|
471 |
smiles = None
|
472 |
+
# If the input is already a valid SMILES string, use it; otherwise, query PubChem.
|
473 |
if Chem.MolFromSmiles(compound_input):
|
474 |
smiles = compound_input
|
475 |
else:
|
|
|
479 |
if img:
|
480 |
st.image(img, caption="2D Structure")
|
481 |
else:
|
482 |
+
st.error("Compound structure not found in databases. Please provide a more specific compound name.")
|
483 |
|
484 |
pubchem_data = _query_api(API_ENDPOINTS["pubchem"].format(compound_input))
|
485 |
if pubchem_data and pubchem_data.get("PC_Compounds"):
|
|
|
510 |
ema_info = scrape_ema_drug_info(drug_name)
|
511 |
ema_status = ema_info.get("EMA Approval Status") if ema_info else "Not Available"
|
512 |
|
513 |
+
# WHO Data from Health Canada API – fallback if not found
|
514 |
who = _query_api(API_ENDPOINTS["who_drugs"], params={"name": drug_name})
|
515 |
who_status = "Yes" if who else "No"
|
516 |
|
|
|
625 |
st.pyplot(fig)
|
626 |
|
627 |
st.subheader("Gene-Variant-Drug Network (Sample)")
|
|
|
628 |
sample_gene = "CYP2C19"
|
629 |
sample_variants = ["rs4244285", "rs12248560"]
|
630 |
sample_annotations = {
|
|
|
635 |
network_fig = _create_variant_network(sample_gene, sample_variants, sample_annotations)
|
636 |
st.plotly_chart(network_fig, use_container_width=True)
|
637 |
except Exception as e:
|
638 |
+
st.error(f"Error generating network graph: {e}")
|
639 |
+
|
640 |
+
# -----------------------------
|
641 |
+
# Sidebar Information
|
642 |
+
# -----------------------------
|
643 |
+
st.sidebar.header("About")
|
644 |
+
st.sidebar.info("""
|
645 |
+
**Pharma Research Expert Platform**
|
646 |
+
|
647 |
+
An integrated tool for drug discovery, clinical research, and regulatory affairs.
|
648 |
+
|
649 |
+
**Developed by:** Your Name
|
650 |
+
**Contact:** [[email protected]](mailto:[email protected])
|
651 |
+
""")
|