everestapi 0.2.5__tar.gz → 0.2.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {everestapi-0.2.5/src/everestapi.egg-info → everestapi-0.2.7}/PKG-INFO +60 -6
  2. {everestapi-0.2.5 → everestapi-0.2.7}/README.md +52 -5
  3. {everestapi-0.2.5 → everestapi-0.2.7}/pyproject.toml +4 -1
  4. {everestapi-0.2.5 → everestapi-0.2.7}/src/everestapi/__init__.py +12 -1
  5. {everestapi-0.2.5 → everestapi-0.2.7}/src/everestapi/cli.py +1 -1
  6. {everestapi-0.2.5 → everestapi-0.2.7}/src/everestapi/client.py +101 -31
  7. {everestapi-0.2.5 → everestapi-0.2.7}/src/everestapi/mcp/server.py +687 -43
  8. {everestapi-0.2.5 → everestapi-0.2.7}/src/everestapi/plots.py +4 -5
  9. everestapi-0.2.7/src/everestapi/scoring.py +197 -0
  10. {everestapi-0.2.5 → everestapi-0.2.7/src/everestapi.egg-info}/PKG-INFO +60 -6
  11. {everestapi-0.2.5 → everestapi-0.2.7}/src/everestapi.egg-info/SOURCES.txt +6 -1
  12. {everestapi-0.2.5 → everestapi-0.2.7}/src/everestapi.egg-info/requires.txt +8 -0
  13. {everestapi-0.2.5 → everestapi-0.2.7}/tests/test_client.py +116 -5
  14. everestapi-0.2.7/tests/test_eve953_mcp_progress.py +176 -0
  15. everestapi-0.2.7/tests/test_eve957_mcp_annotations_resources.py +75 -0
  16. everestapi-0.2.7/tests/test_eve959_toolsets.py +72 -0
  17. everestapi-0.2.7/tests/test_mcp_and_models.py +570 -0
  18. {everestapi-0.2.5 → everestapi-0.2.7}/tests/test_prediction_range.py +2 -2
  19. everestapi-0.2.7/tests/test_scoring.py +112 -0
  20. everestapi-0.2.5/tests/test_mcp_and_models.py +0 -195
  21. {everestapi-0.2.5 → everestapi-0.2.7}/LICENSE +0 -0
  22. {everestapi-0.2.5 → everestapi-0.2.7}/setup.cfg +0 -0
  23. {everestapi-0.2.5 → everestapi-0.2.7}/src/everestapi/__main__.py +0 -0
  24. {everestapi-0.2.5 → everestapi-0.2.7}/src/everestapi/mcp/__init__.py +0 -0
  25. {everestapi-0.2.5 → everestapi-0.2.7}/src/everestapi/mcp/__main__.py +0 -0
  26. {everestapi-0.2.5 → everestapi-0.2.7}/src/everestapi/types.py +0 -0
  27. {everestapi-0.2.5 → everestapi-0.2.7}/src/everestapi.egg-info/dependency_links.txt +0 -0
  28. {everestapi-0.2.5 → everestapi-0.2.7}/src/everestapi.egg-info/entry_points.txt +0 -0
  29. {everestapi-0.2.5 → everestapi-0.2.7}/src/everestapi.egg-info/top_level.txt +0 -0
  30. {everestapi-0.2.5 → everestapi-0.2.7}/tests/test_cli.py +0 -0
  31. {everestapi-0.2.5 → everestapi-0.2.7}/tests/test_diagnostics.py +0 -0
  32. {everestapi-0.2.5 → everestapi-0.2.7}/tests/test_json_or_raise.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: everestapi
3
- Version: 0.2.5
3
+ Version: 0.2.7
4
4
  Summary: Python SDK for the Everesteer prediction tournament platform
5
5
  Author-email: Everesteer <support@everesteer.ai>
6
6
  License-Expression: MIT
@@ -24,10 +24,17 @@ Requires-Dist: click>=8.0
24
24
  Provides-Extra: dev
25
25
  Requires-Dist: pytest>=8.0; extra == "dev"
