203 lines
8.8 KiB
Python
Executable File
203 lines
8.8 KiB
Python
Executable File
from flask import Flask, request, render_template, session
|
|
from werkzeug.utils import secure_filename
|
|
from models.time_series import process_time_series
|
|
from models.plotting import create_comparison_plot
|
|
from utils.file_handling import allowed_file, read_file, save_processed_file
|
|
from utils.forecast_history import update_forecast_history, download_forecast_history
|
|
import os
|
|
|
|
app = Flask(__name__)
|
|
app.config['UPLOAD_FOLDER'] = 'Uploads'
|
|
app.config['ALLOWED_EXTENSIONS'] = {'csv', 'xls', 'xlsx'}
|
|
app.secret_key = 'your-secret-key' # Required for session management
|
|
|
|
# Ensure upload folder exists
|
|
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
|
|
|
|
|
@app.route('/')
|
|
def index():
|
|
return render_template('index.html')
|
|
|
|
|
|
@app.route('/upload', methods=['POST'])
|
|
def upload_file():
|
|
if 'file' not in request.files:
|
|
return render_template('index.html', error='No file part')
|
|
|
|
file = request.files['file']
|
|
if file.filename == '':
|
|
return render_template('index.html', error='No selected file')
|
|
|
|
if file and allowed_file(file.filename):
|
|
filename = secure_filename(file.filename)
|
|
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
|
file.save(filepath)
|
|
session['filepath'] = filepath # Store filepath in session
|
|
session['forecast_history'] = [] # Initialize forecast history
|
|
session['selected_indices'] = [] # Initialize selected indices
|
|
|
|
# Get user selections
|
|
do_decomposition = 'decomposition' in request.form
|
|
do_forecasting = 'forecasting' in request.form
|
|
do_acf_pacf = 'acf_pacf' in request.form
|
|
train_percent = float(request.form.get('train_percent', 80)) / 100
|
|
test_percent = float(request.form.get('test_percent', 20)) / 100
|
|
forecast_periods = int(request.form.get('forecast_periods', 12))
|
|
model_type = request.form.get('model_type', 'ARIMA')
|
|
|
|
# Validate train/test percentages
|
|
if abs(train_percent + test_percent - 1.0) > 0.01: # Allow small float precision errors
|
|
return render_template('index.html', error='Train and test percentages must sum to 100%')
|
|
|
|
session['do_decomposition'] = do_decomposition
|
|
session['do_forecasting'] = do_forecasting
|
|
session['do_acf_pacf'] = do_acf_pacf
|
|
session['train_percent'] = train_percent
|
|
session['test_percent'] = test_percent
|
|
session['forecast_periods'] = forecast_periods
|
|
session['model_type'] = model_type
|
|
|
|
result = process_time_series(filepath, do_decomposition, do_forecasting, do_acf_pacf, train_percent,
|
|
forecast_periods, model_type)
|
|
|
|
if 'error' in result:
|
|
return render_template('index.html', error=result['error'])
|
|
|
|
# Update forecast history if unique
|
|
if do_forecasting and result['metrics']:
|
|
update_forecast_history(session, train_percent, test_percent, forecast_periods, model_type,
|
|
result['metrics'])
|
|
|
|
return render_template('results.html',
|
|
do_decomposition=do_decomposition,
|
|
do_forecasting=do_forecasting,
|
|
do_acf_pacf=do_acf_pacf,
|
|
train_percent=train_percent * 100,
|
|
test_percent=test_percent * 100,
|
|
forecast_periods=forecast_periods,
|
|
forecast_history=session['forecast_history'],
|
|
selected_indices=session['selected_indices'],
|
|
**result)
|
|
|
|
|
|
@app.route('/reforecast', methods=['POST'])
|
|
def reforecast():
|
|
filepath = session.get('filepath')
|
|
if not filepath or not os.path.exists(filepath):
|
|
return render_template('index.html', error='Session expired or file not found. Please upload the file again.')
|
|
|
|
# Get user selections from reforecast form
|
|
train_percent = float(request.form.get('train_percent', 80)) / 100
|
|
test_percent = float(request.form.get('test_percent', 20)) / 100
|
|
forecast_periods = int(request.form.get('forecast_periods', 12))
|
|
model_type = request.form.get('model_type', 'ARIMA')
|
|
add_to_existing = 'add_to_existing' in request.form
|
|
|
|
# Validate train/test percentages
|
|
if abs(train_percent + test_percent - 1.0) > 0.01: # Allow small float precision errors
|
|
return render_template('index.html', error='Train and test percentages must sum to 100%')
|
|
|
|
# Get original selections from session or defaults
|
|
do_decomposition = session.get('do_decomposition', False)
|
|
do_forecasting = True # Since this is a reforecast
|
|
do_acf_pacf = session.get('do_acf_pacf', False)
|
|
|
|
result = process_time_series(filepath, do_decomposition, do_forecasting, do_acf_pacf, train_percent,
|
|
forecast_periods, model_type)
|
|
|
|
if 'error' in result:
|
|
return render_template('index.html', error=result['error'])
|
|
|
|
# Update forecast history if unique
|
|
if do_forecasting and result['metrics']:
|
|
update_forecast_history(session, train_percent, test_percent, forecast_periods, model_type, result['metrics'],
|
|
add_to_existing)
|
|
|
|
# Update session with current parameters
|
|
session['train_percent'] = train_percent
|
|
session['test_percent'] = test_percent
|
|
session['forecast_periods'] = forecast_periods
|
|
session['model_type'] = model_type
|
|
|
|
# Generate comparison plot if multiple forecasts are selected
|
|
if len(session.get('selected_indices', [])) > 1:
|
|
result['forecast_html'] = create_comparison_plot(filepath, session['forecast_history'],
|
|
session['selected_indices'])
|
|
|
|
return render_template('results.html',
|
|
do_decomposition=do_decomposition,
|
|
do_forecasting=do_forecasting,
|
|
do_acf_pacf=do_acf_pacf,
|
|
train_percent=train_percent * 100,
|
|
test_percent=test_percent * 100,
|
|
forecast_periods=forecast_periods,
|
|
forecast_history=session['forecast_history'],
|
|
selected_indices=session['selected_indices'],
|
|
scroll_to_forecast=True,
|
|
**result)
|
|
|
|
|
|
@app.route('/compare_forecasts', methods=['POST'])
|
|
def compare_forecasts():
|
|
filepath = session.get('filepath')
|
|
if not filepath or not os.path.exists(filepath):
|
|
return render_template('index.html', error='Session expired or file not found. Please upload the file again.')
|
|
|
|
# Get selected forecast indices
|
|
selected_indices = [int(idx) for idx in request.form.getlist('selected_forecasts')]
|
|
if not selected_indices:
|
|
return render_template('index.html', error='No forecasts selected for comparison')
|
|
|
|
# Update session with selected indices
|
|
session['selected_indices'] = selected_indices
|
|
session.modified = True
|
|
|
|
# Get current parameters and settings
|
|
do_decomposition = session.get('do_decomposition', False)
|
|
do_forecasting = session.get('do_forecasting', True)
|
|
do_acf_pacf = session.get('do_acf_pacf', False)
|
|
train_percent = session.get('train_percent', 0.8)
|
|
test_percent = session.get('test_percent', 0.2)
|
|
forecast_periods = session.get('forecast_periods', 12)
|
|
model_type = session.get('model_type', 'ARIMA')
|
|
|
|
# Generate comparison plot
|
|
forecast_html = create_comparison_plot(filepath, session['forecast_history'], selected_indices)
|
|
|
|
# Re-run the current forecast to maintain other results
|
|
result = process_time_series(filepath, do_decomposition, do_forecasting, do_acf_pacf, train_percent,
|
|
forecast_periods, model_type)
|
|
|
|
if 'error' in result:
|
|
return render_template('index.html', error=result['error'])
|
|
|
|
result['forecast_html'] = forecast_html
|
|
|
|
return render_template('results.html',
|
|
do_decomposition=do_decomposition,
|
|
do_forecasting=do_forecasting,
|
|
do_acf_pacf=do_acf_pacf,
|
|
train_percent=train_percent * 100,
|
|
test_percent=test_percent * 100,
|
|
forecast_periods=forecast_periods,
|
|
forecast_history=session['forecast_history'],
|
|
selected_indices=selected_indices,
|
|
scroll_to_forecast=True,
|
|
**result)
|
|
|
|
|
|
@app.route('/download_forecast_history')
|
|
def download_forecast_history():
|
|
return download_forecast_history(session)
|
|
|
|
|
|
@app.route('/download/<filename>')
|
|
def download_file(filename):
|
|
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
|
return send_file(filepath, as_attachment=True)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
app.run(host='0.0.0.0', port=5000, debug=True)
|