Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# app.py (FULL REPLACEMENT -
|
| 2 |
import streamlit as st
|
| 3 |
import pandas as pd
|
| 4 |
import numpy as np
|
|
@@ -10,7 +10,7 @@ st.set_page_config(page_title="Excel β Management Insights (Power BI style)",
|
|
| 10 |
st.title("π Excel β Interactive Management Dashboard (Power BI style)")
|
| 11 |
st.caption(
|
| 12 |
"Decision rule: **PASS if Grade β₯ C (C, C+, B-, etc.)** and **FAIL if below C (C-, D, F, etc.)**. "
|
| 13 |
-
"
|
| 14 |
)
|
| 15 |
|
| 16 |
# -----------------------------
|
|
@@ -21,17 +21,14 @@ def grade_pass_fail(g):
|
|
| 21 |
return "Unknown"
|
| 22 |
g = str(g).strip().upper()
|
| 23 |
|
| 24 |
-
# Explicit FAIL
|
| 25 |
if g.startswith(("D", "E", "F")):
|
| 26 |
return "Fail"
|
| 27 |
|
| 28 |
-
# C- is FAIL, all other C variants are PASS
|
| 29 |
if g.startswith("C"):
|
| 30 |
if g == "C-" or g.startswith("C-"):
|
| 31 |
return "Fail"
|
| 32 |
return "Pass"
|
| 33 |
|
| 34 |
-
# A/B (with any +/-) are PASS
|
| 35 |
if g.startswith(("A", "B")):
|
| 36 |
return "Pass"
|
| 37 |
|
|
@@ -109,11 +106,8 @@ def infer_component_cols(df: pd.DataFrame, grade_col: str, sno_col: str) -> list
|
|
| 109 |
|
| 110 |
def add_consistency(df: pd.DataFrame, component_cols: list[str]) -> pd.DataFrame:
|
| 111 |
df = df.copy()
|
| 112 |
-
cols_for_sd = [c for c in component_cols if c.lower() != "total" and pd.api.types.is_numeric_dtype(df
|
| 113 |
-
if len(cols_for_sd) >= 2
|
| 114 |
-
df["Consistency_SD"] = df[cols_for_sd].std(axis=1, skipna=True)
|
| 115 |
-
else:
|
| 116 |
-
df["Consistency_SD"] = np.nan
|
| 117 |
return df
|
| 118 |
|
| 119 |
|
|
@@ -152,21 +146,28 @@ def make_fail_reason_hints(df: pd.DataFrame, component_cols: list[str]) -> pd.Da
|
|
| 152 |
|
| 153 |
|
| 154 |
# -----------------------------
|
| 155 |
-
# Upload (HF
|
| 156 |
# -----------------------------
|
| 157 |
uploaded = st.file_uploader("Upload Excel (.xlsx)", type=["xlsx"], key="uploader")
|
| 158 |
|
| 159 |
-
if
|
| 160 |
-
st.
|
| 161 |
-
|
|
|
|
| 162 |
|
| 163 |
-
#
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
st.stop()
|
| 168 |
|
|
|
|
| 169 |
bio = io.BytesIO(file_bytes)
|
|
|
|
| 170 |
try:
|
| 171 |
xls = pd.ExcelFile(bio)
|
| 172 |
except Exception as e:
|
|
@@ -175,7 +176,6 @@ except Exception as e:
|
|
| 175 |
|
| 176 |
sheet = st.selectbox("Select sheet", xls.sheet_names, index=0)
|
| 177 |
|
| 178 |
-
# Rewind and read sheet
|
| 179 |
bio.seek(0)
|
| 180 |
raw = pd.read_excel(bio, sheet_name=sheet)
|
| 181 |
raw = normalize_headers(raw)
|
|
@@ -286,14 +286,6 @@ def executive_view(d: pd.DataFrame):
|
|
| 286 |
with c3:
|
| 287 |
st.metric("Fail with High Total", "β")
|
| 288 |
|
| 289 |
-
numeric_comps = [c for c in component_cols if c in d.columns and pd.api.types.is_numeric_dtype(d[c])]
|
| 290 |
-
if "Total" in d.columns and pd.api.types.is_numeric_dtype(d["Total"]) and numeric_comps:
|
| 291 |
-
st.subheader("What Drives Total? (Correlation)")
|
| 292 |
-
corr_cols = numeric_comps + ["Total"]
|
| 293 |
-
corr = d[corr_cols].corr(numeric_only=True)
|
| 294 |
-
fig = px.imshow(corr, text_auto=True, aspect="auto")
|
| 295 |
-
st.plotly_chart(fig, use_container_width=True)
|
| 296 |
-
|
| 297 |
|
| 298 |
def risk_view(d: pd.DataFrame):
|
| 299 |
st.subheader("Fail List (Grade below C)")
|
|
@@ -319,26 +311,11 @@ def risk_view(d: pd.DataFrame):
|
|
| 319 |
if c in fails.columns and c not in show_cols:
|
| 320 |
show_cols.append(c)
|
| 321 |
show_cols.append("FailReasonHint")
|
| 322 |
-
|
| 323 |
-
st.dataframe(
|
| 324 |
-
fails[show_cols].sort_values(by=["Grade", sno_col]),
|
| 325 |
-
use_container_width=True,
|
| 326 |
-
height=420
|
| 327 |
-
)
|
| 328 |
-
|
| 329 |
-
st.subheader("Intervention Suggestions")
|
| 330 |
-
st.markdown(
|
| 331 |
-
"""
|
| 332 |
-
- **Many C- failures** β target borderline support (revision plan + short formative checks).
|
| 333 |
-
- **Failures with low Final** β structured exam-prep support (mock tests + feedback).
|
| 334 |
-
- **Failures with strong Lab** β review exam alignment + study strategy support.
|
| 335 |
-
"""
|
| 336 |
-
)
|
| 337 |
|
| 338 |
|
| 339 |
def assessment_quality_view(d: pd.DataFrame):
|
| 340 |
st.subheader("Assessment Component Overview")
|
| 341 |
-
|
| 342 |
numeric_comps = [c for c in component_cols if c in d.columns and pd.api.types.is_numeric_dtype(d[c]) and c.lower() != "total"]
|
| 343 |
if not numeric_comps:
|
| 344 |
st.warning("No numeric component columns detected for assessment analysis.")
|
|
@@ -352,26 +329,9 @@ def assessment_quality_view(d: pd.DataFrame):
|
|
| 352 |
fig = px.box(d, x="Grade", y=comp)
|
| 353 |
st.plotly_chart(fig, use_container_width=True)
|
| 354 |
|
| 355 |
-
st.subheader("Data Quality Flags")
|
| 356 |
-
flags = []
|
| 357 |
-
for c in numeric_comps:
|
| 358 |
-
series = d[c]
|
| 359 |
-
missing = int(series.isna().sum())
|
| 360 |
-
zeros = int((series == 0).sum())
|
| 361 |
-
flags.append({"Component": c, "Missing": missing, "Zeros": zeros})
|
| 362 |
-
st.dataframe(pd.DataFrame(flags), use_container_width=True)
|
| 363 |
-
|
| 364 |
-
if "Total" in d.columns and pd.api.types.is_numeric_dtype(d["Total"]):
|
| 365 |
-
st.subheader("Correlation Heatmap")
|
| 366 |
-
corr_cols = numeric_comps + ["Total"]
|
| 367 |
-
corr = d[corr_cols].corr(numeric_only=True)
|
| 368 |
-
fig = px.imshow(corr, text_auto=True, aspect="auto")
|
| 369 |
-
st.plotly_chart(fig, use_container_width=True)
|
| 370 |
-
|
| 371 |
|
| 372 |
def student_drilldown_view(d: pd.DataFrame):
|
| 373 |
st.subheader("Student Drill-down")
|
| 374 |
-
|
| 375 |
sid = st.selectbox("Select student (Sno)", sorted(d[sno_col].unique()))
|
| 376 |
row = d[d[sno_col] == sid].iloc[0]
|
| 377 |
|
|
@@ -381,39 +341,15 @@ def student_drilldown_view(d: pd.DataFrame):
|
|
| 381 |
with c2:
|
| 382 |
st.metric("Status", str(row.get("PassFail", "β")))
|
| 383 |
with c3:
|
| 384 |
-
|
| 385 |
-
st.metric("Total", f"{row['Total']:.2f}")
|
| 386 |
-
else:
|
| 387 |
-
st.metric("Total", "β")
|
| 388 |
-
|
| 389 |
-
hint = row.get("FailReasonHint", "")
|
| 390 |
-
if hint:
|
| 391 |
-
st.write("**Reason hint:**", hint)
|
| 392 |
-
|
| 393 |
-
numeric_comps = [c for c in component_cols if c in d.columns and pd.api.types.is_numeric_dtype(d[c]) and c.lower() != "total"]
|
| 394 |
-
if numeric_comps:
|
| 395 |
-
comp_vals = {c: row.get(c) for c in numeric_comps}
|
| 396 |
-
comp_df = pd.DataFrame({"Component": list(comp_vals.keys()), "Score": list(comp_vals.values())})
|
| 397 |
-
fig = px.bar(comp_df, x="Component", y="Score")
|
| 398 |
-
st.plotly_chart(fig, use_container_width=True)
|
| 399 |
-
|
| 400 |
-
st.subheader("Raw record")
|
| 401 |
-
st.dataframe(pd.DataFrame(row).T, use_container_width=True)
|
| 402 |
|
| 403 |
|
| 404 |
def export_view(d: pd.DataFrame):
|
| 405 |
st.subheader("Export for Power BI")
|
| 406 |
-
|
| 407 |
clean_csv = d.to_csv(index=False).encode("utf-8")
|
| 408 |
-
st.download_button(
|
| 409 |
-
"β¬οΈ Download Cleaned Data (CSV)",
|
| 410 |
-
clean_csv,
|
| 411 |
-
file_name="cleaned_marks_with_passfail.csv",
|
| 412 |
-
mime="text/csv"
|
| 413 |
-
)
|
| 414 |
|
| 415 |
|
| 416 |
-
# Render view
|
| 417 |
if view == "Executive (Management)":
|
| 418 |
executive_view(filtered)
|
| 419 |
elif view == "Risk & Intervention":
|
|
|
|
| 1 |
+
# app.py (FULL REPLACEMENT - session_state upload fix + grade>=C pass logic)
|
| 2 |
import streamlit as st
|
| 3 |
import pandas as pd
|
| 4 |
import numpy as np
|
|
|
|
| 10 |
st.title("π Excel β Interactive Management Dashboard (Power BI style)")
|
| 11 |
st.caption(
|
| 12 |
"Decision rule: **PASS if Grade β₯ C (C, C+, B-, etc.)** and **FAIL if below C (C-, D, F, etc.)**. "
|
| 13 |
+
"Pass/Fail uses **Grade only**."
|
| 14 |
)
|
| 15 |
|
| 16 |
# -----------------------------
|
|
|
|
| 21 |
return "Unknown"
|
| 22 |
g = str(g).strip().upper()
|
| 23 |
|
|
|
|
| 24 |
if g.startswith(("D", "E", "F")):
|
| 25 |
return "Fail"
|
| 26 |
|
|
|
|
| 27 |
if g.startswith("C"):
|
| 28 |
if g == "C-" or g.startswith("C-"):
|
| 29 |
return "Fail"
|
| 30 |
return "Pass"
|
| 31 |
|
|
|
|
| 32 |
if g.startswith(("A", "B")):
|
| 33 |
return "Pass"
|
| 34 |
|
|
|
|
| 106 |
|
| 107 |
def add_consistency(df: pd.DataFrame, component_cols: list[str]) -> pd.DataFrame:
|
| 108 |
df = df.copy()
|
| 109 |
+
cols_for_sd = [c for c in component_cols if c.lower() != "total" and c in df.columns and pd.api.types.is_numeric_dtype(df[c])]
|
| 110 |
+
df["Consistency_SD"] = df[cols_for_sd].std(axis=1, skipna=True) if len(cols_for_sd) >= 2 else np.nan
|
|
|
|
|
|
|
|
|
|
| 111 |
return df
|
| 112 |
|
| 113 |
|
|
|
|
| 146 |
|
| 147 |
|
| 148 |
# -----------------------------
|
| 149 |
+
# Upload (HF safe using session_state)
|
| 150 |
# -----------------------------
|
| 151 |
uploaded = st.file_uploader("Upload Excel (.xlsx)", type=["xlsx"], key="uploader")
|
| 152 |
|
| 153 |
+
if "file_bytes" not in st.session_state:
|
| 154 |
+
st.session_state["file_bytes"] = None
|
| 155 |
+
if "file_name" not in st.session_state:
|
| 156 |
+
st.session_state["file_name"] = None
|
| 157 |
|
| 158 |
+
# If new upload exists, store it in session_state (survives reruns)
|
| 159 |
+
if uploaded is not None:
|
| 160 |
+
st.session_state["file_bytes"] = uploaded.getvalue()
|
| 161 |
+
st.session_state["file_name"] = uploaded.name
|
| 162 |
+
|
| 163 |
+
# If still no bytes, stop safely
|
| 164 |
+
if st.session_state["file_bytes"] is None:
|
| 165 |
+
st.info("Upload an Excel file to begin.")
|
| 166 |
st.stop()
|
| 167 |
|
| 168 |
+
file_bytes = st.session_state["file_bytes"]
|
| 169 |
bio = io.BytesIO(file_bytes)
|
| 170 |
+
|
| 171 |
try:
|
| 172 |
xls = pd.ExcelFile(bio)
|
| 173 |
except Exception as e:
|
|
|
|
| 176 |
|
| 177 |
sheet = st.selectbox("Select sheet", xls.sheet_names, index=0)
|
| 178 |
|
|
|
|
| 179 |
bio.seek(0)
|
| 180 |
raw = pd.read_excel(bio, sheet_name=sheet)
|
| 181 |
raw = normalize_headers(raw)
|
|
|
|
| 286 |
with c3:
|
| 287 |
st.metric("Fail with High Total", "β")
|
| 288 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
|
| 290 |
def risk_view(d: pd.DataFrame):
|
| 291 |
st.subheader("Fail List (Grade below C)")
|
|
|
|
| 311 |
if c in fails.columns and c not in show_cols:
|
| 312 |
show_cols.append(c)
|
| 313 |
show_cols.append("FailReasonHint")
|
| 314 |
+
st.dataframe(fails[show_cols].sort_values(by=["Grade", sno_col]), use_container_width=True, height=420)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
|
| 316 |
|
| 317 |
def assessment_quality_view(d: pd.DataFrame):
|
| 318 |
st.subheader("Assessment Component Overview")
|
|
|
|
| 319 |
numeric_comps = [c for c in component_cols if c in d.columns and pd.api.types.is_numeric_dtype(d[c]) and c.lower() != "total"]
|
| 320 |
if not numeric_comps:
|
| 321 |
st.warning("No numeric component columns detected for assessment analysis.")
|
|
|
|
| 329 |
fig = px.box(d, x="Grade", y=comp)
|
| 330 |
st.plotly_chart(fig, use_container_width=True)
|
| 331 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
|
| 333 |
def student_drilldown_view(d: pd.DataFrame):
|
| 334 |
st.subheader("Student Drill-down")
|
|
|
|
| 335 |
sid = st.selectbox("Select student (Sno)", sorted(d[sno_col].unique()))
|
| 336 |
row = d[d[sno_col] == sid].iloc[0]
|
| 337 |
|
|
|
|
| 341 |
with c2:
|
| 342 |
st.metric("Status", str(row.get("PassFail", "β")))
|
| 343 |
with c3:
|
| 344 |
+
st.metric("At Risk", "Yes" if row.get("Fail") else "No")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 345 |
|
| 346 |
|
| 347 |
def export_view(d: pd.DataFrame):
|
| 348 |
st.subheader("Export for Power BI")
|
|
|
|
| 349 |
clean_csv = d.to_csv(index=False).encode("utf-8")
|
| 350 |
+
st.download_button("β¬οΈ Download Cleaned Data (CSV)", clean_csv, file_name="cleaned_marks_with_passfail.csv", mime="text/csv")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
|
| 352 |
|
|
|
|
| 353 |
if view == "Executive (Management)":
|
| 354 |
executive_view(filtered)
|
| 355 |
elif view == "Risk & Intervention":
|