Mandates & weights
The strategic weighting layer, read directly from MandateProfile.
Weighting a strategic job
All six pillar weights sum to one for each mandate. The same pillar score can therefore contribute differently under Championship Push and Rebuilding. Timeline Alignment also reads mandate-specific targets, so switching mandates can change its pillar score.
contribution(pillar) = pillar_score × mandate_weight
Versioned profiles
The live table represents current profiles. Historical challenge snapshots retain the mandate profile and version used when their evidence was frozen; a later profile change is not a retroactive update to those snapshots.
Current mandate matrix
Loaded from the same registry used by the grader. Each row totals 100%.
| Mandate | Competitive strength | Roster construction | Contract health | Cap & flexibility | Asset management | Timeline alignment |
|---|---|---|---|---|---|---|
| Championship Push | 35.0% | 20.0% | 10.0% | 10.0% | 5.0% | 20.0% |
| Run It Back | 25.0% | 20.0% | 15.0% | 15.0% | 5.0% | 20.0% |
| Ascending | 20.0% | 15.0% | 15.0% | 15.0% | 15.0% | 20.0% |
| Retool | 15.0% | 15.0% | 20.0% | 20.0% | 10.0% | 20.0% |
| Selling | 5.0% | 5.0% | 10.0% | 20.0% | 40.0% | 20.0% |
| Rebuilding | 5.0% | 15.0% | 15.0% | 15.0% | 30.0% | 20.0% |
Implementation
These notes and source files are captured from this deployment's code. Expand a module for its algorithm and branches, or download the Elixir file for local inspection.
mandate_profile.exElixir
lib/trade_machine/gm_grade_v2/mandate_profile.ex
The six canonical, versioned GM Mandate Profiles from AVC's model
(
avc-grading-system/markdown/01-gm-mandate-logic.md
and
07-pillar-6-timeline-alignment.md
).
A Mandate Profile is the *only* thing a mandate controls: the six Final GM Grade pillar weights and Timeline Alignment's mandate-specific internal targets. It does **not** reach into any other pillar's formula — those stay objective and mandate-independent. Per AVC's guardrails, Timeline Alignment is fixed at 20% for every mandate; no pillar weight exceeds 40% or falls below 5%.
Profiles are keyed by
{mandate_key, version}
. Challenge authoring selects a
mandate and pins its profile version; the version is frozen onto the grade so
results stay reproducible when the profile is later retuned.
The original prototype used inferred archetypes with a different vocabulary. Those values must not be mapped here by string similarity (see DECISIONS D-02).
SHA-256 2d8b4ec0de79d7629bd13eb78a5004490db2cb40f5806fac1defcf1ca23f9510
defmodule TradeMachine.GMGradeV2.MandateProfile do
@moduledoc """
The six canonical, versioned GM Mandate Profiles from AVC's model
(`avc-grading-system/markdown/01-gm-mandate-logic.md` and
`07-pillar-6-timeline-alignment.md`).
A Mandate Profile is the *only* thing a mandate controls: the six Final GM
Grade pillar weights and Timeline Alignment's mandate-specific internal
targets. It does **not** reach into any other pillar's formula — those stay
objective and mandate-independent. Per AVC's guardrails, Timeline Alignment is
fixed at 20% for every mandate; no pillar weight exceeds 40% or falls below 5%.
Profiles are keyed by `{mandate_key, version}`. Challenge authoring selects a
mandate and pins its profile version; the version is frozen onto the grade so
results stay reproducible when the profile is later retuned.
The original prototype used inferred archetypes with a different vocabulary.
Those values must not be mapped here by string similarity (see DECISIONS
D-02).
"""
@enforce_keys [
:key,
:version,
:label,
:strategic_question,
:pillar_weights,
:ideal_core_age,
:age_penalty_below,
:age_penalty_above,
:prime_offset_range,
:continuity_horizon,
:young_minute_share_range,
:veteran_count_range,
:young_role_range
]
defstruct @enforce_keys
@type range :: %{min: number(), max: number()}
@type t :: %__MODULE__{
key: atom(),
version: String.t(),
label: String.t(),
strategic_question: String.t(),
pillar_weights: %{
competitive_strength: float(),
roster_construction: float(),
contract_health: float(),
cap_flexibility: float(),
asset_management: float(),
timeline_alignment: float()
},
# Ideal core age range (whole years on the season's first day).
ideal_core_age: range(),
# Asymmetric age-fit penalty rates (points per year outside the range).
age_penalty_below: number(),
age_penalty_above: number(),
# Ideal Prime Timing Offset range (27.5 − age).
prime_offset_range: range(),
# Core Continuity horizon in seasons (includes the current season).
continuity_horizon: pos_integer(),
# Ideal young-player minute share of 240, as fractions (0.05 == 5%).
young_minute_share_range: range(),
# Ideal Established Veteran count.
veteran_count_range: range(),
# Ideal count of meaningful young rotation roles (≥ 8 assigned minutes).
young_role_range: range()
}
@version "1.0.0"
@keys ~w(championship_push run_it_back ascending retool selling rebuilding)a
@profiles %{
{:championship_push, @version} => %{
key: :championship_push,
version: @version,
label: "Championship Push",
strategic_question:
"Did the GM create the strongest realistic championship roster possible?",
pillar_weights: %{
competitive_strength: 0.35,
roster_construction: 0.20,
contract_health: 0.10,
cap_flexibility: 0.10,
asset_management: 0.05,
timeline_alignment: 0.20
},
ideal_core_age: %{min: 25, max: 31},
age_penalty_below: 8,
age_penalty_above: 7,
prime_offset_range: %{min: -2.0, max: 1.0},
continuity_horizon: 2,
young_minute_share_range: %{min: 0.05, max: 0.20},
veteran_count_range: %{min: 4, max: 8},
young_role_range: %{min: 1, max: 3}
},
{:run_it_back, @version} => %{
key: :run_it_back,
version: @version,
label: "Run It Back",
strategic_question:
"Did the GM preserve what already works while making the roster more sustainable and complete?",
pillar_weights: %{
competitive_strength: 0.25,
roster_construction: 0.20,
contract_health: 0.15,
cap_flexibility: 0.15,
asset_management: 0.05,
timeline_alignment: 0.20
},
ideal_core_age: %{min: 24, max: 31},
age_penalty_below: 9,
age_penalty_above: 10,
prime_offset_range: %{min: -1.5, max: 2.0},
continuity_horizon: 3,
young_minute_share_range: %{min: 0.10, max: 0.25},
veteran_count_range: %{min: 3, max: 7},
young_role_range: %{min: 1, max: 4}
},
{:ascending, @version} => %{
key: :ascending,
version: @version,
label: "Ascending",
strategic_question:
"Did the GM make the young team meaningfully better without compromising its long-term trajectory?",
pillar_weights: %{
competitive_strength: 0.20,
roster_construction: 0.15,
contract_health: 0.15,
cap_flexibility: 0.15,
asset_management: 0.15,
timeline_alignment: 0.20
},
ideal_core_age: %{min: 21, max: 27},
age_penalty_below: 7,
age_penalty_above: 12,
prime_offset_range: %{min: 1.0, max: 5.0},
continuity_horizon: 4,
young_minute_share_range: %{min: 0.25, max: 0.45},
veteran_count_range: %{min: 2, max: 5},
young_role_range: %{min: 3, max: 6}
},
{:retool, @version} => %{
key: :retool,
version: @version,
label: "Retool",
strategic_question:
"Did the GM fix the roster's structural problems without unnecessarily destroying its competitive foundation?",
pillar_weights: %{
competitive_strength: 0.15,
roster_construction: 0.15,
contract_health: 0.20,
cap_flexibility: 0.20,
asset_management: 0.10,
timeline_alignment: 0.20
},
ideal_core_age: %{min: 23, max: 29},
age_penalty_below: 8,
age_penalty_above: 12,
prime_offset_range: %{min: 0.0, max: 3.5},
continuity_horizon: 3,
young_minute_share_range: %{min: 0.20, max: 0.40},
veteran_count_range: %{min: 2, max: 5},
young_role_range: %{min: 2, max: 5}
},
{:selling, @version} => %{
key: :selling,
version: @version,
label: "Selling",
strategic_question:
"Did the GM successfully convert current roster value into strong future assets?",
pillar_weights: %{
competitive_strength: 0.05,
roster_construction: 0.05,
contract_health: 0.10,
cap_flexibility: 0.20,
asset_management: 0.40,
timeline_alignment: 0.20
},
ideal_core_age: %{min: 19, max: 25},
age_penalty_below: 5,
age_penalty_above: 15,
prime_offset_range: %{min: 2.0, max: 6.0},
continuity_horizon: 4,
young_minute_share_range: %{min: 0.35, max: 0.60},
veteran_count_range: %{min: 1, max: 4},
young_role_range: %{min: 4, max: 8}
},
{:rebuilding, @version} => %{
key: :rebuilding,
version: @version,
label: "Rebuilding",
strategic_question:
"Did the GM create a credible foundation for the franchise's next competitive era?",
pillar_weights: %{
competitive_strength: 0.05,
roster_construction: 0.15,
contract_health: 0.15,
cap_flexibility: 0.15,
asset_management: 0.30,
timeline_alignment: 0.20
},
ideal_core_age: %{min: 19, max: 25},
age_penalty_below: 4,
age_penalty_above: 14,
prime_offset_range: %{min: 2.5, max: 7.0},
continuity_horizon: 5,
young_minute_share_range: %{min: 0.40, max: 0.65},
veteran_count_range: %{min: 2, max: 4},
young_role_range: %{min: 4, max: 8}
}
}
# Compile-time guardrail assertions: every profile's six weights sum to 1.0,
# Timeline Alignment is fixed at 20%, and no weight breaches the 5%/40% guard.
for {{key, version}, profile} <- @profiles do
sum = profile.pillar_weights |> Map.values() |> Enum.sum()
if abs(sum - 1.0) > 1.0e-9 do
raise "MandateProfile #{key}/#{version} pillar weights sum to #{sum}, not 1.0"
end
if profile.pillar_weights.timeline_alignment != 0.20 do
raise "MandateProfile #{key}/#{version} Timeline Alignment weight must be 0.20"
end
for {pillar, w} <- profile.pillar_weights do
if w < 0.05 - 1.0e-9 or w > 0.40 + 1.0e-9 do
raise "MandateProfile #{key}/#{version} #{pillar} weight #{w} breaches the 5%–40% guardrail"
end
end
end
@doc "The current default profile version."
@spec current_version() :: String.t()
def current_version, do: @version
@doc "All canonical mandate keys."
@spec keys() :: [atom()]
def keys, do: @keys
@doc "All profile versions present in the registry."
@spec versions() :: [String.t()]
def versions, do: @profiles |> Map.keys() |> Enum.map(&elem(&1, 1)) |> Enum.uniq()
@doc "Fetches a profile, raising if the `{key, version}` pair is unknown."
@spec get!(atom(), String.t()) :: t()
def get!(key, version \\ @version) do
case Map.fetch(@profiles, {key, version}) do
{:ok, data} -> struct!(__MODULE__, data)
:error -> raise ArgumentError, "unknown mandate profile #{inspect(key)}/#{inspect(version)}"
end
end
@doc "Fetches a profile, returning `nil` for an unknown `{key, version}` pair."
@spec get(atom(), String.t()) :: t() | nil
def get(key, version \\ @version) do
case Map.get(@profiles, {key, version}) do
nil -> nil
data -> struct!(__MODULE__, data)
end
end
@doc """
Rehydrates the profile copy stored in a challenge snapshot.
Fields omitted by older snapshots retain the named registry version's value;
fields present in the frozen document win. This keeps the snapshot copy, not
the mutable registry, authoritative at grading time.
"""
@spec from_frozen(map()) :: {:ok, t()} | {:error, :invalid_profile}
def from_frozen(stored) when is_map(stored) do
with {:ok, key_value} <- fetch(stored, :key),
{:ok, key} <- mandate_key(key_value),
{:ok, version} <- fetch(stored, :version),
%__MODULE__{} = base <- get(key, version) do
values =
base
|> Map.from_struct()
|> Enum.map(fn {field, default} ->
value =
stored
|> Map.get(field, Map.get(stored, Atom.to_string(field)))
|> case do
nil -> default
frozen -> frozen
end
{field, normalize_frozen_value(field, value)}
end)
|> Map.new()
{:ok, struct!(__MODULE__, values)}
else
_ -> {:error, :invalid_profile}
end
rescue
_error in [ArgumentError, KeyError] -> {:error, :invalid_profile}
end
def from_frozen(_stored), do: {:error, :invalid_profile}
@doc "All profiles at a given version, in canonical mandate order."
@spec all(String.t()) :: [t()]
def all(version \\ @version), do: Enum.map(@keys, &get!(&1, version))
@doc "Human label for a mandate key."
@spec label(atom()) :: String.t()
def label(key), do: get!(key).label
defp fetch(map, key) do
case Map.fetch(map, key) do
{:ok, value} -> {:ok, value}
:error -> Map.fetch(map, Atom.to_string(key))
end
end
defp mandate_key(key) when is_atom(key) do
if key in @keys, do: {:ok, key}, else: :error
end
defp mandate_key(key) when is_binary(key) do
case Enum.find(@keys, &(Atom.to_string(&1) == key)) do
nil -> :error
mandate -> {:ok, mandate}
end
end
defp mandate_key(_key), do: :error
defp normalize_frozen_value(field, value)
when field in [
:ideal_core_age,
:prime_offset_range,
:young_minute_share_range,
:veteran_count_range,
:young_role_range
] and is_map(value) do
%{
min: Map.get(value, :min, Map.get(value, "min")),
max: Map.get(value, :max, Map.get(value, "max"))
}
end
defp normalize_frozen_value(:key, value), do: normalize_key(value)
defp normalize_frozen_value(:pillar_weights, value) when is_map(value) do
Map.new(value, fn {key, weight} -> {normalize_key(key), weight} end)
end
defp normalize_frozen_value(_field, value), do: value
defp normalize_key(key) when is_atom(key), do: key
defp normalize_key(key) when is_binary(key), do: String.to_existing_atom(key)
end