"""Evaluation routes for assessing player performance during tryouts. Uses polymorphic isinstance checks instead of role-string comparisons. """ from flask import Blueprint, render_template, redirect, url_for, flash, request from flask_login import login_required, current_user from app.extensions import db from app.models import ( Admin, Coach, Manager, Player, User, Tryout, Evaluation, TryoutRegistration, OrgTeam, GAME_POSITIONS, ) from sqlalchemy import func from sqlalchemy.orm import aliased evaluations_bp = Blueprint('evaluations', __name__, url_prefix='/evaluations') def validate_score(score_value): """Validate that a score is between 1 and 10.""" if score_value is None: return None try: score = int(score_value) if 1 <= score <= 10: return score return None except (ValueError, TypeError): return None @evaluations_bp.route('') @login_required def list_evaluations(): """List all evaluations accessible to the current user.""" user = current_user if isinstance(user, Player): flash('You do not have permission to view evaluations.', 'danger') return redirect(url_for('main.dashboard')) sort_column = request.args.get('sort', 'created_at') sort_order = request.args.get('order', 'desc') if sort_order not in ('asc', 'desc'): sort_order = 'desc' player_alias = aliased(User, name='eval_player') evaluator_alias = aliased(User, name='eval_evaluator') sort_map = { 'tryout': Tryout.title, 'player': player_alias.username, 'evaluator': evaluator_alias.username, 'mecanics_score': Evaluation.mecanics_score, 'cohesion_score': Evaluation.cohesion_score, 'communication_score': Evaluation.communication_score, 'gamesense_score': Evaluation.gamesense_score, 'versatility_score': Evaluation.versatility_score, 'discipline_score': Evaluation.discipline_score, 'analysis_score': Evaluation.analysis_score, 'sport_ethics_score': Evaluation.sport_ethics_score, 'mental_score': Evaluation.mental_score, 'overall_score': Evaluation.overall_score, 'position_recommendation': Evaluation.position_recommendation, 'created_at': Evaluation.created_at, } sort_expr = sort_map.get(sort_column, Evaluation.created_at) if sort_order == 'asc': sort_expr = sort_expr.asc() else: sort_expr = sort_expr.desc() if isinstance(user, Admin): evaluations = Evaluation.query \ .outerjoin(Tryout, Evaluation.tryout_id == Tryout.id) \ .outerjoin(player_alias, Evaluation.player_id == player_alias.id) \ .outerjoin(evaluator_alias, Evaluation.evaluator_id == evaluator_alias.id) \ .order_by(sort_expr).all() avg_scores = db.session.query( Evaluation.player_id, func.count(Evaluation.id).label('eval_count'), func.avg(Evaluation.overall_score).label('avg_score'), ).group_by(Evaluation.player_id).all() player_scores = {} for row in avg_scores: p = User.query.get(row.player_id) if p: player_scores[p.id] = { 'player': p, 'count': row.eval_count, 'avg': round(row.avg_score, 1) if row.avg_score else 0, } elif user.can_evaluate(): evaluations = Evaluation.query \ .outerjoin(Tryout, Evaluation.tryout_id == Tryout.id) \ .outerjoin(player_alias, Evaluation.player_id == player_alias.id) \ .outerjoin(evaluator_alias, Evaluation.evaluator_id == evaluator_alias.id) \ .filter(Evaluation.evaluator_id == user.id) \ .order_by(sort_expr).all() player_scores = {} else: evaluations = Evaluation.query \ .outerjoin(Tryout, Evaluation.tryout_id == Tryout.id) \ .outerjoin(player_alias, Evaluation.player_id == player_alias.id) \ .outerjoin(evaluator_alias, Evaluation.evaluator_id == evaluator_alias.id) \ .filter(Evaluation.player_id == user.id) \ .order_by(sort_expr).all() player_scores = {} return render_template('pages/evaluations.html', evaluations=evaluations, player_scores=player_scores, sort_column=sort_column, sort_order=sort_order) @evaluations_bp.route('//', methods=['GET', 'POST']) @login_required def evaluate_player(tryout_id, player_id): """Evaluate a specific player in a tryout.""" if not current_user.can_evaluate(): flash('You do not have permission to evaluate players.', 'danger') return redirect(url_for('main.dashboard')) tryout = Tryout.query.get_or_404(tryout_id) if not current_user.can_manage_this_tryout(tryout): flash('You do not have permission to evaluate players in this tryout.', 'danger') return redirect(url_for('tryouts.list_tryouts')) is_registered = TryoutRegistration.query.filter_by( tryout_id=tryout_id, player_id=player_id, ).first() is not None if not is_registered: flash('Player is not registered for this tryout.', 'danger') return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) player = User.query.get_or_404(player_id) if not isinstance(player, Player): flash('Can only evaluate players.', 'danger') return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) existing_eval = Evaluation.query.filter_by( tryout_id=tryout_id, player_id=player_id, evaluator_id=current_user.id, ).first() if request.method == 'POST': mecanics = validate_score(request.form.get('mecanics_score')) cohesion = validate_score(request.form.get('cohesion_score')) communication = validate_score(request.form.get('communication_score')) gamesense = validate_score(request.form.get('gamesense_score')) versatility = validate_score(request.form.get('versatility_score')) discipline = validate_score(request.form.get('discipline_score')) analysis = validate_score(request.form.get('analysis_score')) sport_ethics = validate_score(request.form.get('sport_ethics_score')) mental = validate_score(request.form.get('mental_score')) comments = request.form.get('comments') position = request.form.get('position_recommendation') scores = [s for s in [mecanics, cohesion, communication, gamesense, versatility, discipline, analysis, sport_ethics, mental] if s is not None] overall = sum(scores) / len(scores) if scores else None if existing_eval: existing_eval.mecanics_score = mecanics existing_eval.cohesion_score = cohesion existing_eval.communication_score = communication existing_eval.gamesense_score = gamesense existing_eval.versatility_score = versatility existing_eval.discipline_score = discipline existing_eval.analysis_score = analysis existing_eval.sport_ethics_score = sport_ethics existing_eval.mental_score = mental existing_eval.overall_score = overall existing_eval.comments = comments existing_eval.position_recommendation = position flash('Evaluation updated!', 'success') else: evaluation = Evaluation( tryout_id=tryout_id, player_id=player_id, evaluator_id=current_user.id, mecanics_score=mecanics, cohesion_score=cohesion, communication_score=communication, gamesense_score=gamesense, versatility_score=versatility, discipline_score=discipline, analysis_score=analysis, sport_ethics_score=sport_ethics, mental_score=mental, overall_score=overall, comments=comments, position_recommendation=position, ) db.session.add(evaluation) flash('Evaluation submitted successfully!', 'success') db.session.commit() return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) evaluators = None if isinstance(current_user, Admin): all_evaluations = Evaluation.query.filter_by( tryout_id=tryout_id, player_id=player_id, ).all() evaluators = [{'evaluator': User.query.get(e.evaluator_id), 'eval': e} for e in all_evaluations] return render_template('pages/evaluate_player.html', tryout=tryout, player=player, existing_eval=existing_eval, evaluators=evaluators, game_positions=GAME_POSITIONS) @evaluations_bp.route('//players') @login_required def players_to_evaluate(tryout_id): """List players that need evaluation in a specific tryout.""" if not current_user.can_evaluate(): flash('Permission denied.', 'danger') return redirect(url_for('main.dashboard')) tryout = Tryout.query.get_or_404(tryout_id) if not current_user.can_manage_this_tryout(tryout): flash('You do not have permission to evaluate players in this tryout.', 'danger') return redirect(url_for('tryouts.list_tryouts')) registrations = TryoutRegistration.query.filter_by(tryout_id=tryout_id).all() players = [] for reg in registrations: p = User.query.get(reg.player_id) if p and isinstance(p, Player): existing = Evaluation.query.filter_by( tryout_id=tryout_id, player_id=p.id, evaluator_id=current_user.id, ).first() players.append({'player': p, 'evaluated': existing is not None, 'registration': reg}) return render_template('pages/players_to_evaluate.html', tryout=tryout, players=players)