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
28 changes: 28 additions & 0 deletions docs/simulation_competitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,30 @@ kaggle competitions logs 98765432 1 -p ./logs

This downloads the log file as `episode-98765432-agent-0-logs.json`.

## 9. Inspect Top Teams' Active Agents

You can study how the leading teams' agents are performing — useful for scouting strategies or understanding the metagame. Start from the leaderboard to grab the team ID:

```bash
kaggle competitions leaderboard connectx -s
```

This prints a table with columns `teamId`, `teamName`, `submissionDate`, `score`. Take the `teamId` of the team you want to inspect (e.g., first place), then list every active submission they have on the leaderboard:

```bash
kaggle competitions team-submissions 42
```

This returns the team's public-safe submissions — `id`, `dateSubmitted`, and `publicScore`. For simulation competitions every leaderboard-eligible submission is listed (not just the best one), so you can see the full rotation of agents a top team is fielding.

Pick the submission with the highest `publicScore` and list its episodes, just like you would for your own:

```bash
kaggle competitions episodes 98765432
```

From there you can pull replays and agent logs for any episode that submission played in (`kaggle competitions replay <episode_id>` / `kaggle competitions logs <episode_id> <agent_index>`).

## Putting It All Together

Here's a typical workflow for iterating on a simulation competition agent:
Expand Down Expand Up @@ -167,4 +191,8 @@ kaggle competitions logs 98765432 0

# Check the leaderboard
kaggle competitions leaderboard connectx -s

