Unverified Commit a86ab590 authored by Utorque's avatar Utorque Committed by GitHub
Browse files

Merge pull request #3 from Utorque/claude/horloml-web-app-011CUbWgr7HjWM2LFKdsZCmc

Add Excel download feature to historical data page
parents 9d66f4be 47bf74c7
Loading
Loading
Loading
Loading
+196 −2
Original line number Diff line number Diff line
@@ -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'
@@ -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'])
+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
+36 −1
Original line number Diff line number Diff line
@@ -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>