Files
comunidadhll/backend/app/elo_mmr_models.py
2026-06-04 09:26:38 +02:00

75 lines
2.4 KiB
Python

"""Contracts and capability helpers for the Elo/MMR monthly ranking system."""
from __future__ import annotations
from dataclasses import asdict, dataclass
CAPABILITY_EXACT = "exact"
CAPABILITY_APPROXIMATE = "approximate"
CAPABILITY_UNAVAILABLE = "not_available"
ACCURACY_EXACT = "exact"
ACCURACY_APPROXIMATE = "approximate"
ACCURACY_PARTIAL = "partial"
DEFAULT_BASE_MMR = 1000.0
ELO_K_FACTOR = 60.0
MIN_VALID_MATCH_DURATION_SECONDS = 900
MIN_VALID_MATCH_PLAYERS = 20
MIN_VALID_PLAYER_PARTICIPATION_SECONDS = 900
MIN_VALID_PLAYER_PARTICIPATION_RATIO = 0.45
FULL_QUALITY_PLAYER_COUNT = 70
FULL_QUALITY_DURATION_SECONDS = 3600
MONTHLY_MIN_VALID_MATCHES = 5
MONTHLY_MIN_TIME_SECONDS = 21600
MONTHLY_ACTIVITY_TARGET_MATCHES = 12
MONTHLY_ACTIVITY_TARGET_HOURS = 20.0
DEFAULT_MONTHLY_SCOREBOARD_MIN_MATCHES = 3
@dataclass(frozen=True, slots=True)
class EloSignalAvailability:
"""Normalized availability state for one scoring input."""
name: str
status: str
detail: str
def to_dict(self) -> dict[str, object]:
"""Return the availability entry as a serializable mapping."""
return asdict(self)
def build_signal(name: str, status: str, detail: str) -> dict[str, object]:
"""Create a normalized availability block for one signal."""
return EloSignalAvailability(name=name, status=status, detail=detail).to_dict()
def summarize_accuracy(signals: list[dict[str, object]]) -> dict[str, object]:
"""Summarize exact, approximate and unavailable signals for one calculation."""
exact_count = sum(1 for signal in signals if signal.get("status") == CAPABILITY_EXACT)
approximate_count = sum(
1 for signal in signals if signal.get("status") == CAPABILITY_APPROXIMATE
)
unavailable_count = sum(
1 for signal in signals if signal.get("status") == CAPABILITY_UNAVAILABLE
)
if unavailable_count > 0:
accuracy_mode = ACCURACY_PARTIAL
elif approximate_count > 0:
accuracy_mode = ACCURACY_APPROXIMATE
else:
accuracy_mode = ACCURACY_EXACT
total = max(1, len(signals))
return {
"accuracy_mode": accuracy_mode,
"exact_count": exact_count,
"approximate_count": approximate_count,
"unavailable_count": unavailable_count,
"exact_ratio": round(exact_count / total, 3),
"approximate_ratio": round(approximate_count / total, 3),
"unavailable_ratio": round(unavailable_count / total, 3),
"signals": list(signals),
}