defmodule TradeMachine.GMGradeV2.Pillars.CompetitiveStrength.MetricInputs do @moduledoc false alias TradeMachine.GMGradeV2.Availability alias TradeMachine.GMGradeV2.Benchmark alias TradeMachine.NBA.ScenarioRotation def rotation_entries(entries) when is_list(entries), do: entries def rotation_entries(%{entries: entries}) when is_list(entries), do: entries def rotation_entries(rotation), do: ScenarioRotation.entries(rotation) def player_id(entry), do: fetch_any!(entry, :player_id) def minutes(entry), do: fetch_number!(entry, :minutes) def starter?(entry), do: fetch_any!(entry, :starter) == true @doc """ The availability multiplier for one entry, plus the diagnostic keys to merge into that player's contribution. The annotation is `%{}` unless the user actually set a usable expected-games value, so a contribution map for an un-annotated rotation is byte-identical to the one this metric produced before availability existed — the stored grade document has to be, not just the score. When the annotation *is* present it records both halves of the arithmetic, because a discounted contribution the user later disputes has to be explainable from the grade alone. """ def availability(entry, evidence) do case Availability.expected_games(entry) do nil -> {1.0, %{}} games -> factor = Availability.factor(entry, evidence) {factor, %{expected_games: games, availability: factor}} end end def impact_number!(impacts, player_id, field) do impacts |> player_impact!(player_id) |> fetch_number!(field) end def player_impact!(impacts, player_id) do case Map.fetch(impacts, player_id) do {:ok, impact} -> impact :error -> Map.fetch!(impacts, to_string(player_id)) end end def roster_names(roster) when is_list(roster) do Map.new(roster, fn player -> {fetch_any!(player, :id), fetch_any!(player, :name)} end) end def roster_names(_), do: %{} def player_name(names, player_id), do: Map.get(names, player_id, to_string(player_id)) def benchmark_values(%Benchmark{} = benchmark, metric) when is_atom(metric) do benchmark |> Benchmark.section(:competitive_strength) |> Map.fetch!("metrics") |> Map.fetch!(Atom.to_string(metric)) end def impact_fallbacks(contributions, impacts, impact_label \\ "Adjusted EPM", opts \\ []) do include_imputed_splits? = Keyword.get(opts, :include_imputed_splits?, false) contributions |> Enum.reject(&Map.get(&1, :replacement?, false)) |> Enum.flat_map(fn contribution -> impact = player_impact!(impacts, contribution.player_id) source_fallback(contribution, impact, impact_label) ++ imputed_split_fallbacks(contribution, impact, include_imputed_splits?) end) end def fetch_any!(map, key) do case fetch_any(map, key) do nil -> raise KeyError, key: key, term: map value -> value end end def fetch_any(map, key) when is_map(map) do Map.get(map, key, Map.get(map, Atom.to_string(key))) end defp source_fallback(contribution, impact, impact_label) do case fetch_any(impact, :source) do source when source in [nil, :epm, "epm"] -> [] source -> [ %{ input: contribution.player_id, method: source, note: "#{contribution.name}'s #{impact_label} uses the frozen #{source} fallback." } ] end end defp imputed_split_fallbacks(_contribution, _impact, false), do: [] defp imputed_split_fallbacks(contribution, impact, true) do impact |> fetch_any(:imputed_splits) |> List.wrap() |> Enum.map(fn split -> split = normalize_split!(split) %{ input: contribution.player_id, method: :split_imputation, note: "#{contribution.name}'s adjusted #{split_label(split)} was derived rather than measured." } end) end defp normalize_split!(:offensive), do: :offensive defp normalize_split!("offensive"), do: :offensive defp normalize_split!(:defensive), do: :defensive defp normalize_split!("defensive"), do: :defensive defp normalize_split!(split) do raise ArgumentError, "imputed_splits must contain offensive or defensive, got: #{inspect(split)}" end defp split_label(:offensive), do: "OEPM" defp split_label(:defensive), do: "DEPM" defp fetch_number!(map, key) do case fetch_any!(map, key) do value when is_number(value) -> value value -> raise ArgumentError, "#{key} must be numeric, got: #{inspect(value)}" end end end