diff --git a/hasura/metadata/databases/default/tables/public_player_season_performance_v.yaml b/hasura/metadata/databases/default/tables/public_player_season_performance_v.yaml new file mode 100644 index 00000000..8513e213 --- /dev/null +++ b/hasura/metadata/databases/default/tables/public_player_season_performance_v.yaml @@ -0,0 +1,39 @@ +table: + name: player_season_performance_v + schema: public +object_relationships: + - name: season + using: + manual_configuration: + column_mapping: + season_id: id + insertion_order: null + remote_table: + name: seasons + schema: public +select_permissions: + - role: guest + permission: + columns: + - steam_id + - season_id + - season_starts_at + - rounds + - aim_rating + - positioning_rating + - utility_rating + - accuracy_score + - hs_score + - spotted_score + - crosshair_score + - ttd_score + - counter_strafe_score + - survival_score + - traded_score + - kast_score + - flash_assists_score + - blind_score + - util_eff_score + filter: {} + allow_aggregations: true + comment: "" diff --git a/hasura/metadata/databases/default/tables/public_v_player_weapon_match_damage.yaml b/hasura/metadata/databases/default/tables/public_v_player_weapon_match_damage.yaml new file mode 100644 index 00000000..eabe1e27 --- /dev/null +++ b/hasura/metadata/databases/default/tables/public_v_player_weapon_match_damage.yaml @@ -0,0 +1,25 @@ +table: + name: v_player_weapon_match_damage + schema: public +object_relationships: + - name: match + using: + manual_configuration: + column_mapping: + match_id: id + insertion_order: null + remote_table: + name: matches + schema: public +select_permissions: + - role: guest + permission: + columns: + - player_steam_id + - match_id + - with + - damage + - hits + filter: {} + allow_aggregations: true + comment: "" diff --git a/hasura/metadata/databases/default/tables/public_v_player_weapon_match_kills.yaml b/hasura/metadata/databases/default/tables/public_v_player_weapon_match_kills.yaml new file mode 100644 index 00000000..284e6a47 --- /dev/null +++ b/hasura/metadata/databases/default/tables/public_v_player_weapon_match_kills.yaml @@ -0,0 +1,25 @@ +table: + name: v_player_weapon_match_kills + schema: public +object_relationships: + - name: match + using: + manual_configuration: + column_mapping: + match_id: id + insertion_order: null + remote_table: + name: matches + schema: public +select_permissions: + - role: guest + permission: + columns: + - player_steam_id + - match_id + - with + - kill_count + - rounds + filter: {} + allow_aggregations: true + comment: "" diff --git a/hasura/metadata/databases/default/tables/tables.yaml b/hasura/metadata/databases/default/tables/tables.yaml index b96962dc..0e7079a4 100644 --- a/hasura/metadata/databases/default/tables/tables.yaml +++ b/hasura/metadata/databases/default/tables/tables.yaml @@ -166,6 +166,7 @@ - "!include public_player_objectives.yaml" - "!include public_player_premier_rank_history.yaml" - "!include public_player_sanctions.yaml" +- "!include public_player_season_performance_v.yaml" - "!include public_player_season_stats.yaml" - "!include public_player_stats.yaml" - "!include public_player_steam_bot_friend.yaml" @@ -244,6 +245,8 @@ - "!include public_v_player_multi_kills.yaml" - "!include public_v_player_weapon_damage.yaml" - "!include public_v_player_weapon_kills.yaml" +- "!include public_v_player_weapon_match_damage.yaml" +- "!include public_v_player_weapon_match_kills.yaml" - "!include public_v_pool_maps.yaml" - "!include public_v_role_permissions.yaml" - "!include public_v_steam_account_pool_status.yaml" diff --git a/hasura/views/v_player_perf_season.sql b/hasura/views/v_player_perf_season.sql new file mode 100644 index 00000000..a2de6aca --- /dev/null +++ b/hasura/views/v_player_perf_season.sql @@ -0,0 +1,88 @@ +-- Season split of player_career_stats_v + player_performance_v: the same 0-100 +-- sub-scores, each a cume_dist percentile within that season's pool of players +-- with >= 30 rounds in it. A match belongs to the season its end time falls in. +DROP VIEW IF EXISTS public.player_season_performance_v; +CREATE VIEW public.player_season_performance_v AS +WITH base AS ( + SELECT + s.steam_id, + se.id AS season_id, + se.starts_at AS season_starts_at, + SUM(s.rounds_played)::int AS rounds, + SUM(s.deaths)::int AS deaths, + SUM(s.hits)::int AS hits, + SUM(s.shots_fired)::int AS shots_fired, + SUM(s.headshot_hits)::int AS headshot_hits, + SUM(s.traded_death_successes)::int AS traded_death_successes, + SUM(s.traded_death_opportunities)::int AS traded_death_opportunities, + SUM(s.flash_assists)::int AS flash_assists, + SUM(s.flash_duration_sum)::numeric AS enemy_blind_duration, + SUM(s.he_damage + s.molotov_damage)::int AS util_damage, + SUM(s.he_throws + s.molotov_throws)::int AS util_thrown, + SUM(s.hits_at_spotted)::int AS hits_at_spotted, + SUM(s.shots_at_spotted)::int AS shots_at_spotted, + SUM(s.crosshair_angle_sum_deg)::numeric AS crosshair_angle_sum_deg, + SUM(s.crosshair_angle_count)::int AS crosshair_angle_count, + SUM(s.time_to_damage_sum_s)::numeric AS time_to_damage_sum_s, + SUM(s.time_to_damage_count)::int AS time_to_damage_count, + SUM(s.counter_strafed_shots)::int AS counter_strafed_shots, + SUM(s.counter_strafe_eligible_shots)::int AS counter_strafe_eligible_shots, + SUM(s.kast_rounds)::int AS kast_rounds, + SUM(s.kast_total_rounds)::int AS kast_total_rounds + FROM public.player_match_map_stats s + JOIN public.matches m ON m.id = s.match_id + JOIN public.seasons se + ON m.ended_at >= se.starts_at + AND (se.ends_at IS NULL OR m.ended_at < se.ends_at) + GROUP BY s.steam_id, se.id, se.starts_at +), +pool AS ( + SELECT + b.steam_id, b.season_id, b.season_starts_at, b.rounds, + -- Aim + CASE WHEN b.shots_fired > 0 THEN 100.0 * b.hits / b.shots_fired END AS accuracy, + CASE WHEN b.hits > 0 THEN 100.0 * b.headshot_hits / b.hits END AS hs_pct, + CASE WHEN b.shots_at_spotted > 0 THEN 100.0 * b.hits_at_spotted / b.shots_at_spotted END AS accuracy_spotted, + CASE WHEN b.crosshair_angle_count > 0 THEN b.crosshair_angle_sum_deg / b.crosshair_angle_count END AS crosshair_deg, + CASE WHEN b.time_to_damage_count > 0 THEN b.time_to_damage_sum_s / b.time_to_damage_count END AS time_to_damage_s, + CASE WHEN b.counter_strafe_eligible_shots > 0 THEN 100.0 * b.counter_strafed_shots / b.counter_strafe_eligible_shots END AS counter_strafe_pct, + -- Positioning + CASE WHEN b.rounds > 0 THEN 100.0 * (1.0 - b.deaths::numeric / b.rounds) END AS survival_pct, + CASE WHEN b.traded_death_opportunities > 0 THEN 100.0 * b.traded_death_successes / b.traded_death_opportunities END AS traded_death_pct, + CASE WHEN b.kast_total_rounds > 0 THEN 100.0 * b.kast_rounds / b.kast_total_rounds END AS kast_pct, + -- Utility (quality, not volume) + CASE WHEN b.rounds > 0 THEN b.flash_assists::numeric / b.rounds END AS flash_assists_pr, + CASE WHEN b.rounds > 0 THEN b.enemy_blind_duration / b.rounds END AS enemy_blind_pr, + CASE WHEN b.util_thrown > 0 THEN b.util_damage::numeric / b.util_thrown END AS util_efficiency + FROM base b + WHERE b.rounds >= 30 +), +-- Partitioning on IS NULL ranks each metric only among the players who have +-- it, like the per-metric CTEs in player_performance_v. +scored AS ( + SELECT + p.steam_id, p.season_id, p.season_starts_at, p.rounds, + CASE WHEN p.accuracy IS NOT NULL THEN round(100.0 * cume_dist() OVER (PARTITION BY p.season_id, p.accuracy IS NULL ORDER BY p.accuracy)) END AS accuracy_score, + CASE WHEN p.hs_pct IS NOT NULL THEN round(100.0 * cume_dist() OVER (PARTITION BY p.season_id, p.hs_pct IS NULL ORDER BY p.hs_pct)) END AS hs_score, + CASE WHEN p.accuracy_spotted IS NOT NULL THEN round(100.0 * cume_dist() OVER (PARTITION BY p.season_id, p.accuracy_spotted IS NULL ORDER BY p.accuracy_spotted)) END AS spotted_score, + CASE WHEN p.crosshair_deg IS NOT NULL THEN round(100.0 * cume_dist() OVER (PARTITION BY p.season_id, p.crosshair_deg IS NULL ORDER BY p.crosshair_deg DESC)) END AS crosshair_score, + CASE WHEN p.time_to_damage_s IS NOT NULL THEN round(100.0 * cume_dist() OVER (PARTITION BY p.season_id, p.time_to_damage_s IS NULL ORDER BY p.time_to_damage_s DESC)) END AS ttd_score, + CASE WHEN p.counter_strafe_pct IS NOT NULL THEN round(100.0 * cume_dist() OVER (PARTITION BY p.season_id, p.counter_strafe_pct IS NULL ORDER BY p.counter_strafe_pct)) END AS counter_strafe_score, + CASE WHEN p.survival_pct IS NOT NULL THEN round(100.0 * cume_dist() OVER (PARTITION BY p.season_id, p.survival_pct IS NULL ORDER BY p.survival_pct)) END AS survival_score, + CASE WHEN p.traded_death_pct IS NOT NULL THEN round(100.0 * cume_dist() OVER (PARTITION BY p.season_id, p.traded_death_pct IS NULL ORDER BY p.traded_death_pct)) END AS traded_score, + CASE WHEN p.kast_pct IS NOT NULL THEN round(100.0 * cume_dist() OVER (PARTITION BY p.season_id, p.kast_pct IS NULL ORDER BY p.kast_pct)) END AS kast_score, + CASE WHEN p.flash_assists_pr IS NOT NULL THEN round(100.0 * cume_dist() OVER (PARTITION BY p.season_id, p.flash_assists_pr IS NULL ORDER BY p.flash_assists_pr)) END AS flash_assists_score, + CASE WHEN p.enemy_blind_pr IS NOT NULL THEN round(100.0 * cume_dist() OVER (PARTITION BY p.season_id, p.enemy_blind_pr IS NULL ORDER BY p.enemy_blind_pr)) END AS blind_score, + CASE WHEN p.util_efficiency IS NOT NULL THEN round(100.0 * cume_dist() OVER (PARTITION BY p.season_id, p.util_efficiency IS NULL ORDER BY p.util_efficiency)) END AS util_eff_score + FROM pool p +) +SELECT + s.steam_id, s.season_id, s.season_starts_at, s.rounds, + (SELECT round(avg(x)) FROM unnest(ARRAY[s.accuracy_score, s.hs_score, s.spotted_score, + s.crosshair_score, s.ttd_score, s.counter_strafe_score]) x) AS aim_rating, + (SELECT round(avg(x)) FROM unnest(ARRAY[s.survival_score, s.traded_score, s.kast_score]) x) AS positioning_rating, + (SELECT round(avg(x)) FROM unnest(ARRAY[s.flash_assists_score, s.blind_score, s.util_eff_score]) x) AS utility_rating, + s.accuracy_score, s.hs_score, s.spotted_score, s.crosshair_score, s.ttd_score, s.counter_strafe_score, + s.survival_score, s.traded_score, s.kast_score, + s.flash_assists_score, s.blind_score, s.util_eff_score +FROM scored s; diff --git a/hasura/views/v_player_weapon_match.sql b/hasura/views/v_player_weapon_match.sql new file mode 100644 index 00000000..56f1e16c --- /dev/null +++ b/hasura/views/v_player_weapon_match.sql @@ -0,0 +1,27 @@ +-- Per-(player, match, weapon) kills and damage, so weapon stats can follow a +-- date range or season through the `match` relationship. v_player_weapon_kills +-- and v_player_weapon_damage stay the career rollups. +CREATE OR REPLACE VIEW public.v_player_weapon_match_kills AS +SELECT + pk.attacker_steam_id AS player_steam_id, + pk.match_id, + pk."with" AS "with", + COUNT(*)::bigint AS kill_count, + COUNT(DISTINCT (pk.match_map_id, pk.round))::bigint AS rounds +FROM player_kills pk +WHERE pk.attacker_steam_id IS NOT NULL + AND pk."with" IS NOT NULL +GROUP BY pk.attacker_steam_id, pk.match_id, pk."with"; + +CREATE OR REPLACE VIEW public.v_player_weapon_match_damage AS +SELECT + pd.attacker_steam_id AS player_steam_id, + pd.match_id, + pd."with" AS "with", + SUM(pd.damage)::bigint AS damage, + COUNT(*)::bigint AS hits +FROM player_damages pd +WHERE pd.attacker_steam_id IS NOT NULL + AND pd."with" IS NOT NULL + AND pd.attacker_team <> pd.attacked_team +GROUP BY pd.attacker_steam_id, pd.match_id, pd."with";