Files
RollCalcPython/rollcalc_mcp_tools.py
T

281 lines
9.5 KiB
Python

"""Thin MCP-facing adapters over the shared RollCalc domain services."""
from __future__ import annotations
from typing import Any
from machine_constraints import (
calculate_machine_max_product_length,
check_production_feasibility,
get_machine,
)
from roll_calculation import (
ArticleRepository,
calculate_material_weight,
calculate_product_length,
calculate_roll,
get_article,
transport_roll_inputs_from_roll_calculation,
)
from mcp_pdf_delivery import generate_mcp_calculation_pdf
from transport_calculation import analyze_transport
def get_article_result(
article_number: str | None = None,
article_name_hint: str | None = None,
) -> dict[str, Any]:
"""Delegate article resolution to the shared article repository."""
return get_article(article_number, article_name_hint)
def search_articles_result(query: str) -> dict[str, Any]:
"""Delegate article discovery to the shared article repository."""
return ArticleRepository.load().search(query)
def get_machine_result(machine: str | None = None) -> dict[str, Any]:
"""Delegate configured-machine resolution to the shared repository."""
return get_machine(machine)
def check_production_feasibility_result(
*,
machine: str,
roll_weight_kg: float | None = None,
average_diameter_mm: float | None = None,
maximum_diameter_mm: float | None = None,
core_diameter_mm: float | None = None,
product_width_m: float | None = None,
line_speed_m_per_min: float | None = None,
) -> dict[str, Any]:
"""Delegate configured production feasibility to the shared domain."""
return check_production_feasibility({
"machine": machine,
"roll_weight_kg": roll_weight_kg,
"average_diameter_mm": average_diameter_mm,
"maximum_diameter_mm": maximum_diameter_mm,
"core_diameter_mm": core_diameter_mm,
"product_width_m": product_width_m,
"line_speed_m_per_min": line_speed_m_per_min,
})
def calculate_machine_max_product_length_result(
*,
machine: str,
core_diameter_mm: float,
article_number: str | None = None,
thickness_mm: float | None = None,
thickness_stddev_mm: float | None = None,
product_width_m: float | None = None,
area_weight_g_m2: float | None = None,
) -> dict[str, Any]:
"""Return the compact MCP view, resolving one exact article when supplied."""
explicit_material = {
"thickness_mm": thickness_mm,
"thickness_stddev_mm": thickness_stddev_mm,
"product_width_m": product_width_m,
"area_weight_g_m2": area_weight_g_m2,
}
if article_number is not None:
mixed_fields = [
field for field, value in explicit_material.items() if value is not None
]
if mixed_fields:
return {
"status": "invalid_parameter",
"invalid": [{
"field": "article_number",
"message": (
"cannot be combined with explicit material properties: "
+ ", ".join(mixed_fields)
),
}],
}
article_result = get_article(article_number)
if article_result["status"] != "resolved":
return article_result
article = article_result["article"]
result = calculate_machine_max_product_length(
machine=machine,
core_diameter_mm=core_diameter_mm,
thickness_mm=article["thickness_mm"],
thickness_stddev_mm=article["thickness_stddev_mm"],
product_width_m=article["width_m"],
area_weight_g_m2=article["area_weight_g_m2"],
)
return project_machine_max_product_length_result(result, article=article)
result = calculate_machine_max_product_length(
machine=machine,
core_diameter_mm=core_diameter_mm,
thickness_mm=thickness_mm,
thickness_stddev_mm=thickness_stddev_mm,
product_width_m=product_width_m,
area_weight_g_m2=area_weight_g_m2,
)
return project_machine_max_product_length_result(result)
def project_machine_max_product_length_result(
result: dict[str, Any],
*,
article: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Project successful maximum-length results for MCP without diagnostics.
The shared domain result deliberately contains detailed verification reports.
They remain available to Flask and other consumers, but duplicate machine
metadata and checks are unnecessary in the MCP response.
"""
if result.get("status") != "success":
return result
machine = result["machine"]
constraints = machine["constraints"]
projection = {
"status": result["status"],
"machine": {"id": machine["id"], "name": machine["name"]},
"weight_scope": result["weight_scope"],
"machine_limits": {
"maximum_roll_diameter_mm": constraints["roll_diameter_mm"]["maximum"]
if constraints["roll_diameter_mm"] is not None else None,
"maximum_material_roll_weight_kg": constraints["roll_weight_kg"]["maximum"]
if constraints["roll_weight_kg"] is not None else None,
},
"production_maximums": result["production_maximums"],
"material_weights_kg": result["material_weights_kg"],
"warnings": result["warnings"],
}
if article is not None:
projection["article"] = {
"number": article["number"],
"name": article["name"],
}
return projection
def calculate_material_weight_result(
*,
roll_length_m: float,
width_m: float,
area_weight_g_m2: float,
) -> dict[str, Any]:
"""Delegate material-only weight calculation to the shared domain service."""
return calculate_material_weight(
roll_length_m=roll_length_m,
width_m=width_m,
area_weight_g_m2=area_weight_g_m2,
)
def calculate_product_length_result(
*,
target_roll_diameter_mm: float,
core_diameter_mm: float,
thickness_mm: float,
thickness_stddev_mm: float | None = None,
width_m: float | None = None,
area_weight_g_m2: float | None = None,
) -> dict[str, Any]:
"""Delegate target-diameter product-length calculation to the domain."""
return calculate_product_length(
target_roll_diameter_mm=target_roll_diameter_mm,
core_diameter_mm=core_diameter_mm,
thickness_mm=thickness_mm,
thickness_stddev_mm=thickness_stddev_mm,
width_m=width_m,
area_weight_g_m2=area_weight_g_m2,
)
def calculate_roll_diameter_result(
*,
article_number: str | None = None,
article_name_hint: str | None = None,
roll_length_m: float | None = None,
width_m: float | None = None,
thickness_mm: float | None = None,
thickness_stddev_mm: float | None = None,
area_weight_g_m2: float | None = None,
core_diameter_mm: float | None = None,
core_type: str | None = None,
category: str | None = None,
production_site: str | None = None,
) -> dict[str, Any]:
"""Map MCP inputs to the existing calculation, always requesting weight."""
result = calculate_roll({
"article_number": article_number,
"article_name_hint": article_name_hint,
"roll_length_m": roll_length_m,
"width_m": width_m,
"thickness_mm": thickness_mm,
"thickness_stddev_mm": thickness_stddev_mm,
"area_weight_g_m2": area_weight_g_m2,
"core_diameter_mm": core_diameter_mm,
"core_type": core_type,
"category": category,
"production_site": production_site,
"include_roll_weight": True,
})
if result.get("status") == "success":
result = dict(result)
result["transport_roll_inputs"] = (
transport_roll_inputs_from_roll_calculation(result)
)
return result
def generate_calculation_pdf_result(
*,
article_number: str | None = None,
article_name_hint: str | None = None,
roll_length_m: float | None = None,
width_m: float | None = None,
thickness_mm: float | None = None,
thickness_stddev_mm: float | None = None,
area_weight_g_m2: float | None = None,
core_diameter_mm: float | None = None,
core_type: str | None = None,
category: str | None = None,
production_site: str | None = None,
include_roll_weight: bool = True,
) -> dict[str, Any]:
"""Create the established PDF from original inputs, never LLM result values."""
request = {
"article_number": article_number,
"article_name_hint": article_name_hint,
"roll_length_m": roll_length_m,
"width_m": width_m,
"thickness_mm": thickness_mm,
"thickness_stddev_mm": thickness_stddev_mm,
"area_weight_g_m2": area_weight_g_m2,
"core_diameter_mm": core_diameter_mm,
"core_type": core_type,
"category": category,
"production_site": production_site,
"include_roll_weight": include_roll_weight,
}
return generate_mcp_calculation_pdf(request)
def analyze_transport_capacity_result(
*,
transport_preset: str,
roll_diameter_mm: float,
core_diameter_mm: float,
roll_width_m: float,
roll_weight_kg: float,
product_length_m: float,
) -> dict[str, Any]:
"""Map the preset-only MCP contract to the shared transport domain."""
return analyze_transport({
"transport_preset": transport_preset,
"roll_diameter_mm": roll_diameter_mm,
"core_diameter_mm": core_diameter_mm,
"roll_width_m": roll_width_m,
"roll_weight_kg": roll_weight_kg,
"product_length_m": product_length_m,
})