proxyml 0.4.1__tar.gz → 0.5.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxyml
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: Python SDK for calling the ProxyML API
5
5
  Author-email: ProxyML <contact@proxyml.ai>
6
6
  License: Apache License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "proxyml"
7
- version = "0.4.1"
7
+ version = "0.5.0"
8
8
  description = "Python SDK for calling the ProxyML API"
9
9
  readme = "README.md"
10
10
  license = {file = "LICENSE"}
@@ -11,6 +11,7 @@ from proxyml.local.challenger import (
11
11
  Rung,
12
12
  TrainedChallenger,
13
13
  score_champion,
14
+ to_challenger_upload,
14
15
  train_auto_challenger,
15
16
  train_challenger,
16
17
  )
@@ -19,6 +20,7 @@ __all__ = [
19
20
  "train_challenger",
20
21
  "train_auto_challenger",
21
22
  "score_champion",
23
+ "to_challenger_upload",
22
24
  "Complexity",
23
25
  "Rung",
24
26
  "TrainedChallenger",
@@ -12,6 +12,7 @@ from __future__ import annotations
12
12
 
13
13
  from dataclasses import dataclass, replace
14
14
  from enum import Enum
15
+ from importlib.metadata import version as _pkg_version
15
16
  from pathlib import Path
16
17
  from typing import Any, Callable, Literal
17
18
 
@@ -113,6 +114,7 @@ LADDERS: dict[Complexity, Rung] = {
113
114
  class TrainedChallenger:
114
115
  pipeline: Pipeline
115
116
  task: Literal["classification", "regression"]
117
+ complexity: Complexity
116
118
  metrics: dict[str, float]
117
119
  hyperparameters: dict[str, Any]
118
120
  export: SurrogateExport
@@ -196,6 +198,7 @@ def train_challenger(
196
198
  return TrainedChallenger(
197
199
  pipeline=pipeline,
198
200
  task=resolved_task,
201
+ complexity=complexity,
199
202
  metrics=metrics,
200
203
  hyperparameters=hyperparameters,
201
204
  export=export,
@@ -224,6 +227,63 @@ def score_champion(
224
227
  return score_predictions(np.asarray(labels), np.asarray(predictions), task=task)
225
228
 
226
229
 
230
+ def to_challenger_upload(
231
+ result: TrainedChallenger,
232
+ *,
233
+ n_samples: int,
234
+ champion_metrics: dict[str, float] | None = None,
235
+ sdk_version: str | None = None,
236
+ proxyml_core_version: str | None = None,
237
+ ) -> dict[str, Any]:
238
+ """Assemble the JSON-serializable payload for a challenger upload.
239
+
240
+ Matches the shape ProxyML's dashboard/API expects at
241
+ ``POST /app/projects/{id}/challenger`` — handles the mechanical assembly
242
+ (serializing the export, stamping SDK/core versions, converting
243
+ ``complexity`` to a plain string) so callers don't have to hand-roll it.
244
+ The result is plain ``dict``/``str``/``float`` data, ready for
245
+ ``json.dump`` — upload it either by POSTing it directly, or by saving it
246
+ to a file and using the dashboard's "Upload challenger" button.
247
+
248
+ ``champion_metrics`` is optional: pass ``None`` (the default) to get a
249
+ self-contained export of the challenger alone — e.g. to save/share it
250
+ before you have a champion to compare against — and fill in
251
+ ``champion_metrics`` later. The upload endpoint itself still requires
252
+ ``champion_metrics`` at upload time; this function just doesn't force you
253
+ to have it up front.
254
+
255
+ Args:
256
+ result: output of ``train_challenger()``/``train_auto_challenger()``.
257
+ n_samples: size of the evaluation set both ``result.metrics`` and
258
+ ``champion_metrics`` were scored on. Not derived automatically —
259
+ ``TrainedChallenger`` doesn't retain its internal held-out split,
260
+ and ``champion_metrics`` typically comes from a separate
261
+ ``score_champion()`` call the two need to share, so the caller is
262
+ the only one who actually knows this number.
263
+ champion_metrics: the champion's real-world performance, from
264
+ ``score_champion()`` — same metric keys as ``result.metrics``.
265
+ Omit if you don't have it yet.
266
+ sdk_version: defaults to the installed ``proxyml`` version.
267
+ proxyml_core_version: defaults to the installed ``proxyml-core`` version.
268
+ """
269
+ if sdk_version is None:
270
+ sdk_version = _pkg_version("proxyml")
271
+ if proxyml_core_version is None:
272
+ proxyml_core_version = _pkg_version("proxyml-core")
273
+
274
+ payload: dict[str, Any] = {
275
+ "export": result.export.to_dict(),
276
+ "challenger_metrics": result.metrics,
277
+ "n_samples": n_samples,
278
+ "complexity": result.complexity.value,
279
+ "sdk_version": sdk_version,
280
+ "proxyml_core_version": proxyml_core_version,
281
+ }
282
+ if champion_metrics is not None:
283
+ payload["champion_metrics"] = champion_metrics
284
+ return payload
285
+
286
+
227
287
  def train_auto_challenger(
228
288
  data: str | Path | pd.DataFrame,
229
289
  target_col: str,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxyml
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: Python SDK for calling the ProxyML API
5
5
  Author-email: ProxyML <contact@proxyml.ai>
6
6
  License: Apache License
@@ -9,6 +9,7 @@ from proxyml.local import (
9
9
  LADDERS,
10
10
  TrainedChallenger,
11
11
  score_champion,
12
+ to_challenger_upload,
12
13
  train_auto_challenger,
13
14
  train_challenger,
14
15
  )
@@ -197,6 +198,94 @@ def test_score_champion_uses_same_scoring_as_train_challenger():
197
198
  assert reproduced_metrics["r2"] == pytest.approx(r2_score(target, y_pred))
198
199
 
199
200
 
201
+ def test_train_challenger_result_carries_complexity():
202
+ schema = _schema()
203
+ df = _df(seed=13)
204
+ target = df["age"] * 0.5 + df["income"] * 0.0001
205
+
206
+ result = train_challenger(df, target, schema, complexity=Complexity.FLEXIBLE, task="regression")
207
+
208
+ assert result.complexity is Complexity.FLEXIBLE
209
+
210
+
211
+ def test_train_auto_challenger_result_carries_complexity():
212
+ df = _labeled_df(seed=14)
213
+ result = train_auto_challenger(df, "approved", task="classification", complexity=Complexity.SIMPLE)
214
+
215
+ assert result.complexity is Complexity.SIMPLE
216
+
217
+
218
+ def test_to_challenger_upload_shape_without_champion_metrics():
219
+ schema = _schema()
220
+ df = _df(seed=15)
221
+ target = df["age"] * 0.5 + df["income"] * 0.0001
222
+ result = train_challenger(df, target, schema, complexity=Complexity.MODERATE, task="regression")
223
+
224
+ payload = to_challenger_upload(result, n_samples=40)
225
+
226
+ assert payload["export"] == result.export.to_dict()
227
+ assert payload["challenger_metrics"] == result.metrics
228
+ assert payload["n_samples"] == 40
229
+ assert payload["complexity"] == "moderate"
230
+ assert "champion_metrics" not in payload
231
+ assert isinstance(payload["sdk_version"], str) and payload["sdk_version"]
232
+ assert isinstance(payload["proxyml_core_version"], str) and payload["proxyml_core_version"]
233
+
234
+
235
+ def test_to_challenger_upload_includes_champion_metrics_when_given():
236
+ schema = _schema()
237
+ df = _df(seed=16)
238
+ target = df["age"] * 0.5 + df["income"] * 0.0001
239
+ result = train_challenger(df, target, schema, complexity=Complexity.MODERATE, task="regression")
240
+ champion_metrics = score_champion(target, target, task="regression")
241
+
242
+ payload = to_challenger_upload(result, n_samples=40, champion_metrics=champion_metrics)
243
+
244
+ assert payload["champion_metrics"] == champion_metrics
245
+
246
+
247
+ def test_to_challenger_upload_defaults_versions_to_installed_packages():
248
+ from importlib.metadata import version as pkg_version
249
+
250
+ schema = _schema()
251
+ df = _df(seed=17)
252
+ target = df["age"] * 0.5 + df["income"] * 0.0001
253
+ result = train_challenger(df, target, schema, task="regression")
254
+
255
+ payload = to_challenger_upload(result, n_samples=10)
256
+
257
+ assert payload["sdk_version"] == pkg_version("proxyml")
258
+ assert payload["proxyml_core_version"] == pkg_version("proxyml-core")
259
+
260
+
261
+ def test_to_challenger_upload_allows_version_override():
262
+ schema = _schema()
263
+ df = _df(seed=18)
264
+ target = df["age"] * 0.5 + df["income"] * 0.0001
265
+ result = train_challenger(df, target, schema, task="regression")
266
+
267
+ payload = to_challenger_upload(
268
+ result, n_samples=10, sdk_version="9.9.9", proxyml_core_version="8.8.8"
269
+ )
270
+
271
+ assert payload["sdk_version"] == "9.9.9"
272
+ assert payload["proxyml_core_version"] == "8.8.8"
273
+
274
+
275
+ def test_to_challenger_upload_payload_is_json_serializable():
276
+ import json
277
+
278
+ schema = _schema()
279
+ df = _df(seed=19, n=300)
280
+ target = np.where(df["age"] > 50, "senior", "junior")
281
+ result = train_challenger(df, target, schema, task="classification")
282
+ champion_metrics = score_champion(target, target, task="classification")
283
+
284
+ payload = to_challenger_upload(result, n_samples=300, champion_metrics=champion_metrics)
285
+
286
+ json.dumps(payload) # must not raise
287
+
288
+
200
289
  def test_train_auto_challenger_passes_immutable_cols_to_get_schema():
201
290
  from unittest.mock import patch
202
291
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes