# apps/participants/services.py from django.db import transaction from .models import Participant from apps.settingsapp.models import GradingSettings def _factor(diff: float) -> float: if diff <= 10: return 1.0 elif diff <= 20: return 0.8 return 0.6 def calculate_scores(): """Recalcula y persiste sólo el campo 'score' (base + similitud + bonus objetivo).""" settings = GradingSettings.get() participants = list(Participant.objects.all()) # 1) Base + similitud for p in participants: base = settings.report_weight + settings.presentation_weight sim_total = sum( _factor(getattr(p, f"{fld}_diff")) * getattr(settings, f"{fld}_weight") for fld in ("R", "L", "Ph", "Pcu") ) p.score = base + sim_total # 2) Calculamos bonus de objetivo según ranking de s_vol_over_eta (mayor es mejor) sorted_obj = sorted(participants, key=lambda x: x.s_vol_over_eta, reverse=True) obj_bonus_map = {} for idx, p in enumerate(sorted_obj, start=1): bonus = max(settings.objective_weight - (idx - 1), 0) obj_bonus_map[p.pk] = bonus # 3) Persistimos sumando el bonus a score with transaction.atomic(): for p in participants: p.score += obj_bonus_map[p.pk] p.save(update_fields=["score"]) def calculate_scores_details(): """ Devuelve una lista de dicts con desgloses: [{ participant: , base: float, sim_total: float, obj_bonus: float, total: float, objective_metric: float }, ...] ordenada exclusivamente por 'objective_metric' (s_vol_over_eta) descendente. """ settings = GradingSettings.get() participants = list(Participant.objects.all()) # 1) Calculamos base y similitud details = [] for p in participants: base = settings.report_weight + settings.presentation_weight sim_total = sum( _factor(getattr(p, f"{fld}_diff")) * getattr(settings, f"{fld}_weight") for fld in ("R", "L", "Ph", "Pcu") ) details.append({ "participant": p, "base": base, "sim_total": sim_total, "obj_bonus": 0, # se asignará más abajo "total": base + sim_total, # se incrementará con obj_bonus "objective_metric": p.s_vol_over_eta, }) # 2) Asignamos bonus de objetivo según posición en objective_metric sorted_by_obj = sorted(details, key=lambda d: d["objective_metric"], reverse=True) for idx, entry in enumerate(sorted_by_obj, start=1): bonus = max(settings.objective_weight - (idx - 1), 0) entry["obj_bonus"] = bonus entry["total"] += bonus # 3) Orden final sólo por objective_metric descendente return sorted(details, key=lambda d: d["objective_metric"], reverse=True)