Files
2025-08-01 04:33:03 -04:00

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)