From c0928a02c2a5023ce335fa07d70b420f5b5215d3 Mon Sep 17 00:00:00 2001 From: Kyle Herndon Date: Tue, 16 Jun 2026 21:51:16 +0000 Subject: [PATCH] Select qualifier divisions by topology --- commissioners/common/utils.py | 17 +++-- .../configs/among_them.yaml | 1 - .../configs/cogs_vs_clips.yaml | 1 - .../configs/cue_n_woo.yaml | 1 - .../configs/default.yaml | 1 - .../configs/four_score.yaml | 1 - .../configs/proxywar.yaml | 1 - tests/test_commissioner_strategies.py | 75 +++++++++++++++++++ 8 files changed, 84 insertions(+), 14 deletions(-) diff --git a/commissioners/common/utils.py b/commissioners/common/utils.py index 216162b..f9a4ae8 100644 --- a/commissioners/common/utils.py +++ b/commissioners/common/utils.py @@ -1,13 +1,12 @@ from __future__ import annotations from collections import defaultdict -from datetime import UTC, datetime, timedelta +from datetime import UTC, datetime from math import ceil from typing import Any from uuid import UUID from commissioners.common.models import ( - DIVISION_LEADERBOARD_SCORE_EWMA_HALFLIFE, DIVISION_TYPE_STAGING, DEFAULT_STAGES, DivisionSnapshot, @@ -51,7 +50,11 @@ def select_division( if division is not None: return division if fallback_to_lowest: - return min(candidates, key=lambda division: division.level, default=None) + return min( + candidates, + key=lambda division: (division.level, division.name, str(division.id)), + default=None, + ) return None @@ -60,15 +63,13 @@ def select_qualifier_division( divisions: list[DivisionSnapshot], ) -> DivisionSnapshot | None: from commissioners.common.models import DIVISION_TYPE_STAGING + config = commissioner_config or {} - qualifiers_division_name = config.get("qualifiers_division_name") - if not qualifiers_division_name: - return None return select_division( divisions, - division_name=qualifiers_division_name, + division_name=config.get("qualifiers_division_name"), division_type=DIVISION_TYPE_STAGING, - fallback_to_lowest=False, + fallback_to_lowest=True, ) diff --git a/commissioners/ruleset_strategy_commissioner/configs/among_them.yaml b/commissioners/ruleset_strategy_commissioner/configs/among_them.yaml index 9beb86a..f389bb3 100644 --- a/commissioners/ruleset_strategy_commissioner/configs/among_them.yaml +++ b/commissioners/ruleset_strategy_commissioner/configs/among_them.yaml @@ -16,7 +16,6 @@ defaults: divisions: qualifiers: match: - name: Qualifiers type: staging entrants: qualifying min_entries_to_start: 1 diff --git a/commissioners/ruleset_strategy_commissioner/configs/cogs_vs_clips.yaml b/commissioners/ruleset_strategy_commissioner/configs/cogs_vs_clips.yaml index 05d98a3..0fdb0dd 100644 --- a/commissioners/ruleset_strategy_commissioner/configs/cogs_vs_clips.yaml +++ b/commissioners/ruleset_strategy_commissioner/configs/cogs_vs_clips.yaml @@ -12,7 +12,6 @@ defaults: divisions: qualifiers: match: - name: Qualifiers type: staging entrants: qualifying min_entries_to_start: 1 diff --git a/commissioners/ruleset_strategy_commissioner/configs/cue_n_woo.yaml b/commissioners/ruleset_strategy_commissioner/configs/cue_n_woo.yaml index 43b3257..6ebfb5c 100644 --- a/commissioners/ruleset_strategy_commissioner/configs/cue_n_woo.yaml +++ b/commissioners/ruleset_strategy_commissioner/configs/cue_n_woo.yaml @@ -20,7 +20,6 @@ defaults: divisions: qualifiers: match: - name: Qualifiers type: staging entrants: qualifying min_entries_to_start: 1 diff --git a/commissioners/ruleset_strategy_commissioner/configs/default.yaml b/commissioners/ruleset_strategy_commissioner/configs/default.yaml index 593daa7..668acff 100644 --- a/commissioners/ruleset_strategy_commissioner/configs/default.yaml +++ b/commissioners/ruleset_strategy_commissioner/configs/default.yaml @@ -10,7 +10,6 @@ defaults: divisions: qualifiers: match: - name: Qualifiers type: staging entrants: qualifying min_entries_to_start: 1 diff --git a/commissioners/ruleset_strategy_commissioner/configs/four_score.yaml b/commissioners/ruleset_strategy_commissioner/configs/four_score.yaml index 6ebfa30..6f9aecb 100644 --- a/commissioners/ruleset_strategy_commissioner/configs/four_score.yaml +++ b/commissioners/ruleset_strategy_commissioner/configs/four_score.yaml @@ -13,7 +13,6 @@ defaults: divisions: qualifiers: match: - name: Qualifiers type: staging entrants: qualifying min_entries_to_start: 1 diff --git a/commissioners/ruleset_strategy_commissioner/configs/proxywar.yaml b/commissioners/ruleset_strategy_commissioner/configs/proxywar.yaml index 7de19da..2186140 100644 --- a/commissioners/ruleset_strategy_commissioner/configs/proxywar.yaml +++ b/commissioners/ruleset_strategy_commissioner/configs/proxywar.yaml @@ -26,7 +26,6 @@ defaults: divisions: qualifiers: match: - name: Qualifiers type: staging entrants: qualifying min_entries_to_start: 1 diff --git a/tests/test_commissioner_strategies.py b/tests/test_commissioner_strategies.py index 3b36a45..a1da3a8 100644 --- a/tests/test_commissioner_strategies.py +++ b/tests/test_commissioner_strategies.py @@ -62,6 +62,81 @@ def _ruleset_commissioner(name: str) -> RulesetStrategyCommissioner: return RulesetStrategyCommissioner(_ruleset_config(name)) +@pytest.mark.parametrize("config_name", ["among_them", "cogs_vs_clips", "cue_n_woo", "default", "four_score", "proxywar"]) +def test_ruleset_strategy_configs_do_not_pin_qualifier_division_name(config_name: str) -> None: + config = _ruleset_config(config_name) + + assert config["divisions"]["qualifiers"]["match"] == {"type": "staging"} + + +def test_baseline_commissioner_graduates_renamed_qualifier_by_topology() -> None: + qualifier_id = uuid4() + competition_id = uuid4() + policy_version_id = uuid4() + round_start = _round_start( + policy_version_ids=[policy_version_id], + num_agents=1, + commissioner_config={"qualifiers_division_name": "Qualifiers"}, + division_name="Entry Gate", + division_id=qualifier_id, + division_type="staging", + extra_divisions=[DivisionInfo(id=competition_id, name="Daily", level=1, type="competition")], + ) + + complete = complete_round_for_round_start( + BaselineCommissioner(), + round_start, + [ + ProtocolEpisodeResult( + request_id="0", + scores=[EpisodeScore(policy_version_id=policy_version_id, score=1.0)], + ) + ], + ) + + assert complete.membership_changes[0].from_division_id == qualifier_id + assert complete.membership_changes[0].to_division_id == competition_id + + +def test_ruleset_strategy_graduates_renamed_qualifier_by_topology() -> None: + qualifier_id = uuid4() + competition_id = uuid4() + policy_version_id = uuid4() + round_start = _round_start( + policy_version_ids=[policy_version_id], + num_agents=1, + commissioner_config={}, + division_name="Entry Gate", + division_id=qualifier_id, + division_type="staging", + extra_divisions=[DivisionInfo(id=competition_id, name="Daily", level=1, type="competition")], + ) + + complete = complete_round_for_round_start( + _ruleset_commissioner("default"), + round_start, + [ + ProtocolEpisodeResult( + request_id="0", + scores=[EpisodeScore(policy_version_id=policy_version_id, score=1.0)], + ) + ], + [ + ProtocolEpisodeRequest( + request_id="0", + variant_id="default", + policy_version_ids=[policy_version_id], + ) + ], + ) + + event = complete.policy_membership_events[0] + assert event.from_division_id == qualifier_id + assert event.to_division_id == competition_id + assert event.status == "competing" + assert event.substatus == "champion" + + def _round_start( *, policy_version_ids: list[UUID],