# Scout the leader: list their active agents, then pick the best one's episodes
kaggle competitions team-submissions <leader-team-id>
kaggle competitions episodes <best-submission-id>
```
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ keywords = ["Kaggle", "API"]
requires-python = ">= 3.11"
dependencies = [
"bleach",
"kagglesdk >= 0.1.26, < 1.0", # sync with kagglehub
"kagglesdk >= 0.1.27, < 1.0", # sync with kagglehub
"python-slugify",
"requests",
"python-dateutil",
Expand Down
41 changes: 41 additions & 0 deletions src/kaggle/api/kaggle_api_extended.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
ApiCreateSubmissionRequest,
ApiSubmission,
ApiListSubmissionsRequest,
ApiListTeamPublicSubmissionsRequest,
ApiListDataFilesResponse,
ApiListDataFilesRequest,
ApiDownloadDataFileRequest,
Expand Down Expand Up @@ -2037,6 +2038,46 @@ def competition_leaderboard_cli(
else:
print("No results found")

team_public_submission_fields = ["id", "dateSubmitted", "publicScore"]

def competition_team_submissions(self, team_id: int):
"""List the public-safe submissions for a team.

For simulation competitions this returns every active
(leaderboard-eligible) submission for the team. For regular competitions
it returns the single submission currently on the public leaderboard
(or an empty list if the team has none).

Args:
team_id (int): The team ID (find these with
"kaggle competitions leaderboard <competition> --show").

Returns:
list: A list of ApiPublicSubmission objects.
"""
with self.build_kaggle_client() as kaggle:
request = ApiListTeamPublicSubmissionsRequest()
request.team_id = team_id
response = kaggle.competitions.competition_api_client.list_team_public_submissions(request)
return response.submissions

def competition_team_submissions_cli(self, team_id, csv_display=False, quiet=False):
"""CLI wrapper for competition_team_submissions.

Args:
team_id (int): The team ID.
csv_display (bool): If True, print CSV instead of table.
quiet (bool): Suppress verbose output.
"""
submissions = self.competition_team_submissions(team_id)
if not submissions:
print("No submissions found")
return
if csv_display:
self.print_csv(submissions, self.team_public_submission_fields)
else:
self.print_table(submissions, self.team_public_submission_fields)

def competition_list_episodes(self, submission_id: int):
"""List episodes for a submission in a simulation competition.

Expand Down
23 changes: 23 additions & 0 deletions src/kaggle/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,27 @@ def parse_competitions(subparsers) -> None:
parser_competitions_leaderboard._action_groups.append(parser_competitions_leaderboard_optional)
parser_competitions_leaderboard.set_defaults(func=api.competition_leaderboard_cli)

# Competitions list team public submissions
parser_competitions_team_submissions = subparsers_competitions.add_parser(
"team-submissions",
formatter_class=argparse.RawTextHelpFormatter,
help=Help.command_competitions_team_submissions,
)
parser_competitions_team_submissions_optional = parser_competitions_team_submissions._action_groups.pop()
parser_competitions_team_submissions_optional.add_argument(
"team_id",
type=int,
help='Team ID (find these with "kaggle competitions leaderboard <competition> --show")',
)
parser_competitions_team_submissions_optional.add_argument(
"-v", "--csv", dest="csv_display", action="store_true", help=Help.param_csv
)
parser_competitions_team_submissions_optional.add_argument(
"-q", "--quiet", dest="quiet", action="store_true", help=Help.param_quiet
)
parser_competitions_team_submissions._action_groups.append(parser_competitions_team_submissions_optional)
parser_competitions_team_submissions.set_defaults(func=api.competition_team_submissions_cli)

# Competitions list episodes
parser_competitions_episodes = subparsers_competitions.add_parser(
"episodes", formatter_class=argparse.RawTextHelpFormatter, help=Help.command_competitions_episodes
Expand Down Expand Up @@ -1841,6 +1862,7 @@ class Help(object):
"submit",
"submissions",
"leaderboard",
"team-submissions",
"episodes",
"replay",
"logs",
Expand Down Expand Up @@ -1958,6 +1980,7 @@ class Help(object):
command_competitions_submit = "Make a new competition submission"
command_competitions_submissions = "Show your competition submissions"
command_competitions_leaderboard = "Get competition leaderboard information"
command_competitions_team_submissions = "List a team's public submissions (every active submission for simulation competitions, or the public leaderboard submission for regular competitions)"
command_competitions_episodes = "List episodes for a submission in a simulation competition"
command_competitions_episode_replay = "Download the replay for a simulation episode"
command_competitions_episode_logs = "Download agent logs for a simulation episode"
Expand Down
98 changes: 98 additions & 0 deletions tests/test_team_submissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# coding=utf-8
import io
import sys
import unittest
from unittest.mock import MagicMock, patch

sys.path.insert(0, "..")

from kaggle.api.kaggle_api_extended import KaggleApi


def _mock_submission(id_, date_iso, public_score):
sub = MagicMock()
sub.id = id_
sub.date_submitted = date_iso
sub.public_score = public_score
return sub


def _build_response(submissions):
response = MagicMock()
response.submissions = submissions
return response


class TestTeamPublicSubmissions(unittest.TestCase):
"""Tests for competition_team_submissions and its CLI wrapper."""

def setUp(self):
self.api = KaggleApi.__new__(KaggleApi)

@patch.object(KaggleApi, "build_kaggle_client")
def test_competition_team_submissions_returns_list(self, mock_client):
expected = [_mock_submission(1, "2026-01-01T00:00:00Z", "50.5")]
response = _build_response(expected)
mock_kaggle = MagicMock()
mock_kaggle.competitions.competition_api_client.list_team_public_submissions.return_value = response
mock_client.return_value.__enter__ = MagicMock(return_value=mock_kaggle)
mock_client.return_value.__exit__ = MagicMock(return_value=False)

result = self.api.competition_team_submissions(team_id=42)

self.assertEqual(result, expected)
called_request = (
mock_kaggle.competitions.competition_api_client.list_team_public_submissions.call_args[0][0]
)
self.assertEqual(called_request.team_id, 42)

@patch.object(KaggleApi, "competition_team_submissions")
def test_cli_table_output(self, mock_view):
mock_view.return_value = [
_mock_submission(11, "2026-01-02T00:00:00Z", "100.0"),
_mock_submission(22, "2026-01-01T00:00:00Z", "50.0"),
]

captured = io.StringIO()
sys.stdout = captured
try:
self.api.competition_team_submissions_cli(team_id=42)
finally:
sys.stdout = sys.__stdout__

output = captured.getvalue()
self.assertIn("11", output)
self.assertIn("22", output)
self.assertIn("100.0", output)

@patch.object(KaggleApi, "competition_team_submissions")
def test_cli_csv_output(self, mock_view):
mock_view.return_value = [_mock_submission(11, "2026-01-02T00:00:00Z", "100.0")]

captured = io.StringIO()
sys.stdout = captured
try:
self.api.competition_team_submissions_cli(team_id=42, csv_display=True)
finally:
sys.stdout = sys.__stdout__

lines = [line for line in captured.getvalue().splitlines() if line]
self.assertEqual(lines[0], "id,dateSubmitted,publicScore")
self.assertEqual(lines[1], "11,2026-01-02T00:00:00Z,100.0")

@patch.object(KaggleApi, "competition_team_submissions")
def test_cli_empty(self, mock_view):
mock_view.return_value = []

captured = io.StringIO()
sys.stdout = captured
try:
self.api.competition_team_submissions_cli(team_id=42)
finally:
sys.stdout = sys.__stdout__

self.assertIn("No submissions found", captured.getvalue())


if __name__ == "__main__":
unittest.main()
Loading