subspacecomputing 0.1.4__tar.gz → 0.1.6__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.
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6}/LICENSE +1 -1
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6}/MANIFEST.in +6 -0
- {subspacecomputing-0.1.4/subspacecomputing.egg-info → subspacecomputing-0.1.6}/PKG-INFO +88 -10
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6}/README.md +85 -7
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6}/pyproject.toml +2 -2
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6}/setup.py +4 -4
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6}/subspacecomputing/__init__.py +1 -1
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6}/subspacecomputing/client.py +189 -1
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6/subspacecomputing.egg-info}/PKG-INFO +88 -10
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6}/subspacecomputing.egg-info/SOURCES.txt +1 -4
- subspacecomputing-0.1.4/tests/test_client_integration.py +0 -320
- subspacecomputing-0.1.4/tests/test_client_unit.py +0 -380
- subspacecomputing-0.1.4/tests/test_pandas_integration.py +0 -41
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6}/setup.cfg +0 -0
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6}/subspacecomputing/errors.py +0 -0
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6}/subspacecomputing/utils/__init__.py +0 -0
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6}/subspacecomputing/utils/pandas_integration.py +0 -0
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6}/subspacecomputing.egg-info/dependency_links.txt +0 -0
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6}/subspacecomputing.egg-info/requires.txt +0 -0
- {subspacecomputing-0.1.4 → subspacecomputing-0.1.6}/subspacecomputing.egg-info/top_level.txt +0 -0
|
@@ -4,6 +4,12 @@ include README.md
|
|
|
4
4
|
include LICENSE
|
|
5
5
|
include pyproject.toml
|
|
6
6
|
recursive-include subspacecomputing *.py
|
|
7
|
+
prune tests
|
|
8
|
+
prune doc
|
|
9
|
+
prune examples
|
|
10
|
+
recursive-exclude tests *
|
|
11
|
+
recursive-exclude doc *
|
|
12
|
+
recursive-exclude examples *
|
|
7
13
|
recursive-exclude * __pycache__
|
|
8
14
|
recursive-exclude * *.py[co]
|
|
9
15
|
recursive-exclude * .git*
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: subspacecomputing
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: Python SDK for ASTRIA — Subspace Computing Engine
|
|
5
5
|
Home-page: https://www.subspacecomputing.com/developer
|
|
6
|
-
Author: Subspace Computing
|
|
7
|
-
Author-email: Subspace Computing <contact@
|
|
6
|
+
Author: Subspace Computing Inc.
|
|
7
|
+
Author-email: "Subspace Computing Inc." <contact@subspacecomputing.com>
|
|
8
8
|
Project-URL: Documentation, https://www.subspacecomputing.com/developer
|
|
9
9
|
Project-URL: Homepage, https://www.subspacecomputing.com/developer
|
|
10
10
|
Project-URL: Support, https://www.subspacecomputing.com/developer
|
|
@@ -26,9 +26,9 @@ Dynamic: home-page
|
|
|
26
26
|
Dynamic: license-file
|
|
27
27
|
Dynamic: requires-python
|
|
28
28
|
|
|
29
|
-
#
|
|
29
|
+
# ASTRIA — Python SDK
|
|
30
30
|
|
|
31
|
-
Python SDK for the Subspace Computing Engine
|
|
31
|
+
Python SDK for ASTRIA, the Subspace Computing Engine.
|
|
32
32
|
|
|
33
33
|
## Installation
|
|
34
34
|
|
|
@@ -45,9 +45,6 @@ from subspacecomputing import ASTRIA
|
|
|
45
45
|
|
|
46
46
|
# Initialize the client (defaults to production URL)
|
|
47
47
|
client = ASTRIA(api_key='your-api-key-here')
|
|
48
|
-
|
|
49
|
-
# For local testing or custom environments (optional)
|
|
50
|
-
# client = ASTRIA(api_key='your-api-key-here', base_url='http://localhost:8000')
|
|
51
48
|
```
|
|
52
49
|
|
|
53
50
|
### Teams (`X-Team-Id`)
|
|
@@ -176,6 +173,87 @@ print(f"Total capital: {result['aggregations']['capital_total']}")
|
|
|
176
173
|
print(f"Average: {result['aggregations']['moyenne_capital']}")
|
|
177
174
|
```
|
|
178
175
|
|
|
176
|
+
### Run a stored model (`projection_id` + inputs)
|
|
177
|
+
|
|
178
|
+
Execute a projection already saved in Subspace. Load `run_inputs` once (cache in your app), build UI/batch from that descriptor, then call `run_model` many times with only the overrides. Astria runs the calculation; model structure (`formula`, dist type, `per`) is never overridable.
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
proj = client.load_model(projection_id) # get_projection + in-memory cache on this client
|
|
182
|
+
# Or: proj = client.get_projection(projection_id)
|
|
183
|
+
|
|
184
|
+
result = client.run_model(projection_id, inputs={"capital": 25000})
|
|
185
|
+
print(result["final_values"]["capital"])
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Input shapes by `run_inputs.kind`:
|
|
189
|
+
|
|
190
|
+
| `kind` | Example value in `inputs` |
|
|
191
|
+
|--------|---------------------------|
|
|
192
|
+
| `init` | `{"capital": 25000}` |
|
|
193
|
+
| `params` | `{"taux": {"min": 0.02, "max": 0.05}}` |
|
|
194
|
+
| `values` | `{"inflation": [0.01, 0.02, 0.03]}` |
|
|
195
|
+
| `table_ref` / `table` | `{"mortality": "<uuid>"}` or `{"grid": {...}}` |
|
|
196
|
+
| `run` | `{"scenarios": 100, "steps": 12}` |
|
|
197
|
+
|
|
198
|
+
Unknown input keys → API **400** `unknown_run_input`. `rerun_run(run_id)` re-executes a past **run snapshot**, not the live model.
|
|
199
|
+
|
|
200
|
+
Pattern for a dynamic form / batch loop:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
proj = client.load_model(projection_id)
|
|
204
|
+
inputs = {}
|
|
205
|
+
for desc in proj.get("run_inputs") or []:
|
|
206
|
+
kind = desc.get("kind")
|
|
207
|
+
name = desc["name"]
|
|
208
|
+
if kind == "init":
|
|
209
|
+
inputs[name] = desc.get("default") # or from UI
|
|
210
|
+
elif kind == "params":
|
|
211
|
+
inputs[name] = dict(desc.get("default") or {})
|
|
212
|
+
# elif kind == "values": ...
|
|
213
|
+
result = client.run_model(projection_id, inputs=inputs)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Runs: read, artifact, rerun, replay
|
|
217
|
+
|
|
218
|
+
Four distinct operations on persisted runs:
|
|
219
|
+
|
|
220
|
+
| Method | What it does |
|
|
221
|
+
|--------|----------------|
|
|
222
|
+
| `get_run(run_id)` | Read metadata, stored SP Model snapshot, and `result_summary` |
|
|
223
|
+
| `get_run_artifact(artifact_id)` | Read heavy results already computed (`final_values` / URL) |
|
|
224
|
+
| `rerun_run(run_id, spec_patch=None)` | Re-execute the run's snapshot in Astria; creates a **new** run |
|
|
225
|
+
| `replay_run(run_id, scenario_id)` | Reproduce **one** Monte Carlo scenario when seeds were stored |
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
# Cross-language analysis: app computed → Python reads original results
|
|
229
|
+
run = client.get_run(run_id)
|
|
230
|
+
print(run["result_summary"])
|
|
231
|
+
if run.get("artefact_id"):
|
|
232
|
+
artifact = client.get_run_artifact(run["artefact_id"])
|
|
233
|
+
print(artifact.get("final_values"))
|
|
234
|
+
|
|
235
|
+
# Re-execute the exact snapshot (validation) — new run, same projection
|
|
236
|
+
baseline = client.rerun_run(run_id)
|
|
237
|
+
print(baseline["run_id"], baseline["source_spec_hash"], baseline["spec_changed"])
|
|
238
|
+
|
|
239
|
+
# What-if: fork the snapshot with a JSON Merge Patch (source run unchanged)
|
|
240
|
+
what_if = client.rerun_run(
|
|
241
|
+
run_id,
|
|
242
|
+
spec_patch={"meta": {"seed": 42}},
|
|
243
|
+
)
|
|
244
|
+
print(what_if["executed_spec_hash"], what_if["final_values"])
|
|
245
|
+
|
|
246
|
+
# Exact MC scenario reproduction (requires meta.store_seeds_for_replay at creation)
|
|
247
|
+
replay = client.replay_run(run_id, scenario_id=0)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Notes**
|
|
251
|
+
|
|
252
|
+
- `rerun_run` does **not** load the projection's current `dsl_template`; it uses the run's stored `spec`.
|
|
253
|
+
- Requires `persist_mode=full` (headless minimal/metadata keys cannot create a new run).
|
|
254
|
+
- Exact stochastic reproduction of one scenario uses `replay_run` + stored seeds; a plain `rerun_run` of a Monte Carlo may draw new samples unless `meta.seed` was in the snapshot.
|
|
255
|
+
- Live `table_refs` may resolve to newer data than at original execution time.
|
|
256
|
+
|
|
179
257
|
### Validation
|
|
180
258
|
|
|
181
259
|
```python
|
|
@@ -259,7 +337,7 @@ Check out the full documentation at https://www.subspacecomputing.com/developer
|
|
|
259
337
|
|
|
260
338
|
API reference is available at https://www.subspacecomputing.com/docs
|
|
261
339
|
|
|
262
|
-
For support, reach out to contact@
|
|
340
|
+
For support, reach out to contact@subspacecomputing.com
|
|
263
341
|
|
|
264
342
|
## License
|
|
265
343
|
|
|
@@ -267,4 +345,4 @@ MIT License. Check the LICENSE file for details.
|
|
|
267
345
|
|
|
268
346
|
## Copyright
|
|
269
347
|
|
|
270
|
-
©
|
|
348
|
+
© 2026 Subspace Computing Inc. All Rights Reserved
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ASTRIA — Python SDK
|
|
2
2
|
|
|
3
|
-
Python SDK for the Subspace Computing Engine
|
|
3
|
+
Python SDK for ASTRIA, the Subspace Computing Engine.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -17,9 +17,6 @@ from subspacecomputing import ASTRIA
|
|
|
17
17
|
|
|
18
18
|
# Initialize the client (defaults to production URL)
|
|
19
19
|
client = ASTRIA(api_key='your-api-key-here')
|
|
20
|
-
|
|
21
|
-
# For local testing or custom environments (optional)
|
|
22
|
-
# client = ASTRIA(api_key='your-api-key-here', base_url='http://localhost:8000')
|
|
23
20
|
```
|
|
24
21
|
|
|
25
22
|
### Teams (`X-Team-Id`)
|
|
@@ -148,6 +145,87 @@ print(f"Total capital: {result['aggregations']['capital_total']}")
|
|
|
148
145
|
print(f"Average: {result['aggregations']['moyenne_capital']}")
|
|
149
146
|
```
|
|
150
147
|
|
|
148
|
+
### Run a stored model (`projection_id` + inputs)
|
|
149
|
+
|
|
150
|
+
Execute a projection already saved in Subspace. Load `run_inputs` once (cache in your app), build UI/batch from that descriptor, then call `run_model` many times with only the overrides. Astria runs the calculation; model structure (`formula`, dist type, `per`) is never overridable.
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
proj = client.load_model(projection_id) # get_projection + in-memory cache on this client
|
|
154
|
+
# Or: proj = client.get_projection(projection_id)
|
|
155
|
+
|
|
156
|
+
result = client.run_model(projection_id, inputs={"capital": 25000})
|
|
157
|
+
print(result["final_values"]["capital"])
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Input shapes by `run_inputs.kind`:
|
|
161
|
+
|
|
162
|
+
| `kind` | Example value in `inputs` |
|
|
163
|
+
|--------|---------------------------|
|
|
164
|
+
| `init` | `{"capital": 25000}` |
|
|
165
|
+
| `params` | `{"taux": {"min": 0.02, "max": 0.05}}` |
|
|
166
|
+
| `values` | `{"inflation": [0.01, 0.02, 0.03]}` |
|
|
167
|
+
| `table_ref` / `table` | `{"mortality": "<uuid>"}` or `{"grid": {...}}` |
|
|
168
|
+
| `run` | `{"scenarios": 100, "steps": 12}` |
|
|
169
|
+
|
|
170
|
+
Unknown input keys → API **400** `unknown_run_input`. `rerun_run(run_id)` re-executes a past **run snapshot**, not the live model.
|
|
171
|
+
|
|
172
|
+
Pattern for a dynamic form / batch loop:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
proj = client.load_model(projection_id)
|
|
176
|
+
inputs = {}
|
|
177
|
+
for desc in proj.get("run_inputs") or []:
|
|
178
|
+
kind = desc.get("kind")
|
|
179
|
+
name = desc["name"]
|
|
180
|
+
if kind == "init":
|
|
181
|
+
inputs[name] = desc.get("default") # or from UI
|
|
182
|
+
elif kind == "params":
|
|
183
|
+
inputs[name] = dict(desc.get("default") or {})
|
|
184
|
+
# elif kind == "values": ...
|
|
185
|
+
result = client.run_model(projection_id, inputs=inputs)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Runs: read, artifact, rerun, replay
|
|
189
|
+
|
|
190
|
+
Four distinct operations on persisted runs:
|
|
191
|
+
|
|
192
|
+
| Method | What it does |
|
|
193
|
+
|--------|----------------|
|
|
194
|
+
| `get_run(run_id)` | Read metadata, stored SP Model snapshot, and `result_summary` |
|
|
195
|
+
| `get_run_artifact(artifact_id)` | Read heavy results already computed (`final_values` / URL) |
|
|
196
|
+
| `rerun_run(run_id, spec_patch=None)` | Re-execute the run's snapshot in Astria; creates a **new** run |
|
|
197
|
+
| `replay_run(run_id, scenario_id)` | Reproduce **one** Monte Carlo scenario when seeds were stored |
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
# Cross-language analysis: app computed → Python reads original results
|
|
201
|
+
run = client.get_run(run_id)
|
|
202
|
+
print(run["result_summary"])
|
|
203
|
+
if run.get("artefact_id"):
|
|
204
|
+
artifact = client.get_run_artifact(run["artefact_id"])
|
|
205
|
+
print(artifact.get("final_values"))
|
|
206
|
+
|
|
207
|
+
# Re-execute the exact snapshot (validation) — new run, same projection
|
|
208
|
+
baseline = client.rerun_run(run_id)
|
|
209
|
+
print(baseline["run_id"], baseline["source_spec_hash"], baseline["spec_changed"])
|
|
210
|
+
|
|
211
|
+
# What-if: fork the snapshot with a JSON Merge Patch (source run unchanged)
|
|
212
|
+
what_if = client.rerun_run(
|
|
213
|
+
run_id,
|
|
214
|
+
spec_patch={"meta": {"seed": 42}},
|
|
215
|
+
)
|
|
216
|
+
print(what_if["executed_spec_hash"], what_if["final_values"])
|
|
217
|
+
|
|
218
|
+
# Exact MC scenario reproduction (requires meta.store_seeds_for_replay at creation)
|
|
219
|
+
replay = client.replay_run(run_id, scenario_id=0)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Notes**
|
|
223
|
+
|
|
224
|
+
- `rerun_run` does **not** load the projection's current `dsl_template`; it uses the run's stored `spec`.
|
|
225
|
+
- Requires `persist_mode=full` (headless minimal/metadata keys cannot create a new run).
|
|
226
|
+
- Exact stochastic reproduction of one scenario uses `replay_run` + stored seeds; a plain `rerun_run` of a Monte Carlo may draw new samples unless `meta.seed` was in the snapshot.
|
|
227
|
+
- Live `table_refs` may resolve to newer data than at original execution time.
|
|
228
|
+
|
|
151
229
|
### Validation
|
|
152
230
|
|
|
153
231
|
```python
|
|
@@ -231,7 +309,7 @@ Check out the full documentation at https://www.subspacecomputing.com/developer
|
|
|
231
309
|
|
|
232
310
|
API reference is available at https://www.subspacecomputing.com/docs
|
|
233
311
|
|
|
234
|
-
For support, reach out to contact@
|
|
312
|
+
For support, reach out to contact@subspacecomputing.com
|
|
235
313
|
|
|
236
314
|
## License
|
|
237
315
|
|
|
@@ -239,4 +317,4 @@ MIT License. Check the LICENSE file for details.
|
|
|
239
317
|
|
|
240
318
|
## Copyright
|
|
241
319
|
|
|
242
|
-
©
|
|
320
|
+
© 2026 Subspace Computing Inc. All Rights Reserved
|
|
@@ -6,9 +6,9 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "subspacecomputing"
|
|
9
|
-
version = "0.1.
|
|
9
|
+
version = "0.1.6"
|
|
10
10
|
description = "Python SDK for ASTRIA — Subspace Computing Engine"
|
|
11
|
-
authors = [{name = "Subspace Computing", email = "contact@
|
|
11
|
+
authors = [{name = "Subspace Computing Inc.", email = "contact@subspacecomputing.com"}]
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.10"
|
|
14
14
|
dependencies = [
|
|
@@ -9,16 +9,16 @@ try:
|
|
|
9
9
|
with open("README.md", "r", encoding="utf-8") as fh:
|
|
10
10
|
long_description = fh.read()
|
|
11
11
|
except FileNotFoundError:
|
|
12
|
-
long_description = "Python SDK for Subspace Computing Engine API
|
|
12
|
+
long_description = "Python SDK for ASTRIA — Subspace Computing Engine API"
|
|
13
13
|
|
|
14
14
|
setup(
|
|
15
15
|
name="subspacecomputing",
|
|
16
|
-
version="0.1.
|
|
16
|
+
version="0.1.6",
|
|
17
17
|
description="Python SDK for ASTRIA — Subspace Computing Engine",
|
|
18
18
|
long_description=long_description,
|
|
19
19
|
long_description_content_type="text/markdown",
|
|
20
|
-
author="Subspace Computing",
|
|
21
|
-
author_email="contact@
|
|
20
|
+
author="Subspace Computing Inc.",
|
|
21
|
+
author_email="contact@subspacecomputing.com",
|
|
22
22
|
url="https://www.subspacecomputing.com/developer",
|
|
23
23
|
packages=find_packages(),
|
|
24
24
|
install_requires=[
|
|
@@ -6,6 +6,7 @@ This module provides a simple Python interface to interact with the Subspace Com
|
|
|
6
6
|
|
|
7
7
|
import requests
|
|
8
8
|
from typing import Dict, Any, Optional, List
|
|
9
|
+
from urllib.parse import quote
|
|
9
10
|
from .errors import (
|
|
10
11
|
SubspaceError,
|
|
11
12
|
QuotaExceededError,
|
|
@@ -60,6 +61,7 @@ class ASTRIA:
|
|
|
60
61
|
headers["X-Team-Id"] = self._team_id
|
|
61
62
|
self.session.headers.update(headers)
|
|
62
63
|
self.last_response = None # Store last response for header access
|
|
64
|
+
self._model_cache: Dict[str, Dict[str, Any]] = {}
|
|
63
65
|
|
|
64
66
|
def set_team_id(self, team_id: Optional[str] = None) -> None:
|
|
65
67
|
"""Set or clear ``X-Team-Id`` for subsequent requests."""
|
|
@@ -148,6 +150,56 @@ class ASTRIA:
|
|
|
148
150
|
self._handle_response(response)
|
|
149
151
|
return response.json()
|
|
150
152
|
|
|
153
|
+
# Registry — execute published capability by name (production version)
|
|
154
|
+
|
|
155
|
+
def execute(
|
|
156
|
+
self,
|
|
157
|
+
name: str,
|
|
158
|
+
inputs: Optional[Dict[str, Any]] = None,
|
|
159
|
+
*,
|
|
160
|
+
correlation_id: Optional[str] = None,
|
|
161
|
+
) -> Dict[str, Any]:
|
|
162
|
+
"""
|
|
163
|
+
Execute a published Registry capability by name (resolves production version).
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
name: Capability name (e.g. ``calculatePremium``).
|
|
167
|
+
inputs: Parameters merged into the stored spec (batch_params / variables).
|
|
168
|
+
correlation_id: Optional trace id (body + ``X-Correlation-Id`` header).
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Simulate-style response plus ``capability``, ``capability_version``,
|
|
172
|
+
and optional ``correlation_id``.
|
|
173
|
+
|
|
174
|
+
Example:
|
|
175
|
+
result = client.execute(
|
|
176
|
+
"valorisation_ebitda",
|
|
177
|
+
inputs={"capital": 1_000_000},
|
|
178
|
+
correlation_id="quote-001",
|
|
179
|
+
)
|
|
180
|
+
"""
|
|
181
|
+
if not name or not str(name).strip():
|
|
182
|
+
raise ValueError("Capability name is required.")
|
|
183
|
+
|
|
184
|
+
payload: Dict[str, Any] = {"inputs": inputs or {}}
|
|
185
|
+
if correlation_id:
|
|
186
|
+
payload["correlation_id"] = correlation_id
|
|
187
|
+
|
|
188
|
+
extra_headers: Dict[str, str] = {}
|
|
189
|
+
if correlation_id:
|
|
190
|
+
extra_headers["X-Correlation-Id"] = correlation_id
|
|
191
|
+
|
|
192
|
+
safe_name = quote(str(name).strip(), safe="")
|
|
193
|
+
response = self.session.post(
|
|
194
|
+
f"{self.base_url}/capabilities/{safe_name}/execute",
|
|
195
|
+
json=payload,
|
|
196
|
+
headers=extra_headers or None,
|
|
197
|
+
timeout=self.timeout,
|
|
198
|
+
)
|
|
199
|
+
self.last_response = response
|
|
200
|
+
self._handle_response(response)
|
|
201
|
+
return response.json()
|
|
202
|
+
|
|
151
203
|
def replay_scenario(
|
|
152
204
|
self,
|
|
153
205
|
original_spec: Dict[str, Any],
|
|
@@ -328,7 +380,7 @@ class ASTRIA:
|
|
|
328
380
|
projection_id: Projection ID
|
|
329
381
|
|
|
330
382
|
Returns:
|
|
331
|
-
Response with projection details
|
|
383
|
+
Response with projection details (includes ``run_inputs`` when present)
|
|
332
384
|
"""
|
|
333
385
|
response = self.session.get(
|
|
334
386
|
f"{self.base_url}/projections/{projection_id}",
|
|
@@ -338,6 +390,60 @@ class ASTRIA:
|
|
|
338
390
|
self._handle_response(response)
|
|
339
391
|
return response.json()
|
|
340
392
|
|
|
393
|
+
def load_model(self, projection_id: str) -> Dict[str, Any]:
|
|
394
|
+
"""
|
|
395
|
+
Load a projection (schema + ``run_inputs``) with a simple in-memory cache
|
|
396
|
+
on this client instance. Prefer this once per session; still avoid calling
|
|
397
|
+
on every ``run_model``.
|
|
398
|
+
"""
|
|
399
|
+
if not projection_id or not str(projection_id).strip():
|
|
400
|
+
raise ValueError("projection_id is required.")
|
|
401
|
+
pid = str(projection_id).strip()
|
|
402
|
+
cached = self._model_cache.get(pid)
|
|
403
|
+
if cached is not None:
|
|
404
|
+
return cached
|
|
405
|
+
proj = self.get_projection(pid)
|
|
406
|
+
self._model_cache[pid] = proj
|
|
407
|
+
return proj
|
|
408
|
+
|
|
409
|
+
def clear_model_cache(self, projection_id: Optional[str] = None) -> None:
|
|
410
|
+
"""Clear in-memory model cache from :meth:`load_model`."""
|
|
411
|
+
if projection_id and str(projection_id).strip():
|
|
412
|
+
self._model_cache.pop(str(projection_id).strip(), None)
|
|
413
|
+
else:
|
|
414
|
+
self._model_cache.clear()
|
|
415
|
+
|
|
416
|
+
def run_model(
|
|
417
|
+
self,
|
|
418
|
+
projection_id: str,
|
|
419
|
+
inputs: Optional[Dict[str, Any]] = None,
|
|
420
|
+
) -> Dict[str, Any]:
|
|
421
|
+
"""
|
|
422
|
+
Run a stored projection by ID with optional runtime input overrides.
|
|
423
|
+
|
|
424
|
+
Applies ``run_inputs`` by kind (init, params, values, tables, run knobs).
|
|
425
|
+
Does not change formula / dist type / per. Distinct from :meth:`rerun_run`.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
projection_id: Stored projection UUID
|
|
429
|
+
inputs: Optional overrides keyed by run_input name
|
|
430
|
+
(e.g. ``{"capital": 25000}``, ``{"taux": {"min": 0.02, "max": 0.05}}``)
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
Same shape as ``project()`` or ``simulate()`` depending on scenarios
|
|
434
|
+
"""
|
|
435
|
+
if not projection_id or not str(projection_id).strip():
|
|
436
|
+
raise ValueError("projection_id is required.")
|
|
437
|
+
safe_id = quote(str(projection_id).strip(), safe="")
|
|
438
|
+
response = self.session.post(
|
|
439
|
+
f"{self.base_url}/projections/{safe_id}/run",
|
|
440
|
+
json={"inputs": inputs or {}},
|
|
441
|
+
timeout=self.timeout,
|
|
442
|
+
)
|
|
443
|
+
self.last_response = response
|
|
444
|
+
self._handle_response(response)
|
|
445
|
+
return response.json()
|
|
446
|
+
|
|
341
447
|
def delete_projection(self, projection_id: str) -> None:
|
|
342
448
|
"""
|
|
343
449
|
Delete a projection.
|
|
@@ -414,6 +520,41 @@ class ASTRIA:
|
|
|
414
520
|
self._handle_response(response)
|
|
415
521
|
return response.json()
|
|
416
522
|
|
|
523
|
+
def get_run_proof(self, run_id: str, *, include_spec: bool = False) -> Dict[str, Any]:
|
|
524
|
+
"""
|
|
525
|
+
Fetch Astria run proof pack for a projection run.
|
|
526
|
+
|
|
527
|
+
Args:
|
|
528
|
+
run_id: Projection run ID
|
|
529
|
+
include_spec: If True, include resolved SP snapshot in the proof
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
Proof pack (schema_version, hashes, seed, …)
|
|
533
|
+
"""
|
|
534
|
+
params = {"include_spec": "true"} if include_spec else None
|
|
535
|
+
response = self.session.get(
|
|
536
|
+
f"{self.base_url}/projection-runs/{run_id}/proof",
|
|
537
|
+
params=params,
|
|
538
|
+
timeout=self.timeout,
|
|
539
|
+
)
|
|
540
|
+
self.last_response = response
|
|
541
|
+
self._handle_response(response)
|
|
542
|
+
return response.json()
|
|
543
|
+
|
|
544
|
+
def get_execution_proof(self, execution_id: str) -> Dict[str, Any]:
|
|
545
|
+
"""
|
|
546
|
+
Fetch Astria run proof pack from a Registry execution (lineage.astria).
|
|
547
|
+
|
|
548
|
+
Requires Console JWT auth on the session (same as other /executions routes).
|
|
549
|
+
"""
|
|
550
|
+
response = self.session.get(
|
|
551
|
+
f"{self.base_url}/executions/{execution_id}/proof",
|
|
552
|
+
timeout=self.timeout,
|
|
553
|
+
)
|
|
554
|
+
self.last_response = response
|
|
555
|
+
self._handle_response(response)
|
|
556
|
+
return response.json()
|
|
557
|
+
|
|
417
558
|
def replay_run(self, run_id: str, scenario_id: int) -> Dict[str, Any]:
|
|
418
559
|
"""
|
|
419
560
|
Replay a scenario from a stored run (requires persisted seeds).
|
|
@@ -442,6 +583,53 @@ class ASTRIA:
|
|
|
442
583
|
self._handle_response(response)
|
|
443
584
|
return response.json()
|
|
444
585
|
|
|
586
|
+
def rerun_run(
|
|
587
|
+
self,
|
|
588
|
+
run_id: str,
|
|
589
|
+
spec_patch: Optional[Dict[str, Any]] = None,
|
|
590
|
+
) -> Dict[str, Any]:
|
|
591
|
+
"""
|
|
592
|
+
Re-execute the SP Model snapshot of a persisted run (optional what-if patch).
|
|
593
|
+
|
|
594
|
+
Creates a **new** run on the same projection with ``source_run_id`` set.
|
|
595
|
+
Does **not** mutate the source run or the projection's current template.
|
|
596
|
+
|
|
597
|
+
Unlike ``replay_run``, this recalculates the full snapshot (project if
|
|
598
|
+
scenarios==1, otherwise simulate). Exact Monte Carlo scenario reproduction
|
|
599
|
+
still requires ``replay_run`` with stored seeds.
|
|
600
|
+
|
|
601
|
+
Args:
|
|
602
|
+
run_id: Source run UUID.
|
|
603
|
+
spec_patch: Optional JSON Merge Patch applied to the stored spec
|
|
604
|
+
(objects merge recursively; arrays/scalars replace; null deletes).
|
|
605
|
+
Reserved fields ``run_id``, ``meta.projection_id``,
|
|
606
|
+
``meta._replay_seeds`` are rejected by the API.
|
|
607
|
+
|
|
608
|
+
Returns:
|
|
609
|
+
Project or simulate response plus ``source_run_id``,
|
|
610
|
+
``source_spec_hash``, ``executed_spec_hash``, ``spec_changed``.
|
|
611
|
+
|
|
612
|
+
Example:
|
|
613
|
+
baseline = client.rerun_run(run_id)
|
|
614
|
+
what_if = client.rerun_run(
|
|
615
|
+
run_id,
|
|
616
|
+
spec_patch={"meta": {"seed": 42}},
|
|
617
|
+
)
|
|
618
|
+
"""
|
|
619
|
+
if not run_id or not str(run_id).strip():
|
|
620
|
+
raise ValueError("run_id is required.")
|
|
621
|
+
payload: Dict[str, Any] = {}
|
|
622
|
+
if spec_patch is not None:
|
|
623
|
+
payload["spec_patch"] = spec_patch
|
|
624
|
+
response = self.session.post(
|
|
625
|
+
f"{self.base_url}/projection-runs/{str(run_id).strip()}/rerun",
|
|
626
|
+
json=payload,
|
|
627
|
+
timeout=self.timeout,
|
|
628
|
+
)
|
|
629
|
+
self.last_response = response
|
|
630
|
+
self._handle_response(response)
|
|
631
|
+
return response.json()
|
|
632
|
+
|
|
445
633
|
def delete_run(self, run_id: str) -> None:
|
|
446
634
|
"""
|
|
447
635
|
Delete a run.
|