TrafoKing/apps/participants/services.py
2025-05-13 01:32:51 +02:00

84 lines
2.9 KiB
Python

# 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: <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)