26
26
  Requires-Dist: pytest-httpx>=0.34.0; extra == "dev"
27
+ Requires-Dist: numpy>=1.26; extra == "dev"
28
+ Requires-Dist: pandas>=2.0; extra == "dev"
29
+ Requires-Dist: scipy>=1.10; extra == "dev"
27
30
  Provides-Extra: mcp
28
31
  Requires-Dist: mcp>=1.0; extra == "mcp"
29
32
  Provides-Extra: viz
30
33
  Requires-Dist: plotnine>=0.13; extra == "viz"
34
+ Provides-Extra: scoring
35
+ Requires-Dist: numpy>=1.26; extra == "scoring"
36
+ Requires-Dist: pandas>=2.0; extra == "scoring"
37
+ Requires-Dist: scipy>=1.10; extra == "scoring"
31
38
  Dynamic: license-file
32
39
 
33
40
  # everestapi
@@ -89,8 +96,11 @@ api.submit_futures_predictions(model_id="my-fut-model", predictions={...})
89
96
  Both Parquet and CSV are accepted. **Parquet is recommended** — float precision round-trips cleanly, files compress well, and it matches the format the SDK serves to you (`download_dataset` returns parquet).
90
97
 
91
98
  ```python
99
+ # A model slot must exist before you can submit — the platform never auto-creates one.
100
+ api.create_model(name="my-model")
101
+
92
102
  api.submit_predictions_file(
93
- model_name="my-model",
103
+ model_id="my-model",
94
104
  file_path="predictions.parquet", # or "predictions.csv"
95
105
  tournament="equities",
96
106
  )
@@ -168,16 +178,59 @@ api.get_stake_balance(model_id="my-model")
168
178
  api.claim_payout(model_id="my-model", round_id="42")
169
179
  ```
170
180
 
