Add Incomplete status tab and filtering, update incomplete color consistency

- Add separate Incomplete tab in vendor sections
- Add incomplete_count stat card in vendor headers
- Add total_incomplete summary card
- Track incomplete_items separately from open_items in report generator
- Standardize incomplete status color to #dc2626 across all UI elements
- Remove AI/LLM references from documentation
This commit is contained in:
ilia gu 2025-11-06 00:59:12 +04:00
parent f294ac155e
commit 39245e0999
3 changed files with 49 additions and 9 deletions

View File

@ -4,7 +4,7 @@ A Python tool that generates comprehensive vendor punchlist reports from Excel f
## Features ## Features
- **Direct Excel Processing**: Reads Excel files directly using pandas (no LLM required) - **Direct Excel Processing**: Reads Excel files directly using pandas
- **Data Normalization**: Automatically normalizes vendor names, statuses, and priorities - **Data Normalization**: Automatically normalizes vendor names, statuses, and priorities
- **24-Hour Updates**: Tracks items added, closed, or changed to monitor status in the last 24 hours (based on Baltimore/Eastern timezone) - **24-Hour Updates**: Tracks items added, closed, or changed to monitor status in the last 24 hours (based on Baltimore/Eastern timezone)
- **Priority Tracking**: Groups items by priority levels (Very High, High, Medium, Low) - **Priority Tracking**: Groups items by priority levels (Very High, High, Medium, Low)

View File

