| | """ |
| | Medication Safety Checker |
| | Streamlit app β Hugging Face Spaces (free CPU tier) |
| | """ |
| |
|
| | import streamlit as st |
| |
|
| | from safety.high_alert import (get_all_drugs as ha_all, get_categories as ha_cats, |
| | search_drugs as ha_search, get_by_category, is_high_alert, |
| | HIGH_ALERT_DRUGS) |
| | from safety.lasa import (get_all_pairs, search_lasa, get_critical_pairs, |
| | get_high_risk_pairs, RISK_COLOURS as LASA_RISK_COLOURS, |
| | TYPE_ICONS) |
| | from safety.interactions import (check_all_interactions, check_interactions_local, |
| | SEVERITY_COLOURS, SEVERITY_ICONS) |
| | from safety.dose_checker import (search_drug, check_dose, get_drug_names, DOSE_DATABASE) |
| |
|
| | |
| | |
| | |
| | st.set_page_config( |
| | page_title="Medication Safety Checker β Student Nurses", |
| | page_icon="π", |
| | layout="wide", |
| | initial_sidebar_state="expanded", |
| | ) |
| |
|
| | |
| | |
| | |
| | st.markdown(""" |
| | <style> |
| | .safety-card { |
| | background:#f8fafc; border:1px solid #d0dae8; |
| | border-radius:10px; padding:1.2rem 1.4rem; margin-bottom:1rem; |
| | } |
| | .high-alert-banner { |
| | background:#fff3e0; border-left:5px solid #e65100; |
| | padding:0.8rem 1.2rem; border-radius:4px; margin-bottom:0.8rem; |
| | } |
| | .critical-banner { |
| | background:#fce4ec; border-left:5px solid #c62828; |
| | padding:0.8rem 1.2rem; border-radius:4px; margin-bottom:0.8rem; |
| | } |
| | .safe-banner { |
| | background:#e8f5e9; border-left:5px solid #2e7d32; |
| | padding:0.8rem 1.2rem; border-radius:4px; margin-bottom:0.8rem; |
| | } |
| | .info-banner { |
| | background:#e3f2fd; border-left:5px solid #1565c0; |
| | padding:0.8rem 1.2rem; border-radius:4px; margin-bottom:0.8rem; |
| | } |
| | .warn-banner { |
| | background:#fff8e1; border-left:5px solid #f9a825; |
| | padding:0.8rem 1.2rem; border-radius:4px; margin-bottom:0.8rem; |
| | } |
| | .risk-critical { color:#c62828; font-weight:800; } |
| | .risk-high { color:#e65100; font-weight:700; } |
| | .risk-moderate { color:#f9a825; font-weight:700; } |
| | .risk-low { color:#2e7d32; font-weight:600; } |
| | .badge-critical { background:#fce4ec; color:#c62828; padding:3px 10px; |
| | border-radius:12px; font-size:0.78em; font-weight:800; } |
| | .badge-high { background:#fff3e0; color:#e65100; padding:3px 10px; |
| | border-radius:12px; font-size:0.78em; font-weight:700; } |
| | .badge-moderate{ background:#fff8e1; color:#e65100; padding:3px 10px; |
| | border-radius:12px; font-size:0.78em; font-weight:600; } |
| | .badge-low { background:#e8f5e9; color:#2e7d32; padding:3px 10px; |
| | border-radius:12px; font-size:0.78em; font-weight:600; } |
| | </style> |
| | """, unsafe_allow_html=True) |
| |
|
| |
|
| | |
| | |
| | |
| | def risk_badge(risk: str) -> str: |
| | cls = f"badge-{risk.lower()}" |
| | return f'<span class="{cls}">{risk}</span>' |
| |
|
| |
|
| | def severity_badge(sev: str) -> str: |
| | icon = SEVERITY_ICONS.get(sev.lower(), "βͺ") |
| | cls = f"badge-{sev.lower()}" if sev.lower() in ("critical","high","moderate","low","minor") else "badge-moderate" |
| | return f'{icon} <span class="{cls}">{sev.upper()}</span>' |
| |
|
| |
|
| | |
| | |
| | |
| | st.title("π Medication Safety Checker") |
| | st.caption( |
| | "ISMP High-Alert Medications Β· Drug Interactions Β· Safe Dose Ranges Β· " |
| | "LASA Drug Warnings Β· Five Rights Checklist Β· For educational purposes only" |
| | ) |
| |
|
| | |
| | |
| | |
| | with st.sidebar: |
| | st.markdown("## π Medication Safety") |
| | st.divider() |
| | st.markdown(""" |
| | **Quick Reference** |
| | - π΄ CRITICAL β extreme caution / specialty setting |
| | - π HIGH β significant error potential |
| | - π‘ MODERATE β important precautions |
| | - π’ LOW β standard care |
| | |
| | **ISMP High-Alert Categories** |
| | - Anticoagulants |
| | - Concentrated Electrolytes |
| | - Insulin (all types) |
| | - Opioids |
| | - Neuromuscular Blocking Agents |
| | - Antineoplastics |
| | """) |
| | st.divider() |
| | st.markdown(""" |
| | **β οΈ Disclaimer** |
| | |
| | This tool is for **educational purposes only**. Always consult current clinical guidelines, pharmacy resources, and your institution's protocols before administering any medication. |
| | """) |
| | st.divider() |
| | st.markdown( |
| | "Part of the [Nursing Citizen Development](https://huggingface.co/NurseCitizenDeveloper) suite" |
| | ) |
| |
|
| |
|
| | |
| | |
| | |
| | tab_ha, tab_inter, tab_dose, tab_lasa, tab_5rights = st.tabs([ |
| | "π High-Alert Meds", |
| | "β‘ Drug Interactions", |
| | "π Dose Safety Check", |
| | "π€ LASA Warnings", |
| | "β
Five Rights", |
| | ]) |
| |
|
| |
|
| | |
| | with tab_ha: |
| | st.subheader("π ISMP High-Alert Medication Reference") |
| | st.caption("Based on the Institute for Safe Medication Practices (ISMP) 2023 High-Alert Medications list.") |
| |
|
| | col_search, col_cat = st.columns([2, 2]) |
| | with col_search: |
| | ha_query = st.text_input("π Search drug name", placeholder="e.g. heparin, insulin, morphine") |
| | with col_cat: |
| | categories = ["All categories"] + ha_cats() |
| | sel_cat = st.selectbox("Filter by category", categories) |
| |
|
| | st.divider() |
| |
|
| | |
| | if ha_query.strip(): |
| | drugs = ha_search(ha_query) |
| | if not drugs: |
| | st.warning(f"No high-alert medications found matching '{ha_query}'. Try generic or brand name.") |
| | else: |
| | st.success(f"Found {len(drugs)} high-alert medication(s) matching '{ha_query}'") |
| | elif sel_cat != "All categories": |
| | drugs = get_by_category(sel_cat) |
| | else: |
| | drugs = ha_all() |
| |
|
| | if not ha_query.strip() and sel_cat == "All categories": |
| | st.info("π‘ Search for a drug above or filter by category to view high-alert details. " |
| | f"Database contains **{len(drugs)} high-alert medications** across {len(ha_cats())} categories.") |
| |
|
| | |
| | for cat in ha_cats(): |
| | cat_drugs = get_by_category(cat) |
| | names = ", ".join(d["name"] for d in cat_drugs) |
| | st.markdown(f"**{cat}** β {names}") |
| | else: |
| | for drug in drugs: |
| | rl = drug["risk_level"] |
| | banner_class = "critical-banner" if rl == "CRITICAL" else "high-alert-banner" |
| | icon = "π΄" if rl == "CRITICAL" else "π " |
| |
|
| | with st.expander( |
| | f"{icon} {drug['name']} β {drug['category']}", |
| | expanded=len(drugs) == 1 |
| | ): |
| | col1, col2 = st.columns([3, 1]) |
| | with col1: |
| | st.markdown(f"**Generic:** {drug['generic'].title()}") |
| | st.markdown(f"**Category:** {drug['category']} Β· **ISMP Scope:** {drug['ismp_class']}") |
| | with col2: |
| | st.markdown( |
| | f'<div style="text-align:right">{risk_badge(rl)}</div>', |
| | unsafe_allow_html=True |
| | ) |
| |
|
| | |
| | st.markdown("**β οΈ Key Safety Warnings:**") |
| | for w in drug["warnings"]: |
| | st.markdown( |
| | f'<div class="{banner_class}">β οΈ {w}</div>', |
| | unsafe_allow_html=True |
| | ) |
| |
|
| | col_a, col_b = st.columns(2) |
| |
|
| | with col_a: |
| | st.markdown("**π©Ί Nursing Considerations:**") |
| | for n in drug["nursing_considerations"]: |
| | st.markdown(f"β’ {n}") |
| |
|
| | with col_b: |
| | st.markdown("**π Monitoring Parameters:**") |
| | for m in drug["monitoring"]: |
| | st.markdown(f"β’ {m}") |
| |
|
| | if drug.get("antidote"): |
| | st.markdown( |
| | f'<div class="info-banner">π <b>Antidote / Reversal:</b> {drug["antidote"]}</div>', |
| | unsafe_allow_html=True |
| | ) |
| |
|
| | if drug.get("lasa_risk"): |
| | st.markdown( |
| | f'<div class="warn-banner">π€ <b>LASA Risk:</b> May be confused with: {", ".join(drug["lasa_risk"])}</div>', |
| | unsafe_allow_html=True |
| | ) |
| |
|
| | st.markdown(f"**Routes:** {', '.join(drug['route'])}") |
| |
|
| |
|
| | |
| | with tab_inter: |
| | st.subheader("β‘ Drug Interaction Checker") |
| | st.caption("Enter 2β5 medications to check for clinically significant interactions. Uses NIH RxNorm API + curated clinical database.") |
| |
|
| | st.markdown("**Enter medication names (one per line):**") |
| | drug_input = st.text_area( |
| | "Medications", |
| | placeholder="e.g.\nwarfarin\naspirin\namiodarone", |
| | height=140, |
| | label_visibility="collapsed", |
| | ) |
| |
|
| | col_check, col_clear = st.columns([3, 1]) |
| | with col_check: |
| | check_btn = st.button("β‘ Check Interactions", type="primary", use_container_width=True) |
| | with col_clear: |
| | if st.button("π Clear", use_container_width=True): |
| | st.rerun() |
| |
|
| | st.divider() |
| |
|
| | |
| | st.markdown("**π‘ Quick check β common combinations:**") |
| | q_cols = st.columns(4) |
| | quick_combos = [ |
| | ("Warfarin + Aspirin", ["warfarin", "aspirin"]), |
| | ("Morphine + Lorazepam", ["morphine", "lorazepam"]), |
| | ("Metformin + Contrast", ["metformin", "iodinated contrast"]), |
| | ("SSRI + Tramadol", ["sertraline", "tramadol"]), |
| | ] |
| | for i, (label, combo) in enumerate(quick_combos): |
| | if q_cols[i].button(label, use_container_width=True, key=f"quick_{i}"): |
| | drug_input = "\n".join(combo) |
| | check_btn = True |
| |
|
| | if check_btn and drug_input.strip(): |
| | drug_list = [d.strip() for d in drug_input.strip().splitlines() if d.strip()] |
| | drug_list = list(dict.fromkeys(drug_list)) |
| |
|
| | if len(drug_list) < 2: |
| | st.error("Please enter at least 2 medications.") |
| | elif len(drug_list) > 5: |
| | st.warning("Maximum 5 drugs per check. Only the first 5 will be checked.") |
| | drug_list = drug_list[:5] |
| | else: |
| | st.markdown(f"**Checking interactions between:** {', '.join(d.title() for d in drug_list)}") |
| |
|
| | with st.spinner("Checking interactionsβ¦"): |
| | |
| | local_results = check_interactions_local(drug_list) |
| | api_results = [] |
| |
|
| | from safety.interactions import get_rxcui, check_interactions_api |
| | rxcuis = [] |
| | for name in drug_list: |
| | rxcui = get_rxcui(name) |
| | if rxcui: |
| | rxcuis.append(rxcui) |
| |
|
| | if len(rxcuis) >= 2: |
| | api_results = check_interactions_api(rxcuis) |
| |
|
| | |
| | all_results = [] |
| | seen = set() |
| | for r in (api_results + local_results): |
| | pair = frozenset([r["drug1"].lower(), r["drug2"].lower()]) |
| | if pair not in seen: |
| | all_results.append(r) |
| | seen.add(pair) |
| |
|
| | severity_order = {"critical": 0, "high": 1, "moderate": 2, "minor": 3, "low": 4, "unknown": 5} |
| | all_results.sort(key=lambda x: severity_order.get(x.get("severity", "unknown").lower(), 5)) |
| |
|
| | if not all_results: |
| | st.markdown( |
| | '<div class="safe-banner">β
<b>No significant interactions found</b> in the checked database for these medications. ' |
| | 'Always verify with a pharmacist or clinical drug reference for comprehensive checking.</div>', |
| | unsafe_allow_html=True |
| | ) |
| | else: |
| | n_critical = sum(1 for r in all_results if r.get("severity","").lower() == "critical") |
| | n_high = sum(1 for r in all_results if r.get("severity","").lower() == "high") |
| | n_moderate = sum(1 for r in all_results if r.get("severity","").lower() == "moderate") |
| |
|
| | summary_parts = [] |
| | if n_critical: summary_parts.append(f"π΄ {n_critical} CRITICAL") |
| | if n_high: summary_parts.append(f"π {n_high} HIGH") |
| | if n_moderate: summary_parts.append(f"π‘ {n_moderate} MODERATE") |
| | if summary_parts: |
| | st.markdown( |
| | f'<div class="critical-banner"><b>Found {len(all_results)} interaction(s): {" Β· ".join(summary_parts)}</b></div>', |
| | unsafe_allow_html=True |
| | ) |
| |
|
| | for r in all_results: |
| | sev = r.get("severity", "unknown") |
| | icon = SEVERITY_ICONS.get(sev.lower(), "βͺ") |
| | with st.expander( |
| | f"{icon} {r['drug1'].title()} β {r['drug2'].title()} β {sev.upper()}", |
| | expanded=(sev.lower() in ("critical", "high")) |
| | ): |
| | st.markdown( |
| | f'<div class="{"critical-banner" if sev in ("critical","high") else "warn-banner"}">' |
| | f'{severity_badge(sev)} <b>{r["drug1"].title()}</b> β <b>{r["drug2"].title()}</b>' |
| | f'</div>', |
| | unsafe_allow_html=True |
| | ) |
| | st.markdown(f"**Description:** {r['description']}") |
| | if r.get("management"): |
| | st.markdown( |
| | f'<div class="info-banner">π©Ί <b>Management:</b> {r["management"]}</div>', |
| | unsafe_allow_html=True |
| | ) |
| | if r.get("mechanism"): |
| | st.markdown(f"**Mechanism:** {r['mechanism']}") |
| | st.caption(f"Source: {r.get('source', 'Clinical Database')}") |
| |
|
| |
|
| | |
| | with tab_dose: |
| | st.subheader("π Safe Dose Verification") |
| | st.caption("Verify ordered doses against standard adult therapeutic ranges. Always check weight-based dosing.") |
| |
|
| | col_drug, col_dose, col_wt = st.columns([3, 2, 2]) |
| | with col_drug: |
| | drug_names_list = [""] + get_drug_names() |
| | sel_drug = st.selectbox("Medication", drug_names_list, |
| | format_func=lambda x: "Select or type a medicationβ¦" if x == "" else x) |
| | or_type = st.text_input("Or type drug name", placeholder="e.g. digoxin") |
| | with col_dose: |
| | ordered_dose = st.number_input("Ordered dose", min_value=0.0, step=0.5, format="%.2f") |
| | with col_wt: |
| | patient_weight = st.number_input("Patient weight (kg) β optional", min_value=0.0, step=1.0, format="%.1f") |
| |
|
| | if st.button("π Check Dose", type="primary", use_container_width=True): |
| | drug_to_check = or_type.strip() if or_type.strip() else sel_drug.strip() |
| |
|
| | if not drug_to_check: |
| | st.error("Please select or enter a medication name.") |
| | elif ordered_dose <= 0: |
| | st.error("Please enter a valid ordered dose > 0.") |
| | else: |
| | wt = patient_weight if patient_weight > 0 else None |
| | result = check_dose(drug_to_check, ordered_dose, wt) |
| |
|
| | if result["status"] == "not_found": |
| | st.warning(result["message"]) |
| | else: |
| | drug = result["drug"] |
| | st.markdown(f"### {drug['generic'].title()} ({drug['brand']})") |
| | st.markdown(f"**Category:** {drug['category']} Β· **Route:** {drug['route']} Β· **Unit:** {drug['unit']}") |
| |
|
| | |
| | pct = min(ordered_dose / drug["max_dose"], 1.0) |
| | c1, c2, c3 = st.columns(3) |
| | c1.metric("Minimum", f"{drug['min_dose']} {drug['unit']}") |
| | c2.metric("Ordered", f"{ordered_dose} {drug['unit']}") |
| | c3.metric("Maximum", f"{drug['max_dose']} {drug['unit']}") |
| | st.progress(pct) |
| |
|
| | |
| | for flag in result.get("flags", []): |
| | if "β
" in flag: |
| | st.markdown(f'<div class="safe-banner">{flag}</div>', unsafe_allow_html=True) |
| | elif "π΄" in flag: |
| | st.markdown(f'<div class="critical-banner">{flag}</div>', unsafe_allow_html=True) |
| | elif "β οΈ" in flag: |
| | st.markdown(f'<div class="warn-banner">{flag}</div>', unsafe_allow_html=True) |
| | elif "βοΈ" in flag: |
| | st.markdown(f'<div class="info-banner">{flag}</div>', unsafe_allow_html=True) |
| | else: |
| | st.markdown(f'<div class="info-banner">{flag}</div>', unsafe_allow_html=True) |
| |
|
| | |
| | st.markdown(f"**Typical frequency:** {drug['frequency']}") |
| | if drug.get("therapeutic_range"): |
| | st.markdown(f"**Therapeutic range:** {drug['therapeutic_range']}") |
| |
|
| | st.divider() |
| | st.markdown("**π©Ί Nursing Notes:**") |
| | for note in drug.get("nursing_notes", []): |
| | st.markdown(f"β’ {note}") |
| |
|
| | st.divider() |
| | st.caption(f"π Dose database covers {len(DOSE_DATABASE)} common medications. Always verify against current clinical guidelines and your institution's protocols.") |
| |
|
| |
|
| | |
| | with tab_lasa: |
| | st.subheader("π€ Look-Alike / Sound-Alike (LASA) Drug Warnings") |
| | st.caption("Based on ISMP LASA list and Joint Commission National Patient Safety Goals.") |
| |
|
| | col_ls, col_lt = st.columns([2, 2]) |
| | with col_ls: |
| | lasa_query = st.text_input("π Search drug name", placeholder="e.g. morphine, insulin, warfarin", key="lasa_search") |
| | with col_lt: |
| | lasa_type_filter = st.selectbox( |
| | "Filter by type", |
| | ["All types", "look-alike", "sound-alike", "both"], |
| | format_func=lambda x: {"All types": "All LASA types", "look-alike": "ποΈ Look-Alike", |
| | "sound-alike": "π Sound-Alike", "both": "ποΈπ Both"}[x], |
| | ) |
| |
|
| | lasa_risk_filter = st.radio( |
| | "Risk level", |
| | ["All", "CRITICAL + HIGH only", "CRITICAL only"], |
| | horizontal=True |
| | ) |
| |
|
| | st.divider() |
| |
|
| | |
| | all_pairs = get_all_pairs() |
| |
|
| | if lasa_query.strip(): |
| | pairs = search_lasa(lasa_query) |
| | elif lasa_risk_filter == "CRITICAL only": |
| | pairs = get_critical_pairs() |
| | elif lasa_risk_filter == "CRITICAL + HIGH only": |
| | pairs = get_high_risk_pairs() |
| | else: |
| | pairs = all_pairs |
| |
|
| | if lasa_type_filter != "All types": |
| | pairs = [p for p in pairs if p["type"] == lasa_type_filter or |
| | (lasa_type_filter != "both" and p["type"] == "both")] |
| |
|
| | st.markdown(f"**Showing {len(pairs)} LASA pair(s)**") |
| |
|
| | if not pairs: |
| | st.info("No LASA pairs found matching your filters.") |
| | else: |
| | for pair in pairs: |
| | risk = pair["risk"] |
| | icon = TYPE_ICONS.get(pair["type"], "π€") |
| | risk_icon = {"CRITICAL": "π΄", "HIGH": "π ", "MODERATE": "π‘", "LOW": "π’"}.get(risk, "βͺ") |
| |
|
| | with st.expander( |
| | f"{risk_icon} {pair['drug_a']} β {pair['drug_b']} {icon}", |
| | expanded=(risk == "CRITICAL") |
| | ): |
| | col_a, col_b = st.columns([3, 1]) |
| | with col_a: |
| | st.markdown(f"**Drug A:** {pair['drug_a']}") |
| | st.markdown(f"**Drug B:** {pair['drug_b']}") |
| | with col_b: |
| | st.markdown( |
| | f'<div style="text-align:right">' |
| | f'{risk_badge(risk)}<br/>{icon} <small>{pair["type"].title()}</small>' |
| | f'</div>', |
| | unsafe_allow_html=True |
| | ) |
| |
|
| | banner = "critical-banner" if risk == "CRITICAL" else "high-alert-banner" if risk == "HIGH" else "warn-banner" |
| | st.markdown( |
| | f'<div class="{banner}">β οΈ <b>Clinical Risk:</b> {pair["clinical_note"]}</div>', |
| | unsafe_allow_html=True |
| | ) |
| | st.markdown( |
| | f'<div class="info-banner">β
<b>Prevention Strategy:</b> {pair["strategy"]}</div>', |
| | unsafe_allow_html=True |
| | ) |
| | if pair.get("tags"): |
| | tags = " Β· ".join(f"`{t}`" for t in pair["tags"]) |
| | st.markdown(f"**Tags:** {tags}") |
| |
|
| |
|
| | |
| | with tab_5rights: |
| | st.subheader("β
Medication Administration: 9 Rights Checklist") |
| | st.caption("The Five Rights (+ 4 additional) of safe medication administration. Use this as a pre-administration checklist.") |
| |
|
| | if "rights_state" not in st.session_state: |
| | st.session_state.rights_state = {} |
| |
|
| | drug_name_input = st.text_input("π Medication being prepared:", placeholder="e.g. Metoprolol 50 mg PO") |
| |
|
| | st.divider() |
| |
|
| | RIGHTS = [ |
| | { |
| | "right": "1οΈβ£ Right PATIENT", |
| | "description": "Verify patient identity using TWO identifiers (name + DOB, or name + MRN)", |
| | "checks": [ |
| | "Asked the patient to state their full name", |
| | "Verified date of birth or MRN", |
| | "Checked wristband against MAR", |
| | "Confirmed allergies", |
| | ], |
| | }, |
| | { |
| | "right": "2οΈβ£ Right DRUG", |
| | "description": "Confirm the correct medication name β generic AND brand", |
| | "checks": [ |
| | "Compared drug name on MAR to medication label", |
| | "Checked for LASA look-alikes", |
| | "Verified correct formulation (IR vs XR, concentration)", |
| | "Checked if medication is on ISMP high-alert list", |
| | ], |
| | }, |
| | { |
| | "right": "3οΈβ£ Right DOSE", |
| | "description": "Verify the dose is correct and within safe therapeutic range", |
| | "checks": [ |
| | "Calculated dose independently", |
| | "Verified dose against standard range", |
| | "Performed weight-based calculation if applicable", |
| | "Second nurse verification completed (if required by policy)", |
| | ], |
| | }, |
| | { |
| | "right": "4οΈβ£ Right ROUTE", |
| | "description": "Confirm the ordered route is appropriate and safe", |
| | "checks": [ |
| | "Verified route on MAR (oral, IV, SubQ, IM, etc.)", |
| | "Confirmed IV access is patent and suitable for drug", |
| | "Confirmed oral route is appropriate (no dysphagia/NPO)", |
| | "Verified compatibility if adding to existing IV", |
| | ], |
| | }, |
| | { |
| | "right": "5οΈβ£ Right TIME", |
| | "description": "Administer the medication at the correct time", |
| | "checks": [ |
| | "Confirmed time on MAR matches current administration window", |
| | "Verified last dose time (avoid double dosing)", |
| | "Confirmed patient has not already received the dose", |
| | "For time-critical medications (antibiotics, insulin): on time", |
| | ], |
| | }, |
| | { |
| | "right": "6οΈβ£ Right DOCUMENTATION", |
| | "description": "Document immediately after β never before β administration", |
| | "checks": [ |
| | "MAR signed immediately after administration", |
| | "Noted time, route, site (for injections), patient response", |
| | "Documented any refused or withheld doses with reason", |
| | "PRN medications: documented reason for administration", |
| | ], |
| | }, |
| | { |
| | "right": "7οΈβ£ Right REASON", |
| | "description": "Understand WHY this medication is being given", |
| | "checks": [ |
| | "Know the indication for this medication", |
| | "Can explain purpose to patient if asked", |
| | "Verified indication matches the patient's current diagnosis", |
| | "Questioned any orders that don't match clinical presentation", |
| | ], |
| | }, |
| | { |
| | "right": "8οΈβ£ Right RESPONSE", |
| | "description": "Evaluate the patient's response to the medication", |
| | "checks": [ |
| | "Assessed baseline vital signs and relevant parameters before administration", |
| | "Documented pre-administration assessment", |
| | "Plan for post-administration re-assessment", |
| | "Know expected therapeutic effect timeline", |
| | ], |
| | }, |
| | { |
| | "right": "9οΈβ£ Right to REFUSE", |
| | "description": "Patient has the right to refuse any medication", |
| | "checks": [ |
| | "Patient has been informed about the medication and its purpose", |
| | "Patient understands the consequences of refusal", |
| | "Refusal documented and provider notified", |
| | "Patient's autonomy and right to decide is respected", |
| | ], |
| | }, |
| | ] |
| |
|
| | all_checked = True |
| | total_checks = 0 |
| | completed_checks = 0 |
| |
|
| | for right_item in RIGHTS: |
| | right_key = right_item["right"] |
| | with st.expander(right_key, expanded=True): |
| | st.caption(right_item["description"]) |
| | for ci, check_text in enumerate(right_item["checks"]): |
| | key = f"right_{right_key}_{ci}" |
| | current = st.session_state.rights_state.get(key, False) |
| | checked = st.checkbox(check_text, value=current, key=key) |
| | st.session_state.rights_state[key] = checked |
| | total_checks += 1 |
| | if checked: |
| | completed_checks += 1 |
| | else: |
| | all_checked = False |
| |
|
| | st.divider() |
| |
|
| | pct_done = round((completed_checks / total_checks) * 100) if total_checks > 0 else 0 |
| | st.progress(pct_done / 100) |
| | st.markdown(f"**{completed_checks} / {total_checks} checks completed ({pct_done}%)**") |
| |
|
| | if all_checked: |
| | drug_label = f" β {drug_name_input}" if drug_name_input else "" |
| | st.markdown( |
| | f'<div class="safe-banner">β
<b>All 9 Rights verified{drug_label}</b>. ' |
| | f'Safe to administer. Document immediately after administration.</div>', |
| | unsafe_allow_html=True |
| | ) |
| | elif pct_done >= 75: |
| | st.markdown( |
| | '<div class="warn-banner">π‘ Almost complete β review remaining unchecked items before administering.</div>', |
| | unsafe_allow_html=True |
| | ) |
| | else: |
| | st.markdown( |
| | '<div class="high-alert-banner">β οΈ Complete all verification checks before administering medication.</div>', |
| | unsafe_allow_html=True |
| | ) |
| |
|
| | col_reset, _ = st.columns([1, 3]) |
| | with col_reset: |
| | if st.button("π Reset Checklist"): |
| | st.session_state.rights_state = {} |
| | st.rerun() |
| |
|
| |
|
| | |
| | |
| | |
| | st.divider() |
| | st.caption( |
| | "Based on ISMP High-Alert Medications list (2023), Joint Commission NPSG, and standard clinical references. " |
| | "Drug interaction data supplemented by NIH RxNorm API. " |
| | "For educational purposes only β always follow your institution's protocols and consult a pharmacist." |
| | ) |
| |
|