Compare commits
7 Commits
8ed22bebf0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f6b4a211b8 | |||
| 4c8364328c | |||
| 5192978917 | |||
| 4dfae60875 | |||
| b7723c91df | |||
| 3993f97de1 | |||
| f0a7fc1303 |
@@ -11,7 +11,9 @@ defmodule LoLAnalytics.Application do
|
|||||||
LoLAnalytics.Repo,
|
LoLAnalytics.Repo,
|
||||||
{DNSCluster, query: Application.get_env(:lol_analytics, :dns_cluster_query) || :ignore},
|
{DNSCluster, query: Application.get_env(:lol_analytics, :dns_cluster_query) || :ignore},
|
||||||
{Phoenix.PubSub, name: LoLAnalytics.PubSub},
|
{Phoenix.PubSub, name: LoLAnalytics.PubSub},
|
||||||
{Task.Supervisor, name: LoLAnalytics.TaskSupervisor}
|
{Task.Supervisor, name: LoLAnalytics.TaskSupervisor},
|
||||||
|
# {LolAnalytics.MatchProcessor.MatchesBroadwayProcessor, []},
|
||||||
|
{LolAnalytics.MatchProcessor.MatchesProducer, []}
|
||||||
# Start a worker by calling: LoLAnalytics.Worker.start_link(arg)
|
# Start a worker by calling: LoLAnalytics.Worker.start_link(arg)
|
||||||
# {LoLAnalytics.Worker, arg}
|
# {LoLAnalytics.Worker, arg}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
defmodule LolAnalytics.ChampionWinRate.ChampionWinRateRepo do
|
|
||||||
import Ecto.Query
|
|
||||||
alias LolAnalytics.ChampionWinRate.ChampionWinRateSchema
|
|
||||||
alias LoLAnalytics.Repo
|
|
||||||
|
|
||||||
@spec add_champion_win_rate(
|
|
||||||
champion_id :: String.t(),
|
|
||||||
patch :: String.t(),
|
|
||||||
position :: String.t(),
|
|
||||||
win? :: boolean
|
|
||||||
) :: {:ok, ChampionWinRateSchema.t()} | {:error, Ecto.Changeset.t()}
|
|
||||||
def add_champion_win_rate(champion_id, patch, position, win?) do
|
|
||||||
Repo.transaction(fn ->
|
|
||||||
champion_query =
|
|
||||||
from cwr in LolAnalytics.ChampionWinRate.ChampionWinRateSchema,
|
|
||||||
where: cwr.champion_id == ^champion_id and cwr.position == ^position,
|
|
||||||
lock: "FOR UPDATE"
|
|
||||||
|
|
||||||
champion_data = Repo.one(champion_query)
|
|
||||||
|
|
||||||
case champion_data do
|
|
||||||
nil ->
|
|
||||||
ChampionWinRateSchema.changeset(%ChampionWinRateSchema{}, %{
|
|
||||||
champion_id: champion_id,
|
|
||||||
patch: patch,
|
|
||||||
total_games: 1,
|
|
||||||
position: position,
|
|
||||||
total_wins: if(win?, do: 1, else: 0)
|
|
||||||
})
|
|
||||||
|> Repo.insert!()
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
total_games = champion_data.total_games + 1
|
|
||||||
total_wins = champion_data.total_wins + if win?, do: 1, else: 0
|
|
||||||
|
|
||||||
ChampionWinRateSchema.changeset(champion_data, %{
|
|
||||||
total_games: total_games,
|
|
||||||
total_wins: total_wins
|
|
||||||
})
|
|
||||||
|> Repo.update!()
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def list_win_rates() do
|
|
||||||
Repo.all(ChampionWinRateSchema)
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_champion_win_rate(champion_id, _patch) do
|
|
||||||
champion_query =
|
|
||||||
from cwr in LolAnalytics.ChampionWinRate.ChampionWinRateSchema,
|
|
||||||
where: cwr.champion_id == ^champion_id
|
|
||||||
|
|
||||||
Repo.one(champion_query)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
defmodule LolAnalytics.ChampionWinRate.ChampionWinRateSchema do
|
|
||||||
use Ecto.Schema
|
|
||||||
import Ecto.Changeset
|
|
||||||
|
|
||||||
schema "champion_win_rate" do
|
|
||||||
field :champion_id, :integer
|
|
||||||
field :total_games, :integer
|
|
||||||
field :patch, :string
|
|
||||||
field :position, :string
|
|
||||||
field :total_wins, :integer
|
|
||||||
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
def changeset(%__MODULE__{} = champion_win_rate, attrs) do
|
|
||||||
champion_win_rate
|
|
||||||
|> cast(attrs, [:champion_id, :total_games, :patch, :total_wins, :position])
|
|
||||||
|> validate_required([:champion_id, :total_games, :patch, :total_wins, :position])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -61,10 +61,25 @@ defmodule LolAnalytics.Dimensions.Match.MatchRepo do
|
|||||||
Repo.all(MatchSchema)
|
Repo.all(MatchSchema)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def list_unprocessed_matches(limit, queue \\ 420) do
|
||||||
|
query =
|
||||||
|
from m in MatchSchema,
|
||||||
|
where:
|
||||||
|
(m.fact_champion_picked_item_status == 0 or
|
||||||
|
m.fact_champion_picked_summoner_spell_status == 0 or
|
||||||
|
m.fact_champion_played_game_status == 0) and
|
||||||
|
m.queue_id == ^queue,
|
||||||
|
order_by: [desc: m.updated_at],
|
||||||
|
limit: ^limit
|
||||||
|
|
||||||
|
Repo.all(query)
|
||||||
|
end
|
||||||
|
|
||||||
@type process_status :: :not_processed | :processed | :error
|
@type process_status :: :not_processed | :processed | :error
|
||||||
defp process_status_atom_to_db(:not_processed), do: 0
|
defp process_status_atom_to_db(:not_processed), do: 0
|
||||||
defp process_status_atom_to_db(:enqueued), do: 1
|
defp process_status_atom_to_db(:enqueued), do: 1
|
||||||
defp process_status_atom_to_db(:processed), do: 2
|
defp process_status_atom_to_db(:processed), do: 2
|
||||||
defp process_status_atom_to_db(:error), do: 3
|
defp process_status_atom_to_db(:error), do: 3
|
||||||
|
defp process_status_atom_to_db(:error_match_not_found), do: 4
|
||||||
defp process_status_atom_to_db(_), do: raise("Invalid processing status")
|
defp process_status_atom_to_db(_), do: raise("Invalid processing status")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,14 +2,20 @@ defmodule LolAnalytics.Dimensions.Player.PlayerSchema do
|
|||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@attrs [:puuid, :last_processed_at]
|
||||||
|
|
||||||
schema "dim_player" do
|
schema "dim_player" do
|
||||||
field :puuid, :string
|
field :puuid, :string
|
||||||
|
|
||||||
|
field :last_processed_at, :utc_datetime,
|
||||||
|
default: DateTime.utc_now() |> DateTime.truncate(:second)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(player = %__MODULE__{}, attrs \\ %{}) do
|
def changeset(player = %__MODULE__{}, attrs \\ %{}) do
|
||||||
player
|
player
|
||||||
|> cast(attrs, [:puuid])
|
|> cast(attrs, @attrs)
|
||||||
|> validate_required([:puuid])
|
|> validate_required([:puuid])
|
||||||
|> unique_constraint([:puuid])
|
|> unique_constraint([:puuid])
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
defmodule LolAnalytics.Facts.ChampionPickedItem.FactProcessor do
|
defmodule LolAnalytics.Facts.ChampionPickedItem.FactProcessor do
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
alias LolAnalytics.Dimensions.Match.MatchSchema
|
||||||
alias LolAnalytics.Dimensions.Match.MatchRepo
|
alias LolAnalytics.Dimensions.Match.MatchRepo
|
||||||
alias LolAnalytics.Facts.ChampionPickedItem.Repo
|
alias LolAnalytics.Facts.ChampionPickedItem.Repo
|
||||||
require Logger
|
|
||||||
@behaviour LolAnalytics.Facts.FactBehaviour
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
||||||
@@ -21,6 +22,24 @@ defmodule LolAnalytics.Facts.ChampionPickedItem.FactProcessor do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec process_match(%MatchSchema{}) :: :ok | {:error, String.t()}
|
||||||
|
def process_match(match) do
|
||||||
|
match_url = "http://192.168.1.55:9000/ranked/#{match.patch_number}/#{match.match_id}.json"
|
||||||
|
|
||||||
|
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
|
||||||
|
HTTPoison.get(match_url),
|
||||||
|
{:ok, decoded_match} <- Poison.decode(body, as: %LoLAPI.Model.MatchResponse{}) do
|
||||||
|
process_game_data(decoded_match)
|
||||||
|
MatchRepo.update(match, %{fact_champion_picked_item_status: :processed})
|
||||||
|
:ok
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
MatchRepo.update(match, fact_champion_picked_item_status: :error_match_not_found)
|
||||||
|
Logger.error("Could not process data from #{match_url} for ChampionPickedItem")
|
||||||
|
{:error, "Could not process data from #{match_url}"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp process_game_data(decoded_match) do
|
defp process_game_data(decoded_match) do
|
||||||
participants = decoded_match.info.participants
|
participants = decoded_match.info.participants
|
||||||
version = extract_game_version(decoded_match)
|
version = extract_game_version(decoded_match)
|
||||||
@@ -57,8 +76,6 @@ defmodule LolAnalytics.Facts.ChampionPickedItem.FactProcessor do
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
MatchRepo.update(match, %{fact_champion_picked_item_status: :processed})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp extract_game_version(game_data) do
|
defp extract_game_version(game_data) do
|
||||||
|
|||||||
@@ -36,13 +36,6 @@ defmodule LolAnalytics.Facts.ChampionPickedItem.Repo do
|
|||||||
_patch = PatchRepo.get_or_create(attrs.patch_number)
|
_patch = PatchRepo.get_or_create(attrs.patch_number)
|
||||||
_item_id = ItemRepo.get_or_create(attrs.item_id)
|
_item_id = ItemRepo.get_or_create(attrs.item_id)
|
||||||
|
|
||||||
_match =
|
|
||||||
MatchRepo.get_or_create(%{
|
|
||||||
match_id: attrs.match_id,
|
|
||||||
patch_number: attrs.patch_number,
|
|
||||||
queue_id: attrs.queue_id
|
|
||||||
})
|
|
||||||
|
|
||||||
prev =
|
prev =
|
||||||
from(f in Schema,
|
from(f in Schema,
|
||||||
where:
|
where:
|
||||||
|
|||||||
+11
-10
@@ -1,22 +1,25 @@
|
|||||||
defmodule LolAnalytics.Facts.ChampionPickedSummonerSpell.FactProcessor do
|
defmodule LolAnalytics.Facts.ChampionPickedSummonerSpell.FactProcessor do
|
||||||
@behaviour LolAnalytics.Facts.FactBehaviour
|
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
alias LolAnalytics.Dimensions.Match.MatchSchema
|
||||||
alias LolAnalytics.Dimensions.Match.MatchRepo
|
alias LolAnalytics.Dimensions.Match.MatchRepo
|
||||||
alias LolAnalytics.Facts.ChampionPickedSummonerSpell
|
alias LolAnalytics.Facts.ChampionPickedSummonerSpell
|
||||||
|
|
||||||
@impl true
|
@spec process_match(%MatchSchema{}) :: :ok | {:error, String.t()}
|
||||||
@spec process_game_at_url(String.t()) :: any()
|
def process_match(match) do
|
||||||
def process_game_at_url(url) do
|
match_url = "http://192.168.1.55:9000/ranked/#{match.patch_number}/#{match.match_id}.json"
|
||||||
|
|
||||||
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
|
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
|
||||||
HTTPoison.get(url),
|
HTTPoison.get(match_url),
|
||||||
{:ok, decoded_match} <- Poison.decode(body, as: %LoLAPI.Model.MatchResponse{}) do
|
{:ok, decoded_match} <- Poison.decode(body, as: %LoLAPI.Model.MatchResponse{}) do
|
||||||
process_game_data(decoded_match)
|
process_game_data(decoded_match)
|
||||||
|
MatchRepo.update(match, %{fact_champion_picked_summoner_spell_status: :processed})
|
||||||
|
:ok
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
Logger.error("Could not process data from #{url} for ChampionPickedSummonerSpell")
|
MatchRepo.update(match, fact_champion_picked_summoner_spell_status: :error_match_not_found)
|
||||||
{:error, "Could not process data from #{url}"}
|
Logger.error("Could not process data from #{match_url} for ChampionPickedItem")
|
||||||
|
{:error, "Could not process data from #{match_url}"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -64,8 +67,6 @@ defmodule LolAnalytics.Facts.ChampionPickedSummonerSpell.FactProcessor do
|
|||||||
ChampionPickedSummonerSpell.Repo.insert(attrs_spell_2)
|
ChampionPickedSummonerSpell.Repo.insert(attrs_spell_2)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
MatchRepo.update(match, %{fact_champion_picked_summoner_spell_status: :processed})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp extract_game_version(game_data) do
|
defp extract_game_version(game_data) do
|
||||||
|
|||||||
@@ -27,13 +27,6 @@ defmodule LolAnalytics.Facts.ChampionPickedSummonerSpell.Repo do
|
|||||||
_spell = SummonerSpellRepo.get_or_create(attrs.summoner_spell_id)
|
_spell = SummonerSpellRepo.get_or_create(attrs.summoner_spell_id)
|
||||||
_patch = PatchRepo.get_or_create(attrs.patch_number)
|
_patch = PatchRepo.get_or_create(attrs.patch_number)
|
||||||
|
|
||||||
_match =
|
|
||||||
MatchRepo.get_or_create(%{
|
|
||||||
match_id: attrs.match_id,
|
|
||||||
patch_number: attrs.patch_number,
|
|
||||||
queue_id: attrs.queue_id
|
|
||||||
})
|
|
||||||
|
|
||||||
prev =
|
prev =
|
||||||
from(f in Schema,
|
from(f in Schema,
|
||||||
where:
|
where:
|
||||||
|
|||||||
-8
@@ -5,7 +5,6 @@ defmodule LolAnalytics.Facts.ChampionPlayedGame.Repo do
|
|||||||
alias LolAnalytics.Dimensions.Champion.ChampionSchema
|
alias LolAnalytics.Dimensions.Champion.ChampionSchema
|
||||||
alias LolAnalytics.Dimensions.Player.PlayerRepo
|
alias LolAnalytics.Dimensions.Player.PlayerRepo
|
||||||
alias LolAnalytics.Dimensions.Champion.ChampionRepo
|
alias LolAnalytics.Dimensions.Champion.ChampionRepo
|
||||||
alias LolAnalytics.Dimensions.Match.MatchRepo
|
|
||||||
alias LolAnalytics.Facts.ChampionPlayedGame.Schema
|
alias LolAnalytics.Facts.ChampionPlayedGame.Schema
|
||||||
alias LoLAnalytics.Repo
|
alias LoLAnalytics.Repo
|
||||||
|
|
||||||
@@ -14,13 +13,6 @@ defmodule LolAnalytics.Facts.ChampionPlayedGame.Repo do
|
|||||||
_player = PlayerRepo.get_or_create(attrs.puuid)
|
_player = PlayerRepo.get_or_create(attrs.puuid)
|
||||||
_patch = PatchRepo.get_or_create(attrs.patch_number)
|
_patch = PatchRepo.get_or_create(attrs.patch_number)
|
||||||
|
|
||||||
_match =
|
|
||||||
MatchRepo.get_or_create(%{
|
|
||||||
match_id: attrs.match_id,
|
|
||||||
patch_number: attrs.patch_number,
|
|
||||||
queue_id: attrs.queue_id
|
|
||||||
})
|
|
||||||
|
|
||||||
prev =
|
prev =
|
||||||
from(f in Schema,
|
from(f in Schema,
|
||||||
where:
|
where:
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
defmodule LolAnalytics.Facts.ChampionPlayedGame.FactProcessor do
|
defmodule LolAnalytics.Facts.ChampionPlayedGame.FactProcessor do
|
||||||
alias LolAnalytics.Dimensions.Match.MatchRepo
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@behaviour LolAnalytics.Facts.FactBehaviour
|
alias LolAnalytics.Dimensions.Match.MatchSchema
|
||||||
|
alias LolAnalytics.Dimensions.Match.MatchRepo
|
||||||
|
|
||||||
|
@spec process_match(%MatchSchema{}) :: :ok | {:error, String.t()}
|
||||||
|
def process_match(match) do
|
||||||
|
match_url = "http://192.168.1.55:9000/ranked/#{match.patch_number}/#{match.match_id}.json"
|
||||||
|
|
||||||
@impl true
|
|
||||||
@spec process_game_at_url(String.t()) :: none()
|
|
||||||
def process_game_at_url(url) do
|
|
||||||
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
|
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
|
||||||
HTTPoison.get(url),
|
HTTPoison.get(match_url),
|
||||||
{:ok, decoded_match} <- Poison.decode(body, as: %LoLAPI.Model.MatchResponse{}) do
|
{:ok, decoded_match} <- Poison.decode(body, as: %LoLAPI.Model.MatchResponse{}) do
|
||||||
process_game_data(decoded_match)
|
process_game_data(decoded_match)
|
||||||
|
MatchRepo.update(match, %{fact_champion_played_game_status: :processed})
|
||||||
|
:ok
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
Logger.error("Could not process data from #{url} for ChampionPlayedGame")
|
MatchRepo.update(match, fact_champion_played_game_status: :error_match_not_found)
|
||||||
{:error, "Could not process data from #{url}"}
|
Logger.error("Could not process data from #{match_url} for ChampionPickedItem")
|
||||||
|
{:error, "Could not process data from #{match_url}"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -48,8 +52,6 @@ defmodule LolAnalytics.Facts.ChampionPlayedGame.FactProcessor do
|
|||||||
LolAnalytics.Facts.ChampionPlayedGame.Repo.insert(attrs)
|
LolAnalytics.Facts.ChampionPlayedGame.Repo.insert(attrs)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
MatchRepo.update(match, %{fact_champion_played_game_status: :processed})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp extract_game_version(game_data) do
|
defp extract_game_version(game_data) do
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
defmodule LolAnalytics.Facts.FactBehaviour do
|
|
||||||
@callback process_game_at_url(String.t()) :: any()
|
|
||||||
end
|
|
||||||
@@ -1,34 +1,18 @@
|
|||||||
defmodule LolAnalytics.Facts.FactsRunner do
|
defmodule LolAnalytics.Facts.FactsRunner do
|
||||||
alias LolAnalytics.Facts
|
alias LolAnalytics.Facts
|
||||||
|
|
||||||
def analyze_by_patch(patch) do
|
def analyze_match(match) do
|
||||||
Storage.MatchStorage.S3MatchStorage.stream_files("ranked", patch: patch)
|
get_facts()
|
||||||
|> peach(fn %{key: path} ->
|
|> Enum.each(fn fact_runner ->
|
||||||
get_facts()
|
apply(fact_runner, [match])
|
||||||
|> Enum.each(fn fact_runner ->
|
|
||||||
apply(fact_runner, ["http://192.168.1.55:9000/ranked/#{path}"])
|
|
||||||
end)
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def analyze_all_matches do
|
|
||||||
Storage.MatchStorage.S3MatchStorage.stream_files("ranked")
|
|
||||||
|> peach(fn %{key: path} ->
|
|
||||||
get_facts()
|
|
||||||
|> Enum.each(fn fact_runner ->
|
|
||||||
apply(fact_runner, ["http://192.168.1.55:9000/ranked/#{path}"])
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def analyze_match() do
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_facts() do
|
def get_facts() do
|
||||||
[
|
[
|
||||||
&Facts.ChampionPickedSummonerSpell.FactProcessor.process_game_at_url/1,
|
&Facts.ChampionPickedSummonerSpell.FactProcessor.process_match/1,
|
||||||
&Facts.ChampionPlayedGame.FactProcessor.process_game_at_url/1,
|
&Facts.ChampionPlayedGame.FactProcessor.process_match/1,
|
||||||
&Facts.ChampionPickedItem.FactProcessor.process_game_at_url/1
|
&Facts.ChampionPickedItem.FactProcessor.process_match/1
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
defmodule LolAnalytics.MatchProcessor.MatchesBroadwayProcessor do
|
||||||
|
alias LolAnalytics.Facts.FactsRunner
|
||||||
|
use Broadway
|
||||||
|
|
||||||
|
def start_link(opts) do
|
||||||
|
Broadway.start_link(__MODULE__,
|
||||||
|
name: __MODULE__,
|
||||||
|
processors: [default: []],
|
||||||
|
producer: [
|
||||||
|
module: {LolAnalytics.MatchProcessor.MatchesProducer, []},
|
||||||
|
rate_limiting: [
|
||||||
|
interval: 1000,
|
||||||
|
allowed_messages: 40
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Broadway
|
||||||
|
def handle_message(_processor, message, _context) do
|
||||||
|
message.data
|
||||||
|
# build_match_url(message.data.queue_id, message.data.patch_number, message.data.match_id)
|
||||||
|
|> FactsRunner.analyze_match()
|
||||||
|
|
||||||
|
message
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_match_url(queue, patch_id, match_id) do
|
||||||
|
"http://192.168.1.55:9000/#{queue_to_dir(queue)}/#{patch_id}/#{match_id}.json"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp queue_to_dir(420), do: "ranked"
|
||||||
|
defp queue_to_dir(_), do: "ranked"
|
||||||
|
end
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
defmodule LolAnalytics.MatchProcessor.MatchesProducer do
|
||||||
|
use GenStage
|
||||||
|
|
||||||
|
@impl GenStage
|
||||||
|
def init(opts) do
|
||||||
|
{:producer, opts}
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_link(opts) do
|
||||||
|
GenStage.start_link(__MODULE__, :ok)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl GenStage
|
||||||
|
def handle_demand(demand, state) do
|
||||||
|
matches = query_unprocessed_matches(demand)
|
||||||
|
|
||||||
|
{:noreply, matches, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp query_unprocessed_matches(demand) when demand <= 0, do: []
|
||||||
|
|
||||||
|
defp query_unprocessed_matches(demand) do
|
||||||
|
LolAnalytics.Dimensions.Match.MatchRepo.list_unprocessed_matches(demand)
|
||||||
|
|> Enum.map(&broadway_transform/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp broadway_transform(match) do
|
||||||
|
%Broadway.Message{
|
||||||
|
data: match,
|
||||||
|
acknowledger: Broadway.NoopAcknowledger.init()
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
defmodule LolAnalytics.MatchesProcessor do
|
|
||||||
use GenServer
|
|
||||||
|
|
||||||
def init(init_args) do
|
|
||||||
{:ok, init_args}
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
iex> LolAnalytics.MatchesProcessor.process_for_patch "14.12.593.5894"
|
|
||||||
"""
|
|
||||||
def process_for_patch(patch) do
|
|
||||||
Task.Supervisor.async(LoLAnalytics.TaskSupervisor, fn ->
|
|
||||||
LolAnalytics.Facts.FactsRunner.analyze_by_patch(patch)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_all_matches() do
|
|
||||||
Task.Supervisor.async(LoLAnalytics.TaskSupervisor, fn ->
|
|
||||||
LolAnalytics.Facts.FactsRunner.analyze_all_matches()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_running_processes() do
|
|
||||||
Task.Supervisor.children(LoLAnalytics.TaskSupervisor)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -44,7 +44,9 @@ defmodule LoLAnalytics.MixProject do
|
|||||||
{:lol_api, in_umbrella: true},
|
{:lol_api, in_umbrella: true},
|
||||||
{:storage, in_umbrella: true},
|
{:storage, in_umbrella: true},
|
||||||
{:httpoison, "~> 2.2"},
|
{:httpoison, "~> 2.2"},
|
||||||
{:poison, "~> 5.0"}
|
{:poison, "~> 5.0"},
|
||||||
|
{:gen_stage, "~> 1.2.1"},
|
||||||
|
{:broadway, "~> 1.1"}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
defmodule LoLAnalytics.Repo.Migrations.DimPlayerLastProcessed do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table "dim_player" do
|
||||||
|
add :last_processed_at, :utc_datetime
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -3,10 +3,18 @@ import Chart from "chart.js/auto"
|
|||||||
const ChampionWinRate = {
|
const ChampionWinRate = {
|
||||||
mounted() {
|
mounted() {
|
||||||
this.handleEvent("win-rate", ({ winRates }) => {
|
this.handleEvent("win-rate", ({ winRates }) => {
|
||||||
this.patches = winRates.map((winRate) => {
|
this.sortedWinRates = winRates.sort((a, b) => {
|
||||||
|
let [p1Major, p1Minor] = a.patch_number.split(".").map(Number);
|
||||||
|
let [p2Major, p2Minor] = b.patch_number.split(".").map(Number);
|
||||||
|
|
||||||
|
|
||||||
|
if (p1Major > p2Major || (p1Major == p2Major && p1Minor > p2Minor)) return 1;
|
||||||
|
return -1
|
||||||
|
})
|
||||||
|
this.patches = this.sortedWinRates.map((winRate) => {
|
||||||
return winRate.patch_number
|
return winRate.patch_number
|
||||||
})
|
})
|
||||||
this.winRateValues = winRates.map((winRate) => winRate.win_rate)
|
this.winRateValues = this.sortedWinRates.map((winRate) => winRate.win_rate)
|
||||||
// TODO: it breaks on liveview updates, should apply a better fix...
|
// TODO: it breaks on liveview updates, should apply a better fix...
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const data = {
|
const data = {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ defmodule LolAnalyticsWeb.PatchSelector do
|
|||||||
phx-change="selected_patch"
|
phx-change="selected_patch"
|
||||||
id="patch"
|
id="patch"
|
||||||
name="patch"
|
name="patch"
|
||||||
class="patch-selector block w-full rounded-md border border-gray-300 bg-white shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm"
|
class="patch-selector cursor-pointer block w-full rounded-md border border-gray-300 bg-white shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm"
|
||||||
>
|
>
|
||||||
<%= for patch <- @patch_numbers do %>
|
<%= for patch <- @patch_numbers do %>
|
||||||
<option key={patch} phx-click="select-patch" name={patch} value={patch}>
|
<option key={patch} phx-click="select-patch" name={patch} value={patch}>
|
||||||
|
|||||||
-1
@@ -22,7 +22,6 @@ defmodule LolAnalyticsWeb.ChampionLive.Components.ChampionFilters do
|
|||||||
end
|
end
|
||||||
|
|
||||||
attr :selectedrole, :string, required: true
|
attr :selectedrole, :string, required: true
|
||||||
attr :roles, :list, default: []
|
|
||||||
|
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
selected_class =
|
selected_class =
|
||||||
|
|||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
defmodule LolAnalyticsWeb.ChampionLive.Components.ChampionsList do
|
||||||
|
use Phoenix.Component
|
||||||
|
|
||||||
|
attr :champion, :map
|
||||||
|
|
||||||
|
defp render_champion(assigns) do
|
||||||
|
detail_url =
|
||||||
|
"/champions/#{assigns.champion.id}?team-position=#{assigns.champion.team_position}&patch=#{assigns.champion.patch_number}"
|
||||||
|
|
||||||
|
~H"""
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<.link patch={detail_url}>
|
||||||
|
<div class="flex cursor-pointer items-center gap-2">
|
||||||
|
<img
|
||||||
|
class="champion_image rounded-md"
|
||||||
|
src={"https://ddragon.leagueoflegends.com/cdn/14.11.1/img/champion/#{assigns.champion.image}"}
|
||||||
|
/>
|
||||||
|
<%= @champion.name %>
|
||||||
|
</div>
|
||||||
|
</.link>
|
||||||
|
</td>
|
||||||
|
<td><%= @champion.win_rate %>%</td>
|
||||||
|
<td><%= @champion.total_games %></td>
|
||||||
|
<td>
|
||||||
|
<img src={team_position_image(@champion.team_position)} class="w-5 h-5" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
attr :champions, :list
|
||||||
|
attr :patch_number, :integer
|
||||||
|
attr :position, :string
|
||||||
|
|
||||||
|
def champions_list(assigns) do
|
||||||
|
~H"""
|
||||||
|
<style>
|
||||||
|
.champion_image {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<table class="table w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="py-3 text-left text-gray-500 uppercase tracking-wider">
|
||||||
|
Champion
|
||||||
|
</th>
|
||||||
|
<th scope="col" class="py-3 text-left text-gray-500 uppercase tracking-wider">
|
||||||
|
Win rate
|
||||||
|
</th>
|
||||||
|
<th scope="col" class="py-3 text-left text-gray-500 uppercase tracking-wider">
|
||||||
|
Total games
|
||||||
|
</th>
|
||||||
|
<th scope="col" class="py-3 text-left text-gray-500 uppercase tracking-wider">
|
||||||
|
Position
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<%= for champion <- assigns.champions do %>
|
||||||
|
<div class="cursor-pointer">
|
||||||
|
<.link patch={"/champions/#{champion.id}?team-position=#{champion.team_position}&patch=#{champion.patch_number}"}>
|
||||||
|
<.render_champion champion={champion} ) />
|
||||||
|
</.link>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<!-- table rows and cells go here -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
defp team_position_image("BOTTOM"), do: "/images/lanes/bot.png"
|
||||||
|
defp team_position_image("MIDDLE"), do: "/images/lanes/mid.png"
|
||||||
|
defp team_position_image("TOP"), do: "/images/lanes/top.png"
|
||||||
|
defp team_position_image("JUNGLE"), do: "/images/lanes/jungle.png"
|
||||||
|
defp team_position_image("UTILITY"), do: "/images/lanes/utility.png"
|
||||||
|
end
|
||||||
@@ -2,6 +2,7 @@ defmodule LoLAnalyticsWeb.ChampionLive.Index do
|
|||||||
use LoLAnalyticsWeb, :live_view
|
use LoLAnalyticsWeb, :live_view
|
||||||
|
|
||||||
import LolAnalyticsWeb.ChampionComponents.ChampionCard
|
import LolAnalyticsWeb.ChampionComponents.ChampionCard
|
||||||
|
import LolAnalyticsWeb.ChampionLive.Components.ChampionsList
|
||||||
import LolAnalyticsWeb.Loader
|
import LolAnalyticsWeb.Loader
|
||||||
import Phoenix.VerifiedRoutes
|
import Phoenix.VerifiedRoutes
|
||||||
|
|
||||||
@@ -15,12 +16,14 @@ defmodule LoLAnalyticsWeb.ChampionLive.Index do
|
|||||||
def mount(params, _session, socket) do
|
def mount(params, _session, socket) do
|
||||||
role = params["role"] || "all"
|
role = params["role"] || "all"
|
||||||
patch = params["patch"] || "14.12"
|
patch = params["patch"] || "14.12"
|
||||||
|
display_mode = params["display_mode"] || "grid"
|
||||||
|
|
||||||
socket =
|
socket =
|
||||||
socket
|
socket
|
||||||
|> assign(:selected_role, role)
|
|> assign(:selected_role, role)
|
||||||
|> assign(:selected_patch, patch)
|
|> assign(:selected_patch, patch)
|
||||||
|> assign(:champions, %{status: :loading})
|
|> assign(:champions, %{status: :loading})
|
||||||
|
|> assign(:display_mode, display_mode)
|
||||||
|> load_champs(role, patch)
|
|> load_champs(role, patch)
|
||||||
|
|
||||||
{:ok, socket}
|
{:ok, socket}
|
||||||
@@ -28,18 +31,22 @@ defmodule LoLAnalyticsWeb.ChampionLive.Index do
|
|||||||
|
|
||||||
def handle_params(params, _uri, socket) do
|
def handle_params(params, _uri, socket) do
|
||||||
role = params["role"] || "all"
|
role = params["role"] || "all"
|
||||||
patch = params["patch"] || "14.12"
|
patch = params["patch"] || "14.10"
|
||||||
|
display_mode = params["display_mode"] || "grid"
|
||||||
|
|
||||||
{
|
{
|
||||||
:noreply,
|
:noreply,
|
||||||
assign(socket, selected_role: role)
|
assign(socket, selected_role: role)
|
||||||
|> assign(selected_patch: patch)
|
|> assign(selected_patch: patch)
|
||||||
|
|> assign(display_mode: display_mode)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("filter", %{"role" => selected_role} = params, socket) do
|
def handle_event("filter", %{"role" => selected_role} = params, socket) do
|
||||||
query = Map.merge(params, %{patch: socket.assigns.selected_patch || "14.12"})
|
query =
|
||||||
|
get_query_params(socket)
|
||||||
|
|> Map.merge(%{role: selected_role})
|
||||||
|
|
||||||
{:reply, %{},
|
{:reply, %{},
|
||||||
socket
|
socket
|
||||||
@@ -49,9 +56,22 @@ defmodule LoLAnalyticsWeb.ChampionLive.Index do
|
|||||||
|> assign(:selected_role, selected_role)}
|
|> assign(:selected_role, selected_role)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_event("set-display-mode", %{"mode" => mode} = params, socket) do
|
||||||
|
query_params =
|
||||||
|
get_query_params(socket)
|
||||||
|
|> Map.merge(%{display_mode: mode})
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
assign(socket, :display_mode, mode)
|
||||||
|
|> push_patch(to: ~p"/champions?#{query_params}")}
|
||||||
|
end
|
||||||
|
|
||||||
def handle_info(%{patch: patch}, socket) do
|
def handle_info(%{patch: patch}, socket) do
|
||||||
selected_role = socket.assigns.selected_role
|
selected_role = socket.assigns.selected_role
|
||||||
query_params = %{role: selected_role, patch: patch}
|
|
||||||
|
query_params =
|
||||||
|
get_query_params(socket)
|
||||||
|
|> Map.merge(%{patch: patch})
|
||||||
|
|
||||||
socket =
|
socket =
|
||||||
assign(socket, :champions, %{status: :loading})
|
assign(socket, :champions, %{status: :loading})
|
||||||
@@ -93,13 +113,54 @@ defmodule LoLAnalyticsWeb.ChampionLive.Index do
|
|||||||
{:noreply, assign(socket, :champions, %{status: :data, data: champs})}
|
{:noreply, assign(socket, :champions, %{status: :data, data: champs})}
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_champions(assigns) do
|
def render_display_mode_selector_selector(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div class="flex">
|
||||||
|
<div phx-click="set-display-mode" phx-value-mode="grid" class="cursor-pointer">
|
||||||
|
<.icon name={grid_icon(assigns.display_mode)} alt="table" />
|
||||||
|
</div>
|
||||||
|
<div phx-click="set-display-mode" phx-value-mode="list" class="cursor-pointer">
|
||||||
|
<.icon name={list_icon(assigns.display_mode)} alt="table" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
defp grid_icon(selected_display_mode) do
|
||||||
|
case selected_display_mode do
|
||||||
|
"grid" -> "hero-squares-2x2-solid"
|
||||||
|
"list" -> "hero-squares-2x2"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def list_icon(selected_display_mode) do
|
||||||
|
case selected_display_mode do
|
||||||
|
"grid" -> "hero-table-cells"
|
||||||
|
"list" -> "hero-table-cells-solid"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_champions_list(assigns) do
|
||||||
case assigns.champions do
|
case assigns.champions do
|
||||||
%{status: :loading} ->
|
%{status: :loading} ->
|
||||||
~H"""
|
~H"""
|
||||||
<.loader />
|
<.loader />
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
%{status: :data, data: champions} ->
|
||||||
|
~H"""
|
||||||
|
<.champions_list champions={champions} />
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_champions_grid(%{:champions => champions_state} = assigns) do
|
||||||
|
case champions_state do
|
||||||
|
%{status: :loading} ->
|
||||||
|
~H"""
|
||||||
|
<.loader />
|
||||||
|
"""
|
||||||
|
|
||||||
%{status: :data, data: champions} ->
|
%{status: :data, data: champions} ->
|
||||||
~H"""
|
~H"""
|
||||||
<div id="champions" class="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
<div id="champions" class="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
||||||
@@ -126,4 +187,12 @@ defmodule LoLAnalyticsWeb.ChampionLive.Index do
|
|||||||
socket
|
socket
|
||||||
|> assign(:page_title, "Listing Champions")
|
|> assign(:page_title, "Listing Champions")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp get_query_params(socket) do
|
||||||
|
%{
|
||||||
|
patch: socket.assigns.selected_patch,
|
||||||
|
role: socket.assigns.selected_role,
|
||||||
|
display_mode: socket.assigns.display_mode
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
<.header>
|
<.header>
|
||||||
Champions
|
<p class="text-3xl">
|
||||||
|
Champions
|
||||||
|
</p>
|
||||||
|
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
|
<div class="h-4" />
|
||||||
|
|
||||||
<div class="px-2">
|
<div class="px-2">
|
||||||
<h1 class="text-l font-semibold">Filters</h1>
|
<h1 class="text-l font-semibold">Filters</h1>
|
||||||
|
|
||||||
@@ -10,9 +15,18 @@
|
|||||||
<.live_component module={ChampionFilters} id="role-filters" selectedrole={@selected_role || "all" } />
|
<.live_component module={ChampionFilters} id="role-filters" selectedrole={@selected_role || "all" } />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<.live_component module={PatchSelector} id="patch-selector" />
|
<div class="flex justify-between items-center">
|
||||||
|
<.live_component module={PatchSelector} id="patch-selector" initial_patch={@selected_patch} />
|
||||||
|
|
||||||
|
<.render_display_mode_selector_selector display_mode={@display_mode} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="h-4"></div>
|
<div class="h-4"></div>
|
||||||
|
|
||||||
<.render_champions champions={@champions} />
|
<%= if @display_mode=="grid" do %>
|
||||||
|
<.render_champions_grid champions={@champions} />
|
||||||
|
<% else %>
|
||||||
|
<.render_champions_list id="champions-list" champions={@champions} />
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"amqp": {:hex, :amqp, "3.3.0", "056d9f4bac96c3ab5a904b321e70e78b91ba594766a1fc2f32afd9c016d9f43b", [:mix], [{:amqp_client, "~> 3.9", [hex: :amqp_client, repo: "hexpm", optional: false]}], "hexpm", "8d3ae139d2646c630d674a1b8d68c7f85134f9e8b2a1c3dd5621616994b10a8b"},
|
"amqp": {:hex, :amqp, "3.3.0", "056d9f4bac96c3ab5a904b321e70e78b91ba594766a1fc2f32afd9c016d9f43b", [:mix], [{:amqp_client, "~> 3.9", [hex: :amqp_client, repo: "hexpm", optional: false]}], "hexpm", "8d3ae139d2646c630d674a1b8d68c7f85134f9e8b2a1c3dd5621616994b10a8b"},
|
||||||
"amqp_client": {:hex, :amqp_client, "3.12.13", "6fc6a7c681e53fed4cbd3f5bcdda342a2b46976345e460ef85414c63698cfe70", [:make, :rebar3], [{:credentials_obfuscation, "3.4.0", [hex: :credentials_obfuscation, repo: "hexpm", optional: false]}, {:rabbit_common, "3.12.13", [hex: :rabbit_common, repo: "hexpm", optional: false]}], "hexpm", "76f41bff0792193f00e0062128db51eb68bcee0eb8236139247a7d1866438d03"},
|
"amqp_client": {:hex, :amqp_client, "3.12.13", "6fc6a7c681e53fed4cbd3f5bcdda342a2b46976345e460ef85414c63698cfe70", [:make, :rebar3], [{:credentials_obfuscation, "3.4.0", [hex: :credentials_obfuscation, repo: "hexpm", optional: false]}, {:rabbit_common, "3.12.13", [hex: :rabbit_common, repo: "hexpm", optional: false]}], "hexpm", "76f41bff0792193f00e0062128db51eb68bcee0eb8236139247a7d1866438d03"},
|
||||||
"bandit": {:hex, :bandit, "1.5.0", "3bc864a0da7f013ad3713a7f550c6a6ec0e19b8d8715ec678256a0dc197d5539", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "92d18d9a7228a597e0d4661ef69a874ea82d63ff49c7d801a5c68cb18ebbbd72"},
|
"bandit": {:hex, :bandit, "1.5.0", "3bc864a0da7f013ad3713a7f550c6a6ec0e19b8d8715ec678256a0dc197d5539", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "92d18d9a7228a597e0d4661ef69a874ea82d63ff49c7d801a5c68cb18ebbbd72"},
|
||||||
"broadway": {:hex, :broadway, "1.0.7", "7808f9e3eb6f53ca6d060f0f9d61012dd8feb0d7a82e62d087dd517b9b66fa53", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.7 or ~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e76cfb0a7d64176c387b8b1ddbfb023e2ee8a63e92f43664d78e6d5d0b1177c6"},
|
"broadway": {:hex, :broadway, "1.1.0", "8ed3aea01fd6f5640b3e1515b90eca51c4fc1fac15fb954cdcf75dc054ae719c", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.7 or ~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25e315ef1afe823129485d981dcc6d9b221cea30e625fd5439e9b05f44fb60e4"},
|
||||||
"broadway_rabbitmq": {:hex, :broadway_rabbitmq, "0.8.1", "6d68a480b2e49694e4f3836dcbbf8e621bb97b34e84787a2093d5cc3078a4d87", [:mix], [{:amqp, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :amqp, repo: "hexpm", optional: false]}, {:broadway, "~> 1.0", [hex: :broadway, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5 or ~> 0.4.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eebe9b03c9673cbda790430389c47e4ca867f9904418cff1b71a74a59c2a986"},
|
"broadway_rabbitmq": {:hex, :broadway_rabbitmq, "0.8.1", "6d68a480b2e49694e4f3836dcbbf8e621bb97b34e84787a2093d5cc3078a4d87", [:mix], [{:amqp, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :amqp, repo: "hexpm", optional: false]}, {:broadway, "~> 1.0", [hex: :broadway, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5 or ~> 0.4.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eebe9b03c9673cbda790430389c47e4ca867f9904418cff1b71a74a59c2a986"},
|
||||||
"castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"},
|
"castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"},
|
||||||
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
|
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||||
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
|
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
|
||||||
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
|
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
|
||||||
"nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
|
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
||||||
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
|
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
|
||||||
"phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"},
|
"phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"},
|
||||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.5.1", "6fdbc334ea53620e71655664df6f33f670747b3a7a6c4041cdda3e2c32df6257", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ebe43aa580db129e54408e719fb9659b7f9e0d52b965c5be26cdca416ecead28"},
|
"phoenix_ecto": {:hex, :phoenix_ecto, "4.5.1", "6fdbc334ea53620e71655664df6f33f670747b3a7a6c4041cdda3e2c32df6257", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ebe43aa580db129e54408e719fb9659b7f9e0d52b965c5be26cdca416ecead28"},
|
||||||
|
|||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
```
|
|
||||||
SELECT
|
|
||||||
(cast(count(CASE WHEN is_win THEN 1 END) as float) / cast(count(*) as float)) * 100.0 as win_rate,
|
|
||||||
count(CASE WHEN is_win THEN 1 END) as games_won,
|
|
||||||
count(*) as total_games,
|
|
||||||
champion_id
|
|
||||||
FROM fact_champion_played_game
|
|
||||||
GROUP BY champion_id
|
|
||||||
ORDER BY win_rate desc;
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
SELECT
|
|
||||||
(cast(count(CASE WHEN is_win THEN 1 END) as float) / cast(count(*) as float)) * 100.0 as win_rate,
|
|
||||||
count(CASE WHEN is_win THEN 1 END) as games_won,
|
|
||||||
count(*) as total_games,
|
|
||||||
champion_id,
|
|
||||||
team_position
|
|
||||||
FROM fact_champion_played_game
|
|
||||||
GROUP BY champion_id, team_position
|
|
||||||
ORDER BY win_rate desc;
|
|
||||||
```
|
|
||||||
Reference in New Issue
Block a user