@ -510,8 +510,8 @@ def generate_html_content(report_data: Dict) -> str:
}} }}
.badge-danger {{ .badge-danger {{
background: #fee2e2; background: #dc2626;
color: #991b1b; color: white;
}} }}
.badge-secondary {{ .badge-secondary {{
@ -777,6 +777,10 @@ def generate_html_content(report_data: Dict) -> str:
<h3>{summary.get('total_open', 0)}</h3> <h3>{summary.get('total_open', 0)}</h3>
<p>Open</p> <p>Open</p>
</div> </div>
<div class="summary-card danger">
<h3>{summary.get('total_incomplete', 0)}</h3>
<p>Incomplete</p>
</div>
</div> </div>
<div class="tabs-container"> <div class="tabs-container">
@ -1160,6 +1164,7 @@ def generate_vendor_section(vendor: Dict) -> str:
closed_count = vendor.get('closed_count', 0) closed_count = vendor.get('closed_count', 0)
open_count = vendor.get('open_count', 0) open_count = vendor.get('open_count', 0)
monitor_count = vendor.get('monitor_count', 0) monitor_count = vendor.get('monitor_count', 0)
incomplete_count = vendor.get('incomplete_count', 0)
updates_24h = vendor.get('updates_24h', {}) updates_24h = vendor.get('updates_24h', {})
oldest_unaddressed = vendor.get('oldest_unaddressed', []) oldest_unaddressed = vendor.get('oldest_unaddressed', [])
@ -1171,6 +1176,7 @@ def generate_vendor_section(vendor: Dict) -> str:
closed_items = vendor.get('closed_items', []) closed_items = vendor.get('closed_items', [])
monitor_items = vendor.get('monitor_items', []) monitor_items = vendor.get('monitor_items', [])
open_items = vendor.get('open_items', []) open_items = vendor.get('open_items', [])
incomplete_items = vendor.get('incomplete_items', [])
# Convert to lists if needed (they should already be lists) # Convert to lists if needed (they should already be lists)
if not isinstance(closed_items, list): if not isinstance(closed_items, list):
@ -1179,6 +1185,8 @@ def generate_vendor_section(vendor: Dict) -> str:
monitor_items = [] monitor_items = []
if not isinstance(open_items, list): if not isinstance(open_items, list):
open_items = [] open_items = []
if not isinstance(incomplete_items, list):
incomplete_items = []
# Group all items by priority for the "All" tab # Group all items by priority for the "All" tab
# Combine all items first # Combine all items first
@ -1206,6 +1214,13 @@ def generate_vendor_section(vendor: Dict) -> str:
seen_names.add(name) seen_names.add(name)
all_items_combined.append(item) all_items_combined.append(item)
# Add all incomplete items
for item in incomplete_items:
name = item.get('punchlist_name', '')
if name and name not in seen_names:
seen_names.add(name)
all_items_combined.append(item)
# Group items by priority level # Group items by priority level
very_high_all = [] very_high_all = []
high_all = [] high_all = []
@ -1252,6 +1267,10 @@ def generate_vendor_section(vendor: Dict) -> str:
<div class="stat-value" style="color: #ef4444;">{open_count}</div> <div class="stat-value" style="color: #ef4444;">{open_count}</div>
<div class="stat-label">Open</div> <div class="stat-label">Open</div>
</div> </div>
<div class="stat-item">
<div class="stat-value" style="color: #dc2626;">{incomplete_count}</div>
<div class="stat-label">Incomplete</div>
</div>
</div> </div>
</div> </div>
@ -1263,6 +1282,7 @@ def generate_vendor_section(vendor: Dict) -> str:
<button class="status-tab" onclick="switchStatusTab(this, '{vendor_name}')" data-status="closed">Closed ({closed_count})</button> <button class="status-tab" onclick="switchStatusTab(this, '{vendor_name}')" data-status="closed">Closed ({closed_count})</button>
<button class="status-tab" onclick="switchStatusTab(this, '{vendor_name}')" data-status="monitor">Monitor ({monitor_count})</button> <button class="status-tab" onclick="switchStatusTab(this, '{vendor_name}')" data-status="monitor">Monitor ({monitor_count})</button>
<button class="status-tab" onclick="switchStatusTab(this, '{vendor_name}')" data-status="open">Open ({open_count})</button> <button class="status-tab" onclick="switchStatusTab(this, '{vendor_name}')" data-status="open">Open ({open_count})</button>
<button class="status-tab" onclick="switchStatusTab(this, '{vendor_name}')" data-status="incomplete">Incomplete ({incomplete_count})</button>
</div> </div>
<div class="status-tab-content active" data-status="all" data-vendor="{vendor_name}"> <div class="status-tab-content active" data-status="all" data-vendor="{vendor_name}">
@ -1309,6 +1329,15 @@ def generate_vendor_section(vendor: Dict) -> str:
</ul> </ul>
</div> </div>
</div> </div>
<div class="status-tab-content" data-status="incomplete" data-vendor="{vendor_name}">
<div class="section">
<div class="section-title">Incomplete Items ({len(incomplete_items)})</div>
<ul class="item-list">
{''.join([generate_item_html(item) for item in incomplete_items]) if incomplete_items else '<li class="empty">No incomplete items</li>'}
</ul>
</div>
</div>
</div> </div>
</div> </div>
""" """

View File

@ -1,9 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Report Generator - Direct Generation (No LLM Required) Report Generator
Generates vendor reports directly from preprocessed Excel data. Generates vendor reports directly from preprocessed Excel data.
All requirements fulfilled programmatically - no AI needed!
""" """
import json import json
@ -38,7 +37,7 @@ def generate_report(
verbose: bool = True verbose: bool = True
) -> dict: ) -> dict:
""" """
Generate vendor report directly from preprocessed data - NO LLM required! Generate vendor report directly from preprocessed data.
Args: Args:
reports_dir: Directory containing Excel files reports_dir: Directory containing Excel files
@ -50,7 +49,7 @@ def generate_report(
""" """
if verbose: if verbose:
print("=" * 70) print("=" * 70)
print("DIRECT REPORT GENERATION (No LLM Required)") print("REPORT GENERATION")
print("=" * 70) print("=" * 70)
print(f"Loading and preprocessing Excel files from '{reports_dir}'...") print(f"Loading and preprocessing Excel files from '{reports_dir}'...")
@ -115,9 +114,14 @@ def generate_report(
if item.get('status', '').lower() == 'complete' or item.get('is_closed', False)] if item.get('status', '').lower() == 'complete' or item.get('is_closed', False)]
monitor_items = [convert_item_to_punchlist_item(item) for item in all_items monitor_items = [convert_item_to_punchlist_item(item) for item in all_items
if item.get('status', '').lower() == 'monitor'] if item.get('status', '').lower() == 'monitor']
incomplete_items = [convert_item_to_punchlist_item(item) for item in all_items
if item.get('status', '').lower() == 'incomplete' and not item.get('is_closed', False)]
open_items = [convert_item_to_punchlist_item(item) for item in all_items open_items = [convert_item_to_punchlist_item(item) for item in all_items
if item.get('status', '').lower() == 'incomplete' and not item.get('is_closed', False)] if item.get('status', '').lower() == 'incomplete' and not item.get('is_closed', False)]
# Calculate incomplete count
incomplete_count = len(incomplete_items)
# Create vendor metrics # Create vendor metrics
vendor_metrics = VendorMetrics( vendor_metrics = VendorMetrics(
vendor_name=vendor_name, vendor_name=vendor_name,
@ -137,6 +141,8 @@ def generate_report(
vendor_dict['closed_items'] = [item.model_dump() for item in closed_items] vendor_dict['closed_items'] = [item.model_dump() for item in closed_items]
vendor_dict['monitor_items'] = [item.model_dump() for item in monitor_items] vendor_dict['monitor_items'] = [item.model_dump() for item in monitor_items]
vendor_dict['open_items'] = [item.model_dump() for item in open_items] vendor_dict['open_items'] = [item.model_dump() for item in open_items]
vendor_dict['incomplete_items'] = [item.model_dump() for item in incomplete_items]
vendor_dict['incomplete_count'] = incomplete_count
vendors.append(vendor_dict) vendors.append(vendor_dict)
@ -149,7 +155,8 @@ def generate_report(
"total_items": sum(v.get('total_items', 0) if isinstance(v, dict) else v.total_items for v in vendors), "total_items": sum(v.get('total_items', 0) if isinstance(v, dict) else v.total_items for v in vendors),
"total_closed": sum(v.get('closed_count', 0) if isinstance(v, dict) else v.closed_count for v in vendors), "total_closed": sum(v.get('closed_count', 0) if isinstance(v, dict) else v.closed_count for v in vendors),
"total_open": sum(v.get('open_count', 0) if isinstance(v, dict) else v.open_count for v in vendors), "total_open": sum(v.get('open_count', 0) if isinstance(v, dict) else v.open_count for v in vendors),
"total_monitor": sum(v.get('monitor_count', 0) if isinstance(v, dict) else v.monitor_count for v in vendors) "total_monitor": sum(v.get('monitor_count', 0) if isinstance(v, dict) else v.monitor_count for v in vendors),
"total_incomplete": sum(v.get('incomplete_count', 0) if isinstance(v, dict) else 0 for v in vendors)
} }
) )
@ -167,6 +174,10 @@ def generate_report(
report_data['vendors'][i]['monitor_items'] = vendor_dict['monitor_items'] report_data['vendors'][i]['monitor_items'] = vendor_dict['monitor_items']
if 'open_items' in vendor_dict: if 'open_items' in vendor_dict:
report_data['vendors'][i]['open_items'] = vendor_dict['open_items'] report_data['vendors'][i]['open_items'] = vendor_dict['open_items']
if 'incomplete_items' in vendor_dict:
report_data['vendors'][i]['incomplete_items'] = vendor_dict['incomplete_items']
if 'incomplete_count' in vendor_dict:
report_data['vendors'][i]['incomplete_count'] = vendor_dict['incomplete_count']
# Save to file if specified # Save to file if specified
if output_file: if output_file:
@ -203,7 +214,7 @@ def generate_report(
if __name__ == "__main__": if __name__ == "__main__":
import argparse import argparse
parser = argparse.ArgumentParser(description="Generate vendor reports from Excel files (no LLM required)") parser = argparse.ArgumentParser(description="Generate vendor reports from Excel files")
parser.add_argument( parser.add_argument(
"--reports-dir", "--reports-dir",
type=str, type=str,