<< All versions
Skill v1.0.0
currentAutomated scan100/100datadrivenconstruction/ddc_skills_for_ai_agents_in_construction/cwicr-work-breakdown
──Details
PublishedSeptember 29, 2026 at 07:56 AM
Content Hashsha256:cbd91d7be3579c0d...
Git SHAce45bbfbdd63
──Files
Files (1 file, 17.1 KB)
SKILL.md17.1 KBactive
SKILL.md · 470 lines · 17.1 KB
version: "1.0.0" name: "cwicr-work-breakdown" description: "Break down CWICR work items into component resources. Decompose aggregate items, analyze resource composition, and generate detailed bills of resources." homepage: "https://datadrivenconstruction.io" metadata: {"openclaw": {"emoji": "🗄️", "os": ["darwin", "linux", "win32"], "homepage": "https://datadrivenconstruction.io", "requires": {"bins": ["python3"]}}}
CWICR Work Breakdown
Business Case
Problem Statement
Work items in CWICR contain aggregated resources:
- What materials make up a concrete work item?
- What labor categories are needed?
- What equipment is involved?
- How to generate detailed resource bills?
Solution
Decompose CWICR work items into their constituent resources (labor, materials, equipment) with quantities and costs.
Business Value
- Transparency - See inside aggregated items
- Procurement - Generate material lists
- Scheduling - Identify resource needs
- Cost control - Track resource consumption
Technical Implementation
python
import pandas as pdimport numpy as npfrom typing import Dict, Any, List, Optional, Tuplefrom dataclasses import dataclass, fieldfrom datetime import datetimefrom enum import Enumfrom collections import defaultdictclass ResourceType(Enum):"""Types of resources in work items."""LABOR = "labor"MATERIAL = "material"EQUIPMENT = "equipment"OVERHEAD = "overhead"@dataclassclass ResourceComponent:"""Single resource component of a work item."""resource_code: strresource_type: ResourceTypedescription: strunit: strquantity_per_unit: float # Per unit of work itemunit_rate: floatcost_per_unit: float # Per unit of work item@dataclassclass WorkItemBreakdown:"""Complete breakdown of a work item."""work_item_code: strwork_item_description: strwork_item_unit: strcomponents: List[ResourceComponent]labor_cost_per_unit: floatmaterial_cost_per_unit: floatequipment_cost_per_unit: floattotal_cost_per_unit: float@dataclassclass BillOfResources:"""Bill of resources for multiple work items."""project_name: strtotal_labor_cost: floattotal_material_cost: floattotal_equipment_cost: floattotal_cost: floatlabor_resources: List[Dict[str, Any]]material_resources: List[Dict[str, Any]]equipment_resources: List[Dict[str, Any]]class CWICRWorkBreakdown:"""Break down work items into resources."""def __init__(self, cwicr_data: pd.DataFrame,resources_data: pd.DataFrame = None):self.work_items = cwicr_dataself.resources = resources_dataself._index_data()def _index_data(self):"""Index data for fast lookup."""if 'work_item_code' in self.work_items.columns:self._work_index = self.work_items.set_index('work_item_code')else:self._work_index = Noneif self.resources is not None and 'resource_code' in self.resources.columns:self._resource_index = self.resources.set_index('resource_code')else:self._resource_index = Nonedef breakdown_work_item(self, work_item_code: str) -> Optional[WorkItemBreakdown]:"""Break down single work item into components."""if self._work_index is None or work_item_code not in self._work_index.index:return Noneitem = self._work_index.loc[work_item_code]components = []# Extract labor componentlabor_norm = float(item.get('labor_norm', 0) or 0)labor_rate = float(item.get('labor_rate', 35) or 35)labor_cost = float(item.get('labor_cost', labor_norm * labor_rate) or labor_norm * labor_rate)if labor_norm > 0:components.append(ResourceComponent(resource_code=f"{work_item_code}-LABOR",resource_type=ResourceType.LABOR,description=f"Labor for {item.get('description', '')}",unit="hr",quantity_per_unit=labor_norm,unit_rate=labor_rate,cost_per_unit=labor_cost))# Extract material componentmaterial_norm = float(item.get('material_norm', 1) or 1)material_cost = float(item.get('material_cost', 0) or 0)if material_cost > 0:components.append(ResourceComponent(resource_code=f"{work_item_code}-MAT",resource_type=ResourceType.MATERIAL,description=str(item.get('material_description', 'Materials')),unit=str(item.get('material_unit', item.get('unit', 'ea'))),quantity_per_unit=material_norm,unit_rate=material_cost / material_norm if material_norm > 0 else material_cost,cost_per_unit=material_cost))# Extract equipment componentequipment_norm = float(item.get('equipment_norm', 0) or 0)equipment_rate = float(item.get('equipment_rate', 0) or 0)equipment_cost = float(item.get('equipment_cost', equipment_norm * equipment_rate) or 0)if equipment_norm > 0 or equipment_cost > 0:components.append(ResourceComponent(resource_code=f"{work_item_code}-EQUIP",resource_type=ResourceType.EQUIPMENT,description=str(item.get('equipment_description', 'Equipment')),unit="hr",quantity_per_unit=equipment_norm,unit_rate=equipment_rate,cost_per_unit=equipment_cost))return WorkItemBreakdown(work_item_code=work_item_code,work_item_description=str(item.get('description', '')),work_item_unit=str(item.get('unit', '')),components=components,labor_cost_per_unit=labor_cost,material_cost_per_unit=material_cost,equipment_cost_per_unit=equipment_cost,total_cost_per_unit=labor_cost + material_cost + equipment_cost)def generate_bill_of_resources(self,items: List[Dict[str, Any]],project_name: str = "Project") -> BillOfResources:"""Generate bill of resources from work items."""labor_agg = defaultdict(lambda: {'hours': 0, 'cost': 0, 'work_items': []})material_agg = defaultdict(lambda: {'quantity': 0, 'cost': 0, 'unit': '', 'work_items': []})equipment_agg = defaultdict(lambda: {'hours': 0, 'cost': 0, 'work_items': []})for item in items:code = item.get('work_item_code', item.get('code'))qty = item.get('quantity', 0)breakdown = self.breakdown_work_item(code)if not breakdown:continuefor component in breakdown.components:scaled_qty = component.quantity_per_unit * qtyscaled_cost = component.cost_per_unit * qtyif component.resource_type == ResourceType.LABOR:key = 'General Labor' # Could be more specific with skill datalabor_agg[key]['hours'] += scaled_qtylabor_agg[key]['cost'] += scaled_costlabor_agg[key]['work_items'].append(code)elif component.resource_type == ResourceType.MATERIAL:key = component.descriptionmaterial_agg[key]['quantity'] += scaled_qtymaterial_agg[key]['cost'] += scaled_costmaterial_agg[key]['unit'] = component.unitmaterial_agg[key]['work_items'].append(code)elif component.resource_type == ResourceType.EQUIPMENT:key = component.descriptionequipment_agg[key]['hours'] += scaled_qtyequipment_agg[key]['cost'] += scaled_costequipment_agg[key]['work_items'].append(code)# Convert to listslabor_resources = [{'resource': name,'hours': round(data['hours'], 1),'cost': round(data['cost'], 2),'work_items': len(set(data['work_items']))}for name, data in labor_agg.items()]material_resources = [{'resource': name,'quantity': round(data['quantity'], 2),'unit': data['unit'],'cost': round(data['cost'], 2),'work_items': len(set(data['work_items']))}for name, data in material_agg.items()]equipment_resources = [{'resource': name,'hours': round(data['hours'], 1),'cost': round(data['cost'], 2),'work_items': len(set(data['work_items']))}for name, data in equipment_agg.items()]total_labor = sum(r['cost'] for r in labor_resources)total_material = sum(r['cost'] for r in material_resources)total_equipment = sum(r['cost'] for r in equipment_resources)return BillOfResources(project_name=project_name,total_labor_cost=round(total_labor, 2),total_material_cost=round(total_material, 2),total_equipment_cost=round(total_equipment, 2),total_cost=round(total_labor + total_material + total_equipment, 2),labor_resources=labor_resources,material_resources=material_resources,equipment_resources=equipment_resources)def get_resource_composition(self, work_item_code: str) -> Dict[str, float]:"""Get percentage composition of work item by resource type."""breakdown = self.breakdown_work_item(work_item_code)if not breakdown or breakdown.total_cost_per_unit == 0:return {'labor': 0, 'material': 0, 'equipment': 0}total = breakdown.total_cost_per_unitreturn {'labor': round(breakdown.labor_cost_per_unit / total * 100, 1),'material': round(breakdown.material_cost_per_unit / total * 100, 1),'equipment': round(breakdown.equipment_cost_per_unit / total * 100, 1)}def analyze_labor_intensity(self,work_items: List[str]) -> pd.DataFrame:"""Analyze labor intensity of work items."""data = []for code in work_items:breakdown = self.breakdown_work_item(code)if breakdown:composition = self.get_resource_composition(code)labor_components = [c for c in breakdown.components if c.resource_type == ResourceType.LABOR]labor_hours = sum(c.quantity_per_unit for c in labor_components)data.append({'work_item_code': code,'description': breakdown.work_item_description,'labor_hours_per_unit': labor_hours,'labor_cost_pct': composition['labor'],'material_cost_pct': composition['material'],'equipment_cost_pct': composition['equipment'],'labor_intensive': composition['labor'] > 50})return pd.DataFrame(data).sort_values('labor_cost_pct', ascending=False)def export_breakdown(self,breakdown: WorkItemBreakdown,output_path: str) -> str:"""Export single work item breakdown."""df = pd.DataFrame([{'Resource Code': c.resource_code,'Type': c.resource_type.value,'Description': c.description,'Unit': c.unit,'Quantity/Unit': c.quantity_per_unit,'Rate': c.unit_rate,'Cost/Unit': c.cost_per_unit}for c in breakdown.components])df.to_excel(output_path, index=False)return output_pathdef export_bill_of_resources(self,bill: BillOfResources,output_path: str) -> str:"""Export bill of resources to Excel."""with pd.ExcelWriter(output_path, engine='openpyxl') as writer:# Summarysummary_df = pd.DataFrame([{'Project': bill.project_name,'Total Labor Cost': bill.total_labor_cost,'Total Material Cost': bill.total_material_cost,'Total Equipment Cost': bill.total_equipment_cost,'Grand Total': bill.total_cost}])summary_df.to_excel(writer, sheet_name='Summary', index=False)# Laborlabor_df = pd.DataFrame(bill.labor_resources)labor_df.to_excel(writer, sheet_name='Labor', index=False)# Materialsmaterial_df = pd.DataFrame(bill.material_resources)material_df.to_excel(writer, sheet_name='Materials', index=False)# Equipmentequipment_df = pd.DataFrame(bill.equipment_resources)equipment_df.to_excel(writer, sheet_name='Equipment', index=False)return output_pathclass ResourceAggregator:"""Aggregate resources across work items."""def __init__(self, breakdown_tool: CWICRWorkBreakdown):self.breakdown = breakdown_tooldef aggregate_by_trade(self,items: List[Dict[str, Any]]) -> Dict[str, Dict[str, float]]:"""Aggregate resources by trade/category."""by_trade = defaultdict(lambda: {'labor_hours': 0, 'labor_cost': 0, 'items': 0})for item in items:code = item.get('work_item_code', item.get('code'))qty = item.get('quantity', 0)# Extract trade from code prefixtrade = code.split('-')[0] if '-' in code else 'General'breakdown = self.breakdown.breakdown_work_item(code)if breakdown:by_trade[trade]['labor_hours'] += breakdown.labor_cost_per_unit / 35 * qty # Estimate hoursby_trade[trade]['labor_cost'] += breakdown.labor_cost_per_unit * qtyby_trade[trade]['items'] += 1return dict(by_trade)def identify_critical_resources(self,bill: BillOfResources,threshold_pct: float = 10) -> Dict[str, List[Dict]]:"""Identify resources that contribute significantly to cost."""critical = {'labor': [],'material': [],'equipment': []}# Laborfor r in bill.labor_resources:if bill.total_labor_cost > 0:pct = r['cost'] / bill.total_labor_cost * 100if pct >= threshold_pct:critical['labor'].append({**r, 'percentage': round(pct, 1)})# Materialsfor r in bill.material_resources:if bill.total_material_cost > 0:pct = r['cost'] / bill.total_material_cost * 100if pct >= threshold_pct:critical['material'].append({**r, 'percentage': round(pct, 1)})# Equipmentfor r in bill.equipment_resources:if bill.total_equipment_cost > 0:pct = r['cost'] / bill.total_equipment_cost * 100if pct >= threshold_pct:critical['equipment'].append({**r, 'percentage': round(pct, 1)})return critical
Quick Start
python
# Load CWICR datacwicr = pd.read_parquet("TR_workitems_costs_resources_DDC_CWICR.parquet")# Initialize breakdown toolbreakdown = CWICRWorkBreakdown(cwicr)# Break down single itemitem_breakdown = breakdown.breakdown_work_item("CONC-001")print(f"Work Item: {item_breakdown.work_item_description}")print(f"Labor: ${item_breakdown.labor_cost_per_unit}")print(f"Material: ${item_breakdown.material_cost_per_unit}")print(f"Equipment: ${item_breakdown.equipment_cost_per_unit}")for comp in item_breakdown.components:print(f" - {comp.resource_type.value}: {comp.description}")
Common Use Cases
1. Bill of Resources
python
items = [{'work_item_code': 'CONC-001', 'quantity': 150},{'work_item_code': 'REBAR-002', 'quantity': 5000},{'work_item_code': 'FORM-003', 'quantity': 300}]bill = breakdown.generate_bill_of_resources(items, "Building A")print(f"Total Labor: ${bill.total_labor_cost:,.2f}")print(f"Total Material: ${bill.total_material_cost:,.2f}")
2. Resource Composition
python
composition = breakdown.get_resource_composition("CONC-001")print(f"Labor: {composition['labor']}%")print(f"Material: {composition['material']}%")
3. Labor Intensity Analysis
python
analysis = breakdown.analyze_labor_intensity(['CONC-001', 'EXCV-002', 'REBAR-003'])print(analysis)
4. Export Report
python
breakdown.export_bill_of_resources(bill, "bill_of_resources.xlsx")
Resources
- GitHub: OpenConstructionEstimate-DDC-CWICR
- DDC Book: Chapter 3.1 - Resource-Based Estimating