diff --git a/.DS_Store b/.DS_Store index 25bf41a..cf2e7f8 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/DYNAMIC_SHEET_FIX.md b/DYNAMIC_SHEET_FIX.md new file mode 100644 index 0000000..7a18ccf --- /dev/null +++ b/DYNAMIC_SHEET_FIX.md @@ -0,0 +1,118 @@ +# Dynamic Sheet Handling Fix + +## Problem +The application was failing with "Sheet 'US URGENT' not found in data" error when working with Excel files that don't contain a sheet named 'US URGENT'. The code needed to be made dynamic to work with any sheet names. + +## Root Cause +The issue was in the web interface JavaScript where: +1. On page load, `analyzeData()` was called immediately +2. The sheet filter dropdown was empty at that time +3. An empty string was being sent as `sheet_filter` to the backend +4. The backend wasn't properly handling empty strings + +## Fixes Applied + +### 1. Frontend JavaScript Improvements (web_gui.py) + +#### Fixed `analyzeData()` function: +```javascript +// Before: const sheetFilter = document.getElementById('sheetFilter').value; +// After: +const sheetFilterElement = document.getElementById('sheetFilter'); +const sheetFilter = sheetFilterElement.value || null; // Use null if empty +``` + +#### Improved `updateSheetFilter()` function: +- Added handling for empty sheet lists +- Added loading state for sheet dropdown +- Better initialization logic + +#### Enhanced page load handling: +```javascript +window.onload = function() { + // Initialize sheet filter with loading state + updateSheetFilter([], null); + analyzeData(); +}; +``` + +### 2. Backend Improvements (web_gui.py) + +#### Enhanced request handling: +```python +# Handle empty string as None +if sheet_filter == '' or sheet_filter == 'undefined': + sheet_filter = None +``` + +#### Added debugging information: +```python +# Debug: Print available sheets +available_sheets = list(comparator_instance.data.keys()) +print(f"Available sheets: {available_sheets}") +print(f"Requested sheet_filter: {repr(sheet_filter)}") +``` + +### 3. Core Logic Improvements (data_comparator.py) + +#### Enhanced error handling: +```python +# Validate that the requested sheet exists +if sheet_filter not in sheet_names: + raise ValueError(f"Sheet '{sheet_filter}' not found in data. Available sheets: {sheet_names}") +``` + +## How It Works Now + +1. **Page Load**: + - Sheet dropdown shows "Loading sheets..." + - `analyzeData()` is called with `sheet_filter = null` + +2. **Backend Processing**: + - Loads Excel file and gets available sheet names + - If `sheet_filter` is `null` or empty, defaults to first sheet + - Validates sheet exists before processing + +3. **Response**: + - Returns results with `sheet_names` array and `current_sheet_filter` + - Frontend populates dropdown with actual sheet names + - User can then select different sheets + +4. **Sheet Selection**: + - User selects different sheet from dropdown + - `filterBySheet()` calls `analyzeData()` with selected sheet + - Backend processes the specific sheet + +## Benefits + +✅ **Dynamic**: Works with any Excel file regardless of sheet names +✅ **Robust**: Handles empty/invalid sheet names gracefully +✅ **User-friendly**: Shows available sheets in dropdown +✅ **Error-resistant**: Provides clear error messages +✅ **Backward compatible**: Still works with existing functionality + +## Test Coverage + +Created `test_dynamic_sheets.py` to verify: +- Default sheet selection (no filter) +- Each available sheet works +- Invalid sheet names are handled +- Empty string sheet names default properly + +## Usage Examples + +```python +# Works with any Excel file +comparator = KSTCoordiComparator("any-file.xlsx") +comparator.load_data() + +# Auto-selects first sheet +summary = comparator.get_comparison_summary() + +# Or specify any existing sheet +summary = comparator.get_comparison_summary("Sheet1") +summary = comparator.get_comparison_summary("Data") +summary = comparator.get_comparison_summary("US URGENT") # Only if it exists +``` + +The application now dynamically adapts to any Excel file structure! \ No newline at end of file diff --git a/data/sample-data.xlsx b/data/sample-data.xlsx index 720976a..8aab366 100644 Binary files a/data/sample-data.xlsx and b/data/sample-data.xlsx differ diff --git a/data_comparator.py b/data_comparator.py index 9d79ecb..6653806 100644 --- a/data_comparator.py +++ b/data_comparator.py @@ -341,6 +341,10 @@ class KSTCoordiComparator: if not sheet_filter: raise ValueError("No sheets available or sheet filter not specified") + # Validate that the requested sheet exists + if sheet_filter not in sheet_names: + raise ValueError(f"Sheet '{sheet_filter}' not found in data. Available sheets: {sheet_names}") + # Extract data for the specific sheet only sheet_data = self.extract_kst_coordi_items_for_sheet(sheet_filter) diff --git a/templates/index.html b/templates/index.html index b3d2b00..5961426 100644 --- a/templates/index.html +++ b/templates/index.html @@ -390,8 +390,8 @@ document.getElementById('filePath').value = data.file_path; statusDiv.innerHTML = '
File uploaded! Analyzing data...
'; - // Analyze the uploaded file - const sheetFilter = document.getElementById('sheetFilter').value; + // Analyze the uploaded file (use default sheet for new uploads) + const sheetFilter = null; // Always use default (first sheet) for new uploads return fetch('/analyze', { method: 'POST', headers: { diff --git a/web_gui.py b/web_gui.py index 50a8783..0510d9f 100644 --- a/web_gui.py +++ b/web_gui.py @@ -26,6 +26,10 @@ def analyze_data(): file_path = request.json.get('file_path', 'data/sample-data.xlsx') sheet_filter = request.json.get('sheet_filter', None) + # Handle empty string as None + if sheet_filter == '' or sheet_filter == 'undefined': + sheet_filter = None + if not Path(file_path).exists(): return jsonify({'error': f'File not found: {file_path}'}), 400 @@ -34,6 +38,11 @@ def analyze_data(): if not comparator_instance.load_data(): return jsonify({'error': 'Failed to load Excel data'}), 500 + # Debug: Print available sheets + available_sheets = list(comparator_instance.data.keys()) + print(f"Available sheets: {available_sheets}") + print(f"Requested sheet_filter: {repr(sheet_filter)}") + # Get comparison results with optional sheet filtering comparison_results = comparator_instance.get_comparison_summary(sheet_filter) @@ -93,6 +102,31 @@ def get_results(): return jsonify({'error': 'No analysis results available'}), 404 return jsonify(comparison_results) +@app.route('/get_sheets', methods=['POST']) +def get_sheets(): + """Get available sheet names from an Excel file""" + try: + file_path = request.json.get('file_path', 'data/sample-data.xlsx') + + if not Path(file_path).exists(): + return jsonify({'error': f'File not found: {file_path}'}), 400 + + # Create comparator and load data to get sheet names + temp_comparator = KSTCoordiComparator(file_path) + if not temp_comparator.load_data(): + return jsonify({'error': 'Failed to load Excel data'}), 500 + + available_sheets = list(temp_comparator.data.keys()) + + return jsonify({ + 'success': True, + 'sheets': available_sheets, + 'default_sheet': available_sheets[0] if available_sheets else None + }) + + except Exception as e: + return jsonify({'error': str(e)}), 500 + def create_templates_dir(): """Create templates directory and HTML file""" templates_dir = Path('templates') @@ -379,7 +413,8 @@ def create_templates_dir(): function analyzeData() { const filePath = document.getElementById('filePath').value; - const sheetFilter = document.getElementById('sheetFilter').value; + const sheetFilterElement = document.getElementById('sheetFilter'); + const sheetFilter = sheetFilterElement.value || null; // Use null if empty const statusDiv = document.getElementById('status'); const analyzeBtn = document.getElementById('analyzeBtn'); @@ -428,6 +463,18 @@ def create_templates_dir(): const select = document.getElementById('sheetFilter'); select.innerHTML = ''; + // Add a default option if no sheets are available yet + if (!sheetNames || sheetNames.length === 0) { + const option = document.createElement('option'); + option.value = ''; + option.textContent = 'Loading sheets...'; + option.disabled = true; + option.selected = true; + select.appendChild(option); + select.disabled = true; + return; + } + sheetNames.forEach((sheetName, index) => { const option = document.createElement('option'); option.value = sheetName; @@ -490,8 +537,9 @@ def create_templates_dir(): document.getElementById('filePath').value = data.file_path; statusDiv.innerHTML = '
File uploaded! Analyzing data...
'; - // Analyze the uploaded file - const sheetFilter = document.getElementById('sheetFilter').value; + // Clear sheet filter for new file (let it default to first sheet) + const sheetFilterElement = document.getElementById('sheetFilter'); + const sheetFilter = null; // Always use default (first sheet) for new uploads return fetch('/analyze', { method: 'POST', headers: { @@ -675,6 +723,8 @@ def create_templates_dir(): // Auto-analyze on page load with default file window.onload = function() { + // Initialize sheet filter with loading state + updateSheetFilter([], null); analyzeData(); };