ai generated solutions to our ai generated problems

This commit is contained in:
Heidi
2026-06-19 23:33:01 +01:00
parent 6745a2ff81
commit 9263d6f8eb
4 changed files with 278 additions and 1 deletions
+2
View File
@@ -15,11 +15,13 @@ It currently exposes:
- `GET /health`
- `GET /api/tss/leaderboard/teams?limit=100`
- `GET /api/tss/leaderboard/players?limit=100`
- `GET /api/tss/teams/resolve?name=...`
- `GET /api/tss/teams/search?q=...&limit=10`
- `GET /api/tss/teams/:team`
- `GET /api/tss/teams/:team/history`
- `GET /api/tss/teams/:team/games`
- `GET /api/tss/player/:uid`
## Local development
+130
View File
@@ -103,6 +103,11 @@ struct LeaderboardResponse {
teams: Vec<TeamLeaderboardRow>,
}
#[derive(Serialize)]
struct PlayerLeaderboardResponse {
players: Vec<PlayerLeaderboardRow>,
}
#[derive(Serialize)]
struct RecentGamesResponse {
matches: Vec<GameRow>,
@@ -138,6 +143,26 @@ struct TeamLeaderboardRow {
total_kills: i64,
}
#[derive(Serialize)]
struct PlayerLeaderboardRow {
uid: String,
nick: Option<String>,
total_battles: i64,
wins: i64,
losses: i64,
win_rate: f64,
ground_kills: i64,
air_kills: i64,
total_kills: i64,
assists: i64,
captures: i64,
deaths: i64,
score: i64,
kdr: f64,
teams_seen: i64,
last_seen: i64,
}
#[derive(Serialize)]
struct TeamDetail {
team_id: i64,
@@ -323,6 +348,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = Router::new()
.route("/health", get(health))
.route("/api/tss/leaderboard/teams", get(leaderboard))
.route("/api/tss/leaderboard/players", get(player_leaderboard))
.route("/api/tss/games/recent", get(recent_games))
.route("/api/tss/games/{session_id}", get(game_detail))
.route("/api/tss/teams/resolve", get(resolve_team))
@@ -413,6 +439,16 @@ async fn leaderboard(
Ok(Json(LeaderboardResponse { teams }))
}
async fn player_leaderboard(
State(state): State<Arc<AppState>>,
Query(query): Query<LimitQuery>,
) -> ApiResult<PlayerLeaderboardResponse> {
let limit = i64::from(query.limit.unwrap_or(100).clamp(1, 100));
let battles_conn = open_db(&state.battles_db)?;
let players = player_leaderboard_rows(&battles_conn, limit)?;
Ok(Json(PlayerLeaderboardResponse { players }))
}
fn leaderboard_teams(state: &AppState, limit: usize) -> Result<Vec<TeamRecord>, ApiError> {
let teams_conn = open_db(&state.teams_db)?;
// Deduplicate teams by name across tournaments — pick the highest team_id
@@ -951,6 +987,100 @@ fn player_summaries_for(
Ok(out)
}
fn player_leaderboard_rows(
conn: &Connection,
limit: i64,
) -> Result<Vec<PlayerLeaderboardRow>, ApiError> {
let mut stmt = conn
.prepare(
"WITH per_session AS (
SELECT
UID,
session_id,
MAX(victor_bool) AS victor_bool,
MAX(ground_kills) AS ground_kills,
MAX(air_kills) AS air_kills,
MAX(assists) AS assists,
MAX(captures) AS captures,
MAX(deaths) AS deaths,
MAX(score) AS score,
MAX(endtime_unix) AS endtime_unix,
MAX(team_name) AS team_name
FROM player_games_hist
WHERE UID IS NOT NULL AND UID != ''
GROUP BY UID, session_id
),
latest_names AS (
SELECT UID, nick
FROM (
SELECT
UID,
nick,
ROW_NUMBER() OVER (
PARTITION BY UID
ORDER BY endtime_unix DESC, nick COLLATE NOCASE ASC
) AS rn
FROM player_games_hist
WHERE nick IS NOT NULL AND nick != ''
)
WHERE rn = 1
)
SELECT
p.UID,
n.nick,
COUNT(*) AS battles,
COALESCE(SUM(CASE WHEN UPPER(p.victor_bool) = 'WIN' THEN 1 ELSE 0 END), 0) AS wins,
COALESCE(SUM(CASE WHEN UPPER(p.victor_bool) = 'LOSS' THEN 1 ELSE 0 END), 0) AS losses,
COALESCE(SUM(p.ground_kills), 0) AS ground_kills,
COALESCE(SUM(p.air_kills), 0) AS air_kills,
COALESCE(SUM(p.assists), 0) AS assists,
COALESCE(SUM(p.captures), 0) AS captures,
COALESCE(SUM(p.deaths), 0) AS deaths,
COALESCE(SUM(p.score), 0) AS score,
COUNT(DISTINCT p.team_name) AS teams_seen,
MAX(p.endtime_unix) AS last_seen
FROM per_session p
LEFT JOIN latest_names n ON n.UID = p.UID
GROUP BY p.UID
ORDER BY score DESC, (ground_kills + air_kills) DESC, battles DESC, p.UID ASC
LIMIT ?1",
)
.map_err(db_error)?;
let players = stmt
.query_map(params![limit], |row| {
let ground: i64 = row.get(5)?;
let air: i64 = row.get(6)?;
let battles: i64 = row.get(2)?;
let wins: i64 = row.get(3)?;
let deaths: i64 = row.get(9)?;
let total_kills = ground + air;
Ok(PlayerLeaderboardRow {
uid: row.get(0)?,
nick: row.get(1)?,
total_battles: battles,
wins,
losses: row.get(4)?,
win_rate: percent(wins, battles),
ground_kills: ground,
air_kills: air,
total_kills,
assists: row.get(7)?,
captures: row.get(8)?,
deaths,
score: row.get(10)?,
kdr: ratio(total_kills, deaths),
teams_seen: row.get(11)?,
last_seen: row.get(12)?,
})
})
.map_err(db_error)?
.collect::<Result<Vec<_>, _>>()
.map_err(db_error)?;
Ok(players)
}
fn player_search(conn: &Connection, query: &str, limit: i64) -> Result<Vec<PlayerRef>, ApiError> {
let like = format!("%{}%", escape_like(query));
let mut stmt = conn