diff --git a/data_comparator.py b/data_comparator.py index 968e200..cf9a37b 100644 --- a/data_comparator.py +++ b/data_comparator.py @@ -646,6 +646,33 @@ class KSTCoordiComparator: return visualize_rows + def generate_excel_export_data(self) -> Dict[str, List[Dict[str, Any]]]: + """Generate data for Excel export with all sheets in visualize format""" + export_data = {} + + # Get all sheet names + sheet_names = list(self.data.keys()) if self.data else [] + + for sheet_name in sheet_names: + # Generate visualize data for each sheet + sheet_visualize_data = self.generate_visualize_data(sheet_name) + + # Convert to Excel-friendly format + excel_rows = [] + for row in sheet_visualize_data: + excel_rows.append({ + 'Coordi Title': row.get('coordi_title', ''), + 'Coordi Chapter': row.get('coordi_chapter', ''), + 'KST Title': row.get('kst_title', ''), + 'KST Chapter': row.get('kst_chapter', ''), + 'Status': row.get('reason', ''), + 'Type': row.get('row_type', '').replace('_', ' ').title() + }) + + export_data[sheet_name] = excel_rows + + return export_data + def print_comparison_summary(self, sheet_filter: str | None = None): """Print a formatted summary of the comparison for a specific sheet""" diff --git a/templates/index.html b/templates/index.html index 4c437d2..1c31b45 100644 --- a/templates/index.html +++ b/templates/index.html @@ -284,7 +284,10 @@
-

Data

+
+

Excel-like Visualization

