SYS2011 commited on
Commit
f8a1969
·
verified ·
1 Parent(s): 782bff3

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +307 -0
  2. requirements.txt +3 -0
app.py ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import json
4
+ import requests
5
+ from typing import Dict, Any
6
+ import os
7
+ from datetime import datetime
8
+
9
+ # Configuration
10
+ HF_ENDPOINT = os.getenv("HF_ENDPOINT", "https://i3302d3uxvtjxwjm.us-east-1.aws.endpoints.huggingface.cloud")
11
+ HF_TOKEN = os.getenv("HF_TOKEN")
12
+
13
+ # Validate that HF_TOKEN is set
14
+ if not HF_TOKEN:
15
+ raise ValueError(
16
+ "HF_TOKEN environment variable is not set. "
17
+ "Please set it in your HuggingFace Space secrets or local environment."
18
+ )
19
+
20
+
21
+ def load_scenarios_from_file(scenarios_file_path: str) -> Dict[str, Any]:
22
+ """Load scenarios from JSON file"""
23
+ try:
24
+ with open(scenarios_file_path, 'r') as f:
25
+ return json.load(f)
26
+ except Exception as e:
27
+ # Return default scenarios if file cannot be loaded
28
+ print(f"Warning: Could not load scenarios file. Using defaults. Error: {e}")
29
+ return {
30
+ "base": {},
31
+ "price_up_2pct": {"planned_price_index": 1.02},
32
+ "price_up_5pct": {"planned_price_index": 1.05},
33
+ "price_up_10pct": {"planned_price_index": 1.1},
34
+ "price_down_2pct": {"planned_price_index": 0.98},
35
+ "price_down_5pct": {"planned_price_index": 0.95},
36
+ "price_down_10pct": {"planned_price_index": 0.9},
37
+ "discount_5pct": {"planned_discount_pct": 5},
38
+ "discount_10pct": {"planned_discount_pct": 10},
39
+ "discount_20pct": {"planned_discount_pct": 20},
40
+ "promo_on": {"planned_promo_flag": 1.0},
41
+ "promo_off": {"planned_promo_flag": 0.0},
42
+ "tender_on": {"planned_tender_flag": 1.0},
43
+ "regulatory_event": {"planned_regulatory_event_flag": 1.0},
44
+ "supply_risk_high": {"planned_supply_risk": 1.0},
45
+ "competitor_pressure_high": {"planned_competitor_pressure": 1.0},
46
+ "growth_push": {
47
+ "planned_price_index": 0.95,
48
+ "planned_discount_pct": 10,
49
+ "planned_promo_flag": 1.0
50
+ },
51
+ "margin_push": {
52
+ "planned_price_index": 1.05,
53
+ "planned_discount_pct": 0.0,
54
+ "planned_promo_flag": 0.0
55
+ },
56
+ "promo_plus_discount": {
57
+ "planned_discount_pct": 15,
58
+ "planned_promo_flag": 1.0
59
+ },
60
+ "tender_plus_supply_risk": {
61
+ "planned_tender_flag": 1.0,
62
+ "planned_supply_risk": 1.0
63
+ },
64
+ "worst_case": {
65
+ "planned_price_index": 1.1,
66
+ "planned_supply_risk": 1.0,
67
+ "planned_competitor_pressure": 1.0
68
+ }
69
+ }
70
+
71
+
72
+ def csv_to_inference_json(csv_file_path: str, scenarios: Dict[str, Any]) -> Dict[str, Any]:
73
+ """Convert CSV file to inference JSON format"""
74
+ # Read CSV
75
+ df = pd.read_csv(csv_file_path)
76
+
77
+ # Clean up month column - handle both datetime and string formats
78
+ df['month'] = pd.to_datetime(df['month']).dt.strftime('%Y-%m')
79
+
80
+ # Replace NaN with None for JSON serialization
81
+ df = df.where(pd.notna(df), None)
82
+
83
+ # Build the inference JSON structure
84
+ inference_data = {
85
+ "inputs": {
86
+ "data": {
87
+ "month": df['month'].tolist(),
88
+ "product_id": df['product_id'].tolist(),
89
+ "market_id": df['market_id'].tolist(),
90
+ "units_sold": df['units_sold'].tolist(),
91
+ "planned_price_index": df['planned_price_index'].tolist(),
92
+ "planned_discount_pct": df['planned_discount_pct'].tolist(),
93
+ "planned_promo_flag": df['planned_promo_flag'].tolist(),
94
+ "planned_tender_flag": df['planned_tender_flag'].tolist(),
95
+ "planned_supply_risk": df['planned_supply_risk'].tolist(),
96
+ "planned_competitor_pressure": df['planned_competitor_pressure'].tolist(),
97
+ "planned_regulatory_event_flag": df['planned_regulatory_event_flag'].tolist()
98
+ },
99
+ "parameters": {
100
+ "encoder_length": 12,
101
+ "prediction_length": 6,
102
+ "batch_size": 256,
103
+ "n_samples": 1000,
104
+ "quantiles": [0.1, 0.5, 0.9],
105
+ "scenarios": scenarios,
106
+ "round_outputs": True
107
+ }
108
+ }
109
+ }
110
+
111
+ return inference_data
112
+
113
+
114
+ def send_inference_request(inference_json: Dict[str, Any], endpoint: str, token: str) -> Dict[str, Any]:
115
+ """Send inference request to HuggingFace endpoint"""
116
+ headers = {
117
+ "Authorization": f"Bearer {token}",
118
+ "Content-Type": "application/json"
119
+ }
120
+
121
+ response = requests.post(endpoint, headers=headers, json=inference_json)
122
+ response.raise_for_status()
123
+
124
+ return response.json()
125
+
126
+
127
+ def parse_forecast_response(response_json: Dict[str, Any]) -> pd.DataFrame:
128
+ """Parse the forecast response and convert to DataFrame"""
129
+ forecasts = response_json.get("forecasts", [])
130
+
131
+ if not forecasts:
132
+ return pd.DataFrame()
133
+
134
+ # Convert to DataFrame
135
+ df = pd.DataFrame(forecasts)
136
+
137
+ # Reorder columns for better display
138
+ column_order = [
139
+ 'scenario', 'product_id', 'market_id', 'month', 'horizon_step',
140
+ 'point_mean', 'p10', 'p50', 'p90',
141
+ 'confidence_label', 'confidence_score', 'requires_review',
142
+ 'planned_price_index', 'planned_discount_pct', 'planned_promo_flag',
143
+ 'planned_tender_flag', 'planned_regulatory_event_flag',
144
+ 'planned_supply_risk', 'planned_competitor_pressure'
145
+ ]
146
+
147
+ # Only include columns that exist
148
+ column_order = [col for col in column_order if col in df.columns]
149
+ df = df[column_order]
150
+
151
+ return df
152
+
153
+
154
+ def process_forecast(csv_file, scenarios_file):
155
+ """Main processing function for the Gradio interface"""
156
+ try:
157
+ # Validate inputs
158
+ if csv_file is None:
159
+ return None, "❌ **Error**: Please upload a CSV file", None
160
+
161
+ if scenarios_file is None:
162
+ return None, "❌ **Error**: Please upload a scenarios JSON file", None
163
+
164
+ # Load scenarios from uploaded file
165
+ scenarios = load_scenarios_from_file(scenarios_file.name)
166
+
167
+ # Convert CSV to inference JSON
168
+ inference_json = csv_to_inference_json(csv_file.name, scenarios)
169
+
170
+ # Send request to endpoint
171
+ response_json = send_inference_request(inference_json, HF_ENDPOINT, HF_TOKEN)
172
+
173
+ # Parse response into DataFrame
174
+ df_forecasts = parse_forecast_response(response_json)
175
+
176
+ if df_forecasts.empty:
177
+ return None, "⚠️ **Warning**: No forecasts returned from the model", None
178
+
179
+ # Generate summary statistics
180
+ summary_text = f"""
181
+ ✅ **Forecast Generation Successful!**
182
+
183
+ **Summary:**
184
+ - 📊 Total forecasts: **{len(df_forecasts)}**
185
+ - 🎯 Unique scenarios: **{df_forecasts['scenario'].nunique()}**
186
+ - 📦 Products: **{df_forecasts['product_id'].nunique()}**
187
+ - 🌍 Markets: **{df_forecasts['market_id'].nunique()}**
188
+ - 📅 Date range: **{df_forecasts['month'].min()}** to **{df_forecasts['month'].max()}**
189
+
190
+ **Confidence Distribution:**
191
+ - 🟢 HIGH: **{(df_forecasts['confidence_label'] == 'HIGH').sum()}** forecasts
192
+ - 🟡 MEDIUM: **{(df_forecasts['confidence_label'] == 'MEDIUM').sum()}** forecasts
193
+ - 🔴 LOW: **{(df_forecasts['confidence_label'] == 'LOW').sum()}** forecasts
194
+
195
+ **Scenarios Processed:** {', '.join(df_forecasts['scenario'].unique()[:5])}{'...' if df_forecasts['scenario'].nunique() > 5 else ''}
196
+ """
197
+
198
+ # Save to CSV for download
199
+ output_csv_path = f"forecasts_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
200
+ df_forecasts.to_csv(output_csv_path, index=False)
201
+
202
+ return df_forecasts, summary_text, output_csv_path
203
+
204
+ except Exception as e:
205
+ error_msg = f"❌ **Error**: {str(e)}"
206
+ return None, error_msg, None
207
+
208
+
209
+ # Create Gradio interface
210
+ with gr.Blocks(title="Demand Forecasting - HF Endpoint", theme=gr.themes.Soft()) as demo:
211
+ gr.Markdown(
212
+ """
213
+ # 📊 Demand Forecasting Application
214
+ Upload your historical demand CSV file and scenarios JSON to get forecasts for multiple scenarios.
215
+ """
216
+ )
217
+
218
+ with gr.Row():
219
+ with gr.Column(scale=1):
220
+ gr.Markdown("### 📁 Input Files")
221
+
222
+ csv_input = gr.File(
223
+ label="1️⃣ Upload CSV File (Historical Data)",
224
+ file_types=[".csv"]
225
+ )
226
+
227
+ scenarios_input = gr.File(
228
+ label="2️⃣ Upload Scenarios JSON File",
229
+ file_types=[".json"]
230
+ )
231
+
232
+ gr.Markdown(
233
+ """
234
+ **CSV Format:**
235
+ ```
236
+ month,product_id,market_id,units_sold,
237
+ planned_price_index,planned_discount_pct,
238
+ planned_promo_flag,planned_tender_flag,
239
+ planned_supply_risk,planned_competitor_pressure,
240
+ planned_regulatory_event_flag
241
+ ```
242
+ ⚠️ Last 6 rows: empty `units_sold` (forecast horizon)
243
+
244
+ **JSON Format:**
245
+ ```json
246
+ {
247
+ "base": {},
248
+ "price_up_10pct": {
249
+ "planned_price_index": 1.1
250
+ },
251
+ "discount_10pct": {
252
+ "planned_discount_pct": 10
253
+ }
254
+ }
255
+ ```
256
+ """
257
+ )
258
+
259
+ submit_btn = gr.Button("🚀 Generate Forecasts", variant="primary", size="lg")
260
+
261
+ with gr.Column(scale=2):
262
+ gr.Markdown("### 📈 Results")
263
+ summary_output = gr.Markdown(label="Summary")
264
+
265
+ with gr.Row():
266
+ forecast_table = gr.Dataframe(
267
+ label="Forecast Results",
268
+ interactive=False,
269
+ wrap=True
270
+ )
271
+
272
+ with gr.Row():
273
+ download_btn = gr.File(label="📥 Download Forecasts CSV")
274
+
275
+ gr.Markdown(
276
+ """
277
+ ---
278
+ ### 🔍 About
279
+ This application uses a private HuggingFace endpoint to generate demand forecasts for multiple scenarios.
280
+
281
+ **Common Scenarios:**
282
+ - 📈 Base scenario
283
+ - 💰 Price adjustments (±2%, ±5%, ±10%)
284
+ - 🏷️ Discount variations (5%, 10%, 20%)
285
+ - 🎁 Promotional scenarios
286
+ - ⚠️ Supply risk scenarios
287
+ - 🏆 Competitive pressure scenarios
288
+ - 🎯 Strategic scenarios (growth_push, margin_push, worst_case, etc.)
289
+
290
+ **Output Columns:**
291
+ - Point forecasts (mean) and prediction intervals (P10, P50, P90)
292
+ - Confidence scores and labels (HIGH/MEDIUM/LOW)
293
+ - Review flags for forecasts requiring attention
294
+ - All input parameters used for each scenario
295
+ """
296
+ )
297
+
298
+ # Connect the button to the processing function
299
+ submit_btn.click(
300
+ fn=process_forecast,
301
+ inputs=[csv_input, scenarios_input],
302
+ outputs=[forecast_table, summary_output, download_btn]
303
+ )
304
+
305
+ # Launch the app
306
+ if __name__ == "__main__":
307
+ demo.launch(share=False)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio>=4.0.0
2
+ pandas>=2.0.0
3
+ requests>=2.31.0