everestapi 0.2.6__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.6/src/everestapi.egg-info → everestapi-0.2.7}/PKG-INFO +60 -6
  2. {everestapi-0.2.6 → everestapi-0.2.7}/README.md +52 -5
  3. {everestapi-0.2.6 → everestapi-0.2.7}/pyproject.toml +4 -1
  4. {everestapi-0.2.6 → everestapi-0.2.7}/src/everestapi/__init__.py +12 -1
  5. {everestapi-0.2.6 → everestapi-0.2.7}/src/everestapi/cli.py +1 -1
  6. {everestapi-0.2.6 → everestapi-0.2.7}/src/everestapi/client.py +84 -33
  7. {everestapi-0.2.6 → everestapi-0.2.7}/src/everestapi/mcp/server.py +687 -43
  8. {everestapi-0.2.6 → 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.6 → everestapi-0.2.7/src/everestapi.egg-info}/PKG-INFO +60 -6
  11. {everestapi-0.2.6 → everestapi-0.2.7}/src/everestapi.egg-info/SOURCES.txt +6 -1
  12. {everestapi-0.2.6 → everestapi-0.2.7}/src/everestapi.egg-info/requires.txt +8 -0
  13. {everestapi-0.2.6 → everestapi-0.2.7}/tests/test_client.py +87 -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.6 → 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.6/tests/test_mcp_and_models.py +0 -195
  21. {everestapi-0.2.6 → everestapi-0.2.7}/LICENSE +0 -0
  22. {everestapi-0.2.6 → everestapi-0.2.7}/setup.cfg +0 -0
  23. {everestapi-0.2.6 → everestapi-0.2.7}/src/everestapi/__main__.py +0 -0
  24. {everestapi-0.2.6 → everestapi-0.2.7}/src/everestapi/mcp/__init__.py +0 -0
  25. {everestapi-0.2.6 → everestapi-0.2.7}/src/everestapi/mcp/__main__.py +0 -0
  26. {everestapi-0.2.6 → everestapi-0.2.7}/src/everestapi/types.py +0 -0
  27. {everestapi-0.2.6 → everestapi-0.2.7}/src/everestapi.egg-info/dependency_links.txt +0 -0
  28. {everestapi-0.2.6 → everestapi-0.2.7}/src/everestapi.egg-info/entry_points.txt +0 -0
  29. {everestapi-0.2.6 → everestapi-0.2.7}/src/everestapi.egg-info/top_level.txt +0 -0
  30. {everestapi-0.2.6 → everestapi-0.2.7}/tests/test_cli.py +0 -0
  31. {everestapi-0.2.6 → everestapi-0.2.7}/tests/test_diagnostics.py +0 -0
  32. {everestapi-0.2.6 → 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.6
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.6"
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,8 +168,7 @@ 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
 
@@ -187,11 +181,7 @@ def _resolve_default_base_url() -> str:
187
181
  (CloudFront→S3) that serves no ``/api`` — defaulting there returns an S3
188
182
  ``AccessDenied`` for every call, so it is deliberately not the fallback.
189
183
  """
190
- return (
191
- os.getenv("EIQ_BASE_URL")
192
- or os.getenv("EVEREST_API_URL")
193
- or "https://app.everesteer.ai"
194
- )
184
+ return os.getenv("EIQ_BASE_URL") or os.getenv("EVEREST_API_URL") or "https://app.everesteer.ai"
195
185
 
196
186
 
197
187
  class EverestAPI:
@@ -326,29 +316,42 @@ class EverestAPI:
326
316
 
327
317
  def submit_predictions_file(
328
318
  self,
329
- model_name: str,
330
- file_path: str,
319
+ model_id: str | None = None,
320
+ file_path: str | None = None,
331
321
  tournament: str | None = None,
322
+ *,
323
+ model_name: str | None = None,
332
324
  ) -> dict:
333
325
  """POST /api/v1/predictions/upload — submit predictions as a CSV or Parquet file.
334
326
 
335
327
  Parameters
336
328
  ----------
337
- model_name : str
338
- Model identifier.
329
+ model_id : str
330
+ Model identifier. (``model_name`` is a deprecated alias.)
339
331
  file_path : str
340
332
  Path to a ``.csv`` or ``.parquet`` file with columns ``ticker`` (str)
341
333
  and ``score`` (float in [-1, 1]).
342
334
  tournament : str, optional
343
335
  Tournament identifier. Defaults to the client's tournament.
344
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'")
345
348
  tourn = tournament or self.tournament
346
349
  with open(file_path, "rb") as f:
347
350
  fname = file_path.rsplit("/", 1)[-1].rsplit("\\", 1)[-1]
348
351
  resp = self._client.post(
349
352
  "/api/v1/predictions/upload",
350
353
  files={"file": (fname, f, "application/octet-stream")},
351
- params={"model_id": model_name, "tournament": tourn},
354
+ params={"model_id": model_id, "tournament": tourn},
352
355
  )
353
356
  return _json_or_raise(resp)
354
357
 
@@ -491,9 +494,7 @@ class EverestAPI:
491
494
  )
492
495
  return "\n".join(lines)
493
496
 
494
- def get_validation_panel(
495
- self, model_id: str, days: int = 365, source: str = "auto"
496
- ) -> dict:
497
+ def get_validation_panel(self, model_id: str, days: int = 365, source: str = "auto") -> dict:
497
498
  """GET /api/v1/models/{model_id}/diagnostics/validation — full validation metrics.
498
499
 
499
500
  ``source="auto"`` (default) returns the latest completed validation-diagnostics
@@ -551,9 +552,19 @@ class EverestAPI:
551
552
  output_path,
552
553
  )
553
554
 
554
- def get_scores(self, model_id: str, days: int = 30) -> dict:
555
- """GET /api/v1/scores — scoring results for a model."""
556
- 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
+ }
557
568
  return self._request("GET", "/api/v1/scores", params=params)
558
569
 
559
570
  def get_leaderboard(self, period: str = "30d") -> dict:
@@ -632,9 +643,15 @@ class EverestAPI:
632
643
  params["date"] = date
633
644
  return self._request("GET", f"/api/v1/diagnostics/{model_id}", params=params)
634
645
 
635
- def get_round_diagnostics(self, model_id: str) -> dict:
636
- """GET /api/v1/diagnostics/{model_id}/rounds — per-round scoring breakdown."""
637
- 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)
638
655
 
639
656
  # -- futures tournament -----------------------------------------------
640
657
 
@@ -900,8 +917,8 @@ class EverestAPI:
900
917
 
901
918
  # -- rounds -----------------------------------------------------------
902
919
 
903
- def get_rounds(self, tournament: str = "equities", limit: int = 25) -> dict:
904
- """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)."""
905
922
  return self._request(
906
923
  "GET",
907
924
  "/api/v1/rounds",
@@ -911,8 +928,13 @@ class EverestAPI:
911
928
  },
912
929
  )
913
930
 
914
- def get_current_round(self, tournament: str = "equities") -> dict:
915
- """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
+ """
916
938
  return self._request(
917
939
  "GET",
918
940
  "/api/v1/rounds/current",
@@ -925,6 +947,35 @@ class EverestAPI:
925
947
  """GET /api/v1/schedule — round schedule for both tournaments."""
926
948
  return self._request("GET", "/api/v1/schedule")
927
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
+
928
979
  # -- model uploads ----------------------------------------------------
929
980
 
930
981
  def upload_model(self, model_id: str, file_path: str) -> dict: