everestapi 0.2.6__tar.gz → 0.2.8__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 (33) hide show
  1. {everestapi-0.2.6/src/everestapi.egg-info → everestapi-0.2.8}/PKG-INFO +74 -13
  2. {everestapi-0.2.6 → everestapi-0.2.8}/README.md +66 -12
  3. {everestapi-0.2.6 → everestapi-0.2.8}/pyproject.toml +4 -1
  4. {everestapi-0.2.6 → everestapi-0.2.8}/src/everestapi/__init__.py +12 -1
  5. {everestapi-0.2.6 → everestapi-0.2.8}/src/everestapi/cli.py +1 -1
  6. {everestapi-0.2.6 → everestapi-0.2.8}/src/everestapi/client.py +104 -38
  7. {everestapi-0.2.6 → everestapi-0.2.8}/src/everestapi/mcp/server.py +728 -57
  8. {everestapi-0.2.6 → everestapi-0.2.8}/src/everestapi/plots.py +4 -5
  9. everestapi-0.2.8/src/everestapi/scoring.py +205 -0
  10. {everestapi-0.2.6 → everestapi-0.2.8/src/everestapi.egg-info}/PKG-INFO +74 -13
  11. {everestapi-0.2.6 → everestapi-0.2.8}/src/everestapi.egg-info/SOURCES.txt +7 -1
  12. {everestapi-0.2.6 → everestapi-0.2.8}/src/everestapi.egg-info/requires.txt +8 -0
  13. {everestapi-0.2.6 → everestapi-0.2.8}/tests/test_client.py +87 -5
  14. everestapi-0.2.8/tests/test_eve953_mcp_progress.py +176 -0
  15. everestapi-0.2.8/tests/test_eve957_mcp_annotations_resources.py +75 -0
  16. everestapi-0.2.8/tests/test_eve959_toolsets.py +72 -0
  17. everestapi-0.2.8/tests/test_eve967_mcp_discoverability.py +40 -0
  18. everestapi-0.2.8/tests/test_mcp_and_models.py +570 -0
  19. {everestapi-0.2.6 → everestapi-0.2.8}/tests/test_prediction_range.py +2 -2
  20. everestapi-0.2.8/tests/test_scoring.py +112 -0
  21. everestapi-0.2.6/tests/test_mcp_and_models.py +0 -195
  22. {everestapi-0.2.6 → everestapi-0.2.8}/LICENSE +0 -0
  23. {everestapi-0.2.6 → everestapi-0.2.8}/setup.cfg +0 -0
  24. {everestapi-0.2.6 → everestapi-0.2.8}/src/everestapi/__main__.py +0 -0
  25. {everestapi-0.2.6 → everestapi-0.2.8}/src/everestapi/mcp/__init__.py +0 -0
  26. {everestapi-0.2.6 → everestapi-0.2.8}/src/everestapi/mcp/__main__.py +0 -0
  27. {everestapi-0.2.6 → everestapi-0.2.8}/src/everestapi/types.py +0 -0
  28. {everestapi-0.2.6 → everestapi-0.2.8}/src/everestapi.egg-info/dependency_links.txt +0 -0
  29. {everestapi-0.2.6 → everestapi-0.2.8}/src/everestapi.egg-info/entry_points.txt +0 -0
  30. {everestapi-0.2.6 → everestapi-0.2.8}/src/everestapi.egg-info/top_level.txt +0 -0
  31. {everestapi-0.2.6 → everestapi-0.2.8}/tests/test_cli.py +0 -0
  32. {everestapi-0.2.6 → everestapi-0.2.8}/tests/test_diagnostics.py +0 -0
  33. {everestapi-0.2.6 → everestapi-0.2.8}/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.8
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
  )
@@ -106,16 +116,23 @@ everestapi submit --model my-model --file predictions.parquet
106
116
 
107
117
  ### Data & diagnostics
108
118
 
119
+ The hackathon is a display-only diagnostics event. **Tune and self-score offline
120
+ on the labeled validation set** (features + `target_*` columns), then **predict on
121
+ the blind `eiq_live_2026` set and submit** — the leaderboard ranks your
122
+ out-of-sample 2026 CORR on `target_everest_20`. In-sample fit is not rewarded. The
123
+ labeled `eiq_live_2026` answers are held out server-side and are never downloadable.
124
+
109
125
  ```python
110
- api.download_dataset(universe="futures", split="train")
111
- api.download_benchmark(universe="futures", split="validation")
112
- api.get_dataset_info(universe="futures")
113
- api.get_diagnostics(model_id="my-model")
126
+ # Labeled practice set — tune + self-score offline with everestapi.scoring:
127
+ api.download_dataset(universe="futures", split="validation")
114
128
 
115
- # Validation diagnostics: eiq_validation_example_preds.parquet is the upload template
116
- # (same `id` set, your own `prediction` column).
117
- api.download_dataset(universe="futures", split="validation_example_preds")
129
+ # Blind scored set (columns: exped, exped_date, instrument, id — no targets).
130
+ # Predict on it, then submit; it is also the upload id template.
131
+ api.download_dataset(universe="futures", split="live")
118
132
  api.submit_validation_diagnostics(model_id="my-model", predictions=df)
133
+
134
+ api.get_dataset_info(universe="futures")
135
+ api.get_diagnostics(model_id="my-model")
119
136
  ```
120
137
 
121
138
  ### Plotting (optional `viz` extra)
@@ -168,16 +185,59 @@ api.get_stake_balance(model_id="my-model")
168
185
  api.claim_payout(model_id="my-model", round_id="42")
169
186
  ```
170
187
 
171
- ### Local evaluation
188
+ ### Score validation predictions offline
189
+
190
+ Reproduce the server's **exact** scoring — CORR20, AIMC20, FNC — *before* you
191
+ submit, so you stop guessing the sign of your signal ("submit raw and negated, let
192
+ the server decide"). The `everestapi.scoring` functions are a verbatim port of the
193
+ platform's scoring engine (verified equal to 1e-12), so your offline number **is**
194
+ the server's number.
195
+
196
+ Install the optional scoring extra (keeps the base SDK light — numpy/pandas/scipy
197
+ are only pulled in here):
198
+
199
+ ```bash
200
+ pip install "everestapi[scoring]"
201
+ ```
172
202
 
173
203
  ```python
174
204
  import pandas as pd
205
+ from everestapi import scoring
206
+
207
+ val = pd.read_parquet("eiq_validation.parquet")
208
+ preds = my_model.predict(val.filter(like="feature_"))
209
+
210
+ # Score per exped (cross-section), then average — matches how the server scores.
211
+ per_exped = [
212
+ scoring.corr20(preds[val.exped == e], val.loc[val.exped == e, "target"])
213
+ for e in val.exped.unique()
214
+ ]
215
+ print("mean CORR20:", sum(per_exped) / len(per_exped))
175
216
 
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": {...}}
217
+ # Or every metric at once for one exped (ai_model = crowd consensus for that exped):
218
+ scoring.score(preds_e, target_e, ai_model=consensus_e, features=features_e)
219
+ # -> {"corr20", "aimc20", "payout", "fnc", "feature_exposure"}
179
220
  ```
180
221
 
222
+ **Sanity-check your pipeline against the example predictions.** The published
223
+ `eiq_validation_example_preds` are a benchmark-grade signal (the Minera ensemble)
224
+ and score a positive mean **CORR20 of ≈ 0.07**. Score that file and reproduce a
225
+ similar number — if you instead get ≈ −0.07, your sign is flipped; if you get ≈ 0,
226
+ your ids/alignment are off:
227
+
228
+ ```python
229
+ ex = pd.read_parquet("eiq_validation_example_preds.parquet") # column: prediction
230
+ val = pd.read_parquet("eiq_validation.parquet")
231
+ ref = [
232
+ scoring.corr20(ex.loc[val.exped == e, "prediction"], val.loc[val.exped == e, "target"])
233
+ for e in val.exped.unique()
234
+ ]
235
+ print(sum(ref) / len(ref)) # ~0.07 -> pipeline + sign are correct
236
+ ```
237
+
238
+ A quick convenience for a single overall correlation is also available:
239
+ `EverestAPI.evaluate(predictions, val, target="target_everest_20")`.
240
+
181
241
  ### CLI
182
242
 
183
243
  ```bash
@@ -207,6 +267,7 @@ with EverestAPI(api_key="...") as api:
207
267
 
208
268
  - Python 3.10+
209
269
  - httpx >= 0.27
270
+ - Optional `scoring` extra (`pip install "everestapi[scoring]"`): numpy, pandas, scipy — only needed for offline `everestapi.scoring`.
210
271
 
211
272
  ## Disclaimers
212
273
 
@@ -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
  )
@@ -74,16 +77,23 @@ everestapi submit --model my-model --file predictions.parquet
74
77
 
75
78
  ### Data & diagnostics
76
79
 
80
+ The hackathon is a display-only diagnostics event. **Tune and self-score offline
81
+ on the labeled validation set** (features + `target_*` columns), then **predict on
82
+ the blind `eiq_live_2026` set and submit** — the leaderboard ranks your
83
+ out-of-sample 2026 CORR on `target_everest_20`. In-sample fit is not rewarded. The
84
+ labeled `eiq_live_2026` answers are held out server-side and are never downloadable.
85
+
77
86
  ```python
78
- api.download_dataset(universe="futures", split="train")
79
- api.download_benchmark(universe="futures", split="validation")
80
- api.get_dataset_info(universe="futures")
81
- api.get_diagnostics(model_id="my-model")
87
+ # Labeled practice set — tune + self-score offline with everestapi.scoring:
88
+ api.download_dataset(universe="futures", split="validation")
82
89
 
83
- # Validation diagnostics: eiq_validation_example_preds.parquet is the upload template
84
- # (same `id` set, your own `prediction` column).
85
- api.download_dataset(universe="futures", split="validation_example_preds")
90
+ # Blind scored set (columns: exped, exped_date, instrument, id — no targets).
91
+ # Predict on it, then submit; it is also the upload id template.
92
+ api.download_dataset(universe="futures", split="live")
86
93
  api.submit_validation_diagnostics(model_id="my-model", predictions=df)
94
+
95
+ api.get_dataset_info(universe="futures")
96
+ api.get_diagnostics(model_id="my-model")
87
97
  ```
88
98
 
89
99
  ### Plotting (optional `viz` extra)
@@ -136,16 +146,59 @@ api.get_stake_balance(model_id="my-model")
136
146
  api.claim_payout(model_id="my-model", round_id="42")
137
147
  ```
138
148
 
139
- ### Local evaluation
149
+ ### Score validation predictions offline
150
+
151
+ Reproduce the server's **exact** scoring — CORR20, AIMC20, FNC — *before* you
152
+ submit, so you stop guessing the sign of your signal ("submit raw and negated, let
153
+ the server decide"). The `everestapi.scoring` functions are a verbatim port of the
154
+ platform's scoring engine (verified equal to 1e-12), so your offline number **is**
155
+ the server's number.
156
+
157
+ Install the optional scoring extra (keeps the base SDK light — numpy/pandas/scipy
158
+ are only pulled in here):
159
+
160
+ ```bash
161
+ pip install "everestapi[scoring]"
162
+ ```
140
163
 
141
164
  ```python
142
165
  import pandas as pd
166
+ from everestapi import scoring
167
+
168
+ val = pd.read_parquet("eiq_validation.parquet")
169
+ preds = my_model.predict(val.filter(like="feature_"))
170
+
171
+ # Score per exped (cross-section), then average — matches how the server scores.
172
+ per_exped = [
173
+ scoring.corr20(preds[val.exped == e], val.loc[val.exped == e, "target"])
174
+ for e in val.exped.unique()
175
+ ]
176
+ print("mean CORR20:", sum(per_exped) / len(per_exped))
143
177
 
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": {...}}
178
+ # Or every metric at once for one exped (ai_model = crowd consensus for that exped):
179
+ scoring.score(preds_e, target_e, ai_model=consensus_e, features=features_e)
180
+ # -> {"corr20", "aimc20", "payout", "fnc", "feature_exposure"}
147
181
  ```
148
182
 
183
+ **Sanity-check your pipeline against the example predictions.** The published
184
+ `eiq_validation_example_preds` are a benchmark-grade signal (the Minera ensemble)
185
+ and score a positive mean **CORR20 of ≈ 0.07**. Score that file and reproduce a
186
+ similar number — if you instead get ≈ −0.07, your sign is flipped; if you get ≈ 0,
187
+ your ids/alignment are off:
188
+
189
+ ```python
190
+ ex = pd.read_parquet("eiq_validation_example_preds.parquet") # column: prediction
191
+ val = pd.read_parquet("eiq_validation.parquet")
192
+ ref = [
193
+ scoring.corr20(ex.loc[val.exped == e, "prediction"], val.loc[val.exped == e, "target"])
194
+ for e in val.exped.unique()
195
+ ]
196
+ print(sum(ref) / len(ref)) # ~0.07 -> pipeline + sign are correct
197
+ ```
198
+
199
+ A quick convenience for a single overall correlation is also available:
200
+ `EverestAPI.evaluate(predictions, val, target="target_everest_20")`.
201
+
149
202
  ### CLI
150
203
 
151
204
  ```bash
