PraneshJs's picture
end of support for 2022-2026 4th Years
358b920 verified
import os
import pandas as pd
import gspread
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
import json
import urllib
import gradio as gr
import time
from datetime import datetime
from pytz import timezone
import threading
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Time zone Conversion
ist = timezone("Asia/Kolkata")
# Scopes (read-only)
SCOPES = ["https://www.googleapis.com/auth/spreadsheets.readonly"]
def authorize():
creds = None
# Get JSON content from environment variables
token_json_content = os.getenv('TOKEN_JSON')
credentials_json_content = os.getenv('CREDENTIALS_JSON')
# Load token from environment variable if exists
if token_json_content:
try:
token_info = json.loads(token_json_content)
creds = Credentials.from_authorized_user_info(token_info, SCOPES)
except json.JSONDecodeError:
print("⚠️ Invalid TOKEN_JSON format in environment variable")
# If no valid credentials, start OAuth flow
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
if not credentials_json_content:
raise ValueError("CREDENTIALS_JSON environment variable is required for OAuth flow")
try:
credentials_info = json.loads(credentials_json_content)
flow = InstalledAppFlow.from_client_config(credentials_info, SCOPES)
creds = flow.run_local_server(port=0)
except json.JSONDecodeError:
raise ValueError("Invalid CREDENTIALS_JSON format in environment variable")
# Save token back to environment (for this session only)
# Note: You may want to update your .env file manually with the new token
print("🔄 New token generated. Consider updating TOKEN_JSON in your .env file with:")
print(f"TOKEN_JSON={creds.to_json()}")
return gspread.authorize(creds)
# NEW FUNCTION: Extract subjects and marks
def extract_subjects_and_marks_for_gradio(roll_no):
"""
Extract subjects with their redeemed points and marks for Gradio interface
Uses cached studentwise_data instead of making fresh API calls
"""
if not roll_no.strip():
return "❌ Please enter a roll number"
try:
# Get cached data instead of making API calls
combined_df, studentwise_data, details_info, reward_points_df = get_cached_data()
if not studentwise_data or len(studentwise_data) < 2:
return "❌ Studentwise data not available in cache"
headers = studentwise_data[0]
roll_no = roll_no.strip().upper()
# Replace indexing with loop search:
student_row = None
for row in studentwise_data[1:]: # Skip header row
# Check multiple columns for roll number
for i, cell in enumerate(row[:5]): # Check first 5 columns
if cell.strip().upper() == roll_no:
student_row = row
break
if student_row is not None:
break
if student_row is None:
return f"❌ Student with Roll No '{roll_no}' not found."
# Rest of the function remains the same...
def get_value_if_not_empty(col_name):
"""Helper function to get value only if it's not empty"""
if col_name in headers:
idx = headers.index(col_name)
value = student_row[idx] if idx < len(student_row) else ''
return value.strip() if value.strip() else None
return None
def get_numeric_value(col_name):
"""Helper function to get numeric value, return 0 if empty or invalid"""
value = get_value_if_not_empty(col_name)
try:
return float(value) if value else 0.0
except (ValueError, TypeError):
return 0.0
# Get student basic info
student_name = get_value_if_not_empty("Student Name") or "Unknown"
# Collect theory subjects (optimized loops)
theory_subjects = []
for i in range(1, 10):
subject_code = get_value_if_not_empty(f"TS{i}")
if subject_code:
theory_subjects.append({
'code': subject_code,
'ip1_points': get_numeric_value(f"IP1TS{i}R"),
'ip2_points': get_numeric_value(f"IP2TS{i}R"),
'ip1_marks': get_numeric_value(f"IP1TS{i}M"),
'ip2_marks': get_numeric_value(f"IP2TS{i}M")
})
# Collect lab subjects (optimized loops)
lab_subjects = []
for i in range(1, 3):
subject_code = get_value_if_not_empty(f"LS{i}")
if subject_code:
lab_subjects.append({
'code': subject_code,
'ip1_points': get_numeric_value(f"IP1LS{i}R"),
'ip2_points': get_numeric_value(f"IP2LS{i}R"),
'ip1_marks': get_numeric_value(f"IP1LS{i}M"),
'ip2_marks': get_numeric_value(f"IP2LS{i}M")
})
# Calculate totals using list comprehensions (faster)
for subject in theory_subjects + lab_subjects:
subject['total_points'] = subject['ip1_points'] + subject['ip2_points']
subject['total_marks'] = subject['ip1_marks'] + subject['ip2_marks']
# Calculate grand totals
total_theory_ip1_points = sum(s['ip1_points'] for s in theory_subjects)
total_theory_ip2_points = sum(s['ip2_points'] for s in theory_subjects)
total_lab_ip1_points = sum(s['ip1_points'] for s in lab_subjects)
total_lab_ip2_points = sum(s['ip2_points'] for s in lab_subjects)
total_theory_ip1_marks = sum(s['ip1_marks'] for s in theory_subjects)
total_theory_ip2_marks = sum(s['ip2_marks'] for s in theory_subjects)
total_lab_ip1_marks = sum(s['ip1_marks'] for s in lab_subjects)
total_lab_ip2_marks = sum(s['ip2_marks'] for s in lab_subjects)
# Build output with clean card-style formatting
output = []
output.append("")
output.append("🏆 INNOVATIVE PRACTICE (IP) SUMMARY")
output.append("=" * 80)
# Theory subjects section
if theory_subjects:
output.append(f"\n📚 THEORY SUBJECTS ({len(theory_subjects)} subjects)")
output.append("-" * 50)
for subject in theory_subjects:
output.append(f"\n🔹 {subject['code']}")
# Points section
if subject['ip1_points'] > 0 or subject['ip2_points'] > 0:
points_line = " Reward Points: "
if subject['ip1_points'] > 0:
points_line += f"IP-1: {subject['ip1_points']:.2f}"
if subject['ip2_points'] > 0:
if subject['ip1_points'] > 0:
points_line += f" | IP-2: {subject['ip2_points']:.2f}"
else:
points_line += f"IP-2: {subject['ip2_points']:.2f}"
points_line += f" | Total: {subject['total_points']:.2f}"
output.append(points_line)
# Marks section
if subject['ip1_marks'] > 0 or subject['ip2_marks'] > 0:
marks_line = " Internal Marks: "
if subject['ip1_marks'] > 0:
marks_line += f"IP-1: {subject['ip1_marks']:.2f}"
if subject['ip2_marks'] > 0:
if subject['ip1_marks'] > 0:
marks_line += f" | IP-2: {subject['ip2_marks']:.2f}"
else:
marks_line += f"IP-2: {subject['ip2_marks']:.2f}"
marks_line += f" | Total: {subject['total_marks']:.2f}"
output.append(marks_line)
# Lab subjects section
if lab_subjects:
output.append(f"\n🧪 LAB SUBJECTS ({len(lab_subjects)} subjects)")
output.append("-" * 50)
for subject in lab_subjects:
output.append(f"\n🔹 {subject['code']}")
# Points section
if subject['ip1_points'] > 0 or subject['ip2_points'] > 0:
points_line = " Reward Points: "
if subject['ip1_points'] > 0:
points_line += f"IP-1: {subject['ip1_points']:.2f}"
if subject['ip2_points'] > 0:
if subject['ip1_points'] > 0:
points_line += f" | IP-2: {subject['ip2_points']:.2f}"
else:
points_line += f"IP-2: {subject['ip2_points']:.2f}"
points_line += f" | Total: {subject['total_points']:.2f}"
output.append(points_line)
# Marks section
if subject['ip1_marks'] > 0 or subject['ip2_marks'] > 0:
marks_line = " Internal Marks: "
if subject['ip1_marks'] > 0:
marks_line += f"IP-1: {subject['ip1_marks']:.2f}"
if subject['ip2_marks'] > 0:
if subject['ip1_marks'] > 0:
marks_line += f" | IP-2: {subject['ip2_marks']:.2f}"
else:
marks_line += f"IP-2: {subject['ip2_marks']:.2f}"
marks_line += f" | Total: {subject['total_marks']:.2f}"
output.append(marks_line)
# Summary section
output.append("\n" + "=" * 80)
output.append("📊 OVERALL SUMMARY")
output.append("=" * 80)
# Reward Points Summary
output.append("\n🏅 REWARD POINTS BREAKDOWN:")
if theory_subjects:
theory_total = total_theory_ip1_points + total_theory_ip2_points
output.append(f" Theory Subjects: {theory_total:.2f} points")
if total_theory_ip1_points > 0:
output.append(f" ➤ IP-1: {total_theory_ip1_points:.2f}")
if total_theory_ip2_points > 0:
output.append(f" ➤ IP-2: {total_theory_ip2_points:.2f}")
if lab_subjects:
lab_total = total_lab_ip1_points + total_lab_ip2_points
output.append(f" Lab Subjects: {lab_total:.2f} points")
if total_lab_ip1_points > 0:
output.append(f" ➤ IP-1: {total_lab_ip1_points:.2f}")
if total_lab_ip2_points > 0:
output.append(f" ➤ IP-2: {total_lab_ip2_points:.2f}")
grand_total_points = (total_theory_ip1_points + total_theory_ip2_points +
total_lab_ip1_points + total_lab_ip2_points)
output.append(f"\n🎯 TOTAL REWARD POINTS: {grand_total_points:.2f}")
# Internal Marks Summary
output.append("\n📝 INTERNAL MARKS BREAKDOWN:")
if theory_subjects:
theory_marks_total = total_theory_ip1_marks + total_theory_ip2_marks
output.append(f" Theory Subjects: {theory_marks_total:.2f} marks")
if total_theory_ip1_marks > 0:
output.append(f" ➤ IP-1: {total_theory_ip1_marks:.2f}")
if total_theory_ip2_marks > 0:
output.append(f" ➤ IP-2: {total_theory_ip2_marks:.2f}")
if lab_subjects:
lab_marks_total = total_lab_ip1_marks + total_lab_ip2_marks
output.append(f" Lab Subjects: {lab_marks_total:.2f} marks")
if total_lab_ip1_marks > 0:
output.append(f" ➤ IP-1: {total_lab_ip1_marks:.2f}")
if total_lab_ip2_marks > 0:
output.append(f" ➤ IP-2: {total_lab_ip2_marks:.2f}")
grand_total_marks = (total_theory_ip1_marks + total_theory_ip2_marks +
total_lab_ip1_marks + total_lab_ip2_marks)
output.append(f"\n📊 TOTAL INTERNAL MARKS: {grand_total_marks:.2f}")
total_subjects = len(theory_subjects) + len(lab_subjects)
output.append(f"\n📚 TOTAL SUBJECTS: {total_subjects}")
output.append("\n" + "=" * 80)
# Log the search
now_ist = datetime.now(ist).strftime("%Y-%m-%d %H:%M:%S")
print(f"IP Details Searched - Roll No: {roll_no} | Student: {student_name} | Time (IST): {now_ist}")
return "\n".join(output)
except Exception as e:
error_msg = f"❌ Error extracting subject details: {str(e)}"
print(error_msg)
return error_msg
# Function to get data from a specific sheet
def get_sheet_data(spreadsheet, gid, sheet_name):
try:
sheet = spreadsheet.get_worksheet_by_id(gid)
all_values = sheet.get_all_values()
if not all_values or len(all_values) <= 4:
print(f"⚠️ {sheet_name} sheet doesn't have enough data")
return pd.DataFrame()
# Use row 4 as headers (the actual column names)
headers = all_values[4]
clean_headers = []
seen_headers = {}
for i, header in enumerate(headers):
if header.strip(): # Non-empty header
base_header = header.strip()
# Handle duplicate headers by adding a counter
if base_header in seen_headers:
seen_headers[base_header] += 1
clean_header = f"{base_header}_{seen_headers[base_header]}"
else:
seen_headers[base_header] = 0
clean_header = base_header
clean_headers.append(clean_header)
else: # Empty header
clean_headers.append(f"Empty_Col_{i}")
# Create DataFrame starting from row 5 (after headers)
data_rows = all_values[5:]
if data_rows:
df = pd.DataFrame(data_rows, columns=clean_headers)
# Remove completely empty columns
df = df.loc[:, (df != '').any(axis=0)]
return df
else:
print(f"⚠️ No data rows found in {sheet_name}")
return pd.DataFrame()
except Exception as e:
print(f"❌ Error loading {sheet_name}: {str(e)}")
return pd.DataFrame()
# Function to get studentwise reward points data
def get_studentwise_data(spreadsheet):
try:
worksheet = spreadsheet.worksheet("Studentwise Reward Points")
all_values = worksheet.get_all_values()
if len(all_values) < 3:
print("⚠️ Studentwise Reward Points sheet doesn't have enough data")
return None
print(f"✅ Loaded {len(all_values)} rows from Studentwise Reward Points")
return all_values
except Exception as e:
print(f"❌ Error loading Studentwise Reward Points: {str(e)}")
return None
# Function to load and cache reward points activity data
def load_reward_points_data():
"""Load and cache reward points activity data"""
try:
# Get the reward points sheet ID from environment
REWARD_POINTS_SHEET_ID = os.getenv('REWARD_POINTS_SHEET_ID')
if not REWARD_POINTS_SHEET_ID:
print("⚠️ REWARD_POINTS_SHEET_ID not found in environment variables")
return None
client = authorize()
spreadsheet = client.open_by_key(REWARD_POINTS_SHEET_ID)
worksheet = spreadsheet.get_worksheet_by_id(1113414351) # Activity Sheet GID
all_values = worksheet.get_all_values()
if not all_values or len(all_values) < 2:
print("⚠️ Reward Points sheet doesn't have enough data")
return None
# First row is header
headers = all_values[0]
df = pd.DataFrame(all_values[1:], columns=headers)
if df.empty:
print("⚠️ Reward Points sheet is empty")
return None
print(f"✅ Loaded {len(df)} rows from Reward Points Entry sheet")
return df
except Exception as e:
print(f"❌ Error loading Reward Points data: {str(e)}")
return None
# Function to get activity details from cached data in breakdown format
# Function to get activity details from cached data in breakdown format
def get_activity_details(roll_no, reward_points_df):
"""Get activity details for a specific roll number from cached reward points data."""
try:
if reward_points_df is None or reward_points_df.empty:
return ""
roll_no_search = roll_no.strip().upper()
# -----------------------------
# FIXED ROLL NUMBER DETECTION
# -----------------------------
roll_col = None
# 1️⃣ Try header match
for col in reward_points_df.columns:
cl = col.lower().replace(" ", "")
if "roll" in cl or "reg" in cl:
roll_col = col
break
# 2️⃣ Pattern match (fallback)
if roll_col is None:
for col in reward_points_df.columns:
sample = (
reward_points_df[col]
.astype(str)
.str.upper()
.head(40)
)
if sample.str.match(r"(7376|2025)[A-Z0-9]+").sum() >= 2:
roll_col = col
break
# 3️⃣ Ultimate fallback
if roll_col is None:
roll_col = reward_points_df.columns[len(reward_points_df.columns)//2]
# Normalize roll number column
df = reward_points_df.copy()
df[roll_col] = df[roll_col].astype(str).str.strip().str.upper()
student_rows = df[df[roll_col] == roll_no_search]
# Try partial match
if student_rows.empty:
partial_matches = df[df[roll_col].str.contains(roll_no_search, na=False)]
if not partial_matches.empty:
student_rows = partial_matches
if student_rows.empty:
return "" # No activity rows
# Build output
output = []
activity_summary = {}
activity_count = {}
total_points = 0
for _, row in student_rows.iterrows():
activity_type = str(row.get("Activity Type", "")).strip()
reward_points = str(row.get("Reward Points", "0")).replace(",", "")
# Convert points
try:
points_val = float(reward_points)
except:
points_val = 0
total_points += points_val
# Update summary
activity_summary[activity_type] = activity_summary.get(activity_type, 0) + points_val
activity_count[activity_type] = activity_count.get(activity_type, 0) + 1
output.append("📋 DETAILED ACTIVITY LIST")
output.append("=" * 80)
for idx, (_, row) in enumerate(student_rows.iterrows(), 1):
name = str(row.get("Activity Name", "")).strip()
points = str(row.get("Reward Points", "0"))
activity_type = str(row.get("Activity Type", "")).strip()
display = name[:85] + "..." if len(name) > 85 else name
output.append(f"{idx:2d}. {activity_type}: {display} - {points} pts")
output.append("=" * 80)
output.append(f"🎯 TOTAL REWARD POINTS FROM ACTIVITIES: {total_points:.2f}")
output.append("=" * 80)
return "\n".join(output)
except Exception as e:
print(f"❌ Error fetching activity details: {str(e)}")
return ""
# Function to get details sheet information
def get_details_info(spreadsheet):
try:
details_sheet = spreadsheet.get_worksheet_by_id(847680829)
all_values = details_sheet.get_all_values()
if not all_values:
return None
# Use row 4 as headers
headers = all_values[4]
clean_headers = []
for i, header in enumerate(headers):
if header.strip():
clean_headers.append(header.strip())
else:
clean_headers.append(f"Empty_Col_{i}")
# Get data rows after header
data_rows = all_values[5:]
if data_rows:
df = pd.DataFrame(data_rows, columns=clean_headers)
df = df.loc[:, (df != '').any(axis=0)]
details_info = {}
# Extract specific information
for idx in range(len(df)):
student_data = df.iloc[idx]
year_value = str(student_data.get('YEAR', '')).strip()
# Get Average Reward Points
if 'AVERAGE REWARD POINT' in year_value:
details_info['average_points'] = {
'I': student_data.get('I', ''),
'II': student_data.get('II', ''),
'II L': student_data.get('II L', ''),
'III': student_data.get('III', ''),
'IV': student_data.get('IV', '')
}
# Get IP 2 Redemption Dates
elif 'Last Day for IP 2 Redemption Duration' in str(student_data.get('Redemption Dates', '')):
details_info['ip2_redemption'] = {
'S1': student_data.get('S1', ''),
'S2': student_data.get('S2', ''),
'S3': student_data.get('S3', ''),
'S4': student_data.get('S4', ''),
'S5': student_data.get('S5', ''),
'S6': student_data.get('S6', ''),
'S7': student_data.get('S7', ''),
'S8': student_data.get('S8', '')
}
# Get IP 1 Redemption Dates
elif 'Last Day for IP 1 Redemption Duration' in str(student_data.get('Redemption Dates', '')):
details_info['ip1_redemption'] = {
'S1': student_data.get('S1', ''),
'S2': student_data.get('S2', ''),
'S3': student_data.get('S3', ''),
'S4': student_data.get('S4', ''),
'S5': student_data.get('S5', ''),
'S6': student_data.get('S6', ''),
'S7': student_data.get('S7', ''),
'S8': student_data.get('S8', '')
}
# Get Last Updated Information
elif 'POINTS LAST UPDATED' in year_value:
details_info['last_updated'] = year_value
return details_info
except Exception as e:
print(f"❌ Error loading Details Sheet: {str(e)}")
return None
# Initialize global variables
print("🚀 Initializing application...")
client = authorize()
# Get spreadsheet IDs from environment variables
MAIN_SHEET_ID = os.getenv('GOOGLE_SHEET_ID') # Your main sheets (20 sheets)
STUDENTWISE_SHEET_ID = os.getenv('STUDENTWISE_SHEET_ID') # Studentwise Reward Points sheet
if not MAIN_SHEET_ID:
raise ValueError("GOOGLE_SHEET_ID environment variable is required")
# Open both spreadsheets
main_spreadsheet = client.open_by_key(MAIN_SHEET_ID)
studentwise_spreadsheet = client.open_by_key(STUDENTWISE_SHEET_ID)
# Load data from all sheets (Original 3 + New 17 = 20 sheets total)
sheet_configs = [
# Original sheets
{"gid": 688907204, "name": "AIML"},
{"gid": 451167295, "name": "AIDS"},
{"gid": 1955995189, "name": "Sheet_3"},
{"gid": 821473193, "name": "Sheet_4"},
{"gid": 1798819643, "name": "Sheet_5"},
{"gid": 1057532042, "name": "Sheet_6"},
{"gid": 1848020834, "name": "Sheet_7"},
{"gid": 48570283, "name": "Sheet_8"},
{"gid": 559332743, "name": "Sheet_9"},
{"gid": 1481375682, "name": "Sheet_10"},
{"gid": 1136877763, "name": "Sheet_11"},
{"gid": 510521423, "name": "Sheet_12"},
{"gid": 1936618, "name": "Sheet_13"},
{"gid": 91989289, "name": "Sheet_14"},
{"gid": 30073516, "name": "Sheet_15"},
{"gid": 857542309, "name": "Sheet_16"},
{"gid": 790318539, "name": "Sheet_17"},
{"gid": 587090068, "name": "Sheet_18"},
{"gid": 260192612, "name": "Sheet_19"},
{"gid": 400900059, "name": "Sheet_20"}
]
# GLOBAL DATA CACHE WITH 12-HOUR AUTO-REFRESH
data_cache = {
"combined_df": None,
"studentwise_data": None,
"details_info": None,
"reward_points_df": None,
"last_update": None,
"cache_duration_hours": 12, # 12 hours cache
"is_loading": False
}
def load_all_data():
"""Load and cache all data from Google Sheets (auto-retries failed sheets with backoff)"""
global data_cache
if data_cache["is_loading"]:
print("⏳ Data loading already in progress...")
return (data_cache["combined_df"], data_cache["studentwise_data"],
data_cache["details_info"], data_cache["reward_points_df"])
data_cache["is_loading"] = True
print(f"🔄 Loading data from {len(sheet_configs)} Google Sheets + Reward Points sheet...")
start_time = time.time()
try:
all_dataframes = []
failed_sheets = []
max_retries = 5 # Prevent infinite recursion
retry_delay = 2 # Start with 2 seconds
# 1️⃣ First attempt to load each sheet
print("📋 Initial loading attempt...")
for config in sheet_configs:
df = get_sheet_data(main_spreadsheet, config["gid"], config["name"])
if df.empty:
print(f"⚠️ {config['name']} failed to load, will retry...")
failed_sheets.append(config)
else:
df['Source_Sheet'] = config["name"]
all_dataframes.append(df)
print(f"✅ Loaded {len(df)} rows from {config['name']}")
# 2️⃣ Retry failed sheets with exponential backoff
for attempt in range(1, max_retries + 1):
if not failed_sheets:
break
print(f"🔄 Retry attempt {attempt}/{max_retries} for {len(failed_sheets)} failed sheets...")
time.sleep(retry_delay)
retry_failed = []
for config in failed_sheets:
print(f" 🔄 Retrying {config['name']}...")
df = get_sheet_data(main_spreadsheet, config["gid"], config["name"])
if df.empty:
print(f" ❌ Still failed: {config['name']}")
retry_failed.append(config)
else:
df['Source_Sheet'] = config["name"]
all_dataframes.append(df)
print(f" ✅ Retry success: {config['name']} ({len(df)} rows)")
failed_sheets = retry_failed
retry_delay *= 2 # Exponential backoff: 2s, 4s, 8s
# 3️⃣ Handle permanently failed sheets
if failed_sheets:
failed_names = [config['name'] for config in failed_sheets]
print(f"⚠️ Warning: {len(failed_sheets)} sheets failed after {max_retries} retries: {', '.join(failed_names)}")
# Continue with available data instead of aborting
if all_dataframes:
print(f"✅ Continuing with {len(all_dataframes)} successfully loaded sheets")
else:
print("❌ Critical: No sheets loaded successfully!")
# Return empty data but don't crash
return pd.DataFrame(), None, None, None
# 4️⃣ Combine successfully loaded sheets
if all_dataframes:
try:
combined_df = pd.concat(all_dataframes, ignore_index=True, sort=False)
print(f"✅ Successfully combined {len(combined_df)} records from {len(all_dataframes)} sheets")
except Exception as e:
print(f"❌ Error combining dataframes: {str(e)}")
print("🔄 Trying alternative approach...")
# Alternative approach: standardize columns first
standard_columns = ['SL. NO.', 'YEAR', 'ROLL NO.', 'STUDENT NAME', 'COURSE CODE',
'DEPARTMENT', 'MENTOR NAME', 'CUMULATIVE REWARD POINTS',
'REEDEMED POINTS', 'BALANCE POINTS', 'Source_Sheet']
standardized_dfs = []
for df in all_dataframes:
new_df = pd.DataFrame()
for col in standard_columns:
if col == 'Source_Sheet':
new_df[col] = df.get('Source_Sheet', '')
else:
# Try to find matching column
found_col = None
for df_col in df.columns:
if col.upper() in df_col.upper() or df_col.upper() in col.upper():
found_col = df_col
break
if found_col:
new_df[col] = df[found_col]
else:
new_df[col] = ''
standardized_dfs.append(new_df)
combined_df = pd.concat(standardized_dfs, ignore_index=True, sort=False)
print(f"✅ Alternative approach successful: {len(combined_df)} records combined")
else:
combined_df = pd.DataFrame()
print("⚠️ No sheets loaded successfully")
# 5️⃣ Load supporting data (with error handling)
print("📊 Loading supporting data...")
studentwise_data = get_studentwise_data(studentwise_spreadsheet)
details_info = get_details_info(main_spreadsheet)
reward_points_df = load_reward_points_data()
# 6️⃣ Update cache
data_cache.update({
"combined_df": combined_df,
"studentwise_data": studentwise_data,
"details_info": details_info,
"reward_points_df": reward_points_df,
"last_update": datetime.now(),
})
load_time = time.time() - start_time
if failed_sheets:
print(f"⚠️ Partial load completed in {load_time:.2f} seconds")
print(f"✅ {len(all_dataframes)}/{len(sheet_configs)} sheets loaded successfully")
else:
print(f"⏱️ All data successfully loaded in {load_time:.2f} seconds")
print("✅ Application ready — all sheets verified")
return combined_df, studentwise_data, details_info, reward_points_df
except Exception as e:
print(f"❌ Critical error in load_all_data: {str(e)}")
return (
data_cache.get("combined_df", pd.DataFrame()),
data_cache.get("studentwise_data", None),
data_cache.get("details_info", None),
data_cache.get("reward_points_df", None),
)
finally:
data_cache["is_loading"] = False
def get_cached_data():
"""Get data from cache or refresh if 12 hours have passed"""
now = datetime.now()
# Check if cache is empty or expired (12 hours)
if (data_cache["last_update"] is None or
data_cache["combined_df"] is None or
(now - data_cache["last_update"]).total_seconds() > (data_cache["cache_duration_hours"] * 3600)):
print("🔄 Cache expired or empty, loading fresh data...")
return load_all_data()
else:
cache_age_hours = (now - data_cache["last_update"]).total_seconds() / 3600
print(f"🚀 Using cached data (age: {cache_age_hours:.1f} hours)")
return (data_cache["combined_df"], data_cache["studentwise_data"],
data_cache["details_info"], data_cache["reward_points_df"])
def auto_refresh_worker():
"""Background worker to auto-refresh data every 12 hours"""
while True:
try:
# Sleep for 12 hours (43200 seconds)
time.sleep(43200)
print("⏰ 12-hour auto-refresh triggered...")
load_all_data()
except Exception as e:
print(f"❌ Auto-refresh error: {str(e)}")
# If error, wait 1 hour before trying again
time.sleep(3600)
def details_sheet_watcher():
"""Background watcher: checks every 30 seconds (drift-free, IST logs) if 'POINTS LAST UPDATED' changed safely"""
import time
from datetime import datetime
last_seen_update = None
consecutive_errors = 0
max_errors = 10
watcher_client = None
watcher_spreadsheet = None
watcher_sheet = None
last_connection_time = None
connection_duration = 3600 # 1 hour
check_interval = 30 # seconds
next_check = time.time()
TARGET_ROW, TARGET_COL = 16, 2
print(f"👀 Starting optimized details sheet watcher (R{TARGET_ROW}C{TARGET_COL}, every {check_interval}s)...")
global last_auth_refresh
last_auth_refresh = 0
while True:
try:
if data_cache["is_loading"]:
print("⏳ Watcher: Skipping check – data loading in progress")
else:
current_time = datetime.now()
# Refresh only if 24 hours passed or connection lost
should_refresh = (
watcher_client is None
or watcher_sheet is None
or last_connection_time is None
or (current_time - last_connection_time).total_seconds() > connection_duration
)
if should_refresh:
# Enforce 5-min cooldown between authorizations
if time.time() - last_auth_refresh < 300:
print("⏳ Skipping token refresh — recently done.")
else:
print("🔄 Watcher: Refreshing connection...")
try:
watcher_client = authorize()
watcher_spreadsheet = watcher_client.open_by_key(os.getenv("GOOGLE_SHEET_ID"))
watcher_sheet = watcher_spreadsheet.get_worksheet_by_id(847680829)
last_connection_time = current_time
last_auth_refresh = time.time()
print(f"✅ Watcher: Connection refreshed (monitoring cell R{TARGET_ROW}C{TARGET_COL})")
except Exception as e:
print(f"⚠️ Watcher: Failed to refresh connection — {str(e)[:100]}")
time.sleep(60)
continue
# Skip check if no valid sheet
if not watcher_sheet:
time.sleep(check_interval)
continue
try:
# Fetch only specific cell value
cell_value = watcher_sheet.cell(TARGET_ROW, TARGET_COL).value
current_update = str(cell_value).strip() if cell_value else ""
now_ist = datetime.now(ist).strftime("%Y-%m-%d %H:%M:%S")
if last_seen_update is None:
last_seen_update = current_update
print(f"🕒 Watcher started at {now_ist}")
print(f" Target R{TARGET_ROW}C{TARGET_COL}: {current_update[:80]}...")
elif current_update != last_seen_update:
print(f"🔄 CHANGE DETECTED! ({now_ist})")
print(f" Old: {last_seen_update[:60]}...")
print(f" New: {current_update[:60]}...")
last_seen_update = current_update
if not data_cache["is_loading"]:
print("🔄 Reloading data directly...")
load_all_data()
print(f"✅ Data reload completed successfully ({now_ist})")
else:
print(f"⏳ Data already loading, skipping reload ({now_ist})")
else:
current_ist = datetime.now(ist)
if current_ist.minute % 5 == 0 and current_ist.second < check_interval:
print(f"✅ Watcher: No changes detected in R{TARGET_ROW}C{TARGET_COL} ({current_ist.strftime('%H:%M:%S')})")
except Exception as cell_error:
print(f"⚠️ Error reading cell R{TARGET_ROW}C{TARGET_COL}: {str(cell_error)[:200]}")
consecutive_errors += 1
# Reset error counter if stable
if consecutive_errors > 0:
print(f"✅ Watcher: Connection restored (cleared {consecutive_errors} errors)")
consecutive_errors = 0
except Exception as e:
consecutive_errors += 1
print(f"⚠️ Watcher error #{consecutive_errors}: {str(e)[:200]}")
watcher_client = None
watcher_spreadsheet = None
watcher_sheet = None
if consecutive_errors >= max_errors:
print("❌ Too many watcher errors, waiting 5 minutes before retry...")
time.sleep(300)
consecutive_errors = 0
# Drift-free sleep to maintain 30s sync
next_check += check_interval
time.sleep(max(0, next_check - time.time()))
def get_detailed_student_points(roll_no, studentwise_data):
"""Get detailed points breakdown from studentwise data"""
if not studentwise_data or len(studentwise_data) < 3:
return ""
headers = studentwise_data[0]
student_found = None
for row in studentwise_data[2:]:
if len(row) > 1 and row[1].strip().upper() == roll_no.strip().upper():
student_found = row
break
if not student_found:
return ""
student_data = {}
for i, header in enumerate(headers):
student_data[header] = student_found[i] if i < len(student_found) else ""
output = []
output.append("")
output.append("🏆 REWARD POINTS BREAKDOWN")
output.append("=" * 80)
# Using a different approach - no column headers, just data with clear labels
categories = [
("INITIAL POINTS / CARRY-OVER", "-", "Initial Points"),
("TECHNICAL EVENTS", "Technical Events Count", "Technical Events Points"),
("SKILLS", "Skill Count", "Skill Points"),
("ASSIGNMENTS", "Assignement Count", "Assignment Points"),
("INTERVIEW", "Interview Count", "Interview Points"),
("TECHNICAL SOCIETY ACTIVITIES", "TECHNICAL SOCIETY ACTIVITIES Count", "TECHNICAL SOCIETY ACTIVITIES Points"),
("P SKILL", "P Skill Count", "P Skill Points"),
("TAC", "TAC Count", "TAC Points"),
("SPECIAL LAB INITIATIVES", "Special Lab Initiatives Count", "Special Lab Initiatives Points"),
("EXTRA-CURRICULAR ACTIVITIES", "EXTRA-CURRICULAR ACTIVITIES COUNT", "EXTRA-CURRICULAR ACTIVITIES POINTS"),
("STUDENT INITIATIVES", "STUDENT INITIATIVES COUNT", "STUDENT INITIATIVES POINTS"),
("EXTERNAL EVENTS", "EXTERNAL EVENTS COUNT", "EXTERNAL EVENTS POINTS"),
("TOTAL (2023-2024 EVEN)", "Total Count", "Total Points"),
("PENALTIES", "Negative Count", "Negative Points"),
("CUMULATIVE POINTS", "-", "Cumulative Points"),
("INNOVATIVE PRACTICE - 1 (IP-1)", "-", "IP 1 R"),
("INNOVATIVE PRACTICE - 2 (IP-2)", "-", "IP 2 R"),
("REDEEMED POINTS", "-", "Redeemed Points"),
("BALANCE POINTS", "-", "Balance Points"),
("CARRY FORWARD TO NEXT SEMESTER", "-", "EL. CA. FR. POINTS")
]
total_earned = 0
total_redeemed = 0
for idx, (category_name, count_key, points_key) in enumerate(categories):
count_val = "-" if count_key == "-" else student_data.get(count_key, "0")
points_val = student_data.get(points_key, "0.00")
try:
if points_val and points_val != "-":
points_float = float(str(points_val).replace(',', ''))
points_val = f"{points_float:.2f}"
if points_key == "Cumulative Points":
total_earned = points_float
elif points_key == "Redeemed Points":
total_redeemed = points_float
except:
pass
# Alternative format - more readable
output.append(f"📋 **{category_name}**")
output.append(f" Count: {count_val} | Points: {points_val}")
output.append("=" * 80)
return "\n".join(output)
def calculate_yearwise_average_points():
"""Calculate year-wise average points from the combined data"""
combined_df, _, _, _ = get_cached_data()
if combined_df.empty:
return "❌ No data available to calculate averages"
# Try to find columns automatically
year_col, points_col = None, None
for col in combined_df.columns:
if 'year' in col.lower():
year_col = col
if 'balance' in col.lower() and 'points' in col.lower():
points_col = col
if not year_col or not points_col:
return "⚠️ Required columns not found in the data"
# Create a copy for processing
df = combined_df[[year_col, points_col]].copy()
# Clean and convert points data
df[points_col] = pd.to_numeric(
df[points_col].astype(str).str.replace(',', '').str.strip(),
errors='coerce'
)
df.dropna(subset=[points_col], inplace=True)
# Remove rows with zero or negative points for more accurate averages
df = df[df[points_col] > 0]
if df.empty:
return "⚠️ No valid points data found for calculation"
# Group by year
yearwise = df.groupby(year_col)[points_col].agg(['sum', 'count', 'mean', 'min', 'max']).reset_index()
yearwise['average'] = yearwise['mean'] # Use pandas mean for consistency
# Format the output neatly
output = []
output.append("=" * 90)
output.append(" ")
output.append("📊 YEAR-WISE AVERAGE REWARD POINTS (CALCULATED)")
output.append("-" * 90)
for _, row in yearwise.iterrows():
year = str(row[year_col]).strip()
total_points = f"{row['sum']:.0f}"
count = int(row['count'])
avg = f"{row['average']:.2f}"
min_pts = f"{row['min']:.0f}"
max_pts = f"{row['max']:.0f}"
output.append(f"Year {year:<10} {avg}")
output.append("=" * 90)
return "\n".join(output)
# Load initial data
print("📊 Loading initial data...")
load_all_data()
# Start background auto-refresh thread
refresh_thread = threading.Thread(target=auto_refresh_worker, daemon=True)
refresh_thread.start()
print("🕒 Auto-refresh thread started (updates every 12 hours)")
# Start details sheet watcher thread
watcher_thread = threading.Thread(target=details_sheet_watcher, daemon=True)
watcher_thread.start()
print("👀 Details sheet watcher started (checks every 1 minute)")
# Function to search student with cached data
def search_student(roll_no):
if not roll_no.strip():
return "❌ Please enter a roll number"
# Convert roll number to uppercase for consistent searching
roll_no = roll_no.strip().upper()
# Get cached data (fast response, auto-refreshes every 12 hours)
combined_df, studentwise_data, details_info, reward_points_df = get_cached_data()
if combined_df.empty:
return "❌ No data available from Google Sheets"
# Look for 'ROLL NO.' column
roll_column = None
for col in combined_df.columns:
if 'roll' in col.lower() and 'no' in col.lower():
roll_column = col
break
if roll_column is None:
return f"❌ Roll number column not found. Available columns: {list(combined_df.columns)}"
# Convert the roll numbers in DataFrame to uppercase for comparison
student = combined_df[combined_df[roll_column].astype(str).str.strip().str.upper() == roll_no]
if student.empty:
return f"❌ Roll No '{roll_no}' not found in any sheet"
record = student.iloc[0].to_dict()
student_name = str(record.get('STUDENT NAME', 'Unknown')).strip()
student_year = str(record.get('YEAR', '')).strip()
now_ist = datetime.now(ist).strftime("%Y-%m-%d %H:%M:%S")
# Log to see which roll number and student name is searched by user
print(f"Roll No Searched: {roll_no} | Student Name: {student_name} | Time (IST): {now_ist}")
# Format output - Simplified version
output = []
output.append(f"Hello {student_name} 👋")
output.append("=" * 80)
output.append("YOUR DETAILS")
output.append("=" * 80)
# Main student details
main_fields = ['ROLL NO.', 'STUDENT NAME', 'YEAR', 'DEPARTMENT', 'MENTOR NAME',
'CUMULATIVE REWARD POINTS', 'REEDEMED POINTS', 'BALANCE POINTS']
for field in main_fields:
value = record.get(field, '')
if str(value).strip():
output.append(f"{field:<25}: {value}")
# Get student's current points (clean numeric value)
try:
student_points_str = str(record.get('BALANCE POINTS', '')).replace(',', '').strip()
student_points = float(student_points_str) if student_points_str else 0
except:
student_points = 0
# Add year-specific average points and analysis
if details_info and 'average_points' in details_info:
output.append("\n" + "=" * 80)
output.append(f"AVERAGE REWARD POINTS FOR YEAR {student_year}")
output.append("=" * 80)
if student_year in details_info['average_points']:
avg_points_str = details_info['average_points'][student_year]
try:
avg_points = float(avg_points_str) if avg_points_str else 0
except:
avg_points = 0
if avg_points > 0:
output.append(f"Average Points for Year {student_year:<8}: {avg_points_str}")
# Calculate difference and provide guidance
points_difference = avg_points - student_points
if points_difference > 0:
# Student is below average
output.append(f"\n🎯 POINTS NEEDED TO REACH AVERAGE: {points_difference:.0f} points")
output.append("\n💡 WAYS TO EARN POINTS:")
output.append(" • PS Activities")
output.append(" • TAC")
output.append(" • Hackathons / Technical Events")
output.append(" • Project Competitions")
output.append(" • Refer Reward points Breakdown for more details")
else:
# Student is at or above average
output.append(f"\n🎉 EXCELLENT! You are {abs(points_difference):.0f} points ABOVE the average!")
output.append(" Keep up the great work! 🌟")
output.append(" Refer Reward points Breakdown for more details")
# Add individual activity details from cached reward points data
activity_details = get_activity_details(roll_no, reward_points_df)
if activity_details:
output.append(activity_details)
# Add detailed points breakdown from studentwise data
detailed_points = get_detailed_student_points(roll_no, studentwise_data)
if detailed_points:
output.append(detailed_points)
# Add last updated info
if details_info and 'last_updated' in details_info:
output.append("\n" + "-" * 60)
output.append("LAST UPDATE INFO")
output.append("-" * 60)
output.append(details_info['last_updated'])
# Show cache info
if data_cache["last_update"]:
cache_age = datetime.now() - data_cache["last_update"]
hours = cache_age.total_seconds() / 3600
next_refresh_hours = 12 - hours
output.append(f"\n📊 Data age: {hours:.1f} hours")
if next_refresh_hours > 0:
output.append(f"⏰ Next auto-refresh in: {next_refresh_hours:.1f} hours")
else:
output.append("⏰ Auto-refresh due now")
output.append("\n" + "=" * 80)
return "\n".join(output)
# Function to get system information
def get_system_info():
combined_df, studentwise_data, details_info, reward_points_df = get_cached_data()
if not details_info:
return "❌ No system information available"
output = []
output.append("=" * 80)
output.append("SYSTEM INFORMATION")
output.append("=" * 80)
# Average Points
if 'average_points' in details_info:
output.append("\nAVERAGE REWARD POINTS BY YEAR:")
output.append("-" * 40)
for year, points in details_info['average_points'].items():
if points:
output.append(f"Year {year:<10}: {points}")
# Calculated Year-wise Average Points
calculated_averages = calculate_yearwise_average_points()
if calculated_averages and not calculated_averages.startswith("❌") and not calculated_averages.startswith("⚠️"):
output.append(calculated_averages)
# Redemption Dates
if 'ip1_redemption' in details_info:
output.append("\nIP 1 REDEMPTION DATES:")
output.append("-" * 40)
for semester, date in details_info['ip1_redemption'].items():
if date and date != '-':
output.append(f"{semester:<10}: {date}")
if 'ip2_redemption' in details_info:
output.append("\nIP 2 REDEMPTION DATES:")
output.append("-" * 40)
for semester, date in details_info['ip2_redemption'].items():
if date and date != '-':
output.append(f"{semester:<10}: {date}")
if 'last_updated' in details_info:
output.append(f"\nLAST UPDATED:")
output.append("-" * 40)
output.append(details_info['last_updated'])
# Cache info
if data_cache["last_update"]:
cache_age = datetime.now() - data_cache["last_update"]
hours = cache_age.total_seconds() / 3600
next_refresh_hours = 12 - hours
output.append(f"\n📊 Data age: {hours:.1f} hours")
if next_refresh_hours > 0:
output.append(f"⏰ Next auto-refresh in: {next_refresh_hours:.1f} hours")
else:
output.append("⏰ Auto-refresh due now")
output.append("\n" + "=" * 80)
return "\n".join(output)
# Admin UI Controls
def build_admin_section():
"""Build admin controls section"""
with gr.Accordion("🔧 Admin Controls", open=False, visible=True) as admin_accordion:
admin_key = gr.Textbox(
label="Enter Admin Key",
type="password",
placeholder="Admin Only",
value=""
)
load_button = gr.Button("🔁 Reload All Data", visible=False, variant="primary")
admin_status = gr.Markdown("ℹ️ Enter admin key to access controls", visible=True)
def verify_admin_key(key):
"""Verify admin key and show/hide controls"""
if key.strip() == os.getenv("ADMIN_KEY", ""):
return (
gr.update(visible=True), # Show reload button
"✅ Access granted. You can reload data now."
)
elif key.strip() == "":
return (
gr.update(visible=False), # Hide reload button
"ℹ️ Enter admin key to access controls"
)
else:
return (
gr.update(visible=False), # Hide reload button
"❌ Invalid admin key."
)
def admin_reload():
"""Admin function to reload all data"""
try:
print("🔧 Admin reload triggered...")
combined_df, studentwise_data, details_info, reward_points_df = load_all_data()
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return f"✅ Data reloaded successfully at {timestamp}\n📊 Total records: {len(combined_df) if not combined_df.empty else 0}"
except Exception as e:
return f"❌ Error reloading data: {str(e)}"
# Event handlers
admin_key.change(
fn=verify_admin_key,
inputs=admin_key,
outputs=[load_button, admin_status]
)
load_button.click(
fn=admin_reload,
outputs=admin_status
)
return admin_accordion, admin_key, load_button, admin_status
# Function to determine if admin mode is enabled via URL parameter
def check_admin_mode(request: gr.Request) -> bool:
"""Check if admin mode is enabled via URL parameter"""
try:
query_params = urllib.parse.parse_qs(str(request.url).split('?')[1] if '?' in str(request.url) else "")
admin_param = query_params.get(os.getenv("ADMIN_MODE_URL"), [""])[0]
admin_mode_key = os.getenv("ADMIN_MODE_KEY", "")
is_admin = admin_param == admin_mode_key and admin_mode_key != ""
if is_admin:
print(f"🔧 Admin mode activated via URL parameter")
return is_admin
except Exception as e:
print(f"⚠️ Error checking admin mode: {str(e)}")
return False
# Create Gradio interface
with gr.Blocks(
title="Student Reward Points Check",
theme=gr.themes.Soft(),
) as app:
gr.Markdown("## 🎓 Student Reward Points Checker")
gr.HTML(
"""
<style>
.info-banner {
background: linear-gradient(90deg, #2c3e50, #4b6cb7);
padding: 14px 18px;
border-radius: 10px;
color: #ffffff;
font-size: 15px;
font-weight: 600;
text-align: center;
box-shadow: 0 2px 10px rgba(0,0,0,0.25);
margin-bottom: 14px;
}
.info-banner span {
color: #ffd966;
font-weight: 700;
}
</style>
<div class="info-banner">
ℹ️ <strong>Notice:</strong> Support has ended for <span>2022–2026 (4th Year)</span> students.<br>
📚 <span>Innovative Practice (IP) Details</span> will continue to be available.
</div>
"""
)
gr.Markdown("##### Search for Reward Points, Redemption Dates and Innovative Practice (IP) Details")
gr.Markdown("##### எல்லா புகழும் இறைவனுக்கே ✝ 🕉 ☪")
gr.Markdown("""
***Available Features*** :
**🔍 Student Search Tab**,
**📚 IP Details Tab**,
**ℹ️ System Info Tab**
""")
gr.Markdown("💻 **Mode**: Use Desktop Mode in browser for Good UI and UX")
gr.Markdown("🕒 **Auto-Updates**: Data automatically refreshes when there is a change in Reward Points Sheet")
gr.Markdown("📝 **Issue/Feedback Form** : [Issue/Feedback Form](https://docs.google.com/forms/d/e/1FAIpQLScnl0udcN2pUDENHl45HIj5HZbvDuwZ0g2eepBbp8tJYg-NvQ/viewform)")
with gr.Tabs():
with gr.TabItem("🔍 Student Search"):
with gr.Row():
with gr.Column(scale=3):
roll_input = gr.Textbox(
label="Enter Roll Number",
placeholder="e.g., 7376222AL181",
value=""
)
with gr.Column(scale=1):
search_btn = gr.Button("🔍 Search Student", variant="primary")
result_output = gr.Textbox(
label="Student Details",
lines=50,
max_lines=60,
show_copy_button=True,
autoscroll=False
)
with gr.TabItem("📚 Innovative Practice (IP) Details"):
with gr.Row():
with gr.Column(scale=3):
subject_roll_input = gr.Textbox(
label="Enter Roll Number for Innovative Practice (IP) Details",
placeholder="e.g., 7376222AL181",
value=""
)
with gr.Column(scale=1):
subject_search_btn = gr.Button("📚 Get Innovative Practice (IP) Details", variant="primary")
subject_output = gr.Textbox(
label="Innovative Practice (IP) Details",
lines=50,
max_lines=60,
show_copy_button=True,
autoscroll=False
)
with gr.TabItem("ℹ️ System Information"):
with gr.Row():
with gr.Column():
system_btn = gr.Button("📊 Get System Information", variant="secondary", size="lg")
system_output = gr.Textbox(
label="System Information",
lines=50,
max_lines=60,
show_copy_button=True,
autoscroll=False
)
with gr.TabItem("🔧 Admin Controls", visible=False) as admin_tab: # Start hidden by default
gr.Markdown("### 🔐 Administrative Functions")
gr.Markdown("⚠️ **Access restricted to authorized personnel only**")
with gr.Row():
with gr.Column(scale=1):
admin_key = gr.Textbox(
label="Enter Admin Key",
type="password",
placeholder="Enter admin password",
value=""
)
with gr.Column(scale=1):
load_button = gr.Button("🔁 Reload All Data", visible=False, variant="primary", size="lg")
admin_status = gr.Markdown("ℹ️ Enter admin key to access controls", visible=True)
# Admin functions
def verify_admin_key(key):
"""Verify admin key and show/hide controls"""
if key.strip() == os.getenv("ADMIN_KEY", ""):
return (
gr.update(visible=True), # Show reload button
"✅ **Access Granted!** You can now reload data."
)
elif key.strip() == "":
return (
gr.update(visible=False), # Hide reload button
"ℹ️ Enter admin key to access controls"
)
else:
return (
gr.update(visible=False), # Hide reload button
"❌ **Access Denied!** Invalid admin key."
)
def admin_reload():
"""Admin function to reload all data"""
try:
print("🔧 Admin reload triggered...")
combined_df, studentwise_data, details_info, reward_points_df = load_all_data()
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
total_records = len(combined_df) if not combined_df.empty else 0
return f"""✅ **Data Reload Successful!**
📅 **Timestamp:** {timestamp}\n
📊 **Total Records:** {total_records:,}\n
🔄 **Status:** All data sources refreshed\n
⏰ **Next Auto-refresh:** 12 hours from now\n
🎯 **Data Sources Updated:**\n
• Main spreadsheet ({len(sheet_configs)} sheets)\n
• Studentwise reward points data\n
• Activity breakdown data\n
• System information"""
except Exception as e:
return f"❌ **Error reloading data:** {str(e)}"
# Event handlers for admin tab
admin_key.change(
fn=verify_admin_key,
inputs=admin_key,
outputs=[load_button, admin_status]
)
load_button.click(
fn=admin_reload,
outputs=admin_status
)
# Event handlers - FIXED: Proper input/output mapping
# 1. Student search handlers
search_btn.click(
fn=search_student,
inputs=roll_input, # Changed from [roll_input] to roll_input
outputs=result_output
)
roll_input.submit(
fn=search_student,
inputs=roll_input, # Changed from [roll_input] to roll_input
outputs=result_output
)
# 2. IP Details handlers - FIXED: Proper input parameter
subject_search_btn.click(
fn=extract_subjects_and_marks_for_gradio,
inputs=subject_roll_input, # Changed from [subject_roll_input] to subject_roll_input
outputs=subject_output
)
subject_roll_input.submit(
fn=extract_subjects_and_marks_for_gradio,
inputs=subject_roll_input, # Changed from [subject_roll_input] to subject_roll_input
outputs=subject_output
)
# 3. System info handler - Correct (no inputs needed)
system_btn.click(
fn=get_system_info,
inputs=[],
outputs=system_output
)
# Footer section
gr.Markdown("---")
with gr.Row():
with gr.Column():
gr.Markdown(
"""
<div style="text-align: center; margin-top: 20px; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; color: white;">
<h3 style="margin: 0; color: white;">💻 Developed with ❤️ by</h3>
<a href="https://praneshjs.vercel.app" target="_blank" style="text-decoration: none;">
<h2 style="margin: 5px 0; color: #ffd700; cursor: pointer; transition: color 0.3s ease;">PRANESH S</h2>
</a>
<div style="margin: 15px 0;">
<a href="https://github.com/Pranesh-2005" target="_blank" style="color: #ffd700; text-decoration: none; margin: 0 10px; font-size: 16px;">
🐱 GitHub
</a>
<span style="color: #ffd700;">|</span>
<a href="https://www.linkedin.com/in/pranesh5264/" target="_blank" style="color: #ffd700; text-decoration: none; margin: 0 10px; font-size: 16px;">
💼 LinkedIn
</a>
<span style="color: #ffd700;">|</span>
<a href="https://mail.google.com/mail/?view=cm&fs=1&to=praneshmadhan646@gmail.com&su=Student%20Reward%20Points%20App%20-%20Feedback&body=Hi%20Pranesh,%0A%0AI%20am%20writing%20regarding%20the%20Student%20Reward%20Points%20application.%0A%0A" target="_blank" style="color: #ffd700; text-decoration: none; margin: 0 10px; font-size: 16px;">
📧 Contact Developer
</a>
</div>
<p style="margin: 10px 0; font-style: italic; color: #e0e0e0;">Made with 💝 Love and Support</p>
<p style="margin: 5px 0; font-size: 14px; color: #b0b0b0;">🚀 Empowering students with instant reward points tracking</p>
</div>
""",
elem_id="footer"
)
def setup_admin_mode(request: gr.Request):
"""Setup admin mode based on URL parameters"""
try:
is_admin = check_admin_mode(request)
if is_admin:
print("🔧 Admin tab will be visible")
return gr.update(visible=True) # Show admin tab
else:
return gr.update(visible=False) # Hide admin tab
except Exception as e:
print(f"⚠️ Error in setup_admin_mode: {str(e)}")
return gr.update(visible=False) # Hide admin tab on error
# System info initialization function - Fixed to handle errors gracefully
def initialize_system_info():
"""Initialize system information display with error handling"""
try:
return get_system_info()
except Exception as e:
error_msg = f"⚠️ Error initializing system info: {str(e)}"
print(error_msg)
return "⚠️ System information will be available after data loads completely. Please click 'Get System Information' button to retry."
app.load(
fn=setup_admin_mode,
outputs=admin_tab
)
# Load system info on startup
app.load(
fn=initialize_system_info,
inputs=[],
outputs=system_output
)
# Launch the app
if __name__ == "__main__":
print("🚀 Launching Gradio interface...")
app.launch(share=False, debug=True, server_name="0.0.0.0", server_port=7860, pwa=True)