> ## Documentation Index
> Fetch the complete documentation index at: https://fastapi.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Project: Performance Dashboard

> Build a complete Student Performance Analytics Dashboard incorporating files, charts, layout, and session state

In this capstone project, you will build a complete, interactive **Student Performance Analytics Dashboard** in Streamlit. You will combine file handling, state management, layout controls, and data visualization.

## ❓ The Goal (Question)

Build a multi-functional analytics dashboard that enables teachers to:

1. Upload a student grades CSV file (`students.csv`).
2. Track dashboard opens/visits in the sidebar using **Session State**.
3. Dynamically filter the dataset by Department, Subject, and Search Name using a sidebar form.
4. Organize content cleanly into three tabs: **Dataset**, **Reports** (KPI metrics), and **Charts** (visualizations using Matplotlib).
5. Download the filtered report as a new CSV file.

## 📋 Implementation Plan

1. **Setup**: Initialize the project directory and install dependencies using `uv` (`streamlit`, `pandas`, `matplotlib`).
2. **State & Sidebar**: Initialize session state to track page visits and build the filters sidebar.
3. **Data Loading**: Implement a CSV file uploader to load the grades dataset.
4. **Data Filtering**: Filter the dataframe based on the user's form inputs.
5. **Layout & Tabs**: Organize the dashboard main panel into three switchable tabs:
   * **Dataset Tab**: Shows the raw filtered data table.
   * **Reports Tab**: Displays key performance indicators (KPIs) like student count, highest/lowest/average marks.
   * **Charts Tab**: Renders average marks by department and grade distribution charts.
6. **Actions & Feedback**: Add a download button to export the filtered dataset and include progress spinners.

## 🛠️ Step-by-Step Implementation

### Step 1: Project Setup

Create a new directory for your project and add the following structure:

```text theme={null}
student_dashboard/
├── app.py          # Main application file
└── students.csv    # Sample data file
```

Install the required libraries using `uv`:

```bash theme={null}
uv add streamlit pandas matplotlib
```

### Step 2: Initialize App & Session State

Open `app.py` and set up the page config, title, and session state page-visit counter:

```python theme={null}
import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import time

# Configure page layout
st.set_page_config(page_title="Student Analytics Dashboard", layout="wide")

st.title("📊 Student Performance Analytics Dashboard")
st.write("Upload a CSV file to analyze student performance.")

# Initialize and increment visit count using Session State
if "visits" not in st.session_state:
    st.session_state.visits = 0
st.session_state.visits += 1

st.sidebar.title("Filters")
st.sidebar.write(f"ℹ️ Dashboard Opened: **{st.session_state.visits}** times")
```

### Step 3: Handle CSV File Upload

Below the visit counter, implement the file uploader and load the data:

```python theme={null}
# File uploader
uploaded_file = st.sidebar.file_uploader("Upload Student CSV", type="csv")

if uploaded_file is not None:
    # Load dataset
    df = pd.read_csv(uploaded_file)
    st.sidebar.success("File uploaded successfully!")
else:
    st.info("Please upload a student CSV file to activate the dashboard.")
    st.stop()  # Stops execution until file is uploaded
```

### Step 4: Construct the Filter Form

Create input widgets inside a form inside the sidebar to gather filtering options:

```python theme={null}
# Sidebar form for filters
with st.sidebar.form("filter_form"):
    st.subheader("Filter Settings")
    
    # Generate filter options dynamically from data
    departments = ["All"] + sorted(df["Department"].unique().tolist())
    subjects = ["All"] + sorted(df["Subject"].unique().tolist())
    
    dept = st.selectbox("Department", departments)
    subject = st.selectbox("Subject", subjects)
    search = st.text_input("Search Student Name")
    
    submit = st.form_submit_button("Apply Filters")
```

### Step 5: Apply Filters & Display Row Metrics

Filter the dataframe according to the selected parameters and display basic metadata:

```python theme={null}
# Filter logic
filtered_df = df.copy()

if dept != "All":
    filtered_df = filtered_df[filtered_df["Department"] == dept]

if subject != "All":
    filtered_df = filtered_df[filtered_df["Subject"] == subject]

if search:
    filtered_df = filtered_df[filtered_df["Name"].str.contains(search, case=False)]

# Top row metadata cards
col1, col2, col3 = st.columns(3)
col1.metric("Total Records in Upload", len(df))
col2.metric("Filtered Records", len(filtered_df))
col3.metric("Dataset Average Marks", f"{df['Marks'].mean():.2f}")
```

