Fix
This commit is contained in:
74
backend/app/elo_mmr_models.py
Normal file
74
backend/app/elo_mmr_models.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""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),
|
||||
}
|
||||
Reference in New Issue
Block a user