Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Automated cost estimation from BIM models using DDC CWICR database with 55,719 work items. AI classification + vector search for accurate pricing.
.claude/skills/datadrivenconstruction-bim-cost-estimation-cwicr/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 127% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 159% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 315% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 369% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 229% | 0% |
Generate accurate cost estimates from BIM models using AI classification and the DDC CWICR construction cost database.
Problem: Traditional cost estimation:
Solution: Automated BIM-to-cost pipeline:
ROI: 80% reduction in estimation time, consistent methodology
┌──────────────────────────────────────────────────────────────────────────┐
│ BIM TO COST ESTIMATION PIPELINE │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────────┐ │
│ │ BIM │ │ DDC │ │ AI │ │ DDC CWICR │ │
│ │ Model │────►│Converter│────►│ LLM │────►│ Vector Search │ │
│ │.rvt/.ifc│ │ │ │ │ │ (Qdrant) │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ │
│ │ .xlsx │ │ Work │ │ Matched │ │
│ │ QTO │ │ Items │ │ Rates │ │
│ └─────────┘ └─────────┘ └──────────┘ │
│ │ │ │ │
│ └──────────────┼────────────────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ COST ESTIMATE │ │
│ │ │ │
│ │ • By element │ │
│ │ • By trade │ │
│ │ • By phase │ │
│ │ • Resources │ │
│ └─────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘yamlDatabase Overview: work_items: 78228 # 8 national bases resources: 27,672 languages: 9 (AR, DE, EN, ES, FR, HI, PT, RU, ZH) fields_per_item: 85 embedding_model: BAAI/bge-m3 (1024d) vector_db: Qdrant Collections: - ddc_cwicr_ar # Arabic (Dubai prices) - ddc_cwicr_de # German (Berlin prices) - cwicr_en_v3 # English (Toronto prices) - ddc_cwicr_es # Spanish (Barcelona prices) - ddc_cwicr_fr # French (Paris prices) - ddc_cwicr_hi # Hindi (Mumbai prices) - ddc_cwicr_pt # Portuguese (São Paulo prices) - ddc_cwicr_ru # Russian (St. Petersburg prices) - ddc_cwicr_zh # Chinese (Shanghai prices)
| Stage | Name | Description | |-------|------|-------------| | 0 | Collect BIM Data | Extract elements from Revit/IFC | | 1 | Project Detection | AI identifies project type | | 2 | Phase Generation | AI creates construction phases | | 3 | Element Assignment | AI maps types to phases | | 4 | Work Decomposition | AI breaks types into work items | | 5 | Vector Search | Find matching rates in CWICR | | 6 | Unit Mapping | Convert BIM units to rate units | | 7 | Cost Calculation | Qty × Unit Price | | 7.5 | Validation | CTO review for completeness | | 8 | Aggregation | Sum by phases and categories | | 9 | Report Generation | HTML and Excel outputs |
pythonimport pandas as pd import numpy as np from qdrant_client import QdrantClient from qdrant_client.models import Filter, FieldCondition, MatchValue from openai import OpenAI from typing import List, Dict, Optional from dataclasses import dataclass import json @dataclass class WorkItem: """Matched work item from CWICR""" cwicr_code: str description: str unit: str unit_price: float labor_cost: float material_cost: float equipment_cost: float productivity: float # units per hour currency: str confidence: float @dataclass class CostLineItem: """Single line item in estimate""" bim_type: str work_item: WorkItem quantity: float quantity_unit: str total_cost: float labor_cost: float material_cost: float equipment_cost: float phase: str trade: str class BIMCostEstimator: """BIM to cost estimation using DDC CWICR""" def __init__( self, qdrant_url: str, qdrant_api_key: str = None, openai_api_key: str = None, language: str = "EN" ): self.qdrant = QdrantClient(url=qdrant_url, api_key=qdrant_api_key) self.openai = OpenAI(api_key=openai_api_key) self.language = language self.collection = f"ddc_cwicr_{language.lower()}" def get_embedding(self, text: str) -> List[float]: """Generate embedding for text""" response = self.openai.embeddings.create( model="BAAI/bge-m3", input=text, dimensions=1024 ) return response.data[0].embedding def search_cwicr( self, query: str, limit: int = 5, category_filter: str = None ) -> List[WorkItem]: """Search CWICR database for matching work items""" # Get embedding query_vector = self.get_embedding(query) # Build filter if category specified query_filter = None if category_filter: query_filter = Filter( must=[ FieldCondition( key="category", match=MatchValue(value=category_filter) ) ] ) # Search results = self.qdrant.search( collection_name=self.collection, query_vector=query_vector, query_filter=query_filter, limit=limit ) # Parse results work_items = [] for r in results: payload = r.payload work_items.append(WorkItem( cwicr_code=payload.get('code', ''), description=payload.get('description', ''), unit=payload.get('unit', ''), unit_price=float(payload.get('unit_price', 0)), labor_cost=float(payload.get('labor_cost', 0)), material_cost=float(payload.get('material_cost', 0)), equipment_cost=float(payload.get('equipment_cost', 0)), productivity=float(payload.get('productivity', 1)), currency=payload.get('currency', 'USD'), confidence=r.score )) return work_items def decompose_bim_type( self, bim_type: str, category: str ) -> List[str]: """Use LLM to decompose BIM type into work items""" prompt = f""" Decompose this BIM element type into construction work items: BIM Type: {bim_type} Category: {category} List the individual work activities needed to construct this element. For example, "Brick Wall 240mm" decomposes into: - Masonry: Brick laying - Mortar: Cement mortar for joints - Plaster: Internal plaster finish - Paint: Wall painting Return a JSON array of work item descriptions. Example: ["Brick masonry laying", "Cement mortar for brick joints", "Internal cement plaster 15mm"] """ response = self.openai.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": prompt}], response_format={"type": "json_object"} ) try: result = json.loads(response.choices[0].message.content) return result.get('work_items', [bim_type]) except: return [bim_type] def estimate_element( self, bim_type: str, category: str, quantity: float, quantity_unit: str, phase: str = "Construction" ) -> List[CostLineItem]: """Estimate cost for single BIM element type""" # Decompose into work items work_descriptions = self.decompose_bim_type(bim_type, category) line_items = [] for work_desc in work_descriptions: # Search CWICR for matching rate matches = self.search_cwicr(work_desc, limit=1) if not matches: continue best_match = matches[0] # Convert quantity if units don't match adjusted_qty = self._convert_units( quantity, quantity_unit, best_match.unit ) # Calculate costs total = adjusted_qty * best_match.unit_price labor = adjusted_qty * best_match.labor_cost material = adjusted_qty * best_match.material_cost equipment = adjusted_qty * best_match.equipment_cost line_items.append(CostLineItem( bim_type=bim_type, work_item=best_match, quantity=adjusted_qty, quantity_unit=best_match.unit, total_cost=total, labor_cost=labor, material_cost=material, equipment_cost=equipment, phase=phase, trade=self._get_trade(category) )) return line_items def estimate_from_qto( self, qto_data: pd.DataFrame, type_column: str = "Type Name", category_column: str = "Category", quantity_column: str = "Volume" ) -> List[CostLineItem]: """Generate estimate from QTO DataFrame""" all_line_items = [] # Group by type grouped = qto_data.groupby([category_column, type_column]).agg({ quantity_column: 'sum' }).reset_index() for _, row in grouped.iterrows(): items = self.estimate_element( bim_type=row[type_column], category=row[category_column], quantity=row[quantity_column], quantity_unit="m³" # Assume volume, adjust based on category ) all_line_items.extend(items) return all_line_items def _convert_units( self, value: float, from_unit: str, to_unit: str ) -> float: """Convert between units""" # Simplified conversion - expand as needed conversions = { ('m³', 'm³'): 1.0, ('m²', 'm²'): 1.0, ('m', 'm'): 1.0, ('ft³', 'm³'): 0.0283168, ('ft²', 'm²'): 0.092903, ('ft', 'm'): 0.3048, } key = (from_unit.lower(), to_unit.lower()) factor = conversions.get(key, 1.0) return value * factor def _get_trade(self, category: str) -> str: """Map BIM category to trade""" trade_map = { 'Walls': 'Masonry', 'Floors': 'Concrete', 'Structural Columns': 'Concrete', 'Structural Framing': 'Steel', 'Doors': 'Carpentry', 'Windows': 'Glazing', 'Plumbing Fixtures': 'Plumbing', 'Electrical Equipment': 'Electrical', 'Mechanical Equipment': 'HVAC' } return trade_map.get(category, 'General') def generate_estimate_report( self, line_items: List[CostLineItem], project_name: str, output_path: str ) -> dict: """Generate comprehensive estimate report""" # Convert to DataFrame records = [] for item in line_items: records.append({ 'BIM Type': item.bim_type, 'Work Item': item.work_item.description, 'CWICR Code': item.work_item.cwicr_code, 'Quantity': round(item.quantity, 2), 'Unit': item.quantity_unit, 'Unit Price': round(item.work_item.unit_price, 2), 'Labor': round(item.labor_cost, 2), 'Material': round(item.material_cost, 2), 'Equipment': round(item.equipment_cost, 2), 'Total': round(item.total_cost, 2), 'Phase': item.phase, 'Trade': item.trade, 'Currency': item.work_item.currency, 'Confidence': round(item.work_item.confidence, 2) }) df = pd.DataFrame(records) # Calculate totals total_cost = df['Total'].sum() total_labor = df['Labor'].sum() total_material = df['Material'].sum() total_equipment = df['Equipment'].sum() # Summary by trade by_trade = df.groupby('Trade')['Total'].sum().sort_values(ascending=False) # Write Excel excel_path = f"{output_path}/{project_name}_Estimate.xlsx" with pd.ExcelWriter(excel_path, engine='openpyxl') as writer: # Summary sheet summary_data = { 'Metric': ['Total Cost', 'Labor Cost', 'Material Cost', 'Equipment Cost'], 'Value': [total_cost, total_labor, total_material, total_equipment] } pd.DataFrame(summary_data).to_excel(writer, sheet_name='Summary', index=False) # By Trade by_trade.to_frame().to_excel(writer, sheet_name='By Trade') # Detail df.to_excel(writer, sheet_name='Detail', index=False) return { 'excel_path': excel_path, 'total_cost': total_cost, 'total_labor': total_labor, 'total_material': total_material, 'total_equipment': total_equipment, 'by_trade': by_trade.to_dict(), 'line_items': len(df), 'currency': line_items[0].work_item.currency if line_items else 'USD' } # Usage Example def estimate_from_bim_model( model_path: str, qdrant_url: str, language: str = "EN", output_dir: str = "." ) -> dict: """Complete BIM to cost estimation workflow""" import subprocess from pathlib import Path # Step 1: Convert BIM to Excel print("Converting BIM model...") subprocess.run([ r"C:\DDC\RvtExporter.exe", model_path, "complete", "bbox" ]) xlsx_path = Path(model_path).with_suffix('.xlsx') # Step 2: Load QTO data print("Loading quantity data...") df = pd.read_excel(xlsx_path) # Step 3: Initialize estimator estimator = BIMCostEstimator( qdrant_url=qdrant_url, language=language ) # Step 4: Generate estimate print("Generating cost estimate...") line_items = estimator.estimate_from_qto(df) # Step 5: Generate report project_name = Path(model_path).stem result = estimator.generate_estimate_report( line_items=line_items, project_name=project_name, output_path=output_dir ) print(f"\nEstimate Complete!") print(f"Total Cost: {result['currency']} {result['total_cost']:,.2f}") print(f"Excel Report: {result['excel_path']}") return result if __name__ == "__main__": result = estimate_from_bim_model( model_path=r"C:\Projects\Building.rvt", qdrant_url="https://your-qdrant-instance.io", language="DE", output_dir=r"C:\Projects\Estimates" )
See: n8n_4_CAD_(BIM)_Cost_Estimation_Pipeline_4D_5D_with_DDC_CWICR.json
yamlstages: - convert: RvtExporter → XLSX - detect_project: LLM identifies project type - generate_phases: LLM creates construction phases - decompose: LLM breaks types into work items - vector_search: Qdrant finds CWICR matches - calculate: Qty × Unit Price - validate: CTO review - report: HTML + Excel output
╔══════════════════════════════════════════════════════════════╗
║ COST ESTIMATE SUMMARY ║
║ Project: Office Building Berlin ║
║ Date: 2026-01-24 ║
╠══════════════════════════════════════════════════════════════╣
TOTAL PROJECT COST: EUR 4,523,678.00
───────────────────────────────────────────────────────────────
Labor: EUR 1,847,234.00 (41%)
Materials: EUR 2,312,456.00 (51%)
Equipment: EUR 363,988.00 ( 8%)
BY TRADE
───────────────────────────────────────────────────────────────
Concrete: EUR 1,234,567.00 (27%)
Masonry: EUR 876,543.00 (19%)
Steel Structure: EUR 654,321.00 (14%)
MEP: EUR 543,210.00 (12%)
Finishes: EUR 432,109.00 (10%)
Other: EUR 782,928.00 (18%)
CONFIDENCE ANALYSIS
───────────────────────────────────────────────────────────────
High (>0.85): 78%
Medium (0.70-0.85): 18%
Low (<0.70): 4%
╚══════════════════════════════════════════════════════════════╝"Resource-based costing separates physical quantities from volatile prices, enabling transparent and auditable estimates."
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 41,765 | 38,321 | -8% | 1 | 1 | 0% | 6,937 | 13,115 | +89% | 0 | 0 | — |
case-02 | fail→fail | 50,364 | 36,630 | -27% | 1 | 1 | 0% | 8,252 | 13,098 | +59% | 0 | 0 | — |
case-03 | pass→pass | 24,459 | 29,721 | +22% | 1 | 1 | 0% | 4,070 | 10,399 | +156% | 0 | 0 | — |
case-04 | pass→pass | 12,619 | 14,283 | +13% | 1 | 1 | 0% | 2,482 | 7,838 | +216% | 0 | 0 | — |
case-05 | pass→pass | 19,551 | 20,488 | +5% | 1 | 1 | 0% | 3,291 | 8,415 | +156% | 0 | 0 | — |
case-06 | fail→pass | 15,384 | 9,782 | -36% | 1 | 1 | 0% | 2,950 | 6,688 | +127% | 0 | 0 | — |
case-07 | fail→pass | 13,593 | 8,825 | -35% | 1 | 1 | 0% | 2,545 | 6,596 | +159% | 0 | 0 | — |
case-08 | fail→pass | 7,973 | 2,916 | -63% | 1 | 1 | 0% | 1,321 | 5,477 | +315% | 0 | 0 | — |
case-09 | fail→pass | 6,605 | 2,530 | -62% | 1 | 1 | 0% | 1,099 | 5,150 | +369% | 0 | 0 | — |
case-10 | fail→pass | 9,633 | 2,259 | -77% | 1 | 1 | 0% | 1,582 | 5,212 | +229% | 0 | 0 | — |
case-11 | pass→pass | 12,096 | 13,384 | +11% | 1 | 1 | 0% | 2,081 | 7,231 | +247% | 0 | 0 | — |
case-12 | pass→pass | 13,084 | 14,050 | +7% | 1 | 1 | 0% | 2,251 | 7,481 | +232% | 0 | 0 | — |
case-17 | pass→pass | 11,782 | 3,756 | -68% | 1 | 1 | 0% | 2,144 | 5,543 | +159% | 0 | 0 | — |
case-13 | pass→pass | 4,407 | 3,494 | -21% | 1 | 1 | 0% | 697 | 5,540 | +695% | 0 | 0 | — |
case-14 | pass→pass | 7,077 | 3,204 | -55% | 1 | 1 | 0% | 1,307 | 5,475 | +319% | 0 | 0 | — |
case-15 | fail→fail | 12,846 | 9,990 | -22% | 1 | 1 | 0% | 2,379 | 6,752 | +184% | 0 | 0 | — |
case-16 | fail→fail | 13,585 | 3,355 | -75% | 1 | 1 | 0% | 2,317 | 5,386 | +132% | 0 | 0 | — |
case-18 | fail→pass | 8,538 | 2,495 | -71% | 1 | 1 | 0% | 1,420 | 5,237 | +269% | 0 | 0 | — |
case-19 | fail→pass | 15,783 | 2,401 | -85% | 1 | 1 | 0% | 2,768 | 5,218 | +89% | 0 | 0 | — |
case-20 | pass→pass | 12,196 | 12,671 | +4% | 1 | 1 | 0% | 2,313 | 7,417 | +221% | 0 | 0 | — |
case-21 | fail→pass | 16,846 | 18,213 | +8% | 1 | 1 | 0% | 2,859 | 7,819 | +173% | 0 | 0 | — |
case-22 | fail→pass | 14,928 | 9,814 | -34% | 1 | 1 | 0% | 2,548 | 6,771 | +166% | 0 | 0 | — |
case-23 | fail→pass | 13,602 | 7,898 | -42% | 1 | 1 | 0% | 2,075 | 6,416 | +209% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +43 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 8/22/2026 | +45% |
Other measured skills in the registry, with their headline benchmark lift.