feat: timely_rating_filtrate #18
@@ -202,6 +202,9 @@ def detail(steam_id):
|
||||
})
|
||||
map_stats_list.sort(key=lambda x: x['matches'], reverse=True)
|
||||
|
||||
# --- New: Recent Performance Stats ---
|
||||
recent_stats = StatsService.get_recent_performance_stats(steam_id)
|
||||
|
||||
return render_template('players/profile.html',
|
||||
player=player,
|
||||
features=features,
|
||||
@@ -211,7 +214,8 @@ def detail(steam_id):
|
||||
distribution=distribution,
|
||||
map_stats=map_stats_list,
|
||||
l2_stats=l2_stats,
|
||||
side_stats=side_stats)
|
||||
side_stats=side_stats,
|
||||
recent_stats=recent_stats)
|
||||
|
||||
@bp.route('/comment/<int:comment_id>/like', methods=['POST'])
|
||||
def like_comment(comment_id):
|
||||
|
||||
@@ -632,6 +632,72 @@ class StatsService:
|
||||
"""
|
||||
return query_db('l2', sql, [steam_id, limit])
|
||||
|
||||
@staticmethod
|
||||
def get_recent_performance_stats(steam_id):
|
||||
"""
|
||||
Calculates Avg Rating and Rating Variance for:
|
||||
- Last 5, 10, 15 matches
|
||||
- Last 5, 10, 15 days
|
||||
"""
|
||||
import numpy as np
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Fetch all match ratings with timestamps
|
||||
sql = """
|
||||
SELECT m.start_time, mp.rating
|
||||
FROM fact_match_players mp
|
||||
JOIN fact_matches m ON mp.match_id = m.match_id
|
||||
WHERE mp.steam_id_64 = ?
|
||||
ORDER BY m.start_time DESC
|
||||
"""
|
||||
rows = query_db('l2', sql, [steam_id])
|
||||
|
||||
if not rows:
|
||||
return {}
|
||||
|
||||
# Convert to list of dicts
|
||||
matches = [{'time': r['start_time'], 'rating': r['rating'] or 0} for r in rows]
|
||||
|
||||
stats = {}
|
||||
|
||||
# 1. Recent N Matches
|
||||
for n in [5, 10, 15]:
|
||||
subset = matches[:n]
|
||||
if not subset:
|
||||
stats[f'last_{n}_matches'] = {'avg': 0, 'var': 0, 'count': 0}
|
||||
continue
|
||||
|
||||
ratings = [m['rating'] for m in subset]
|
||||
stats[f'last_{n}_matches'] = {
|
||||
'avg': np.mean(ratings),
|
||||
'var': np.var(ratings),
|
||||
'count': len(ratings)
|
||||
}
|
||||
|
||||
# 2. Recent N Days
|
||||
# Use server time or max match time? usually server time 'now' is fine if data is fresh.
|
||||
# But if data is old, 'last 5 days' might be empty.
|
||||
# User asked for "recent 5/10/15 days", implying calendar days from now.
|
||||
import time
|
||||
now = time.time()
|
||||
|
||||
for d in [5, 10, 15]:
|
||||
cutoff = now - (d * 24 * 3600)
|
||||
subset = [m for m in matches if m['time'] >= cutoff]
|
||||
|
||||
if not subset:
|
||||
stats[f'last_{d}_days'] = {'avg': 0, 'var': 0, 'count': 0}
|
||||
continue
|
||||
|
||||
ratings = [m['rating'] for m in subset]
|
||||
stats[f'last_{d}_days'] = {
|
||||
'avg': np.mean(ratings),
|
||||
'var': np.var(ratings),
|
||||
'count': len(ratings)
|
||||
}
|
||||
|
||||
return stats
|
||||
|
||||
@staticmethod
|
||||
def get_roster_stats_distribution(target_steam_id):
|
||||
"""
|
||||
|
||||
@@ -141,6 +141,66 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2.2 Recent Performance Stability -->
|
||||
<div class="bg-white dark:bg-slate-800 shadow-lg rounded-2xl p-6 border border-gray-100 dark:border-slate-700">
|
||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-6 flex items-center gap-2">
|
||||
<span>📅</span> 近期表现稳定性 (Recent Stability)
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<!-- By Match Count -->
|
||||
<div>
|
||||
<h4 class="text-sm font-bold text-gray-500 uppercase tracking-wider mb-4">By Matches</h4>
|
||||
<div class="space-y-4">
|
||||
{% for n in [5, 10, 15] %}
|
||||
{% set key = 'last_' ~ n ~ '_matches' %}
|
||||
{% set data = recent_stats.get(key) %}
|
||||
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-slate-700/50 rounded-lg border border-gray-100 dark:border-slate-600">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-2xl font-black text-gray-300 dark:text-slate-600 w-8 text-center">{{ n }}</span>
|
||||
<span class="text-xs font-bold text-gray-500 uppercase">Matches</span>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
{% if data and data.count > 0 %}
|
||||
<div class="text-xl font-black text-gray-900 dark:text-white">{{ "{:.2f}".format(data.avg) }} <span class="text-xs text-gray-400 font-normal">Rating</span></div>
|
||||
<div class="text-xs text-gray-500 font-mono">Var: {{ "{:.3f}".format(data.var) }}</div>
|
||||
{% else %}
|
||||
<span class="text-xs text-gray-400">N/A</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- By Days -->
|
||||
<div>
|
||||
<h4 class="text-sm font-bold text-gray-500 uppercase tracking-wider mb-4">By Days</h4>
|
||||
<div class="space-y-4">
|
||||
{% for n in [5, 10, 15] %}
|
||||
{% set key = 'last_' ~ n ~ '_days' %}
|
||||
{% set data = recent_stats.get(key) %}
|
||||
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-slate-700/50 rounded-lg border border-gray-100 dark:border-slate-600">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-2xl font-black text-gray-300 dark:text-slate-600 w-8 text-center">{{ n }}</span>
|
||||
<span class="text-xs font-bold text-gray-500 uppercase">Days</span>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
{% if data and data.count > 0 %}
|
||||
<div class="text-xl font-black text-gray-900 dark:text-white">{{ "{:.2f}".format(data.avg) }} <span class="text-xs text-gray-400 font-normal">Rating</span></div>
|
||||
<div class="text-xs text-gray-500 font-mono">Var: {{ "{:.3f}".format(data.var) }}</div>
|
||||
<div class="text-[10px] text-gray-400">{{ data.count }} matches</div>
|
||||
{% else %}
|
||||
<span class="text-xs text-gray-400">No matches</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2.5 Detailed Stats Panel -->
|
||||
<div class="bg-white dark:bg-slate-800 shadow-lg rounded-2xl p-6 border border-gray-100 dark:border-slate-700">
|
||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-6 flex items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user