""" Student Data Manager Manages student data from Excel/CSV files to ensure correct data is used for each student according to their creation records. This prevents age verification modals by using the correct DOB that matches the school records. """ import csv import os from pathlib import Path from typing import Dict, Optional from datetime import datetime, timedelta from config.config import BASE_DIR class StudentDataManager: """ Manages student data from CSV/Excel files. Ensures correct data is used for each student according to their creation records, preventing age verification modals and other data mismatches. """ _instance: Optional['StudentDataManager'] = None _students: Dict[str, Dict] = {} # CPID -> student data _data_file: Optional[Path] = None def __new__(cls): """Singleton pattern""" if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def load_students_from_csv(self, csv_file_path: Optional[str] = None): """ Load student data from CSV file. Args: csv_file_path: Path to CSV file. If None, searches for latest CSV in project root. """ if csv_file_path is None: # Find latest CSV file in project root csv_files = list(BASE_DIR.glob("students_with_passwords_*.csv")) if not csv_files: raise FileNotFoundError("No student CSV file found. Expected: students_with_passwords_*.csv") csv_file_path = max(csv_files, key=lambda p: p.stat().st_mtime) print(f"📋 Using latest CSV file: {csv_file_path.name}") csv_path = Path(csv_file_path) if not csv_path.exists(): raise FileNotFoundError(f"CSV file not found: {csv_path}") self._data_file = csv_path self._students.clear() with open(csv_path, 'r', encoding='utf-8') as f: reader = csv.DictReader(f) for row in reader: cpid = row.get('Student CPID', '').strip() if not cpid: continue # Extract all relevant data student_data = { 'cpid': cpid, 'password': row.get('Password', '').strip(), 'first_name': row.get('First Name', '').strip(), 'last_name': row.get('Last Name', '').strip(), 'gender': row.get('Gender', '').strip(), 'age': self._parse_age(row.get('Age', '').strip()), 'email': row.get('Email', '').strip(), 'phone': row.get('Phone Number', '').strip(), 'nationality': row.get('Nationality', '').strip(), 'native_state': row.get('Native', '').strip(), 'language': row.get('Language', '').strip(), 'address': row.get('Address line 1', '').strip(), 'city': row.get('City', '').strip(), 'state': row.get('State', '').strip(), 'pin_code': row.get('Pin code', '').strip(), 'father_name': row.get('Father Full Name', '').strip(), 'father_phone': row.get('Number', '').strip(), # First Number column 'father_occupation': row.get('Occupation', '').strip(), # First Occupation 'father_email': row.get('Email', '').strip(), # First Email 'mother_name': row.get('Mother Full Name', '').strip(), 'current_grade': row.get('Current Grade', '').strip(), 'section': row.get('Section/ Course', '').strip(), 'roll_number': row.get('Roll Number', '').strip(), 'institution_name': row.get('Institution Name', '').strip(), 'board_stream': row.get('Affiliation', '').strip(), # ICSE, CBSE, etc. } # Calculate DOB from age (to match school records) student_data['dob'] = self._calculate_dob_from_age(student_data['age']) self._students[cpid] = student_data print(f"✅ Loaded student data: {cpid} (Age: {student_data['age']}, DOB: {student_data['dob']})") print(f"✅ Loaded {len(self._students)} students from {csv_path.name}") def _parse_age(self, age_str: str) -> int: """Parse age string to integer""" try: return int(age_str.strip()) except (ValueError, AttributeError): return 16 # Default age def _calculate_dob_from_age(self, age: int) -> str: """ Calculate DOB from age to match school records. Uses current date minus age to get approximate DOB. This ensures the DOB matches the age in school records. Args: age: Student age from school records Returns: str: DOB in format YYYY-MM-DD """ today = datetime.now() # Calculate birth year (approximately) birth_year = today.year - age # Use January 1st as default date (can be adjusted if needed) dob = datetime(birth_year, 1, 15) return dob.strftime("%Y-%m-%d") def get_student_data(self, cpid: str) -> Optional[Dict]: """ Get student data by CPID. Args: cpid: Student CPID Returns: Dict with student data or None if not found """ # Auto-load if not loaded yet if not self._students: try: self.load_students_from_csv() except FileNotFoundError: print(f"⚠️ No student data file found. Cannot get data for {cpid}") return None return self._students.get(cpid) def get_dob_for_student(self, cpid: str) -> Optional[str]: """ Get correct DOB for student that matches school records. Args: cpid: Student CPID Returns: DOB string (YYYY-MM-DD) or None if not found """ student_data = self.get_student_data(cpid) if student_data: return student_data.get('dob') return None def get_age_for_student(self, cpid: str) -> Optional[int]: """ Get age for student from school records. Args: cpid: Student CPID Returns: Age as integer or None if not found """ student_data = self.get_student_data(cpid) if student_data: return student_data.get('age') return None def get_all_student_data(self, cpid: str) -> Optional[Dict]: """ Get all student data for a given CPID. Args: cpid: Student CPID Returns: Dict with all student data or None if not found """ return self.get_student_data(cpid) # Global instance student_data_manager = StudentDataManager()