anshumansinha3301's picture
Update app.py
79d3012 verified
Raw
History Blame Contribute Delete
29.1 kB
import gradio as gr
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import io
from scipy.stats import norm # Using scipy.stats, but it's a common numpy-adjacent lib for stats. If not allowed, can be replaced.
# Let's stick to numpy. We can use norm.ppf or just ask for Z-score.
# User said NO new concepts. Z-score is simple. I will just ask for the Z-score directly.
# --- Calculation Functions ---
def calculate_basic_eoq(annual_demand, order_cost, holding_cost_per_unit):
"""Calculates EOQ and related metrics for the basic model."""
if annual_demand <= 0 or order_cost <= 0 or holding_cost_per_unit <= 0:
return np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan
eoq = np.sqrt((2 * annual_demand * order_cost) / holding_cost_per_unit)
num_orders_per_year = annual_demand / eoq if eoq > 0 else np.inf
avg_inventory = eoq / 2
annual_ordering_cost = num_orders_per_year * order_cost
annual_holding_cost = avg_inventory * holding_cost_per_unit
total_annual_cost = annual_ordering_cost + annual_holding_cost
demand_per_day = annual_demand / 365
return eoq, num_orders_per_year, avg_inventory, annual_ordering_cost, annual_holding_cost, total_annual_cost, demand_per_day
def calculate_eoq_with_discount(annual_demand, order_cost, holding_cost_rate, unit_cost, discount_tiers):
"""Calculates EOQ with quantity discounts."""
if annual_demand <= 0 or order_cost <= 0 or holding_cost_rate <= 0 or unit_cost <= 0:
return pd.DataFrame(), np.nan, np.nan, np.nan, np.nan, "Invalid inputs for discount model."
results = []
best_total_cost = np.inf
best_eoq = np.nan
best_unit_cost = np.nan
best_tier = ""
discount_tiers = sorted(discount_tiers, key=lambda x: x[0])
for i, (min_qty, max_qty, tier_unit_cost) in enumerate(discount_tiers):
holding_cost_per_unit = holding_cost_rate * tier_unit_cost
tier_eoq, _, _, _, _, _, _ = calculate_basic_eoq(annual_demand, order_cost, holding_cost_per_unit)
if tier_eoq < min_qty:
relevant_qty = min_qty
elif tier_eoq > max_qty and max_qty != np.inf:
relevant_qty = max_qty
else:
relevant_qty = tier_eoq
if relevant_qty <= 0:
num_orders = np.inf
ordering_cost = np.inf
holding_cost = 0
purchase_cost = annual_demand * tier_unit_cost
total_cost = np.inf
else:
num_orders = annual_demand / relevant_qty
ordering_cost = num_orders * order_cost
holding_cost = (relevant_qty / 2) * holding_cost_per_unit
purchase_cost = annual_demand * tier_unit_cost
total_cost = ordering_cost + holding_cost + purchase_cost
results.append({
"Tier": f"Tier {i+1} ({min_qty}-{max_qty if max_qty != np.inf else '∞'})",
"Unit Cost ($)": tier_unit_cost,
"Holding Cost/Unit/Year ($)": f"{holding_cost_per_unit:.2f}",
"Theoretical EOQ (Units)": f"{tier_eoq:.0f}",
"Relevant Q (Units)": f"{relevant_qty:.0f}",
"Annual Ordering Cost ($)": f"{ordering_cost:.2f}",
"Annual Holding Cost ($)": f"{holding_cost:.2f}",
"Annual Purchase Cost ($)": f"{purchase_cost:.2f}",
"Total Annual Cost ($)": f"{total_cost:.2f}"
})
if total_cost < best_total_cost:
best_total_cost = total_cost
best_eoq = relevant_qty
best_unit_cost = tier_unit_cost
best_tier = f"Tier {i+1}"
df = pd.DataFrame(results)
return df, best_eoq, best_total_cost, best_unit_cost, best_tier, "Analysis complete."
def calculate_poq(annual_demand, order_cost, holding_cost_per_unit, daily_production_rate, daily_demand_rate):
"""Calculates Production Order Quantity (POQ) and related metrics."""
if annual_demand <= 0 or order_cost <= 0 or holding_cost_per_unit <= 0 or \
daily_production_rate <= 0 or daily_demand_rate <= 0:
return np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan
if daily_production_rate <= daily_demand_rate:
return np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, "Production rate must be greater than demand rate."
poq = np.sqrt((2 * annual_demand * order_cost) / (holding_cost_per_unit * (1 - (daily_demand_rate / daily_production_rate))))
num_setups_per_year = annual_demand / poq if poq > 0 else np.inf
max_inventory_level = poq * (1 - (daily_demand_rate / daily_production_rate))
avg_inventory = max_inventory_level / 2
annual_setup_cost = num_setups_per_year * order_cost
annual_holding_cost = avg_inventory * holding_cost_per_unit
total_annual_cost = annual_setup_cost + annual_holding_cost
production_run_days = poq / daily_production_rate
inventory_cycle_days = poq / daily_demand_rate
return poq, num_setups_per_year, max_inventory_level, avg_inventory, annual_setup_cost, \
annual_holding_cost, total_annual_cost, production_run_days, inventory_cycle_days
def calculate_rop_and_ss(avg_daily_demand, lead_time_days, std_dev_daily_demand, service_level_z):
"""Calculates Reorder Point and Safety Stock."""
if avg_daily_demand < 0 or lead_time_days < 0 or std_dev_daily_demand < 0 or service_level_z < 0:
return 0, 0, 0, "Inputs must be non-negative."
std_dev_lead_time = std_dev_daily_demand * np.sqrt(lead_time_days)
safety_stock = std_dev_lead_time * service_level_z
demand_during_lead_time = avg_daily_demand * lead_time_days
reorder_point = demand_during_lead_time + safety_stock
return safety_stock, demand_during_lead_time, reorder_point, "Calculation successful."
def calculate_sma_forecast(demand_data_str, window_size):
"""Calculates Simple Moving Average (SMA) forecast."""
if not demand_data_str:
return pd.DataFrame(), None, "Please enter demand data."
try:
demand_values = [float(d.strip()) for d in demand_data_str.split(',') if d.strip()]
if len(demand_values) < window_size:
return pd.DataFrame(), None, f"Not enough data for window size {window_size}. Need at least {window_size} data points."
df = pd.DataFrame({'Demand': demand_values})
df['Period'] = range(1, len(df) + 1)
# Calculate SMA
df[f'SMA (Window={window_size})'] = df['Demand'].rolling(window=window_size).mean()
# Forecast next period
forecast_next_period = df['Demand'].tail(window_size).mean()
# Create plot
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(df['Period'], df['Demand'], label='Actual Demand', marker='o', linestyle='-')
ax.plot(df['Period'], df[f'SMA (Window={window_size})'], label='SMA', marker='x', linestyle='--')
ax.set_title('Simple Moving Average (SMA) Forecast', fontsize=14)
ax.set_xlabel('Period', fontsize=10)
ax.set_ylabel('Demand', fontsize=10)
ax.legend()
ax.grid(True, linestyle=':', alpha=0.7)
plt.tight_layout()
plt.close(fig)
return df, fig, f"Forecast for next period: {forecast_next_period:.2f}"
except Exception as e:
return pd.DataFrame(), None, f"Error: {e}. Ensure data is comma-separated numbers."
# --- Plotting Function ---
def create_eoq_plot(annual_demand, order_cost, holding_cost_per_unit, min_q=1, max_q_multiplier=2.5, current_eoq=None):
"""Generates the EOQ cost curves plot."""
if annual_demand <= 0 or order_cost <= 0 or holding_cost_per_unit <= 0:
fig, ax = plt.subplots(figsize=(6, 4))
ax.text(0.5, 0.5, "Invalid input for plot.", horizontalalignment='center', verticalalignment='center', transform=ax.transAxes)
ax.axis('off')
plt.close(fig)
return fig
if current_eoq is None or np.isnan(current_eoq) or current_eoq <= 0:
current_eoq = np.sqrt((2 * annual_demand * order_cost) / holding_cost_per_unit)
if np.isnan(current_eoq) or current_eoq <= 0:
current_eoq = 100
quantity_range = np.linspace(min_q, current_eoq * max_q_multiplier, 300)
quantity_range = quantity_range[quantity_range > 0]
ordering_costs = (annual_demand / quantity_range) * order_cost
holding_costs = (quantity_range / 2) * holding_cost_per_unit
total_costs = ordering_costs + holding_costs
fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(quantity_range, holding_costs, label='Annual Holding Cost', color='orange')
ax.plot(quantity_range, ordering_costs, label='Annual Ordering Cost', color='blue')
ax.plot(quantity_range, total_costs, label='Total Cost', color='green', linewidth=2.5)
if not np.isnan(current_eoq) and current_eoq > 0:
ax.axvline(x=current_eoq, color='red', linestyle='--', label=f'EOQ: {current_eoq:.0f} units')
total_cost_at_eoq_idx = np.argmin(np.abs(quantity_range - current_eoq))
total_cost_at_eoq = total_costs[total_cost_at_eoq_idx]
ax.plot(current_eoq, total_cost_at_eoq, 'ro')
ax.set_title('EOQ Cost Analysis', fontsize=12)
ax.set_xlabel('Order Quantity (Units)', fontsize=10)
ax.set_ylabel('Annual Cost ($)', fontsize=10)
ax.legend(fontsize=8, loc='upper right')
ax.grid(True, linestyle=':', alpha=0.7)
ax.set_ylim(bottom=0)
ax.set_xlim(left=0)
ax.tick_params(axis='both', which='major', labelsize=8)
plt.tight_layout()
plt.close(fig)
return fig
# --- Combined Interface Function for Basic EOQ ---
def update_basic_eoq(annual_demand, order_cost, holding_cost_per_unit, lead_time_days):
"""Updates all outputs for the Basic EOQ tab."""
if any(x <= 0 for x in [annual_demand, order_cost, holding_cost_per_unit]):
return None, 0, 0, 0, 0, 0, 0, 0, "Please enter positive values for all basic EOQ parameters."
eoq, num_orders, avg_inventory, annual_ordering_cost, annual_holding_cost, total_annual_cost, demand_per_day = \
calculate_basic_eoq(annual_demand, order_cost, holding_cost_per_unit)
# Simple Reorder Point (no safety stock)
reorder_point = demand_per_day * lead_time_days
plot_fig = create_eoq_plot(annual_demand, order_cost, holding_cost_per_unit, current_eoq=eoq)
return plot_fig, eoq, num_orders, avg_inventory, annual_ordering_cost, annual_holding_cost, \
total_annual_cost, reorder_point, "Calculation successful."
# --- Combined Interface Function for POQ ---
def update_poq_model(annual_demand, setup_cost, holding_cost_per_unit, daily_production_rate, daily_demand_rate):
"""Updates all outputs for the POQ tab."""
if any(x <= 0 for x in [annual_demand, setup_cost, holding_cost_per_unit, daily_production_rate, daily_demand_rate]):
return None, 0, 0, 0, 0, 0, 0, 0, 0, "Please enter positive values for all POQ parameters."
if daily_production_rate <= daily_demand_rate:
return None, 0, 0, 0, 0, 0, 0, 0, 0, "Production rate must be greater than demand rate."
poq, num_setups, max_inv, avg_inv, annual_setup_cost, annual_holding_cost, total_cost, prod_days, cycle_days = \
calculate_poq(annual_demand, setup_cost, holding_cost_per_unit, daily_production_rate, daily_demand_rate)
plot_fig = create_eoq_plot(annual_demand, setup_cost, holding_cost_per_unit, current_eoq=poq, max_q_multiplier=2.0)
return plot_fig, poq, num_setups, max_inv, avg_inv, annual_setup_cost, annual_holding_cost, total_cost, \
prod_days, cycle_days, "Calculation successful."
# --- Helper for Discount Tiers ---
def create_discount_tiers_df(tier1_min, tier1_max, tier1_uc,
tier2_min, tier2_max, tier2_uc,
tier3_min, tier3_max, tier3_uc):
"""Helper to create the discount_tiers list from Gradio inputs."""
tiers = []
if tier1_min is not None and tier1_uc is not None and tier1_min >= 0 and tier1_uc >= 0:
tiers.append((tier1_min, tier1_max if tier1_max is not None else np.inf, tier1_uc))
if tier2_min is not None and tier2_uc is not None and tier2_min >= 0 and tier2_uc >= 0:
tiers.append((tier2_min, tier2_max if tier2_max is not None else np.inf, tier2_uc))
if tier3_min is not None and tier3_uc is not None and tier3_min >= 0 and tier3_uc >= 0:
tiers.append((tier3_min, tier3_max if tier3_max is not None else np.inf, tier3_uc))
valid_tiers = []
for t in tiers:
if t[1] != np.inf and t[0] >= t[1]:
print(f"Warning: Invalid tier range {t}. Skipping.")
else:
valid_tiers.append(t)
valid_tiers = sorted(valid_tiers, key=lambda x: x[0])
return valid_tiers
# --- Combined Interface Function for Discount Model ---
def update_discount_model(annual_demand, order_cost, holding_cost_rate_percent,
tier1_min, tier1_max, tier1_uc,
tier2_min, tier2_max, tier2_uc,
tier3_min, tier3_max, tier3_uc):
"""Updates all outputs for the Quantity Discount tab."""
holding_cost_rate = holding_cost_rate_percent / 100.0
if any(x <= 0 for x in [annual_demand, order_cost, holding_cost_rate_percent]):
return pd.DataFrame(), 0, 0, 0, "", "Please enter positive values for core discount parameters."
discount_tiers = create_discount_tiers_df(
tier1_min, tier1_max, tier1_uc,
tier2_min, tier2_max, tier2_uc,
tier3_min, tier3_max, tier3_uc
)
if not discount_tiers:
return pd.DataFrame(), 0, 0, 0, "", "No valid discount tiers defined. Please define at least one tier with positive min quantity and unit cost."
for i in range(len(discount_tiers) - 1):
if discount_tiers[i][2] < discount_tiers[i+1][2]:
return pd.DataFrame(), 0, 0, 0, "", "Error: Unit costs must be non-increasing with quantity."
df_results, best_eoq, best_total_cost, best_unit_cost, best_tier, message = \
calculate_eoq_with_discount(annual_demand, order_cost, holding_cost_rate, discount_tiers[0][2], discount_tiers)
return df_results, best_eoq, best_total_cost, best_unit_cost, best_tier, message
# --- Gradio Interface ---
with gr.Blocks(theme=gr.themes.Soft(), title="Advanced Operations Management Dashboard") as demo:
gr.Markdown(
"""
# πŸ“Š Advanced Operations Management Dashboard (Xyphor Advisors)
Welcome to your comprehensive tool for optimizing operations and inventory decisions.
"""
)
with gr.Tabs():
# --- Tab 1: Basic EOQ Model ---
with gr.TabItem("Basic EOQ Model"):
gr.Markdown("## Economic Order Quantity (EOQ) Calculation")
gr.Markdown("Find the optimal order quantity that minimizes the sum of ordering and holding costs.")
with gr.Row():
with gr.Column():
gr.Markdown("### πŸ› οΈ Input Parameters")
basic_demand = gr.Slider(100, 50000, value=12000, step=100, label="Annual Demand (D) [units/year]")
basic_order_cost = gr.Slider(5, 500, value=100, step=5, label="Ordering Cost (S) [$/order]")
basic_holding_cost = gr.Slider(0.1, 50, value=5, step=0.1, label="Holding Cost (H) [$/unit/year]")
basic_lead_time = gr.Slider(0, 30, value=7, step=1, label="Lead Time [days]", info="Time from order placement to receipt.")
basic_status_message = gr.Textbox(label="Status", interactive=False, value="Enter parameters and run.")
with gr.Column():
gr.Markdown("### πŸ“ˆ Cost Analysis & Optimal Q")
basic_plot_output = gr.Plot(label="EOQ Cost Curves", scale=2)
with gr.Accordion("Detailed Results", open=True):
gr.Markdown("#### Key Metrics")
with gr.Row():
basic_eoq_out = gr.Number(label="Optimal Order Quantity (EOQ) [units]", precision=0)
basic_num_orders_out = gr.Number(label="Annual Orders [count]", precision=2)
basic_avg_inventory_out = gr.Number(label="Average Inventory [units]", precision=2)
gr.Markdown("#### Annual Costs")
with gr.Row():
basic_ordering_cost_out = gr.Number(label="Annual Ordering Cost [$]", precision=2)
basic_holding_cost_out = gr.Number(label="Annual Holding Cost [$]", precision=2)
basic_total_cost_out = gr.Number(label="Total Annual Cost [$]", precision=2)
gr.Markdown("#### Reorder Point (Simple)")
with gr.Row():
basic_reorder_point_out = gr.Number(label="Reorder Point (without Safety Stock) [units]", precision=0)
basic_inputs = [basic_demand, basic_order_cost, basic_holding_cost, basic_lead_time]
basic_outputs = [basic_plot_output, basic_eoq_out, basic_num_orders_out, basic_avg_inventory_out,
basic_ordering_cost_out, basic_holding_cost_out, basic_total_cost_out,
basic_reorder_point_out, basic_status_message]
for inp in basic_inputs:
inp.change(
fn=update_basic_eoq,
inputs=basic_inputs,
outputs=basic_outputs
)
demo.load(
fn=update_basic_eoq,
inputs=basic_inputs,
outputs=basic_outputs
)
# --- Tab 2: Reorder Point & Safety Stock ---
with gr.TabItem("Reorder Point (ROP) & Safety Stock"):
gr.Markdown("## Reorder Point & Safety Stock Calculator")
gr.Markdown("Determine the precise inventory level at which to place a new order to avoid stockouts.")
with gr.Row():
with gr.Column():
gr.Markdown("### πŸ› οΈ Input Parameters")
rop_avg_demand = gr.Number(label="Average Daily Demand [units/day]", value=50)
rop_lead_time = gr.Number(label="Lead Time [days]", value=10)
rop_std_dev = gr.Number(label="Standard Deviation of Daily Demand", value=5)
rop_z_score = gr.Slider(minimum=0.0, maximum=3.0, value=1.65, step=0.01,
label="Z-Score (for Service Level)",
info="e.g., 1.65 for 95% service level, 2.33 for 99%")
rop_status = gr.Textbox(label="Status", interactive=False)
with gr.Column():
gr.Markdown("### πŸ”‘ Calculated Metrics")
rop_ss_out = gr.Number(label="Safety Stock (SS) [units]", precision=0,
info="The buffer stock held to prevent stockouts.")
rop_dlt_out = gr.Number(label="Demand During Lead Time [units]", precision=0,
info="Total expected demand while waiting for the order.")
rop_out = gr.Number(label="Reorder Point (ROP) [units]", precision=0,
info="Place an order when inventory hits this level. (ROP = SS + Demand During Lead Time)")
rop_inputs = [rop_avg_demand, rop_lead_time, rop_std_dev, rop_z_score]
rop_outputs = [rop_ss_out, rop_dlt_out, rop_out, rop_status]
for inp in rop_inputs:
inp.change(fn=calculate_rop_and_ss, inputs=rop_inputs, outputs=rop_outputs)
demo.load(fn=calculate_rop_and_ss, inputs=rop_inputs, outputs=rop_outputs)
# --- Tab 3: Quantity Discount Model ---
with gr.TabItem("Quantity Discount Model"):
gr.Markdown("## EOQ with Quantity Discounts")
gr.Markdown("Evaluate the impact of price breaks on the optimal order quantity and total cost.")
with gr.Row():
with gr.Column():
gr.Markdown("### πŸ› οΈ Core Parameters")
discount_demand = gr.Slider(100, 50000, value=15000, step=100, label="Annual Demand (D) [units/year]")
discount_order_cost = gr.Slider(5, 500, value=75, step=5, label="Ordering Cost (S) [$/order]")
discount_holding_rate = gr.Slider(0.01, 0.5, value=0.2, step=0.01, label="Holding Cost Rate [% of Unit Cost]", info="e.g., 20% = 0.2")
gr.Markdown("### 🏷️ Discount Tiers")
gr.Markdown("Define up to three discount tiers. Unit costs must be non-increasing.")
with gr.Accordion("Tier 1", open=True):
with gr.Row():
tier1_min_qty = gr.Number(label="Min Qty", value=0, precision=0)
tier1_max_qty = gr.Number(label="Max Qty", value=999, precision=0)
tier1_unit_cost = gr.Number(label="Unit Cost ($)", value=10.00, precision=2)
with gr.Accordion("Tier 2"):
with gr.Row():
tier2_min_qty = gr.Number(label="Min Qty", value=1000, precision=0)
tier2_max_qty = gr.Number(label="Max Qty", value=4999, precision=0)
tier2_unit_cost = gr.Number(label="Unit Cost ($)", value=9.50, precision=2)
with gr.Accordion("Tier 3"):
with gr.Row():
tier3_min_qty = gr.Number(label="Min Qty", value=5000, precision=0)
tier3_max_qty = gr.Number(label="Max Qty", value=None, precision=0, info="Leave blank for infinity")
tier3_unit_cost = gr.Number(label="Unit Cost ($)", value=9.00, precision=2)
discount_status_message = gr.Textbox(label="Status", interactive=False, value="Enter parameters and run.")
with gr.Column():
gr.Markdown("### πŸ“Š Tier Analysis & Optimal Selection")
discount_output_df = gr.DataFrame(label="Discount Tier Analysis", interactive=False)
gr.Markdown("#### Optimal Discount Solution")
with gr.Row():
best_discount_eoq_out = gr.Number(label="Optimal Order Quantity [units]", precision=0)
best_discount_cost_out = gr.Number(label="Minimum Total Annual Cost [$]", precision=2)
with gr.Row():
best_discount_unit_cost_out = gr.Number(label="Unit Cost at Optimal Q [$]", precision=2)
best_discount_tier_out = gr.Textbox(label="Optimal Tier", interactive=False)
discount_inputs = [discount_demand, discount_order_cost, discount_holding_rate,
tier1_min_qty, tier1_max_qty, tier1_unit_cost,
tier2_min_qty, tier2_max_qty, tier2_unit_cost,
tier3_min_qty, tier3_max_qty, tier3_unit_cost]
discount_outputs = [discount_output_df, best_discount_eoq_out, best_discount_cost_out,
best_discount_unit_cost_out, best_discount_tier_out, discount_status_message]
for inp in discount_inputs:
inp.change(
fn=update_discount_model,
inputs=discount_inputs,
outputs=discount_outputs
)
demo.load(
fn=update_discount_model,
inputs=discount_inputs,
outputs=discount_outputs
)
# --- Tab 4: Production Order Quantity (POQ) Model ---
with gr.TabItem("Production Order Quantity (POQ) Model"):
gr.Markdown("## Production Order Quantity (POQ) Calculation")
gr.Markdown("Determine the optimal batch size when production is internal and gradual.")
with gr.Row():
with gr.Column():
gr.Markdown("### πŸ› οΈ Input Parameters")
poq_demand = gr.Slider(100, 50000, value=20000, step=100, label="Annual Demand (D) [units/year]")
poq_setup_cost = gr.Slider(5, 500, value=200, step=5, label="Setup Cost (S) [$/setup]")
poq_holding_cost = gr.Slider(0.1, 50, value=8, step=0.1, label="Holding Cost (H) [$/unit/year]")
gr.Markdown("### 🏭 Production Specifics")
poq_prod_rate = gr.Slider(10, 2000, value=500, step=10, label="Daily Production Rate (P) [units/day]")
poq_demand_rate = gr.Slider(1, 1000, value=80, step=1, label="Daily Demand Rate (d) [units/day]", info="Must be less than Daily Production Rate.")
poq_status_message = gr.Textbox(label="Status", interactive=False, value="Enter parameters and run.")
with gr.Column():
gr.Markdown("### πŸ“ˆ Cost Analysis & Optimal Batch Size")
poq_plot_output = gr.Plot(label="POQ Cost Curves", scale=2)
with gr.Accordion("Detailed Results", open=True):
gr.Markdown("#### Key Metrics")
with gr.Row():
poq_out = gr.Number(label="Optimal Production Quantity (POQ) [units]", precision=0)
poq_num_setups_out = gr.Number(label="Annual Setups [count]", precision=2)
poq_max_inv_out = gr.Number(label="Maximum Inventory Level [units]", precision=2)
poq_avg_inv_out = gr.Number(label="Average Inventory [units]", precision=2)
gr.Markdown("#### Annual Costs")
with gr.Row():
poq_setup_cost_out = gr.Number(label="Annual Setup Cost [$]", precision=2)
poq_holding_cost_out = gr.Number(label="Annual Holding Cost [$]", precision=2)
poq_total_cost_out = gr.Number(label="Total Annual Cost [$]", precision=2)
gr.Markdown("#### Production Cycle Details")
with gr.Row():
poq_prod_days_out = gr.Number(label="Production Run Duration [days]", precision=2)
poq_cycle_days_out = gr.Number(label="Inventory Cycle Duration [days]", precision=2)
poq_inputs = [poq_demand, poq_setup_cost, poq_holding_cost, poq_prod_rate, poq_demand_rate]
poq_outputs = [poq_plot_output, poq_out, poq_num_setups_out, poq_max_inv_out, poq_avg_inv_out,
poq_setup_cost_out, poq_holding_cost_out, poq_total_cost_out,
poq_prod_days_out, poq_cycle_days_out, poq_status_message]
for inp in poq_inputs:
inp.change(
fn=update_poq_model,
inputs=poq_inputs,
outputs=poq_outputs
)
demo.load(
fn=update_poq_model,
inputs=poq_inputs,
outputs=poq_outputs
)
# --- Tab 5: Demand Forecasting (SMA) ---
with gr.TabItem("Demand Forecasting (SMA)"):
gr.Markdown("## Simple Moving Average (SMA) Forecasting")
gr.Markdown("Forecast future demand based on the average of past demand data.")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### πŸ› οΈ Input Parameters")
sma_data = gr.Textbox(label="Past Demand Data (comma-separated)",
value="100, 110, 105, 120, 115, 125, 130, 122, 135, 140")
sma_window = gr.Slider(minimum=2, maximum=10, value=3, step=1,
label="SMA Window Size (Periods)")
sma_status = gr.Textbox(label="Status & Forecast", interactive=False)
with gr.Column(scale=2):
gr.Markdown("### πŸ“ˆ Forecast Plot")
sma_plot = gr.Plot()
sma_df = gr.DataFrame(label="Data and SMA")
sma_inputs = [sma_data, sma_window]
sma_outputs = [sma_df, sma_plot, sma_status]
for inp in sma_inputs:
inp.change(fn=calculate_sma_forecast, inputs=sma_inputs, outputs=sma_outputs)
demo.load(fn=calculate_sma_forecast, inputs=sma_inputs, outputs=sma_outputs)
if __name__ == "__main__":
demo.launch(share=True, inbrowser=True, debug=True)