File size: 12,506 Bytes
10b617b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
from typing import Dict, Any
from langchain_openai import ChatOpenAI
from langchain.schema import HumanMessage, SystemMessage
from state import ConversationState
from agents import BaseAgent


class DetailedBudgetAgent(BaseAgent):
    """Creates comprehensive Montreal construction cost estimates based on detailed floorplan"""
    
    def process(self, state: ConversationState) -> ConversationState:
        """Generate detailed budget breakdown based on floorplan and user requirements"""
        
        # Get shared information from other agents
        user_reqs = state["user_requirements"]
        floorplan_details = state["detailed_floorplan"]
        detailed_rooms = floorplan_details["detailed_rooms"]
        
        # Build context for budget analysis
        context = self._build_budget_context(state)
        
        system_prompt = f"""You are a senior construction estimator with 15+ years of experience in Montreal residential construction.

        You have access to detailed floorplan information from the architectural team.
        Create a comprehensive cost breakdown for this custom home construction project.

        MONTREAL CONSTRUCTION COSTS (2024):
        - Site preparation: $8-15 per sq ft
        - Foundation: $15-25 per sq ft  
        - Framing: $25-40 per sq ft
        - Roofing: $12-20 per sq ft
        - Exterior finishes: $20-35 per sq ft
        - Interior finishes: $30-60 per sq ft (varies by quality)
        - Kitchen: $25,000-60,000 (depending on size/finishes)
        - Bathrooms: $15,000-35,000 each
        - Mechanical (HVAC): $8-15 per sq ft
        - Electrical: $6-12 per sq ft  
        - Plumbing: $8-15 per sq ft
        - Permits & fees: $8,000-15,000
        - Professional services (architect/engineer): 8-12% of construction
        - Contingency: 10-15% of total
        
        QUEBEC-SPECIFIC FACTORS:
        - GST: 5%
        - QST: 9.975%
        - Cold climate considerations (insulation, heating)
        - Local building codes and requirements
        
        Analyze each room from the floorplan and provide detailed cost estimates.
        Consider the user's budget and provide realistic recommendations.
        
        Return detailed JSON format cost breakdown."""
        
        # Safe formatting for budget
        budget = user_reqs.get('budget', 0)
        budget_str = f"${budget:,.0f}" if budget else "Not specified"
        
        user_message = f"""
        CLIENT REQUIREMENTS:
        Budget: {budget_str} CAD
        Family size: {user_reqs.get('family_size', 'Not specified')}
        Location: {user_reqs.get('location', 'Montreal')}
        
        DETAILED FLOORPLAN INFORMATION:
        {context}
        
        Please provide a comprehensive construction cost estimate for this project.
        Break down costs by:
        1. Major construction categories
        2. Room-by-room estimates where relevant
        3. Quebec taxes and permits
        4. Professional services
        5. Contingency recommendations
        
        Respond with detailed JSON:
        {{
            "site_preparation": cost_in_cad,
            "foundation": cost_in_cad,
            "framing": cost_in_cad,
            "roofing": cost_in_cad,
            "exterior_finishes": cost_in_cad,
            "interior_finishes": cost_in_cad,
            "kitchen_costs": cost_in_cad,
            "bathroom_costs": cost_in_cad,
            "mechanical_systems": cost_in_cad,
            "electrical_systems": cost_in_cad,
            "plumbing_systems": cost_in_cad,
            "permits_fees": cost_in_cad,
            "professional_services": cost_in_cad,
            "subtotal": cost_in_cad,
            "taxes_gst_qst": cost_in_cad,
            "contingency": cost_in_cad,
            "total_construction_cost": cost_in_cad,
            "cost_per_sqft": cost_per_sqft,
            "budget_analysis": "analysis of fit with client budget",
            "recommendations": ["recommendation1", "recommendation2"],
            "room_breakdown": {{"room_name": cost_estimate}},
            "timeline_estimate": "construction timeline estimate"
        }}
        """
        
        messages = [
            SystemMessage(content=system_prompt),
            HumanMessage(content=user_message)
        ]
        
        response = self.model.invoke(messages)
        
        try:
            import json
            budget_data = json.loads(response.content)
            
            # Update state with detailed budget breakdown
            state["budget_breakdown"].update({
                "site_preparation": budget_data.get("site_preparation"),
                "foundation": budget_data.get("foundation"),
                "framing": budget_data.get("framing"),
                "roofing": budget_data.get("roofing"),
                "exterior_finishes": budget_data.get("exterior_finishes"),
                "interior_finishes": budget_data.get("interior_finishes"),
                "mechanical_systems": budget_data.get("mechanical_systems"),
                "electrical_systems": budget_data.get("electrical_systems"),
                "plumbing_systems": budget_data.get("plumbing_systems"),
                "permits_fees": budget_data.get("permits_fees"),
                "professional_services": budget_data.get("professional_services"),
                "contingency": budget_data.get("contingency"),
                "total_construction_cost": budget_data.get("total_construction_cost"),
                "cost_per_sqft": budget_data.get("cost_per_sqft"),
                "budget_analysis": budget_data.get("budget_analysis")
            })
            
            # Store additional details in agent memory for future reference
            state["agent_memory"]["detailed_budget"] = budget_data
            state["budget_ready"] = True
            
            # Create comprehensive response
            formatted_response = self._format_budget_response(budget_data, user_reqs)
            
        except json.JSONDecodeError:
            # Fallback if JSON parsing fails
            formatted_response = f"""πŸ’° **DETAILED CONSTRUCTION COST ESTIMATE** πŸ’°

Based on the architectural floorplan and Montreal market conditions, here's a comprehensive cost breakdown:

{response.content}

Note: This is an estimated breakdown. Final costs may vary based on specific material choices, contractor selection, and market conditions."""
            
            # Set basic budget ready flag even with fallback
            state["budget_ready"] = True
        
        # Add response to conversation
        state["messages"].append({
            "role": "assistant",
            "content": formatted_response,
            "agent": "detailed_budget"
        })
        
        return state
    
    def _build_budget_context(self, state: ConversationState) -> str:
        """Build context from floorplan details for budget estimation"""
        floorplan_details = state["detailed_floorplan"]
        floorplan_reqs = state["floorplan_requirements"]
        
        context_parts = []
        
        # Basic project info
        if floorplan_reqs["total_sqft"]:
            context_parts.append(f"Total Area: {floorplan_reqs['total_sqft']:,} sq ft")
        if floorplan_reqs["num_floors"]:
            context_parts.append(f"Number of Floors: {floorplan_reqs['num_floors']}")
        if floorplan_reqs["lot_dimensions"]:
            context_parts.append(f"Lot Size: {floorplan_reqs['lot_dimensions']}")
        
        # Detailed rooms information
        detailed_rooms = floorplan_details["detailed_rooms"]
        if detailed_rooms:
            context_parts.append("\nROOM BREAKDOWN:")
            for room in detailed_rooms:
                room_info = f"- {room.get('label', room.get('type', 'Unknown'))}: {room.get('width', '?')}' Γ— {room.get('height', '?')}' = {room.get('width', 0) * room.get('height', 0)} sq ft"
                if room.get('features'):
                    room_info += f" (Features: {', '.join(room['features'])})"
                context_parts.append(room_info)
        
        # Architectural features
        arch_features = floorplan_details["architectural_features"]
        if arch_features:
            context_parts.append(f"\nSpecial Architectural Features: {', '.join(arch_features)}")
        
        # Structural elements
        structural = floorplan_details["structural_elements"]
        if structural:
            context_parts.append(f"\nStructural Elements: {len(structural)} custom elements")
        
        return "\n".join(context_parts) if context_parts else "Basic floorplan information available"
    
    def _format_budget_response(self, budget_data: Dict[str, Any], user_reqs: Dict[str, Any]) -> str:
        """Format the budget response in a clear, professional manner"""
        
        total_cost = budget_data.get("total_construction_cost", 0)
        user_budget = user_reqs.get("budget", 0)
        cost_per_sqft = budget_data.get("cost_per_sqft", 0)
        
        # Budget fit analysis
        if user_budget and total_cost:
            if total_cost <= user_budget * 0.95:
                budget_status = "βœ… WITHIN BUDGET"
                budget_message = f"Excellent! The estimated cost is within your ${user_budget:,.0f} budget with room for upgrades."
            elif total_cost <= user_budget * 1.1:
                budget_status = "⚠️ CLOSE TO BUDGET"  
                budget_message = f"The estimate is close to your ${user_budget:,.0f} budget. Minor adjustments may be needed."
            else:
                budget_status = "❌ OVER BUDGET"
                budget_message = f"The estimate exceeds your ${user_budget:,.0f} budget by ${total_cost - user_budget:,.0f}. Consider modifications."
        else:
            budget_status = "πŸ“Š COST ESTIMATE"
            budget_message = "Comprehensive cost breakdown based on architectural specifications."
        
        response = f"""πŸ’° **DETAILED MONTREAL CONSTRUCTION ESTIMATE** πŸ’°

{budget_status}
{budget_message}

**CONSTRUCTION COST BREAKDOWN:**
πŸ—οΈ Site Preparation: ${budget_data.get('site_preparation', 0):,.0f} CAD
🏠 Foundation: ${budget_data.get('foundation', 0):,.0f} CAD  
πŸ”¨ Framing: ${budget_data.get('framing', 0):,.0f} CAD
🏠 Roofing: ${budget_data.get('roofing', 0):,.0f} CAD
🎨 Exterior Finishes: ${budget_data.get('exterior_finishes', 0):,.0f} CAD
🏑 Interior Finishes: ${budget_data.get('interior_finishes', 0):,.0f} CAD
🍳 Kitchen: ${budget_data.get('kitchen_costs', 0):,.0f} CAD
πŸ› Bathrooms: ${budget_data.get('bathroom_costs', 0):,.0f} CAD

**SYSTEMS & SERVICES:**
🌑️ Mechanical (HVAC): ${budget_data.get('mechanical_systems', 0):,.0f} CAD
⚑ Electrical: ${budget_data.get('electrical_systems', 0):,.0f} CAD
🚿 Plumbing: ${budget_data.get('plumbing_systems', 0):,.0f} CAD
πŸ“‹ Permits & Fees: ${budget_data.get('permits_fees', 0):,.0f} CAD
πŸ‘· Professional Services: ${budget_data.get('professional_services', 0):,.0f} CAD

**FINAL TOTALS:**
Subtotal: ${budget_data.get('subtotal', total_cost * 0.85):,.0f} CAD
Quebec Taxes (GST+QST): ${budget_data.get('taxes_gst_qst', total_cost * 0.15):,.0f} CAD
Contingency (12%): ${budget_data.get('contingency', 0):,.0f} CAD

**πŸ’° TOTAL PROJECT COST: ${total_cost:,.0f} CAD**
**πŸ“ Cost per sq ft: ${cost_per_sqft:.0f} CAD/sq ft**

**BUDGET ANALYSIS:**
{budget_data.get('budget_analysis', 'Cost analysis based on Montreal market rates and architectural specifications.')}

**RECOMMENDATIONS:**
"""
        
        # Add recommendations
        recommendations = budget_data.get('recommendations', [])
        for rec in recommendations:
            response += f"β€’ {rec}\n"
        
        # Add room breakdown if available
        room_breakdown = budget_data.get('room_breakdown', {})
        if room_breakdown:
            response += "\n**ROOM-BY-ROOM COSTS:**\n"
            for room, cost in room_breakdown.items():
                response += f"β€’ {room}: ${cost:,.0f} CAD\n"
        
        # Add timeline
        timeline = budget_data.get('timeline_estimate', 'Construction timeline to be determined')
        response += f"\n**CONSTRUCTION TIMELINE:**\n{timeline}"
        
        response += "\n\n*Note: This estimate is based on current Montreal market conditions and the provided architectural specifications. Final costs may vary based on material selections, contractor choice, and market fluctuations.*"
        
        return response