171
- ### Local evaluation
181
+ ### Score validation predictions offline
182
+
183
+ Reproduce the server's **exact** scoring — CORR20, AIMC20, FNC — *before* you
184
+ submit, so you stop guessing the sign of your signal ("submit raw and negated, let
185
+ the server decide"). The `everestapi.scoring` functions are a verbatim port of the
186
+ platform's scoring engine (verified equal to 1e-12), so your offline number **is**
187
+ the server's number.
188
+
189
+ Install the optional scoring extra (keeps the base SDK light — numpy/pandas/scipy
190
+ are only pulled in here):
191
+
192
+ ```bash
193
+ pip install "everestapi[scoring]"
194
+ ```
172
195
 
173
196
  ```python
174
197
  import pandas as pd
198
+ from everestapi import scoring
175
199
 
176
- val = pd.read_parquet("futures_validation.parquet")
177
- metrics = EverestAPI.evaluate(predictions, val, target="target_everest_20")
178
- print(metrics) # {"simple_corr": 0.023, "weighted_corr": 0.019, "per_difficulty": {...}}
200
+ val = pd.read_parquet("eiq_validation.parquet")
201
+ preds = my_model.predict(val.filter(like="feature_"))
202
+
203
+ # Score per exped (cross-section), then average — matches how the server scores.
204
+ per_exped = [
205
+ scoring.corr20(preds[val.exped == e], val.loc[val.exped == e, "target"])
206
+ for e in val.exped.unique()
207
+ ]
208
+ print("mean CORR20:", sum(per_exped) / len(per_exped))
209
+
210
+ # Or every metric at once for one exped (ai_model = crowd consensus for that exped):
211
+ scoring.score(preds_e, target_e, ai_model=consensus_e, features=features_e)
212
+ # -> {"corr20", "aimc20", "payout", "fnc", "feature_exposure"}
179
213
  ```
180
214
 
215
+ **Sanity-check your pipeline against the example predictions.** The published
216
+ `eiq_validation_example_preds` are a benchmark-grade signal (the Minera ensemble)
217
+ and score a positive mean **CORR20 of ≈ 0.07**. Score that file and reproduce a
218
+ similar number — if you instead get ≈ −0.07, your sign is flipped; if you get ≈ 0,
219
+ your ids/alignment are off:
220
+
221
+ ```python
222
+ ex = pd.read_parquet("eiq_validation_example_preds.parquet") # column: prediction
223
+ val = pd.read_parquet("eiq_validation.parquet")
224
+ ref = [
225
+ scoring.corr20(ex.loc[val.exped == e, "prediction"], val.loc[val.exped == e, "target"])
226
+ for e in val.exped.unique()
227
+ ]
228
+ print(sum(ref) / len(ref)) # ~0.07 -> pipeline + sign are correct
229
+ ```
230
+
231
+ A quick convenience for a single overall correlation is also available:
232
+ `EverestAPI.evaluate(predictions, val, target="target_everest_20")`.
233
+
181
234
  ### CLI
182
235
 
183
236
  ```bash
@@ -207,6 +260,7 @@ with EverestAPI(api_key="...") as api:
207
260
 
208
261
  - Python 3.10+
209
262
  - httpx >= 0.27
263
+ - Optional `scoring` extra (`pip install "everestapi[scoring]"`): numpy, pandas, scipy — only needed for offline `everestapi.scoring`.
210
264
 
211
265
  ## Disclaimers
212
266
 
@@ -57,8 +57,11 @@ api.submit_futures_predictions(model_id="my-fut-model", predictions={...})
57
57
  Both Parquet and CSV are accepted. **Parquet is recommended** — float precision round-trips cleanly, files compress well, and it matches the format the SDK serves to you (`download_dataset` returns parquet).
58
58
 
59
59
  ```python
60
+ # A model slot must exist before you can submit — the platform never auto-creates one.
61
+ api.create_model(name="my-model")
62
+
60
63
  api.submit_predictions_file(
61
- model_name="my-model",
64
+ model_id="my-model",
62
65
  file_path="predictions.parquet", # or "predictions.csv"
63
66
  tournament="equities",
64
67
  )
@@ -136,16 +139,59 @@ api.get_stake_balance(model_id="my-model")
136
139
  api.claim_payout(model_id="my-model", round_id="42")
137
140
  ```
138
141
 
139
- ### Local evaluation
142
+ ### Score validation predictions offline
143
+
144
+ Reproduce the server's **exact** scoring — CORR20, AIMC20, FNC — *before* you
145
+ submit, so you stop guessing the sign of your signal ("submit raw and negated, let
146
+ the server decide"). The `everestapi.scoring` functions are a verbatim port of the
147
+ platform's scoring engine (verified equal to 1e-12), so your offline number **is**
148
+ the server's number.
149
+
150
+ Install the optional scoring extra (keeps the base SDK light — numpy/pandas/scipy
151
+ are only pulled in here):
152
+
153
+ ```bash
154
+ pip install "everestapi[scoring]"
155
+ ```
140
156
 
141
157
  ```python
142
158
  import pandas as pd
159
+ from everestapi import scoring
143
160
 
144
- val = pd.read_parquet("futures_validation.parquet")
145
- metrics = EverestAPI.evaluate(predictions, val, target="target_everest_20")
146
- print(metrics) # {"simple_corr": 0.023, "weighted_corr": 0.019, "per_difficulty": {...}}
161
+ val = pd.read_parquet("eiq_validation.parquet")
162
+ preds = my_model.predict(val.filter(like="feature_"))
163
+
164
+ # Score per exped (cross-section), then average — matches how the server scores.
165
+ per_exped = [
166
+ scoring.corr20(preds[val.exped == e], val.loc[val.exped == e, "target"])
167
+ for e in val.exped.unique()
168
+ ]
169
+ print("mean CORR20:", sum(per_exped) / len(per_exped))
170
+
171
+ # Or every metric at once for one exped (ai_model = crowd consensus for that exped):
172
+ scoring.score(preds_e, target_e, ai_model=consensus_e, features=features_e)
173
+ # -> {"corr20", "aimc20", "payout", "fnc", "feature_exposure"}
147
174
  ```
148
175
 
176
+ **Sanity-check your pipeline against the example predictions.** The published
177
+ `eiq_validation_example_preds` are a benchmark-grade signal (the Minera ensemble)
178
+ and score a positive mean **CORR20 of ≈ 0.07**. Score that file and reproduce a
179
+ similar number — if you instead get ≈ −0.07, your sign is flipped; if you get ≈ 0,
180
+ your ids/alignment are off:
181
+
182
+ ```python
183
+ ex = pd.read_parquet("eiq_validation_example_preds.parquet") # column: prediction
184
+ val = pd.read_parquet("eiq_validation.parquet")
185
+ ref = [
186
+ scoring.corr20(ex.loc[val.exped == e, "prediction"], val.loc[val.exped == e, "target"])
187
+ for e in val.exped.unique()
188
+ ]
189
+ print(sum(ref) / len(ref)) # ~0.07 -> pipeline + sign are correct
190
+ ```
191
+
192
+ A quick convenience for a single overall correlation is also available:
193
+ `EverestAPI.evaluate(predictions, val, target="target_everest_20")`.
194
+
149
195
  ### CLI
150
196
 
151
197
  ```bash
@@ -175,6 +221,7 @@ with EverestAPI(api_key="...") as api:
175
221
 
176
222
  - Python 3.10+
177
223
  - httpx >= 0.27
224
+ - Optional `scoring` extra (`pip install "everestapi[scoring]"`): numpy, pandas, scipy — only needed for offline `everestapi.scoring`.
178
225
 
179
226
  ## Disclaimers
180
227
 
@@ -27,13 +27,16 @@ dependencies = [
27
27
  ]
28
28
 
29
29
  [project.optional-dependencies]
30
- dev = ["pytest>=8.0", "pytest-httpx>=0.34.0"]
30
+ dev = ["pytest>=8.0", "pytest-httpx>=0.34.0", "numpy>=1.26", "pandas>=2.0", "scipy>=1.10"]
31
31
  # MCP server (`python -m everestapi.mcp`). Optional — the server falls back to a
32
32
  # built-in JSON-RPC stdio loop when the official `mcp` SDK isn't installed.
33
33
  mcp = ["mcp>=1.0"]
34
34
  # Plotting helpers (`everestapi.plots`). Heavy — pulls matplotlib/pandas/numpy/
35
35
  # scipy/statsmodels/mizani. Optional: the SDK and MCP server never import it.
36
36
  viz = ["plotnine>=0.13"]
37
+ # Local scoring (`everestapi.scoring`). Optional — keeps the base SDK light
38
+ # (httpx + click only); pull these in to score validation preds offline.
39
+ scoring = ["numpy>=1.26", "pandas>=2.0", "scipy>=1.10"]
37
40
 
38
41
  [project.scripts]
39
42
  everestapi = "everestapi.cli:cli"
@@ -1,6 +1,6 @@
1
1
  """EverestAPI — Python SDK for the Everesteer prediction tournament platform."""
2
2
 
3
- __version__ = "0.2.5"
3
+ __version__ = "0.2.7"
4
4
 
5
5
  from everestapi.client import EverestAPI, EverestError
6
6
  from everestapi.types import (
@@ -29,4 +29,15 @@ __all__ = [
29
29
  "StakeResponse",
30
30
  "Submission",
31
31
  "UniverseResponse",
32
+ "scoring",
32
33
  ]
34
+
35
+
36
+ def __getattr__(name):
37
+ # Lazy import so `import everestapi` stays light (httpx + click only) — the
38
+ # numpy/pandas/scipy stack is only loaded if you actually use scoring.
39
+ if name == "scoring":
40
+ import everestapi.scoring as scoring
41
+
42
+ return scoring
43
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -108,7 +108,7 @@ def submit(model: str, file_path: str, tournament: str, api_key: str | None) ->
108
108
  try:
109
109
  if ext == "parquet":
110
110
  result = api.submit_predictions_file(
111
- model_name=model,
111
+ model_id=model,
112
112
  file_path=file_path,
113
113
  tournament=tournament,
114
114
  )
@@ -133,9 +133,7 @@ def _json_or_raise(resp: httpx.Response) -> dict:
133
133
  except Exception:
134
134
  body = (resp.text or "").strip()
135
135
  looks_like_cf = (
136
- "<html" in body[:200].lower()
137
- or "cloudflare" in body.lower()
138
- or "AccessDenied" in body
136
+ "<html" in body[:200].lower() or "cloudflare" in body.lower() or "AccessDenied" in body
139
137
  )
140
138
  hint = (
141
139
  " — this looks like a Cloudflare Access challenge; set "
@@ -145,14 +143,11 @@ def _json_or_raise(resp: httpx.Response) -> dict:
145
143
  )
146
144
  raise EverestError(
147
145
  resp.status_code,
148
- f"expected a JSON response but received non-JSON content{hint}: "
149
- f"{body[:200]}",
146
+ f"expected a JSON response but received non-JSON content{hint}: {body[:200]}",
150
147
  )
151
148
 
152
149
 
153
- def _check_prediction_range(
154
- values: dict[str, float], *, lo: float = 0.0, hi: float = 1.0
155
- ) -> None:
150
+ def _check_prediction_range(values: dict[str, float], *, lo: float = 0.0, hi: float = 1.0) -> None:
156
151
  """Raise ``ValueError`` if any prediction is non-finite or outside ``[lo, hi]``.
157
152
 
158
153
  Mirrors the server-side futures bound so a bad submission fails fast
@@ -173,11 +168,22 @@ def _check_prediction_range(
173
168
  preview = ", ".join(bad[:5])
174
169
  more = f" (+{len(bad) - 5} more)" if len(bad) > 5 else ""
175
170
  raise ValueError(
176
- f"{len(bad)} prediction(s) must be finite and within "
177
- f"[{lo}, {hi}]: {preview}{more}"
171
+ f"{len(bad)} prediction(s) must be finite and within [{lo}, {hi}]: {preview}{more}"
178
172
  )
179
173
 
180
174
 
175
+ def _resolve_default_base_url() -> str:
176
+ """Resolve the default API base URL.
177
+
178
+ Honors ``EIQ_BASE_URL`` first (the var the MCP server + onboarding set), then
179
+ legacy ``EVEREST_API_URL``, then defaults to the API host
180
+ ``https://app.everesteer.ai``. The apex ``everesteer.ai`` is a marketing site
181
+ (CloudFront→S3) that serves no ``/api`` — defaulting there returns an S3
182
+ ``AccessDenied`` for every call, so it is deliberately not the fallback.
183
+ """
184
+ return os.getenv("EIQ_BASE_URL") or os.getenv("EVEREST_API_URL") or "https://app.everesteer.ai"
185
+
186
+
181
187
  class EverestAPI:
182
188
  """Synchronous client for the Everesteer tournament API.
183
189
 
@@ -186,8 +192,11 @@ class EverestAPI:
186
192
  api_key : str, optional
187
193
  Everesteer API key. Falls back to ``EIQ_API_KEY`` (or legacy ``EVEREST_API_KEY``) env var.
188
194
  base_url : str, optional
189
- Base URL for the API. Defaults to ``https://everesteer.ai`` (apex
190
- domain), overridable via ``EVEREST_API_URL`` env var.
195
+ Base URL for the API. Defaults to the API host ``https://app.everesteer.ai``;
196
+ overridable via ``EIQ_BASE_URL`` (preferred — the var the MCP server and
197
+ onboarding set) or legacy ``EVEREST_API_URL``. The apex ``everesteer.ai``
198
+ is a marketing site (CloudFront→S3) that serves no ``/api``, so it is NOT
199
+ the default.
191
200
  timeout : float
192
201
  HTTP timeout in seconds.
193
202
  tournament : str
@@ -200,7 +209,7 @@ class EverestAPI:
200
209
  explicitly supplied.
201
210
  """
202
211
 
203
- DEFAULT_BASE_URL = os.getenv("EVEREST_API_URL", "https://everesteer.ai")
212
+ DEFAULT_BASE_URL = _resolve_default_base_url()
204
213
 
205
214
  def __init__(
206
215
  self,
@@ -307,29 +316,42 @@ class EverestAPI:
307
316
 
308
317
  def submit_predictions_file(
309
318
  self,
310
- model_name: str,
311
- file_path: str,
319
+ model_id: str | None = None,
320
+ file_path: str | None = None,
312
321
  tournament: str | None = None,
322
+ *,
323
+ model_name: str | None = None,
313
324
  ) -> dict:
314
325
  """POST /api/v1/predictions/upload — submit predictions as a CSV or Parquet file.
315
326
 
316
327
  Parameters
317
328
  ----------
318
- model_name : str
319
- Model identifier.
329
+ model_id : str
330
+ Model identifier. (``model_name`` is a deprecated alias.)
320
331
  file_path : str
321
332
  Path to a ``.csv`` or ``.parquet`` file with columns ``ticker`` (str)
322
333
  and ``score`` (float in [-1, 1]).
323
334
  tournament : str, optional
324
335
  Tournament identifier. Defaults to the client's tournament.
325
336
  """
337
+ if model_id is None:
338
+ if model_name is None:
339
+ raise TypeError("submit_predictions_file() missing required argument: 'model_id'")
340
+ import warnings
341
+
342
+ warnings.warn(
343
+ "model_name= is deprecated; use model_id=", DeprecationWarning, stacklevel=2
344
+ )
345
+ model_id = model_name
346
+ if file_path is None:
347
+ raise TypeError("submit_predictions_file() missing required argument: 'file_path'")
326
348
  tourn = tournament or self.tournament
327
349
  with open(file_path, "rb") as f:
328
350
  fname = file_path.rsplit("/", 1)[-1].rsplit("\\", 1)[-1]
329
351
  resp = self._client.post(
330
352
  "/api/v1/predictions/upload",
331
353
  files={"file": (fname, f, "application/octet-stream")},
332
- params={"model_id": model_name, "tournament": tourn},
354
+ params={"model_id": model_id, "tournament": tourn},
333
355
  )
334
356
  return _json_or_raise(resp)
335
357
 
@@ -472,9 +494,7 @@ class EverestAPI:
472
494
  )
473
495
  return "\n".join(lines)
474
496
 
475
- def get_validation_panel(
476
- self, model_id: str, days: int = 365, source: str = "auto"
477
- ) -> dict:
497
+ def get_validation_panel(self, model_id: str, days: int = 365, source: str = "auto") -> dict:
478
498
  """GET /api/v1/models/{model_id}/diagnostics/validation — full validation metrics.
479
499
 
480
500
  ``source="auto"`` (default) returns the latest completed validation-diagnostics
@@ -532,9 +552,19 @@ class EverestAPI:
532
552
  output_path,
533
553
  )
