MBG0903's picture
Update agents/reasoner.py
c7f64e3 verified
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import io
from PIL import Image # ⭐ REQUIRED FOR IMAGE FIX
# ============================================================
# SLOTING OPTIMIZATION β€” PHASE 1
# ============================================================
def run_slotting_analysis(message, slotting_df):
reasoning_steps = []
df = slotting_df.copy()
velocity_map = {
"fast": 3,
"medium": 2,
"slow": 1
}
df["VelocityNorm"] = df["Velocity"].str.lower().map(velocity_map)
reasoning_steps.append("Mapped velocity categories to numerical weights.")
df["FreqNorm"] = (df["Frequency"] - df["Frequency"].min()) / (
df["Frequency"].max() - df["Frequency"].min() + 1e-8
)
reasoning_steps.append("Normalized frequency to 0–1 scale.")
df["Score"] = (0.6 * df["VelocityNorm"]) + (0.4 * df["FreqNorm"])
reasoning_steps.append("Computed weighted slotting score.")
df = df.sort_values("Score", ascending=False).reset_index(drop=True)
df["Aisle"] = np.arange(1, len(df) + 1)
df["Rack"] = np.linspace(1, 20, len(df)).astype(int)
reasoning_steps.append("Assigned optimal aisle & rack positions.")
explanation = (
"### πŸ“¦ Slotting Optimization\n"
"High-velocity & high-frequency SKUs placed in prime aisles.\n\n"
"#### πŸ” Reasoning\n" + "\n".join([f"- {r}" for r in reasoning_steps])
)
return explanation, df
# ============================================================
# PICKING ROUTE OPTIMIZATION β€” PHASE 1
# ============================================================
def run_picking_optimization(message, picking_df):
reasoning_steps = []
df = picking_df.copy()
df["x"] = df["Aisle"]
df["y"] = df["Rack"]
reasoning_steps.append("Converted Aisle–Rack to coordinate grid.")
df["Distance"] = df["x"].abs() + df["y"].abs()
df = df.sort_values("Distance").reset_index(drop=True)
reasoning_steps.append("Calculated Manhattan distance & sorted sequence.")
# --- Generate plot ---
plt.figure(figsize=(6, 6))
plt.plot(df["x"], df["y"], marker="o", linestyle="-")
plt.title("Optimized Picking Route")
plt.xlabel("Aisle")
plt.ylabel("Rack")
buffer = io.BytesIO()
plt.savefig(buffer, format="png")
plt.close()
buffer.seek(0)
# ⭐ Convert BytesIO β†’ PIL (Gradio requirement)
image = Image.open(buffer)
reasoning_steps.append("Generated walking route visualization.")
explanation = (
"### 🚚 Picking Route Optimization\n"
"Manhattan-distance-based walk path generated.\n\n"
"#### πŸ” Reasoning\n" + "\n".join([f"- {r}" for r in reasoning_steps])
)
return explanation, image
# ============================================================
# DEMAND FORECASTING β€” MODULE 1
# ============================================================
def run_demand_forecast(message, slotting_df):
reasoning_steps = []
df = slotting_df.copy()
if "Frequency" not in df.columns:
return "Frequency missing β€” cannot forecast.", None, None
demand = df["Frequency"].astype(float)
reasoning_steps.append("Used SKU picking frequency as demand signal.")
moving_avg = demand.mean()
reasoning_steps.append(f"Computed moving average: {moving_avg:.2f}")
weights = np.linspace(0.1, 1.0, len(demand))
trend = np.sum(demand * weights) / np.sum(weights)
reasoning_steps.append(f"Weighted trend adjustment: {trend:.2f}")
forecast_value = (moving_avg * 0.6) + (trend * 0.4)
next_7_days = [forecast_value * (1 + 0.05 * i) for i in range(7)]
forecast_df = pd.DataFrame({
"Day": [f"Day {i+1}" for i in range(7)],
"Forecasted_Demand": next_7_days
})
reasoning_steps.append("Generated 7-day demand projection.")
# --- Plot forecast ---
plt.figure(figsize=(6, 4))
plt.plot(forecast_df["Day"], forecast_df["Forecasted_Demand"], marker="o")
plt.title("7-Day Demand Forecast")
plt.xlabel("Day")
plt.ylabel("Forecasted Demand")
buffer = io.BytesIO()
plt.savefig(buffer, format="png")
plt.close()
buffer.seek(0)
# ⭐ Convert BytesIO β†’ PIL
image = Image.open(buffer)
explanation = (
"### πŸ“ˆ Demand Forecasting\n"
"Trend-weighted moving average model applied.\n\n"
"#### πŸ” Reasoning\n" + "\n".join([f"- {r}" for r in reasoning_steps])
)
return explanation, image, forecast_df
# ============================================================
# REPLENISHMENT ANALYSIS β€” MODULE 2
# ============================================================
def run_replenishment_analysis(message, slotting_df):
reasoning_steps = []
df = slotting_df.copy()
if "Frequency" not in df.columns:
return "Frequency missing β€” cannot run replenishment.", None
BIN_CAPACITY = 200
df["CurrentStock"] = BIN_CAPACITY * 0.5
reasoning_steps.append("Assumed current stock = 50% bin capacity.")
df["SafetyStock"] = df["Frequency"] * 3
reasoning_steps.append("Safety stock = 3 days of demand.")
df["DaysUntilStockout"] = df["CurrentStock"] / df["Frequency"].replace(0, 0.1)
reasoning_steps.append("Estimated days until stock-out.")
df["ReplenishmentQty"] = (BIN_CAPACITY - df["CurrentStock"]).clip(lower=0)
reasoning_steps.append("Calculated replenishment quantity needed.")
df["Risk"] = df["DaysUntilStockout"].apply(
lambda x: "πŸ”΄ HIGH" if x < 3 else ("🟑 MEDIUM" if x < 7 else "🟒 LOW")
)
reasoning_steps.append("Assigned risk level based on depletion rate.")
explanation = (
"### πŸ”„ Replenishment Analysis\n"
"Replenishment needs evaluated using bin capacity, demand & stock-out timing.\n\n"
"#### πŸ” Reasoning\n" + "\n".join([f"- {r}" for r in reasoning_steps])
)
return explanation, df
# ============================================================
# INVENTORY REBALANCING β€” MODULE 3
# ============================================================
def run_rebalancing_analysis(message, slotting_df):
reasoning = []
df = slotting_df.copy()
if "Frequency" not in df.columns:
return "Frequency missing β€” cannot rebalance.", None
if "Aisle" not in df.columns:
df["Aisle"] = np.arange(1, len(df) + 1)
reasoning.append("Aisle data missing β€” assigned aisles automatically.")
velocity_map = {"fast": 3, "medium": 2, "slow": 1}
df["VelScore"] = df["Velocity"].str.lower().map(velocity_map)
df["LoadScore"] = df["VelScore"] * 0.6 + df["Frequency"] * 0.4
reasoning.append("Calculated SKU load score (velocity + frequency).")
aisle_load = df.groupby("Aisle")["LoadScore"].sum().reset_index()
avg_load = aisle_load["LoadScore"].mean()
aisle_load["Congestion"] = aisle_load["LoadScore"].apply(
lambda x: "πŸ”΄ High" if x > avg_load * 1.25
else ("🟑 Medium" if x > avg_load * 0.75 else "🟒 Low")
)
df = df.merge(aisle_load[["Aisle", "Congestion"]], on="Aisle", how="left")
reasoning.append("Assigned congestion levels to aisles.")
high_aisles = aisle_load[aisle_load["Congestion"] == "πŸ”΄ High"]["Aisle"].tolist()
low_aisles = aisle_load[aisle_load["Congestion"] == "🟒 Low"]["Aisle"].tolist()
move_plan = []
if high_aisles and low_aisles:
for aisle in high_aisles:
congested_skus = df[df["Aisle"] == aisle].sort_values("LoadScore", ascending=False)
top_to_move = congested_skus.head(2)
for i, row in top_to_move.iterrows():
target_aisle = low_aisles[i % len(low_aisles)]
move_plan.append({
"SKU": row["SKU"],
"FromAisle": row["Aisle"],
"ToAisle": target_aisle,
"LoadScore": round(row["LoadScore"], 2),
"Reason": "Reduce congestion"
})
reasoning.append("Generated SKU redistribution plan.")
else:
reasoning.append("No congestion found β€” no rebalancing needed.")
move_df = pd.DataFrame(move_plan)
explanation = (
"### πŸ”„ Inventory Rebalancing\n"
"SKU redistribution plan to reduce aisle congestion.\n\n"
"#### πŸ” Reasoning\n" + "\n".join([f"- {r}" for r in reasoning])
)
return explanation, move_df
# ============================================================
# WORKFORCE OPTIMIZATION β€” MODULE 4
# ============================================================
def run_workforce_optimization(message, slotting_df):
reasoning = []
df = slotting_df.copy()
if "Frequency" not in df.columns:
return "Cannot calculate workforce β€” missing Frequency column.", None
df["Workload"] = df["Frequency"] * 1.2
total_workload = df["Workload"].sum()
workers_needed = max(1, int(total_workload // 150))
reasoning.append(f"Total workload: {total_workload:.2f}")
reasoning.append(f"Workers required (estimated): {workers_needed}")
result = pd.DataFrame({
"Metric": ["Total Workload", "Estimated Workers Needed"],
"Value": [total_workload, workers_needed]
})
explanation = (
"### πŸ‘· Workforce Optimization\n"
"Estimated staffing requirement based on SKU workload.\n\n"
"#### πŸ” Reasoning\n" + "\n".join([f"- {r}" for r in reasoning])
)
return explanation, result
# ============================================================
# DOCK SCHEDULING OPTIMIZATION β€” MODULE 5
# ============================================================
def run_dock_scheduling(message, slotting_df):
reasoning = []
df = slotting_df.copy()
df["Priority"] = df["Frequency"].rank(ascending=False)
df["AssignedDock"] = df["Priority"].apply(lambda x: int((x - 1) % 3) + 1)
reasoning.append("Assigned SKUs to 3 docks based on priority rank.")
explanation = (
"### πŸš› Dock Scheduling Optimization\n"
"SKUs allocated to dock doors based on priority.\n\n"
"#### πŸ” Reasoning\n" + "\n".join([f"- {r}" for r in reasoning])
)
return explanation, df[["SKU", "Frequency", "Priority", "AssignedDock"]]