Harden MCP article and weight workflows

This commit is contained in:
2026-09-25 16:34:49 +02:00
parent 1169df96a8
commit 580e9caa5e
6 changed files with 181 additions and 12 deletions
+47 -4
View File
@@ -5,9 +5,15 @@ import unittest
from unittest.mock import patch
import mcp_server
from roll_calculation import ArticleRepository, calculate_roll, get_article
from roll_calculation import (
ArticleRepository,
calculate_material_weight,
calculate_roll,
get_article,
)
from rollcalc_mcp_tools import (
analyze_transport_capacity_result,
calculate_material_weight_result,
calculate_roll_diameter_result,
get_article_result,
search_articles_result,
@@ -68,6 +74,19 @@ class McpAdapterTests(unittest.TestCase):
"candidates": [],
})
def test_material_weight_adapter_delegates_without_a_core_diameter(self):
arguments = {
"roll_length_m": 60.0,
"width_m": 5.0,
"area_weight_g_m2": 4850.079676,
}
result = calculate_material_weight_result(**arguments)
self.assertEqual(result, calculate_material_weight(**arguments))
self.assertEqual(result["weight_scope"], "material_only")
self.assertAlmostEqual(result["material_weight_kg"], 1455.0239028)
def test_calculate_roll_diameter_delegates_for_normal_weighted_and_invalid_cases(self):
for arguments in (
{
@@ -158,6 +177,7 @@ class McpAdapterTests(unittest.TestCase):
[
"get_article",
"search_articles",
"calculate_material_weight",
"calculate_roll_diameter",
"analyze_transport_capacity",
],
@@ -167,6 +187,7 @@ class McpAdapterTests(unittest.TestCase):
instance = mcp_server.create_server()
tools = instance._tool_manager._tools
search_tool = tools["search_articles"]
material_weight_tool = tools["calculate_material_weight"]
roll_schema = tools["calculate_roll_diameter"].parameters
transport_tool = tools["analyze_transport_capacity"]
transport_schema = transport_tool.parameters
@@ -175,6 +196,12 @@ class McpAdapterTests(unittest.TestCase):
self.assertEqual(
search_tool.parameters["properties"]["query"]["type"], "string"
)
material_weight_schema = material_weight_tool.parameters
self.assertEqual(
material_weight_schema["required"],
["roll_length_m", "width_m", "area_weight_g_m2"],
)
self.assertNotIn("core_diameter_mm", material_weight_schema["properties"])
self.assertNotIn("include_roll_weight", roll_schema["properties"])
preset_schema = transport_schema["properties"]["transport_preset"]
@@ -235,10 +262,11 @@ class McpAdapterTests(unittest.TestCase):
for text in (
"name, family, designation, or descriptive article text",
"Do not guess or invent an article number",
"deterministic candidates from the RollCalc article master data",
"deterministic retrieval candidates from the RollCalc article master data",
"not product recommendations",
"total_matches is exactly 1",
"use that candidate's exact article_number",
"do NOT select the first or highest-ranked candidate",
"do NOT select the first or highest-ranked candidate, do not treat ranking as permission to choose",
"ask the user which article number is intended",
"do not calculate yet",
"no matching RollCalc article was found",
@@ -246,11 +274,26 @@ class McpAdapterTests(unittest.TestCase):
"do not invent an article",
"production_site=Malaysia is descriptive metadata only",
"does not imply Bentofix, Bento 2, or any production machine",
"Ranking/order expresses relevance only",
"Ranking/order expresses retrieval relevance only",
"get_article -> calculate_roll_diameter -> optionally analyze_transport_capacity",
):
self.assertIn(text, search_description)
material_weight_description = material_weight_tool.description
for text in (
"material-only roll weight",
"does not require, accept, infer, or default a core diameter",
"never invent a standard core",
"excludes any core weight",
"cannot provide roll diameter or a core-inclusive total weight",
):
self.assertIn(text, material_weight_description)
roll_description = tools["calculate_roll_diameter"].description
self.assertIn("requires a known positive core diameter", roll_description)
self.assertIn("do not infer or default one", roll_description)
self.assertIn("call calculate_material_weight instead", roll_description)
if __name__ == "__main__":
unittest.main()
+30
View File
@@ -8,6 +8,7 @@ from roll_calculation import (
CalculationState,
_canonical_name,
article_production_site,
calculate_material_weight,
calculate_roll,
get_article,
modify_calculation,
@@ -182,6 +183,35 @@ class RollCalculationTests(unittest.TestCase):
self.assertEqual(result["status"], "success")
self.assertIsNone(result["calculation"]["roll_weight_kg"])
def test_material_weight_does_not_require_or_assume_a_core_diameter(self):
result = calculate_material_weight(
roll_length_m=60,
width_m=5.0,
area_weight_g_m2=4850.079676,
)
self.assertEqual(result["status"], "success")
self.assertEqual(result["weight_scope"], "material_only")
self.assertAlmostEqual(result["material_weight_kg"], 1455.0239028)
self.assertNotIn("core_diameter_mm", result["inputs"])
def test_roll_diameter_still_rejects_a_zero_core_diameter(self):
result = calculate_roll(
{
"roll_length_m": 60,
"width_m": 5.0,
"area_weight_g_m2": 4850.079676,
"thickness_mm": 6.461044,
"core_diameter_mm": 0,
"include_roll_weight": True,
}
)
self.assertEqual(result["status"], "invalid_parameter")
self.assertIn("core_diameter_mm", [
item["field"] for item in result["invalid"]
])
def test_diameter_only_calculation_does_not_require_width(self):
result = self.calculate_fixture(
{