File size: 14,623 Bytes
5095870
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
"""
EBP Research Tool for Student Nurses
Streamlit app — deployable to Hugging Face Spaces (free CPU tier).
"""

import streamlit as st

from search.pubmed import search_pubmed, fetch_abstract, fetch_summaries
from search.clinical_trials import search_trials
from summarizer import summarize, infer_evidence_level
from utils.citations import format_apa7, format_ama

# ---------------------------------------------------------------------------
# Page config (must be first Streamlit call)
# ---------------------------------------------------------------------------
st.set_page_config(
    page_title="EBP Research Tool — Student Nurses",
    page_icon="🩺",
    layout="wide",
    initial_sidebar_state="expanded",
)

# ---------------------------------------------------------------------------
# Minimal custom CSS
# ---------------------------------------------------------------------------
st.markdown(
    """
    <style>
    /* Card container */
    .ebp-card {
        border: 1px solid #d9e2ec;
        border-radius: 10px;
        padding: 1rem 1.2rem;
        margin-bottom: 1rem;
        background: #ffffff;
    }
    /* Source badges */
    .badge-pubmed  { background:#e8f4fd; color:#1558b0; padding:2px 8px;
                     border-radius:4px; font-size:0.72em; font-weight:600; }
    .badge-trials  { background:#fff3e0; color:#e65100; padding:2px 8px;
                     border-radius:4px; font-size:0.72em; font-weight:600; }
    /* Evidence level colours */
    .ev-I   { color:#1b7c1b; font-weight:700; }
    .ev-II  { color:#7c6b00; font-weight:700; }
    .ev-III { color:#b04e00; font-weight:700; }
    .ev-IV  { color:#b04e00; font-weight:700; }
    .ev-V   { color:#005f8c; font-weight:700; }
    .ev-VI  { color:#005f8c; font-weight:700; }
    .ev-VII { color:#333;    font-weight:700; }
    /* Slightly tighter headings */
    h1 { margin-bottom: 0 !important; }
    </style>
    """,
    unsafe_allow_html=True,
)

# ---------------------------------------------------------------------------
# Session state initialisation
# ---------------------------------------------------------------------------
_DEFAULTS = {
    "saved_articles":   [],
    "search_results":   [],
    "search_query":     "",
    "abstracts":        {},   # pmid → abstract text
    "summaries":        {},   # pmid → summary dict
}
for _k, _v in _DEFAULTS.items():
    if _k not in st.session_state:
        st.session_state[_k] = _v


