File size: 11,075 Bytes
38e0dee 045edbf 38e0dee 01f2701 38e0dee 045edbf 38e0dee 01f2701 38e0dee 01f2701 045edbf 01f2701 045edbf 01f2701 71c2730 045edbf 01f2701 045edbf 01f2701 045edbf 01f2701 045edbf 01f2701 045edbf 01f2701 045edbf 01f2701 045edbf 01f2701 045edbf 01f2701 045edbf 01f2701 045edbf 71c2730 045edbf 01f2701 71c2730 01f2701 71c2730 045edbf 01f2701 045edbf 01f2701 045edbf 01f2701 045edbf 01f2701 045edbf 01f2701 045edbf 01f2701 045edbf 01f2701 045edbf 01f2701 045edbf 01f2701 045edbf 01f2701 045edbf 71c2730 045edbf 01f2701 045edbf 01f2701 045edbf |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
"""
Costs Display Component
Displays material costs and estimates
"""
from models import CostData, MaterialCost
from typing import Dict, List
def format_costs_display(cost_data) -> str:
"""
Format cost data for display with itemized materials and estimates
Args:
cost_data: Material cost data (dict or CostData object)
Returns:
Formatted HTML/Markdown string
"""
import logging
logger = logging.getLogger(__name__)
# Debug logging
logger.info(f"format_costs_display received type: {type(cost_data)}")
if not cost_data:
return "β οΈ Cost data unavailable"
# Handle both dict and object formats
success = cost_data.get('success', True) if isinstance(cost_data, dict) else getattr(cost_data, 'success', True)
if not success:
return "β οΈ Cost data unavailable"
html = """
<div style="padding: 20px;">
<h2 style="margin-top: 0; color: #ffffff !important; font-weight: 700;">π° Material Cost Analysis</h2>
"""
# Total Cost Estimate - handle both dict and object
total_estimate = cost_data.get('total_estimate') if isinstance(cost_data, dict) else getattr(cost_data, 'total_estimate', None)
if total_estimate:
# Extract values from dict or object
currency = total_estimate.get('currency', 'PHP') if isinstance(total_estimate, dict) else getattr(total_estimate, 'currency', 'PHP')
low = total_estimate.get('low', 0) if isinstance(total_estimate, dict) else getattr(total_estimate, 'low', 0)
mid = total_estimate.get('mid', 0) if isinstance(total_estimate, dict) else getattr(total_estimate, 'mid', 0)
high = total_estimate.get('high', 0) if isinstance(total_estimate, dict) else getattr(total_estimate, 'high', 0)
html += f"""
<div style="background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); padding: 25px; border-radius: 8px; margin-bottom: 20px; color: white; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
<h3 style="margin-top: 0; color: white; font-weight: 600;">π΅ Total Cost Estimate</h3>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin-top: 15px;">
<div style="background: rgba(255,255,255,0.25); padding: 15px; border-radius: 6px; text-align: center;">
<div style="font-size: 0.9em; font-weight: 500;">Low Estimate</div>
<div style="font-size: 1.5em; font-weight: bold; margin-top: 5px;">
{currency} {low:,.2f}
</div>
</div>
<div style="background: rgba(255,255,255,0.35); padding: 15px; border-radius: 6px; text-align: center;">
<div style="font-size: 0.9em; font-weight: 500;">Mid Estimate</div>
<div style="font-size: 1.5em; font-weight: bold; margin-top: 5px;">
{currency} {mid:,.2f}
</div>
</div>
<div style="background: rgba(255,255,255,0.25); padding: 15px; border-radius: 6px; text-align: center;">
<div style="font-size: 0.9em; font-weight: 500;">High Estimate</div>
<div style="font-size: 1.5em; font-weight: bold; margin-top: 5px;">
{currency} {high:,.2f}
</div>
</div>
</div>
</div>
"""
# Market Conditions - handle both dict and object
market_conditions = cost_data.get('market_conditions') if isinstance(cost_data, dict) else getattr(cost_data, 'market_conditions', None)
last_updated = cost_data.get('last_updated') if isinstance(cost_data, dict) else getattr(cost_data, 'last_updated', None)
if market_conditions or last_updated:
html += """
<div style="background: white; padding: 15px; border-radius: 8px; margin-bottom: 20px; border-left: 5px solid #f59e0b; border: 1px solid #fde68a;">
<h4 style="margin-top: 0; color: #d97706 !important;">π Market Information</h4>
"""
if market_conditions:
html += f"<p style='margin: 5px 0; color: #0f172a !important;'><strong style='color: #0f172a !important;'>Market Conditions:</strong> {market_conditions}</p>"
if last_updated:
html += f"<p style='margin: 5px 0; color: #0f172a !important;'><strong style='color: #0f172a !important;'>Last Updated:</strong> {last_updated}</p>"
html += """
</div>
"""
# Itemized Materials by Category - handle both dict and object
materials = cost_data.get('materials', []) if isinstance(cost_data, dict) else getattr(cost_data, 'materials', [])
if materials:
# Group materials by category
materials_by_category = _group_materials_by_category(materials)
for category, materials_list in materials_by_category.items():
# Calculate category total - handle both dict and object
category_total = 0
first_currency = 'PHP'
for m in materials_list:
total_cost = m.get('total_cost', 0) if isinstance(m, dict) else getattr(m, 'total_cost', 0)
if total_cost:
category_total += total_cost
if first_currency == 'PHP':
first_currency = m.get('currency', 'PHP') if isinstance(m, dict) else getattr(m, 'currency', 'PHP')
html += f"""
<div style="background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); border: 1px solid #e2e8f0;">
<h3 style="color: #1e40af !important; margin-top: 0; font-weight: 600;">
{_get_category_icon(category)} {category}
{f'<span style="float: right; color: #15803d !important; font-weight: 700;">Total: {first_currency} {category_total:,.2f}</span>' if category_total > 0 else ''}
</h3>
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="background: #f8fafc;">
<th style="padding: 12px; text-align: left; border-bottom: 2px solid #cbd5e1; color: #0f172a !important; font-weight: 600;">Material</th>
<th style="padding: 12px; text-align: right; border-bottom: 2px solid #cbd5e1; color: #0f172a !important; font-weight: 600;">Unit Price</th>
<th style="padding: 12px; text-align: right; border-bottom: 2px solid #cbd5e1; color: #0f172a !important; font-weight: 600;">Quantity</th>
<th style="padding: 12px; text-align: right; border-bottom: 2px solid #cbd5e1; color: #0f172a !important; font-weight: 600;">Total Cost</th>
</tr>
</thead>
<tbody>
"""
for material in materials_list:
# Extract material properties - handle both dict and object
material_name = material.get('material_name', 'Unknown') if isinstance(material, dict) else getattr(material, 'material_name', 'Unknown')
source = material.get('source') if isinstance(material, dict) else getattr(material, 'source', None)
currency = material.get('currency', 'PHP') if isinstance(material, dict) else getattr(material, 'currency', 'PHP')
price_per_unit = material.get('price_per_unit', 0) if isinstance(material, dict) else getattr(material, 'price_per_unit', 0)
unit = material.get('unit', 'unit') if isinstance(material, dict) else getattr(material, 'unit', 'unit')
quantity_needed = material.get('quantity_needed') if isinstance(material, dict) else getattr(material, 'quantity_needed', None)
total_cost = material.get('total_cost') if isinstance(material, dict) else getattr(material, 'total_cost', None)
quantity_str = f"{quantity_needed:.2f} {unit}" if quantity_needed else "TBD"
total_str = f"{currency} {total_cost:,.2f}" if total_cost else "TBD"
html += f"""
<tr style="border-bottom: 1px solid #e2e8f0;">
<td style="padding: 12px; color: #0f172a !important;">
<strong style="color: #0f172a !important;">{material_name}</strong>
{f'<br><span style="font-size: 0.85em; color: #475569 !important;">Source: {source}</span>' if source else ''}
</td>
<td style="padding: 12px; text-align: right; color: #0f172a !important;">
{currency} {price_per_unit:,.2f} / {unit}
</td>
<td style="padding: 12px; text-align: right; color: #0f172a !important;">{quantity_str}</td>
<td style="padding: 12px; text-align: right; font-weight: 700; color: #15803d !important;">{total_str}</td>
</tr>
"""
html += """
</tbody>
</table>
</div>
"""
else:
html += """
<div style="background: white; padding: 20px; border-radius: 8px; text-align: center; color: #6b7280;">
<p>No material cost data available</p>
</div>
"""
# Disclaimer
html += """
<div style="background: white; padding: 15px; border-radius: 8px; margin-top: 20px; font-size: 0.9em; border: 1px solid #e2e8f0;">
<p style="margin: 0; color: #0f172a !important;"><strong style="color: #0f172a !important;">Note:</strong> Cost estimates are based on current market conditions and may vary.
Actual costs depend on supplier, location, quantity discounts, and market fluctuations.
Please consult with local suppliers for accurate quotes.</p>
</div>
</div>
"""
return html
def _group_materials_by_category(materials: List) -> Dict[str, List]:
"""Group materials by category (handles both dict and object formats)"""
grouped = {}
for material in materials:
# Handle both dict and object formats
category = material.get('category', 'Other') if isinstance(material, dict) else getattr(material, 'category', 'Other')
if not category:
category = "Other"
if category not in grouped:
grouped[category] = []
grouped[category].append(material)
return grouped
def _get_category_icon(category: str) -> str:
"""Get icon for material category"""
icons = {
"Structural": "ποΈ",
"Roofing": "π ",
"Foundation": "β",
"Disaster-Resistant Features": "π‘οΈ",
"Finishing": "π¨",
"Electrical": "β‘",
"Plumbing": "π°",
"Other": "π¦"
}
return icons.get(category, "π¦")
|