kk20krishna's picture
Update app.py
bf00c52 verified
import gradio as gr
import pandas as pd
from pulp import *
from io import StringIO
import numpy as np
import xlsxwriter
from datetime import datetime
# Function to process the inputs and execute the optimization
def process_data(stock_data, order_data, cost_data, priority_data):
try:
# Convert inputs to DataFrames
stock_data = pd.DataFrame(stock_data, columns=["Warehouse", "Product#1", "Product#2", "Product#3", "Product#4"])
order_data = pd.DataFrame(order_data, columns=["Order", "Product#1", "Product#2", "Product#3", "Product#4"])
cost_data = pd.DataFrame(cost_data, columns=["Warehouse", "Order", "Product", "Cost"])
priority_data = pd.DataFrame(priority_data, columns=["Warehouse", "Priority"])
# Convert numeric columns to appropriate types
for col in stock_data.columns[1:]:
stock_data[col] = pd.to_numeric(stock_data[col], errors='coerce')
for col in order_data.columns[1:]:
order_data[col] = pd.to_numeric(order_data[col], errors='coerce')
cost_data["Priority"] = pd.to_numeric(cost_data["Cost"], errors='coerce')
priority_data["Priority"] = pd.to_numeric(priority_data["Priority"], errors='coerce')
# Drop rows with invalid data
#stock_data.dropna(inplace=True)
#order_data.dropna(inplace=True)
#priority_data.dropna(inplace=True)
##############################################################################################################
# Creates a dictionary of the available stock of each product in each warehouse
Warehouses = stock_data['Warehouse'].to_list()
Products = stock_data.columns[1:]
stock = makeDict([Warehouses, Products], stock_data.drop('Warehouse', axis=1).values, default=0)
# Creates a dictionary of the number of each product in each deamnd
Orders = order_data['Order'].to_list()
quantity = makeDict([Orders, Products], order_data.drop('Order', axis=1).values, default=0)
# Calculate the minimum and maximum cost
min_cost = cost_data['Cost'].min()
max_cost = cost_data['Cost'].max()
# Normalize the 'Cost' column
cost_data['Cost'] = (cost_data['Cost'] - min_cost) / (max_cost - min_cost)
# Creates a dictionary of the cost for each Warehouse, Order and Product Combination
# Reshape cost_data["Cost"].values to match the dimensions of Warehouses, Orders, Products
cost_values = cost_data["Cost"].values.reshape(len(Warehouses), len(Orders), len(Products))
cost_values.shape
cost = makeDict([Warehouses, Orders, Products], cost_values, default=0)
# Calculate the minimum and maximum cost
min_cost = priority_data['Priority'].min()
max_cost = priority_data['Priority'].max()
# Normalize the 'Cost' column
priority_data['Priority'] = (priority_data['Priority'] - min_cost) / (max_cost - min_cost)
# Creates a dictionary of the priority for warehouse
priority = makeDict([Warehouses], priority_data.drop('Warehouse', axis=1).values.reshape(len(Warehouses)), default=0)
priority
# Creates a list of tuples containing all the possible routes for transport
routes = [(w, o, s) for w in Warehouses for o in Orders for s in Products]
# A dictionary called 'Vars' is created to contain the referenced variables(the routes)
variable = LpVariable.dicts("Route", (Warehouses, Orders, Products), 0, None, LpInteger)
for w in Warehouses:
for o in Orders:
for p in Products:
print(f'The variable for transporting {p} from {w} to {o} is {variable[w][o][p]}')
# Creates the 'prob' variable to contain the problem data
prob = LpProblem("Distribution Problem", LpMinimize)
# The objective function is added to 'prob' first
prob += (
lpSum([cost[w][o][p] * variable[w][o][p] * -priority[w] for (w,o,p) in routes]),
"Sum_of_Costs"
)
# The stock constraints are added to prob for each Warehouse
for w in Warehouses:
for p in Products:
prob += (
lpSum([variable[w][o][p] for o in Orders ]) <= stock[w][p],
f"Sum_of_{p}_out_of_{w}"
)
# Quantity constrainsts are added for each Order
for o in Orders:
for p in Products:
prob += (
lpSum([variable[w][o][p] for w in Warehouses ]) == quantity[o][p],
f"Sum_of_{p}_sent_to_{o}"
)
# The problem is solved using PuLP's choice of Solver
prob.solve()
# Capture output
output = StringIO()
print("Status:", LpStatus[prob.status], file=output)
if LpStatus[prob.status] != "Optimal":
print("Optimization was not successful!!", file=output)
print("Optimization was not successful!!", file=output)
print("Optimization was not successful!!", file=output)
print("Optimization was not successful!!", file=output)
print("Optimization was not successful!!", file=output)
print("Optimization was not successful!!", file=output)
print("Optimization was not successful!!", file=output)
raise ValueError(f"ERR!! No solution found!! - Status is {LpStatus[prob.status]}")
return output.getvalue()
gr.Info(f"Solution found!! - Status is {LpStatus[prob.status]}")
print("\n Fulfillment Report:", file=output)
print("========================", file=output)
print("\nSupply from each Warehouse to each Order:", file=output)
for w in Warehouses:
print(f"Warehouse {w}:", file=output)
for p in Products:
for o in Orders:
route_var = variable[w][o][p]
if route_var.varValue > 0:
print(f" supplies {route_var.varValue} units of {p}", file=output)
print(f" to Order {o}", file=output)
print("\nUnfulfilled Demand:", file=output)
unfulfilled_flag = False
for o in Orders:
for p in Products:
total_received = sum(variable[w][o][p].varValue for w in Warehouses)
unfulfilled = quantity[o][p] - total_received
if unfulfilled > 0:
print(f"Order {o}: Unfulfilled demand for {p} : {unfulfilled}", file=output)
if unfulfilled_flag is False:
print("All orders are fulfilled", file=output)
print("\n Validation Report:", file=output)
print("========================", file=output)
print("\nValidation - Warehouse level:", file=output)
for w in Warehouses:
print(f"Warehouse {w}:", file=output)
for p in Products:
total_supplied = sum(variable[w][o][p].varValue for o in Orders)
if total_supplied > stock[w][p]:
print(f"ERROR!!! - Warehouse {w}:", file=output)
print(f" Product {p}: Shipped ({total_supplied} units) is > than Stock ({stock[w][p]} units)", file=output)
print(f" Product {p}: Shipped ({total_supplied} units) is <= than Stock ({stock[w][p]} units). Remaining is {stock[w][p]} - {total_supplied}", file=output)
print("\nValidation - Order level:", file=output)
for o in Orders:
print(f"Order {w}:", file=output)
for p in Products:
total_received = sum(variable[w][o][p].varValue for w in Warehouses)
if total_received != quantity[o][p]:
print(f"ERROR!!! - Order {o}:", file=output)
print(f" Product {p}: Received ({total_received} units) is != than Quantity ({quantity[o][p]} units)", file=output)
print(f" Product {p}: Received ({total_received} units) is = than Quantity ({quantity[o][p]} units)", file=output)
# Generate a timestamped file name
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
file_name = f"/tmp/Intelligent_Sourcing_{timestamp}.xlsx"
# Create a Pandas Excel writer using XlsxWriter as the engine
writer = pd.ExcelWriter(file_name, engine='xlsxwriter')
# Write each DataFrame to a different worksheet.
stock_data.to_excel(writer, sheet_name='Stock Data', index=False)
order_data.to_excel(writer, sheet_name='Order Data', index=False)
cost_data.to_excel(writer, sheet_name='Cost Data', index=False)
priority_data.to_excel(writer, sheet_name='Priority Data', index=False)
fulfillment_solution = []
for w in Warehouses:
for p in Products:
for o in Orders:
fulfillment_solution.append({
"Warehouse": w,
"Product": p,
"Order": o,
"Supply Quantity": variable[w][o][p].varValue
})
fulfillment_solution = pd.DataFrame(fulfillment_solution)
fulfillment_solution.to_excel(writer, sheet_name='Fulfillment Solution', index=False)
# Close the Pandas Excel writer and output the Excel file.
writer.close()
return output.getvalue(), file_name
except Exception as e:
return f"Error: {str(e)}"
# Define example data
stock_example = [["Warehouse#1", 5, 1, 5, 1],
["Warehouse#2", 5, 5, 1, 15],
["Warehouse#3", 2, 5, 10, 0],
["Warehouse#4", 4, 5, 0, 2]]
order_example = [["Order#1", 2, 3, 2, 3],
["Order#2", 4, 1, 4, 1],
["Order#3", 5, 3, 5, 3],
["Order#4", 2, 2, 2, 2]]
cost_example = [
["Warehouse#1", "Order#1", "Product#1", 103],
["Warehouse#1", "Order#1", "Product#2", 436],
["Warehouse#1", "Order#1", "Product#3", 861],
["Warehouse#1", "Order#1", "Product#4", 271],
["Warehouse#1", "Order#2", "Product#1", 107],
["Warehouse#1", "Order#2", "Product#2", 72],
["Warehouse#1", "Order#2", "Product#3", 701],
["Warehouse#1", "Order#2", "Product#4", 21],
["Warehouse#1", "Order#3", "Product#1", 615],
["Warehouse#1", "Order#3", "Product#2", 122],
["Warehouse#1", "Order#3", "Product#3", 467],
["Warehouse#1", "Order#3", "Product#4", 215],
["Warehouse#1", "Order#4", "Product#1", 331],
["Warehouse#1", "Order#4", "Product#2", 459],
["Warehouse#1", "Order#4", "Product#3", 88],
["Warehouse#1", "Order#4", "Product#4", 373],
["Warehouse#2", "Order#1", "Product#1", 100],
["Warehouse#2", "Order#1", "Product#2", 872],
["Warehouse#2", "Order#1", "Product#3", 664],
["Warehouse#2", "Order#1", "Product#4", 131],
["Warehouse#2", "Order#2", "Product#1", 662],
["Warehouse#2", "Order#2", "Product#2", 309],
["Warehouse#2", "Order#2", "Product#3", 770],
["Warehouse#2", "Order#2", "Product#4", 344],
["Warehouse#2", "Order#3", "Product#1", 492],
["Warehouse#2", "Order#3", "Product#2", 414],
["Warehouse#2", "Order#3", "Product#3", 806],
["Warehouse#2", "Order#3", "Product#4", 386],
["Warehouse#2", "Order#4", "Product#1", 192],
["Warehouse#2", "Order#4", "Product#2", 956],
["Warehouse#2", "Order#4", "Product#3", 277],
["Warehouse#2", "Order#4", "Product#4", 161],
["Warehouse#3", "Order#1", "Product#1", 460],
["Warehouse#3", "Order#1", "Product#2", 314],
["Warehouse#3", "Order#1", "Product#3", 22],
["Warehouse#3", "Order#1", "Product#4", 253],
["Warehouse#3", "Order#2", "Product#1", 748],
["Warehouse#3", "Order#2", "Product#2", 857],
["Warehouse#3", "Order#2", "Product#3", 561],
["Warehouse#3", "Order#2", "Product#4", 475],
["Warehouse#3", "Order#3", "Product#1", 59],
["Warehouse#3", "Order#3", "Product#2", 511],
["Warehouse#3", "Order#3", "Product#3", 682],
["Warehouse#3", "Order#3", "Product#4", 476],
["Warehouse#3", "Order#4", "Product#1", 700],
["Warehouse#3", "Order#4", "Product#2", 976],
["Warehouse#3", "Order#4", "Product#3", 783],
["Warehouse#3", "Order#4", "Product#4", 190],
["Warehouse#4", "Order#1", "Product#1", 958],
["Warehouse#4", "Order#1", "Product#2", 687],
["Warehouse#4", "Order#1", "Product#3", 958],
["Warehouse#4", "Order#1", "Product#4", 563],
["Warehouse#4", "Order#2", "Product#1", 876],
["Warehouse#4", "Order#2", "Product#2", 567],
["Warehouse#4", "Order#2", "Product#3", 244],
["Warehouse#4", "Order#2", "Product#4", 832],
["Warehouse#4", "Order#3", "Product#1", 505],
["Warehouse#4", "Order#3", "Product#2", 131],
["Warehouse#4", "Order#3", "Product#3", 485],
["Warehouse#4", "Order#3", "Product#4", 819],
["Warehouse#4", "Order#4", "Product#1", 647],
["Warehouse#4", "Order#4", "Product#2", 21],
["Warehouse#4", "Order#4", "Product#3", 841],
["Warehouse#4", "Order#4", "Product#4", 167],
]
priority_example = [["Warehouse#1", 100],
["Warehouse#2", 200],
["Warehouse#3", 300],
["Warehouse#4", 1000]]
# Create Gradio interface
with gr.Blocks(gr.themes.Soft()) as app:
gr.Markdown("# Intelligent Sourcing Optimization App")
gr.Markdown("## Created by Krishna Kumar.S")
gr.Markdown("### PoC - Work in Progress!!")
with gr.Tab("Stock Data"):
stock_data_input = gr.Dataframe(label="Stock Data", headers=["Warehouse", "Product#1", "Product#2", "Product#3", "Product#4"],
value=stock_example, datatype="str")
with gr.Tab("Order Data"):
order_data_input = gr.Dataframe(label="Order Data", headers=["Order", "Product#1", "Product#2", "Product#3", "Product#4"],
value=order_example, datatype="str")
with gr.Tab("Cost Data"):
cost_data_input = gr.Dataframe(label="Cost Data", headers=["Warehouse", "Order", "Product", "Cost"],
value=cost_example, datatype="str")
with gr.Tab("Priority Data"):
priority_data_input = gr.Dataframe(label="Priority Data", headers=["Warehouse", "Priority"],
value=priority_example, datatype="str")
file_path = gr.State() # State to hold the file path dynamically
submit_button = gr.Button("Run Optimization")
download_button = gr.File(label="Download Excel", file_types=[".xlsx"])
output_text = gr.Textbox(label="Output", lines=110, interactive=False)
submit_button.click(process_data, inputs=[stock_data_input, order_data_input, cost_data_input, priority_data_input], outputs=[output_text, file_path])
# Update the download button dynamically with the generated file path
file_path.change(
fn=lambda path, : path,
inputs=file_path,
outputs=download_button
)
app.launch()