[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6FJQ1BM2SCBKTZPY4Z0HT6Y":3,"skill-same:01M6FJQ1BM2SCBKTZPY4Z0HT6Y":62},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":29,"linkOnly":30,"markdownContent":31,"markdownBody":32,"frontMatter":33,"refName":35,"commitSha":36,"skillDirectory":37,"forksCount":38,"repositoryHtmlUrl":39,"repositoryHomepageUrl":6,"ownerHtmlUrl":40,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":41,"install":42,"resources":55},"01M6FJQ1BM2SCBKTZPY4Z0HT6Y","cwicr-material-procurement",null,"Generate material procurement lists from CWICR data. Calculate quantities with waste factors, group by supplier categories, and create purchase orders.","kbarbel640-del\u002Fskills","kbarbel640-del","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F255315490?v=4","01M6CJPPQGCCESXMG3K7BBGQ5J","01M6CJPPX2PB2BCZERFVM2C2B3",1,"MIT","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdatadrivenconstruction\u002Fcwicr-material-procurement\u002FSKILL.md",[],[18,19,20,21,22,23,24,25,26,27,28],"procurement","material","cwicr material procurement","create","generate","data","calculate","orders","with","categories","purchase","2026-11-10 10:12:30",false,"---\r\nslug: \"cwicr-material-procurement\"\r\ndisplay_name: \"CWICR Material Procurement\"\r\ndescription: \"Generate material procurement lists from CWICR data. Calculate quantities with waste factors, group by supplier categories, and create purchase orders.\"\r\n---\r\n\r\n# CWICR Material Procurement\r\n\r\n## Business Case\r\n\r\n### Problem Statement\r\nMaterial procurement needs accurate quantity lists:\r\n- What materials are needed?\r\n- How much of each with waste allowance?\r\n- When are they needed on site?\r\n- How to group for suppliers?\r\n\r\n### Solution\r\nGenerate procurement lists from CWICR material data with waste factors, delivery scheduling, and supplier grouping.\r\n\r\n### Business Value\r\n- **Accurate quantities** - Based on validated norms\r\n- **Waste included** - Industry-standard waste factors\r\n- **Timely delivery** - Aligned with schedule\r\n- **Cost optimization** - Bulk ordering opportunities\r\n\r\n## Technical Implementation\r\n\r\n```python\r\nimport pandas as pd\r\nimport numpy as np\r\nfrom typing import Dict, Any, List, Optional, Tuple\r\nfrom dataclasses import dataclass, field\r\nfrom datetime import datetime, timedelta\r\nfrom enum import Enum\r\nfrom collections import defaultdict\r\n\r\n\r\nclass MaterialCategory(Enum):\r\n    \"\"\"Material categories for procurement.\"\"\"\r\n    CONCRETE = \"concrete\"\r\n    STEEL = \"steel\"\r\n    TIMBER = \"timber\"\r\n    MASONRY = \"masonry\"\r\n    FINISHES = \"finishes\"\r\n    MEP = \"mep\"\r\n    INSULATION = \"insulation\"\r\n    ROOFING = \"roofing\"\r\n    EARTHWORK = \"earthwork\"\r\n    OTHER = \"other\"\r\n\r\n\r\nclass ProcurementPriority(Enum):\r\n    \"\"\"Procurement priority levels.\"\"\"\r\n    CRITICAL = 1\r\n    HIGH = 2\r\n    MEDIUM = 3\r\n    LOW = 4\r\n\r\n\r\n@dataclass\r\nclass MaterialItem:\r\n    \"\"\"Single material item for procurement.\"\"\"\r\n    material_code: str\r\n    description: str\r\n    category: MaterialCategory\r\n    unit: str\r\n    net_quantity: float\r\n    waste_factor: float\r\n    gross_quantity: float\r\n    unit_price: float\r\n    total_cost: float\r\n    lead_time_days: int\r\n    required_date: datetime\r\n    order_date: datetime\r\n    supplier: str = \"\"\r\n    work_item_codes: List[str] = field(default_factory=list)\r\n\r\n\r\n@dataclass\r\nclass ProcurementList:\r\n    \"\"\"Complete procurement list.\"\"\"\r\n    project_name: str\r\n    generated_date: datetime\r\n    total_items: int\r\n    total_cost: float\r\n    items: List[MaterialItem]\r\n    by_category: Dict[str, float]\r\n    by_supplier: Dict[str, List[MaterialItem]]\r\n\r\n\r\n# Standard waste factors by material type\r\nWASTE_FACTORS = {\r\n    'concrete': 0.05,      # 5%\r\n    'reinforcement': 0.03, # 3%\r\n    'formwork': 0.10,      # 10%\r\n    'masonry': 0.05,       # 5%\r\n    'timber': 0.08,        # 8%\r\n    'drywall': 0.10,       # 10%\r\n    'tiles': 0.10,         # 10%\r\n    'paint': 0.05,         # 5%\r\n    'insulation': 0.05,    # 5%\r\n    'pipes': 0.03,         # 3%\r\n    'cables': 0.05,        # 5%\r\n    'default': 0.05        # 5%\r\n}\r\n\r\n# Standard lead times by category (days)\r\nLEAD_TIMES = {\r\n    'concrete': 1,         # Ready-mix\r\n    'reinforcement': 7,    # Steel delivery\r\n    'formwork': 3,         # Standard forms\r\n    'masonry': 5,          # Block delivery\r\n    'timber': 5,           # Lumber\r\n    'structural_steel': 21, # Fabrication\r\n    'windows': 28,         # Manufacturing\r\n    'doors': 14,           # Standard doors\r\n    'mep': 14,             # MEP equipment\r\n    'finishes': 7,         # Standard finishes\r\n    'default': 7\r\n}\r\n\r\n\r\nclass CWICRMaterialProcurement:\r\n    \"\"\"Generate procurement lists from CWICR data.\"\"\"\r\n\r\n    def __init__(self, cwicr_data: pd.DataFrame,\r\n                 resources_data: pd.DataFrame = None):\r\n        self.work_items = cwicr_data\r\n        self.resources = resources_data\r\n        self._index_data()\r\n\r\n    def _index_data(self):\r\n        \"\"\"Index data for fast lookup.\"\"\"\r\n        if 'work_item_code' in self.work_items.columns:\r\n            self._work_index = self.work_items.set_index('work_item_code')\r\n        else:\r\n            self._work_index = None\r\n\r\n    def get_waste_factor(self, material_type: str) -> float:\r\n        \"\"\"Get waste factor for material type.\"\"\"\r\n        material_lower = str(material_type).lower()\r\n        for key, factor in WASTE_FACTORS.items():\r\n            if key in material_lower:\r\n                return factor\r\n        return WASTE_FACTORS['default']\r\n\r\n    def get_lead_time(self, material_type: str) -> int:\r\n        \"\"\"Get lead time for material type.\"\"\"\r\n        material_lower = str(material_type).lower()\r\n        for key, days in LEAD_TIMES.items():\r\n            if key in material_lower:\r\n                return days\r\n        return LEAD_TIMES['default']\r\n\r\n    def get_category(self, material_type: str) -> MaterialCategory:\r\n        \"\"\"Determine material category.\"\"\"\r\n        material_lower = str(material_type).lower()\r\n\r\n        category_mapping = {\r\n            'concrete': MaterialCategory.CONCRETE,\r\n            'cement': MaterialCategory.CONCRETE,\r\n            'steel': MaterialCategory.STEEL,\r\n            'rebar': MaterialCategory.STEEL,\r\n            'reinforcement': MaterialCategory.STEEL,\r\n            'timber': MaterialCategory.TIMBER,\r\n            'wood': MaterialCategory.TIMBER,\r\n            'lumber': MaterialCategory.TIMBER,\r\n            'masonry': MaterialCategory.MASONRY,\r\n            'block': MaterialCategory.MASONRY,\r\n            'brick': MaterialCategory.MASONRY,\r\n            'paint': MaterialCategory.FINISHES,\r\n            'tile': MaterialCategory.FINISHES,\r\n            'floor': MaterialCategory.FINISHES,\r\n            'electrical': MaterialCategory.MEP,\r\n            'plumbing': MaterialCategory.MEP,\r\n            'hvac': MaterialCategory.MEP,\r\n            'insulation': MaterialCategory.INSULATION,\r\n            'roof': MaterialCategory.ROOFING\r\n        }\r\n\r\n        for key, cat in category_mapping.items():\r\n            if key in material_lower:\r\n                return cat\r\n        return MaterialCategory.OTHER\r\n\r\n    def extract_materials(self,\r\n                         items: List[Dict[str, Any]],\r\n                         schedule: Dict[str, datetime] = None) -> List[MaterialItem]:\r\n        \"\"\"Extract material requirements from work items.\"\"\"\r\n\r\n        materials = defaultdict(lambda: {\r\n            'net_quantity': 0,\r\n            'work_items': [],\r\n            'required_date': None\r\n        })\r\n\r\n        for item in items:\r\n            code = item.get('work_item_code', item.get('code'))\r\n            qty = item.get('quantity', 0)\r\n            required_date = item.get('required_date')\r\n\r\n            if self._work_index is not None and code in self._work_index.index:\r\n                work_item = self._work_index.loc[code]\r\n\r\n                # Get material info from work item\r\n                material_desc = str(work_item.get('material_description',\r\n                                                   work_item.get('description', '')))\r\n                material_unit = str(work_item.get('material_unit',\r\n                                                   work_item.get('unit', '')))\r\n                material_norm = float(work_item.get('material_norm', 1) or 1)\r\n                material_cost = float(work_item.get('material_cost', 0) or 0)\r\n\r\n                # Calculate material quantity\r\n                material_qty = qty * material_norm\r\n\r\n                # Aggregate by material description\r\n                mat_key = f\"{material_desc}|{material_unit}\"\r\n                materials[mat_key]['net_quantity'] += material_qty\r\n                materials[mat_key]['work_items'].append(code)\r\n                materials[mat_key]['description'] = material_desc\r\n                materials[mat_key]['unit'] = material_unit\r\n                materials[mat_key]['unit_price'] = material_cost \u002F material_norm if material_norm > 0 else 0\r\n\r\n                if required_date:\r\n                    if materials[mat_key]['required_date'] is None:\r\n                        materials[mat_key]['required_date'] = required_date\r\n                    else:\r\n                        materials[mat_key]['required_date'] = min(\r\n                            materials[mat_key]['required_date'], required_date\r\n                        )\r\n\r\n        # Convert to MaterialItem list\r\n        result = []\r\n        for mat_key, data in materials.items():\r\n            description = data['description']\r\n            waste_factor = self.get_waste_factor(description)\r\n            lead_time = self.get_lead_time(description)\r\n            net_qty = data['net_quantity']\r\n            gross_qty = net_qty * (1 + waste_factor)\r\n            unit_price = data.get('unit_price', 0)\r\n\r\n            required_date = data['required_date'] or datetime.now() + timedelta(days=30)\r\n            order_date = required_date - timedelta(days=lead_time)\r\n\r\n            result.append(MaterialItem(\r\n                material_code=mat_key.split('|')[0][:20],\r\n                description=description,\r\n                category=self.get_category(description),\r\n                unit=data['unit'],\r\n                net_quantity=round(net_qty, 2),\r\n                waste_factor=waste_factor,\r\n                gross_quantity=round(gross_qty, 2),\r\n                unit_price=round(unit_price, 2),\r\n                total_cost=round(gross_qty * unit_price, 2),\r\n                lead_time_days=lead_time,\r\n                required_date=required_date,\r\n                order_date=order_date,\r\n                work_item_codes=data['work_items']\r\n            ))\r\n\r\n        return result\r\n\r\n    def generate_procurement_list(self,\r\n                                  items: List[Dict[str, Any]],\r\n                                  project_name: str = \"Project\") -> ProcurementList:\r\n        \"\"\"Generate complete procurement list.\"\"\"\r\n\r\n        materials = self.extract_materials(items)\r\n\r\n        # Group by category\r\n        by_category = defaultdict(float)\r\n        for mat in materials:\r\n            by_category[mat.category.value] += mat.total_cost\r\n\r\n        # Group by supplier (placeholder - would use supplier mapping)\r\n        by_supplier = defaultdict(list)\r\n        for mat in materials:\r\n            supplier = self._suggest_supplier(mat)\r\n            mat.supplier = supplier\r\n            by_supplier[supplier].append(mat)\r\n\r\n        return ProcurementList(\r\n            project_name=project_name,\r\n            generated_date=datetime.now(),\r\n            total_items=len(materials),\r\n            total_cost=sum(m.total_cost for m in materials),\r\n            items=materials,\r\n            by_category=dict(by_category),\r\n            by_supplier=dict(by_supplier)\r\n        )\r\n\r\n    def _suggest_supplier(self, material: MaterialItem) -> str:\r\n        \"\"\"Suggest supplier based on material category.\"\"\"\r\n        supplier_mapping = {\r\n            MaterialCategory.CONCRETE: \"Ready-Mix Supplier\",\r\n            MaterialCategory.STEEL: \"Steel Fabricator\",\r\n            MaterialCategory.TIMBER: \"Lumber Yard\",\r\n            MaterialCategory.MASONRY: \"Masonry Supplier\",\r\n            MaterialCategory.MEP: \"MEP Distributor\",\r\n            MaterialCategory.FINISHES: \"Building Materials\",\r\n            MaterialCategory.INSULATION: \"Insulation Supplier\",\r\n            MaterialCategory.ROOFING: \"Roofing Supplier\"\r\n        }\r\n        return supplier_mapping.get(material.category, \"General Supplier\")\r\n\r\n    def create_purchase_order(self,\r\n                              materials: List[MaterialItem],\r\n                              supplier: str,\r\n                              po_number: str) -> Dict[str, Any]:\r\n        \"\"\"Create purchase order for supplier.\"\"\"\r\n\r\n        po_items = [m for m in materials if m.supplier == supplier]\r\n\r\n        return {\r\n            'po_number': po_number,\r\n            'supplier': supplier,\r\n            'date': datetime.now().isoformat(),\r\n            'delivery_date': min(m.required_date for m in po_items).isoformat() if po_items else None,\r\n            'items': [\r\n                {\r\n                    'description': m.description,\r\n                    'quantity': m.gross_quantity,\r\n                    'unit': m.unit,\r\n                    'unit_price': m.unit_price,\r\n                    'total': m.total_cost\r\n                }\r\n                for m in po_items\r\n            ],\r\n            'subtotal': sum(m.total_cost for m in po_items),\r\n            'item_count': len(po_items)\r\n        }\r\n\r\n    def export_to_excel(self,\r\n                       procurement_list: ProcurementList,\r\n                       output_path: str) -> str:\r\n        \"\"\"Export procurement list to Excel.\"\"\"\r\n\r\n        with pd.ExcelWriter(output_path, engine='openpyxl') as writer:\r\n            # All materials\r\n            items_df = pd.DataFrame([\r\n                {\r\n                    'Description': m.description,\r\n                    'Category': m.category.value,\r\n                    'Unit': m.unit,\r\n                    'Net Qty': m.net_quantity,\r\n                    'Waste %': m.waste_factor * 100,\r\n                    'Gross Qty': m.gross_quantity,\r\n                    'Unit Price': m.unit_price,\r\n                    'Total Cost': m.total_cost,\r\n                    'Lead Time': m.lead_time_days,\r\n                    'Order By': m.order_date.strftime('%Y-%m-%d'),\r\n                    'Required': m.required_date.strftime('%Y-%m-%d'),\r\n                    'Supplier': m.supplier\r\n                }\r\n                for m in procurement_list.items\r\n            ])\r\n            items_df.to_excel(writer, sheet_name='Materials', index=False)\r\n\r\n            # By category\r\n            cat_df = pd.DataFrame([\r\n                {'Category': cat, 'Total Cost': cost}\r\n                for cat, cost in procurement_list.by_category.items()\r\n            ])\r\n            cat_df.to_excel(writer, sheet_name='By Category', index=False)\r\n\r\n            # Summary\r\n            summary_df = pd.DataFrame([{\r\n                'Project': procurement_list.project_name,\r\n                'Generated': procurement_list.generated_date.strftime('%Y-%m-%d'),\r\n                'Total Items': procurement_list.total_items,\r\n                'Total Cost': procurement_list.total_cost\r\n            }])\r\n            summary_df.to_excel(writer, sheet_name='Summary', index=False)\r\n\r\n        return output_path\r\n\r\n    def get_critical_orders(self,\r\n                           procurement_list: ProcurementList,\r\n                           days_ahead: int = 14) -> List[MaterialItem]:\r\n        \"\"\"Get materials that need to be ordered soon.\"\"\"\r\n\r\n        cutoff = datetime.now() + timedelta(days=days_ahead)\r\n        return [\r\n            m for m in procurement_list.items\r\n            if m.order_date \u003C= cutoff\r\n        ]\r\n\r\n    def aggregate_by_material(self,\r\n                              items: List[Dict[str, Any]]) -> pd.DataFrame:\r\n        \"\"\"Aggregate materials across multiple work items.\"\"\"\r\n\r\n        materials = self.extract_materials(items)\r\n\r\n        df = pd.DataFrame([\r\n            {\r\n                'Material': m.description,\r\n                'Category': m.category.value,\r\n                'Total Qty': m.gross_quantity,\r\n                'Unit': m.unit,\r\n                'Total Cost': m.total_cost,\r\n                'Work Items': len(m.work_item_codes)\r\n            }\r\n            for m in materials\r\n        ])\r\n\r\n        return df.sort_values('Total Cost', ascending=False)\r\n```\r\n\r\n## Quick Start\r\n\r\n```python\r\n# Load CWICR data\r\ncwicr = pd.read_parquet(\"ddc_cwicr_en.parquet\")\r\n\r\n# Initialize procurement generator\r\nprocurement = CWICRMaterialProcurement(cwicr)\r\n\r\n# Define work items\r\nitems = [\r\n    {'work_item_code': 'CONC-001', 'quantity': 150},\r\n    {'work_item_code': 'REBAR-002', 'quantity': 5000},\r\n    {'work_item_code': 'FORM-003', 'quantity': 300}\r\n]\r\n\r\n# Generate procurement list\r\nproc_list = procurement.generate_procurement_list(items, \"Building A\")\r\n\r\nprint(f\"Total Items: {proc_list.total_items}\")\r\nprint(f\"Total Cost: ${proc_list.total_cost:,.2f}\")\r\n```\r\n\r\n## Common Use Cases\r\n\r\n### 1. Get Critical Orders\r\n```python\r\ncritical = procurement.get_critical_orders(proc_list, days_ahead=7)\r\nprint(f\"Order immediately: {len(critical)} items\")\r\n```\r\n\r\n### 2. Create Purchase Order\r\n```python\r\npo = procurement.create_purchase_order(\r\n    proc_list.items,\r\n    supplier=\"Steel Fabricator\",\r\n    po_number=\"PO-2024-001\"\r\n)\r\n```\r\n\r\n### 3. Export to Excel\r\n```python\r\nprocurement.export_to_excel(proc_list, \"procurement_list.xlsx\")\r\n```\r\n\r\n### 4. Material Aggregation\r\n```python\r\nmaterials_df = procurement.aggregate_by_material(items)\r\nprint(materials_df.head(10))\r\n```\r\n\r\n## Resources\r\n- **GitHub**: [OpenConstructionEstimate-DDC-CWICR](https:\u002F\u002Fgithub.com\u002Fdatadrivenconstruction\u002FOpenConstructionEstimate-DDC-CWICR)\r\n- **DDC Book**: Chapter 3.1 - Material Resource Planning\r\n","# CWICR Material Procurement\r\n\r\n## Business Case\r\n\r\n### Problem Statement\r\nMaterial procurement needs accurate quantity lists:\r\n- What materials are needed?\r\n- How much of each with waste allowance?\r\n- When are they needed on site?\r\n- How to group for suppliers?\r\n\r\n### Solution\r\nGenerate procurement lists from CWICR material data with waste factors, delivery scheduling, and supplier grouping.\r\n\r\n### Business Value\r\n- **Accurate quantities** - Based on validated norms\r\n- **Waste included** - Industry-standard waste factors\r\n- **Timely delivery** - Aligned with schedule\r\n- **Cost optimization** - Bulk ordering opportunities\r\n\r\n## Technical Implementation\r\n\r\n```python\r\nimport pandas as pd\r\nimport numpy as np\r\nfrom typing import Dict, Any, List, Optional, Tuple\r\nfrom dataclasses import dataclass, field\r\nfrom datetime import datetime, timedelta\r\nfrom enum import Enum\r\nfrom collections import defaultdict\r\n\r\n\r\nclass MaterialCategory(Enum):\r\n    \"\"\"Material categories for procurement.\"\"\"\r\n    CONCRETE = \"concrete\"\r\n    STEEL = \"steel\"\r\n    TIMBER = \"timber\"\r\n    MASONRY = \"masonry\"\r\n    FINISHES = \"finishes\"\r\n    MEP = \"mep\"\r\n    INSULATION = \"insulation\"\r\n    ROOFING = \"roofing\"\r\n    EARTHWORK = \"earthwork\"\r\n    OTHER = \"other\"\r\n\r\n\r\nclass ProcurementPriority(Enum):\r\n    \"\"\"Procurement priority levels.\"\"\"\r\n    CRITICAL = 1\r\n    HIGH = 2\r\n    MEDIUM = 3\r\n    LOW = 4\r\n\r\n\r\n@dataclass\r\nclass MaterialItem:\r\n    \"\"\"Single material item for procurement.\"\"\"\r\n    material_code: str\r\n    description: str\r\n    category: MaterialCategory\r\n    unit: str\r\n    net_quantity: float\r\n    waste_factor: float\r\n    gross_quantity: float\r\n    unit_price: float\r\n    total_cost: float\r\n    lead_time_days: int\r\n    required_date: datetime\r\n    order_date: datetime\r\n    supplier: str = \"\"\r\n    work_item_codes: List[str] = field(default_factory=list)\r\n\r\n\r\n@dataclass\r\nclass ProcurementList:\r\n    \"\"\"Complete procurement list.\"\"\"\r\n    project_name: str\r\n    generated_date: datetime\r\n    total_items: int\r\n    total_cost: float\r\n    items: List[MaterialItem]\r\n    by_category: Dict[str, float]\r\n    by_supplier: Dict[str, List[MaterialItem]]\r\n\r\n\r\n# Standard waste factors by material type\r\nWASTE_FACTORS = {\r\n    'concrete': 0.05,      # 5%\r\n    'reinforcement': 0.03, # 3%\r\n    'formwork': 0.10,      # 10%\r\n    'masonry': 0.05,       # 5%\r\n    'timber': 0.08,        # 8%\r\n    'drywall': 0.10,       # 10%\r\n    'tiles': 0.10,         # 10%\r\n    'paint': 0.05,         # 5%\r\n    'insulation': 0.05,    # 5%\r\n    'pipes': 0.03,         # 3%\r\n    'cables': 0.05,        # 5%\r\n    'default': 0.05        # 5%\r\n}\r\n\r\n# Standard lead times by category (days)\r\nLEAD_TIMES = {\r\n    'concrete': 1,         # Ready-mix\r\n    'reinforcement': 7,    # Steel delivery\r\n    'formwork': 3,         # Standard forms\r\n    'masonry': 5,          # Block delivery\r\n    'timber': 5,           # Lumber\r\n    'structural_steel': 21, # Fabrication\r\n    'windows': 28,         # Manufacturing\r\n    'doors': 14,           # Standard doors\r\n    'mep': 14,             # MEP equipment\r\n    'finishes': 7,         # Standard finishes\r\n    'default': 7\r\n}\r\n\r\n\r\nclass CWICRMaterialProcurement:\r\n    \"\"\"Generate procurement lists from CWICR data.\"\"\"\r\n\r\n    def __init__(self, cwicr_data: pd.DataFrame,\r\n                 resources_data: pd.DataFrame = None):\r\n        self.work_items = cwicr_data\r\n        self.resources = resources_data\r\n        self._index_data()\r\n\r\n    def _index_data(self):\r\n        \"\"\"Index data for fast lookup.\"\"\"\r\n        if 'work_item_code' in self.work_items.columns:\r\n            self._work_index = self.work_items.set_index('work_item_code')\r\n        else:\r\n            self._work_index = None\r\n\r\n    def get_waste_factor(self, material_type: str) -> float:\r\n        \"\"\"Get waste factor for material type.\"\"\"\r\n        material_lower = str(material_type).lower()\r\n        for key, factor in WASTE_FACTORS.items():\r\n            if key in material_lower:\r\n                return factor\r\n        return WASTE_FACTORS['default']\r\n\r\n    def get_lead_time(self, material_type: str) -> int:\r\n        \"\"\"Get lead time for material type.\"\"\"\r\n        material_lower = str(material_type).lower()\r\n        for key, days in LEAD_TIMES.items():\r\n            if key in material_lower:\r\n                return days\r\n        return LEAD_TIMES['default']\r\n\r\n    def get_category(self, material_type: str) -> MaterialCategory:\r\n        \"\"\"Determine material category.\"\"\"\r\n        material_lower = str(material_type).lower()\r\n\r\n        category_mapping = {\r\n            'concrete': MaterialCategory.CONCRETE,\r\n            'cement': MaterialCategory.CONCRETE,\r\n            'steel': MaterialCategory.STEEL,\r\n            'rebar': MaterialCategory.STEEL,\r\n            'reinforcement': MaterialCategory.STEEL,\r\n            'timber': MaterialCategory.TIMBER,\r\n            'wood': MaterialCategory.TIMBER,\r\n            'lumber': MaterialCategory.TIMBER,\r\n            'masonry': MaterialCategory.MASONRY,\r\n            'block': MaterialCategory.MASONRY,\r\n            'brick': MaterialCategory.MASONRY,\r\n            'paint': MaterialCategory.FINISHES,\r\n            'tile': MaterialCategory.FINISHES,\r\n            'floor': MaterialCategory.FINISHES,\r\n            'electrical': MaterialCategory.MEP,\r\n            'plumbing': MaterialCategory.MEP,\r\n            'hvac': MaterialCategory.MEP,\r\n            'insulation': MaterialCategory.INSULATION,\r\n            'roof': MaterialCategory.ROOFING\r\n        }\r\n\r\n        for key, cat in category_mapping.items():\r\n            if key in material_lower:\r\n                return cat\r\n        return MaterialCategory.OTHER\r\n\r\n    def extract_materials(self,\r\n                         items: List[Dict[str, Any]],\r\n                         schedule: Dict[str, datetime] = None) -> List[MaterialItem]:\r\n        \"\"\"Extract material requirements from work items.\"\"\"\r\n\r\n        materials = defaultdict(lambda: {\r\n            'net_quantity': 0,\r\n            'work_items': [],\r\n            'required_date': None\r\n        })\r\n\r\n        for item in items:\r\n            code = item.get('work_item_code', item.get('code'))\r\n            qty = item.get('quantity', 0)\r\n            required_date = item.get('required_date')\r\n\r\n            if self._work_index is not None and code in self._work_index.index:\r\n                work_item = self._work_index.loc[code]\r\n\r\n                # Get material info from work item\r\n                material_desc = str(work_item.get('material_description',\r\n                                                   work_item.get('description', '')))\r\n                material_unit = str(work_item.get('material_unit',\r\n                                                   work_item.get('unit', '')))\r\n                material_norm = float(work_item.get('material_norm', 1) or 1)\r\n                material_cost = float(work_item.get('material_cost', 0) or 0)\r\n\r\n                # Calculate material quantity\r\n                material_qty = qty * material_norm\r\n\r\n                # Aggregate by material description\r\n                mat_key = f\"{material_desc}|{material_unit}\"\r\n                materials[mat_key]['net_quantity'] += material_qty\r\n                materials[mat_key]['work_items'].append(code)\r\n                materials[mat_key]['description'] = material_desc\r\n                materials[mat_key]['unit'] = material_unit\r\n                materials[mat_key]['unit_price'] = material_cost \u002F material_norm if material_norm > 0 else 0\r\n\r\n                if required_date:\r\n                    if materials[mat_key]['required_date'] is None:\r\n                        materials[mat_key]['required_date'] = required_date\r\n                    else:\r\n                        materials[mat_key]['required_date'] = min(\r\n                            materials[mat_key]['required_date'], required_date\r\n                        )\r\n\r\n        # Convert to MaterialItem list\r\n        result = []\r\n        for mat_key, data in materials.items():\r\n            description = data['description']\r\n            waste_factor = self.get_waste_factor(description)\r\n            lead_time = self.get_lead_time(description)\r\n            net_qty = data['net_quantity']\r\n            gross_qty = net_qty * (1 + waste_factor)\r\n            unit_price = data.get('unit_price', 0)\r\n\r\n            required_date = data['required_date'] or datetime.now() + timedelta(days=30)\r\n            order_date = required_date - timedelta(days=lead_time)\r\n\r\n            result.append(MaterialItem(\r\n                material_code=mat_key.split('|')[0][:20],\r\n                description=description,\r\n                category=self.get_category(description),\r\n                unit=data['unit'],\r\n                net_quantity=round(net_qty, 2),\r\n                waste_factor=waste_factor,\r\n                gross_quantity=round(gross_qty, 2),\r\n                unit_price=round(unit_price, 2),\r\n                total_cost=round(gross_qty * unit_price, 2),\r\n                lead_time_days=lead_time,\r\n                required_date=required_date,\r\n                order_date=order_date,\r\n                work_item_codes=data['work_items']\r\n            ))\r\n\r\n        return result\r\n\r\n    def generate_procurement_list(self,\r\n                                  items: List[Dict[str, Any]],\r\n                                  project_name: str = \"Project\") -> ProcurementList:\r\n        \"\"\"Generate complete procurement list.\"\"\"\r\n\r\n        materials = self.extract_materials(items)\r\n\r\n        # Group by category\r\n        by_category = defaultdict(float)\r\n        for mat in materials:\r\n            by_category[mat.category.value] += mat.total_cost\r\n\r\n        # Group by supplier (placeholder - would use supplier mapping)\r\n        by_supplier = defaultdict(list)\r\n        for mat in materials:\r\n            supplier = self._suggest_supplier(mat)\r\n            mat.supplier = supplier\r\n            by_supplier[supplier].append(mat)\r\n\r\n        return ProcurementList(\r\n            project_name=project_name,\r\n            generated_date=datetime.now(),\r\n            total_items=len(materials),\r\n            total_cost=sum(m.total_cost for m in materials),\r\n            items=materials,\r\n            by_category=dict(by_category),\r\n            by_supplier=dict(by_supplier)\r\n        )\r\n\r\n    def _suggest_supplier(self, material: MaterialItem) -> str:\r\n        \"\"\"Suggest supplier based on material category.\"\"\"\r\n        supplier_mapping = {\r\n            MaterialCategory.CONCRETE: \"Ready-Mix Supplier\",\r\n            MaterialCategory.STEEL: \"Steel Fabricator\",\r\n            MaterialCategory.TIMBER: \"Lumber Yard\",\r\n            MaterialCategory.MASONRY: \"Masonry Supplier\",\r\n            MaterialCategory.MEP: \"MEP Distributor\",\r\n            MaterialCategory.FINISHES: \"Building Materials\",\r\n            MaterialCategory.INSULATION: \"Insulation Supplier\",\r\n            MaterialCategory.ROOFING: \"Roofing Supplier\"\r\n        }\r\n        return supplier_mapping.get(material.category, \"General Supplier\")\r\n\r\n    def create_purchase_order(self,\r\n                              materials: List[MaterialItem],\r\n                              supplier: str,\r\n                              po_number: str) -> Dict[str, Any]:\r\n        \"\"\"Create purchase order for supplier.\"\"\"\r\n\r\n        po_items = [m for m in materials if m.supplier == supplier]\r\n\r\n        return {\r\n            'po_number': po_number,\r\n            'supplier': supplier,\r\n            'date': datetime.now().isoformat(),\r\n            'delivery_date': min(m.required_date for m in po_items).isoformat() if po_items else None,\r\n            'items': [\r\n                {\r\n                    'description': m.description,\r\n                    'quantity': m.gross_quantity,\r\n                    'unit': m.unit,\r\n                    'unit_price': m.unit_price,\r\n                    'total': m.total_cost\r\n                }\r\n                for m in po_items\r\n            ],\r\n            'subtotal': sum(m.total_cost for m in po_items),\r\n            'item_count': len(po_items)\r\n        }\r\n\r\n    def export_to_excel(self,\r\n                       procurement_list: ProcurementList,\r\n                       output_path: str) -> str:\r\n        \"\"\"Export procurement list to Excel.\"\"\"\r\n\r\n        with pd.ExcelWriter(output_path, engine='openpyxl') as writer:\r\n            # All materials\r\n            items_df = pd.DataFrame([\r\n                {\r\n                    'Description': m.description,\r\n                    'Category': m.category.value,\r\n                    'Unit': m.unit,\r\n                    'Net Qty': m.net_quantity,\r\n                    'Waste %': m.waste_factor * 100,\r\n                    'Gross Qty': m.gross_quantity,\r\n                    'Unit Price': m.unit_price,\r\n                    'Total Cost': m.total_cost,\r\n                    'Lead Time': m.lead_time_days,\r\n                    'Order By': m.order_date.strftime('%Y-%m-%d'),\r\n                    'Required': m.required_date.strftime('%Y-%m-%d'),\r\n                    'Supplier': m.supplier\r\n                }\r\n                for m in procurement_list.items\r\n            ])\r\n            items_df.to_excel(writer, sheet_name='Materials', index=False)\r\n\r\n            # By category\r\n            cat_df = pd.DataFrame([\r\n                {'Category': cat, 'Total Cost': cost}\r\n                for cat, cost in procurement_list.by_category.items()\r\n            ])\r\n            cat_df.to_excel(writer, sheet_name='By Category', index=False)\r\n\r\n            # Summary\r\n            summary_df = pd.DataFrame([{\r\n                'Project': procurement_list.project_name,\r\n                'Generated': procurement_list.generated_date.strftime('%Y-%m-%d'),\r\n                'Total Items': procurement_list.total_items,\r\n                'Total Cost': procurement_list.total_cost\r\n            }])\r\n            summary_df.to_excel(writer, sheet_name='Summary', index=False)\r\n\r\n        return output_path\r\n\r\n    def get_critical_orders(self,\r\n                           procurement_list: ProcurementList,\r\n                           days_ahead: int = 14) -> List[MaterialItem]:\r\n        \"\"\"Get materials that need to be ordered soon.\"\"\"\r\n\r\n        cutoff = datetime.now() + timedelta(days=days_ahead)\r\n        return [\r\n            m for m in procurement_list.items\r\n            if m.order_date \u003C= cutoff\r\n        ]\r\n\r\n    def aggregate_by_material(self,\r\n                              items: List[Dict[str, Any]]) -> pd.DataFrame:\r\n        \"\"\"Aggregate materials across multiple work items.\"\"\"\r\n\r\n        materials = self.extract_materials(items)\r\n\r\n        df = pd.DataFrame([\r\n            {\r\n                'Material': m.description,\r\n                'Category': m.category.value,\r\n                'Total Qty': m.gross_quantity,\r\n                'Unit': m.unit,\r\n                'Total Cost': m.total_cost,\r\n                'Work Items': len(m.work_item_codes)\r\n            }\r\n            for m in materials\r\n        ])\r\n\r\n        return df.sort_values('Total Cost', ascending=False)\r\n```\r\n\r\n## Quick Start\r\n\r\n```python\r\n# Load CWICR data\r\ncwicr = pd.read_parquet(\"ddc_cwicr_en.parquet\")\r\n\r\n# Initialize procurement generator\r\nprocurement = CWICRMaterialProcurement(cwicr)\r\n\r\n# Define work items\r\nitems = [\r\n    {'work_item_code': 'CONC-001', 'quantity': 150},\r\n    {'work_item_code': 'REBAR-002', 'quantity': 5000},\r\n    {'work_item_code': 'FORM-003', 'quantity': 300}\r\n]\r\n\r\n# Generate procurement list\r\nproc_list = procurement.generate_procurement_list(items, \"Building A\")\r\n\r\nprint(f\"Total Items: {proc_list.total_items}\")\r\nprint(f\"Total Cost: ${proc_list.total_cost:,.2f}\")\r\n```\r\n\r\n## Common Use Cases\r\n\r\n### 1. Get Critical Orders\r\n```python\r\ncritical = procurement.get_critical_orders(proc_list, days_ahead=7)\r\nprint(f\"Order immediately: {len(critical)} items\")\r\n```\r\n\r\n### 2. Create Purchase Order\r\n```python\r\npo = procurement.create_purchase_order(\r\n    proc_list.items,\r\n    supplier=\"Steel Fabricator\",\r\n    po_number=\"PO-2024-001\"\r\n)\r\n```\r\n\r\n### 3. Export to Excel\r\n```python\r\nprocurement.export_to_excel(proc_list, \"procurement_list.xlsx\")\r\n```\r\n\r\n### 4. Material Aggregation\r\n```python\r\nmaterials_df = procurement.aggregate_by_material(items)\r\nprint(materials_df.head(10))\r\n```\r\n\r\n## Resources\r\n- **GitHub**: [OpenConstructionEstimate-DDC-CWICR](https:\u002F\u002Fgithub.com\u002Fdatadrivenconstruction\u002FOpenConstructionEstimate-DDC-CWICR)\r\n- **DDC Book**: Chapter 3.1 - Material Resource Planning\r\n",{"slug":5,"description":7,"display_name":34},"CWICR Material Procurement","main","e7e336ac628c3e3bd771aff1aaa469c93778f94a","skills\u002Fdatadrivenconstruction\u002Fcwicr-material-procurement",0,"https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del","2026-08-23 22:39:37",{"source":8,"skillName":5,"commands":43,"prompt":53,"zipAvailable":54},[44,47,50],{"tool":45,"command":46},"npx","npx skills add kbarbel640-del\u002Fskills --skill cwicr-material-procurement",{"tool":48,"command":49},"bunx","bunx skills add kbarbel640-del\u002Fskills --skill cwicr-material-procurement",{"tool":51,"command":52},"pnpm","pnpm dlx skills add kbarbel640-del\u002Fskills --skill cwicr-material-procurement","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdatadrivenconstruction\u002Fcwicr-material-procurement\u002FSKILL.md",true,[56],{"path":57,"resourceType":58,"resourceTypeDesc":59,"sizeBytes":60,"sourceHtmlUrl":61},"_meta.json","REFERENCE","参考文档",320,"https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdatadrivenconstruction\u002Fcwicr-material-procurement\u002F_meta.json",[63,93,121,142,148,168],{"skillNo":64,"name":65,"nameZh":6,"description":66,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":67,"categories":68,"keywords":74,"publishedAt":29},"01M6G8J55967D6281ACMKAKTEQ","crowd-prompting","A marketplace where AI agents improve prompts, system instructions, tool descriptions, and other text-based content with domain expertise from real-world operations — and earn tokens for valuable contributions.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzzadrian\u002Fcrowd-prompting\u002FSKILL.md",[69],{"categoryNo":70,"categoryName":71,"taxonomyType":72,"taxonomyTypeDesc":73},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[75,76,77,26,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92],"crowd prompting","prompting","api","tokens","domain","world","prompts","operations","instructions","text","tool","system","improve","productivity","marketplace","https","content","ai agents",{"skillNo":94,"name":95,"nameZh":6,"description":96,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":97,"categories":98,"keywords":104,"publishedAt":29},"01M6G8J0PKPAYVET6BWE8W4C26","dilemm-ai","Compete in DilemmAI, the prisoner's dilemma AI arena at dilemm.ai. Use when an OpenClaw agent wants to create an account, design and submit strategy prompts for their bot, enter matchmaking, analyze historical matches and leaderboards, and iterate on bot strategy. Requires headless browser automation (no API available yet).","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzyori\u002Fdilemm-ai\u002FSKILL.md",[99],{"categoryNo":100,"categoryName":101,"taxonomyType":102,"taxonomyTypeDesc":103},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[105,77,106,107,108,109,21,110,111,112,113,114,115,116,117,118,119,81,120],"dilemm ai","arena","analyze","browser","openclaw agent","automation","browser automation","bot","openclaw","design","strategy","headless","compete","submit","matchmaking","use",{"skillNo":122,"name":123,"nameZh":6,"description":124,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":125,"categories":126,"keywords":130,"publishedAt":29},"01M6G8HWAF3K9SW7NP3Z83CPJ6","arxiv-paper-reviews","Interact with arXiv Crawler API to fetch papers, read reviews, and submit comments. Use when working with arXiv papers, fetching paper lists by date\u002Fcategory\u002Finterest, viewing paper details with comments, or submitting paper reviews via API at http:\u002F\u002F150.158.152.82:8000.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzxrys\u002Fweak-accept\u002FSKILL.md",[127],{"categoryNo":128,"categoryName":129,"taxonomyType":102,"taxonomyTypeDesc":103},"01JBSEEDCAT000000000000021","资料检索与研究",[131,132,133,134,135,136,137,26,138,118,120,139,77,140,141],"arxiv paper reviews","reviews","arxiv","paper","crawler","papers","comments","fetch","read","http","via",{"skillNo":143,"name":123,"nameZh":6,"description":124,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":144,"categories":145,"keywords":147,"publishedAt":29},"01M6G8HQJ7N7WMY6YV7D7SSJ16","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzxrys\u002Farxiv-paper-reviews\u002FSKILL.md",[146],{"categoryNo":128,"categoryName":129,"taxonomyType":102,"taxonomyTypeDesc":103},[134,133,132,131,141,136,137,26,135,118,120,138,139,77,140],{"skillNo":149,"name":150,"nameZh":6,"description":151,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":152,"categories":153,"keywords":154,"publishedAt":29},"01M6G8HM95Y64JNVFC2HN4E2BG","git-summary","Get a quick summary of the current Git repository including status, recent commits, branches, and contributors.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzweack\u002Fgit-summary\u002FSKILL.md",[],[155,156,157,158,159,160,161,162,163,164,165,166,167],"git","summary","git summary","commits","repository","win32","status","linux","contributors","true","quick","get","branches",{"skillNo":169,"name":170,"nameZh":6,"description":171,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":172,"categories":173,"keywords":178,"publishedAt":29},"01M6G8HGEGF05CNEJCGVYFSC4R","agent-constitution","Interact with AgentConstitution governance contracts on Base Sepolia. Check compliance, read rules, log actions, query governance state.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fztsalexey\u002Fagent-constitution\u002FSKILL.md",[174,175],{"categoryNo":100,"categoryName":101,"taxonomyType":102,"taxonomyTypeDesc":103},{"categoryNo":176,"categoryName":177,"taxonomyType":72,"taxonomyTypeDesc":73},"01JBSEEDCAT000000000000037","律师与法务",[179,180,181,182,26,183,184,35,185,139,186,187,188,189,190,90,191,192,193],"agent constitution","constitution","contracts","tree","query","base","log","check","Rules","projects","compliance","actions","state","governance","github"]