Expose product length calculation through MCP
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user