dcata004's picture
Create app.py
ce0cada verified
Raw
History Blame Contribute Delete
7.73 kB
import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px
import datetime
import time
# ==========================================
# 1. CONFIGURATION & MOCK DATA GENERATION
# ==========================================
st.set_page_config(
page_title="WDE Accountability Dashboard",
page_icon="🀠",
layout="wide"
)
# Simulated Wyoming Districts
WY_DISTRICTS = [
"Laramie County SD #1",
"Natrona County SD #1",
"Sheridan County SD #2",
"Teton County SD #1",
"Albany County SD #1"
]
# Simulated "Nightly Update" Data Generator
@st.cache_data(ttl=3600) # Cache mimics the static nature of nightly builds
def load_data(simulation_date):
"""
Simulates fetching data from a Data Warehouse updated nightly.
"""
data = []
# Generate mock data
for dist in WY_DISTRICTS:
# Create 3 schools per district
for school_level in ['Elementary', 'Middle', 'High']:
school_name = f"{dist.split(' ')[0]} {school_level}"
# Generate 50 students per school for the prototype
for i in range(50):
student_id = f"WY-{np.random.randint(100000, 999999)}"
# Simulate WY-TOPP Scores (Scale 200-800)
math_score = np.random.normal(500, 50) + (20 if "Teton" in dist else 0)
ela_score = np.random.normal(510, 45)
attendance_rate = np.clip(np.random.normal(92, 5), 50, 100)
data.append({
"District": dist,
"School": school_name,
"Student_ID": student_id,
"Grade_Level": np.random.choice([3, 4, 5, 6, 7, 8, 9, 10, 11]),
"WY_TOPP_Math": int(math_score),
"WY_TOPP_ELA": int(ela_score),
"Attendance_Pct": round(attendance_rate, 1),
"At_Risk": "Yes" if attendance_rate < 85 or math_score < 450 else "No",
"Last_Updated": simulation_date
})
return pd.DataFrame(data)
# ==========================================
# 2. AUTHENTICATION & SECURITY (RBAC)
# ==========================================
# In production, connect this to SSO / LDAP / Active Directory
USERS = {
"admin": {"password": "password123", "role": "State_Admin", "access": "All"},
"laramie_supt": {"password": "wyo", "role": "District_Admin", "access": "Laramie County SD #1"},
"teton_principal": {"password": "mountains", "role": "School_Admin", "access": "Teton Elementary"},
}
def login():
st.markdown("## πŸ”’ WDE Secure Login")
if "authenticated" not in st.session_state:
st.session_state["authenticated"] = False
if not st.session_state["authenticated"]:
username = st.text_input("Username")
password = st.text_input("Password", type="password")
if st.button("Login"):
if username in USERS and USERS[username]["password"] == password:
st.session_state["authenticated"] = True
st.session_state["user"] = username
st.session_state["role"] = USERS[username]["role"]
st.session_state["access"] = USERS[username]["access"]
st.rerun()
else:
st.error("Invalid credentials")
else:
return True
return False
def logout():
st.session_state["authenticated"] = False
st.rerun()
# ==========================================
# 3. DASHBOARD LOGIC
# ==========================================
def main_dashboard():
# --- Sidebar ---
st.sidebar.title("Navigation")
st.sidebar.write(f"Logged in as: **{st.session_state['user']}**")
st.sidebar.write(f"Role: *{st.session_state['role']}*")
if st.sidebar.button("Logout"):
logout()
st.sidebar.markdown("---")
# Simulate Nightly Update Info
today = datetime.date.today()
st.sidebar.info(f"πŸ“… Data Current As Of: \n{today} 03:00 AM MST")
# --- Data Loading ---
df = load_data(today)
# --- Security Filter (Row Level Security) ---
# Filter data based on user role before it hits the UI
if st.session_state["access"] != "All":
if st.session_state["role"] == "District_Admin":
df = df[df["District"] == st.session_state["access"]]
elif st.session_state["role"] == "School_Admin":
df = df[df["School"] == st.session_state["access"]]
# --- Page Content ---
st.title("πŸ”οΈ Wyoming Accountability & Analytics")
# Top Level Metrics
col1, col2, col3, col4 = st.columns(4)
col1.metric("Total Students", f"{len(df)}")
col2.metric("Avg Math Score", f"{int(df['WY_TOPP_Math'].mean())}")
col3.metric("Avg Attendance", f"{df['Attendance_Pct'].mean():.1f}%")
at_risk_count = len(df[df["At_Risk"] == "Yes"])
col4.metric("At-Risk Students", f"{at_risk_count}", delta="-Action Required", delta_color="inverse")
st.markdown("---")
# --- Visualizations ---
# 1. Drill Down Controls
st.subheader("πŸ“Š Performance Analytics")
view_type = st.radio("Analyze by:", ["District", "School", "Grade_Level"], horizontal=True)
# Group data based on selection
agg_df = df.groupby(view_type)[["WY_TOPP_Math", "WY_TOPP_ELA", "Attendance_Pct"]].mean().reset_index()
# Chart
fig = px.bar(
agg_df,
x=view_type,
y=["WY_TOPP_Math", "WY_TOPP_ELA"],
barmode='group',
title=f"Average WY-TOPP Scores by {view_type}",
color_discrete_sequence=["#F2A900", "#53565A"] # Wyoming Colors (approx)
)
st.plotly_chart(fig, use_container_width=True)
# 2. Risk Analysis
c1, c2 = st.columns(2)
with c1:
st.subheader("Attendance vs. Performance")
fig2 = px.scatter(
df,
x="Attendance_Pct",
y="WY_TOPP_Math",
color="At_Risk",
hover_data=["Student_ID", "School"],
title="Correlation: Attendance vs Math Scores"
)
st.plotly_chart(fig2, use_container_width=True)
with c2:
st.subheader("At-Risk Distribution")
risk_dist = df['At_Risk'].value_counts()
fig3 = px.pie(values=risk_dist, names=risk_dist.index, hole=0.4, color_discrete_sequence=['#2ecc71', '#e74c3c'])
st.plotly_chart(fig3, use_container_width=True)
# --- Data Grid (Secure View) ---
st.subheader("πŸ“‚ Student Data Details")
# Search / Filter
text_search = st.text_input("Search Student ID", "")
grade_filter = st.multiselect("Filter by Grade", sorted(df["Grade_Level"].unique()))
filtered_df = df.copy()
if text_search:
filtered_df = filtered_df[filtered_df["Student_ID"].str.contains(text_search)]
if grade_filter:
filtered_df = filtered_df[filtered_df["Grade_Level"].isin(grade_filter)]
# Styling the dataframe (Highlighting low attendance)
st.dataframe(
filtered_df.style.map(lambda x: 'color: red; font-weight: bold' if isinstance(x, (int, float)) and x < 85 else '', subset=['Attendance_Pct']),
use_container_width=True,
hide_index=True
)
# Export Button (Audit Log Placeholder)
if st.button("πŸ“₯ Export Report to CSV"):
st.toast("Export started... Logged in Audit Trail.", icon="βœ…")
# In production, this would trigger a download and write to an SQL audit log
# ==========================================
# 4. APP ENTRY POINT
# ==========================================
if __name__ == "__main__":
if login():
main_dashboard()