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
- **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
- **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)

View File

@ -510,8 +510,8 @@ def generate_html_content(report_data: Dict) -> str:
}}
.badge-danger {{
background: #fee2e2;
color: #991b1b;
background: #dc2626;
color: white;
}}
.badge-secondary {{
@ -777,6 +777,10 @@ def generate_html_content(report_data: Dict) -> str:
<h3>{summary.get('total_open', 0)}</h3>
<p>Open</p>
</div>
<div class="summary-card danger">
<h3>{summary.get('total_incomplete', 0)}</h3>
<p>Incomplete</p>
</div>
</div>
<div class="tabs-container">
@ -1160,6 +1164,7 @@ def generate_vendor_section(vendor: Dict) -> str:
closed_count = vendor.get('closed_count', 0)
open_count = vendor.get('open_count', 0)
monitor_count = vendor.get('monitor_count', 0)
incomplete_count = vendor.get('incomplete_count', 0)
updates_24h = vendor.get('updates_24h', {})
oldest_unaddressed = vendor.get('oldest_unaddressed', [])
@ -1171,6 +1176,7 @@ def generate_vendor_section(vendor: Dict) -> str:
closed_items = vendor.get('closed_items', [])
monitor_items = vendor.get('monitor_items', [])
open_items = vendor.get('open_items', [])
incomplete_items = vendor.get('incomplete_items', [])
# Convert to lists if needed (they should already be lists)
if not isinstance(closed_items, list):
@ -1179,6 +1185,8 @@ def generate_vendor_section(vendor: Dict) -> str:
monitor_items = []
if not isinstance(open_items, list):
open_items = []
if not isinstance(incomplete_items, list):
incomplete_items = []
# Group all items by priority for the "All" tab
# Combine all items first
@ -1206,6 +1214,13 @@ def generate_vendor_section(vendor: Dict) -> str:
seen_names.add(name)
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
very_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-label">Open</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>
@ -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="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="incomplete">Incomplete ({incomplete_count})</button>
</div>
<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>
</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>
"""

View File

@ -1,9 +1,8 @@
#!/usr/bin/env python3
"""
Report Generator - Direct Generation (No LLM Required)
Report Generator
Generates vendor reports directly from preprocessed Excel data.
All requirements fulfilled programmatically - no AI needed!
"""
import json
@ -38,7 +37,7 @@ def generate_report(
verbose: bool = True
) -> dict:
"""
Generate vendor report directly from preprocessed data - NO LLM required!
Generate vendor report directly from preprocessed data.
Args:
reports_dir: Directory containing Excel files
@ -50,7 +49,7 @@ def generate_report(
"""
if verbose:
print("=" * 70)
print("DIRECT REPORT GENERATION (No LLM Required)")
print("REPORT GENERATION")
print("=" * 70)
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)]
monitor_items = [convert_item_to_punchlist_item(item) for item in all_items
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
if item.get('status', '').lower() == 'incomplete' and not item.get('is_closed', False)]
# Calculate incomplete count
incomplete_count = len(incomplete_items)
# Create vendor metrics
vendor_metrics = VendorMetrics(
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['monitor_items'] = [item.model_dump() for item in monitor_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)
@ -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_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_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']
if 'open_items' in vendor_dict:
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
if output_file:
@ -203,7 +214,7 @@ def generate_report(
if __name__ == "__main__":
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(
"--reports-dir",
type=str,