Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Build cost assemblies from CWICR work items. Combine multiple items into reusable templates for common construction elements.
.claude/skills/datadrivenconstruction-cwicr-assembly-builder/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 617% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 16% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 164% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 71% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 124% | 0% |
Estimating repetitive elements requires:
Build and manage assemblies of CWICR work items that can be applied as templates to speed up estimating and ensure completeness.
pythonimport pandas as pd import json from typing import Dict, Any, List, Optional from dataclasses import dataclass, field from enum import Enum from datetime import datetime class AssemblyType(Enum): """Types of assemblies.""" STRUCTURAL = "structural" ARCHITECTURAL = "architectural" MECHANICAL = "mechanical" ELECTRICAL = "electrical" SITEWORK = "sitework" GENERAL = "general" @dataclass class AssemblyItem: """Single item in assembly.""" work_item_code: str description: str quantity_per_unit: float # Quantity per assembly unit unit: str unit_cost: float total_cost: float notes: str = "" @dataclass class Assembly: """Complete assembly definition.""" assembly_code: str name: str description: str assembly_type: AssemblyType unit: str # Assembly unit (e.g., "m2", "each", "LF") items: List[AssemblyItem] total_cost_per_unit: float labor_hours_per_unit: float created_date: datetime version: int = 1 class CWICRAssemblyBuilder: """Build and manage assemblies from CWICR data.""" def __init__(self, cwicr_data: pd.DataFrame): self.cwicr = cwicr_data self._index_cwicr() self._assemblies: Dict[str, Assembly] = {} def _index_cwicr(self): """Index CWICR data.""" if 'work_item_code' in self.cwicr.columns: self._cwicr_index = self.cwicr.set_index('work_item_code') else: self._cwicr_index = None def _get_item_cost(self, code: str) -> Tuple[float, float, str]: """Get item unit cost and labor hours.""" if self._cwicr_index is None or code not in self._cwicr_index.index: return (0, 0, 'unit') item = self._cwicr_index.loc[code] labor = float(item.get('labor_cost', 0) or 0) material = float(item.get('material_cost', 0) or 0) equipment = float(item.get('equipment_cost', 0) or 0) labor_hours = float(item.get('labor_norm', item.get('labor_hours', 0)) or 0) unit = str(item.get('unit', 'unit')) return (labor + material + equipment, labor_hours, unit) def create_assembly(self, assembly_code: str, name: str, description: str, assembly_type: AssemblyType, unit: str, items: List[Dict[str, Any]]) -> Assembly: """Create new assembly from work items.""" assembly_items = [] total_cost = 0 total_hours = 0 for item_def in items: code = item_def.get('work_item_code', item_def.get('code')) qty_per_unit = item_def.get('quantity_per_unit', 1) notes = item_def.get('notes', '') unit_cost, labor_hours, item_unit = self._get_item_cost(code) # Get description from CWICR if self._cwicr_index is not None and code in self._cwicr_index.index: desc = str(self._cwicr_index.loc[code].get('description', code)) else: desc = item_def.get('description', code) item_total = unit_cost * qty_per_unit assembly_items.append(AssemblyItem( work_item_code=code, description=desc, quantity_per_unit=qty_per_unit, unit=item_unit, unit_cost=round(unit_cost, 2), total_cost=round(item_total, 2), notes=notes )) total_cost += item_total total_hours += labor_hours * qty_per_unit assembly = Assembly( assembly_code=assembly_code, name=name, description=description, assembly_type=assembly_type, unit=unit, items=assembly_items, total_cost_per_unit=round(total_cost, 2), labor_hours_per_unit=round(total_hours, 2), created_date=datetime.now(), version=1 ) self._assemblies[assembly_code] = assembly return assembly def apply_assembly(self, assembly_code: str, quantity: float, location_factor: float = 1.0) -> Dict[str, Any]: """Apply assembly to get estimate.""" assembly = self._assemblies.get(assembly_code) if assembly is None: return {'error': f"Assembly {assembly_code} not found"} items = [] total_cost = 0 total_hours = 0 for item in assembly.items: qty = item.quantity_per_unit * quantity cost = item.total_cost * quantity * location_factor hours = qty * (item.unit_cost / 50 if item.unit_cost > 0 else 0) # Approximate labor hours items.append({ 'work_item_code': item.work_item_code, 'description': item.description, 'quantity': round(qty, 2), 'unit': item.unit, 'cost': round(cost, 2) }) total_cost += cost total_hours += hours return { 'assembly_code': assembly_code, 'assembly_name': assembly.name, 'quantity': quantity, 'unit': assembly.unit, 'location_factor': location_factor, 'items': items, 'total_cost': round(total_cost, 2), 'total_labor_hours': round(total_hours, 2), 'cost_per_unit': round(total_cost / quantity, 2) if quantity > 0 else 0 } def get_assembly(self, assembly_code: str) -> Optional[Assembly]: """Get assembly by code.""" return self._assemblies.get(assembly_code) def list_assemblies(self, assembly_type: AssemblyType = None) -> List[Dict[str, Any]]: """List all assemblies.""" assemblies = self._assemblies.values() if assembly_type: assemblies = [a for a in assemblies if a.assembly_type == assembly_type] return [ { 'code': a.assembly_code, 'name': a.name, 'type': a.assembly_type.value, 'unit': a.unit, 'cost_per_unit': a.total_cost_per_unit, 'item_count': len(a.items) } for a in assemblies ] def clone_assembly(self, source_code: str, new_code: str, new_name: str = None) -> Optional[Assembly]: """Clone existing assembly.""" source = self._assemblies.get(source_code) if source is None: return None new_assembly = Assembly( assembly_code=new_code, name=new_name or f"{source.name} (Copy)", description=source.description, assembly_type=source.assembly_type, unit=source.unit, items=source.items.copy(), total_cost_per_unit=source.total_cost_per_unit, labor_hours_per_unit=source.labor_hours_per_unit, created_date=datetime.now(), version=1 ) self._assemblies[new_code] = new_assembly return new_assembly def compare_assemblies(self, codes: List[str], quantity: float = 1) -> pd.DataFrame: """Compare multiple assemblies.""" data = [] for code in codes: assembly = self._assemblies.get(code) if assembly: result = self.apply_assembly(code, quantity) data.append({ 'Assembly': assembly.name, 'Code': code, 'Unit': assembly.unit, 'Cost/Unit': assembly.total_cost_per_unit, 'Hours/Unit': assembly.labor_hours_per_unit, f'Total ({quantity} {assembly.unit})': result['total_cost'], 'Items': len(assembly.items) }) return pd.DataFrame(data) def create_standard_assemblies(self): """Create standard construction assemblies.""" # Concrete slab assembly self.create_assembly( assembly_code="SLAB-100", name="Concrete Slab 100mm", description="Standard 100mm concrete slab on grade", assembly_type=AssemblyType.STRUCTURAL, unit="m2", items=[ {'code': 'PREP-001', 'quantity_per_unit': 1.0, 'notes': 'Subgrade preparation'}, {'code': 'GRAVEL-001', 'quantity_per_unit': 0.15, 'notes': '150mm gravel base'}, {'code': 'VAPOR-001', 'quantity_per_unit': 1.1, 'notes': 'Vapor barrier'}, {'code': 'MESH-001', 'quantity_per_unit': 1.1, 'notes': 'Welded wire mesh'}, {'code': 'CONC-001', 'quantity_per_unit': 0.1, 'notes': '100mm concrete'}, {'code': 'FINISH-001', 'quantity_per_unit': 1.0, 'notes': 'Power trowel finish'} ] ) # Stud wall assembly self.create_assembly( assembly_code="WALL-STUD", name="Metal Stud Wall", description="Metal stud wall with drywall both sides", assembly_type=AssemblyType.ARCHITECTURAL, unit="m2", items=[ {'code': 'TRACK-001', 'quantity_per_unit': 0.8, 'notes': 'Floor/ceiling track'}, {'code': 'STUD-001', 'quantity_per_unit': 2.5, 'notes': 'Studs @ 400mm OC'}, {'code': 'INSUL-001', 'quantity_per_unit': 1.0, 'notes': 'Batt insulation'}, {'code': 'GYP-001', 'quantity_per_unit': 2.2, 'notes': 'Drywall both sides'}, {'code': 'TAPE-001', 'quantity_per_unit': 2.0, 'notes': 'Tape and mud'} ] ) def export_assemblies(self, output_path: str) -> str: """Export assemblies to Excel.""" with pd.ExcelWriter(output_path, engine='openpyxl') as writer: # Summary summary_df = pd.DataFrame([ { 'Code': a.assembly_code, 'Name': a.name, 'Type': a.assembly_type.value, 'Unit': a.unit, 'Cost/Unit': a.total_cost_per_unit, 'Hours/Unit': a.labor_hours_per_unit, 'Items': len(a.items), 'Version': a.version } for a in self._assemblies.values() ]) summary_df.to_excel(writer, sheet_name='Assemblies', index=False) # Details for each assembly for code, assembly in self._assemblies.items(): if len(code) > 25: sheet_name = code[:25] else: sheet_name = code detail_df = pd.DataFrame([ { 'Work Item': item.work_item_code, 'Description': item.description, 'Qty/Unit': item.quantity_per_unit, 'Item Unit': item.unit, 'Unit Cost': item.unit_cost, 'Total Cost': item.total_cost, 'Notes': item.notes } for item in assembly.items ]) detail_df.to_excel(writer, sheet_name=sheet_name, index=False) return output_path def save_library(self, filepath: str): """Save assembly library to JSON.""" data = {} for code, assembly in self._assemblies.items(): data[code] = { 'assembly_code': assembly.assembly_code, 'name': assembly.name, 'description': assembly.description, 'assembly_type': assembly.assembly_type.value, 'unit': assembly.unit, 'items': [ { 'work_item_code': item.work_item_code, 'description': item.description, 'quantity_per_unit': item.quantity_per_unit, 'unit': item.unit, 'notes': item.notes } for item in assembly.items ], 'version': assembly.version } with open(filepath, 'w') as f: json.dump(data, f, indent=2)
python# Load CWICR data cwicr = pd.read_parquet("TR_workitems_costs_resources_DDC_CWICR.parquet") # Initialize builder builder = CWICRAssemblyBuilder(cwicr) # Create assembly builder.create_assembly( assembly_code="FDN-STRIP", name="Strip Foundation", description="Standard strip foundation 600x300", assembly_type=AssemblyType.STRUCTURAL, unit="LM", items=[ {'code': 'EXCV-001', 'quantity_per_unit': 0.5}, {'code': 'CONC-001', 'quantity_per_unit': 0.18}, {'code': 'REBAR-001', 'quantity_per_unit': 15} ] ) # Apply assembly result = builder.apply_assembly("FDN-STRIP", quantity=50) print(f"Total Cost: ${result['total_cost']:,.2f}")
pythonbuilder.create_standard_assemblies() assemblies = builder.list_assemblies() for a in assemblies: print(f"{a['code']}: ${a['cost_per_unit']:.2f}/{a['unit']}")
pythoncomparison = builder.compare_assemblies( ["WALL-STUD", "WALL-BLOCK"], quantity=100 ) print(comparison)
pythonbuilder.clone_assembly("SLAB-100", "SLAB-150", "Concrete Slab 150mm")
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 9,384 | 19,769 | +111% | 1 | 1 | 0% | 891 | 6,386 | +617% | 0 | 0 | — |
case-02 | fail→pass | 34,790 | 22,551 | -35% | 1 | 1 | 0% | 8,280 | 9,596 | +16% | 0 | 0 | — |
case-03 | fail→pass | 50,071 | 12,100 | -76% | 1 | 1 | 0% | 2,660 | 7,015 | +164% | 0 | 0 | — |
case-04 | pass→pass | 13,093 | 49,322 | +277% | 1 | 1 | 0% | 2,810 | 8,052 | +187% | 0 | 0 | — |
case-05 | pass→pass | 26,354 | 24,562 | -7% | 1 | 1 | 0% | 5,387 | 8,944 | +66% | 0 | 0 | — |
case-06 | pass→pass | 17,069 | 21,161 | +24% | 1 | 1 | 0% | 3,384 | 8,136 | +140% | 0 | 0 | — |
case-07 | pass→pass | 13,753 | 14,276 | +4% | 1 | 1 | 0% | 2,719 | 6,895 | +154% | 0 | 0 | — |
case-08 | fail→pass | 15,006 | 8,237 | -45% | 1 | 1 | 0% | 3,355 | 5,733 | +71% | 0 | 0 | — |
case-09 | fail→pass | 16,820 | 16,720 | -1% | 1 | 1 | 0% | 3,348 | 7,485 | +124% | 0 | 0 | — |
case-10 | fail→pass | 12,898 | 6,220 | -52% | 1 | 1 | 0% | 2,466 | 5,259 | +113% | 0 | 0 | — |
case-11 | fail→fail | 13,709 | 11,478 | -16% | 1 | 1 | 0% | 2,637 | 6,206 | +135% | 0 | 0 | — |
case-12 | fail→pass | 17,533 | 2,482 | -86% | 1 | 1 | 0% | 3,019 | 4,443 | +47% | 0 | 0 | — |
case-13 | fail→fail | 10,690 | 6,320 | -41% | 1 | 1 | 0% | 2,037 | 5,255 | +158% | 0 | 0 | — |
case-14 | fail→pass | 24,750 | 6,053 | -76% | 1 | 1 | 0% | 4,294 | 5,356 | +25% | 0 | 0 | — |
case-15 | fail→pass | 6,701 | 4,463 | -33% | 1 | 1 | 0% | 1,136 | 4,866 | +328% | 0 | 0 | — |
case-16 | pass→pass | 7,541 | 4,099 | -46% | 1 | 1 | 0% | 1,194 | 4,688 | +293% | 0 | 0 | — |
case-17 | fail→pass | 16,206 | 3,870 | -76% | 1 | 1 | 0% | 2,624 | 4,778 | +82% | 0 | 0 | — |
case-18 | pass→pass | 10,886 | 8,042 | -26% | 1 | 1 | 0% | 1,941 | 5,705 | +194% | 0 | 0 | — |
case-19 | pass→pass | 6,243 | 3,274 | -48% | 1 | 1 | 0% | 1,053 | 4,652 | +342% | 0 | 0 | — |
case-20 | fail→pass | 32,061 | 3,451 | -89% | 1 | 1 | 0% | 5,811 | 4,433 | -24% | 0 | 0 | — |
case-21 | pass→pass | 19,083 | 3,822 | -80% | 1 | 1 | 0% | 3,400 | 4,742 | +39% | 0 | 0 | — |
case-22 | fail→pass | 7,993 | 5,077 | -36% | 1 | 1 | 0% | 1,363 | 5,044 | +270% | 0 | 0 | — |
case-23 | pass→pass | 10,471 | 2,945 | -72% | 1 | 1 | 0% | 1,891 | 4,498 | +138% | 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, and 22 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +52 percentage points is the difference between those two pass rates over the 22 comparable cases.
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 | +64% |
Other measured skills in the registry, with their headline benchmark lift.