@@ -175,6 +228,7 @@ with EverestAPI(api_key="...") as api:
175
228
 
176
229
  - Python 3.10+
177
230
  - httpx >= 0.27
231
+ - Optional `scoring` extra (`pip install "everestapi[scoring]"`): numpy, pandas, scipy — only needed for offline `everestapi.scoring`.
178
232
 
179
233
  ## Disclaimers
180
234
 
@@ -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.8"
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
 
@@ -363,10 +366,14 @@ class EverestAPI:
363
366
  poll_interval: float = 2.0,
364
367
  timeout: float = 600.0,
365
368
  ) -> dict:
366
- """POST /api/v1/diagnostics/upload (multipart) — score validation predictions.
369
+ """POST /api/v1/diagnostics/upload (multipart) — score out-of-sample predictions.
367
370
 
368
371
  ``predictions`` is a pandas DataFrame (``id`` + ``prediction`` columns) or a
369
- path to a ``.parquet`` / ``.csv`` file. Returns the 202 accept dict; with
372
+ path to a ``.parquet`` / ``.csv`` file, generated on the blind eiq_live_2026
373
+ set (``download_dataset(split="live")``). The platform scores it server-side
374
+ against the held-out labeled eiq_live_2026 answers — those answers are never
375
+ downloadable. Tune and self-score offline on the labeled validation set first
376
+ (see :mod:`everestapi.scoring`). Returns the 202 accept dict; with
370
377
  ``wait=True`` polls ``runs/{upload_id}`` until ``done`` (returns the run) and
371
378
  raises :class:`EverestError` on ``failed`` or timeout. Display-only; results
372
379
  also surface in the website Validation Diagnostics rail.
@@ -491,9 +498,7 @@ class EverestAPI:
491
498
  )
492
499
  return "\n".join(lines)
493
500
 
494
- def get_validation_panel(
495
- self, model_id: str, days: int = 365, source: str = "auto"
496
- ) -> dict:
501
+ def get_validation_panel(self, model_id: str, days: int = 365, source: str = "auto") -> dict:
497
502
  """GET /api/v1/models/{model_id}/diagnostics/validation — full validation metrics.
498
503
 
499
504
  ``source="auto"`` (default) returns the latest completed validation-diagnostics
@@ -551,9 +556,19 @@ class EverestAPI:
551
556
  output_path,
552
557
  )
553
558
 
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)}
559
+ def get_scores(self, model_id: str, days: int = 30, response_format: str = "concise") -> dict:
560
+ """GET /api/v1/scores — scoring results for a model.
561
+
562
+ ``response_format="detailed"`` asks the platform to add a plain-language
563
+ ``interpretation`` field that reads the model's own latest numbers and
564
+ state and explains how to improve. ``"concise"`` (default) leaves the
565
+ response unchanged.
566
+ """
567
+ params = {
568
+ "model_id": model_id,
569
+ "days": str(days),
570
+ "response_format": response_format,
571
+ }
557
572
  return self._request("GET", "/api/v1/scores", params=params)
558
573
 
559
574
  def get_leaderboard(self, period: str = "30d") -> dict:
@@ -579,11 +594,18 @@ class EverestAPI:
579
594
  404 the current version is resolved and the download retried once.
580
595
 
581
596
  Splits: ``train`` / ``validation`` (features + targets), ``live``
582
- (current round features only).
597
+ (features only, no targets). In hackathon mode, ``validation`` is the
598
+ LABELED practice set — features + all ``target_*`` columns — that you tune
599
+ and self-score on offline (see :mod:`everestapi.scoring`), and ``live`` is
600
+ the BLIND eiq_live_2026 out-of-sample set (columns exactly ``exped``,
601
+ ``exped_date``, ``instrument``, ``id`` — no targets) that you predict on and
602
+ submit. The labeled eiq_live_2026 answers are held out server-side and never
603
+ downloadable.
583
604
 
584
605
  Futures is served by the futures endpoint (``version`` is a no-op):
585
- it returns the real bregen tree to full-scope keys and the
586
- target-stripped hackathon tree to hackathon-scoped keys (EVE-931).
606
+ it returns the real bregen tree to full-scope keys and the hackathon tree
607
+ (labeled ``validation`` practice set + blind ``live`` eiq_live_2026 scored
608
+ set) to hackathon-scoped keys.
587
609
  """
