muddasser commited on
Commit
b079756
·
unverified ·
0 Parent(s):

Add files via upload

Browse files
Files changed (3) hide show
  1. README.md +110 -0
  2. app.py +220 -0
  3. requirements.txt +6 -0
README.md ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ✈️ Travel Demand Forecaster — Prophet + Streamlit
2
+
3
+ A time series forecasting app for travel demand using Facebook Prophet, deployed via CI/CD to Hugging Face Spaces.
4
+
5
+ ---
6
+
7
+ ## 🚀 CI/CD Pipeline
8
+
9
+ ```
10
+ Push code to GitHub
11
+
12
+ GitHub Actions triggers
13
+
14
+ 🧪 Run all tests (CI)
15
+
16
+ ✅ Tests pass?
17
+
18
+ 🤗 Auto deploy to Hugging Face (CD)
19
+
20
+ 🌐 App is live!
21
+ ```
22
+
23
+ ---
24
+
25
+ ## 🛠️ Setup Instructions
26
+
27
+ ### Step 1 — Fork or clone this repo
28
+ ```bash
29
+ git clone https://github.com/YOUR_USERNAME/travel-prophet.git
30
+ cd travel-prophet
31
+ ```
32
+
33
+ ### Step 2 — Create Hugging Face Space
34
+ 1. Go to https://huggingface.co/spaces
35
+ 2. Click **Create new Space**
36
+ 3. Choose **Streamlit** as SDK
37
+ 4. Name it e.g. `travel-prophet-forecaster`
38
+
39
+ ### Step 3 — Add HF Token to GitHub Secrets
40
+ 1. Go to https://huggingface.co/settings/tokens
41
+ 2. Create a new token with **write** access
42
+ 3. Go to your GitHub repo → **Settings** → **Secrets and variables** → **Actions**
43
+ 4. Click **New repository secret**
44
+ 5. Name: `HF_TOKEN`, Value: (paste your token)
45
+
46
+ ### Step 4 — Update deploy.yml
47
+ In `.github/workflows/deploy.yml`, replace:
48
+ ```
49
+ YOUR_HF_USERNAME → your Hugging Face username
50
+ YOUR_SPACE_NAME → your Space name
51
+ ```
52
+
53
+ ### Step 5 — Push to GitHub
54
+ ```bash
55
+ git add .
56
+ git commit -m "Initial commit"
57
+ git push origin main
58
+ ```
59
+
60
+ ### Step 6 — Watch it deploy! 🎉
61
+ Go to your GitHub repo → **Actions** tab to watch the pipeline run.
62
+
63
+ ---
64
+
65
+ ## 📁 Project Structure
66
+
67
+ ```
68
+ travel-prophet/
69
+ ├── app.py # Main Streamlit app
70
+ ├── requirements.txt # Dependencies
71
+ ├── tests/
72
+ │ └── test_app.py # All tests (CI runs these)
73
+ ├── .github/
74
+ │ └── workflows/
75
+ │ └── deploy.yml # CI/CD pipeline definition
76
+ └── README.md
77
+ ```
78
+
79
+ ---
80
+
81
+ ## 🧪 Run Tests Locally
82
+
83
+ ```bash
84
+ pip install -r requirements.txt
85
+ pip install pytest
86
+ pytest tests/ -v
87
+ ```
88
+
89
+ ---
90
+
91
+ ## 📊 Features
92
+
93
+ - Upload your own CSV travel data
94
+ - Sample data included for demo
95
+ - Adjustable forecast period (30–365 days)
96
+ - Yearly and weekly seasonality
97
+ - Confidence intervals
98
+ - Downloadable forecast CSV
99
+ - Interactive Plotly charts
100
+
101
+ ---
102
+
103
+ ## 🔄 How CI/CD Works
104
+
105
+ | Event | What Happens |
106
+ |-------|-------------|
107
+ | Push to `main` | Tests run → if pass → deploy to HF |
108
+ | Pull Request | Tests run only → no deployment |
109
+ | Tests fail | Pipeline stops → no deployment |
110
+ | Tests pass | Auto deploys to Hugging Face Spaces |
app.py ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import numpy as np
4
+ from prophet import Prophet
5
+ import plotly.graph_objects as go
6
+ from datetime import datetime, timedelta
7
+
8
+ # ======================================
9
+ # Page Config
10
+ # ======================================
11
+ st.set_page_config(
12
+ page_title="✈️ Travel Demand Forecaster",
13
+ page_icon="✈️",
14
+ layout="wide"
15
+ )
16
+
17
+ st.title("✈️ Travel Demand Forecaster")
18
+ st.markdown("**Powered by Facebook Prophet** — Upload your travel data or use sample data to forecast future demand.")
19
+
20
+ # ======================================
21
+ # Sidebar Controls
22
+ # ======================================
23
+ st.sidebar.header("⚙️ Forecast Settings")
24
+
25
+ forecast_days = st.sidebar.slider(
26
+ "Forecast Period (days)",
27
+ min_value=30,
28
+ max_value=365,
29
+ value=90,
30
+ step=30
31
+ )
32
+
33
+ seasonality_mode = st.sidebar.selectbox(
34
+ "Seasonality Mode",
35
+ ["multiplicative", "additive"],
36
+ index=0
37
+ )
38
+
39
+ yearly_seasonality = st.sidebar.checkbox("Yearly Seasonality", value=True)
40
+ weekly_seasonality = st.sidebar.checkbox("Weekly Seasonality", value=True)
41
+ daily_seasonality = st.sidebar.checkbox("Daily Seasonality", value=False)
42
+
43
+ # ======================================
44
+ # Data Input
45
+ # ======================================
46
+ st.subheader("📂 Data Input")
47
+
48
+ data_option = st.radio(
49
+ "Choose data source:",
50
+ ["Use Sample Travel Data", "Upload CSV File"],
51
+ horizontal=True
52
+ )
53
+
54
+ def generate_sample_data():
55
+ """Generate realistic travel demand sample data"""
56
+ np.random.seed(42)
57
+ dates = pd.date_range(start="2021-01-01", end="2023-12-31", freq="D")
58
+
59
+ # Base demand with trend
60
+ trend = np.linspace(100, 150, len(dates))
61
+
62
+ # Yearly seasonality (peak in summer and holidays)
63
+ yearly = 30 * np.sin(2 * np.pi * np.arange(len(dates)) / 365 - np.pi/2)
64
+
65
+ # Weekly seasonality (weekends higher)
66
+ weekly = 10 * np.sin(2 * np.pi * np.arange(len(dates)) / 7)
67
+
68
+ # Random noise
69
+ noise = np.random.normal(0, 8, len(dates))
70
+
71
+ demand = trend + yearly + weekly + noise
72
+ demand = np.maximum(demand, 10) # no negative demand
73
+
74
+ df = pd.DataFrame({"ds": dates, "y": demand.round(0)})
75
+ return df
76
+
77
+ if data_option == "Use Sample Travel Data":
78
+ df = generate_sample_data()
79
+ st.success("✅ Sample travel demand data loaded! (Jan 2021 — Dec 2023)")
80
+ st.dataframe(df.tail(10), use_container_width=True)
81
+
82
+ else:
83
+ uploaded_file = st.file_uploader(
84
+ "Upload CSV with 'ds' (date) and 'y' (value) columns",
85
+ type=["csv"]
86
+ )
87
+
88
+ if uploaded_file:
89
+ df = pd.read_csv(uploaded_file)
90
+ df['ds'] = pd.to_datetime(df['ds'])
91
+ st.success(f"✅ Loaded {len(df)} rows of data!")
92
+ st.dataframe(df.tail(10), use_container_width=True)
93
+ else:
94
+ st.info("👆 Please upload a CSV file with columns: **ds** (date) and **y** (value)")
95
+ st.stop()
96
+
97
+ # ======================================
98
+ # Train & Forecast
99
+ # ======================================
100
+ st.divider()
101
+ st.subheader("🔮 Forecast")
102
+
103
+ if st.button("🚀 Generate Forecast", type="primary"):
104
+ with st.spinner("Training Prophet model..."):
105
+
106
+ # Train model
107
+ model = Prophet(
108
+ seasonality_mode=seasonality_mode,
109
+ yearly_seasonality=yearly_seasonality,
110
+ weekly_seasonality=weekly_seasonality,
111
+ daily_seasonality=daily_seasonality
112
+ )
113
+ model.fit(df)
114
+
115
+ # Make future dataframe
116
+ future = model.make_future_dataframe(periods=forecast_days)
117
+ forecast = model.predict(future)
118
+
119
+ st.success(f"✅ Forecast generated for next {forecast_days} days!")
120
+
121
+ # ======================================
122
+ # Plot Results
123
+ # ======================================
124
+ col1, col2 = st.columns(2)
125
+
126
+ with col1:
127
+ st.metric(
128
+ "Average Forecasted Demand",
129
+ f"{forecast['yhat'].tail(forecast_days).mean():.0f}",
130
+ delta=f"+{forecast['yhat'].tail(forecast_days).mean() - df['y'].mean():.0f} vs historical"
131
+ )
132
+
133
+ with col2:
134
+ st.metric(
135
+ "Peak Forecasted Demand",
136
+ f"{forecast['yhat'].tail(forecast_days).max():.0f}",
137
+ delta="Next period peak"
138
+ )
139
+
140
+ # Main forecast plot
141
+ fig = go.Figure()
142
+
143
+ # Historical data
144
+ fig.add_trace(go.Scatter(
145
+ x=df['ds'], y=df['y'],
146
+ name='Historical',
147
+ mode='lines',
148
+ line=dict(color='#1f77b4', width=1.5)
149
+ ))
150
+
151
+ # Forecast
152
+ forecast_future = forecast[forecast['ds'] > df['ds'].max()]
153
+ fig.add_trace(go.Scatter(
154
+ x=forecast_future['ds'], y=forecast_future['yhat'],
155
+ name='Forecast',
156
+ mode='lines',
157
+ line=dict(color='#ff7f0e', width=2, dash='dash')
158
+ ))
159
+
160
+ # Confidence interval
161
+ fig.add_trace(go.Scatter(
162
+ x=pd.concat([forecast_future['ds'], forecast_future['ds'][::-1]]),
163
+ y=pd.concat([forecast_future['yhat_upper'], forecast_future['yhat_lower'][::-1]]),
164
+ fill='toself',
165
+ fillcolor='rgba(255,127,14,0.2)',
166
+ line=dict(color='rgba(255,255,255,0)'),
167
+ name='Confidence Interval'
168
+ ))
169
+
170
+ fig.update_layout(
171
+ title="Travel Demand Forecast",
172
+ xaxis_title="Date",
173
+ yaxis_title="Demand",
174
+ hovermode='x unified',
175
+ height=450
176
+ )
177
+
178
+ st.plotly_chart(fig, use_container_width=True)
179
+
180
+ # Components plot
181
+ st.subheader("📊 Forecast Components")
182
+
183
+ col1, col2 = st.columns(2)
184
+
185
+ with col1:
186
+ # Trend
187
+ fig_trend = go.Figure()
188
+ fig_trend.add_trace(go.Scatter(
189
+ x=forecast['ds'], y=forecast['trend'],
190
+ mode='lines', line=dict(color='green', width=2)
191
+ ))
192
+ fig_trend.update_layout(title="Trend", height=300)
193
+ st.plotly_chart(fig_trend, use_container_width=True)
194
+
195
+ with col2:
196
+ # Yearly seasonality
197
+ if yearly_seasonality:
198
+ fig_yearly = go.Figure()
199
+ fig_yearly.add_trace(go.Scatter(
200
+ x=forecast['ds'], y=forecast['yearly'],
201
+ mode='lines', line=dict(color='purple', width=2)
202
+ ))
203
+ fig_yearly.update_layout(title="Yearly Seasonality", height=300)
204
+ st.plotly_chart(fig_yearly, use_container_width=True)
205
+
206
+ # Forecast table
207
+ st.subheader("📋 Forecast Data")
208
+ forecast_display = forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail(forecast_days)
209
+ forecast_display.columns = ['Date', 'Forecast', 'Lower Bound', 'Upper Bound']
210
+ forecast_display = forecast_display.round(2)
211
+ st.dataframe(forecast_display, use_container_width=True)
212
+
213
+ # Download button
214
+ csv = forecast_display.to_csv(index=False)
215
+ st.download_button(
216
+ label="📥 Download Forecast CSV",
217
+ data=csv,
218
+ file_name="travel_forecast.csv",
219
+ mime="text/csv"
220
+ )
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ streamlit==1.32.0
2
+ prophet==1.1.5
3
+ pandas==2.0.3
4
+ numpy==1.24.3
5
+ plotly==5.18.0
6
+ pystan==3.7.0