Update app.py
Browse files
app.py
CHANGED
@@ -1,467 +1,285 @@
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import streamlit as st
|
2 |
import requests
|
|
|
|
|
3 |
import pandas as pd
|
4 |
import matplotlib.pyplot as plt
|
5 |
import seaborn as sns
|
6 |
-
from rdkit import Chem
|
7 |
-
from rdkit.Chem import Draw
|
8 |
-
from fpdf import FPDF
|
9 |
-
import tempfile
|
10 |
-
import logging
|
11 |
-
import os
|
12 |
-
import plotly.graph_objects as go
|
13 |
-
import networkx as nx
|
14 |
-
from typing import Optional, Dict, List, Any
|
15 |
-
from datetime import datetime
|
16 |
from openai import OpenAI
|
|
|
17 |
|
18 |
-
#
|
19 |
-
#
|
20 |
-
#
|
21 |
-
st.set_page_config(
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
# -------------------------------
|
27 |
-
API_ENDPOINTS = {
|
28 |
-
"clinical_trials": "https://clinicaltrials.gov/api/v2/studies",
|
29 |
-
"pubchem": "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/{}/JSON",
|
30 |
-
"pubmed": "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi",
|
31 |
-
"fda_drug_approval": "https://api.fda.gov/drug/label.json",
|
32 |
-
"faers_adverse_events": "https://api.fda.gov/drug/event.json",
|
33 |
-
"rxnorm_rxcui": "https://rxnav.nlm.nih.gov/REST/rxcui.json",
|
34 |
-
"rxnorm_properties": "https://rxnav.nlm.nih.gov/REST/rxcui/{}/properties.json"
|
35 |
-
# RxClass and EMA endpoints are omitted due to reliability issues.
|
36 |
-
}
|
37 |
-
|
38 |
-
# -------------------------------
|
39 |
-
# TRADE-TO-GENERIC MAPPING (FALLBACK)
|
40 |
-
# -------------------------------
|
41 |
-
TRADE_TO_GENERIC = {
|
42 |
-
"tylenol": "acetaminophen",
|
43 |
-
"panadol": "acetaminophen",
|
44 |
-
"asprin": "aspirin"
|
45 |
-
}
|
46 |
-
|
47 |
-
# -------------------------------
|
48 |
-
# RETRIEVE SECRETS
|
49 |
-
# -------------------------------
|
50 |
-
OPENAI_API_KEY = st.secrets.get("OPENAI_API_KEY")
|
51 |
-
OPENFDA_KEY = st.secrets.get("OPENFDA_KEY")
|
52 |
-
PUB_EMAIL = st.secrets.get("PUB_EMAIL")
|
53 |
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
|
61 |
-
#
|
62 |
-
#
|
63 |
-
#
|
64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
|
66 |
-
def
|
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 |
if mol:
|
118 |
-
|
119 |
-
|
120 |
-
logging.error(f"Error drawing molecule: {e}")
|
121 |
-
return None
|
122 |
-
|
123 |
-
def save_pdf_report(content: str, filename: str) -> Optional[str]:
|
124 |
-
"""Save a text report as a PDF file using FPDF."""
|
125 |
-
try:
|
126 |
-
pdf = FPDF()
|
127 |
-
pdf.add_page()
|
128 |
-
pdf.set_font("Arial", size=12)
|
129 |
-
pdf.multi_cell(0, 10, content)
|
130 |
-
pdf.output(filename)
|
131 |
-
return filename
|
132 |
-
except Exception as e:
|
133 |
-
logging.error(f"Error saving PDF: {e}")
|
134 |
-
return None
|
135 |
-
|
136 |
-
@st.cache_data(show_spinner=False)
|
137 |
-
def get_clinical_trials(query: str) -> Optional[Dict]:
|
138 |
-
"""Query ClinicalTrials.gov using a term or NCT number."""
|
139 |
-
if query.upper().startswith("NCT") and query[3:].isdigit():
|
140 |
-
params = {"id": query, "fmt": "json"}
|
141 |
-
else:
|
142 |
-
params = {"query.term": query, "retmax": 10, "retmode": "json"}
|
143 |
-
return query_api(API_ENDPOINTS["clinical_trials"], params)
|
144 |
-
|
145 |
-
@st.cache_data(show_spinner=False)
|
146 |
-
def get_pubmed(query: str) -> Optional[Dict]:
|
147 |
-
"""Query PubMed using the provided search term."""
|
148 |
-
params = {"db": "pubmed", "term": query, "retmax": 10, "retmode": "json", "email": PUB_EMAIL}
|
149 |
-
return query_api(API_ENDPOINTS["pubmed"], params)
|
150 |
-
|
151 |
-
@st.cache_data(show_spinner=False)
|
152 |
-
def get_fda_approval(drug_name: str) -> Optional[Dict]:
|
153 |
-
"""Retrieve FDA approval data from openFDA."""
|
154 |
-
query = f'openfda.brand_name:"{drug_name}"'
|
155 |
-
params = {"api_key": OPENFDA_KEY, "search": query, "limit": 1}
|
156 |
-
data = query_api(API_ENDPOINTS["fda_drug_approval"], params)
|
157 |
-
if data and data.get("results"):
|
158 |
-
return data["results"][0]
|
159 |
-
return None
|
160 |
-
|
161 |
-
@st.cache_data(show_spinner=False)
|
162 |
-
def analyze_adverse_events(drug_name: str, limit: int = 5) -> Optional[Dict]:
|
163 |
-
"""Retrieve adverse events from FAERS."""
|
164 |
-
query = f'patient.drug.medicinalproduct:"{drug_name}"'
|
165 |
-
params = {"api_key": OPENFDA_KEY, "search": query, "limit": limit}
|
166 |
-
return query_api(API_ENDPOINTS["faers_adverse_events"], params)
|
167 |
-
|
168 |
-
@st.cache_data(show_spinner=False)
|
169 |
-
def get_rxnorm_rxcui(drug_name: str) -> Optional[str]:
|
170 |
-
"""Retrieve RxCUI for a given drug using RxNorm."""
|
171 |
-
url = f"{API_ENDPOINTS['rxnorm_rxcui']}?name={drug_name}"
|
172 |
-
data = query_api(url)
|
173 |
-
if data and "idGroup" in data and data["idGroup"].get("rxnormId"):
|
174 |
-
return data["idGroup"]["rxnormId"][0]
|
175 |
-
return None
|
176 |
-
|
177 |
-
@st.cache_data(show_spinner=False)
|
178 |
-
def get_rxnorm_properties(rxcui: str) -> Optional[Dict]:
|
179 |
-
"""Retrieve RxNorm properties for a given RxCUI."""
|
180 |
-
url = API_ENDPOINTS["rxnorm_properties"].format(rxcui)
|
181 |
-
return query_api(url)
|
182 |
-
|
183 |
-
# -------------------------------
|
184 |
-
# AI-DRIVEN DRUG INSIGHTS
|
185 |
-
# -------------------------------
|
186 |
-
def generate_drug_insights(drug_name: str) -> str:
|
187 |
-
"""
|
188 |
-
Gather FDA, PubChem, and RxNorm data (using trade-to-generic fallback) and build a GPT‑4 prompt
|
189 |
-
for an innovative drug analysis report.
|
190 |
-
"""
|
191 |
-
query_name = TRADE_TO_GENERIC.get(drug_name.lower(), drug_name)
|
192 |
-
fda_info = get_fda_approval(query_name)
|
193 |
-
fda_status = "Not Available"
|
194 |
-
if fda_info and fda_info.get("openfda", {}).get("brand_name"):
|
195 |
-
fda_status = ", ".join(fda_info["openfda"]["brand_name"])
|
196 |
-
pubchem_details = get_pubchem_drug_details(query_name)
|
197 |
-
if pubchem_details:
|
198 |
-
formula = pubchem_details.get("Molecular Formula", "N/A")
|
199 |
-
iupac = pubchem_details.get("IUPAC Name", "N/A")
|
200 |
-
canon_smiles = pubchem_details.get("Canonical SMILES", "N/A")
|
201 |
-
else:
|
202 |
-
formula = iupac = canon_smiles = "Not Available"
|
203 |
-
rxnorm_id = get_rxnorm_rxcui(query_name)
|
204 |
-
if rxnorm_id:
|
205 |
-
rx_props = get_rxnorm_properties(rxnorm_id)
|
206 |
-
rxnorm_info = f"RxCUI: {rxnorm_id}\nProperties: {rx_props}"
|
207 |
-
else:
|
208 |
-
rxnorm_info = "No RxNorm data available."
|
209 |
-
prompt = (
|
210 |
-
f"Provide an innovative, advanced analysis for '{drug_name}' (generic: {query_name}).\n\n"
|
211 |
-
f"**FDA Approval Status:** {fda_status}\n\n"
|
212 |
-
f"**PubChem Details:**\n"
|
213 |
-
f"- Molecular Formula: {formula}\n"
|
214 |
-
f"- IUPAC Name: {iupac}\n"
|
215 |
-
f"- Canonical SMILES: {canon_smiles}\n\n"
|
216 |
-
f"**RxNorm Info:** {rxnorm_info}\n\n"
|
217 |
-
f"Include bullet points on:\n"
|
218 |
-
f"- Pharmacogenomic considerations (e.g., genetic variations impacting drug metabolism or toxicity)\n"
|
219 |
-
f"- Potential repurposing opportunities based on drug mechanisms\n"
|
220 |
-
f"- Regulatory insights and challenges for personalized medicine\n"
|
221 |
-
f"- Recommendations for future research and data integration\n"
|
222 |
-
)
|
223 |
-
return generate_ai_content(prompt)
|
224 |
-
|
225 |
-
# -------------------------------
|
226 |
-
# STREAMLIT APP LAYOUT
|
227 |
-
# -------------------------------
|
228 |
-
tabs = st.tabs([
|
229 |
-
"💊 Drug Development",
|
230 |
-
"📊 Trial Analytics",
|
231 |
-
"🧬 Molecular Profiling",
|
232 |
-
"📜 Regulatory Intelligence",
|
233 |
-
"📚 Literature Search",
|
234 |
-
"📈 Dashboard",
|
235 |
-
"🧪 Drug Data Integration",
|
236 |
-
"🤖 AI Insights"
|
237 |
-
])
|
238 |
-
|
239 |
-
# ----- Tab 1: Drug Development -----
|
240 |
-
with tabs[0]:
|
241 |
-
st.header("AI‑Driven Drug Development Strategy")
|
242 |
-
target = st.text_input("Target Disease/Pathway:", placeholder="Enter disease mechanism or target")
|
243 |
-
target_gene = st.text_input("Target Gene (PharmGKB Accession):", placeholder="e.g., PA1234")
|
244 |
-
strategy = st.selectbox("Development Strategy:", ["First-in-class", "Me-too", "Repurposing", "Biologic"])
|
245 |
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
"
|
251 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
252 |
)
|
253 |
-
|
254 |
-
|
255 |
-
|
|
|
|
|
|
|
256 |
|
257 |
-
st.
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
264 |
|
265 |
-
st.
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
st.error(f"Network graph error: {e}")
|
278 |
-
else:
|
279 |
-
st.write("Provide a PharmGKB gene accession to retrieve pharmacogenomic data.")
|
280 |
-
|
281 |
-
# ----- Tab 2: Clinical Trial Analytics -----
|
282 |
-
with tabs[1]:
|
283 |
-
st.header("Clinical Trial Landscape Analytics")
|
284 |
-
trial_query = st.text_input("Search Clinical Trials:", placeholder="Enter condition, intervention, or NCT number")
|
285 |
-
if st.button("Analyze Trial Landscape"):
|
286 |
-
with st.spinner("Fetching trial data..."):
|
287 |
-
trials = get_clinical_trials(trial_query)
|
288 |
-
if trials and trials.get("studies"):
|
289 |
-
trial_data = []
|
290 |
-
for study in trials["studies"][:5]:
|
291 |
-
trial_data.append({
|
292 |
-
"Title": study.get("protocolSection", {}).get("identificationModule", {}).get("briefTitle", "N/A"),
|
293 |
-
"Status": study.get("protocolSection", {}).get("statusModule", {}).get("overallStatus", "N/A"),
|
294 |
-
"Phase": study.get("protocolSection", {}).get("designModule", {}).get("phases", ["Not Available"])[0],
|
295 |
-
"Enrollment": study.get("protocolSection", {}).get("designModule", {}).get("enrollmentInfo", {}).get("count", "N/A")
|
296 |
-
})
|
297 |
-
df_trials = pd.DataFrame(trial_data)
|
298 |
-
st.dataframe(df_trials)
|
299 |
-
else:
|
300 |
-
st.warning("No clinical trials found for the query.")
|
301 |
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
st.image(mol_image, caption="2D Molecular Structure")
|
337 |
else:
|
338 |
-
st.
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
with st.spinner("Compiling regulatory data..."):
|
353 |
-
fda_data = get_fda_approval(drug_prod)
|
354 |
-
fda_status = "Not Available"
|
355 |
-
if fda_data and fda_data.get("openfda", {}).get("brand_name"):
|
356 |
-
fda_status = ", ".join(fda_data["openfda"]["brand_name"])
|
357 |
-
pubchem_details = get_pubchem_drug_details(drug_prod)
|
358 |
-
if pubchem_details:
|
359 |
-
formula = pubchem_details.get("Molecular Formula", "N/A")
|
360 |
-
iupac = pubchem_details.get("IUPAC Name", "N/A")
|
361 |
-
canon_smiles = pubchem_details.get("Canonical SMILES", "N/A")
|
362 |
-
else:
|
363 |
-
formula = iupac = canon_smiles = "Not Available"
|
364 |
-
col1, col2 = st.columns(2)
|
365 |
-
with col1:
|
366 |
-
st.markdown("**FDA Status**")
|
367 |
-
st.write(fda_status)
|
368 |
-
with col2:
|
369 |
-
st.markdown("**Drug Details (PubChem)**")
|
370 |
-
st.write(f"**Molecular Formula:** {formula}")
|
371 |
-
st.write(f"**IUPAC Name:** {iupac}")
|
372 |
-
st.write(f"**Canonical SMILES:** {canon_smiles}")
|
373 |
-
report_text = (
|
374 |
-
f"### Regulatory Report for {drug_prod}\n\n"
|
375 |
-
f"**FDA Status:** {fda_status}\n\n"
|
376 |
-
f"**Molecular Formula:** {formula}\n\n"
|
377 |
-
f"**IUPAC Name:** {iupac}\n\n"
|
378 |
-
f"**Canonical SMILES:** {canon_smiles}\n"
|
379 |
-
)
|
380 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
|
381 |
-
pdf_file = save_pdf_report(report_text, tmp.name)
|
382 |
-
if pdf_file:
|
383 |
-
with open(pdf_file, "rb") as f:
|
384 |
-
st.download_button("Download Regulatory Report (PDF)", data=f, file_name=f"{drug_prod}_report.pdf", mime="application/pdf")
|
385 |
-
os.remove(pdf_file)
|
386 |
-
|
387 |
-
# ----- Tab 5: Literature Search -----
|
388 |
-
with tabs[4]:
|
389 |
-
st.header("Literature Search")
|
390 |
-
lit_query = st.text_input("Enter search query for PubMed:", placeholder="e.g., Alzheimer's disease genetics")
|
391 |
-
if st.button("Search PubMed"):
|
392 |
-
with st.spinner("Searching PubMed..."):
|
393 |
-
pubmed_results = get_pubmed(lit_query)
|
394 |
-
if pubmed_results and pubmed_results.get("esearchresult", {}).get("idlist"):
|
395 |
-
id_list = pubmed_results["esearchresult"]["idlist"]
|
396 |
-
st.subheader(f"Found {len(id_list)} PubMed Results")
|
397 |
-
for pmid in id_list:
|
398 |
-
st.markdown(f"- [PMID: {pmid}](https://pubmed.ncbi.nlm.nih.gov/{pmid}/)")
|
399 |
-
else:
|
400 |
-
st.write("No PubMed results found.")
|
401 |
-
|
402 |
-
# ----- Tab 6: Comprehensive Dashboard -----
|
403 |
-
with tabs[5]:
|
404 |
-
st.header("Comprehensive Dashboard")
|
405 |
-
kpi_fda = 5000
|
406 |
-
kpi_trials = 12000
|
407 |
-
kpi_pubs = 250000
|
408 |
-
col1, col2, col3 = st.columns(3)
|
409 |
-
col1.metric("FDA Approved Drugs", kpi_fda)
|
410 |
-
col2.metric("Ongoing Trials", kpi_trials)
|
411 |
-
col3.metric("Publications", kpi_pubs)
|
412 |
-
st.subheader("Trend Analysis")
|
413 |
-
years = list(range(2000, 2026))
|
414 |
-
approvals = [kpi_fda // len(years)] * len(years)
|
415 |
-
fig_trend, ax_trend = plt.subplots(figsize=(10, 6))
|
416 |
-
sns.lineplot(x=years, y=approvals, marker="o", ax=ax_trend)
|
417 |
-
ax_trend.set_title("FDA Approvals Over Time")
|
418 |
-
ax_trend.set_xlabel("Year")
|
419 |
-
ax_trend.set_ylabel("Number of Approvals")
|
420 |
-
st.pyplot(fig_trend)
|
421 |
-
st.subheader("Gene-Variant-Drug Network (Sample)")
|
422 |
-
sample_gene = "CYP2C19"
|
423 |
-
sample_variants = ["rs4244285", "rs12248560"]
|
424 |
-
sample_annots = {"rs4244285": ["Clopidogrel", "Omeprazole"], "rs12248560": ["Sertraline"]}
|
425 |
-
try:
|
426 |
-
net_fig = create_variant_network(sample_gene, sample_variants, sample_annots)
|
427 |
-
st.plotly_chart(net_fig, use_container_width=True)
|
428 |
-
except Exception as e:
|
429 |
-
st.error(f"Network graph error: {e}")
|
430 |
-
|
431 |
-
# ----- Tab 7: Drug Data Integration -----
|
432 |
-
with tabs[6]:
|
433 |
-
st.header("🧪 Drug Data Integration")
|
434 |
-
drug_integration = st.text_input("Enter Drug Name for API Integration:", placeholder="e.g., aspirin")
|
435 |
-
if st.button("Retrieve Drug Data"):
|
436 |
-
with st.spinner("Fetching drug data from multiple sources..."):
|
437 |
-
query_drug = TRADE_TO_GENERIC.get(drug_integration.lower(), drug_integration)
|
438 |
-
rxnorm_id = get_rxnorm_rxcui(query_drug)
|
439 |
-
if rxnorm_id:
|
440 |
-
rx_props = get_rxnorm_properties(rxnorm_id)
|
441 |
-
st.subheader("RxNorm Data")
|
442 |
-
st.write(f"RxCUI for {drug_integration}: {rxnorm_id}")
|
443 |
-
st.json(rx_props if rx_props else {"message": "No RxNorm properties found."})
|
444 |
-
else:
|
445 |
-
st.write("No RxCUI found for the given drug name.")
|
446 |
-
st.subheader("PubChem Drug Details")
|
447 |
-
pubchem_info = get_pubchem_drug_details(query_drug)
|
448 |
-
if pubchem_info:
|
449 |
-
st.write(f"**Molecular Formula:** {pubchem_info.get('Molecular Formula', 'N/A')}")
|
450 |
-
st.write(f"**IUPAC Name:** {pubchem_info.get('IUPAC Name', 'N/A')}")
|
451 |
-
st.write(f"**Canonical SMILES:** {pubchem_info.get('Canonical SMILES', 'N/A')}")
|
452 |
-
else:
|
453 |
-
st.write("No PubChem details found.")
|
454 |
-
st.subheader("RxClass Information")
|
455 |
-
st.write("RxClass data is currently unavailable via API. Consider alternative sources for drug class information.")
|
456 |
-
|
457 |
-
# ----- Tab 8: AI Insights -----
|
458 |
-
with tabs[7]:
|
459 |
-
st.header("🤖 AI Insights")
|
460 |
-
ai_drug = st.text_input("Enter Drug Name for AI-Driven Analysis:", placeholder="e.g., tylenol")
|
461 |
-
if st.button("Generate AI Insights"):
|
462 |
-
with st.spinner("Generating AI-driven insights..."):
|
463 |
-
query_ai_drug = TRADE_TO_GENERIC.get(ai_drug.lower(), ai_drug)
|
464 |
-
insights_text = generate_drug_insights(query_ai_drug)
|
465 |
-
st.subheader("AI‑Driven Drug Analysis")
|
466 |
-
st.markdown(insights_text)
|
467 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Pharma Research Intelligence Suite (PRIS) v2.0
|
3 |
+
Enterprise-Grade Drug Development Platform with Robust UI
|
4 |
+
"""
|
5 |
+
|
6 |
import streamlit as st
|
7 |
import requests
|
8 |
+
from rdkit import Chem
|
9 |
+
from rdkit.Chem import Draw
|
10 |
import pandas as pd
|
11 |
import matplotlib.pyplot as plt
|
12 |
import seaborn as sns
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
from openai import OpenAI
|
14 |
+
from typing import Optional, Dict, List, Any
|
15 |
|
16 |
+
# ======================
|
17 |
+
# Configuration
|
18 |
+
# ======================
|
19 |
+
st.set_page_config(
|
20 |
+
page_title="PRIS 2.0 - Pharma Research Platform",
|
21 |
+
page_icon="🧬",
|
22 |
+
layout="wide"
|
23 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
+
# ======================
|
26 |
+
# API Configuration
|
27 |
+
# ======================
|
28 |
+
CLINICAL_TRIALS_API = "https://clinicaltrials.gov/api/v2/studies"
|
29 |
+
FDA_API = "https://api.fda.gov/drug/label.json"
|
30 |
+
PUBCHEM_API = "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/{}/JSON"
|
31 |
|
32 |
+
# ======================
|
33 |
+
# Core Services
|
34 |
+
# ======================
|
35 |
+
class PharmaResearchService:
|
36 |
+
"""Enterprise API integration layer with advanced resilience"""
|
37 |
+
|
38 |
+
def __init__(self):
|
39 |
+
self.session = requests.Session()
|
40 |
+
self.session.headers.update({
|
41 |
+
"User-Agent": "PRIS/2.0 (Enterprise Pharma Analytics)",
|
42 |
+
"Accept": "application/json"
|
43 |
+
})
|
44 |
+
|
45 |
+
def get_clinical_trials(self, query: str) -> List[Dict]:
|
46 |
+
"""Get clinical trials with robust error handling"""
|
47 |
+
try:
|
48 |
+
response = self.session.get(
|
49 |
+
CLINICAL_TRIALS_API,
|
50 |
+
params={"query": query, "format": "json", "limit": 5},
|
51 |
+
timeout=10
|
52 |
+
)
|
53 |
+
response.raise_for_status()
|
54 |
+
data = response.json()
|
55 |
+
return data.get("studies", [])[:5]
|
56 |
+
except Exception as e:
|
57 |
+
st.error(f"Clinical trials API error: {str(e)}")
|
58 |
+
return []
|
59 |
|
60 |
+
def get_fda_approval(self, drug_name: str) -> Optional[Dict]:
|
61 |
+
"""Retrieve FDA approval information"""
|
62 |
+
try:
|
63 |
+
response = self.session.get(
|
64 |
+
FDA_API,
|
65 |
+
params={
|
66 |
+
"api_key": st.secrets["OPENFDA_KEY"],
|
67 |
+
"search": f'openfda.brand_name:"{drug_name}"',
|
68 |
+
"limit": 1
|
69 |
+
},
|
70 |
+
timeout=10
|
71 |
+
)
|
72 |
+
response.raise_for_status()
|
73 |
+
data = response.json()
|
74 |
+
return data.get("results", [None])[0]
|
75 |
+
except Exception as e:
|
76 |
+
st.error(f"FDA API error: {str(e)}")
|
77 |
+
return None
|
78 |
|
79 |
+
def get_compound_data(self, name: str) -> Optional[Dict]:
|
80 |
+
"""Get PubChem compound data"""
|
81 |
+
try:
|
82 |
+
response = self.session.get(
|
83 |
+
PUBCHEM_API.format(name),
|
84 |
+
timeout=10
|
85 |
+
)
|
86 |
+
response.raise_for_status()
|
87 |
+
data = response.json()
|
88 |
+
return data.get("PC_Compounds", [{}])[0]
|
89 |
+
except Exception as e:
|
90 |
+
st.error(f"PubChem API error: {str(e)}")
|
91 |
+
return None
|
92 |
|
93 |
+
# ======================
|
94 |
+
# UI Components
|
95 |
+
# ======================
|
96 |
+
class PharmaDashboard:
|
97 |
+
"""Professional pharmaceutical research dashboard"""
|
98 |
+
|
99 |
+
def __init__(self):
|
100 |
+
self.service = PharmaResearchService()
|
101 |
+
self._configure_styles()
|
102 |
+
|
103 |
+
def _configure_styles(self):
|
104 |
+
"""Inject custom CSS for professional appearance"""
|
105 |
+
st.markdown("""
|
106 |
+
<style>
|
107 |
+
.main {background-color: #f8f9fa;}
|
108 |
+
.block-container {padding-top: 2rem;}
|
109 |
+
.header {color: #2c3e50; border-bottom: 2px solid #3498db;}
|
110 |
+
.metric {background-color: white; padding: 1.5rem; border-radius: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);}
|
111 |
+
.plot-container {background-color: white; padding: 1rem; border-radius: 10px; margin-top: 1rem;}
|
112 |
+
</style>
|
113 |
+
""", unsafe_allow_html=True)
|
114 |
+
|
115 |
+
def render(self):
|
116 |
+
"""Main application interface"""
|
117 |
+
st.markdown("<h1 class='header'>PRIS 2.0 - Pharma Research Platform</h1>", unsafe_allow_html=True)
|
118 |
+
|
119 |
+
tab1, tab2, tab3 = st.tabs([
|
120 |
+
"🧪 Compound Analyzer",
|
121 |
+
"📊 Clinical Trials",
|
122 |
+
"📜 Regulatory Info"
|
123 |
+
])
|
124 |
+
|
125 |
+
with tab1:
|
126 |
+
self._render_compound_analyzer()
|
127 |
+
with tab2:
|
128 |
+
self._render_clinical_trials()
|
129 |
+
with tab3:
|
130 |
+
self._render_regulatory_info()
|
131 |
+
|
132 |
+
def _render_compound_analyzer(self):
|
133 |
+
"""Compound analysis section"""
|
134 |
+
st.subheader("Molecular Analysis")
|
135 |
+
col1, col2 = st.columns([1, 2])
|
136 |
+
|
137 |
+
with col1:
|
138 |
+
compound = st.text_input("Enter compound name:", "Aspirin")
|
139 |
+
if st.button("Analyze"):
|
140 |
+
with st.spinner("Analyzing compound..."):
|
141 |
+
data = self.service.get_compound_data(compound)
|
142 |
+
if data:
|
143 |
+
self._display_compound_info(data)
|
144 |
+
else:
|
145 |
+
st.warning("Compound not found")
|
146 |
|
147 |
+
with col2:
|
148 |
+
if "mol_image" in st.session_state:
|
149 |
+
st.image(st.session_state.mol_image, caption="Molecular Structure")
|
150 |
+
|
151 |
+
def _display_compound_info(self, data: Dict):
|
152 |
+
"""Show compound information"""
|
153 |
+
properties = self._extract_properties(data)
|
154 |
+
|
155 |
+
col1, col2, col3 = st.columns(3)
|
156 |
+
with col1:
|
157 |
+
st.markdown(f"**Molecular Formula** \n{properties['formula']}")
|
158 |
+
with col2:
|
159 |
+
st.markdown(f"**Molecular Weight** \n{properties['weight']}")
|
160 |
+
with col3:
|
161 |
+
st.markdown(f"**IUPAC Name** \n{properties['iupac']}")
|
162 |
+
|
163 |
+
# Generate molecular structure
|
164 |
+
mol = Chem.MolFromSmiles(properties['smiles'])
|
165 |
if mol:
|
166 |
+
img = Draw.MolToImage(mol, size=(400, 300))
|
167 |
+
st.session_state.mol_image = img
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
168 |
|
169 |
+
def _extract_properties(self, data: Dict) -> Dict:
|
170 |
+
"""Extract chemical properties from PubChem data"""
|
171 |
+
return {
|
172 |
+
"formula": next(
|
173 |
+
(p["value"]["sval"] for p in data.get("props", [])
|
174 |
+
if p.get("urn", {}).get("label") == "Molecular Formula"),
|
175 |
+
"N/A"
|
176 |
+
),
|
177 |
+
"weight": next(
|
178 |
+
(p["value"]["sval"] for p in data.get("props", [])
|
179 |
+
if p.get("urn", {}).get("label") == "Molecular Weight"),
|
180 |
+
"N/A"
|
181 |
+
),
|
182 |
+
"iupac": next(
|
183 |
+
(p["value"]["sval"] for p in data.get("props", [])
|
184 |
+
if p.get("urn", {}).get("name") == "Preferred"),
|
185 |
+
"N/A"
|
186 |
+
),
|
187 |
+
"smiles": next(
|
188 |
+
(p["value"]["sval"] for p in data.get("props", [])
|
189 |
+
if p.get("name") == "Canonical SMILES"),
|
190 |
+
"N/A"
|
191 |
)
|
192 |
+
}
|
193 |
+
|
194 |
+
def _render_clinical_trials(self):
|
195 |
+
"""Clinical trials analysis section"""
|
196 |
+
st.subheader("Clinical Trial Explorer")
|
197 |
+
query = st.text_input("Search for trials:", "Diabetes")
|
198 |
|
199 |
+
if st.button("Search Trials"):
|
200 |
+
with st.spinner("Searching clinical trials..."):
|
201 |
+
trials = self.service.get_clinical_trials(query)
|
202 |
+
if trials:
|
203 |
+
self._display_trial_results(trials)
|
204 |
+
else:
|
205 |
+
st.warning("No trials found for this query")
|
206 |
+
|
207 |
+
def _display_trial_results(self, trials: List[Dict]):
|
208 |
+
"""Display clinical trial results"""
|
209 |
+
df = pd.DataFrame([{
|
210 |
+
"NCT ID": t.get("protocolSection", {}).get("identificationModule", {}).get("nctId", "N/A"),
|
211 |
+
"Title": t.get("protocolSection", {}).get("identificationModule", {}).get("briefTitle", "N/A"),
|
212 |
+
"Status": t.get("protocolSection", {}).get("statusModule", {}).get("overallStatus", "N/A"),
|
213 |
+
"Phase": self._parse_phase(t),
|
214 |
+
"Enrollment": t.get("protocolSection", {}).get("designModule", {}).get("enrollmentInfo", {}).get("count", "N/A")
|
215 |
+
} for t in trials])
|
216 |
|
217 |
+
st.dataframe(
|
218 |
+
df,
|
219 |
+
use_container_width=True,
|
220 |
+
hide_index=True,
|
221 |
+
column_config={
|
222 |
+
"NCT ID": st.column_config.TextColumn(width="medium"),
|
223 |
+
"Title": st.column_config.TextColumn(width="large"),
|
224 |
+
"Status": st.column_config.TextColumn(width="small"),
|
225 |
+
"Phase": st.column_config.TextColumn(width="small"),
|
226 |
+
"Enrollment": st.column_config.NumberColumn(format="%d")
|
227 |
+
}
|
228 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
229 |
|
230 |
+
# Phase distribution visualization
|
231 |
+
st.subheader("Trial Phase Distribution")
|
232 |
+
self._plot_phase_distribution(df)
|
233 |
+
|
234 |
+
def _parse_phase(self, trial: Dict) -> str:
|
235 |
+
"""Extract and format trial phase"""
|
236 |
+
phases = trial.get("protocolSection", {}).get("designModule", {}).get("phases", [])
|
237 |
+
return phases[0] if phases else "N/A"
|
238 |
+
|
239 |
+
def _plot_phase_distribution(self, df: pd.DataFrame):
|
240 |
+
"""Create phase distribution visualization"""
|
241 |
+
fig, ax = plt.subplots(figsize=(8, 4))
|
242 |
+
sns.countplot(
|
243 |
+
data=df,
|
244 |
+
x="Phase",
|
245 |
+
order=sorted(df["Phase"].unique()),
|
246 |
+
palette="Blues_d",
|
247 |
+
ax=ax
|
248 |
+
)
|
249 |
+
ax.set_title("Clinical Trial Phases")
|
250 |
+
ax.set_xlabel("")
|
251 |
+
ax.set_ylabel("Number of Trials")
|
252 |
+
st.pyplot(fig)
|
253 |
+
|
254 |
+
def _render_regulatory_info(self):
|
255 |
+
"""Regulatory information section"""
|
256 |
+
st.subheader("Regulatory Intelligence")
|
257 |
+
drug_name = st.text_input("Enter drug name:", "Aspirin")
|
258 |
+
|
259 |
+
if st.button("Search Regulatory Data"):
|
260 |
+
with st.spinner("Retrieving FDA information..."):
|
261 |
+
fda_data = self.service.get_fda_approval(drug_name)
|
262 |
+
if fda_data:
|
263 |
+
self._display_fda_info(fda_data)
|
|
|
264 |
else:
|
265 |
+
st.warning("No FDA data found for this drug")
|
266 |
+
|
267 |
+
def _display_fda_info(self, data: Dict):
|
268 |
+
"""Display FDA approval information"""
|
269 |
+
openfda = data.get("openfda", {})
|
270 |
+
st.markdown(f"""
|
271 |
+
**Brand Name**: {', '.join(openfda.get('brand_name', ['N/A']))}
|
272 |
+
**Manufacturer**: {', '.join(openfda.get('manufacturer_name', ['N/A']))}
|
273 |
+
**Approval Date**: {data.get('effective_time', 'N/A')}
|
274 |
+
**Dosage Form**: {', '.join(openfda.get('dosage_form', ['N/A']))}
|
275 |
+
""")
|
276 |
+
|
277 |
+
st.subheader("Indications and Usage")
|
278 |
+
st.write(data.get("indications_and_usage", ["No information available"])[0])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
279 |
|
280 |
+
# ======================
|
281 |
+
# Application Entry
|
282 |
+
# ======================
|
283 |
+
if __name__ == "__main__":
|
284 |
+
app = PharmaDashboard()
|
285 |
+
app.render()
|