196 lines
7.1 KiB
Python
196 lines
7.1 KiB
Python
"""
|
|
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()
|
|
|
|
|