+ +
@@ -670,6 +673,60 @@ }); } + function downloadExcel() { + const downloadBtn = document.getElementById('downloadBtn'); + const originalText = downloadBtn.textContent; + + // Show loading state + downloadBtn.disabled = true; + downloadBtn.textContent = '⏳ Generating...'; + downloadBtn.style.background = '#6c757d'; + + // Create a temporary link and trigger download + fetch('/download_excel', { + method: 'GET' + }) + .then(response => { + if (!response.ok) { + throw new Error('Download failed'); + } + return response.blob(); + }) + .then(blob => { + // Create download link + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + a.download = 'data_comparison_export.xlsx'; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + + // Show success message + const statusDiv = document.getElementById('status'); + statusDiv.innerHTML = '
Excel file downloaded successfully!
'; + setTimeout(() => { + statusDiv.innerHTML = ''; + }, 3000); + }) + .catch(error => { + console.error('Download error:', error); + const statusDiv = document.getElementById('status'); + statusDiv.innerHTML = '
Download failed. Please try again.
'; + setTimeout(() => { + statusDiv.innerHTML = ''; + }, 5000); + }) + .finally(() => { + // Reset button state + downloadBtn.disabled = false; + downloadBtn.textContent = originalText; + downloadBtn.style.background = '#28a745'; + }); + } + // Auto-analyze on page load with default file window.onload = function() { // Initialize sheet filter with loading state diff --git a/web_gui.py b/web_gui.py index 0e5a547..d4b9d01 100644 --- a/web_gui.py +++ b/web_gui.py @@ -1,7 +1,8 @@ -from flask import Flask, render_template, request, jsonify +from flask import Flask, render_template, request, jsonify, send_file import json import os import tempfile +import pandas as pd from pathlib import Path from werkzeug.utils import secure_filename from data_comparator import KSTCoordiComparator @@ -131,6 +132,99 @@ def get_sheets(): except Exception as e: return jsonify({'error': str(e)}), 500 +@app.route('/download_excel') +def download_excel(): + """Generate and download Excel file with all sheets in visualize format""" + global comparator_instance + + try: + if not comparator_instance: + return jsonify({'error': 'No data available. Please analyze data first.'}), 400 + + # Generate export data for all sheets + export_data = comparator_instance.generate_excel_export_data() + + if not export_data: + return jsonify({'error': 'No data available for export'}), 400 + + # Create temporary Excel file + temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.xlsx') + temp_path = temp_file.name + temp_file.close() + + try: + # Create Excel writer with multiple sheets + with pd.ExcelWriter(temp_path, engine='openpyxl') as writer: + for sheet_name, sheet_data in export_data.items(): + if sheet_data: # Only create sheet if there's data + df = pd.DataFrame(sheet_data) + + # Clean sheet name for Excel (remove invalid characters) + clean_sheet_name = str(sheet_name).replace('/', '_').replace('\\', '_')[:31] + + df.to_excel(writer, sheet_name=clean_sheet_name, index=False) + + # Get the workbook and worksheet to apply formatting + workbook = writer.book + worksheet = writer.sheets[clean_sheet_name] + + # Apply color formatting based on Type column + from openpyxl.styles import PatternFill + + # Define colors matching the web interface + colors = { + 'Coordi Only': PatternFill(start_color='FF4444', end_color='FF4444', fill_type='solid'), + 'Kst Only': PatternFill(start_color='4488FF', end_color='4488FF', fill_type='solid'), + 'Mixed Duplicate': PatternFill(start_color='FF8800', end_color='FF8800', fill_type='solid'), + 'Pure Duplicate': PatternFill(start_color='8844FF', end_color='8844FF', fill_type='solid'), + 'Matched': PatternFill(start_color='FFFFFF', end_color='FFFFFF', fill_type='solid') + } + + # Find the Type column (should be column F, index 5) + type_col_idx = None + for idx, col in enumerate(df.columns): + if col == 'Type': + type_col_idx = idx + 1 # Excel is 1-indexed + break + + # Apply formatting to data rows (skip header) + if type_col_idx: + for row_idx, row_data in enumerate(sheet_data, start=2): # Start from row 2 (after header) + row_type = row_data.get('Type', '') + fill = colors.get(row_type) + if fill: + for col_idx in range(1, len(df.columns) + 1): + cell = worksheet.cell(row=row_idx, column=col_idx) + cell.fill = fill + + # Auto-adjust column widths + for column in worksheet.columns: + max_length = 0 + column_letter = column[0].column_letter + for cell in column: + try: + if len(str(cell.value)) > max_length: + max_length = len(str(cell.value)) + except: + pass + adjusted_width = min(max_length + 2, 50) # Cap at 50 characters + worksheet.column_dimensions[column_letter].width = adjusted_width + + # Send file for download + return send_file( + temp_path, + as_attachment=True, + download_name='data_comparison_export.xlsx', + mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ) + + finally: + # Clean up temporary file after a delay (Flask handles this) + pass + + except Exception as e: + return jsonify({'error': f'Export failed: {str(e)}'}), 500 + def create_templates_dir(): """Create templates directory and HTML file""" templates_dir = Path('templates') @@ -422,7 +516,10 @@ def create_templates_dir():
-

Data

+
+

Excel-like Visualization

+ +
@@ -808,6 +905,60 @@ def create_templates_dir(): }); } + function downloadExcel() { + const downloadBtn = document.getElementById('downloadBtn'); + const originalText = downloadBtn.textContent; + + // Show loading state + downloadBtn.disabled = true; + downloadBtn.textContent = '⏳ Generating...'; + downloadBtn.style.background = '#6c757d'; + + // Create a temporary link and trigger download + fetch('/download_excel', { + method: 'GET' + }) + .then(response => { + if (!response.ok) { + throw new Error('Download failed'); + } + return response.blob(); + }) + .then(blob => { + // Create download link + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + a.download = 'data_comparison_export.xlsx'; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + + // Show success message + const statusDiv = document.getElementById('status'); + statusDiv.innerHTML = '
Excel file downloaded successfully!
'; + setTimeout(() => { + statusDiv.innerHTML = ''; + }, 3000); + }) + .catch(error => { + console.error('Download error:', error); + const statusDiv = document.getElementById('status'); + statusDiv.innerHTML = '
Download failed. Please try again.
'; + setTimeout(() => { + statusDiv.innerHTML = ''; + }, 5000); + }) + .finally(() => { + // Reset button state + downloadBtn.disabled = false; + downloadBtn.textContent = originalText; + downloadBtn.style.background = '#28a745'; + }); + } + // Auto-analyze on page load with default file window.onload = function() { // Initialize sheet filter with loading state