Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,7 +1,13 @@
|
|
| 1 |
# app.py (Fast-build Hugging Face Gradio)
|
| 2 |
# School Mark Analysis: RegNo, Name, Tamil, English, Maths, Science, Social
|
| 3 |
-
# Features:
|
| 4 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
import os
|
| 7 |
import tempfile
|
|
@@ -9,43 +15,16 @@ import numpy as np
|
|
| 9 |
import pandas as pd
|
| 10 |
import gradio as gr
|
| 11 |
import matplotlib.pyplot as plt
|
| 12 |
-
import
|
| 13 |
-
import gradio as gr
|
| 14 |
-
|
| 15 |
-
def logo_html():
|
| 16 |
-
# Put the logo in the SAME folder as app.py
|
| 17 |
-
candidates = [
|
| 18 |
-
"logo.png", "logo.jpg", "logo.jpeg",
|
| 19 |
-
"Logo.png", "Logo.jpg", "Logo.jpeg"
|
| 20 |
-
]
|
| 21 |
-
for fn in candidates:
|
| 22 |
-
if os.path.exists(fn):
|
| 23 |
-
return f"""
|
| 24 |
-
<div style="text-align:center; margin:6px 0 10px 0;">
|
| 25 |
-
<img src="file={fn}" style="width:130px; display:block; margin:0 auto;" />
|
| 26 |
-
<div style="font-size:30px; font-weight:800; color:#1E5AA8;">Amrita Manthana</div>
|
| 27 |
-
<div style="font-size:18px; font-weight:800; color:#1E5AA8;">Prof.B.Santhi,SRC,SASTRA</div>
|
| 28 |
-
</div>
|
| 29 |
-
"""
|
| 30 |
-
# fallback (no image found)
|
| 31 |
-
return """
|
| 32 |
-
<div style="text-align:center; margin:6px 0 10px 0;">
|
| 33 |
-
<div style="font-size:30px; font-weight:800; color:#1E5AA8;">Amrita Manthana</div>
|
| 34 |
-
<div style="font-size:18px; font-weight:800; color:#1E5AA8;">Prof.B.Santhi,SRC,SASTRA</div>
|
| 35 |
-
<div style="font-size:12px; color:#777;">(logo file not found in repo root)</div>
|
| 36 |
-
</div>
|
| 37 |
-
"""
|
| 38 |
-
|
| 39 |
-
# Use this inside your Blocks:
|
| 40 |
-
# with gr.Blocks(...) as demo:
|
| 41 |
-
# gr.HTML(logo_html())
|
| 42 |
-
|
| 43 |
|
| 44 |
SUBJECTS_DEFAULT = ["Tamil", "English", "Maths", "Science", "Social"]
|
| 45 |
ID_COL_DEFAULT = "RegNo"
|
| 46 |
NAME_COL_DEFAULT = "Name"
|
| 47 |
|
| 48 |
|
|
|
|
|
|
|
|
|
|
| 49 |
def _clean_columns(df: pd.DataFrame) -> pd.DataFrame:
|
| 50 |
df = df.copy()
|
| 51 |
df.columns = [c.strip() for c in df.columns]
|
|
@@ -73,7 +52,6 @@ def _validate_and_prepare(df: pd.DataFrame, id_col: str, name_col: str, subjects
|
|
| 73 |
def _remark(avg: float, failed_subjects: int) -> str:
|
| 74 |
if failed_subjects > 0:
|
| 75 |
return "Fail"
|
| 76 |
-
# Only pass students reach here
|
| 77 |
if avg >= 80:
|
| 78 |
return "Distinction"
|
| 79 |
if 60 <= avg <= 79:
|
|
@@ -83,8 +61,16 @@ def _remark(avg: float, failed_subjects: int) -> str:
|
|
| 83 |
return "Pass"
|
| 84 |
|
| 85 |
|
| 86 |
-
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
df = _validate_and_prepare(df, id_col, name_col, subjects)
|
| 89 |
|
| 90 |
out = df.copy()
|
|
@@ -140,6 +126,9 @@ def compute_marks(df: pd.DataFrame, pass_mark: int = 35, id_col: str = ID_COL_DE
|
|
| 140 |
return out, subj_avg, fail_dist, top3_overall, top3_each_subject, summary
|
| 141 |
|
| 142 |
|
|
|
|
|
|
|
|
|
|
| 143 |
def plot_subject_avg(subj_avg: pd.DataFrame):
|
| 144 |
fig, ax = plt.subplots(figsize=(7, 4))
|
| 145 |
ax.bar(subj_avg["Subject"], subj_avg["Class_Average"])
|
|
@@ -149,6 +138,7 @@ def plot_subject_avg(subj_avg: pd.DataFrame):
|
|
| 149 |
ax.set_ylim(0, 100)
|
| 150 |
plt.xticks(rotation=25, ha="right")
|
| 151 |
plt.tight_layout()
|
|
|
|
| 152 |
return fig
|
| 153 |
|
| 154 |
|
|
@@ -165,9 +155,13 @@ def plot_remark_distribution(result_df: pd.DataFrame):
|
|
| 165 |
ax.set_ylabel("Number of Students")
|
| 166 |
plt.xticks(rotation=20, ha="right")
|
| 167 |
plt.tight_layout()
|
|
|
|
| 168 |
return fig
|
| 169 |
|
| 170 |
|
|
|
|
|
|
|
|
|
|
| 171 |
def load_csv(file_obj):
|
| 172 |
if file_obj is None:
|
| 173 |
return None, "Please upload a CSV.", None
|
|
@@ -207,9 +201,7 @@ def search_regno(result_df, regno_value):
|
|
| 207 |
if not regno_value:
|
| 208 |
return "Enter RegNo to search.", pd.DataFrame()
|
| 209 |
|
| 210 |
-
# Exact match (string or numeric)
|
| 211 |
col = result_df[ID_COL_DEFAULT]
|
| 212 |
-
res = None
|
| 213 |
if pd.api.types.is_numeric_dtype(col):
|
| 214 |
try:
|
| 215 |
q = float(regno_value)
|
|
@@ -219,41 +211,48 @@ def search_regno(result_df, regno_value):
|
|
| 219 |
else:
|
| 220 |
res = result_df[col.astype(str) == str(regno_value)]
|
| 221 |
|
| 222 |
-
if res
|
| 223 |
return "No matching record found.", pd.DataFrame()
|
| 224 |
return f"Found {len(res)} record(s).", res
|
| 225 |
|
| 226 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
CSS = """
|
| 228 |
#titleblock {text-align:center; margin-top: 6px; margin-bottom: 8px;}
|
| 229 |
#t1 {font-size:30px; font-weight:800; color:#1E5AA8;}
|
| 230 |
#t2 {font-size:18px; font-weight:800; color:#1E5AA8;}
|
| 231 |
"""
|
| 232 |
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
"""
|
| 242 |
-
return """
|
| 243 |
-
<div id="titleblock">
|
| 244 |
<div id="t1">Amrita Manthana</div>
|
| 245 |
<div id="t2">Prof.B.Santhi,SRC,SASTRA</div>
|
| 246 |
</div>
|
| 247 |
-
"""
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
with gr.Blocks(css=CSS, title="School Mark Analysis") as demo:
|
| 253 |
-
gr.HTML(header_html())
|
| 254 |
|
| 255 |
df_state = gr.State(None)
|
| 256 |
-
result_state = gr.State(None)
|
| 257 |
|
| 258 |
with gr.Row():
|
| 259 |
with gr.Column(scale=1, min_width=340):
|
|
@@ -273,12 +272,20 @@ with gr.Blocks(css=CSS, title="School Mark Analysis") as demo:
|
|
| 273 |
with gr.Column(scale=2):
|
| 274 |
with gr.Tabs():
|
| 275 |
with gr.Tab("Result Table"):
|
| 276 |
-
result_table = gr.Dataframe(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
with gr.Tab("Subject Averages"):
|
| 278 |
subj_avg_table = gr.Dataframe(label="Subject-wise Averages", interactive=False, wrap=True)
|
| 279 |
avg_plot = gr.Plot(label="Bar Chart: Subject-wise Average")
|
| 280 |
with gr.Tab("Fail Counts"):
|
| 281 |
-
fail_dist_table = gr.Dataframe(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
with gr.Tab("Toppers"):
|
| 283 |
top3_overall_table = gr.Dataframe(label="Overall Top 3", interactive=False, wrap=True)
|
| 284 |
top3_each_subject_table = gr.Dataframe(label="Top 3 in each subject", interactive=False, wrap=True)
|
|
@@ -304,8 +311,10 @@ with gr.Blocks(css=CSS, title="School Mark Analysis") as demo:
|
|
| 304 |
run_btn.click(
|
| 305 |
run_and_store,
|
| 306 |
inputs=[df_state, pass_mark],
|
| 307 |
-
outputs=[
|
| 308 |
-
|
|
|
|
|
|
|
| 309 |
)
|
| 310 |
|
| 311 |
search_btn.click(
|
|
|
|
| 1 |
# app.py (Fast-build Hugging Face Gradio)
|
| 2 |
# School Mark Analysis: RegNo, Name, Tamil, English, Maths, Science, Social
|
| 3 |
+
# Features:
|
| 4 |
+
# - Total, Average, Rank, Remark
|
| 5 |
+
# - Subject-wise average + bar chart
|
| 6 |
+
# - Fail-count distribution (failed in 1..5 subjects)
|
| 7 |
+
# - Top-3 overall, Top-3 in each subject
|
| 8 |
+
# - Search by RegNo
|
| 9 |
+
# - Download final result CSV
|
| 10 |
+
# - Logo display (reliable on Hugging Face) using gr.Image + Pillow
|
| 11 |
|
| 12 |
import os
|
| 13 |
import tempfile
|
|
|
|
| 15 |
import pandas as pd
|
| 16 |
import gradio as gr
|
| 17 |
import matplotlib.pyplot as plt
|
| 18 |
+
from PIL import Image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
SUBJECTS_DEFAULT = ["Tamil", "English", "Maths", "Science", "Social"]
|
| 21 |
ID_COL_DEFAULT = "RegNo"
|
| 22 |
NAME_COL_DEFAULT = "Name"
|
| 23 |
|
| 24 |
|
| 25 |
+
# -----------------------------
|
| 26 |
+
# Utilities
|
| 27 |
+
# -----------------------------
|
| 28 |
def _clean_columns(df: pd.DataFrame) -> pd.DataFrame:
|
| 29 |
df = df.copy()
|
| 30 |
df.columns = [c.strip() for c in df.columns]
|
|
|
|
| 52 |
def _remark(avg: float, failed_subjects: int) -> str:
|
| 53 |
if failed_subjects > 0:
|
| 54 |
return "Fail"
|
|
|
|
| 55 |
if avg >= 80:
|
| 56 |
return "Distinction"
|
| 57 |
if 60 <= avg <= 79:
|
|
|
|
| 61 |
return "Pass"
|
| 62 |
|
| 63 |
|
| 64 |
+
# -----------------------------
|
| 65 |
+
# Core computations
|
| 66 |
+
# -----------------------------
|
| 67 |
+
def compute_marks(
|
| 68 |
+
df: pd.DataFrame,
|
| 69 |
+
pass_mark: int = 35,
|
| 70 |
+
id_col: str = ID_COL_DEFAULT,
|
| 71 |
+
name_col: str = NAME_COL_DEFAULT,
|
| 72 |
+
subjects: list[str] = SUBJECTS_DEFAULT,
|
| 73 |
+
):
|
| 74 |
df = _validate_and_prepare(df, id_col, name_col, subjects)
|
| 75 |
|
| 76 |
out = df.copy()
|
|
|
|
| 126 |
return out, subj_avg, fail_dist, top3_overall, top3_each_subject, summary
|
| 127 |
|
| 128 |
|
| 129 |
+
# -----------------------------
|
| 130 |
+
# Plots (close figs to avoid memory growth)
|
| 131 |
+
# -----------------------------
|
| 132 |
def plot_subject_avg(subj_avg: pd.DataFrame):
|
| 133 |
fig, ax = plt.subplots(figsize=(7, 4))
|
| 134 |
ax.bar(subj_avg["Subject"], subj_avg["Class_Average"])
|
|
|
|
| 138 |
ax.set_ylim(0, 100)
|
| 139 |
plt.xticks(rotation=25, ha="right")
|
| 140 |
plt.tight_layout()
|
| 141 |
+
plt.close(fig)
|
| 142 |
return fig
|
| 143 |
|
| 144 |
|
|
|
|
| 155 |
ax.set_ylabel("Number of Students")
|
| 156 |
plt.xticks(rotation=20, ha="right")
|
| 157 |
plt.tight_layout()
|
| 158 |
+
plt.close(fig)
|
| 159 |
return fig
|
| 160 |
|
| 161 |
|
| 162 |
+
# -----------------------------
|
| 163 |
+
# Gradio handlers
|
| 164 |
+
# -----------------------------
|
| 165 |
def load_csv(file_obj):
|
| 166 |
if file_obj is None:
|
| 167 |
return None, "Please upload a CSV.", None
|
|
|
|
| 201 |
if not regno_value:
|
| 202 |
return "Enter RegNo to search.", pd.DataFrame()
|
| 203 |
|
|
|
|
| 204 |
col = result_df[ID_COL_DEFAULT]
|
|
|
|
| 205 |
if pd.api.types.is_numeric_dtype(col):
|
| 206 |
try:
|
| 207 |
q = float(regno_value)
|
|
|
|
| 211 |
else:
|
| 212 |
res = result_df[col.astype(str) == str(regno_value)]
|
| 213 |
|
| 214 |
+
if res.empty:
|
| 215 |
return "No matching record found.", pd.DataFrame()
|
| 216 |
return f"Found {len(res)} record(s).", res
|
| 217 |
|
| 218 |
|
| 219 |
+
# -----------------------------
|
| 220 |
+
# Logo loader (reliable in HF)
|
| 221 |
+
# -----------------------------
|
| 222 |
+
def load_logo():
|
| 223 |
+
for fn in ["logo.jpg", "logo.png", "logo.jpeg", "Logo.jpg", "Logo.png", "Logo.jpeg"]:
|
| 224 |
+
if os.path.exists(fn):
|
| 225 |
+
try:
|
| 226 |
+
return Image.open(fn)
|
| 227 |
+
except Exception:
|
| 228 |
+
return None
|
| 229 |
+
return None
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
# -----------------------------
|
| 233 |
+
# UI
|
| 234 |
+
# -----------------------------
|
| 235 |
CSS = """
|
| 236 |
#titleblock {text-align:center; margin-top: 6px; margin-bottom: 8px;}
|
| 237 |
#t1 {font-size:30px; font-weight:800; color:#1E5AA8;}
|
| 238 |
#t2 {font-size:18px; font-weight:800; color:#1E5AA8;}
|
| 239 |
"""
|
| 240 |
|
| 241 |
+
with gr.Blocks(css=CSS, title="School Mark Analysis") as demo:
|
| 242 |
+
# Logo + title
|
| 243 |
+
logo = load_logo()
|
| 244 |
+
if logo is not None:
|
| 245 |
+
gr.Image(value=logo, show_label=False, interactive=False, height=160)
|
| 246 |
+
|
| 247 |
+
gr.HTML("""
|
| 248 |
+
<div id="titleblock" style="margin-top:-10px;">
|
|
|
|
|
|
|
|
|
|
| 249 |
<div id="t1">Amrita Manthana</div>
|
| 250 |
<div id="t2">Prof.B.Santhi,SRC,SASTRA</div>
|
| 251 |
</div>
|
| 252 |
+
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
|
| 254 |
df_state = gr.State(None)
|
| 255 |
+
result_state = gr.State(None)
|
| 256 |
|
| 257 |
with gr.Row():
|
| 258 |
with gr.Column(scale=1, min_width=340):
|
|
|
|
| 272 |
with gr.Column(scale=2):
|
| 273 |
with gr.Tabs():
|
| 274 |
with gr.Tab("Result Table"):
|
| 275 |
+
result_table = gr.Dataframe(
|
| 276 |
+
label="Result (Total, Average, Rank, Remark, Failed_Subjects)",
|
| 277 |
+
interactive=False,
|
| 278 |
+
wrap=True
|
| 279 |
+
)
|
| 280 |
with gr.Tab("Subject Averages"):
|
| 281 |
subj_avg_table = gr.Dataframe(label="Subject-wise Averages", interactive=False, wrap=True)
|
| 282 |
avg_plot = gr.Plot(label="Bar Chart: Subject-wise Average")
|
| 283 |
with gr.Tab("Fail Counts"):
|
| 284 |
+
fail_dist_table = gr.Dataframe(
|
| 285 |
+
label="Students failed in 1/2/3/4/5 subjects",
|
| 286 |
+
interactive=False,
|
| 287 |
+
wrap=True
|
| 288 |
+
)
|
| 289 |
with gr.Tab("Toppers"):
|
| 290 |
top3_overall_table = gr.Dataframe(label="Overall Top 3", interactive=False, wrap=True)
|
| 291 |
top3_each_subject_table = gr.Dataframe(label="Top 3 in each subject", interactive=False, wrap=True)
|
|
|
|
| 311 |
run_btn.click(
|
| 312 |
run_and_store,
|
| 313 |
inputs=[df_state, pass_mark],
|
| 314 |
+
outputs=[
|
| 315 |
+
summary, result_table, subj_avg_table, fail_dist_table, top3_overall_table, top3_each_subject_table,
|
| 316 |
+
avg_plot, remark_plot, download_file, result_state
|
| 317 |
+
]
|
| 318 |
)
|
| 319 |
|
| 320 |
search_btn.click(
|