Deevyankar commited on
Commit
09d1bbf
Β·
verified Β·
1 Parent(s): 6e3c696

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +22 -86
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # app.py (FULL REPLACEMENT - fixes NoneType getvalue + grade>=C pass logic)
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
- "This dashboard uses the **Grade column only** for pass/fail."
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.get(c, pd.Series(dtype=float)))]
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 SAFE) β€” no getvalue until uploaded is confirmed
156
  # -----------------------------
157
  uploaded = st.file_uploader("Upload Excel (.xlsx)", type=["xlsx"], key="uploader")
158
 
159
- if uploaded is None:
160
- st.info("Upload an Excel file to begin.")
161
- st.stop()
 
162
 
163
- # ONLY HERE we access bytes
164
- file_bytes = uploaded.read() # more robust than getvalue() on HF
165
- if not file_bytes:
166
- st.warning("Uploaded file appears empty. Please re-upload the Excel file.")
 
 
 
 
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
- if "Total" in d.columns and pd.notna(row.get("Total", np.nan)):
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":