Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# app.py (FULL
|
| 2 |
import streamlit as st
|
| 3 |
import pandas as pd
|
| 4 |
import numpy as np
|
|
@@ -14,21 +14,24 @@ st.caption(
|
|
| 14 |
)
|
| 15 |
|
| 16 |
# -----------------------------
|
| 17 |
-
# Grade logic (
|
| 18 |
# -----------------------------
|
| 19 |
def grade_pass_fail(g):
|
| 20 |
if pd.isna(g):
|
| 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 |
|
|
@@ -54,6 +57,7 @@ def coerce_numeric(df: pd.DataFrame, cols):
|
|
| 54 |
|
| 55 |
|
| 56 |
def detect_student_rows(df: pd.DataFrame, grade_col: str) -> pd.DataFrame:
|
|
|
|
| 57 |
tmp_grade = df[grade_col].astype(str).str.strip()
|
| 58 |
grade_like = tmp_grade.str.match(r"^[A-Fa-f][\+\-]?$", na=False)
|
| 59 |
|
|
@@ -87,6 +91,7 @@ def infer_component_cols(df: pd.DataFrame, grade_col: str, sno_col: str) -> list
|
|
| 87 |
component_cols = [c for c in df.columns if c in common and c not in [grade_col, sno_col]]
|
| 88 |
|
| 89 |
if not component_cols:
|
|
|
|
| 90 |
numeric_cols = []
|
| 91 |
for c in df.columns:
|
| 92 |
if c in [grade_col, sno_col]:
|
|
@@ -106,14 +111,20 @@ def infer_component_cols(df: pd.DataFrame, grade_col: str, sno_col: str) -> list
|
|
| 106 |
|
| 107 |
def add_consistency(df: pd.DataFrame, component_cols: list[str]) -> pd.DataFrame:
|
| 108 |
df = df.copy()
|
| 109 |
-
cols_for_sd = [
|
|
|
|
|
|
|
|
|
|
| 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 |
|
| 114 |
def make_fail_reason_hints(df: pd.DataFrame, component_cols: list[str]) -> pd.DataFrame:
|
| 115 |
df = df.copy()
|
| 116 |
-
comps = [
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
if not comps:
|
| 119 |
df["FailReasonHint"] = np.where(df["PassFail"] == "Fail", "Grade below C.", "")
|
|
@@ -146,7 +157,7 @@ def make_fail_reason_hints(df: pd.DataFrame, component_cols: list[str]) -> pd.Da
|
|
| 146 |
|
| 147 |
|
| 148 |
# -----------------------------
|
| 149 |
-
# Upload (HF
|
| 150 |
# -----------------------------
|
| 151 |
uploaded = st.file_uploader("Upload Excel (.xlsx)", type=["xlsx"], key="uploader")
|
| 152 |
|
|
@@ -154,32 +165,39 @@ 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 |
-
#
|
| 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 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
|
|
|
| 176 |
|
| 177 |
-
sheet = st.selectbox("Select sheet",
|
| 178 |
|
| 179 |
-
|
|
|
|
| 180 |
raw = pd.read_excel(bio, sheet_name=sheet)
|
| 181 |
raw = normalize_headers(raw)
|
| 182 |
|
|
|
|
|
|
|
|
|
|
| 183 |
grade_col_name = pick_grade_column(raw)
|
| 184 |
|
| 185 |
df = detect_student_rows(raw, grade_col_name)
|
|
@@ -343,13 +361,23 @@ def student_drilldown_view(d: pd.DataFrame):
|
|
| 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
|
| 352 |
|
|
|
|
| 353 |
if view == "Executive (Management)":
|
| 354 |
executive_view(filtered)
|
| 355 |
elif view == "Risk & Intervention":
|
|
|
|
| 1 |
+
# app.py (UPDATED FULL FILE - HF/Streamlit stable uploader + no xls NameError + Grade>=C pass)
|
| 2 |
import streamlit as st
|
| 3 |
import pandas as pd
|
| 4 |
import numpy as np
|
|
|
|
| 14 |
)
|
| 15 |
|
| 16 |
# -----------------------------
|
| 17 |
+
# Grade logic (as specified)
|
| 18 |
# -----------------------------
|
| 19 |
def grade_pass_fail(g):
|
| 20 |
if pd.isna(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 |
|
|
|
|
| 57 |
|
| 58 |
|
| 59 |
def detect_student_rows(df: pd.DataFrame, grade_col: str) -> pd.DataFrame:
|
| 60 |
+
# keep rows that look like grade (A, B+, C-, etc.) OR have any numeric mark
|
| 61 |
tmp_grade = df[grade_col].astype(str).str.strip()
|
| 62 |
grade_like = tmp_grade.str.match(r"^[A-Fa-f][\+\-]?$", na=False)
|
| 63 |
|
|
|
|
| 91 |
component_cols = [c for c in df.columns if c in common and c not in [grade_col, sno_col]]
|
| 92 |
|
| 93 |
if not component_cols:
|
| 94 |
+
# fallback: numeric columns except grade/sno
|
| 95 |
numeric_cols = []
|
| 96 |
for c in df.columns:
|
| 97 |
if c in [grade_col, sno_col]:
|
|
|
|
| 111 |
|
| 112 |
def add_consistency(df: pd.DataFrame, component_cols: list[str]) -> pd.DataFrame:
|
| 113 |
df = df.copy()
|
| 114 |
+
cols_for_sd = [
|
| 115 |
+
c for c in component_cols
|
| 116 |
+
if c.lower() != "total" and c in df.columns and pd.api.types.is_numeric_dtype(df[c])
|
| 117 |
+
]
|
| 118 |
df["Consistency_SD"] = df[cols_for_sd].std(axis=1, skipna=True) if len(cols_for_sd) >= 2 else np.nan
|
| 119 |
return df
|
| 120 |
|
| 121 |
|
| 122 |
def make_fail_reason_hints(df: pd.DataFrame, component_cols: list[str]) -> pd.DataFrame:
|
| 123 |
df = df.copy()
|
| 124 |
+
comps = [
|
| 125 |
+
c for c in component_cols
|
| 126 |
+
if c.lower() != "total" and c in df.columns and pd.api.types.is_numeric_dtype(df[c])
|
| 127 |
+
]
|
| 128 |
|
| 129 |
if not comps:
|
| 130 |
df["FailReasonHint"] = np.where(df["PassFail"] == "Fail", "Grade below C.", "")
|
|
|
|
| 157 |
|
| 158 |
|
| 159 |
# -----------------------------
|
| 160 |
+
# Upload (HF stable using session_state)
|
| 161 |
# -----------------------------
|
| 162 |
uploaded = st.file_uploader("Upload Excel (.xlsx)", type=["xlsx"], key="uploader")
|
| 163 |
|
|
|
|
| 165 |
st.session_state["file_bytes"] = None
|
| 166 |
if "file_name" not in st.session_state:
|
| 167 |
st.session_state["file_name"] = None
|
| 168 |
+
if "sheet_names" not in st.session_state:
|
| 169 |
+
st.session_state["sheet_names"] = None
|
| 170 |
|
| 171 |
+
# Persist uploaded bytes across reruns
|
| 172 |
if uploaded is not None:
|
| 173 |
st.session_state["file_bytes"] = uploaded.getvalue()
|
| 174 |
st.session_state["file_name"] = uploaded.name
|
| 175 |
+
st.session_state["sheet_names"] = None # reset sheets on new upload
|
| 176 |
|
|
|
|
| 177 |
if st.session_state["file_bytes"] is None:
|
| 178 |
st.info("Upload an Excel file to begin.")
|
| 179 |
st.stop()
|
| 180 |
|
| 181 |
+
# Load sheet names into session_state (prevents xls NameError on reruns)
|
| 182 |
+
if st.session_state["sheet_names"] is None:
|
| 183 |
+
try:
|
| 184 |
+
bio = io.BytesIO(st.session_state["file_bytes"])
|
| 185 |
+
xls_local = pd.ExcelFile(bio)
|
| 186 |
+
st.session_state["sheet_names"] = xls_local.sheet_names
|
| 187 |
+
except Exception as e:
|
| 188 |
+
st.error(f"Could not read Excel file: {e}")
|
| 189 |
+
st.stop()
|
| 190 |
|
| 191 |
+
sheet = st.selectbox("Select sheet", st.session_state["sheet_names"], index=0)
|
| 192 |
|
| 193 |
+
# Read selected sheet
|
| 194 |
+
bio = io.BytesIO(st.session_state["file_bytes"])
|
| 195 |
raw = pd.read_excel(bio, sheet_name=sheet)
|
| 196 |
raw = normalize_headers(raw)
|
| 197 |
|
| 198 |
+
# -----------------------------
|
| 199 |
+
# Build dataframe
|
| 200 |
+
# -----------------------------
|
| 201 |
grade_col_name = pick_grade_column(raw)
|
| 202 |
|
| 203 |
df = detect_student_rows(raw, grade_col_name)
|
|
|
|
| 361 |
with c3:
|
| 362 |
st.metric("At Risk", "Yes" if row.get("Fail") else "No")
|
| 363 |
|
| 364 |
+
hint = row.get("FailReasonHint", "")
|
| 365 |
+
if hint:
|
| 366 |
+
st.write("**Reason hint:**", hint)
|
| 367 |
+
|
| 368 |
|
| 369 |
def export_view(d: pd.DataFrame):
|
| 370 |
st.subheader("Export for Power BI")
|
| 371 |
clean_csv = d.to_csv(index=False).encode("utf-8")
|
| 372 |
+
st.download_button(
|
| 373 |
+
"⬇️ Download Cleaned Data (CSV)",
|
| 374 |
+
clean_csv,
|
| 375 |
+
file_name="cleaned_marks_with_passfail.csv",
|
| 376 |
+
mime="text/csv"
|
| 377 |
+
)
|
| 378 |
|
| 379 |
|
| 380 |
+
# Render view
|
| 381 |
if view == "Executive (Management)":
|
| 382 |
executive_view(filtered)
|
| 383 |
elif view == "Risk & Intervention":
|