optimize_production_plan_tool
Optimize multi-period production planning to maximize profit or minimize costs by balancing product demand, resource capacity, and inventory constraints.
Instructions
Optimize multi-period production planning to maximize profit or minimize costs.
Args:
products: List of product dictionaries with costs and resource requirements
resources: List of resource dictionaries with capacity constraints
periods: Number of planning periods
demand: List of demand requirements per product per period
objective: Optimization objective ("maximize_profit", "minimize_cost", "minimize_time")
inventory_costs: Optional inventory holding costs per product
setup_costs: Optional setup costs per product
solver_name: Solver to use ("CBC", "GLPK", "GUROBI", "CPLEX")
time_limit_seconds: Maximum solving time in seconds (default: 30.0)
Returns:
Optimization result with optimal production plan
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| products | Yes | ||
| resources | Yes | ||
| periods | Yes | ||
| demand | Yes | ||
| objective | No | maximize_profit | |
| inventory_costs | No | ||
| setup_costs | No | ||
| solver_name | No | CBC | |
| time_limit_seconds | No |
Implementation Reference
- The handler function for 'optimize_production_plan_tool', decorated with @mcp.tool(). It constructs input data from parameters and calls the core solver solve_production_planning.@mcp.tool() def optimize_production_plan_tool( products: list[dict[str, Any]], resources: list[dict[str, Any]], periods: int, demand: list[dict[str, Any]], objective: str = "maximize_profit", inventory_costs: dict[str, float] | None = None, setup_costs: dict[str, float] | None = None, solver_name: str = "CBC", time_limit_seconds: float = 30.0, ) -> dict[str, Any]: """Optimize multi-period production planning to maximize profit or minimize costs. Args: products: List of product dictionaries with costs and resource requirements resources: List of resource dictionaries with capacity constraints periods: Number of planning periods demand: List of demand requirements per product per period objective: Optimization objective ("maximize_profit", "minimize_cost", "minimize_time") inventory_costs: Optional inventory holding costs per product setup_costs: Optional setup costs per product solver_name: Solver to use ("CBC", "GLPK", "GUROBI", "CPLEX") time_limit_seconds: Maximum solving time in seconds (default: 30.0) Returns: Optimization result with optimal production plan """ input_data = { "products": products, "resources": resources, "periods": periods, "demand": demand, "objective": objective, "inventory_costs": inventory_costs, "setup_costs": setup_costs, "solver_name": solver_name, "time_limit_seconds": time_limit_seconds, } result = solve_production_planning(input_data) result_dict: dict[str, Any] = result.model_dump() return result_dict
- Core helper function that implements the mathematical optimization model using PuLP for production planning, handling multi-period, inventory, setup costs, etc.@with_resource_limits(timeout_seconds=120.0, estimated_memory_mb=150.0) def solve_production_planning(input_data: dict[str, Any]) -> OptimizationResult: """Solve Production Planning Problem using PuLP. Args: input_data: Production planning problem specification Returns: OptimizationResult with optimal production plan """ start_time = time.time() try: # Parse and validate input planning_input = ProductionPlanningInput(**input_data) products = planning_input.products resources = planning_input.resources demand_constraints = planning_input.demand_constraints horizon = planning_input.planning_horizon # Create optimization problem if planning_input.objective == "maximize_profit": prob = pulp.LpProblem("Production_Planning", pulp.LpMaximize) else: prob = pulp.LpProblem("Production_Planning", pulp.LpMinimize) # Decision variables: production quantities for each product in each period production_vars: dict[int, dict[str, Any]] = {} setup_vars: dict[int, dict[str, Any]] = {} # Binary variables for setup decisions inventory_vars: dict[int, dict[str, Any]] = {} # Inventory levels for period in range(horizon): production_vars[period] = {} setup_vars[period] = {} inventory_vars[period] = {} for product in products: # Production quantity production_vars[period][product.name] = pulp.LpVariable( f"production_{product.name}_period_{period}", lowBound=0, cat="Continuous", ) # Setup decision (binary) if product.setup_cost > 0: setup_vars[period][product.name] = pulp.LpVariable( f"setup_{product.name}_period_{period}", cat="Binary" ) # Inventory level inventory_vars[period][product.name] = pulp.LpVariable( f"inventory_{product.name}_period_{period}", lowBound=0, cat="Continuous", ) # Resource capacity constraints for period in range(horizon): for resource_name, resource in resources.items(): resource_usage = pulp.lpSum( production_vars[period][product.name] * product.resources.get(resource_name, 0) for product in products ) prob += ( resource_usage <= resource.available, f"Resource_{resource_name}_Period_{period}", ) # Production constraints for period in range(horizon): for product in products: prod_var = production_vars[period][product.name] # Minimum production if product.min_production > 0: prob += ( prod_var >= product.min_production, f"Min_Production_{product.name}_Period_{period}", ) # Maximum production if product.max_production is not None: prob += ( prod_var <= product.max_production, f"Max_Production_{product.name}_Period_{period}", ) # Setup constraints if product.setup_cost > 0: setup_var = setup_vars[period][product.name] # If producing, must setup prob += ( prod_var <= (product.max_production or 1000000) * setup_var, f"Setup_{product.name}_Period_{period}", ) # Demand constraints demand_by_product_period = {} for constraint in demand_constraints: period = constraint.period if constraint.period is not None else 0 if period < horizon: key = (constraint.product, period) demand_by_product_period[key] = constraint # Inventory balance constraints for period in range(horizon): for product in products: prod_var = production_vars[period][product.name] inv_var = inventory_vars[period][product.name] # Get demand for this product in this period demand_constraint = demand_by_product_period.get((product.name, period)) demand = demand_constraint.min_demand if demand_constraint else 0 if period == 0: # First period: production + initial_inventory = demand + ending_inventory prob += ( prod_var == demand + inv_var, f"Inventory_Balance_{product.name}_Period_{period}", ) else: # Other periods: production + previous_inventory = demand + ending_inventory prev_inv_var = inventory_vars[period - 1][product.name] prob += ( prod_var + prev_inv_var == demand + inv_var, f"Inventory_Balance_{product.name}_Period_{period}", ) # Maximum demand constraints if demand_constraint and demand_constraint.max_demand is not None: prob += ( prod_var + (inventory_vars[period - 1][product.name] if period > 0 else 0) <= demand_constraint.max_demand + inv_var, f"Max_Demand_{product.name}_Period_{period}", ) # Objective function if planning_input.objective == "maximize_profit": # Maximize profit = revenue - production costs - setup costs - inventory costs total_profit = 0.0 for period in range(horizon): # Production profit period_profit = pulp.lpSum( production_vars[period][product.name] * product.profit for product in products ) total_profit += period_profit # Setup costs if any(product.setup_cost > 0 for product in products): setup_costs = pulp.lpSum( setup_vars[period][product.name] * product.setup_cost for product in products if product.setup_cost > 0 ) total_profit -= setup_costs # Inventory costs if planning_input.inventory_cost > 0: inventory_costs = pulp.lpSum( inventory_vars[period][product.name] * planning_input.inventory_cost for product in products ) total_profit -= inventory_costs prob += total_profit, "Total_Profit" elif planning_input.objective == "minimize_cost": # Minimize total production and setup costs total_cost = 0.0 for period in range(horizon): # Production costs (negative profit) production_costs = pulp.lpSum( production_vars[period][product.name] * (-product.profit) for product in products ) total_cost += production_costs # Setup costs if any(product.setup_cost > 0 for product in products): setup_costs = pulp.lpSum( setup_vars[period][product.name] * product.setup_cost for product in products if product.setup_cost > 0 ) total_cost += setup_costs prob += total_cost, "Total_Cost" elif planning_input.objective == "minimize_time": # Minimize total production time total_time = pulp.lpSum( production_vars[period][product.name] * product.production_time for period in range(horizon) for product in products ) prob += total_time, "Total_Time" # Solve prob.solve(pulp.PULP_CBC_CMD(msg=0)) # Process results status = pulp.LpStatus[prob.status] execution_time = time.time() - start_time if prob.status == pulp.LpStatusOptimal: # Extract solution production_plan: list[dict[str, Any]] = [] total_profit = 0.0 total_cost = 0.0 total_time = 0.0 resource_utilization: dict[str, list[float]] = {} for period in range(horizon): period_plan: dict[str, Any] = { "period": period, "products": [], "resource_usage": {}, "period_profit": 0.0, "period_cost": 0.0, } for product in products: production_qty = production_vars[period][product.name].varValue or 0 inventory_qty = inventory_vars[period][product.name].varValue or 0 setup_decision = ( setup_vars[period][product.name].varValue if product.setup_cost > 0 else 0 ) product_profit = production_qty * product.profit product_cost = production_qty * (-product.profit) if product.profit < 0 else 0 product_time = production_qty * product.production_time period_plan["products"].append( { "name": product.name, "production_quantity": production_qty, "inventory_level": inventory_qty, "setup_required": bool(setup_decision), "profit": product_profit, "production_time": product_time, } ) period_plan["period_profit"] = float(period_plan["period_profit"]) + float( product_profit ) period_plan["period_cost"] = float(period_plan["period_cost"]) + float( product_cost ) total_profit += float(product_profit) total_cost += float(product_cost) total_time += float(product_time) # Calculate resource usage for this period for resource_name, resource in resources.items(): usage = sum( production_vars[period][product.name].varValue * product.resources.get(resource_name, 0) for product in products ) period_plan["resource_usage"][resource_name] = { "used": usage, "available": resource.available, "utilization": usage / resource.available if resource.available > 0 else 0, } if resource_name not in resource_utilization: resource_utilization[resource_name] = [] resource_list = resource_utilization[resource_name] resource_list.append(float(usage)) production_plan.append(period_plan) # Calculate summary statistics resource_utilization_summary: dict[str, dict[str, float]] = {} for resource_name, usage_list in resource_utilization.items(): resource = resources[resource_name] resource_utilization_summary[resource_name] = { "total_usage": sum(usage_list), "average_utilization": sum(u / resource.available for u in usage_list) / len(usage_list) if resource.available > 0 else 0, "peak_utilization": max(u / resource.available for u in usage_list) if resource.available > 0 else 0, } summary = { "total_profit": total_profit, "total_cost": total_cost, "total_production_time": total_time, "planning_horizon": horizon, "resource_utilization_summary": resource_utilization_summary, } return OptimizationResult( status=OptimizationStatus.OPTIMAL, objective_value=pulp.value(prob.objective), variables={"production_plan": production_plan, "summary": summary}, execution_time=execution_time, solver_info={ "solver_name": "PuLP CBC", "objective": planning_input.objective, "num_products": len(products), "num_resources": len(resources), "planning_horizon": horizon, }, ) elif prob.status == pulp.LpStatusInfeasible: return OptimizationResult( status=OptimizationStatus.INFEASIBLE, error_message="Production planning problem is infeasible. Check resource constraints and demand requirements.", execution_time=execution_time, ) elif prob.status == pulp.LpStatusUnbounded: return OptimizationResult( status=OptimizationStatus.UNBOUNDED, error_message="Production planning problem is unbounded.", execution_time=execution_time, ) else: return OptimizationResult( status=OptimizationStatus.ERROR, error_message=f"Solver failed with status: {status}", execution_time=execution_time, ) except Exception as e: return OptimizationResult( status=OptimizationStatus.ERROR, error_message=f"Production planning error: {str(e)}", execution_time=time.time() - start_time, )
- src/mcp_optimizer/tools/production.py:521-567 (registration)The registration function that defines and registers the tool using the MCP decorator.def register_production_tools(mcp: FastMCP[Any]) -> None: """Register production planning optimization tools with MCP server.""" @mcp.tool() def optimize_production_plan_tool( products: list[dict[str, Any]], resources: list[dict[str, Any]], periods: int, demand: list[dict[str, Any]], objective: str = "maximize_profit", inventory_costs: dict[str, float] | None = None, setup_costs: dict[str, float] | None = None, solver_name: str = "CBC", time_limit_seconds: float = 30.0, ) -> dict[str, Any]: """Optimize multi-period production planning to maximize profit or minimize costs. Args: products: List of product dictionaries with costs and resource requirements resources: List of resource dictionaries with capacity constraints periods: Number of planning periods demand: List of demand requirements per product per period objective: Optimization objective ("maximize_profit", "minimize_cost", "minimize_time") inventory_costs: Optional inventory holding costs per product setup_costs: Optional setup costs per product solver_name: Solver to use ("CBC", "GLPK", "GUROBI", "CPLEX") time_limit_seconds: Maximum solving time in seconds (default: 30.0) Returns: Optimization result with optimal production plan """ input_data = { "products": products, "resources": resources, "periods": periods, "demand": demand, "objective": objective, "inventory_costs": inventory_costs, "setup_costs": setup_costs, "solver_name": solver_name, "time_limit_seconds": time_limit_seconds, } result = solve_production_planning(input_data) result_dict: dict[str, Any] = result.model_dump() return result_dict
- src/mcp_optimizer/mcp_server.py:144-144 (registration)The call site where register_production_tools is invoked during MCP server creation to register the production tools.register_production_tools(mcp)
- Pydantic schema used for validating the input data to the solver function.class ProductionPlanningInput(BaseModel): """Input schema for Production Planning.""" products: list[Product] resources: dict[str, Resource] demand_constraints: list[DemandConstraint] = Field(default_factory=list) planning_horizon: int = Field(default=1, ge=1) objective: str = Field( default="maximize_profit", pattern="^(maximize_profit|minimize_cost|minimize_time)$", ) allow_backorders: bool = Field(default=False) inventory_cost: float = Field(default=0.0, ge=0) @field_validator("products") @classmethod def validate_products(cls, v: list[Product]) -> list[Product]: if not v: raise ValueError("Must have at least one product") return v @field_validator("resources") @classmethod def validate_resources(cls, v: dict[str, Resource]) -> dict[str, Resource]: if not v: raise ValueError("Must have at least one resource") return v @field_validator("demand_constraints") @classmethod def validate_demand_constraints(cls, v: list[DemandConstraint]) -> list[DemandConstraint]: if not v: raise ValueError("Must have at least one demand constraint") return v