ForecastIQ-Pro / app.py
Syed Ali Reza
Simplify app.py for HF Spaces - self-contained minimal version
e8a8147
"""
Main application entry point for Hugging Face Spaces.
"""
import gradio as gr
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from datetime import datetime, timedelta
# Simple in-memory data storage
class AppState:
def __init__(self):
self.data = None
self.forecast = None
app_state = AppState()
def load_sample_data(dataset_name):
"""Load or generate sample data."""
try:
# Generate synthetic data
dates = pd.date_range(start='2023-01-01', periods=365, freq='D')
if "Retail" in dataset_name:
# Retail pattern with weekly seasonality
base = 100
trend = np.linspace(0, 20, 365)
seasonal = 20 * np.sin(2 * np.pi * np.arange(365) / 7)
noise = np.random.normal(0, 5, 365)
values = base + trend + seasonal + noise
elif "Walmart" in dataset_name:
# Walmart M5 style
base = 150
trend = np.linspace(0, 30, 365)
weekly = 40 * np.sin(2 * np.pi * np.arange(365) / 7)
noise = np.random.normal(0, 10, 365)
values = base + trend + weekly + noise
else:
# Synthetic random walk
values = 100 + np.cumsum(np.random.randn(365) * 5)
values = np.maximum(values, 10) # Keep positive
data = pd.DataFrame({
'date': dates,
'value': values
})
app_state.data = data
# Create stats
stats = f"""
📊 Dataset Statistics:
• Total Rows: {len(data)}
• Date Range: {data['date'].min().date()} to {data['date'].max().date()}
• Mean Value: {data['value'].mean():.2f}
• Std Dev: {data['value'].std():.2f}
• Min Value: {data['value'].min():.2f}
• Max Value: {data['value'].max():.2f}
"""
# Create plot
fig = go.Figure()
fig.add_trace(go.Scatter(
x=data['date'],
y=data['value'],
mode='lines',
name='Historical Data',
line=dict(color='blue', width=2)
))
fig.update_layout(
title="Time Series Data",
xaxis_title="Date",
yaxis_title="Value",
template='plotly_white',
height=400
)
return data.head(100), stats, fig, "✅ Data loaded successfully!"
except Exception as e:
return None, f"❌ Error: {str(e)}", None, "Failed to load data"
def simple_forecast(horizon):
"""Generate simple moving average forecast."""
try:
if app_state.data is None:
return None, "❌ Please load data first!", "Please load data in Data Upload tab"
data = app_state.data
# Simple moving average forecast
window = min(30, len(data) // 2)
ma = data['value'].tail(window).mean()
std = data['value'].tail(window).std()
# Generate future dates
last_date = data['date'].iloc[-1]
future_dates = pd.date_range(start=last_date + timedelta(days=1), periods=horizon, freq='D')
# Simple forecast
forecast_values = np.ones(horizon) * ma
lower = forecast_values - 1.96 * std
upper = forecast_values + 1.96 * std
forecast_df = pd.DataFrame({
'date': future_dates,
'forecast': forecast_values,
'lower_bound': lower,
'upper_bound': upper
})
app_state.forecast = forecast_df
# Create plot
fig = go.Figure()
# Historical
fig.add_trace(go.Scatter(
x=data['date'],
y=data['value'],
mode='lines',
name='Historical',
line=dict(color='blue', width=2)
))
# Forecast
fig.add_trace(go.Scatter(
x=forecast_df['date'],
y=forecast_df['forecast'],
mode='lines',
name='Forecast',
line=dict(color='red', width=2, dash='dash')
))
# Confidence interval
fig.add_trace(go.Scatter(
x=forecast_df['date'].tolist() + forecast_df['date'].tolist()[::-1],
y=forecast_df['upper_bound'].tolist() + forecast_df['lower_bound'].tolist()[::-1],
fill='toself',
fillcolor='rgba(255,0,0,0.2)',
line=dict(color='rgba(255,255,255,0)'),
name='95% Confidence',
showlegend=True
))
fig.update_layout(
title=f"{horizon}-Day Forecast",
xaxis_title="Date",
yaxis_title="Value",
template='plotly_white',
height=400
)
info = f"""
📈 Forecast Information:
• Model: Moving Average (Simple)
• Horizon: {horizon} days
• Mean Forecast: {forecast_values.mean():.2f}
• Confidence Level: 95%
"""
return fig, info, "✅ Forecast generated successfully!"
except Exception as e:
return None, f"❌ Error: {str(e)}", "Forecast generation failed"
def calculate_eoq():
"""Calculate Economic Order Quantity."""
try:
if app_state.forecast is None:
return "❌ Please generate forecast first!", ""
# Simple EOQ calculation
annual_demand = app_state.forecast['forecast'].sum() * (365 / len(app_state.forecast))
ordering_cost = 100.0
holding_cost = 10.0 * 0.20
# EOQ formula
eoq = np.sqrt(2 * annual_demand * ordering_cost / holding_cost)
num_orders = annual_demand / eoq
total_cost = (num_orders * ordering_cost) + ((eoq / 2) * holding_cost)
results = f"""
📦 Inventory Optimization Results (EOQ Model):
• Optimal Order Quantity: {eoq:.2f} units
• Number of Orders per Year: {num_orders:.2f}
• Order Cycle Time: {365 / num_orders:.1f} days
• Total Annual Cost: ${total_cost:.2f}
- Ordering Cost: ${num_orders * ordering_cost:.2f}
- Holding Cost: ${(eoq / 2) * holding_cost:.2f}
• Annual Demand: {annual_demand:.2f} units
"""
recommendations = f"""
💡 Recommendations:
• Place an order of {eoq:.0f} units every {365 / num_orders:.0f} days
• Maintain safety stock of {eoq * 0.2:.0f} units
• Monitor demand patterns for adjustments
"""
return results, recommendations
except Exception as e:
return f"❌ Error: {str(e)}", ""
# Create Gradio interface
with gr.Blocks(title="ForecastIQ Pro", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# 🎯 ForecastIQ Pro
**AI-Powered Demand Forecasting & Inventory Optimization**
""")
with gr.Tabs():
# Tab 1: Data Upload
with gr.Tab("📁 Data Upload"):
with gr.Row():
with gr.Column():
dataset_selector = gr.Radio(
choices=["Sample: Retail Sales", "Sample: Walmart M5", "Sample: Synthetic"],
label="Select Dataset",
value="Sample: Retail Sales"
)
load_btn = gr.Button("Load Data", variant="primary")
status_text = gr.Textbox(label="Status", lines=1)
with gr.Column():
data_stats = gr.Textbox(label="Statistics", lines=8)
data_preview = gr.Dataframe(label="Data Preview")
data_plot = gr.Plot(label="Time Series Visualization")
load_btn.click(
load_sample_data,
inputs=[dataset_selector],
outputs=[data_preview, data_stats, data_plot, status_text]
)
# Tab 2: Forecasting
with gr.Tab("🔮 Forecasting"):
with gr.Row():
with gr.Column():
horizon_slider = gr.Slider(
minimum=7,
maximum=90,
value=30,
step=1,
label="Forecast Horizon (days)"
)
forecast_btn = gr.Button("Generate Forecast", variant="primary")
forecast_status = gr.Textbox(label="Status", lines=1)
with gr.Column():
forecast_info = gr.Textbox(label="Forecast Information", lines=8)
forecast_plot = gr.Plot(label="Forecast Results")
forecast_btn.click(
simple_forecast,
inputs=[horizon_slider],
outputs=[forecast_plot, forecast_info, forecast_status]
)
# Tab 3: Optimization
with gr.Tab("📦 Inventory Optimization"):
gr.Markdown("### Economic Order Quantity (EOQ) Model")
optimize_btn = gr.Button("Calculate Optimal Inventory", variant="primary")
with gr.Row():
opt_results = gr.Textbox(label="Optimization Results", lines=12)
opt_recommendations = gr.Textbox(label="Recommendations", lines=12)
optimize_btn.click(
calculate_eoq,
inputs=[],
outputs=[opt_results, opt_recommendations]
)
gr.Markdown("""
---
### 📚 About ForecastIQ Pro
A production-grade platform for demand forecasting and inventory optimization.
Combines time series analysis with operations research for data-driven decision making.
""")
if __name__ == "__main__":
demo.launch(share=True)