Signed-off-by: TIANHE <TIANHE@GMAIL.COM>
This commit is contained in:
TIANHE
2026-03-01 17:20:37 +08:00
parent d60409f7f8
commit db91fa4580
53 changed files with 1596 additions and 611 deletions
+76 -47
View File
@@ -1334,32 +1334,29 @@ def get_admin_ai_stats():
memory_summary = {}
# --- Per-user stats ---
user_conditions = []
# Build WHERE clause for user search (applied after JOIN)
user_where_clause = ""
user_params = []
if search:
user_conditions.append("(u.username ILIKE ? OR u.nickname ILIKE ? OR u.email ILIKE ?)")
like_val = f"%{search}%"
user_params.extend([like_val, like_val, like_val])
user_where_clause = "WHERE (u.username ILIKE ? OR u.nickname ILIKE ? OR u.email ILIKE ?)"
like_val = f"%{search.strip()}%"
user_params = [like_val, like_val, like_val]
user_where = ""
if user_conditions:
user_where = "WHERE " + " AND ".join(user_conditions)
# Count distinct users who have analysis records
cur.execute(
f"""
# Count distinct users who have analysis records (matching search criteria)
count_sql = f"""
SELECT COUNT(DISTINCT t.user_id) AS cnt
FROM qd_analysis_tasks t
LEFT JOIN qd_users u ON u.id = t.user_id
{user_where}
""",
tuple(user_params)
)
user_total = cur.fetchone()['cnt']
{user_where_clause}
"""
cur.execute(count_sql, tuple(user_params))
count_result = cur.fetchone()
user_total = count_result['cnt'] if count_result else 0
# Get per-user aggregated stats
cur.execute(
f"""
# Important: Filter by user search criteria AFTER grouping, but we need to apply it in WHERE
# Since we're grouping by user fields, we need to filter before GROUP BY
stats_sql = f"""
SELECT
t.user_id,
u.username,
@@ -1372,13 +1369,12 @@ def get_admin_ai_stats():
MIN(t.created_at) AS first_analysis_at
FROM qd_analysis_tasks t
LEFT JOIN qd_users u ON u.id = t.user_id
{user_where}
{user_where_clause}
GROUP BY t.user_id, u.username, u.nickname, u.email
ORDER BY analysis_count DESC
LIMIT ? OFFSET ?
""",
tuple(user_params) + (page_size, offset)
)
"""
cur.execute(stats_sql, tuple(user_params) + (page_size, offset))
user_rows = cur.fetchall() or []
# Get per-user analysis_memory stats (correct/helpful counts)
@@ -1417,13 +1413,15 @@ def get_admin_ai_stats():
memory_stats_map = {}
# Get recent analysis records (last 50)
# Ensure we get user info even if user_id is NULL or user doesn't exist
cur.execute(
"""
SELECT
t.id,
t.user_id,
u.username,
u.nickname,
COALESCE(u.username, '') AS username,
COALESCE(u.nickname, '') AS nickname,
COALESCE(u.email, '') AS email,
t.market,
t.symbol,
t.model,
@@ -1432,6 +1430,7 @@ def get_admin_ai_stats():
t.completed_at
FROM qd_analysis_tasks t
LEFT JOIN qd_users u ON u.id = t.user_id
WHERE t.user_id IS NOT NULL
ORDER BY t.created_at DESC
LIMIT 50
"""
@@ -1444,26 +1443,40 @@ def get_admin_ai_stats():
user_items = []
for row in user_rows:
uid = row.get('user_id')
if not uid: # Skip rows with NULL user_id
continue
ms = memory_stats_map.get(uid, {})
last_at = row.get('last_analysis_at')
first_at = row.get('first_analysis_at')
if hasattr(last_at, 'isoformat'):
# Convert datetime to ISO format string if needed
if last_at and hasattr(last_at, 'isoformat'):
last_at = last_at.isoformat()
if hasattr(first_at, 'isoformat'):
elif last_at:
last_at = str(last_at)
else:
last_at = None
if first_at and hasattr(first_at, 'isoformat'):
first_at = first_at.isoformat()
elif first_at:
first_at = str(first_at)
else:
first_at = None
user_items.append({
'user_id': uid,
'username': row.get('username') or '',
'nickname': row.get('nickname') or '',
'email': row.get('email') or '',
'analysis_count': row.get('analysis_count') or 0,
'symbol_count': row.get('symbol_count') or 0,
'market_count': row.get('market_count') or 0,
'correct': ms.get('correct', 0),
'incorrect': ms.get('incorrect', 0),
'helpful': ms.get('helpful', 0),
'not_helpful': ms.get('not_helpful', 0),
'user_id': int(uid),
'username': str(row.get('username') or ''),
'nickname': str(row.get('nickname') or ''),
'email': str(row.get('email') or ''),
'analysis_count': int(row.get('analysis_count') or 0),
'symbol_count': int(row.get('symbol_count') or 0),
'market_count': int(row.get('market_count') or 0),
'correct': int(ms.get('correct', 0)),
'incorrect': int(ms.get('incorrect', 0)),
'helpful': int(ms.get('helpful', 0)),
'not_helpful': int(ms.get('not_helpful', 0)),
'last_analysis_at': last_at,
'first_analysis_at': first_at
})
@@ -1471,22 +1484,38 @@ def get_admin_ai_stats():
# Build recent records
recent_items = []
for row in recent_rows:
user_id = row.get('user_id')
if not user_id: # Skip rows with NULL user_id
continue
created_at = row.get('created_at')
completed_at = row.get('completed_at')
if hasattr(created_at, 'isoformat'):
# Convert datetime to ISO format string if needed
if created_at and hasattr(created_at, 'isoformat'):
created_at = created_at.isoformat()
if hasattr(completed_at, 'isoformat'):
elif created_at:
created_at = str(created_at)
else:
created_at = None
if completed_at and hasattr(completed_at, 'isoformat'):
completed_at = completed_at.isoformat()
elif completed_at:
completed_at = str(completed_at)
else:
completed_at = None
recent_items.append({
'id': row['id'],
'user_id': row.get('user_id'),
'username': row.get('username') or '',
'nickname': row.get('nickname') or '',
'market': row.get('market') or '',
'symbol': row.get('symbol') or '',
'model': row.get('model') or '',
'status': row.get('status') or '',
'id': int(row.get('id') or 0),
'user_id': int(user_id),
'username': str(row.get('username') or ''),
'nickname': str(row.get('nickname') or ''),
'email': str(row.get('email') or ''),
'market': str(row.get('market') or ''),
'symbol': str(row.get('symbol') or ''),
'model': str(row.get('model') or ''),
'status': str(row.get('status') or ''),
'created_at': created_at,
'completed_at': completed_at
})