<< All versions
Skill v1.0.0
currentAutomated scan100/100datadrivenconstruction/ddc_skills_for_ai_agents_in_construction/estimate-builder
──Details
PublishedSeptember 29, 2026 at 07:00 AM
Content Hashsha256:e8dea0e800b61acc...
Git SHA
──Files
Files (1 file, 10.0 KB)
SKILL.md10.0 KBactive
SKILL.md · 324 lines · 10.0 KB
version: "1.0.0" name: "estimate-builder" description: "Build construction project estimates. Generate detailed cost breakdowns with labor, materials, equipment, and overhead." homepage: "https://datadrivenconstruction.io" metadata: {"openclaw":{"emoji":"📊","os":["darwin","linux","win32"],"homepage":"https://datadrivenconstruction.io","requires":{"bins":["python3"]}}}
Estimate Builder
Business Case
Problem Statement
Estimate creation challenges:
- Complex cost structures
- Multiple cost categories
- Markup calculations
- Format requirements vary
Solution
Structured estimate builder that creates professional construction estimates with proper cost categorization, markups, and export capabilities.
Technical Implementation
python
import pandas as pdfrom typing import Dict, Any, List, Optionalfrom dataclasses import dataclass, fieldfrom datetime import datefrom enum import Enumclass CostCategory(Enum):LABOR = "labor"MATERIAL = "material"EQUIPMENT = "equipment"SUBCONTRACTOR = "subcontractor"OTHER = "other"@dataclassclass EstimateLineItem:line_number: intwbs_code: strdescription: strquantity: floatunit: strunit_cost: floatcategory: CostCategorynotes: str = ""@propertydef total_cost(self) -> float:return round(self.quantity * self.unit_cost, 2)@dataclassclass CostSummary:labor: float = 0material: float = 0equipment: float = 0subcontractor: float = 0other: float = 0@propertydef direct_cost(self) -> float:return self.labor + self.material + self.equipment + self.subcontractor + self.other@dataclassclass Markup:name: strrate: float # As decimal (0.10 = 10%)base: str = "direct" # "direct" or "subtotal"class EstimateBuilder:"""Build construction project estimates."""def __init__(self, project_name: str, project_number: str = ""):self.project_name = project_nameself.project_number = project_numberself.estimate_date = date.today()self.items: List[EstimateLineItem] = []self.markups: List[Markup] = []self._next_line = 1def add_item(self,wbs_code: str,description: str,quantity: float,unit: str,unit_cost: float,category: CostCategory = CostCategory.OTHER,notes: str = "") -> EstimateLineItem:"""Add line item to estimate."""item = EstimateLineItem(line_number=self._next_line,wbs_code=wbs_code,description=description,quantity=quantity,unit=unit,unit_cost=unit_cost,category=category,notes=notes)self.items.append(item)self._next_line += 1return itemdef add_markup(self, name: str, rate: float, base: str = "direct"):"""Add markup (overhead, profit, contingency, etc.)."""self.markups.append(Markup(name=name, rate=rate, base=base))def set_standard_markups(self,overhead: float = 0.15,profit: float = 0.10,contingency: float = 0.05):"""Set standard construction markups."""self.markups = [Markup("General Conditions / Overhead", overhead, "direct"),Markup("Profit", profit, "subtotal"),Markup("Contingency", contingency, "subtotal")]def get_cost_summary(self) -> CostSummary:"""Get cost summary by category."""summary = CostSummary()for item in self.items:cost = item.total_costif item.category == CostCategory.LABOR:summary.labor += costelif item.category == CostCategory.MATERIAL:summary.material += costelif item.category == CostCategory.EQUIPMENT:summary.equipment += costelif item.category == CostCategory.SUBCONTRACTOR:summary.subcontractor += costelse:summary.other += costreturn summarydef calculate_total(self) -> Dict[str, Any]:"""Calculate total estimate with markups."""summary = self.get_cost_summary()direct_cost = summary.direct_costmarkups_detail = []subtotal = direct_costfor markup in self.markups:if markup.base == "direct":amount = direct_cost * markup.rateelse:amount = subtotal * markup.ratemarkups_detail.append({'name': markup.name,'rate': f"{markup.rate * 100:.1f}%",'amount': round(amount, 2)})subtotal += amountreturn {'cost_summary': {'labor': round(summary.labor, 2),'material': round(summary.material, 2),'equipment': round(summary.equipment, 2),'subcontractor': round(summary.subcontractor, 2),'other': round(summary.other, 2),'direct_cost': round(direct_cost, 2)},'markups': markups_detail,'total_markups': round(subtotal - direct_cost, 2),'grand_total': round(subtotal, 2)}def get_items_by_wbs(self) -> Dict[str, List[EstimateLineItem]]:"""Group items by WBS code prefix."""by_wbs = {}for item in self.items:prefix = item.wbs_code.split('.')[0] if '.' in item.wbs_code else item.wbs_codeif prefix not in by_wbs:by_wbs[prefix] = []by_wbs[prefix].append(item)return by_wbsdef import_from_df(self, df: pd.DataFrame):"""Import line items from DataFrame."""for _, row in df.iterrows():self.add_item(wbs_code=str(row.get('wbs_code', '')),description=row['description'],quantity=float(row['quantity']),unit=row['unit'],unit_cost=float(row['unit_cost']),category=CostCategory(row.get('category', 'other').lower()),notes=row.get('notes', ''))def export_to_df(self) -> pd.DataFrame:"""Export estimate to DataFrame."""data = []for item in self.items:data.append({'Line': item.line_number,'WBS': item.wbs_code,'Description': item.description,'Qty': item.quantity,'Unit': item.unit,'Unit Cost': item.unit_cost,'Total': item.total_cost,'Category': item.category.value,'Notes': item.notes})return pd.DataFrame(data)def export_to_excel(self, output_path: str) -> str:"""Export estimate to Excel."""totals = self.calculate_total()with pd.ExcelWriter(output_path, engine='openpyxl') as writer:# Cover sheetcover_df = pd.DataFrame([{'Project Name': self.project_name,'Project Number': self.project_number,'Estimate Date': self.estimate_date,'Total Items': len(self.items),'Direct Cost': totals['cost_summary']['direct_cost'],'Grand Total': totals['grand_total']}])cover_df.to_excel(writer, sheet_name='Summary', index=False)# Line itemsitems_df = self.export_to_df()items_df.to_excel(writer, sheet_name='Line Items', index=False)# Cost breakdownbreakdown_df = pd.DataFrame([totals['cost_summary']])breakdown_df.to_excel(writer, sheet_name='Cost Breakdown', index=False)# Markupsif totals['markups']:markups_df = pd.DataFrame(totals['markups'])markups_df.to_excel(writer, sheet_name='Markups', index=False)return output_pathdef validate(self) -> List[str]:"""Validate estimate for common issues."""issues = []if not self.items:issues.append("Estimate has no line items")for item in self.items:if item.quantity <= 0:issues.append(f"Line {item.line_number}: Invalid quantity")if item.unit_cost < 0:issues.append(f"Line {item.line_number}: Negative unit cost")if not item.description:issues.append(f"Line {item.line_number}: Missing description")if not self.markups:issues.append("No markups defined (overhead, profit)")return issues
Quick Start
python
# Create estimateestimate = EstimateBuilder("Office Building A", "PRJ-2024-001")# Add line itemsestimate.add_item("01.01", "Site Preparation", 5000, "SF", 2.50, CostCategory.OTHER)estimate.add_item("03.01", "Concrete Foundation", 200, "CY", 350, CostCategory.MATERIAL)estimate.add_item("03.02", "Foundation Formwork", 1500, "SF", 8.50, CostCategory.LABOR)estimate.add_item("05.01", "Structural Steel", 50, "TON", 4500, CostCategory.SUBCONTRACTOR)# Set markupsestimate.set_standard_markups(overhead=0.15, profit=0.10, contingency=0.05)# Calculate totalresult = estimate.calculate_total()print(f"Direct Cost: ${result['cost_summary']['direct_cost']:,.2f}")print(f"Grand Total: ${result['grand_total']:,.2f}")
Common Use Cases
1. Cost by Category
python
summary = estimate.get_cost_summary()print(f"Labor: ${summary.labor:,.2f}")print(f"Material: ${summary.material:,.2f}")
2. Export to Excel
python
estimate.export_to_excel("estimate_output.xlsx")
3. Validate Estimate
python
issues = estimate.validate()for issue in issues:print(f"Warning: {issue}")
Resources
- DDC Book: Chapter 3.1 - Cost Calculations and Estimates
- Website: https://datadrivenconstruction.io