Loading forecast_app/app.py +196 −2 Original line number Diff line number Diff line Loading @@ -5,10 +5,17 @@ Students analyze 10 years of historical data and predict year 11. Results show financial impact of their predictions. """ from flask import Flask, render_template, request, session, jsonify, redirect, url_for from flask import Flask, render_template, request, session, jsonify, redirect, url_for, send_file import json import os from datetime import datetime from io import BytesIO try: from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment EXCEL_AVAILABLE = True except ImportError: EXCEL_AVAILABLE = False app = Flask(__name__) app.secret_key = 'supply-chain-forecast-secret-key-2024' Loading Loading @@ -244,7 +251,194 @@ def historical(): return render_template('historical.html', watches=watches, historical_data=historical_data, yearly_summary=yearly_summary) yearly_summary=yearly_summary, excel_available=EXCEL_AVAILABLE) @app.route('/download_excel') def download_excel(): """Generate and download Excel file with historical data""" if not EXCEL_AVAILABLE: return "Excel export not available. Please install openpyxl.", 500 training_data = load_training_data() watches = training_data['metadata']['watches'] historical_data = training_data['historical_data'] # Create workbook wb = Workbook() # Remove default sheet wb.remove(wb.active) # Create Summary sheet ws_summary = wb.create_sheet("Summary") # Header style header_fill = PatternFill(start_color="667eea", end_color="667eea", fill_type="solid") header_font = Font(bold=True, color="FFFFFF") # Summary sheet headers ws_summary['A1'] = "HorloML Historical Data Summary" ws_summary['A1'].font = Font(bold=True, size=14) ws_summary.merge_cells('A1:D1') ws_summary['A3'] = "Watch Models" ws_summary['A3'].font = header_font ws_summary['A3'].fill = header_fill # Watch info ws_summary['A4'] = "ID" ws_summary['B4'] = "Name" ws_summary['C4'] = "Category" ws_summary['D4'] = "Retail Price" for cell in ['A4', 'B4', 'C4', 'D4']: ws_summary[cell].font = Font(bold=True) ws_summary[cell].fill = PatternFill(start_color="E0E0E0", end_color="E0E0E0", fill_type="solid") for idx, watch in enumerate(watches, start=5): ws_summary[f'A{idx}'] = watch['id'] ws_summary[f'B{idx}'] = watch['name'] ws_summary[f'C{idx}'] = watch['category'] ws_summary[f'D{idx}'] = f"${watch['sell_price']}" # Adjust column widths ws_summary.column_dimensions['A'].width = 8 ws_summary.column_dimensions['B'].width = 20 ws_summary.column_dimensions['C'].width = 15 ws_summary.column_dimensions['D'].width = 15 # Create Monthly Data sheet ws_monthly = wb.create_sheet("Monthly Data") # Headers headers = ['Year', 'Month', 'Date'] for watch in watches: headers.extend([ f"{watch['name']} - Demand", f"{watch['name']} - Revenue", f"{watch['name']} - Profit" ]) for col_idx, header in enumerate(headers, start=1): cell = ws_monthly.cell(row=1, column=col_idx, value=header) cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal='center') # Data rows for row_idx, month_data in enumerate(historical_data, start=2): ws_monthly.cell(row=row_idx, column=1, value=month_data['year']) ws_monthly.cell(row=row_idx, column=2, value=month_data['month']) ws_monthly.cell(row=row_idx, column=3, value=month_data['date']) col_idx = 4 for watch_data in month_data['watches']: ws_monthly.cell(row=row_idx, column=col_idx, value=watch_data['demand']) ws_monthly.cell(row=row_idx, column=col_idx + 1, value=watch_data['revenue']) ws_monthly.cell(row=row_idx, column=col_idx + 2, value=watch_data['profit']) col_idx += 3 # Adjust column widths ws_monthly.column_dimensions['A'].width = 8 ws_monthly.column_dimensions['B'].width = 8 ws_monthly.column_dimensions['C'].width = 12 for col in range(4, len(headers) + 1): ws_monthly.column_dimensions[ws_monthly.cell(row=1, column=col).column_letter].width = 18 # Create Yearly Summary sheet ws_yearly = wb.create_sheet("Yearly Summary") # Headers yearly_headers = ['Year'] for watch in watches: yearly_headers.extend([ f"{watch['name']} - Total Demand", f"{watch['name']} - Total Revenue", f"{watch['name']} - Total Profit" ]) for col_idx, header in enumerate(yearly_headers, start=1): cell = ws_yearly.cell(row=1, column=col_idx, value=header) cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal='center') # Aggregate by year yearly_data = {} for month_data in historical_data: year = month_data['year'] if year not in yearly_data: yearly_data[year] = {watch['id']: {'demand': 0, 'revenue': 0, 'profit': 0} for watch in watches} for watch_data in month_data['watches']: watch_id = watch_data['watch_id'] yearly_data[year][watch_id]['demand'] += watch_data['demand'] yearly_data[year][watch_id]['revenue'] += watch_data['revenue'] yearly_data[year][watch_id]['profit'] += watch_data['profit'] # Write yearly data for row_idx, (year, data) in enumerate(sorted(yearly_data.items()), start=2): ws_yearly.cell(row=row_idx, column=1, value=year) col_idx = 2 for watch in watches: ws_yearly.cell(row=row_idx, column=col_idx, value=data[watch['id']]['demand']) ws_yearly.cell(row=row_idx, column=col_idx + 1, value=data[watch['id']]['revenue']) ws_yearly.cell(row=row_idx, column=col_idx + 2, value=data[watch['id']]['profit']) col_idx += 3 # Adjust column widths ws_yearly.column_dimensions['A'].width = 8 for col in range(2, len(yearly_headers) + 1): ws_yearly.column_dimensions[ws_yearly.cell(row=1, column=col).column_letter].width = 20 # Create Analysis Template sheet ws_analysis = wb.create_sheet("Analysis Template") ws_analysis['A1'] = "Year 11 Prediction Worksheet" ws_analysis['A1'].font = Font(bold=True, size=14) ws_analysis.merge_cells('A1:D1') ws_analysis['A3'] = "Use this sheet to make your predictions for Year 11" ws_analysis['A3'].font = Font(italic=True) ws_analysis.merge_cells('A3:D3') ws_analysis['A5'] = "Month" ws_analysis['A5'].font = Font(bold=True) ws_analysis['A5'].fill = header_fill for col_idx, watch in enumerate(watches, start=2): cell = ws_analysis.cell(row=5, column=col_idx, value=f"{watch['name']} Prediction") cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal='center') # Add months months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] for row_idx, month in enumerate(months, start=6): ws_analysis.cell(row=row_idx, column=1, value=month) # Adjust column widths ws_analysis.column_dimensions['A'].width = 12 for col in range(2, len(watches) + 2): ws_analysis.column_dimensions[ws_analysis.cell(row=5, column=col).column_letter].width = 25 # Save to BytesIO output = BytesIO() wb.save(output) output.seek(0) # Generate filename with current date filename = f"HorloML_Historical_Data_{datetime.now().strftime('%Y%m%d')}.xlsx" return send_file( output, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', as_attachment=True, download_name=filename ) @app.route('/predict', methods=['GET', 'POST']) Loading forecast_app/requirements.txt +1 −0 Original line number Diff line number Diff line Flask==3.0.0 numpy==2.3.4 Flask-Session==0.5.0 openpyxl==3.1.2 forecast_app/templates/historical.html +36 −1 Original line number Diff line number Diff line Loading @@ -14,9 +14,44 @@ <section class="section"> <div class="container"> <!-- Download Button --> <div class="columns"> <div class="column is-12"> {% if excel_available %} <a href="{{ url_for('download_excel') }}" class="button is-success is-medium is-pulled-right"> <span class="icon"> <i class="fas fa-file-excel"></i> </span> <span>Download as Excel</span> </a> {% else %} <button class="button is-success is-medium is-pulled-right" disabled title="Excel export not available"> <span class="icon"> <i class="fas fa-file-excel"></i> </span> <span>Download as Excel</span> </button> {% endif %} <div style="clear: both;"></div> </div> </div> <!-- Yearly Summary --> <div class="box"> <div class="level"> <div class="level-left"> <div class="level-item"> <h3 class="title is-4">Yearly Summary</h3> </div> </div> <div class="level-right"> <div class="level-item"> <div class="notification is-info is-light"> <strong>Tip:</strong> Download the Excel file to perform your own analysis! </div> </div> </div> </div> <div class="table-container"> <table class="table is-fullwidth is-striped"> <thead> Loading Loading
forecast_app/app.py +196 −2 Original line number Diff line number Diff line Loading @@ -5,10 +5,17 @@ Students analyze 10 years of historical data and predict year 11. Results show financial impact of their predictions. """ from flask import Flask, render_template, request, session, jsonify, redirect, url_for from flask import Flask, render_template, request, session, jsonify, redirect, url_for, send_file import json import os from datetime import datetime from io import BytesIO try: from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment EXCEL_AVAILABLE = True except ImportError: EXCEL_AVAILABLE = False app = Flask(__name__) app.secret_key = 'supply-chain-forecast-secret-key-2024' Loading Loading @@ -244,7 +251,194 @@ def historical(): return render_template('historical.html', watches=watches, historical_data=historical_data, yearly_summary=yearly_summary) yearly_summary=yearly_summary, excel_available=EXCEL_AVAILABLE) @app.route('/download_excel') def download_excel(): """Generate and download Excel file with historical data""" if not EXCEL_AVAILABLE: return "Excel export not available. Please install openpyxl.", 500 training_data = load_training_data() watches = training_data['metadata']['watches'] historical_data = training_data['historical_data'] # Create workbook wb = Workbook() # Remove default sheet wb.remove(wb.active) # Create Summary sheet ws_summary = wb.create_sheet("Summary") # Header style header_fill = PatternFill(start_color="667eea", end_color="667eea", fill_type="solid") header_font = Font(bold=True, color="FFFFFF") # Summary sheet headers ws_summary['A1'] = "HorloML Historical Data Summary" ws_summary['A1'].font = Font(bold=True, size=14) ws_summary.merge_cells('A1:D1') ws_summary['A3'] = "Watch Models" ws_summary['A3'].font = header_font ws_summary['A3'].fill = header_fill # Watch info ws_summary['A4'] = "ID" ws_summary['B4'] = "Name" ws_summary['C4'] = "Category" ws_summary['D4'] = "Retail Price" for cell in ['A4', 'B4', 'C4', 'D4']: ws_summary[cell].font = Font(bold=True) ws_summary[cell].fill = PatternFill(start_color="E0E0E0", end_color="E0E0E0", fill_type="solid") for idx, watch in enumerate(watches, start=5): ws_summary[f'A{idx}'] = watch['id'] ws_summary[f'B{idx}'] = watch['name'] ws_summary[f'C{idx}'] = watch['category'] ws_summary[f'D{idx}'] = f"${watch['sell_price']}" # Adjust column widths ws_summary.column_dimensions['A'].width = 8 ws_summary.column_dimensions['B'].width = 20 ws_summary.column_dimensions['C'].width = 15 ws_summary.column_dimensions['D'].width = 15 # Create Monthly Data sheet ws_monthly = wb.create_sheet("Monthly Data") # Headers headers = ['Year', 'Month', 'Date'] for watch in watches: headers.extend([ f"{watch['name']} - Demand", f"{watch['name']} - Revenue", f"{watch['name']} - Profit" ]) for col_idx, header in enumerate(headers, start=1): cell = ws_monthly.cell(row=1, column=col_idx, value=header) cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal='center') # Data rows for row_idx, month_data in enumerate(historical_data, start=2): ws_monthly.cell(row=row_idx, column=1, value=month_data['year']) ws_monthly.cell(row=row_idx, column=2, value=month_data['month']) ws_monthly.cell(row=row_idx, column=3, value=month_data['date']) col_idx = 4 for watch_data in month_data['watches']: ws_monthly.cell(row=row_idx, column=col_idx, value=watch_data['demand']) ws_monthly.cell(row=row_idx, column=col_idx + 1, value=watch_data['revenue']) ws_monthly.cell(row=row_idx, column=col_idx + 2, value=watch_data['profit']) col_idx += 3 # Adjust column widths ws_monthly.column_dimensions['A'].width = 8 ws_monthly.column_dimensions['B'].width = 8 ws_monthly.column_dimensions['C'].width = 12 for col in range(4, len(headers) + 1): ws_monthly.column_dimensions[ws_monthly.cell(row=1, column=col).column_letter].width = 18 # Create Yearly Summary sheet ws_yearly = wb.create_sheet("Yearly Summary") # Headers yearly_headers = ['Year'] for watch in watches: yearly_headers.extend([ f"{watch['name']} - Total Demand", f"{watch['name']} - Total Revenue", f"{watch['name']} - Total Profit" ]) for col_idx, header in enumerate(yearly_headers, start=1): cell = ws_yearly.cell(row=1, column=col_idx, value=header) cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal='center') # Aggregate by year yearly_data = {} for month_data in historical_data: year = month_data['year'] if year not in yearly_data: yearly_data[year] = {watch['id']: {'demand': 0, 'revenue': 0, 'profit': 0} for watch in watches} for watch_data in month_data['watches']: watch_id = watch_data['watch_id'] yearly_data[year][watch_id]['demand'] += watch_data['demand'] yearly_data[year][watch_id]['revenue'] += watch_data['revenue'] yearly_data[year][watch_id]['profit'] += watch_data['profit'] # Write yearly data for row_idx, (year, data) in enumerate(sorted(yearly_data.items()), start=2): ws_yearly.cell(row=row_idx, column=1, value=year) col_idx = 2 for watch in watches: ws_yearly.cell(row=row_idx, column=col_idx, value=data[watch['id']]['demand']) ws_yearly.cell(row=row_idx, column=col_idx + 1, value=data[watch['id']]['revenue']) ws_yearly.cell(row=row_idx, column=col_idx + 2, value=data[watch['id']]['profit']) col_idx += 3 # Adjust column widths ws_yearly.column_dimensions['A'].width = 8 for col in range(2, len(yearly_headers) + 1): ws_yearly.column_dimensions[ws_yearly.cell(row=1, column=col).column_letter].width = 20 # Create Analysis Template sheet ws_analysis = wb.create_sheet("Analysis Template") ws_analysis['A1'] = "Year 11 Prediction Worksheet" ws_analysis['A1'].font = Font(bold=True, size=14) ws_analysis.merge_cells('A1:D1') ws_analysis['A3'] = "Use this sheet to make your predictions for Year 11" ws_analysis['A3'].font = Font(italic=True) ws_analysis.merge_cells('A3:D3') ws_analysis['A5'] = "Month" ws_analysis['A5'].font = Font(bold=True) ws_analysis['A5'].fill = header_fill for col_idx, watch in enumerate(watches, start=2): cell = ws_analysis.cell(row=5, column=col_idx, value=f"{watch['name']} Prediction") cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal='center') # Add months months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] for row_idx, month in enumerate(months, start=6): ws_analysis.cell(row=row_idx, column=1, value=month) # Adjust column widths ws_analysis.column_dimensions['A'].width = 12 for col in range(2, len(watches) + 2): ws_analysis.column_dimensions[ws_analysis.cell(row=5, column=col).column_letter].width = 25 # Save to BytesIO output = BytesIO() wb.save(output) output.seek(0) # Generate filename with current date filename = f"HorloML_Historical_Data_{datetime.now().strftime('%Y%m%d')}.xlsx" return send_file( output, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', as_attachment=True, download_name=filename ) @app.route('/predict', methods=['GET', 'POST']) Loading
forecast_app/requirements.txt +1 −0 Original line number Diff line number Diff line Flask==3.0.0 numpy==2.3.4 Flask-Session==0.5.0 openpyxl==3.1.2
forecast_app/templates/historical.html +36 −1 Original line number Diff line number Diff line Loading @@ -14,9 +14,44 @@ <section class="section"> <div class="container"> <!-- Download Button --> <div class="columns"> <div class="column is-12"> {% if excel_available %} <a href="{{ url_for('download_excel') }}" class="button is-success is-medium is-pulled-right"> <span class="icon"> <i class="fas fa-file-excel"></i> </span> <span>Download as Excel</span> </a> {% else %} <button class="button is-success is-medium is-pulled-right" disabled title="Excel export not available"> <span class="icon"> <i class="fas fa-file-excel"></i> </span> <span>Download as Excel</span> </button> {% endif %} <div style="clear: both;"></div> </div> </div> <!-- Yearly Summary --> <div class="box"> <div class="level"> <div class="level-left"> <div class="level-item"> <h3 class="title is-4">Yearly Summary</h3> </div> </div> <div class="level-right"> <div class="level-item"> <div class="notification is-info is-light"> <strong>Tip:</strong> Download the Excel file to perform your own analysis! </div> </div> </div> </div> <div class="table-container"> <table class="table is-fullwidth is-striped"> <thead> Loading