534
554
 
535
- def get_scores(self, model_id: str, days: int = 30) -> dict:
536
- """GET /api/v1/scores — scoring results for a model."""
537
- params = {"model_id": model_id, "days": str(days)}
555
+ def get_scores(self, model_id: str, days: int = 30, response_format: str = "concise") -> dict:
556
+ """GET /api/v1/scores — scoring results for a model.
557
+
558
+ ``response_format="detailed"`` asks the platform to add a plain-language
559
+ ``interpretation`` field that reads the model's own latest numbers and
560
+ state and explains how to improve. ``"concise"`` (default) leaves the
561
+ response unchanged.
562
+ """
563
+ params = {
564
+ "model_id": model_id,
565
+ "days": str(days),
566
+ "response_format": response_format,
567
+ }
538
568
  return self._request("GET", "/api/v1/scores", params=params)
539
569
 
540
570
  def get_leaderboard(self, period: str = "30d") -> dict:
@@ -613,9 +643,15 @@ class EverestAPI:
613
643
  params["date"] = date
614
644
  return self._request("GET", f"/api/v1/diagnostics/{model_id}", params=params)
615
645
 
616
- def get_round_diagnostics(self, model_id: str) -> dict:
617
- """GET /api/v1/diagnostics/{model_id}/rounds — per-round scoring breakdown."""
618
- return self._request("GET", f"/api/v1/diagnostics/{model_id}/rounds")
646
+ def get_round_diagnostics(self, model_id: str, response_format: str = "concise") -> dict:
647
+ """GET /api/v1/diagnostics/{model_id}/rounds — per-round scoring breakdown.
648
+
649
+ ``response_format="detailed"`` asks the platform to add a plain-language
650
+ ``interpretation`` field grounded in the model's own latest numbers and
651
+ state. ``"concise"`` (default) leaves the response unchanged.
652
+ """
653
+ params = {"response_format": response_format}
654
+ return self._request("GET", f"/api/v1/diagnostics/{model_id}/rounds", params=params)
619
655
 
