defmodule TradeMachine.GMGradeV2.Pillars.ContractHealth do @moduledoc """ Aggregates AVC's four Contract Health metrics over the final roster. Contract Value, Long-Term Risk, Contract Mobility, and Contract Optionality retain full precision through the fixed 35/30/20/15 weighted sum. """ alias TradeMachine.GMGradeV2.Benchmark alias TradeMachine.GMGradeV2.ChallengeSnapshot alias TradeMachine.GMGradeV2.Pillars.ContractHealth.ContractMobility alias TradeMachine.GMGradeV2.Pillars.ContractHealth.ContractOptionality alias TradeMachine.GMGradeV2.Pillars.ContractHealth.ContractValue alias TradeMachine.GMGradeV2.Pillars.ContractHealth.LongTermRisk alias TradeMachine.GMGradeV2.Result.MetricResult alias TradeMachine.GMGradeV2.Result.PillarResult @metric_weights %{ contract_value: 0.35, long_term_risk: 0.30, contract_mobility: 0.20, contract_optionality: 0.15 } @metric_modules [ ContractValue, LongTermRisk, ContractMobility, ContractOptionality ] if abs(Enum.sum(Map.values(@metric_weights)) - 1.0) > 1.0e-9 do raise "Contract Health metric weights must sum to 1.0" end @doc "Returns the full-precision Contract Health pillar result." @spec score(map(), ChallengeSnapshot.t()) :: PillarResult.t() def score( scenario_state, %ChallengeSnapshot{benchmark: %Benchmark{} = benchmark} = snapshot ) when is_map(scenario_state) do metric_results = Enum.map(@metric_modules, & &1.score(scenario_state, snapshot)) {strongest, weakest} = extrema(metric_results) %PillarResult{ key: :contract_health, version: benchmark.version, score: weighted_score(metric_results), metric_results: metric_results, metric_weights: @metric_weights, explanation: explanation(strongest, weakest), strongest: strongest.key, weakest: weakest.key } end defp weighted_score(metric_results) do Enum.sum( Enum.map(metric_results, fn result -> result.score * Map.fetch!(@metric_weights, result.key) end) ) end defp extrema(metric_results) do { Enum.max_by(metric_results, & &1.score), Enum.min_by(metric_results, & &1.score) } end defp explanation( %MetricResult{key: strongest_key, score: strongest_score}, %MetricResult{key: weakest_key, score: weakest_score} ) do "Contract Health combines four final-roster contract metrics; " <> "#{label(strongest_key)} is strongest at #{strongest_score}, while " <> "#{label(weakest_key)} is weakest at #{weakest_score}." end defp label(:contract_value), do: "Contract Value" defp label(:long_term_risk), do: "Long-Term Risk" defp label(:contract_mobility), do: "Contract Mobility" defp label(:contract_optionality), do: "Contract Optionality" end