Expose product length calculation through MCP

This commit is contained in:
2026-09-25 21:21:20 +02:00
parent 580e9caa5e
commit e6a7d4da72
10 changed files with 357 additions and 7 deletions
+123
View File
@@ -431,6 +431,129 @@ def _material_weight_kg(
return area_weight_g_m2 * roll_length_m * width_m / 1000
def calculate_product_length(
*,
target_roll_diameter_mm: Any = None,
core_diameter_mm: Any = None,
thickness_mm: Any = None,
thickness_stddev_mm: Any = None,
width_m: Any = None,
area_weight_g_m2: Any = None,
) -> dict[str, Any]:
"""Calculate product-length ranges for a target outer roll diameter."""
required = {
"target_roll_diameter_mm": target_roll_diameter_mm,
"core_diameter_mm": core_diameter_mm,
"thickness_mm": thickness_mm,
}
invalid = []
normalized = {}
for field, value in required.items():
if isinstance(value, bool) or not isinstance(value, (int, float)):
invalid.append({"field": field, "message": "must be a number"})
continue
number = float(value)
if not math.isfinite(number) or number <= 0:
invalid.append(
{"field": field, "message": "must be greater than zero"}
)
continue
normalized[field] = number
if thickness_stddev_mm is None:
thickness_stddev = 0.0
elif isinstance(thickness_stddev_mm, bool) or not isinstance(
thickness_stddev_mm, (int, float)
):
invalid.append(
{"field": "thickness_stddev_mm", "message": "must be a number"}
)
thickness_stddev = 0.0
else:
thickness_stddev = float(thickness_stddev_mm)
if not math.isfinite(thickness_stddev) or thickness_stddev < 0:
invalid.append(
{
"field": "thickness_stddev_mm",
"message": "must be zero or greater",
}
)
if invalid:
return {"status": "invalid_parameter", "invalid": invalid}
target = normalized["target_roll_diameter_mm"]
core = normalized["core_diameter_mm"]
thickness = normalized["thickness_mm"]
if target <= core:
return {
"status": "invalid_parameter",
"invalid": [
{
"field": "target_roll_diameter_mm",
"message": "must be greater than core_diameter_mm",
}
],
}
minimum_thickness = thickness - (2 * thickness_stddev)
if minimum_thickness <= 0:
return {
"status": "invalid_parameter",
"invalid": [
{
"field": "thickness_stddev_mm",
"message": "minus two standard deviations must remain positive",
}
],
}
length_factor = math.pi * (target**2 - core**2) / 4000
minimum_length = length_factor / (thickness + (2 * thickness_stddev))
average_length = length_factor / thickness
maximum_length = length_factor / minimum_thickness
lengths = (minimum_length, average_length, maximum_length)
if not all(math.isfinite(value) and value > 0 for value in lengths):
return {
"status": "invalid_parameter",
"invalid": [
{
"field": "request",
"message": "values are outside the supported calculation range",
}
],
}
normalized["thickness_stddev_mm"] = thickness_stddev
material_weights = None
if width_m is not None and area_weight_g_m2 is not None:
weight_result = calculate_material_weight(
roll_length_m=average_length,
width_m=width_m,
area_weight_g_m2=area_weight_g_m2,
)
if weight_result["status"] != "success":
return weight_result
width = weight_result["inputs"]["width_m"]
area_weight = weight_result["inputs"]["area_weight_g_m2"]
normalized["width_m"] = width
normalized["area_weight_g_m2"] = area_weight
material_weights = {
"minimum_kg": _material_weight_kg(minimum_length, width, area_weight),
"average_kg": weight_result["material_weight_kg"],
"maximum_kg": _material_weight_kg(maximum_length, width, area_weight),
}
return {
"status": "success",
"inputs": normalized,
"calculation": {
"minimum_product_length_m": minimum_length,
"average_product_length_m": average_length,
"maximum_product_length_m": maximum_length,
"material_weights_kg": material_weights,
},
}
def _canonical_name(value: str) -> str:
value = value.casefold().replace("×", " x ")
tokens = re.findall(r"\d+(?:[.,]\d+)?|[^\W\d_]+", value)