defmodule TradeMachine.GMGradeV2.Pillars.TimelineAlignment.DevelopmentEnvironment do @moduledoc """ Scores youth opportunity, veteran infrastructure, and developmental roles. All threshold comparisons are inclusive and intentionally preserve AVC's cliffs for the Phase 16 sensitivity lab. """ alias TradeMachine.GMGradeV2.MandateProfile alias TradeMachine.GMGradeV2.Result.MetricResult @rotation_minutes 240 @doc "Returns AVC's 45/35/20 Development Environment metric." @spec score([map()], MandateProfile.t()) :: MetricResult.t() def score(players, %MandateProfile{} = profile) when is_list(players) do young = Enum.filter(players, &(&1.age <= 24)) young_minutes = Enum.reduce(young, 0, &(&1.assigned_minutes + &2)) young_share = young_minutes / @rotation_minutes veteran_count = Enum.count(players, fn player -> player.age >= 27 and player.assigned_minutes >= 12 and is_number(player.season_minutes) and player.season_minutes >= 500 and player.adjusted_epm >= -1.5 end) young_role_count = Enum.count(young, &(&1.assigned_minutes >= 8)) opportunity = opportunity_score(young_share, profile.young_minute_share_range) infrastructure = count_score(veteran_count, profile.veteran_count_range, 25, 10) distribution = count_score(young_role_count, profile.young_role_range, 20, 10) %MetricResult{ key: :development_environment, version: profile.version, score: combine(opportunity, infrastructure, distribution), inputs: %{mandate: profile.key, rotation_minutes: @rotation_minutes}, supporting: %{ young_player_opportunity: opportunity, veteran_infrastructure: infrastructure, developmental_role_distribution: distribution, young_rotation_minutes: young_minutes, young_minute_share: young_share, established_veteran_count: veteran_count, meaningful_young_role_count: young_role_count, targets: %{ young_minute_share: profile.young_minute_share_range, veteran_count: profile.veteran_count_range, young_roles: profile.young_role_range } }, weights: %{ young_player_opportunity: 0.45, veteran_infrastructure: 0.35, developmental_role_distribution: 0.20 }, explanation: "Development Environment balances mandate-appropriate youth minutes, veterans, and meaningful young roles." } end @doc "Combines AVC's three development sub-scores at 45/35/20." def combine(opportunity, infrastructure, distribution) do opportunity * 0.45 + infrastructure * 0.35 + distribution * 0.20 end @doc "Scores young-minute share, applying two points per percentage point outside." def opportunity_score(share, %{min: minimum, max: maximum}) when is_number(share) do percentage_points = cond do share < minimum -> (minimum - share) * 100 share > maximum -> (share - maximum) * 100 true -> 0 end max(0.0, 100.0 - 2 * percentage_points) end @doc "Scores an integer target range with asymmetric missing/excess penalties." def count_score(count, %{min: minimum, max: maximum}, missing_penalty, excess_penalty) do penalty = cond do count < minimum -> (minimum - count) * missing_penalty count > maximum -> (count - maximum) * excess_penalty true -> 0 end max(0.0, 100.0 - penalty) end end