### Step 6: Create the Tabbed Layout

Organize your content by adding three tabs to the main panel:

```python theme={null}
# Create tabs
tab_dataset, tab_reports, tab_charts = st.tabs(["📋 Dataset View", "📊 Reports Summary", "📈 Charts & Insights"])

# 1. Dataset Tab
with tab_dataset:
    st.subheader("Filtered Student Data")
    st.dataframe(filtered_df, use_container_width=True, hide_index=True)
```

### Step 7: Build the Reports Tab (KPIs)

Implement detailed KPI calculations inside the second tab:

```python theme={null}
# 2. Reports Tab
with tab_reports:
    st.subheader("Academic Metrics Summary")
    
    if not filtered_df.empty:
        # Calculate statistics
        num_students = len(filtered_df["Name"].unique())
        avg_mark = filtered_df["Marks"].mean()
        highest_mark = filtered_df["Marks"].max()
        lowest_mark = filtered_df["Marks"].min()
        
        # Display KPI cards
        kpi1, kpi2, kpi3, kpi4 = st.columns(4)
        kpi1.metric("Unique Students", num_students)
        kpi2.metric("Average Marks", f"{avg_mark:.2f}")
        kpi3.metric("Highest Mark", f"{highest_mark}%")
        kpi4.metric("Lowest Mark", f"{lowest_mark}%")
        
        # Add automated warning/remarks
        st.divider()
        st.write("**Performance Alert:**")
        if avg_mark >= 80:
            st.success("Overall group performance is Excellent!")
        elif avg_mark >= 60:
            st.info("Overall group performance is Moderate.")
        else:
            st.warning("Overall group performance needs attention!")
    else:
        st.warning("No records matches the selected filter options.")
```

### Step 8: Build the Charts Tab (Visualizations)

Generate and render the Matplotlib graphs in the third tab:

```python theme={null}
# 3. Charts Tab
with tab_charts:
    st.subheader("Visual Analytics")
    
    if not filtered_df.empty:
        c_left, c_right = st.columns(2)
        
        with c_left:
            st.markdown("**Average Marks by Department**")
            fig1, ax1 = plt.subplots(figsize=(6, 4))
            filtered_df.groupby("Department")["Marks"].mean().plot(kind="bar", color="skyblue", ax=ax1)
            ax1.set_ylabel("Marks")
            st.pyplot(fig1)
            
        with c_right:
            st.markdown("**Grade Distribution**")
            fig2, ax2 = plt.subplots(figsize=(6, 4))
            filtered_df["Marks"].plot(kind="hist", bins=5, color="lightcoral", ax=ax2)
            ax2.set_xlabel("Marks Range")
            st.pyplot(fig2)
    else:
        st.warning("No records to visualize.")
```

### Step 9: Add Download Button & Status Feedback

Provide a download option at the bottom of the page and trigger a loading spinner:

```python theme={null}
st.divider()

# Spinner status feedback
with st.spinner("Preparing export bundle..."):
    time.sleep(0.5)

# Convert filtered data to CSV
csv_data = filtered_df.to_csv(index=False).encode('utf-8')

# Download button
st.download_button(
    label="📥 Download Filtered Report (CSV)",
    data=csv_data,
    file_name="student_filtered_report.csv",
    mime="text/csv"
)
```

## 📄 Sample Dataset (`students.csv`)

Save this data as `students.csv` in your project folder to test the dashboard:

```csv theme={null}
RollNo,Name,Department,Semester,Subject,Marks
101,Sai Kiran,CSE,4,Python,88
102,Sravani Reddy,CSE,4,Python,75
103,Venkatesh Kumar,ECE,4,Python,91
104,Harika Devi,CSE,4,AI,82
105,Naveen Kumar,ECE,4,AI,95
106,Keerthana,MECH,3,Python,69
107,Sandeep Reddy,CSE,3,AI,90
108,Lakshmi Priya,ECE,2,Maths,74
109,Rohith Varma,CSE,4,Maths,86
110,Bhavya Sri,MECH,2,Physics,72
```

## 🚀 Complete Combined Code (`app.py`)

Here is the complete combined code for easy copying:

```python theme={null}
import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import time

# Configure page layout
st.set_page_config(page_title="Student Analytics Dashboard", layout="wide")

st.title("📊 Student Performance Analytics Dashboard")
st.write("Upload a CSV file to analyze student performance.")

# Initialize and increment visit count using Session State
if "visits" not in st.session_state:
    st.session_state.visits = 0
st.session_state.visits += 1

st.sidebar.title("Filters")
st.sidebar.write(f"ℹ️ Dashboard Opened: **{st.session_state.visits}** times")

# File uploader
uploaded_file = st.sidebar.file_uploader("Upload Student CSV", type="csv")

if uploaded_file is not None:
    # Load dataset
    df = pd.read_csv(uploaded_file)
    st.sidebar.success("File uploaded successfully!")
else:
    st.info("Please upload a student CSV file to activate the dashboard.")
    st.stop()

# Sidebar form for filters
with st.sidebar.form("filter_form"):
    st.subheader("Filter Settings")
    departments = ["All"] + sorted(df["Department"].unique().tolist())
    subjects = ["All"] + sorted(df["Subject"].unique().tolist())
    
    dept = st.selectbox("Department", departments)
    subject = st.selectbox("Subject", subjects)
    search = st.text_input("Search Student Name")
    
    submit = st.form_submit_button("Apply Filters")

# Filter logic
filtered_df = df.copy()

if dept != "All":
    filtered_df = filtered_df[filtered_df["Department"] == dept]

if subject != "All":
    filtered_df = filtered_df[filtered_df["Subject"] == subject]

if search:
    filtered_df = filtered_df[filtered_df["Name"].str.contains(search, case=False)]

# Top row metadata cards
col1, col2, col3 = st.columns(3)
col1.metric("Total Records in Upload", len(df))
col2.metric("Filtered Records", len(filtered_df))
col3.metric("Dataset Average Marks", f"{df['Marks'].mean():.2f}")

# Create tabs
tab_dataset, tab_reports, tab_charts = st.tabs(["📋 Dataset View", "📊 Reports Summary", "📈 Charts & Insights"])

# 1. Dataset Tab
with tab_dataset:
    st.subheader("Filtered Student Data")
    st.dataframe(filtered_df, use_container_width=True, hide_index=True)

# 2. Reports Tab
with tab_reports:
    st.subheader("Academic Metrics Summary")
    if not filtered_df.empty:
        num_students = len(filtered_df["Name"].unique())
        avg_mark = filtered_df["Marks"].mean()
        highest_mark = filtered_df["Marks"].max()
        lowest_mark = filtered_df["Marks"].min()
        
        kpi1, kpi2, kpi3, kpi4 = st.columns(4)
        kpi1.metric("Unique Students", num_students)
        kpi2.metric("Average Marks", f"{avg_mark:.2f}")
        kpi3.metric("Highest Mark", f"{highest_mark}%")
        kpi4.metric("Lowest Mark", f"{lowest_mark}%")
        
        st.divider()
        st.write("**Performance Alert:**")
        if avg_mark >= 80:
            st.success("Overall group performance is Excellent!")
        elif avg_mark >= 60:
            st.info("Overall group performance is Moderate.")
        else:
            st.warning("Overall group performance needs attention!")
    else:
        st.warning("No records match the selected filter options.")

# 3. Charts Tab
with tab_charts:
    st.subheader("Visual Analytics")
    if not filtered_df.empty:
        c_left, c_right = st.columns(2)
        
        with c_left:
            st.markdown("**Average Marks by Department**")
            fig1, ax1 = plt.subplots(figsize=(6, 4))
            filtered_df.groupby("Department")["Marks"].mean().plot(kind="bar", color="skyblue", ax=ax1)
            ax1.set_ylabel("Marks")
            st.pyplot(fig1)
            
        with c_right:
            st.markdown("**Grade Distribution**")
            fig2, ax2 = plt.subplots(figsize=(6, 4))
            filtered_df["Marks"].plot(kind="hist", bins=5, color="lightcoral", ax=ax2)
            ax2.set_xlabel("Marks Range")
            st.pyplot(fig2)
    else:
        st.warning("No records to visualize.")

st.divider()

# Spinner status feedback
with st.spinner("Preparing export bundle..."):
    time.sleep(0.5)

# Convert filtered data to CSV
csv_data = filtered_df.to_csv(index=False).encode('utf-8')

# Download button
st.download_button(
    label="📥 Download Filtered Report (CSV)",
    data=csv_data,
    file_name="student_filtered_report.csv",
    mime="text/csv"
)
```
