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:
parent
f294ac155e
commit
39245e0999
@ -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)
|
||||||
|
|||||||
@ -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>
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user