CP_AUTOMATION/utils/student_data_manager.py
2025-12-12 19:54:54 +05:30

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