588
610
  if output_path is None:
589
611
  output_path = f"{universe}_{split}.parquet"
@@ -632,9 +654,15 @@ class EverestAPI:
632
654
  params["date"] = date
633
655
  return self._request("GET", f"/api/v1/diagnostics/{model_id}", params=params)
634
656
 
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")
657
+ def get_round_diagnostics(self, model_id: str, response_format: str = "concise") -> dict:
658
+ """GET /api/v1/diagnostics/{model_id}/rounds — per-round scoring breakdown.
659
+
660
+ ``response_format="detailed"`` asks the platform to add a plain-language
661
+ ``interpretation`` field grounded in the model's own latest numbers and
662
+ state. ``"concise"`` (default) leaves the response unchanged.
663
+ """
664
+ params = {"response_format": response_format}
665
+ return self._request("GET", f"/api/v1/diagnostics/{model_id}/rounds", params=params)
638
666
 
639
667
  # -- futures tournament -----------------------------------------------
640
668
 
@@ -900,8 +928,8 @@ class EverestAPI:
900
928
 
901
929
  # -- rounds -----------------------------------------------------------
902
930
 
903
- def get_rounds(self, tournament: str = "equities", limit: int = 25) -> dict:
904
- """GET /api/v1/rounds — list tournament rounds."""
931
+ def get_rounds(self, tournament: str = "futures", limit: int = 25) -> dict:
932
+ """GET /api/v1/rounds — list tournament rounds (defaults to 'futures'; equities is unlaunched)."""
905
933
  return self._request(
906
934
  "GET",
907
935
  "/api/v1/rounds",
@@ -911,8 +939,14 @@ class EverestAPI:
911
939
  },
912
940
  )
913
941
 
914
- def get_current_round(self, tournament: str = "equities") -> dict:
915
- """GET /api/v1/rounds/current — current active round."""
942
+ def get_current_round(self, tournament: str = "futures") -> dict:
943
+ """GET /api/v1/rounds/current — current active round (defaults to 'futures'; equities is unlaunched).
944
+
945
+ With a hackathon-scoped key there is no live round — the response is a
946
+ diagnostics-mode payload (mode='diagnostics_hackathon') directing you to
947
+ tune offline on the labeled validation set, then predict on the blind
948
+ eiq_live_2026 set and submit_validation_diagnostics.
949
+ """
916
950
  return self._request(
917
951
  "GET",
918
952
  "/api/v1/rounds/current",
@@ -925,6 +959,38 @@ class EverestAPI:
925
959
  """GET /api/v1/schedule — round schedule for both tournaments."""
926
960
  return self._request("GET", "/api/v1/schedule")
927
961
 
962
+ def get_started(self) -> dict:
963
+ """GET /api/v1/get_started — mode-aware orientation.
964
+
965
+ Returns what to do next given this key's scope: the display-only
966
+ diagnostics-hackathon loop (tune and self-score offline on the labeled
967
+ validation set, then predict on the blind eiq_live_2026 set and submit —
968
+ ranked on out-of-sample 2026 CORR on target_everest_20), or the live
969
+ futures tournament flow.
970
+ """
971
+ return self._request("GET", "/api/v1/get_started")
972
+
973
+ def get_status(self) -> dict:
974
+ """GET /api/v1/status — state-aware orientation ("where am I").
975
+
976
+ Your whole situational picture in one call: the models you own, each
977
+ one's latest submission and score state, the open round's clock, your
978
+ stake, and a ``next_actions`` list — instead of stitching together
979
+ get_models + get_scores + get_current_round. Display-only.
980
+ """
981
+ return self._request("GET", "/api/v1/status")
982
+
983
+ def get_capabilities(self) -> dict:
984
+ """GET / (site root) with ``Accept: application/json`` — capability index.
985
+
986
+ A small machine-readable map of where everything lives: pointers to
987
+ ``llms.txt``, the OpenAPI spec, the MCP endpoint, the API base, the SDK,
988
+ and related discovery URLs. Hit before anything else to learn the
989
+ platform's shape without hardcoding paths. The index sits at the host
990
+ root; a leading-slash path resolves there against the API host base URL.
991
+ """
992
+ return self._request("GET", "/", headers={"Accept": "application/json"})
993
+
928
994
  # -- model uploads ----------------------------------------------------
929
995
 
930
996
  def upload_model(self, model_id: str, file_path: str) -> dict: