Attender / attendance_code_module.py
chualinwei3's picture
Upload 30 files
f742815 verified
Raw
History Blame Contribute Delete
8.61 kB
"""
Attendance Code Module for Attendr
Implements KR Rule #5: Auto-Refresh Code Confirmation
∀x ((ValidCodeEntry(User,x) ∧ WithinCycle(Code,x)) → MarkPresent(System,x))
"""
import random
import string
from datetime import datetime, timedelta
from models import AttendanceSession, db
from config import Config
import logging
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class AttendanceCodeModule:
"""Handles attendance code generation, validation, and auto-refresh"""
def __init__(self, code_length=None, refresh_interval=None):
"""
Initialize attendance code module
Args:
code_length: Length of the code (default from config)
refresh_interval: Code refresh interval in seconds (default from config)
"""
self.code_length = code_length or Config.CODE_LENGTH
self.refresh_interval = refresh_interval or Config.CODE_REFRESH_INTERVAL
logger.info(f"Attendance Code Module initialized: length={self.code_length}, refresh={self.refresh_interval}s")
def generate_code(self):
"""
Generate a random attendance code
Returns:
String code (e.g., "A3X9K2")
"""
# Use uppercase letters and digits for clarity
characters = string.ascii_uppercase + string.digits
code = ''.join(random.choice(characters) for _ in range(self.code_length))
logger.info(f"Generated new code: {code}")
return code
def create_session_code(self, session_id):
"""
Create and store a new code for a session
Args:
session_id: ID of the attendance session
Returns:
tuple: (success, code or error_message)
"""
try:
session = AttendanceSession.query.get(session_id)
if not session:
return False, "Session not found"
if not session.is_active:
return False, "Session is not active"
# Generate new code
new_code = self.generate_code()
# Update session
session.current_code = new_code
session.code_generated_at = datetime.utcnow()
db.session.commit()
logger.info(f"Session {session_id} code updated to: {new_code}")
return True, new_code
except Exception as e:
db.session.rollback()
logger.error(f"Error creating session code: {str(e)}")
return False, f"Failed to create code: {str(e)}"
def is_code_valid(self, session_id, entered_code):
"""
Validate if entered code matches current session code and is within refresh cycle
Implements: ValidCodeEntry(User,x) ∧ WithinCycle(Code,x)
Args:
session_id: ID of the attendance session
entered_code: Code entered by student
Returns:
tuple: (is_valid: bool, message: str)
"""
try:
session = AttendanceSession.query.get(session_id)
if not session:
return False, "Session not found"
if not session.is_active:
return False, "Session is no longer active"
if not session.current_code:
return False, "No active code for this session"
# Check if code matches (case-insensitive)
if session.current_code.upper() != entered_code.upper():
logger.warning(f"Code mismatch: expected {session.current_code}, got {entered_code}")
return False, "Invalid code. Please check and try again."
# Check if code is within refresh cycle
if not session.code_generated_at:
return False, "Code timestamp not found"
time_elapsed = (datetime.utcnow() - session.code_generated_at).total_seconds()
if time_elapsed > self.refresh_interval:
logger.warning(f"Code expired: {time_elapsed:.0f}s elapsed (max: {self.refresh_interval}s)")
return False, f"Code has expired. Please use the current code displayed by your lecturer."
# Code is valid
time_remaining = self.refresh_interval - time_elapsed
logger.info(f"Code validated successfully. Time remaining: {time_remaining:.0f}s")
return True, "Code verified successfully"
except Exception as e:
logger.error(f"Code validation error: {str(e)}")
return False, f"Validation failed: {str(e)}"
def should_refresh_code(self, session_id):
"""
Check if code should be refreshed based on time elapsed
Args:
session_id: ID of the attendance session
Returns:
bool: True if code should be refreshed
"""
try:
session = AttendanceSession.query.get(session_id)
if not session or not session.is_active:
return False
if not session.code_generated_at:
return True # No code generated yet
time_elapsed = (datetime.utcnow() - session.code_generated_at).total_seconds()
return time_elapsed >= self.refresh_interval
except Exception as e:
logger.error(f"Error checking refresh status: {str(e)}")
return False
def auto_refresh_code(self, session_id):
"""
Automatically refresh code if needed
Args:
session_id: ID of the attendance session
Returns:
tuple: (refreshed: bool, code or None)
"""
try:
if self.should_refresh_code(session_id):
success, code = self.create_session_code(session_id)
if success:
logger.info(f"Code auto-refreshed for session {session_id}")
return True, code
else:
return False, None
else:
# Return current code
session = AttendanceSession.query.get(session_id)
if session and session.current_code:
return False, session.current_code
return False, None
except Exception as e:
logger.error(f"Auto-refresh error: {str(e)}")
return False, None
def get_code_status(self, session_id):
"""
Get current code status including time remaining
Args:
session_id: ID of the attendance session
Returns:
dict: Code status information
"""
try:
session = AttendanceSession.query.get(session_id)
if not session:
return {'error': 'Session not found'}
if not session.is_active:
return {'error': 'Session is not active'}
if not session.current_code or not session.code_generated_at:
return {
'code': None,
'time_remaining': 0,
'needs_refresh': True
}
time_elapsed = (datetime.utcnow() - session.code_generated_at).total_seconds()
time_remaining = max(0, self.refresh_interval - time_elapsed)
needs_refresh = time_elapsed >= self.refresh_interval
return {
'code': session.current_code,
'time_remaining': int(time_remaining),
'time_elapsed': int(time_elapsed),
'refresh_interval': self.refresh_interval,
'needs_refresh': needs_refresh,
'generated_at': session.code_generated_at.isoformat()
}
except Exception as e:
logger.error(f"Error getting code status: {str(e)}")
return {'error': str(e)}
# Singleton instance
attendance_code_module = AttendanceCodeModule()