From d13150383ca3bce4d4d0b3ce4facfc77a4a88b6b Mon Sep 17 00:00:00 2001 From: uuazed Date: Sun, 10 May 2026 22:42:31 +0200 Subject: [PATCH 1/7] fix docs --- .readthedocs.yaml | 4 ++-- docs/conf.py | 2 +- docs/requirements.txt | 7 ++++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 24f15fe..37b8d21 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -7,9 +7,9 @@ version: 2 # Set the version of Python and other tools you might need build: - os: ubuntu-22.04 + os: ubuntu-24.04 tools: - python: "3.10" + python: "3.12" jobs: pre_build: - sphinx-apidoc --separate --no-toc --force -o docs/api/ numerapi diff --git a/docs/conf.py b/docs/conf.py index 435550f..cfdd147 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -42,7 +42,7 @@ "sphinx.ext.viewcode", "sphinx.ext.napoleon", 'sphinx.ext.doctest', - 'm2r' + 'sphinx_mdinclude', ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/requirements.txt b/docs/requirements.txt index 142a838..6d11b4a 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,6 @@ -m2r +sphinx-mdinclude +python-dateutil +tqdm +pandas +click +pytz From 7131ed46401cea0c1f77aa0c86cbb8b4c0fd92e8 Mon Sep 17 00:00:00 2001 From: Jeethu Rao Date: Tue, 2 Jun 2026 21:39:06 +0100 Subject: [PATCH 2/7] Increase download chunk size to 1MB --- numerapi/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/numerapi/utils.py b/numerapi/utils.py index 76985c0..b3534a4 100644 --- a/numerapi/utils.py +++ b/numerapi/utils.py @@ -14,6 +14,8 @@ logger = logging.getLogger(__name__) +DOWNLOAD_CHUNK_SIZE = 1024 * 1024 # 1 MiB + def load_secrets() -> tuple: """load secrets from environment variables or dotenv file""" @@ -96,9 +98,9 @@ def download_file(url: str, dest_path: str, show_progress_bars: bool = True): # Update progress bar to reflect how much of the file is already downloaded pbar.update(file_size) with open(temp_path, "ab") as dest_file: - for chunk in req.iter_content(1024): + for chunk in req.iter_content(DOWNLOAD_CHUNK_SIZE): dest_file.write(chunk) - pbar.update(1024) + pbar.update(len(chunk)) # move temp file to target destination os.replace(temp_path, dest_path) return dest_path From 7cd0de886826b8e4eeb12a233a83673b986cf685 Mon Sep 17 00:00:00 2001 From: Noah Harasz Date: Tue, 2 Jun 2026 14:20:08 -0700 Subject: [PATCH 3/7] bump version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 86ce77f..5b3797a 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ def load(path): return open(path, "r").read() -numerapi_version = "2.23.1" +numerapi_version = "2.23.2" classifiers = [ From 3607359b0a3b151ea4b6b3439dbff4ad55197895 Mon Sep 17 00:00:00 2001 From: Noah Harasz Date: Mon, 29 Jun 2026 08:56:31 -0700 Subject: [PATCH 4/7] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a51a548..2f4421b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog Notable changes to this project. +## [2.23.2] - 2026-06-02 +- increase dataset download chunk size to 1 MB to improve download speeds ## [2.23.1] - 2026-04-23 - fix package version lookup From b2bb495918c3ce44449e7020ad1d7bd7e45c3aec Mon Sep 17 00:00:00 2001 From: Josh Terrill Date: Tue, 30 Jun 2026 14:35:42 -0700 Subject: [PATCH 5/7] fixes models_of_account incorrect Str! type and changes it to correct String! type --- CHANGELOG.md | 3 +++ numerapi/base_api.py | 2 +- setup.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f4421b..38a193e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog Notable changes to this project. +## [2.23.3] - 2026-06-30 +- fix `models_of_account` referencing incorrect type `Str!` instead of `String!` + ## [2.23.2] - 2026-06-02 - increase dataset download chunk size to 1 MB to improve download speeds diff --git a/numerapi/base_api.py b/numerapi/base_api.py index 97071f9..963bd29 100644 --- a/numerapi/base_api.py +++ b/numerapi/base_api.py @@ -331,7 +331,7 @@ def models_of_account(self, account) -> Dict[str, str]: {'uuazed': '9b157d9b-ce61-4ab5-9413-413f13a0c0a6', ...} """ query = """ - query($username: Str! + query($username: String! $tournament: Int) { accountProfile(username: $username tournament: $tournament){ diff --git a/setup.py b/setup.py index 5b3797a..f5fe41b 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ def load(path): return open(path, "r").read() -numerapi_version = "2.23.2" +numerapi_version = "2.23.3" classifiers = [ From 9aaec7bb1dd24f9df2e4f126613625d6bae311dd Mon Sep 17 00:00:00 2001 From: "numerai-hosted-agent-bot[bot]" <247676148+numerai-hosted-agent-bot[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 20:22:47 +0000 Subject: [PATCH 6/7] T-588 migrate round payout score configuration --- CHANGELOG.md | 9 ++ docs/index.rst | 1 + docs/round-score-configs.md | 89 +++++++++++++++++ numerapi/base_api.py | 82 ++++++++++++++-- ruff.toml | 4 + setup.py | 2 +- tests/test_base_api.py | 187 ++++++++++++++++++++++++++++++++++-- 7 files changed, 358 insertions(+), 16 deletions(-) create mode 100644 docs/round-score-configs.md create mode 100644 ruff.toml diff --git a/CHANGELOG.md b/CHANGELOG.md index 38a193e..ea8fdd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Changelog Notable changes to this project. +## [2.24.0] - 2026-08-03 +- add exact `roundScoreConfigs` identities, scoring windows, and payout settings + to `list_rounds` for Classic, Signals, and Crypto +- stop querying deprecated GraphQL round multiplier fields; keep the six + established Corr/MMC return keys as exact-name compatibility projections + until their scheduled removal in numerapi 3.0.0 +- document migration from legacy round multiplier roles and isolate the + deprecated `round_model_performances_v2` behavior + ## [2.23.3] - 2026-06-30 - fix `models_of_account` referencing incorrect type `Str!` instead of `String!` diff --git a/docs/index.rst b/docs/index.rst index acd8cba..f4ad7ff 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,6 +7,7 @@ Contents :maxdepth: 2 changelog + round-score-configs license Indices and tables diff --git a/docs/round-score-configs.md b/docs/round-score-configs.md new file mode 100644 index 0000000..f42564c --- /dev/null +++ b/docs/round-score-configs.md @@ -0,0 +1,89 @@ +# Round score and payout configuration + +Starting in numerapi 2.24.0, `NumerAPI.list_rounds()`, +`SignalsAPI.list_rounds()`, and `CryptoAPI.list_rounds()` return the public +`roundScoreConfigs` list. Each item is an exact score definition and per-round +snapshot from the Tournament API. New code should select entries by `name`, +`version`, or `scoreConfigId`; it should not infer score identity from a legacy +payout role. + +Each item includes: + +- identity: `id`, `scoreConfigId`, `name`, `version`, and `displayName`; +- applicability: `roundNumberStart`, `roundNumberEnd`, `universe`, + and `isCanonScore`; +- scoring: `totalScoreDays`, `returnsLagDays`, `dataDelayDays`, + `scoringStart`, and `scoringEnd`; +- payout settings: `isPayout`, `minMultiplier`, `maxMultiplier`, + `defaultMultiplier`, `clipThreshold`, `stakeThreshold`, and `payoutFactor`. + +`scoringStart` and `scoringEnd` are returned as `datetime.datetime` objects, +consistent with other date fields in numerapi. GraphQL float and integer fields +retain their normal Python JSON types. + +## Migrating from legacy multiplier keys + +Before 2.24.0, `list_rounds()` requested server compatibility fields. For a +Signals round, a response could look like this even though the payout scores +were Alpha and MPC: + +```python +{ + "defaultCorrMultiplier": 0.3, + "defaultMmcMultiplier": 0.8, +} +``` + +In 2.24.0 the exact identities are available without knowing score names in +advance: + +```python +{ + "roundScoreConfigs": [ + { + "scoreConfigId": "...", + "name": "alpha", + "version": "2", + "displayName": "alpha", + "isPayout": True, + "defaultMultiplier": 0.3, + # Other identity, scoring, timing, and payout fields omitted. + }, + { + "scoreConfigId": "...", + "name": "meta_portfolio_contribution", + "version": "2", + "displayName": "mpc", + "isPayout": True, + "defaultMultiplier": 0.8, + }, + ], + "defaultCorrMultiplier": None, + "defaultMmcMultiplier": None, +} +``` + +The six established Corr/MMC keys (`min`, `max`, and `default` for each) stay +in the returned round dictionary throughout numerapi 2.x. They are now +identity-safe projections: Corr keys select only a payout config whose `name` +is exactly `correlation`, MMC keys select only a payout config whose `name` is +exactly `meta_model_contribution`, and the keys are `None` when there is no +exact match. Alpha and FNC are never projected as Corr; MPC is never projected +as MMC. If multiple exact payout configs exist, the projection uses the config +with the newest `roundNumberStart`, then `version` and `id`, while the complete +list remains available unchanged. + +These six compatibility keys are scheduled for removal in numerapi 3.0.0. +`list_rounds()` never exposed the three legacy TC multiplier fields, so this +migration does not introduce them. Code should migrate now by filtering +`roundScoreConfigs`, normally starting with `isPayout`. + +## Deprecated performance endpoint + +`round_model_performances_v2()` remains an isolated deprecated compatibility +method. Its `corrMultiplier` and `mmcMultiplier` fields come from the deprecated +`v2RoundModelPerformances` GraphQL endpoint and must not be used to infer score +identity. Use `submission_scores()` for identity-preserving score results and +join them to `list_rounds()` by round when payout configuration is needed. +Neither performance method nor `list_rounds()` has a dedicated CLI command, so +there is no CLI return shape to migrate. diff --git a/numerapi/base_api.py b/numerapi/base_api.py index 963bd29..437578b 100644 --- a/numerapi/base_api.py +++ b/numerapi/base_api.py @@ -637,7 +637,16 @@ def list_rounds( limit (int, optional): maximum number of rounds to return Returns: - list of dicts: round entries matching the provided filters + list of dicts: round entries matching the provided filters. Each + entry includes ``roundScoreConfigs``, whose items retain the exact + score identity and per-round payout settings returned by the API. + + The legacy ``minCorrMultiplier`` through + ``defaultMmcMultiplier`` keys remain until numerapi 3.0.0. They are + compatibility projections of payout configs whose names are + exactly ``correlation`` or ``meta_model_contribution``; they are + ``None`` when no such payout config exists. Use + ``roundScoreConfigs`` for all new integrations. """ query = """ query($tournament: Int @@ -663,13 +672,30 @@ def list_rounds( resolvedStaking payoutFactor stakeThreshold - minCorrMultiplier - maxCorrMultiplier - defaultCorrMultiplier - minMmcMultiplier - maxMmcMultiplier - defaultMmcMultiplier dataDatestamp + roundScoreConfigs { + id + scoreConfigId + roundNumberStart + roundNumberEnd + name + version + displayName + totalScoreDays + returnsLagDays + dataDelayDays + universe + isCanonScore + isPayout + scoringStart + scoringEnd + minMultiplier + maxMultiplier + defaultMultiplier + clipThreshold + stakeThreshold + payoutFactor + } } } """ @@ -691,8 +717,50 @@ def list_rounds( ]: utils.replace(round_info, field, utils.parse_datetime_string) utils.replace(round_info, "payoutFactor", utils.parse_float_string) + for config in round_info["roundScoreConfigs"]: + utils.replace( + config, "scoringStart", utils.parse_datetime_string + ) + utils.replace( + config, "scoringEnd", utils.parse_datetime_string + ) + self._add_legacy_round_multipliers(round_info) return rounds + @staticmethod + def _add_legacy_round_multipliers(round_info: dict) -> None: + """Add deprecated, identity-safe round multiplier projections.""" + legacy_scores = { + "Corr": "correlation", + "Mmc": "meta_model_contribution", + } + multiplier_fields = { + "min": "minMultiplier", + "max": "maxMultiplier", + "default": "defaultMultiplier", + } + + for legacy_name, score_name in legacy_scores.items(): + matches = [ + config + for config in round_info["roundScoreConfigs"] + if config["isPayout"] and config["name"] == score_name + ] + config = max( + matches, + key=lambda item: ( + item["roundNumberStart"], + item["version"], + item["id"], + ), + default=None, + ) + for prefix, config_field in multiplier_fields.items(): + field = f"{prefix}{legacy_name}Multiplier" + round_info[field] = ( + None if config is None else config[config_field] + ) + def set_bio(self, model_id: str, bio: str) -> bool: """Set bio field for a model id. diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..04ab7be --- /dev/null +++ b/ruff.toml @@ -0,0 +1,4 @@ +[lint] +# Keep the repository's historical lint baseline stable while the CI action +# follows unpinned Ruff releases. +select = ["E4", "E7", "E9", "F"] diff --git a/setup.py b/setup.py index f5fe41b..8ff0e8a 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ def load(path): return open(path, "r").read() -numerapi_version = "2.23.3" +numerapi_version = "2.24.0" classifiers = [ diff --git a/tests/test_base_api.py b/tests/test_base_api.py index 84aa996..518bc73 100644 --- a/tests/test_base_api.py +++ b/tests/test_base_api.py @@ -5,6 +5,7 @@ import pytest import responses +import numerapi from numerapi import base_api @@ -14,6 +15,42 @@ def api_fixture(): return api +def _round_score_config( + name, + *, + config_id=None, + display_name=None, + version="1", + is_payout=True, + multiplier=0.5, + round_number_start=100, +): + config_id = config_id or f"{name}-{version}" + return { + "id": f"round-{config_id}", + "scoreConfigId": config_id, + "roundNumberStart": round_number_start, + "roundNumberEnd": None, + "name": name, + "version": version, + "displayName": display_name or name, + "totalScoreDays": 20, + "returnsLagDays": 2, + "dataDelayDays": 2, + "universe": "test-universe", + "isCanonScore": False, + "isPayout": is_payout, + "scoringStart": "2026-03-29", + "scoringEnd": "2026-04-18", + "minMultiplier": multiplier, + "maxMultiplier": multiplier, + "defaultMultiplier": multiplier, + "clipThreshold": 0.05, + "stakeThreshold": 100.0, + "payoutFactor": 1.0, + } + + def test_NumerAPI(): # invalid log level should raise with pytest.raises(AttributeError): @@ -180,7 +217,21 @@ def test_submission_scores(api): @responses.activate def test_list_rounds(api): - api.tournament_id = 11 + api.tournament_id = 8 + configs = [ + _round_score_config( + "correlation", + config_id="classic-corr", + display_name="v2_corr20", + multiplier=0.75, + ), + _round_score_config( + "meta_model_contribution", + config_id="classic-mmc", + display_name="mmc", + multiplier=2.25, + ), + ] data = { "data": { "rounds": [ @@ -198,13 +249,8 @@ def test_list_rounds(api): "resolvedStaking": False, "payoutFactor": "0.8", "stakeThreshold": 0.1, - "minCorrMultiplier": 0.0, - "maxCorrMultiplier": 1.0, - "defaultCorrMultiplier": 0.5, - "minMmcMultiplier": 0.0, - "maxMmcMultiplier": 1.0, - "defaultMmcMultiplier": 0.5, "dataDatestamp": 20260320, + "roundScoreConfigs": configs, } ] } @@ -220,13 +266,138 @@ def test_list_rounds(api): assert isinstance(res[0]["scoreTime"], datetime.datetime) assert isinstance(res[0]["resolveTime"], datetime.datetime) assert isinstance(res[0]["payoutFactor"], decimal.Decimal) + assert isinstance( + res[0]["roundScoreConfigs"][0]["scoringStart"], datetime.datetime + ) + assert isinstance( + res[0]["roundScoreConfigs"][0]["scoringEnd"], datetime.datetime + ) + assert res[0]["roundScoreConfigs"][0]["scoreConfigId"] == "classic-corr" + assert res[0]["defaultCorrMultiplier"] == 0.75 + assert res[0]["defaultMmcMultiplier"] == 2.25 request_body = json.loads(responses.calls[0].request.body) - assert request_body["variables"]["tournament"] == 11 + assert request_body["variables"]["tournament"] == 8 assert request_body["variables"]["number"] == 123 assert request_body["variables"]["target"] == "main" assert request_body["variables"]["status"] == "OPEN" assert request_body["variables"]["limit"] == 5 + requested_fields = { + "id", + "scoreConfigId", + "roundNumberStart", + "roundNumberEnd", + "name", + "version", + "displayName", + "totalScoreDays", + "returnsLagDays", + "dataDelayDays", + "universe", + "isCanonScore", + "isPayout", + "scoringStart", + "scoringEnd", + "minMultiplier", + "maxMultiplier", + "defaultMultiplier", + "clipThreshold", + "stakeThreshold", + "payoutFactor", + } + assert "roundScoreConfigs" in request_body["query"] + assert all(field in request_body["query"] for field in requested_fields) + assert "minCorrMultiplier" not in request_body["query"] + assert "minMmcMultiplier" not in request_body["query"] + + +@pytest.mark.parametrize( + ("api_class", "tournament", "score_names", "legacy_multipliers"), + [ + ( + numerapi.NumerAPI, + 8, + ["correlation", "meta_model_contribution"], + (0.5, 0.5), + ), + ( + numerapi.SignalsAPI, + 11, + [ + "alpha", + "v4_feature_neutral_correlation", + "meta_portfolio_contribution", + ], + (None, None), + ), + ( + numerapi.CryptoAPI, + 12, + ["correlation", "meta_model_contribution"], + (0.5, 0.5), + ), + ], +) +@responses.activate +def test_list_rounds_preserves_tournament_score_identities( + api_class, tournament, score_names, legacy_multipliers +): + api = api_class() + configs = [_round_score_config(name) for name in score_names] + responses.add( + responses.POST, + base_api.API_TOURNAMENT_URL, + json={"data": {"rounds": [{"roundScoreConfigs": configs}]}}, + ) + + result = api.list_rounds() + + returned_round = result[0] + assert [ + config["name"] for config in returned_round["roundScoreConfigs"] + ] == score_names + assert returned_round["defaultCorrMultiplier"] == legacy_multipliers[0] + assert returned_round["defaultMmcMultiplier"] == legacy_multipliers[1] + request_body = json.loads(responses.calls[0].request.body) + assert request_body["variables"]["tournament"] == tournament + + +@responses.activate +def test_list_rounds_keeps_coexisting_and_unfamiliar_score_configs(api): + configs = [ + _round_score_config("alpha", multiplier=0.3), + _round_score_config( + "correlation", + config_id="corr-old", + version="1", + multiplier=0.2, + round_number_start=100, + ), + _round_score_config( + "correlation", + config_id="corr-new", + version="2", + multiplier=0.4, + round_number_start=200, + ), + _round_score_config("meta_portfolio_contribution", multiplier=0.8), + _round_score_config("meta_model_contribution", multiplier=0.6), + _round_score_config("unfamiliar_score", multiplier=0.9), + ] + responses.add( + responses.POST, + base_api.API_TOURNAMENT_URL, + json={"data": {"rounds": [{"roundScoreConfigs": configs}]}}, + ) + + returned_round = api.list_rounds()[0] + + assert [ + config["scoreConfigId"] + for config in returned_round["roundScoreConfigs"] + ] == [config["scoreConfigId"] for config in configs] + assert returned_round["defaultCorrMultiplier"] == 0.4 + assert returned_round["defaultMmcMultiplier"] == 0.6 @responses.activate From d7e7edd7af55f74d8caac7e8079337cb8116dccb Mon Sep 17 00:00:00 2001 From: "numerai-hosted-agent-bot[bot]" <247676148+numerai-hosted-agent-bot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:03:40 +0000 Subject: [PATCH 7/7] T-588 order score versions numerically --- docs/round-score-configs.md | 7 ++-- numerapi/base_api.py | 32 +++++++++++++----- tests/test_base_api.py | 66 +++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 11 deletions(-) diff --git a/docs/round-score-configs.md b/docs/round-score-configs.md index f42564c..99db25d 100644 --- a/docs/round-score-configs.md +++ b/docs/round-score-configs.md @@ -70,8 +70,11 @@ is exactly `correlation`, MMC keys select only a payout config whose `name` is exactly `meta_model_contribution`, and the keys are `None` when there is no exact match. Alpha and FNC are never projected as Corr; MPC is never projected as MMC. If multiple exact payout configs exist, the projection uses the config -with the newest `roundNumberStart`, then `version` and `id`, while the complete -list remains available unchanged. +with the newest `roundNumberStart`, then compares the numeric `version` values +as integers and uses `id` for a numeric-version tie. If multiple configs at the +newest start contain a non-numeric future version, the compatibility keys are +`None` rather than guessing an order. The complete list remains available +unchanged in either case. These six compatibility keys are scheduled for removal in numerapi 3.0.0. `list_rounds()` never exposed the three legacy TC multiplier fields, so this diff --git a/numerapi/base_api.py b/numerapi/base_api.py index 437578b..d58d7b0 100644 --- a/numerapi/base_api.py +++ b/numerapi/base_api.py @@ -746,21 +746,35 @@ def _add_legacy_round_multipliers(round_info: dict) -> None: for config in round_info["roundScoreConfigs"] if config["isPayout"] and config["name"] == score_name ] - config = max( - matches, - key=lambda item: ( - item["roundNumberStart"], - item["version"], - item["id"], - ), - default=None, - ) + config = Api._select_legacy_round_config(matches) for prefix, config_field in multiplier_fields.items(): field = f"{prefix}{legacy_name}Multiplier" round_info[field] = ( None if config is None else config[config_field] ) + @staticmethod + def _select_legacy_round_config(configs: List[Dict]) -> Dict | None: + """Select the latest config, failing closed on ambiguous versions.""" + if not configs: + return None + + latest_start = max(item["roundNumberStart"] for item in configs) + candidates = [ + item for item in configs if item["roundNumberStart"] == latest_start + ] + if len(candidates) == 1: + return candidates[0] + + try: + return max( + candidates, + key=lambda item: (int(item["version"]), item["id"]), + ) + except (TypeError, ValueError): + # A future non-numeric version contract cannot be ordered safely. + return None + def set_bio(self, model_id: str, bio: str) -> bool: """Set bio field for a model id. diff --git a/tests/test_base_api.py b/tests/test_base_api.py index 518bc73..9f30a58 100644 --- a/tests/test_base_api.py +++ b/tests/test_base_api.py @@ -400,6 +400,72 @@ def test_list_rounds_keeps_coexisting_and_unfamiliar_score_configs(api): assert returned_round["defaultMmcMultiplier"] == 0.6 +@responses.activate +def test_list_rounds_orders_numeric_score_versions_numerically(api): + configs = [ + _round_score_config( + "correlation", + config_id="corr-9", + version="9", + multiplier=0.9, + round_number_start=200, + ), + _round_score_config( + "correlation", + config_id="corr-10", + version="10", + multiplier=1.0, + round_number_start=200, + ), + ] + responses.add( + responses.POST, + base_api.API_TOURNAMENT_URL, + json={"data": {"rounds": [{"roundScoreConfigs": configs}]}}, + ) + + returned_round = api.list_rounds()[0] + + assert [ + returned_round["minCorrMultiplier"], + returned_round["maxCorrMultiplier"], + returned_round["defaultCorrMultiplier"], + ] == [1.0, 1.0, 1.0] + + +@responses.activate +def test_list_rounds_fails_closed_for_ambiguous_non_numeric_versions(api): + configs = [ + _round_score_config( + "correlation", + config_id="corr-10", + version="10", + multiplier=1.0, + round_number_start=200, + ), + _round_score_config( + "correlation", + config_id="corr-next", + version="next", + multiplier=1.1, + round_number_start=200, + ), + ] + responses.add( + responses.POST, + base_api.API_TOURNAMENT_URL, + json={"data": {"rounds": [{"roundScoreConfigs": configs}]}}, + ) + + returned_round = api.list_rounds()[0] + + assert [ + returned_round["minCorrMultiplier"], + returned_round["maxCorrMultiplier"], + returned_round["defaultCorrMultiplier"], + ] == [None, None, None] + + @responses.activate def test_list_rounds_uses_api_tournament_id_by_default(api): api.tournament_id = 11