# ---------------------------------------------------------------------------
# Helper: render one article card
# ---------------------------------------------------------------------------
def render_card(article: dict, card_idx: int, tab_key: str) -> None:
    pmid    = article.get("pmid", str(card_idx))
    title   = article.get("title", "No title")
    authors = article.get("authors", [])
    journal = article.get("journal", "")
    year    = article.get("year", "")
    url     = article.get("url", "")
    source  = article.get("source", "PubMed")

    badge_cls = "badge-pubmed" if source == "PubMed" else "badge-trials"
    badge_lbl = "PubMed" if source == "PubMed" else "ClinicalTrials"

    # Infer evidence level from whatever we already have
    cached_abstract = st.session_state.abstracts.get(pmid, "")
    ev_code, ev_emoji, ev_desc = infer_evidence_level(title, cached_abstract)

    with st.container():
        st.markdown(
            f'<span class="{badge_cls}">{badge_lbl}</span> '
            f'<span style="font-size:0.8em; color:#555;">{ev_emoji} {ev_code}{ev_desc}</span>',
            unsafe_allow_html=True,
        )
        st.markdown(f"**{title}**")
        meta_parts = []
        if authors:
            meta_parts.append(", ".join(authors[:3]) + (" …" if len(authors) > 3 else ""))
        if journal:
            meta_parts.append(f"*{journal}*")
        if year:
            meta_parts.append(year)
        if meta_parts:
            st.caption(" · ".join(meta_parts))

        # Action row
        col_a, col_b, col_c, col_d, _ = st.columns([1.5, 1.5, 1.5, 1.5, 4])

        with col_a:
            load_key = f"load_{tab_key}_{card_idx}"
            if st.button("📄 Abstract", key=load_key, use_container_width=True):
                if pmid not in st.session_state.abstracts or not st.session_state.abstracts[pmid]:
                    with st.spinner("Loading…"):
                        ab = article.get("abstract") or fetch_abstract(pmid)
                        st.session_state.abstracts[pmid] = ab
                # toggle — if summary shown, clear it so only abstract shows
                if pmid in st.session_state.summaries:
                    del st.session_state.summaries[pmid]

        with col_b:
            sum_key = f"sum_{tab_key}_{card_idx}"
            if st.button("🧠 Summarise", key=sum_key, use_container_width=True):
                if pmid not in st.session_state.abstracts or not st.session_state.abstracts[pmid]:
                    with st.spinner("Loading abstract…"):
                        ab = article.get("abstract") or fetch_abstract(pmid)
                        st.session_state.abstracts[pmid] = ab
                with st.spinner("Summarising…"):
                    st.session_state.summaries[pmid] = summarize(
                        st.session_state.abstracts[pmid], title
                    )

        with col_c:
            save_key = f"save_{tab_key}_{card_idx}"
            already_saved = any(a.get("pmid") == pmid for a in st.session_state.saved_articles)
            if already_saved:
                st.button("✅ Saved", key=save_key, disabled=True, use_container_width=True)
            else:
                if st.button("💾 Save", key=save_key, use_container_width=True):
                    st.session_state.saved_articles.append(article)
                    st.rerun()

        with col_d:
            if url:
                st.link_button("🔗 Full Article", url, use_container_width=True)

        # Abstract pane
        if pmid in st.session_state.abstracts and st.session_state.abstracts[pmid]:
            if pmid not in st.session_state.summaries:
                with st.expander("Abstract", expanded=True):
                    raw = st.session_state.abstracts[pmid]
                    # Render **LABEL:** markdown nicely
                    st.markdown(raw)

        # Summary / nursing card pane
        if pmid in st.session_state.summaries:
            s = st.session_state.summaries[pmid]
            ev_c, ev_e, ev_d = s["evidence_level"]
            with st.expander("Nursing Summary Card", expanded=True):
                cols2 = st.columns(2)
                with cols2[0]:
                    st.markdown("**Overview / Background**")
                    st.write(s["overview"] or "—")
                    if s.get("methods"):
                        st.markdown("**Methods**")
                        st.write(s["methods"])
                with cols2[1]:
                    st.markdown("**Key Findings**")
                    st.write(s["key_findings"] or "—")
                    st.markdown("**Nursing Implications**")
                    st.info(s["nursing_implications"] or "—")

                st.markdown(f"**Evidence Level:** {ev_e} {ev_c}{ev_d}")

                # APA citation
                st.markdown("**APA 7th Citation**")
                st.code(format_apa7(article), language=None)

        st.divider()


# ---------------------------------------------------------------------------
# Sidebar — filters
# ---------------------------------------------------------------------------
with st.sidebar:
    st.markdown("## 🔬 Search Filters")
    st.divider()

    databases = st.multiselect(
        "Databases",
        ["PubMed", "ClinicalTrials.gov"],
        default=["PubMed"],
    )

    date_range = st.radio(
        "Publication date",
        ["Last 1 year", "Last 2 years", "Last 5 years", "Last 10 years", "All time"],
        index=2,
    )

    study_types = st.multiselect(
        "Study design",
        ["Any", "Systematic Review", "Meta-Analysis", "RCT", "Cohort Study", "Case Study"],
        default=["Any"],
    )

    nursing_focus = st.toggle("Nursing-focused results only", value=True)
    max_results   = st.slider("Results per database", min_value=5, max_value=30, value=15)

    st.divider()
    st.markdown(
        """
**Evidence Level Guide**
🟢 Level I — Systematic Review / Meta-Analysis
🟡 Level II — RCT
🟠 Level III–IV — Quasi / Cohort
🔵 Level V–VI — Qualitative
⚫ Level VII — Expert Opinion
⚪ Unclassified
        """
    )


# ---------------------------------------------------------------------------
# Header
# ---------------------------------------------------------------------------
st.title("🩺 EBP Research Tool")
st.caption(
    "Evidence-Based Practice search for student nurses · "
    "PubMed (30 M+ articles) · ClinicalTrials.gov · Free & open"
)

# Quick-topic shortcuts
QUICK_TOPICS = [
    ("Wound Care",         "wound care nursing interventions"),
    ("Pain Management",    "pain management nursing practice"),
    ("Fall Prevention",    "fall prevention hospital nursing"),
    ("Med Safety",         "medication safety nursing errors"),
    ("Infection Control",  "hand hygiene infection control nursing"),
    ("Pt Education",       "patient education nursing outcomes"),
    ("Mental Health",      "mental health nursing interventions"),
    ("Paediatric Care",    "paediatric nursing care outcomes"),
]

