| 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_dotenv() |
|
|
| |
| ist = timezone("Asia/Kolkata") |
|
|
| |
| SCOPES = ["https://www.googleapis.com/auth/spreadsheets.readonly"] |
|
|
| def authorize(): |
| creds = None |
| |
| |
| token_json_content = os.getenv('TOKEN_JSON') |
| credentials_json_content = os.getenv('CREDENTIALS_JSON') |
| |
| |
| 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 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") |
| |
| |
| |
| print("🔄 New token generated. Consider updating TOKEN_JSON in your .env file with:") |
| print(f"TOKEN_JSON={creds.to_json()}") |
| |
| return gspread.authorize(creds) |
|
|
| |
| 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: |
| |
| 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() |
| |
| |
| student_row = None |
| for row in studentwise_data[1:]: |
| |
| for i, cell in enumerate(row[:5]): |
| 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." |
|
|
| |
| 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 |
|
|
| |
| student_name = get_value_if_not_empty("Student Name") or "Unknown" |
| |
| |
| 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") |
| }) |
|
|
| |
| 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") |
| }) |
|
|
| |
| 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'] |
|
|
| |
| 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) |
|
|
| |
| output = [] |
| output.append("") |
| output.append("🏆 INNOVATIVE PRACTICE (IP) SUMMARY") |
| output.append("=" * 80) |
| |
| |
| 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']}") |
| |
| |
| 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) |
| |
| |
| 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) |
| |
| |
| 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']}") |
| |
| |
| 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) |
| |
| |
| 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) |
| |
| |
| output.append("\n" + "=" * 80) |
| output.append("📊 OVERALL SUMMARY") |
| output.append("=" * 80) |
| |
| |
| 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}") |
| |
| |
| 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) |
| |
| |
| 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 |
|
|
|
|
| |
| 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() |
| |
| |
| headers = all_values[4] |
| clean_headers = [] |
| seen_headers = {} |
| |
| for i, header in enumerate(headers): |
| if header.strip(): |
| base_header = header.strip() |
| |
| 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: |
| clean_headers.append(f"Empty_Col_{i}") |
|
|
| |
| data_rows = all_values[5:] |
| if data_rows: |
| df = pd.DataFrame(data_rows, columns=clean_headers) |
| |
| 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() |
|
|
| |
| 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 |
|
|
| |
| def load_reward_points_data(): |
| """Load and cache reward points activity data""" |
| try: |
| |
| 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) |
| 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 |
| |
| |
| 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 |
|
|
| |
| 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() |
|
|
| |
| |
| |
| roll_col = None |
|
|
| |
| for col in reward_points_df.columns: |
| cl = col.lower().replace(" ", "") |
| if "roll" in cl or "reg" in cl: |
| roll_col = col |
| break |
|
|
| |
| 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 |
|
|
| |
| if roll_col is None: |
| roll_col = reward_points_df.columns[len(reward_points_df.columns)//2] |
|
|
| |
| 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] |
|
|
| |
| 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 "" |
|
|
| |
| 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(",", "") |
|
|
| |
| try: |
| points_val = float(reward_points) |
| except: |
| points_val = 0 |
|
|
| total_points += points_val |
|
|
| |
| 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[:65] + "..." if len(name) > 65 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 "" |
|
|
| |
| 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 |
| |
| |
| 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}") |
| |
| |
| 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 = {} |
| |
| |
| for idx in range(len(df)): |
| student_data = df.iloc[idx] |
| year_value = str(student_data.get('YEAR', '')).strip() |
| |
| |
| 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', '') |
| } |
| |
| |
| 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', '') |
| } |
| |
| |
| 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', '') |
| } |
| |
| |
| 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 |
|
|
| |
| print("🚀 Initializing application...") |
| client = authorize() |
|
|
| |
| MAIN_SHEET_ID = os.getenv('GOOGLE_SHEET_ID') |
| STUDENTWISE_SHEET_ID = os.getenv('STUDENTWISE_SHEET_ID') |
|
|
| if not MAIN_SHEET_ID: |
| raise ValueError("GOOGLE_SHEET_ID environment variable is required") |
|
|
| |
| main_spreadsheet = client.open_by_key(MAIN_SHEET_ID) |
| studentwise_spreadsheet = client.open_by_key(STUDENTWISE_SHEET_ID) |
|
|
| |
| sheet_configs = [ |
| |
| {"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"} |
| ] |
|
|
| |
| data_cache = { |
| "combined_df": None, |
| "studentwise_data": None, |
| "details_info": None, |
| "reward_points_df": None, |
| "last_update": None, |
| "cache_duration_hours": 12, |
| "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 |
| retry_delay = 2 |
|
|
| |
| 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']}") |
|
|
| |
| 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 |
|
|
| |
| 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)}") |
| |
| |
| if all_dataframes: |
| print(f"✅ Continuing with {len(all_dataframes)} successfully loaded sheets") |
| else: |
| print("❌ Critical: No sheets loaded successfully!") |
| |
| return pd.DataFrame(), None, None, None |
| |
| |
| 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...") |
| |
| |
| 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: |
| |
| 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") |
|
|
| |
| 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() |
|
|
| |
| 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() |
| |
| |
| 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: |
| |
| time.sleep(43200) |
| print("⏰ 12-hour auto-refresh triggered...") |
| load_all_data() |
| except Exception as e: |
| print(f"❌ Auto-refresh error: {str(e)}") |
| |
| 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 |
| check_interval = 30 |
| 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() |
|
|
| |
| 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: |
| |
| 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 |
|
|
| |
| if not watcher_sheet: |
| time.sleep(check_interval) |
| continue |
|
|
| try: |
| |
| 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 |
|
|
| |
| 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 |
|
|
| |
| 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) |
| |
| |
| 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 |
|
|
| |
| 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" |
|
|
| |
| 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" |
|
|
| |
| df = combined_df[[year_col, points_col]].copy() |
| |
| |
| df[points_col] = pd.to_numeric( |
| df[points_col].astype(str).str.replace(',', '').str.strip(), |
| errors='coerce' |
| ) |
| df.dropna(subset=[points_col], inplace=True) |
| |
| |
| df = df[df[points_col] > 0] |
|
|
| if df.empty: |
| return "⚠️ No valid points data found for calculation" |
|
|
| |
| yearwise = df.groupby(year_col)[points_col].agg(['sum', 'count', 'mean', 'min', 'max']).reset_index() |
| yearwise['average'] = yearwise['mean'] |
|
|
| |
| 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) |
|
|
| |
| print("📊 Loading initial data...") |
| load_all_data() |
|
|
| |
| refresh_thread = threading.Thread(target=auto_refresh_worker, daemon=True) |
| refresh_thread.start() |
| print("🕒 Auto-refresh thread started (updates every 12 hours)") |
|
|
| |
| watcher_thread = threading.Thread(target=details_sheet_watcher, daemon=True) |
| watcher_thread.start() |
| print("👀 Details sheet watcher started (checks every 1 minute)") |
|
|
| |
| def search_student(roll_no): |
| if not roll_no.strip(): |
| return "❌ Please enter a roll number" |
| |
| |
| roll_no = roll_no.strip().upper() |
| |
| |
| combined_df, studentwise_data, details_info, reward_points_df = get_cached_data() |
| |
| if combined_df.empty: |
| return "❌ No data available from Google Sheets" |
| |
| |
| 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)}" |
| |
| |
| 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") |
| |
| print(f"Roll No Searched: {roll_no} | Student Name: {student_name} | Time (IST): {now_ist}") |
| |
| |
| output = [] |
| output.append(f"Hello {student_name} 👋") |
| output.append("=" * 80) |
| output.append("YOUR DETAILS") |
| output.append("=" * 80) |
| |
| |
| 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}") |
| |
| |
| 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 |
| |
| |
| 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}") |
| |
| |
| points_difference = avg_points - student_points |
| |
| if points_difference > 0: |
| |
| 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: |
| |
| 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") |
| |
| |
| activity_details = get_activity_details(roll_no, reward_points_df) |
| if activity_details: |
| output.append(activity_details) |
| |
| |
| detailed_points = get_detailed_student_points(roll_no, studentwise_data) |
| if detailed_points: |
| output.append(detailed_points) |
| |
| |
| 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']) |
| |
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| 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_averages = calculate_yearwise_average_points() |
| if calculated_averages and not calculated_averages.startswith("❌") and not calculated_averages.startswith("⚠️"): |
| output.append(calculated_averages) |
| |
| |
| 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']) |
| |
| |
| 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) |
|
|
| |
| 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), |
| "✅ Access granted. You can reload data now." |
| ) |
| elif key.strip() == "": |
| return ( |
| gr.update(visible=False), |
| "ℹ️ Enter admin key to access controls" |
| ) |
| else: |
| return ( |
| gr.update(visible=False), |
| "❌ 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)}" |
|
|
| |
| 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 |
|
|
| |
| 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 |
|
|
|
|
|
|
|
|
| |
| with gr.Blocks( |
| title="Student Reward Points Check", |
| theme=gr.themes.Soft(), |
| ) as app: |
| gr.Markdown("## 🎓 Student Reward Points Checker") |
| 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.HTML( |
| """ |
| <style> |
| .urgent-banner { |
| background: linear-gradient(90deg, #ff0000, #ffae00, #ff0000); |
| background-size: 300% 300%; |
| animation: gradientMove 4s ease infinite, glowPulse 1.5s infinite; |
| padding: 20px 24px; |
| border-radius: 14px; |
| color: #ffffff; |
| font-weight: 800; |
| font-size: 18px; |
| text-align: center; |
| box-shadow: |
| 0 0 20px rgba(255,0,0,0.9), |
| 0 0 40px rgba(255,174,0,0.8); |
| margin-bottom: 16px; |
| } |
| |
| @keyframes gradientMove { |
| 0% { background-position: 0% 50%; } |
| 50% { background-position: 100% 50%; } |
| 100% { background-position: 0% 50%; } |
| } |
| |
| @keyframes glowPulse { |
| 0% { box-shadow: 0 0 15px rgba(255,0,0,0.8); } |
| 50% { box-shadow: 0 0 35px rgba(255,174,0,1); } |
| 100% { box-shadow: 0 0 15px rgba(255,0,0,0.8); } |
| } |
| |
| .urgent-btn { |
| display: inline-block; |
| margin-top: 12px; |
| padding: 10px 18px; |
| background: #000000; |
| color: #ffd700; |
| font-weight: 800; |
| font-size: 16px; |
| border-radius: 8px; |
| text-decoration: none; |
| box-shadow: 0 0 10px rgba(0,0,0,0.6); |
| transition: transform 0.2s ease, box-shadow 0.2s ease; |
| } |
| |
| .urgent-btn:hover { |
| transform: scale(1.05); |
| box-shadow: 0 0 20px rgba(255,215,0,0.9); |
| } |
| </style> |
| |
| <div class="urgent-banner"> |
| 🚨 <strong>URGENT – RP SITE OVERALL FEEDBACK</strong> 🚨<br><br> |
| |
| 📢 This is an <u>overall feedback form</u>.<br> |
| 📊 Using this feedback, the <strong>future presence of this website for upcoming semesters will be decided</strong>.<br><br> |
| |
| ⚠️ <strong>Kindly give genuine and honest responses.</strong><br> |
| |
| <a class="urgent-btn" href="https://forms.gle/y2xLTtgVxPEsH7jRA" target="_blank"> |
| 📝 GIVE FEEDBACK NOW |
| </a> |
| </div> |
| """ |
| ) |
| 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: |
| 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) |
| |
| |
| def verify_admin_key(key): |
| """Verify admin key and show/hide controls""" |
| if key.strip() == os.getenv("ADMIN_KEY", ""): |
| return ( |
| gr.update(visible=True), |
| "✅ **Access Granted!** You can now reload data." |
| ) |
| elif key.strip() == "": |
| return ( |
| gr.update(visible=False), |
| "ℹ️ Enter admin key to access controls" |
| ) |
| else: |
| return ( |
| gr.update(visible=False), |
| "❌ **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)}" |
|
|
| |
| admin_key.change( |
| fn=verify_admin_key, |
| inputs=admin_key, |
| outputs=[load_button, admin_status] |
| ) |
| |
| load_button.click( |
| fn=admin_reload, |
| outputs=admin_status |
| ) |
| |
| |
| |
| |
| |
| search_btn.click( |
| fn=search_student, |
| inputs=roll_input, |
| outputs=result_output |
| ) |
| |
| roll_input.submit( |
| fn=search_student, |
| inputs=roll_input, |
| outputs=result_output |
| ) |
| |
| |
| subject_search_btn.click( |
| fn=extract_subjects_and_marks_for_gradio, |
| inputs=subject_roll_input, |
| outputs=subject_output |
| ) |
| |
| subject_roll_input.submit( |
| fn=extract_subjects_and_marks_for_gradio, |
| inputs=subject_roll_input, |
| outputs=subject_output |
| ) |
| |
| |
| system_btn.click( |
| fn=get_system_info, |
| inputs=[], |
| outputs=system_output |
| ) |
| |
| |
| 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) |
| else: |
| return gr.update(visible=False) |
| except Exception as e: |
| print(f"⚠️ Error in setup_admin_mode: {str(e)}") |
| return gr.update(visible=False) |
|
|
|
|
|
|
| |
| 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 |
| ) |
| |
| |
| app.load( |
| fn=initialize_system_info, |
| inputs=[], |
| outputs=system_output |
| ) |
|
|
| |
| if __name__ == "__main__": |
| print("🚀 Launching Gradio interface...") |
| app.launch(share=False, debug=True, server_name="0.0.0.0", server_port=7860, pwa=True) |