Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/orchestration/_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@
)
QUESTION_SET_READ_TIMEOUT_SECONDS = 30

# Submissions with a forecast due date on or after this date must provide a `url`
# field with the submitting organization's website, unless the submission is
# anonymous. Forecast sets due before this date predate the requirement and are
# still processed without one.
URL_REQUIRED_AS_OF_DUE_DATE = "2026-08-01"

ANONYMOUS_ORGANIZATION_RE = re.compile(r"^anonymous(\s+\d+)?$", re.IGNORECASE)


# ---------------------------------------------------------------------------
# Question bank loading
Expand Down Expand Up @@ -457,6 +465,10 @@ def get_valid_forecast_files_and_dates(
def read_forecast_file(filename: str, f: TextIO | None = None) -> dict | None:
"""Read a forecast JSON file and validate its content.

The optional `url` field, when present, must be an http(s) URL. It is required
for non-anonymous submissions with a forecast due date on or after
URL_REQUIRED_AS_OF_DUE_DATE.

Args:
filename: Path to the forecast JSON file.
f: Open file handle. If None, filename will be opened.
Expand Down Expand Up @@ -493,6 +505,32 @@ def read_forecast_file(filename: str, f: TextIO | None = None) -> dict | None:
)
return None

url = data.get("url")
if url is not None and (
not isinstance(url, str) or not url.startswith(("http://", "https://"))
):
logger.error(
colored(
f"Problem processing {filename}. `url` must be an http(s) URL, got: {url}.",
"yellow",
)
)
return None

if (
not url
and forecast_due_date >= URL_REQUIRED_AS_OF_DUE_DATE
and not ANONYMOUS_ORGANIZATION_RE.match(organization.strip())
):
logger.error(
colored(
f"Problem processing {filename}. Missing required field `url`; it is "
"required for non-anonymous submissions.",
"yellow",
)
)
return None

df = pd.DataFrame(forecasts)
if df.empty:
logger.error(
Expand Down
1 change: 1 addition & 0 deletions src/orchestration/func_resolve/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"model_run_slug",
"forecast_variant_key",
"uses_freeze_values",
"url",
]


Expand Down
59 changes: 59 additions & 0 deletions src/tests/orchestration/test_forecast_file_io.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,70 @@
"""Tests for processed forecast file IO."""

import io
import json

from helpers import env
from orchestration import _io


def _forecast_file_handle(**overrides) -> io.StringIO:
"""Return a file handle for a forecast submission with sensible defaults."""
data = {
"organization": "Test Org",
"model": "Test Model",
"model_organization": "Test Model Org",
"question_set": "2026-09-01-llm.json",
"forecasts": [{"id": "q1", "forecast": 0.5}],
}
data.update(overrides)
data = {k: v for k, v in data.items() if v is not None}
return io.StringIO(json.dumps(data))


def test_read_forecast_file_passes_url_through():
data = _io.read_forecast_file(
"forecast.json",
f=_forecast_file_handle(url="https://example.org"),
)

assert data is not None
assert data["url"] == "https://example.org"


def test_read_forecast_file_requires_url_for_new_non_anonymous_submissions():
data = _io.read_forecast_file("forecast.json", f=_forecast_file_handle())

assert data is None


def test_read_forecast_file_allows_missing_url_for_anonymous_submissions():
data = _io.read_forecast_file(
"forecast.json",
f=_forecast_file_handle(organization="Anonymous 12"),
)

assert data is not None
assert "url" not in data


def test_read_forecast_file_allows_missing_url_for_old_forecast_sets():
data = _io.read_forecast_file(
"forecast.json",
f=_forecast_file_handle(question_set="2026-05-24-llm.json"),
)

assert data is not None


def test_read_forecast_file_rejects_non_http_url():
data = _io.read_forecast_file(
"forecast.json",
f=_forecast_file_handle(url="example.org"),
)

assert data is None


def test_valid_forecast_files_excludes_nested_test_files(monkeypatch):
"""Do not include date-folder test forecast files in leaderboard inputs."""
monkeypatch.setattr(
Expand Down