| | 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) |