255 lines
9.6 KiB
Python
255 lines
9.6 KiB
Python
from flask import Blueprint, request, jsonify, current_app, render_template, g
|
|
from datetime import datetime, timedelta
|
|
from app.models.genetic_algorithm import ReservationOptimizer
|
|
from app.models.reservation import Reservation, ReservationStatus
|
|
from app import db
|
|
from app.utils.jwt_decorators import jwt_required
|
|
|
|
genetic_bp = Blueprint('genetic_algorithm', __name__, url_prefix='/api/genetic')
|
|
|
|
|
|
@genetic_bp.route('/optimize', methods=['POST'])
|
|
@jwt_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, teacher or RBAC optimizer write)
|
|
if not (g.jwt_user.is_admin() or g.jwt_user.has_permission('optimizer', 'read_write') or g.jwt_user.role 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=g.jwt_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'])
|
|
@jwt_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 not (g.jwt_user.is_admin() or g.jwt_user.has_permission('optimizer', 'read_write') or g.jwt_user.role 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'])
|
|
@jwt_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 g.jwt_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=g.jwt_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'])
|
|
@jwt_required
|
|
def get_commissions():
|
|
"""Get available commissions for optimization"""
|
|
try:
|
|
# Check if user has permission
|
|
if g.jwt_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'])
|
|
@jwt_required
|
|
def get_algorithm_status():
|
|
"""Get genetic algorithm configuration status"""
|
|
try:
|
|
# Check if user has permission
|
|
if g.jwt_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')
|
|
@jwt_required
|
|
def genetic_optimizer():
|
|
"""Render the genetic algorithm optimizer interface"""
|
|
if not (g.jwt_user.has_permission('optimizer', 'read') or (g.jwt_user.role and g.jwt_user.role.upper() == 'ADMIN')):
|
|
return render_template('auth/403.html'), 403
|
|
|
|
return render_template('genetic_optimizer.html') |