sdcmodeler 4.2.0__tar.gz → 4.2.1__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.
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/PKG-INFO +4 -2
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/README.md +3 -1
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/pyproject.toml +1 -1
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/src/sdcmodeler/__init__.py +1 -1
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/src/sdcmodeler/mcp_server.py +2 -2
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/src/sdcmodeler/pricing.py +9 -5
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/src/sdcmodeler.egg-info/PKG-INFO +4 -2
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/tests/test_mcp_server.py +1 -1
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/tests/test_pricing.py +15 -3
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/LICENSE +0 -0
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/setup.cfg +0 -0
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/src/sdcmodeler/cli.py +0 -0
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/src/sdcmodeler/client.py +0 -0
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/src/sdcmodeler.egg-info/SOURCES.txt +0 -0
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/src/sdcmodeler.egg-info/dependency_links.txt +0 -0
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/src/sdcmodeler.egg-info/entry_points.txt +0 -0
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/src/sdcmodeler.egg-info/requires.txt +0 -0
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/src/sdcmodeler.egg-info/top_level.txt +0 -0
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/tests/test_cli.py +0 -0
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/tests/test_client.py +0 -0
- {sdcmodeler-4.2.0 → sdcmodeler-4.2.1}/tests/test_registry_launch.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sdcmodeler
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.1
|
|
4
4
|
Summary: The modeler's client for SDCStudio: search the catalog, estimate, assemble and publish SDC4 data models, generate applications; a CLI and an MCP server over one client
|
|
5
5
|
Author: Axius SDC, Inc.
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -84,7 +84,9 @@ sdcmodeler models "patient"
|
|
|
84
84
|
sdcmodeler model <ct_id>
|
|
85
85
|
sdcmodeler artifact <ct_id> xsd --dir ./schemas
|
|
86
86
|
sdcmodeler estimate tree.json --title "Vitals"
|
|
87
|
-
sdcmodeler build tree.json --title "Vitals"
|
|
87
|
+
sdcmodeler build tree.json --title "Vitals" --wait
|
|
88
|
+
sdcmodeler job <job_ct_id>
|
|
89
|
+
sdcmodeler package <ct_id> --dir ./packages
|
|
88
90
|
sdcmodeler app <ct_id> --profile lightweight --dir ./apps
|
|
89
91
|
sdcmodeler wallet
|
|
90
92
|
```
|
|
@@ -58,7 +58,9 @@ sdcmodeler models "patient"
|
|
|
58
58
|
sdcmodeler model <ct_id>
|
|
59
59
|
sdcmodeler artifact <ct_id> xsd --dir ./schemas
|
|
60
60
|
sdcmodeler estimate tree.json --title "Vitals"
|
|
61
|
-
sdcmodeler build tree.json --title "Vitals"
|
|
61
|
+
sdcmodeler build tree.json --title "Vitals" --wait
|
|
62
|
+
sdcmodeler job <job_ct_id>
|
|
63
|
+
sdcmodeler package <ct_id> --dir ./packages
|
|
62
64
|
sdcmodeler app <ct_id> --profile lightweight --dir ./apps
|
|
63
65
|
sdcmodeler wallet
|
|
64
66
|
```
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sdcmodeler"
|
|
7
|
-
version = "4.2.
|
|
7
|
+
version = "4.2.1"
|
|
8
8
|
description = "The modeler's client for SDCStudio: search the catalog, estimate, assemble and publish SDC4 data models, generate applications; a CLI and an MCP server over one client"
|
|
9
9
|
license = "Apache-2.0"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -17,7 +17,7 @@ servers: sdcvalidator validates, sdcgovernance governs, sdcreader reads,
|
|
|
17
17
|
sdcmodeler models. It also names the concept SDCStudio already has: every user
|
|
18
18
|
is a Modeler, and the Modeler's projects scope every call.
|
|
19
19
|
"""
|
|
20
|
-
__version__ = "4.2.
|
|
20
|
+
__version__ = "4.2.1"
|
|
21
21
|
|
|
22
22
|
from sdcmodeler.client import ( # noqa: E402
|
|
23
23
|
AssemblyRefused,
|
|
@@ -365,7 +365,7 @@ def _handle_request(request: Any) -> dict | None:
|
|
|
365
365
|
"components; estimate before you build and show the person the cost; build only with the "
|
|
366
366
|
"estimate_id you showed; prefer the model package over the generated application when the "
|
|
367
367
|
"feature lives inside an existing application. A 402 means the person funds the wallet at "
|
|
368
|
-
f"{get_client().base_url}/app/wallet; never try to pay. "
|
|
368
|
+
f"{get_client().base_url}/app/settings?tab=wallet; never try to pay. "
|
|
369
369
|
+ ("A key is configured, so building and downloading are available within its policy."
|
|
370
370
|
if has_key else "No key is configured: the catalog and estimates work; building needs SDCSTUDIO_API_KEY.")
|
|
371
371
|
),
|
|
@@ -388,7 +388,7 @@ def _handle_request(request: Any) -> dict | None:
|
|
|
388
388
|
except InsufficientCredits as exc:
|
|
389
389
|
return _tool_error(req_id, (
|
|
390
390
|
f"Insufficient credits: this needs {exc.estimated_cost_credits} credits and the wallet has "
|
|
391
|
-
f"{exc.balance_credits}. The person funds the wallet at {get_client().base_url}/app/wallet; stop here."
|
|
391
|
+
f"{exc.balance_credits}. The person funds the wallet at {get_client().base_url}/app/settings?tab=wallet; stop here."
|
|
392
392
|
))
|
|
393
393
|
except KeyPolicyRefused as exc:
|
|
394
394
|
return _tool_error(req_id, f"The API key refused this ({exc.error_type}): {exc}")
|
|
@@ -5,10 +5,10 @@ SDCStudio's prices (Terms of Service, section 6; ``docs/user-guides/wallet-billi
|
|
|
5
5
|
1,000 credits per dollar; minting a component 100 credits; assembling a model
|
|
6
6
|
500; downloading an application 1,500, once per user per model since 4.16.0;
|
|
7
7
|
settling a record 1,000. The estimate below resolves an assembly tree the way
|
|
8
|
-
the server does
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
the
|
|
8
|
+
the server does (SDCStudio 4.17.5, one rule on every path): 500 for the model,
|
|
9
|
+
which covers its root cluster; 100 for every other new cluster; 100 for every
|
|
10
|
+
new component; anything with a ``ct_id`` is reused and free. With a key the
|
|
11
|
+
server's estimate endpoint is used; this is the fallback without one.
|
|
12
12
|
"""
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
@@ -39,6 +39,8 @@ def _walk(node: dict[str, Any], path: str, reuse: list, mint: list, problems: li
|
|
|
39
39
|
continue
|
|
40
40
|
if not name:
|
|
41
41
|
problems.append(f"{path}: a cluster needs a label")
|
|
42
|
+
# A new cluster below the root is minted and billed like a component.
|
|
43
|
+
mint.append({"path": f"{path}/{name}", "label": name, "data_type": "Cluster"})
|
|
42
44
|
_walk(cluster, f"{path}/{name}", reuse, mint, problems)
|
|
43
45
|
|
|
44
46
|
|
|
@@ -57,6 +59,7 @@ def estimate_tree(tree: dict[str, Any], title: str = "") -> dict[str, Any]:
|
|
|
57
59
|
problems: list = []
|
|
58
60
|
_walk(tree, tree.get("label", "root"), reuse, mint, problems)
|
|
59
61
|
mint_credits = MINT_COMPONENT_CREDITS * len(mint)
|
|
62
|
+
title = (title or "").strip() # the server hashes the title as its serializer trims it
|
|
60
63
|
canonical = json.dumps({"title": title, "tree": tree}, sort_keys=True, separators=(",", ":"), default=str)
|
|
61
64
|
return {
|
|
62
65
|
"estimate_id": hashlib.sha256(canonical.encode("utf-8")).hexdigest()[:12],
|
|
@@ -67,5 +70,6 @@ def estimate_tree(tree: dict[str, Any], title: str = "") -> dict[str, Any]:
|
|
|
67
70
|
"model_credits": ASSEMBLE_MODEL_CREDITS,
|
|
68
71
|
"mint_credits": mint_credits,
|
|
69
72
|
"total_credits": ASSEMBLE_MODEL_CREDITS + mint_credits,
|
|
70
|
-
|
|
73
|
+
# The path assemble/ takes: a new component makes it mixed, a new cluster alone does not.
|
|
74
|
+
"mode": "mixed" if any(m["data_type"] != "Cluster" for m in mint) else "reuse",
|
|
71
75
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sdcmodeler
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.1
|
|
4
4
|
Summary: The modeler's client for SDCStudio: search the catalog, estimate, assemble and publish SDC4 data models, generate applications; a CLI and an MCP server over one client
|
|
5
5
|
Author: Axius SDC, Inc.
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -84,7 +84,9 @@ sdcmodeler models "patient"
|
|
|
84
84
|
sdcmodeler model <ct_id>
|
|
85
85
|
sdcmodeler artifact <ct_id> xsd --dir ./schemas
|
|
86
86
|
sdcmodeler estimate tree.json --title "Vitals"
|
|
87
|
-
sdcmodeler build tree.json --title "Vitals"
|
|
87
|
+
sdcmodeler build tree.json --title "Vitals" --wait
|
|
88
|
+
sdcmodeler job <job_ct_id>
|
|
89
|
+
sdcmodeler package <ct_id> --dir ./packages
|
|
88
90
|
sdcmodeler app <ct_id> --profile lightweight --dir ./apps
|
|
89
91
|
sdcmodeler wallet
|
|
90
92
|
```
|
|
@@ -77,7 +77,7 @@ def test_a_402_names_the_wallet_page_and_stops(bound):
|
|
|
77
77
|
bound.on("POST", "/api/v1/dmgen/assemble/", status=402, body={"error": "x", "estimated_cost_credits": 600, "balance_credits": 20})
|
|
78
78
|
est, _ = call("estimate_model", {"title": "Vitals", "tree": TREE})
|
|
79
79
|
_, err = call("build_model", {"estimate_id": est["estimate_id"], "title": "Vitals", "tree": TREE})
|
|
80
|
-
assert "600 credits" in err and f"{BASE}/app/wallet" in err
|
|
80
|
+
assert "600 credits" in err and f"{BASE}/app/settings?tab=wallet" in err
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
def test_generate_app_needs_confirmation_and_reports_the_charge(bound, tmp_path):
|
|
@@ -13,9 +13,10 @@ TREE = {
|
|
|
13
13
|
def test_reuse_is_free_and_mints_are_counted():
|
|
14
14
|
e = estimate_tree(TREE, "Vitals")
|
|
15
15
|
assert [r["ct_id"] for r in e["reuse"]] == ["abc123abc123abc123abc123", "def456def456def456def456"]
|
|
16
|
-
|
|
17
|
-
assert
|
|
18
|
-
assert e["
|
|
16
|
+
# The new nested cluster Context is minted too; the root cluster is the model's (SDCStudio 4.17.5).
|
|
17
|
+
assert [(m["label"], m["data_type"]) for m in e["mint"]] == [("Pulse", "XdCount"), ("Context", "Cluster"), ("Note", "XdString")]
|
|
18
|
+
assert e["mint"][2]["path"] == "Vitals/Context/Note"
|
|
19
|
+
assert e["total_credits"] == ASSEMBLE_MODEL_CREDITS + 3 * MINT_COMPONENT_CREDITS == 800
|
|
19
20
|
assert e["mode"] == "mixed" and e["problems"] == []
|
|
20
21
|
|
|
21
22
|
|
|
@@ -31,3 +32,14 @@ def test_problems_are_named_not_hidden():
|
|
|
31
32
|
e = estimate_tree({"label": "Root", "components": [{"label": "No type"}], "clusters": [{"components": []}]}, "t")
|
|
32
33
|
assert len(e["problems"]) == 2 and "ct_id (reuse) or label and data_type (mint)" in e["problems"][0]
|
|
33
34
|
assert e["mode"] == "reuse"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_a_new_cluster_alone_is_billed_but_takes_the_reuse_path():
|
|
38
|
+
tree = {"label": "Root", "components": [{"ct_id": "a" * 24}],
|
|
39
|
+
"clusters": [{"label": "Group", "components": [{"ct_id": "b" * 24}], "clusters": []}]}
|
|
40
|
+
e = estimate_tree(tree, "Grouped")
|
|
41
|
+
assert (e["mode"], e["total_credits"]) == ("reuse", 600)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_the_title_is_trimmed_before_hashing_as_the_server_does():
|
|
45
|
+
assert estimate_tree(TREE, " Vitals ")["estimate_id"] == estimate_tree(TREE, "Vitals")["estimate_id"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|