cwicr-material-procurement
Generate material procurement lists from CWICR data. Calculate quantities with waste factors, group by supplier categories, and create purchase orders.
What this skill does
# CWICR Material Procurement
## Business Case
### Problem Statement
Material procurement needs accurate quantity lists:
- What materials are needed?
- How much of each with waste allowance?
- When are they needed on site?
- How to group for suppliers?
### Solution
Generate procurement lists from CWICR material data with waste factors, delivery scheduling, and supplier grouping.
### Business Value
- **Accurate quantities** - Based on validated norms
- **Waste included** - Industry-standard waste factors
- **Timely delivery** - Aligned with schedule
- **Cost optimization** - Bulk ordering opportunities
## Technical Implementation
```python
import pandas as pd
import numpy as np
from typing import Dict, Any, List, Optional, Tuple
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
from collections import defaultdict
class MaterialCategory(Enum):
"""Material categories for procurement."""
CONCRETE = "concrete"
STEEL = "steel"
TIMBER = "timber"
MASONRY = "masonry"
FINISHES = "finishes"
MEP = "mep"
INSULATION = "insulation"
ROOFING = "roofing"
EARTHWORK = "earthwork"
OTHER = "other"
class ProcurementPriority(Enum):
"""Procurement priority levels."""
CRITICAL = 1
HIGH = 2
MEDIUM = 3
LOW = 4
@dataclass
class MaterialItem:
"""Single material item for procurement."""
material_code: str
description: str
category: MaterialCategory
unit: str
net_quantity: float
waste_factor: float
gross_quantity: float
unit_price: float
total_cost: float
lead_time_days: int
required_date: datetime
order_date: datetime
supplier: str = ""
work_item_codes: List[str] = field(default_factory=list)
@dataclass
class ProcurementList:
"""Complete procurement list."""
project_name: str
generated_date: datetime
total_items: int
total_cost: float
items: List[MaterialItem]
by_category: Dict[str, float]
by_supplier: Dict[str, List[MaterialItem]]
# Standard waste factors by material type
WASTE_FACTORS = {
'concrete': 0.05, # 5%
'reinforcement': 0.03, # 3%
'formwork': 0.10, # 10%
'masonry': 0.05, # 5%
'timber': 0.08, # 8%
'drywall': 0.10, # 10%
'tiles': 0.10, # 10%
'paint': 0.05, # 5%
'insulation': 0.05, # 5%
'pipes': 0.03, # 3%
'cables': 0.05, # 5%
'default': 0.05 # 5%
}
# Standard lead times by category (days)
LEAD_TIMES = {
'concrete': 1, # Ready-mix
'reinforcement': 7, # Steel delivery
'formwork': 3, # Standard forms
'masonry': 5, # Block delivery
'timber': 5, # Lumber
'structural_steel': 21, # Fabrication
'windows': 28, # Manufacturing
'doors': 14, # Standard doors
'mep': 14, # MEP equipment
'finishes': 7, # Standard finishes
'default': 7
}
class CWICRMaterialProcurement:
"""Generate procurement lists from CWICR data."""
def __init__(self, cwicr_data: pd.DataFrame,
resources_data: pd.DataFrame = None):
self.work_items = cwicr_data
self.resources = resources_data
self._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 = None
def get_waste_factor(self, material_type: str) -> float:
"""Get waste factor for material type."""
material_lower = str(material_type).lower()
for key, factor in WASTE_FACTORS.items():
if key in material_lower:
return factor
return WASTE_FACTORS['default']
def get_lead_time(self, material_type: str) -> int:
"""Get lead time for material type."""
material_lower = str(material_type).lower()
for key, days in LEAD_TIMES.items():
if key in material_lower:
return days
return LEAD_TIMES['default']
def get_category(self, material_type: str) -> MaterialCategory:
"""Determine material category."""
material_lower = str(material_type).lower()
category_mapping = {
'concrete': MaterialCategory.CONCRETE,
'cement': MaterialCategory.CONCRETE,
'steel': MaterialCategory.STEEL,
'rebar': MaterialCategory.STEEL,
'reinforcement': MaterialCategory.STEEL,
'timber': MaterialCategory.TIMBER,
'wood': MaterialCategory.TIMBER,
'lumber': MaterialCategory.TIMBER,
'masonry': MaterialCategory.MASONRY,
'block': MaterialCategory.MASONRY,
'brick': MaterialCategory.MASONRY,
'paint': MaterialCategory.FINISHES,
'tile': MaterialCategory.FINISHES,
'floor': MaterialCategory.FINISHES,
'electrical': MaterialCategory.MEP,
'plumbing': MaterialCategory.MEP,
'hvac': MaterialCategory.MEP,
'insulation': MaterialCategory.INSULATION,
'roof': MaterialCategory.ROOFING
}
for key, cat in category_mapping.items():
if key in material_lower:
return cat
return MaterialCategory.OTHER
def extract_materials(self,
items: List[Dict[str, Any]],
schedule: Dict[str, datetime] = None) -> List[MaterialItem]:
"""Extract material requirements from work items."""
materials = defaultdict(lambda: {
'net_quantity': 0,
'work_items': [],
'required_date': None
})
for item in items:
code = item.get('work_item_code', item.get('code'))
qty = item.get('quantity', 0)
required_date = item.get('required_date')
if self._work_index is not None and code in self._work_index.index:
work_item = self._work_index.loc[code]
# Get material info from work item
material_desc = str(work_item.get('material_description',
work_item.get('description', '')))
material_unit = str(work_item.get('material_unit',
work_item.get('unit', '')))
material_norm = float(work_item.get('material_norm', 1) or 1)
material_cost = float(work_item.get('material_cost', 0) or 0)
# Calculate material quantity
material_qty = qty * material_norm
# Aggregate by material description
mat_key = f"{material_desc}|{material_unit}"
materials[mat_key]['net_quantity'] += material_qty
materials[mat_key]['work_items'].append(code)
materials[mat_key]['description'] = material_desc
materials[mat_key]['unit'] = material_unit
materials[mat_key]['unit_price'] = material_cost / material_norm if material_norm > 0 else 0
if required_date:
if materials[mat_key]['required_date'] is None:
materials[mat_key]['required_date'] = required_date
else:
materials[mat_key]['required_date'] = min(
materials[mat_key]['required_date'], required_date
)
# Convert to MaterialItem list
result = []
for mat_key, data in materials.items():
description = data['description']
waste_factor = self.get_waste_factor(description)
lead_time = self.get_lead_time(description)
net_qty = data['net_quantity']
gross_qty = net_qty * (1 + waste_factor)
unRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.