- Auth cookie simple (SSO-ready) : login/logout, redirection si non authentifié
- Données isolées par utilisateur dans /data/users/{user_id}/
- Migration automatique des données legacy au premier login
- Page classement avec podium et tableau comparatif (score, précision zéro, conformité)
- Nav mise à jour : Classement, affichage utilisateur courant, déconnexion
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
49 lines
1.9 KiB
Python
49 lines
1.9 KiB
Python
"""Leaderboard stats: compare all users on overtime precision and directive compliance."""
|
|
from models import list_users
|
|
from stats import compute_all_stats, _compliance_over, _day_compliant
|
|
from calcul import minutes_to_hhmm
|
|
|
|
|
|
def compute_classement() -> list[dict]:
|
|
"""Return ranked list of users with their key metrics."""
|
|
results = []
|
|
|
|
for user_id in list_users():
|
|
s = compute_all_stats(user_id)
|
|
if not s["weekly_balances"] and s["n_jours"] == 0:
|
|
continue # skip empty accounts
|
|
|
|
balances = [b["delta_min"] for b in s["weekly_balances"]]
|
|
|
|
# Precision: % of weeks where |solde| ≤ 30 min (≈ at zero)
|
|
if balances:
|
|
pct_zero = round(100 * sum(1 for b in balances if abs(b) <= 30) / len(balances))
|
|
mean_abs = round(sum(abs(b) for b in balances) / len(balances))
|
|
else:
|
|
pct_zero = None
|
|
mean_abs = None
|
|
|
|
conf_pct = s["comp_globale"]["pct"]
|
|
|
|
# Combined score (SSO era: will weight per role/contract)
|
|
score = round(
|
|
(pct_zero or 0) * 0.6 + (conf_pct or 0) * 0.4
|
|
)
|
|
|
|
results.append({
|
|
"user_id": user_id,
|
|
"display_name": user_id.replace("_", " ").replace("-", " ").title(),
|
|
"n_semaines": len(balances),
|
|
"n_jours": s["n_jours"],
|
|
"pct_zero": pct_zero, # % weeks ≈ 0 HS — higher = better
|
|
"mean_abs_min": mean_abs, # mean abs(solde) in minutes — lower = better
|
|
"mean_abs_str": minutes_to_hhmm(mean_abs) if mean_abs is not None else "—",
|
|
"conf_pct": conf_pct, # directive compliance % — higher = better
|
|
"score": score, # combined 0-100 — higher = better
|
|
"avg_delta": s["avg_delta"],
|
|
"avg_delta_min": s["avg_delta_min"],
|
|
})
|
|
|
|
results.sort(key=lambda r: -r["score"])
|
|
return results
|