add excel file export #3
@ -646,6 +646,33 @@ class KSTCoordiComparator:
|
|||||||
|
|
||||||
return visualize_rows
|
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):
|
def print_comparison_summary(self, sheet_filter: str | None = None):
|
||||||
"""Print a formatted summary of the comparison for a specific sheet"""
|
"""Print a formatted summary of the comparison for a specific sheet"""
|
||||||
|
|||||||
@ -284,7 +284,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="visualize" class="tab-content">
|
<div id="visualize" class="tab-content">
|
||||||
<h3>Data </h3>
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
||||||
|
<h3>Excel-like Visualization</h3>
|
||||||
|
<button onclick="downloadExcel()" id="downloadBtn" style="background: #28a745; padding: 8px 16px;">📥 Download All Sheets</button>
|
||||||
|
</div>
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<table id="visualize-table">
|
<table id="visualize-table">
|
||||||
<thead>
|
<thead>
|
||||||
@ -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 = '<div class="success">Excel file downloaded successfully!</div>';
|
||||||
|
setTimeout(() => {
|
||||||
|
statusDiv.innerHTML = '';
|
||||||
|
}, 3000);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Download error:', error);
|
||||||
|
const statusDiv = document.getElementById('status');
|
||||||
|
statusDiv.innerHTML = '<div class="error">Download failed. Please try again.</div>';
|
||||||
|
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
|
// Auto-analyze on page load with default file
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
// Initialize sheet filter with loading state
|
// Initialize sheet filter with loading state
|
||||||
|
|||||||
155
web_gui.py
155
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 json
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import pandas as pd
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from data_comparator import KSTCoordiComparator
|
from data_comparator import KSTCoordiComparator
|
||||||
@ -131,6 +132,99 @@ def get_sheets():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': str(e)}), 500
|
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():
|
def create_templates_dir():
|
||||||
"""Create templates directory and HTML file"""
|
"""Create templates directory and HTML file"""
|
||||||
templates_dir = Path('templates')
|
templates_dir = Path('templates')
|
||||||
@ -422,7 +516,10 @@ def create_templates_dir():
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="visualize" class="tab-content">
|
<div id="visualize" class="tab-content">
|
||||||
<h3>Data </h3>
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
||||||
|
<h3>Excel-like Visualization</h3>
|
||||||
|
<button onclick="downloadExcel()" id="downloadBtn" style="background: #28a745; padding: 8px 16px;">📥 Download All Sheets</button>
|
||||||
|
</div>
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<table id="visualize-table">
|
<table id="visualize-table">
|
||||||
<thead>
|
<thead>
|
||||||
@ -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 = '<div class="success">Excel file downloaded successfully!</div>';
|
||||||
|
setTimeout(() => {
|
||||||
|
statusDiv.innerHTML = '';
|
||||||
|
}, 3000);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Download error:', error);
|
||||||
|
const statusDiv = document.getElementById('status');
|
||||||
|
statusDiv.innerHTML = '<div class="error">Download failed. Please try again.</div>';
|
||||||
|
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
|
// Auto-analyze on page load with default file
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
// Initialize sheet filter with loading state
|
// Initialize sheet filter with loading state
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user