qt_cols = st.columns(4)
for i, (label, query) in enumerate(QUICK_TOPICS):
    if qt_cols[i % 4].button(label, use_container_width=True, key=f"qt_{i}"):
        st.session_state.search_query = query
        st.rerun()

st.divider()

# ---------------------------------------------------------------------------
# Tabs
# ---------------------------------------------------------------------------
n_saved = len(st.session_state.saved_articles)
tab_search, tab_library, tab_cite = st.tabs(
    ["🔍 Search", f"📚 My Library ({n_saved})", "📝 Citation Builder"]
)

# ============================= SEARCH TAB ==================================
with tab_search:
    col_q, col_btn = st.columns([5, 1])
    with col_q:
        query = st.text_input(
            "search_box",
            value=st.session_state.search_query,
            placeholder="e.g. pressure injury prevention nursing ICU",
            label_visibility="collapsed",
        )
    with col_btn:
        do_search = st.button("Search", type="primary", use_container_width=True)

    if do_search and query.strip():
        st.session_state.search_query = query.strip()
        with st.spinner(f"Searching {', '.join(databases)}…"):
            results: list[dict] = []
            if "PubMed" in databases:
                results += search_pubmed(
                    query, date_range, study_types, nursing_focus, max_results
                )
            if "ClinicalTrials.gov" in databases:
                results += search_trials(query, max(5, max_results // 2))
            st.session_state.search_results = results

    results = st.session_state.search_results
    if results:
        st.success(f"**{len(results)}** results")
        for idx, art in enumerate(results):
            render_card(art, idx, "search")
    elif st.session_state.search_query:
        st.warning("No results found — try broader keywords or adjust filters.")
    else:
        st.info("Enter a topic above or choose a Quick Topic to begin.")

# ============================= LIBRARY TAB =================================
with tab_library:
    if not st.session_state.saved_articles:
        st.info("Your library is empty. Search and click 💾 Save to add articles here.")
    else:
        st.write(f"**{n_saved} saved article{'s' if n_saved != 1 else ''}**")

        # Export all citations
        if st.button("📋 Copy All Citations (APA 7th)"):
            all_cites = "\n\n".join(format_apa7(a) for a in st.session_state.saved_articles)
            st.code(all_cites, language=None)

        if st.button("🗑️ Clear Library", type="secondary"):
            st.session_state.saved_articles = []
            st.rerun()

        st.divider()
        for idx, art in enumerate(st.session_state.saved_articles):
            render_card(art, idx, "library")

# ============================= CITE BUILDER TAB ============================
with tab_cite:
    st.subheader("Citation Builder")
    st.write(
        "Enter a PubMed ID to instantly generate a formatted reference "
        "ready to paste into your assignment."
    )

    col1, col2, col3 = st.columns([2, 2, 1])
    with col1:
        pmid_input = st.text_input("PubMed ID (PMID)", placeholder="e.g. 33721172")
    with col2:
        cite_style = st.selectbox("Citation style", ["APA 7th Edition", "AMA"])
    with col3:
        st.write("")  # vertical align
        st.write("")
        gen_cite = st.button("Generate", type="primary", use_container_width=True)

    if gen_cite and pmid_input.strip():
        with st.spinner("Fetching article metadata…"):
            arts = fetch_summaries([pmid_input.strip()])
        if arts:
            art = arts[0]
            citation = format_apa7(art) if cite_style == "APA 7th Edition" else format_ama(art)
            st.success("Citation ready — copy the text below:")
            st.code(citation, language=None)
            st.markdown(
                f"**Title:** {art['title']}  \n"
                f"**Journal:** {art.get('journal','')} · **Year:** {art.get('year','')}  \n"
                f"[View on PubMed]({art.get('url','')})"
            )
        else:
            st.error("Article not found. Check the PMID and try again.")

    st.divider()
    st.markdown(
        """
**Formatting guide — APA 7th (nursing standard)**

> Author, F. M., & Author, F. M. (Year). Title of article. *Journal Name*, *Volume*(Issue), Pages. https://doi.org/xxxxx

**Tips for nursing assignments:**
- Always verify DOI links are active before submitting
- For articles with no DOI, include the journal homepage URL
- Use the exact journal abbreviation from the article, not your own
        """
    )

# ---------------------------------------------------------------------------
# Footer
# ---------------------------------------------------------------------------
st.divider()
st.caption(
    "Data sourced from PubMed / NCBI (NIH) and ClinicalTrials.gov — "
    "both public, freely available databases. "
    "Always verify information against original sources before clinical application."
)