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
|
|
@@ -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 |
|
|
@@ -155,10 +152,8 @@ def make_fail_reason_hints(df: pd.DataFrame, component_cols: list[str]) -> pd.Da
|
|
| 155 |
|
| 156 |
|
| 157 |
# -----------------------------
|
| 158 |
-
#
|
| 159 |
# -----------------------------
|
| 160 |
-
uploaded = st.file_uploader("Upload Excel (.xlsx)", type=["xlsx"], key="uploader")
|
| 161 |
-
|
| 162 |
if "file_bytes" not in st.session_state:
|
| 163 |
st.session_state["file_bytes"] = None
|
| 164 |
if "file_name" not in st.session_state:
|
|
@@ -166,21 +161,43 @@ if "file_name" not in st.session_state:
|
|
| 166 |
if "sheet_names" not in st.session_state:
|
| 167 |
st.session_state["sheet_names"] = None
|
| 168 |
|
| 169 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
if uploaded is not None:
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
|
|
|
|
|
|
|
|
|
| 174 |
|
| 175 |
-
|
|
|
|
|
|
|
|
|
|
| 176 |
st.info("Upload an Excel file to begin.")
|
| 177 |
st.stop()
|
| 178 |
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
if not (len(file_bytes) >= 2 and file_bytes[:2] == b"PK"):
|
| 183 |
-
st.error("This does not look like a valid .xlsx file. Please re-export/download as Excel (.xlsx) and upload again.")
|
| 184 |
st.stop()
|
| 185 |
|
| 186 |
# Load sheet names (FORCE openpyxl)
|
|
@@ -225,7 +242,7 @@ df = add_consistency(df, component_cols)
|
|
| 225 |
df = make_fail_reason_hints(df, component_cols)
|
| 226 |
|
| 227 |
# -----------------------------
|
| 228 |
-
# Sidebar:
|
| 229 |
# -----------------------------
|
| 230 |
st.sidebar.header("Perspective")
|
| 231 |
view = st.sidebar.radio(
|
|
@@ -273,15 +290,15 @@ def executive_view(d: pd.DataFrame):
|
|
| 273 |
|
| 274 |
with left:
|
| 275 |
st.subheader("Grade Distribution")
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
st.plotly_chart(px.bar(
|
| 279 |
|
| 280 |
with right:
|
| 281 |
st.subheader("Pass/Fail Distribution")
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
st.plotly_chart(px.pie(
|
| 285 |
|
| 286 |
st.subheader("Hidden Patterns (Quick Signals)")
|
| 287 |
c1, c2, c3 = st.columns(3)
|
|
@@ -375,7 +392,6 @@ def export_view(d: pd.DataFrame):
|
|
| 375 |
st.download_button("⬇️ Download Cleaned Data (CSV)", clean_csv, file_name="cleaned_marks_with_passfail.csv", mime="text/csv")
|
| 376 |
|
| 377 |
|
| 378 |
-
# Render view
|
| 379 |
if view == "Executive (Management)":
|
| 380 |
executive_view(filtered)
|
| 381 |
elif view == "Risk & Intervention":
|
|
|
|
| 1 |
+
# app.py (FINAL FULL REPLACEMENT - no NoneType crashes + openpyxl forced + Grade>=C pass)
|
| 2 |
import streamlit as st
|
| 3 |
import pandas as pd
|
| 4 |
import numpy as np
|
|
|
|
| 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 |
|
|
|
|
| 152 |
|
| 153 |
|
| 154 |
# -----------------------------
|
| 155 |
+
# Session state init
|
| 156 |
# -----------------------------
|
|
|
|
|
|
|
| 157 |
if "file_bytes" not in st.session_state:
|
| 158 |
st.session_state["file_bytes"] = None
|
| 159 |
if "file_name" not in st.session_state:
|
|
|
|
| 161 |
if "sheet_names" not in st.session_state:
|
| 162 |
st.session_state["sheet_names"] = None
|
| 163 |
|
| 164 |
+
# Reset button (helps a lot on HF reruns)
|
| 165 |
+
topc1, topc2 = st.columns([1, 3])
|
| 166 |
+
with topc1:
|
| 167 |
+
if st.button("🔄 Reset upload"):
|
| 168 |
+
st.session_state["file_bytes"] = None
|
| 169 |
+
st.session_state["file_name"] = None
|
| 170 |
+
st.session_state["sheet_names"] = None
|
| 171 |
+
st.rerun()
|
| 172 |
+
|
| 173 |
+
with topc2:
|
| 174 |
+
if st.session_state["file_name"]:
|
| 175 |
+
st.info(f"Current file loaded: {st.session_state['file_name']}")
|
| 176 |
+
|
| 177 |
+
# -----------------------------
|
| 178 |
+
# Upload
|
| 179 |
+
# -----------------------------
|
| 180 |
+
uploaded = st.file_uploader("Upload Excel (.xlsx)", type=["xlsx"], key="uploader")
|
| 181 |
+
|
| 182 |
+
# On upload, store bytes
|
| 183 |
if uploaded is not None:
|
| 184 |
+
fb = uploaded.getvalue()
|
| 185 |
+
# fb can *rarely* be None/empty on buggy reruns; guard it
|
| 186 |
+
if fb:
|
| 187 |
+
st.session_state["file_bytes"] = fb
|
| 188 |
+
st.session_state["file_name"] = uploaded.name
|
| 189 |
+
st.session_state["sheet_names"] = None
|
| 190 |
|
| 191 |
+
# Re-check bytes RIGHT BEFORE use
|
| 192 |
+
file_bytes = st.session_state.get("file_bytes", None)
|
| 193 |
+
|
| 194 |
+
if file_bytes is None or not isinstance(file_bytes, (bytes, bytearray)) or len(file_bytes) == 0:
|
| 195 |
st.info("Upload an Excel file to begin.")
|
| 196 |
st.stop()
|
| 197 |
|
| 198 |
+
# Signature check for XLSX (zip => PK)
|
| 199 |
+
if len(file_bytes) < 2 or file_bytes[:2] != b"PK":
|
| 200 |
+
st.error("This does not look like a valid .xlsx file. Please Save As → Excel Workbook (.xlsx) and upload again.")
|
|
|
|
|
|
|
| 201 |
st.stop()
|
| 202 |
|
| 203 |
# Load sheet names (FORCE openpyxl)
|
|
|
|
| 242 |
df = make_fail_reason_hints(df, component_cols)
|
| 243 |
|
| 244 |
# -----------------------------
|
| 245 |
+
# Sidebar: Views + Filters
|
| 246 |
# -----------------------------
|
| 247 |
st.sidebar.header("Perspective")
|
| 248 |
view = st.sidebar.radio(
|
|
|
|
| 290 |
|
| 291 |
with left:
|
| 292 |
st.subheader("Grade Distribution")
|
| 293 |
+
gc = d["Grade"].value_counts(dropna=False).reset_index()
|
| 294 |
+
gc.columns = ["Grade", "Count"]
|
| 295 |
+
st.plotly_chart(px.bar(gc, x="Grade", y="Count"), use_container_width=True)
|
| 296 |
|
| 297 |
with right:
|
| 298 |
st.subheader("Pass/Fail Distribution")
|
| 299 |
+
pc = d["PassFail"].value_counts(dropna=False).reset_index()
|
| 300 |
+
pc.columns = ["Status", "Count"]
|
| 301 |
+
st.plotly_chart(px.pie(pc, names="Status", values="Count"), use_container_width=True)
|
| 302 |
|
| 303 |
st.subheader("Hidden Patterns (Quick Signals)")
|
| 304 |
c1, c2, c3 = st.columns(3)
|
|
|
|
| 392 |
st.download_button("⬇️ Download Cleaned Data (CSV)", clean_csv, file_name="cleaned_marks_with_passfail.csv", mime="text/csv")
|
| 393 |
|
| 394 |
|
|
|
|
| 395 |
if view == "Executive (Management)":
|
| 396 |
executive_view(filtered)
|
| 397 |
elif view == "Risk & Intervention":
|