MindGuard-AI / app /components /dashboard_ui.py
MohitRajput45's picture
project completed
8aa3867
# ============================================================
# FILE: app/components/dashboard_ui.py
# PURPOSE: Renders the Clinical Overview dashboard page.
# Organized into two tabs:
# Tab 1 β€” "πŸ“ˆ Analytics": metrics, charts, history table
# Tab 2 β€” "πŸ”¬ XAI Report": renders the latest SHAP HTML
# report so clinicians can review explanations
# without going back to the chat.
# ============================================================
import streamlit as st # Core UI framework
import streamlit.components.v1 as components # For embedding raw HTML (SHAP report)
import pandas as pd # For converting SQL rows into a DataFrame
import os # For checking if the SHAP file exists
import sys # For fixing the import path
# ─────────────────────────────────────────────────────────────
# PATH SETUP BLOCK
# Same pattern as api.py: this file is inside app/components/
# so we need to walk up TWO levels to reach the project root
# where the src/ package lives.
# ─────────────────────────────────────────────────────────────
# Get the absolute path of this file (app/components/dashboard_ui.py)
current_dir = os.path.dirname(os.path.abspath(__file__))
# Walk UP two directories: components/ β†’ app/ β†’ project_root/
project_root = os.path.abspath(os.path.join(current_dir, "../../"))
# Add project_root to Python's search path so "from src.xxx import yyy" works
if project_root not in sys.path:
sys.path.append(project_root)
# Import our database helper class from src/database/db_operations.py
from src.database.db_operations import MindGuardDatabase
# ─────────────────────────────────────────────────────────────
# SHAP REPORT PATH
# shap_explainer.py ALWAYS writes its output to this exact path.
# We define it once here so any function in this file can use it.
# ─────────────────────────────────────────────────────────────
SHAP_HTML_PATH = os.path.join(
project_root, # e.g., /home/user/project/
"artifacts", # The artifacts/ folder at project root
"shap_report.html" # Fixed filename β€” shap_explainer.py never changes this
)
# ─────────────────────────────────────────────────────────────
# MAIN RENDER FUNCTION
# Called by main.py when the user selects "πŸ“Š Clinical Dashboard"
# in the sidebar navigation.
# ─────────────────────────────────────────────────────────────
def render_dashboard():
# Page header in the main content area
st.title("πŸ“Š Clinical Overview")
st.markdown("Real-time emotional tracking, risk assessment, and XAI reports.")
# ── Create Two Tabs ───────────────────────────────────────
# st.tabs() returns a list of tab context managers.
# We unpack them into tab1 and tab2 immediately.
tab1, tab2 = st.tabs(["πŸ“ˆ Analytics", "πŸ”¬ XAI Report"])
# =========================================================
# TAB 1: ANALYTICS
# Shows metrics, bar charts, and a history table built from
# the chat_history table in our SQLite database.
# =========================================================
with tab1:
# ── Database Read ─────────────────────────────────────
# Open a database connection to read all historical records
db = MindGuardDatabase()
# Execute a SQL SELECT to fetch the three columns we need,
# sorted newest-first so the table shows recent entries at the top
db.cursor.execute(
"SELECT timestamp, diagnosed_emotion, risk_level "
"FROM chat_history "
"ORDER BY timestamp DESC"
)
# fetchall() returns a list of sqlite3.Row objects (like dicts)
rows = db.cursor.fetchall()
# Close the DB connection immediately after reading.
# Leaving connections open can cause locking issues.
db.close()
# Guard clause: if there are no rows in the DB yet,
# show an info message and exit the function early.
if not rows:
st.info("No data yet. Start chatting to generate analytics.")
return # Exit render_dashboard() β€” nothing to display
# Convert the list of sqlite3.Row objects into a Pandas DataFrame.
# dict(r) turns each Row into a regular Python dict first.
# DataFrame() then stacks all those dicts into a table.
df = pd.DataFrame([dict(r) for r in rows])
# ── TOP METRICS ROW ───────────────────────────────────
# Compute the three summary statistics shown as metric cards
total = len(df) # Total number of chat interactions recorded
high_risk = len(df[df["risk_level"] == "High"]) # Count rows where risk_level equals "High"
# mode()[0] returns the most-frequently-occurring value in that column
top_emotion = df["diagnosed_emotion"].mode()[0] if not df.empty else "N/A"
# st.columns(3) creates three equal-width side-by-side columns
col1, col2, col3 = st.columns(3)
col1.metric("Total Interactions", total)
# delta_color="inverse" makes the delta arrow red on increase
# (since more high-risk flags is BAD, not good)
col2.metric("High Risk Flags", high_risk, delta_color="inverse")
col3.metric("Primary Emotion", top_emotion)
# ── RISK LEVEL SUMMARY ────────────────────────────────
st.divider() # Horizontal separator line
st.subheader("Risk Level Summary")
# Create three columns, one per risk level
risk_cols = st.columns(3)
# Loop through each risk level and display its count
for i, level in enumerate(["High", "Medium", "Low"]):
# Count rows where risk_level matches this level
count = len(df[df["risk_level"] == level])
# Pick an icon that visually reinforces the severity
icons = {"High": "🚨", "Medium": "⚠️", "Low": "βœ…"}
# Display as a Streamlit metric card in the correct column
risk_cols[i].metric(f"{icons[level]} {level}", count)
# ── BAR CHARTS ───────────────────────────────────────
st.divider()
# Two charts side by side using columns
chart_col1, chart_col2 = st.columns(2)
with chart_col1:
st.subheader("Emotion Frequency")
# value_counts() counts how many times each emotion appeared.
# st.bar_chart() renders it as an interactive bar chart automatically.
st.bar_chart(df["diagnosed_emotion"].value_counts())
with chart_col2:
st.subheader("Risk Level Distribution")
# Same pattern: count occurrences of each risk level
st.bar_chart(df["risk_level"].value_counts())
# ── RECENT HISTORY TABLE ─────────────────────────────
st.divider()
st.subheader("Recent Session History")
# Show only the 20 most recent rows to keep the table manageable.
# .rename() gives the columns friendly display names for the UI.
st.dataframe(
df.head(20).rename(columns={
"timestamp": "Time", # Raw DB column name β†’ human label
"diagnosed_emotion": "Emotion",
"risk_level": "Risk"
}),
use_container_width=True # Stretch table to fill available width
)
# =========================================================
# TAB 2: XAI REPORT
# Loads and renders the SHAP HTML report that was saved to disk
# by shap_explainer.generate_visual_report() during the last chat.
# This gives clinicians a dedicated view of the explanation.
# =========================================================
with tab2:
st.subheader("πŸ”¬ Last SHAP Word-Level Explanation")
# Explanation of what the user is looking at
st.markdown(
"This report shows **which words** drove the model's emotion prediction. "
"**Red highlights** = words that pushed the model TOWARDS that emotion. "
"**Blue highlights** = words that pushed AGAINST it. "
"Generated using SHAP (SHapley Additive exPlanations) β€” Game Theory math."
)
# os.path.exists() checks whether the file has been created yet.
# It won't exist on the very first run before any chat messages.
if os.path.exists(SHAP_HTML_PATH):
# Read the entire SHAP HTML report file into a Python string
with open(SHAP_HTML_PATH, "r", encoding="utf-8") as f:
shap_html = f.read()
# components.html() injects the raw HTML string into an iframe
# inside the Streamlit page. This is how we display SHAP's
# interactive visualization without needing a Jupyter notebook.
# height=500 gives enough space to see word highlights clearly.
# scrolling=True allows vertical scroll if the report is tall.
components.html(shap_html, height=500, scrolling=True)
# Show the file path as a small caption below the report
# so developers can quickly find the file for debugging
st.caption(f"Report path: `{SHAP_HTML_PATH}`")
else:
# No report exists yet β€” guide the user to generate one
st.info(
"No SHAP report found yet. "
"Send a message in the **πŸ’¬ Chat Companion** tab "
"and the XAI report will appear here automatically after your first message."
)