620
656
  # -- futures tournament -----------------------------------------------
621
657
 
@@ -881,8 +917,8 @@ class EverestAPI:
881
917
 
882
918
  # -- rounds -----------------------------------------------------------
883
919
 
884
- def get_rounds(self, tournament: str = "equities", limit: int = 25) -> dict:
885
- """GET /api/v1/rounds — list tournament rounds."""
920
+ def get_rounds(self, tournament: str = "futures", limit: int = 25) -> dict:
921
+ """GET /api/v1/rounds — list tournament rounds (defaults to 'futures'; equities is unlaunched)."""
886
922
  return self._request(
887
923
  "GET",
888
924
  "/api/v1/rounds",
@@ -892,8 +928,13 @@ class EverestAPI:
892
928
  },
893
929
  )
894
930
 
895
- def get_current_round(self, tournament: str = "equities") -> dict:
896
- """GET /api/v1/rounds/current — current active round."""
931
+ def get_current_round(self, tournament: str = "futures") -> dict:
932
+ """GET /api/v1/rounds/current — current active round (defaults to 'futures'; equities is unlaunched).
933
+
934
+ With a hackathon-scoped key there is no live round — the response is a
935
+ diagnostics-mode payload (mode='diagnostics_hackathon') directing you to
936
+ download_dataset + submit_validation_diagnostics.
937
+ """
897
938
  return self._request(
898
939
  "GET",
899
940
  "/api/v1/rounds/current",
@@ -906,6 +947,35 @@ class EverestAPI:
906
947
  """GET /api/v1/schedule — round schedule for both tournaments."""
907
948
  return self._request("GET", "/api/v1/schedule")
908
949
 
950
+ def get_started(self) -> dict:
951
+ """GET /api/v1/get_started — mode-aware orientation.
952
+
953
+ Returns what to do next given this key's scope: the display-only
954
+ diagnostics-hackathon loop, or the live futures tournament flow.
955
+ """
956
+ return self._request("GET", "/api/v1/get_started")
957
+
958
+ def get_status(self) -> dict:
959
+ """GET /api/v1/status — state-aware orientation ("where am I").
960
+
961
+ Your whole situational picture in one call: the models you own, each
962
+ one's latest submission and score state, the open round's clock, your
963
+ stake, and a ``next_actions`` list — instead of stitching together
964
+ get_models + get_scores + get_current_round. Display-only.
965
+ """
966
+ return self._request("GET", "/api/v1/status")
967
+
968
+ def get_capabilities(self) -> dict:
969
+ """GET / (site root) with ``Accept: application/json`` — capability index.
970
+
971
+ A small machine-readable map of where everything lives: pointers to
972
+ ``llms.txt``, the OpenAPI spec, the MCP endpoint, the API base, the SDK,
973
+ and related discovery URLs. Hit before anything else to learn the
974
+ platform's shape without hardcoding paths. The index sits at the host
975
+ root; a leading-slash path resolves there against the API host base URL.
976
+ """
977
+ return self._request("GET", "/", headers={"Accept": "application/json"})
978
+
909
979
  # -- model uploads ----------------------------------------------------
910
980
 
911
981
  def upload_model(self, model_id: str, file_path: str) -> dict: