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.
- {p2predict-0.9.4 → p2predict-0.9.6}/LICENSE +17 -2
- {p2predict-0.9.4/src/p2predict.egg-info → p2predict-0.9.6}/PKG-INFO +10 -8
- {p2predict-0.9.4 → p2predict-0.9.6}/README.md +7 -5
- {p2predict-0.9.4 → p2predict-0.9.6}/pyproject.toml +3 -3
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/cli/predict.py +1 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/cli/train.py +1 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/mcp/server.py +89 -2
- {p2predict-0.9.4 → p2predict-0.9.6/src/p2predict.egg-info}/PKG-INFO +10 -8
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict.egg-info/requires.txt +1 -1
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_mcp.py +73 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/setup.cfg +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/__init__.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/__main__.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/cli/__init__.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/cmdline_io.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/explain.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/feature_selection.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/hpo_training.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/input_checks.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/intervals.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/json_output.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/mcp/__init__.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/mcp/__main__.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/mcp/conversions.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/mcp/registry.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/model_evals.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/model_utils.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/outliers.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/plotting.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/prepare_data.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/preprocessing.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/quality.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/trained_model_io.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/training.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/ui_console.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict/whatif.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict.egg-info/SOURCES.txt +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict.egg-info/dependency_links.txt +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict.egg-info/entry_points.txt +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/src/p2predict.egg-info/top_level.txt +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_benchmarks.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_cli.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_explain.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_feature_selection.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_feature_selection_leakage.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_input_checks.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_intervals.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_io_and_evals.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_json_output.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_outliers.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_plotting.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_prepare_data.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_preprocessing.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_quality.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_training.py +0 -0
- {p2predict-0.9.4 → p2predict-0.9.6}/tests/test_whatif.py +0 -0
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|

|
|
56
56
|

|
|
57
57
|

|
|
58
|
-

|
|
59
59
|
|
|
60
60
|

|
|
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
|
|
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
|
|
265
|
-
- **
|
|
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
|

|
|
10
10
|

|
|
11
11
|

|
|
12
|
-

|
|
13
13
|
|
|
14
14
|

|
|
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
|
|
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
|
|
219
|
-
- **
|
|
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.
|
|
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(
|
|
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.
|
|
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
|
|
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
|

|
|
56
56
|

|
|
57
57
|

|
|
58
|
-

|
|
59
59
|
|
|
60
60
|

|
|
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
|
|
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
|
|
265
|
-
- **
|
|
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
|
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|