p2predict 0.9.4__tar.gz → 0.9.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.
Files changed (56) hide show
  1. {p2predict-0.9.4 → p2predict-0.9.6}/LICENSE +17 -2
  2. {p2predict-0.9.4/src/p2predict.egg-info → p2predict-0.9.6}/PKG-INFO +10 -8
  3. {p2predict-0.9.4 → p2predict-0.9.6}/README.md +7 -5
  4. {p2predict-0.9.4 → p2predict-0.9.6}/pyproject.toml +3 -3
  5. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/cli/predict.py +1 -0
  6. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/cli/train.py +1 -0
  7. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/mcp/server.py +89 -2
  8. {p2predict-0.9.4 → p2predict-0.9.6/src/p2predict.egg-info}/PKG-INFO +10 -8
  9. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict.egg-info/requires.txt +1 -1
  10. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_mcp.py +73 -0
  11. {p2predict-0.9.4 → p2predict-0.9.6}/setup.cfg +0 -0
  12. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/__init__.py +0 -0
  13. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/__main__.py +0 -0
  14. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/cli/__init__.py +0 -0
  15. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/cmdline_io.py +0 -0
  16. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/explain.py +0 -0
  17. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/feature_selection.py +0 -0
  18. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/hpo_training.py +0 -0
  19. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/input_checks.py +0 -0
  20. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/intervals.py +0 -0
  21. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/json_output.py +0 -0
  22. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/mcp/__init__.py +0 -0
  23. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/mcp/__main__.py +0 -0
  24. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/mcp/conversions.py +0 -0
  25. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/mcp/registry.py +0 -0
  26. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/model_evals.py +0 -0
  27. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/model_utils.py +0 -0
  28. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/outliers.py +0 -0
  29. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/plotting.py +0 -0
  30. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/prepare_data.py +0 -0
  31. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/preprocessing.py +0 -0
  32. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/quality.py +0 -0
  33. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/trained_model_io.py +0 -0
  34. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/training.py +0 -0
  35. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/ui_console.py +0 -0
  36. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/whatif.py +0 -0
  37. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict.egg-info/SOURCES.txt +0 -0
  38. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict.egg-info/dependency_links.txt +0 -0
  39. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict.egg-info/entry_points.txt +0 -0
  40. {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict.egg-info/top_level.txt +0 -0
  41. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_benchmarks.py +0 -0
  42. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_cli.py +0 -0
  43. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_explain.py +0 -0
  44. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_feature_selection.py +0 -0
  45. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_feature_selection_leakage.py +0 -0
  46. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_input_checks.py +0 -0
  47. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_intervals.py +0 -0
  48. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_io_and_evals.py +0 -0
  49. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_json_output.py +0 -0
  50. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_outliers.py +0 -0
  51. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_plotting.py +0 -0
  52. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_prepare_data.py +0 -0
  53. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_preprocessing.py +0 -0
  54. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_quality.py +0 -0
  55. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_training.py +0 -0
  56. {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_whatif.py +0 -0
@@ -1,6 +1,21 @@
1
- PolyForm Noncommercial License 1.0.0
1
+ P2Predict License Exception
2
+ Copyright (c) 2023-2026 Ahmed K. Hafsi
3
+
4
+ P2Predict is licensed under the PolyForm Noncommercial License 1.0.0 (see below).
5
+
6
+ ADDITIONAL GRANT OF PERMISSION:
7
+ Notwithstanding the restrictions in the PolyForm Noncommercial License 1.0.0, the Licensor hereby grants any organization (including for-profit corporations) permission to use this software internally for their own direct operations, procurement, and benchmarking purposes at no cost.
8
+
9
+ A commercial license is ONLY required if you:
10
+ 1. Provide consulting, advisory, or procurement services to third-party clients using this software.
11
+ 2. Embed or integrate this software into a paid commercial product or service.
2
12
 
3
- Copyright (c) 2023 Ahmed K. Hafsi
13
+ For commercial licensing inquiries, please visit p2predict.com.
14
+
15
+ ----------------------------------------------------------------------
16
+
17
+ PolyForm Noncommercial License 1.0.0
18
+ Copyright (c) 2023-2026 Ahmed K. Hafsi
4
19
 
5
20
  https://polyformproject.org/licenses/noncommercial/1.0.0
6
21
 
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: p2predict
3
- Version: 0.9.4
3
+ Version: 0.9.6
4
4
  Summary: Parametric price benchmarking for engineering and procurement trade-offs. Agentic first, runs on your data, stays on your machine.
5
5
  Author: Ahmed Khalil Hafsi
6
- License-Expression: PolyForm-Noncommercial-1.0.0
6
+ License-Expression: LicenseRef-PolyForm-Noncommercial-1.0.0-with-Internal-Use-Exception
7
7
  Project-URL: Homepage, https://github.com/ahmed-khalil-hafsi/P2Predict
8
8
  Project-URL: Repository, https://github.com/ahmed-khalil-hafsi/P2Predict
9
9
  Project-URL: Issues, https://github.com/ahmed-khalil-hafsi/P2Predict/issues
@@ -39,7 +39,7 @@ Requires-Dist: halo>=0.0.31
39
39
  Requires-Dist: questionary>=1.10
40
40
  Requires-Dist: shap<0.50,>=0.44
41
41
  Provides-Extra: mcp
42
- Requires-Dist: mcp>=1.0; extra == "mcp"
42
+ Requires-Dist: mcp<2,>=1.0; extra == "mcp"
43
43
  Provides-Extra: dev
44
44
  Requires-Dist: pytest>=7; extra == "dev"
45
45
  Dynamic: license-file
@@ -55,7 +55,7 @@ Dynamic: license-file
55
55
  ![Works with any AI agent](https://img.shields.io/badge/works%20with-any%20AI%20agent-6E56CF) &nbsp;
56
56
  ![Local first](https://img.shields.io/badge/local--first-your%20data%20never%20leaves-1F8A4C) &nbsp;
57
57
  ![Built for procurement](https://img.shields.io/badge/built%20for-procurement-0B6BCB) &nbsp;
58
- ![License](https://img.shields.io/badge/license-PolyForm%20Noncommercial-555)
58
+ ![License](https://img.shields.io/badge/free%20for%20internal%20company%20use-1F8A4C)
59
59
 
60
60
  ![P2Predict MCP Demo](./documentation/p2predict_mcp_demo.gif)
61
61
 
@@ -212,7 +212,7 @@ Each one leads with results, shows where to trust them, and points to exactly wh
212
212
  ## FAQ
213
213
 
214
214
  **Is it really free?**
215
- Yes, for internal use. P2Predict is source-available under the PolyForm Noncommercial License. No seats, no trial period, no sales call. A commercial license is only needed if you deploy it for clients or embed it in a paid product.
215
+ Yes — including for for-profit companies. Any organization can use P2Predict internally for its own operations, procurement, and benchmarking at no cost. No seats, no trial period, no sales call. A commercial license is only needed if you use it to serve third-party clients (consulting or advisory work) or embed it in a paid product or service.
216
216
 
217
217
  **How reliable are the numbers?**
218
218
  Every estimate comes with a range, not just a number, and the range is honest about how thin your data is: wide where history is sparse, tight where it's deep. On the public case studies, predictions land within roughly 16% of the actual price half the time, and within 73% nine times in ten. That's the model showing its work, not asking you to trust it blind.
@@ -259,10 +259,12 @@ P2Predict comes out of that work: the tools a procurement team actually needs to
259
259
 
260
260
  ## Licensing
261
261
 
262
- Source-available under the [PolyForm Noncommercial License 1.0.0](LICENSE).
262
+ Source-available under the [PolyForm Noncommercial License 1.0.0, with an additional grant of permission for internal company use](LICENSE).
263
263
 
264
- - **Free for internal use:** use P2Predict inside your own organization at no cost.
265
- - **Commercial use requires a license:** deploying for clients, embedding it in a paid service, or consulting engagements.
264
+ - **Free for internal use, including for-profit companies.** Any organization may use P2Predict internally for its own operations, procurement, and benchmarking at no cost. You do not need to buy anything, ask permission, or talk to anyone.
265
+ - **A commercial license is only required if you** (1) provide consulting, advisory, or procurement services to third-party clients using P2Predict, or (2) embed or integrate it into a paid product or service.
266
+
267
+ Despite the "Noncommercial" in the underlying license name, commercial *companies* are explicitly covered by the additional grant — the restriction is on selling P2Predict's use to others, not on being a business.
266
268
 
267
269
  For a commercial license, a partnership, or to share a procurement dataset, reach out: **[ahmedhafsi.com/contact](https://ahmedhafsi.com/contact/)**.
268
270
 
@@ -9,7 +9,7 @@
9
9
  ![Works with any AI agent](https://img.shields.io/badge/works%20with-any%20AI%20agent-6E56CF) &nbsp;
10
10
  ![Local first](https://img.shields.io/badge/local--first-your%20data%20never%20leaves-1F8A4C) &nbsp;
11
11
  ![Built for procurement](https://img.shields.io/badge/built%20for-procurement-0B6BCB) &nbsp;
12
- ![License](https://img.shields.io/badge/license-PolyForm%20Noncommercial-555)
12
+ ![License](https://img.shields.io/badge/free%20for%20internal%20company%20use-1F8A4C)
13
13
 
14
14
  ![P2Predict MCP Demo](./documentation/p2predict_mcp_demo.gif)
15
15
 
@@ -166,7 +166,7 @@ Each one leads with results, shows where to trust them, and points to exactly wh
166
166
  ## FAQ
167
167
 
168
168
  **Is it really free?**
169
- Yes, for internal use. P2Predict is source-available under the PolyForm Noncommercial License. No seats, no trial period, no sales call. A commercial license is only needed if you deploy it for clients or embed it in a paid product.
169
+ Yes — including for for-profit companies. Any organization can use P2Predict internally for its own operations, procurement, and benchmarking at no cost. No seats, no trial period, no sales call. A commercial license is only needed if you use it to serve third-party clients (consulting or advisory work) or embed it in a paid product or service.
170
170
 
171
171
  **How reliable are the numbers?**
172
172
  Every estimate comes with a range, not just a number, and the range is honest about how thin your data is: wide where history is sparse, tight where it's deep. On the public case studies, predictions land within roughly 16% of the actual price half the time, and within 73% nine times in ten. That's the model showing its work, not asking you to trust it blind.
@@ -213,10 +213,12 @@ P2Predict comes out of that work: the tools a procurement team actually needs to
213
213
 
214
214
  ## Licensing
215
215
 
216
- Source-available under the [PolyForm Noncommercial License 1.0.0](LICENSE).
216
+ Source-available under the [PolyForm Noncommercial License 1.0.0, with an additional grant of permission for internal company use](LICENSE).
217
217
 
218
- - **Free for internal use:** use P2Predict inside your own organization at no cost.
219
- - **Commercial use requires a license:** deploying for clients, embedding it in a paid service, or consulting engagements.
218
+ - **Free for internal use, including for-profit companies.** Any organization may use P2Predict internally for its own operations, procurement, and benchmarking at no cost. You do not need to buy anything, ask permission, or talk to anyone.
219
+ - **A commercial license is only required if you** (1) provide consulting, advisory, or procurement services to third-party clients using P2Predict, or (2) embed or integrate it into a paid product or service.
220
+
221
+ Despite the "Noncommercial" in the underlying license name, commercial *companies* are explicitly covered by the additional grant — the restriction is on selling P2Predict's use to others, not on being a business.
220
222
 
221
223
  For a commercial license, a partnership, or to share a procurement dataset, reach out: **[ahmedhafsi.com/contact](https://ahmedhafsi.com/contact/)**.
222
224
 
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "p2predict"
7
- version = "0.9.4"
7
+ version = "0.9.6"
8
8
  description = "Parametric price benchmarking for engineering and procurement trade-offs. Agentic first, runs on your data, stays on your machine."
9
9
  readme = "README.md"
10
- license = "PolyForm-Noncommercial-1.0.0"
10
+ license = "LicenseRef-PolyForm-Noncommercial-1.0.0-with-Internal-Use-Exception"
11
11
  license-files = ["LICENSE"]
12
12
  authors = [{name = "Ahmed Khalil Hafsi"}]
13
13
  requires-python = ">=3.10"
@@ -55,7 +55,7 @@ dependencies = [
55
55
 
56
56
  [project.optional-dependencies]
57
57
  mcp = [
58
- "mcp>=1.0",
58
+ "mcp>=1.0,<2",
59
59
  ]
60
60
  dev = [
61
61
  "pytest>=7",
@@ -412,6 +412,7 @@ def _print_whatif(console, result: WhatIfResult, target_name: str) -> None:
412
412
 
413
413
 
414
414
  @click.command()
415
+ @click.version_option(package_name="p2predict", message="%(prog)s %(version)s")
415
416
  @click.option("-m", "--model", type=click.Path(exists=True),
416
417
  help="Path to the trained model file (.model)")
417
418
  @click.option("-p", "--predict_using",
@@ -95,6 +95,7 @@ def _feature_outlier_summary_block(summary: dict) -> dict:
95
95
 
96
96
 
97
97
  @click.command()
98
+ @click.version_option(package_name="p2predict", message="%(prog)s %(version)s")
98
99
  @click.option("-i", "--input", type=click.Path(exists=True), default=None,
99
100
  help="Path to the CSV file containing the training dataset.")
100
101
  @click.option("-t", "--target",
@@ -8,6 +8,8 @@ import argparse
8
8
  import asyncio
9
9
  import datetime
10
10
  import json
11
+ import os
12
+ import sys
11
13
  from pathlib import Path
12
14
  from typing import Any
13
15
 
@@ -1230,6 +1232,73 @@ async def model_resource(model_id: str) -> str:
1230
1232
  # ---------------------------------------------------------------------------
1231
1233
 
1232
1234
 
1235
+ def _launch_command() -> list[str]:
1236
+ """The command an MCP client should use to start this server.
1237
+
1238
+ MCP clients do not inherit the shell's PATH, so a bare ``p2predict-mcp``
1239
+ usually fails in the client even when it works in the terminal. Resolve an
1240
+ absolute path instead: prefer the console script sitting next to the
1241
+ running interpreter (the venv layout INSTALL.md builds), and fall back to
1242
+ ``<python> -m p2predict.mcp``, which works even where the console script
1243
+ was never placed on disk or never landed on PATH.
1244
+ """
1245
+ script = Path(sys.executable).parent / (
1246
+ "p2predict-mcp.exe" if os.name == "nt" else "p2predict-mcp"
1247
+ )
1248
+ if script.exists():
1249
+ return [str(script)]
1250
+ # Deliberately NOT resolve()d: sys.executable is already absolute, and
1251
+ # resolving it follows the venv's python symlink out to the base
1252
+ # interpreter — which does not have p2predict on its path, so the emitted
1253
+ # config would fail with ModuleNotFoundError.
1254
+ return [sys.executable, "-m", "p2predict.mcp"]
1255
+
1256
+
1257
+ def _client_config_path() -> str:
1258
+ """Where Claude Desktop keeps its config, per platform."""
1259
+ if sys.platform == "darwin":
1260
+ return "~/Library/Application Support/Claude/claude_desktop_config.json"
1261
+ if os.name == "nt":
1262
+ return r"%APPDATA%\Claude\claude_desktop_config.json"
1263
+ return "~/.config/Claude/claude_desktop_config.json"
1264
+
1265
+
1266
+ def _print_config(models_dir: Path) -> None:
1267
+ """Print a ready-to-paste MCP client config for this exact install.
1268
+
1269
+ Hand-writing this block is the most error-prone step of setup: the path
1270
+ has to be absolute, and on Windows every backslash has to be doubled.
1271
+ ``json.dumps`` does both correctly, so the user never escapes anything.
1272
+ """
1273
+ command, *extra_args = _launch_command()
1274
+ blob = {
1275
+ "mcpServers": {
1276
+ "p2predict": {
1277
+ "command": command,
1278
+ "args": [*extra_args, "--models-dir", str(models_dir)],
1279
+ }
1280
+ }
1281
+ }
1282
+
1283
+ print("Add this to your Claude Desktop config file:")
1284
+ print(f" {_client_config_path()}")
1285
+ print()
1286
+ print(json.dumps(blob, indent=2))
1287
+ print()
1288
+ if not models_dir.exists():
1289
+ print(
1290
+ f"Note: {models_dir} doesn't exist yet. That's fine — it's created "
1291
+ "when you train your first model."
1292
+ )
1293
+ print()
1294
+ print(
1295
+ "If the file already has other servers in it, add only the "
1296
+ '"p2predict" block, inside the existing "mcpServers".'
1297
+ )
1298
+ quit_hint = "completely (Cmd+Q)" if sys.platform == "darwin" else "completely"
1299
+ print(f"Then quit Claude Desktop {quit_hint} and reopen it.")
1300
+
1301
+
1233
1302
  def main():
1234
1303
  parser = argparse.ArgumentParser(
1235
1304
  description="P2Predict MCP server — parametric price benchmarking for AI agents"
@@ -1239,15 +1308,33 @@ def main():
1239
1308
  default="models",
1240
1309
  help="Directory containing .model files (default: models)",
1241
1310
  )
1311
+ parser.add_argument(
1312
+ "--version",
1313
+ action="version",
1314
+ version=f"p2predict-mcp {_SERVER_BUILD['version']}",
1315
+ )
1316
+ parser.add_argument(
1317
+ "--print-config",
1318
+ action="store_true",
1319
+ help=(
1320
+ "Print a ready-to-paste Claude Desktop config for this install "
1321
+ "(absolute paths, correctly escaped) and exit."
1322
+ ),
1323
+ )
1242
1324
  args = parser.parse_args()
1243
1325
 
1326
+ models_dir = Path(args.models_dir).resolve()
1327
+
1328
+ if args.print_config:
1329
+ _print_config(models_dir)
1330
+ return
1331
+
1244
1332
  global _registry
1245
- _registry = ModelRegistry(Path(args.models_dir).resolve())
1333
+ _registry = ModelRegistry(models_dir)
1246
1334
 
1247
1335
  # stderr is safe on stdio transport (stdout is the MCP protocol channel) and
1248
1336
  # shows up in the client's server logs — a quick way to confirm which build
1249
1337
  # is actually running.
1250
- import sys
1251
1338
  print(
1252
1339
  f"P2Predict MCP server v{_SERVER_BUILD['version']} "
1253
1340
  f"({_SERVER_BUILD['git_sha'] or 'no-git'}) loaded from "
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: p2predict
3
- Version: 0.9.4
3
+ Version: 0.9.6
4
4
  Summary: Parametric price benchmarking for engineering and procurement trade-offs. Agentic first, runs on your data, stays on your machine.
5
5
  Author: Ahmed Khalil Hafsi
6
- License-Expression: PolyForm-Noncommercial-1.0.0
6
+ License-Expression: LicenseRef-PolyForm-Noncommercial-1.0.0-with-Internal-Use-Exception
7
7
  Project-URL: Homepage, https://github.com/ahmed-khalil-hafsi/P2Predict
8
8
  Project-URL: Repository, https://github.com/ahmed-khalil-hafsi/P2Predict
9
9
  Project-URL: Issues, https://github.com/ahmed-khalil-hafsi/P2Predict/issues
@@ -39,7 +39,7 @@ Requires-Dist: halo>=0.0.31
39
39
  Requires-Dist: questionary>=1.10
40
40
  Requires-Dist: shap<0.50,>=0.44
41
41
  Provides-Extra: mcp
42
- Requires-Dist: mcp>=1.0; extra == "mcp"
42
+ Requires-Dist: mcp<2,>=1.0; extra == "mcp"
43
43
  Provides-Extra: dev
44
44
  Requires-Dist: pytest>=7; extra == "dev"
45
45
  Dynamic: license-file
@@ -55,7 +55,7 @@ Dynamic: license-file
55
55
  ![Works with any AI agent](https://img.shields.io/badge/works%20with-any%20AI%20agent-6E56CF) &nbsp;
56
56
  ![Local first](https://img.shields.io/badge/local--first-your%20data%20never%20leaves-1F8A4C) &nbsp;
57
57
  ![Built for procurement](https://img.shields.io/badge/built%20for-procurement-0B6BCB) &nbsp;
58
- ![License](https://img.shields.io/badge/license-PolyForm%20Noncommercial-555)
58
+ ![License](https://img.shields.io/badge/free%20for%20internal%20company%20use-1F8A4C)
59
59
 
60
60
  ![P2Predict MCP Demo](./documentation/p2predict_mcp_demo.gif)
61
61
 
@@ -212,7 +212,7 @@ Each one leads with results, shows where to trust them, and points to exactly wh
212
212
  ## FAQ
213
213
 
214
214
  **Is it really free?**
215
- Yes, for internal use. P2Predict is source-available under the PolyForm Noncommercial License. No seats, no trial period, no sales call. A commercial license is only needed if you deploy it for clients or embed it in a paid product.
215
+ Yes — including for for-profit companies. Any organization can use P2Predict internally for its own operations, procurement, and benchmarking at no cost. No seats, no trial period, no sales call. A commercial license is only needed if you use it to serve third-party clients (consulting or advisory work) or embed it in a paid product or service.
216
216
 
217
217
  **How reliable are the numbers?**
218
218
  Every estimate comes with a range, not just a number, and the range is honest about how thin your data is: wide where history is sparse, tight where it's deep. On the public case studies, predictions land within roughly 16% of the actual price half the time, and within 73% nine times in ten. That's the model showing its work, not asking you to trust it blind.
@@ -259,10 +259,12 @@ P2Predict comes out of that work: the tools a procurement team actually needs to
259
259
 
260
260
  ## Licensing
261
261
 
262
- Source-available under the [PolyForm Noncommercial License 1.0.0](LICENSE).
262
+ Source-available under the [PolyForm Noncommercial License 1.0.0, with an additional grant of permission for internal company use](LICENSE).
263
263
 
264
- - **Free for internal use:** use P2Predict inside your own organization at no cost.
265
- - **Commercial use requires a license:** deploying for clients, embedding it in a paid service, or consulting engagements.
264
+ - **Free for internal use, including for-profit companies.** Any organization may use P2Predict internally for its own operations, procurement, and benchmarking at no cost. You do not need to buy anything, ask permission, or talk to anyone.
265
+ - **A commercial license is only required if you** (1) provide consulting, advisory, or procurement services to third-party clients using P2Predict, or (2) embed or integrate it into a paid product or service.
266
+
267
+ Despite the "Noncommercial" in the underlying license name, commercial *companies* are explicitly covered by the additional grant — the restriction is on selling P2Predict's use to others, not on being a business.
266
268
 
267
269
  For a commercial license, a partnership, or to share a procurement dataset, reach out: **[ahmedhafsi.com/contact](https://ahmedhafsi.com/contact/)**.
268
270
 
@@ -17,4 +17,4 @@ shap<0.50,>=0.44
17
17
  pytest>=7
18
18
 
19
19
  [mcp]
20
- mcp>=1.0
20
+ mcp<2,>=1.0
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import json
5
+ import os
5
6
  from pathlib import Path
6
7
 
7
8
  import numpy as np
@@ -603,3 +604,75 @@ def test_registry_scan(registry, model_id):
603
604
  def test_registry_model_not_found(registry):
604
605
  with pytest.raises(FileNotFoundError):
605
606
  registry.load("nonexistent_model")
607
+
608
+
609
+ # ---------------------------------------------------------------------------
610
+ # --print-config
611
+ # ---------------------------------------------------------------------------
612
+
613
+
614
+ def _emitted_config(capsys, models_dir: Path) -> dict:
615
+ """Run _print_config and parse the JSON block out of what it printed."""
616
+ mcp_server._print_config(models_dir)
617
+ text = capsys.readouterr().out
618
+ return json.loads(text[text.index("{"):text.rindex("}") + 1])
619
+
620
+
621
+ def test_print_config_emits_valid_pasteable_json(capsys, tmp_path):
622
+ """The whole point: the user never hand-escapes anything."""
623
+ models = tmp_path / "models"
624
+ models.mkdir()
625
+ blob = _emitted_config(capsys, models)
626
+
627
+ entry = blob["mcpServers"]["p2predict"]
628
+ assert Path(entry["command"]).is_absolute()
629
+ assert entry["args"][-2] == "--models-dir"
630
+ assert entry["args"][-1] == str(models)
631
+
632
+
633
+ def test_print_config_uses_absolute_command_not_bare_name(capsys, tmp_path):
634
+ """MCP clients don't inherit PATH, so a bare command name would fail."""
635
+ blob = _emitted_config(capsys, tmp_path / "models")
636
+ command = blob["mcpServers"]["p2predict"]["command"]
637
+ assert command != "p2predict-mcp"
638
+ assert Path(command).is_absolute()
639
+
640
+
641
+ def test_print_config_flags_missing_models_dir(capsys, tmp_path):
642
+ mcp_server._print_config(tmp_path / "not_there")
643
+ assert "doesn't exist yet" in capsys.readouterr().out
644
+
645
+
646
+ def test_print_config_stays_quiet_when_models_dir_exists(capsys, tmp_path):
647
+ models = tmp_path / "models"
648
+ models.mkdir()
649
+ mcp_server._print_config(models)
650
+ assert "doesn't exist yet" not in capsys.readouterr().out
651
+
652
+
653
+ def test_launch_command_prefers_console_script(tmp_path, monkeypatch):
654
+ fake_bin = tmp_path / "bin"
655
+ fake_bin.mkdir()
656
+ name = "p2predict-mcp.exe" if os.name == "nt" else "p2predict-mcp"
657
+ script = fake_bin / name
658
+ script.touch()
659
+ monkeypatch.setattr(mcp_server.sys, "executable", str(fake_bin / "python"))
660
+
661
+ assert mcp_server._launch_command() == [str(script)]
662
+
663
+
664
+ def test_launch_command_falls_back_to_unresolved_interpreter(tmp_path, monkeypatch):
665
+ """Regression: resolve() followed a venv's python symlink out to the base
666
+ interpreter, which has no p2predict on its path — the emitted config then
667
+ died with ModuleNotFoundError. sys.executable must be used as-is."""
668
+ venv_bin = tmp_path / "venv" / "bin"
669
+ venv_bin.mkdir(parents=True)
670
+ real_python = tmp_path / "real_python"
671
+ real_python.touch()
672
+ venv_python = venv_bin / "python"
673
+ venv_python.symlink_to(real_python)
674
+ monkeypatch.setattr(mcp_server.sys, "executable", str(venv_python))
675
+
676
+ command = mcp_server._launch_command()
677
+ assert command == [str(venv_python), "-m", "p2predict.mcp"]
678
+ assert command[0] != str(real_python)
File without changes
File without changes