from flask import Blueprint, request, jsonify, current_app, render_template from datetime import datetime, timedelta from app.models.genetic_algorithm import ReservationOptimizer from app.models.reservation import Reservation, ReservationStatus from app import db from flask_login import login_required, current_user genetic_bp = Blueprint('genetic_algorithm', __name__, url_prefix='/api/genetic') @genetic_bp.route('/optimize', methods=['POST']) @login_required def optimize_reservations(): """Optimize reservations using genetic algorithm""" try: data = request.get_json() if not data or 'commission_ids' not in data: return jsonify({'error': 'Missing commission_ids in request'}), 400 commission_ids = data['commission_ids'] if not isinstance(commission_ids, list) or not commission_ids: return jsonify({'error': 'commission_ids must be a non-empty list'}), 400 # Optional parameters start_date = None end_date = None if 'start_date' in data: try: start_date = datetime.fromisoformat(data['start_date']) except ValueError: return jsonify({'error': 'Invalid start_date format. Use ISO format.'}), 400 if 'end_date' in data: try: end_date = datetime.fromisoformat(data['end_date']) except ValueError: return jsonify({'error': 'Invalid end_date format. Use ISO format.'}), 400 # Check if user has permission (admin or teacher) if current_user.role not in ['ADMIN', 'teacher']: return jsonify({'error': 'Insufficient permissions to optimize reservations'}), 403 # Initialize optimizer and run optimization optimizer = ReservationOptimizer() result = optimizer.optimize_schedule( commission_ids=commission_ids, admin_user_id=current_user.id, start_date=start_date, end_date=end_date ) return jsonify({ 'success': True, 'data': { 'fitness_score': result['fitness_score'], 'total_requests': result['total_requests'], 'assigned_reservations': result['assigned_reservations'], 'reservations': result['reservations'], 'conflicts': result['conflicts'] } }), 200 except Exception as e: current_app.logger.error(f"Error in genetic algorithm optimization: {str(e)}") return jsonify({'error': f'Optimization failed: {str(e)}'}), 500 @genetic_bp.route('/apply-optimization', methods=['POST']) @login_required def apply_optimization(): """Apply optimized reservations to the database""" try: data = request.get_json() if not data or 'reservations' not in data: return jsonify({'error': 'Missing reservations data'}), 400 reservations_data = data['reservations'] if not isinstance(reservations_data, list): return jsonify({'error': 'reservations must be a list'}), 400 # Check if user has permission if current_user.role not in ['ADMIN', 'teacher']: return jsonify({'error': 'Insufficient permissions to apply reservations'}), 403 # Initialize optimizer and apply reservations optimizer = ReservationOptimizer() applied_reservations = optimizer.apply_optimized_reservations(reservations_data) return jsonify({ 'success': True, 'data': { 'applied_count': len(applied_reservations), 'reservations': [res.to_dict() for res in applied_reservations] } }), 200 except Exception as e: current_app.logger.error(f"Error applying optimized reservations: {str(e)}") db.session.rollback() return jsonify({'error': f'Failed to apply reservations: {str(e)}'}), 500 @genetic_bp.route('/preview-optimization', methods=['POST']) @login_required def preview_optimization(): """Preview optimization without applying to database""" try: data = request.get_json() if not data or 'commission_ids' not in data: return jsonify({'error': 'Missing commission_ids in request'}), 400 commission_ids = data['commission_ids'] # Optional parameters dry_run = data.get('dry_run', True) # Default to dry run if not isinstance(commission_ids, list) or not commission_ids: return jsonify({'error': 'commission_ids must be a non-empty list'}), 400 # Check if user has permission if current_user.role not in ['ADMIN', 'teacher']: return jsonify({'error': 'Insufficient permissions to preview optimization'}), 403 # Initialize optimizer and run optimization optimizer = ReservationOptimizer() result = optimizer.optimize_schedule( commission_ids=commission_ids, admin_user_id=current_user.id ) # Add additional preview information preview_data = { 'optimization_result': result, 'commission_details': [], 'classroom_utilization': {}, 'time_distribution': {} } # Get commission details from app.models.subject import Commission, Subject commissions = Commission.query.filter(Commission.id.in_(commission_ids)).all() for commission in commissions: preview_data['commission_details'].append({ 'id': commission.id, 'code': commission.get_full_code(), 'name': commission.subject.name if commission.subject else 'Unknown', 'max_students': commission.max_students, 'current_students': commission.current_students }) return jsonify({ 'success': True, 'data': preview_data }), 200 except Exception as e: current_app.logger.error(f"Error in optimization preview: {str(e)}") return jsonify({'error': f'Preview failed: {str(e)}'}), 500 @genetic_bp.route('/commissions', methods=['GET']) @login_required def get_commissions(): """Get available commissions for optimization""" try: # Check if user has permission if current_user.role not in ['ADMIN', 'teacher']: return jsonify({'error': 'Insufficient permissions'}), 403 from app.models.subject import Commission, Subject # Get active commissions commissions = Commission.query.filter_by(active=True).all() commissions_data = [] for commission in commissions: commission_dict = commission.to_dict() commission_dict['get_full_code'] = commission.get_full_code() commissions_data.append(commission_dict) return jsonify({ 'success': True, 'commissions': commissions_data }), 200 except Exception as e: current_app.logger.error(f"Error getting commissions: {str(e)}") return jsonify({'error': f'Failed to load commissions: {str(e)}'}), 500 @genetic_bp.route('/algorithm-status', methods=['GET']) @login_required def get_algorithm_status(): """Get genetic algorithm configuration status""" try: # Check if user has permission if current_user.role not in ['ADMIN']: return jsonify({'error': 'Insufficient permissions'}), 403 # Get system status from app.models.classroom import Classroom from app.models.subject import Commission total_classrooms = Classroom.query.filter_by(is_active=True).count() total_commissions = Commission.query.filter_by(active=True).count() total_reservations = Reservation.query.filter_by(status=ReservationStatus.CONFIRMED.value).count() return jsonify({ 'success': True, 'data': { 'algorithm_config': { 'population_size': 50, 'generations': 100, 'mutation_rate': 0.1, 'crossover_rate': 0.8 }, 'system_status': { 'available_classrooms': total_classrooms, 'active_commissions': total_commissions, 'confirmed_reservations': total_reservations }, 'capabilities': [ 'Automatic classroom assignment', 'Capacity optimization', 'Time conflict resolution', 'Resource matching', 'Batch reservation processing' ] } }), 200 except Exception as e: current_app.logger.error(f"Error getting algorithm status: {str(e)}") return jsonify({'error': f'Status check failed: {str(e)}'}), 500 # Web interface route (without API prefix) genetic_web_bp = Blueprint('genetic_algorithm_web', __name__) @genetic_web_bp.route('/genetic-optimizer') @login_required def genetic_optimizer(): """Render the genetic algorithm optimizer interface""" if not (current_user.has_permission('optimizer', 'read') or (current_user.role and current_user.role.upper() == 'ADMIN')): return render_template('auth/403.html'), 403 return render_template('genetic_optimizer.html')