[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6FJQSPQCE22QA7DAQPYV2EN":3,"skill-same:01M6FJQSPQCE22QA7DAQPYV2EN":61},{"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":28,"linkOnly":29,"markdownContent":30,"markdownBody":31,"frontMatter":32,"refName":34,"commitSha":35,"skillDirectory":36,"forksCount":37,"repositoryHtmlUrl":38,"repositoryHomepageUrl":6,"ownerHtmlUrl":39,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":40,"install":41,"resources":54},"01M6FJQSPQCE22QA7DAQPYV2EN","cwicr-rate-updater",null,"Update CWICR resource rates with current market prices. Integrate external price data, apply inflation adjustments, and maintain rate history.","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-rate-updater\u002FSKILL.md",[],[18,19,20,21,22,23,24,25,26,27],"updater","cwicr rate updater","update","data","history","integrate","Market","maintain","price","with","2026-11-10 10:12:30",false,"---\r\nslug: \"cwicr-rate-updater\"\r\ndisplay_name: \"CWICR Rate Updater\"\r\ndescription: \"Update CWICR resource rates with current market prices. Integrate external price data, apply inflation adjustments, and maintain rate history.\"\r\n---\r\n\r\n# CWICR Rate Updater\r\n\r\n## Business Case\r\n\r\n### Problem Statement\r\nResource rates become outdated:\r\n- Material prices fluctuate with market\r\n- Labor rates change annually\r\n- Equipment costs vary by region\r\n- Historical rates need adjustment\r\n\r\n### Solution\r\nSystematic rate updates integrating market data, inflation indices, and regional factors while maintaining audit trail.\r\n\r\n### Business Value\r\n- **Accuracy** - Current market pricing\r\n- **Flexibility** - Update specific resources or categories\r\n- **Audit trail** - Track rate changes over time\r\n- **Automation** - Integrate with price APIs\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, Callable\r\nfrom dataclasses import dataclass, field\r\nfrom datetime import datetime, date\r\nfrom enum import Enum\r\nimport json\r\n\r\n\r\nclass RateType(Enum):\r\n    \"\"\"Types of rates.\"\"\"\r\n    LABOR = \"labor\"\r\n    MATERIAL = \"material\"\r\n    EQUIPMENT = \"equipment\"\r\n    SUBCONTRACT = \"subcontract\"\r\n\r\n\r\nclass AdjustmentMethod(Enum):\r\n    \"\"\"Methods for rate adjustment.\"\"\"\r\n    FIXED_AMOUNT = \"fixed_amount\"\r\n    PERCENTAGE = \"percentage\"\r\n    MULTIPLIER = \"multiplier\"\r\n    REPLACEMENT = \"replacement\"\r\n\r\n\r\n@dataclass\r\nclass RateChange:\r\n    \"\"\"Record of rate change.\"\"\"\r\n    resource_code: str\r\n    rate_type: RateType\r\n    old_rate: float\r\n    new_rate: float\r\n    change_percent: float\r\n    change_date: datetime\r\n    reason: str\r\n    source: str\r\n\r\n\r\n@dataclass\r\nclass RateUpdateResult:\r\n    \"\"\"Result of rate update operation.\"\"\"\r\n    total_items: int\r\n    updated: int\r\n    unchanged: int\r\n    errors: int\r\n    changes: List[RateChange]\r\n    summary: Dict[str, Any]\r\n\r\n\r\nclass CWICRRateUpdater:\r\n    \"\"\"Update resource rates in CWICR data.\"\"\"\r\n\r\n    def __init__(self, cwicr_data: pd.DataFrame):\r\n        self.data = cwicr_data.copy()\r\n        self.change_log: List[RateChange] = []\r\n        self.original_data = cwicr_data.copy()\r\n\r\n    def get_current_rates(self,\r\n                          rate_type: RateType = None,\r\n                          category: str = None) -> pd.DataFrame:\r\n        \"\"\"Get current rates, optionally filtered.\"\"\"\r\n\r\n        df = self.data.copy()\r\n\r\n        # Filter by category if specified\r\n        if category and 'category' in df.columns:\r\n            df = df[df['category'].str.contains(category, case=False, na=False)]\r\n\r\n        # Select relevant columns based on rate type\r\n        rate_columns = {\r\n            RateType.LABOR: ['work_item_code', 'description', 'labor_rate', 'labor_cost'],\r\n            RateType.MATERIAL: ['work_item_code', 'description', 'material_cost'],\r\n            RateType.EQUIPMENT: ['work_item_code', 'description', 'equipment_cost', 'equipment_rate']\r\n        }\r\n\r\n        if rate_type and rate_type in rate_columns:\r\n            cols = [c for c in rate_columns[rate_type] if c in df.columns]\r\n            return df[cols]\r\n\r\n        return df\r\n\r\n    def update_rate(self,\r\n                    work_item_code: str,\r\n                    rate_type: RateType,\r\n                    new_rate: float,\r\n                    reason: str = \"Manual update\",\r\n                    source: str = \"User\") -> Optional[RateChange]:\r\n        \"\"\"Update single rate.\"\"\"\r\n\r\n        rate_column = self._get_rate_column(rate_type)\r\n        if rate_column not in self.data.columns:\r\n            return None\r\n\r\n        mask = self.data['work_item_code'] == work_item_code\r\n        if not mask.any():\r\n            return None\r\n\r\n        old_rate = float(self.data.loc[mask, rate_column].iloc[0])\r\n        self.data.loc[mask, rate_column] = new_rate\r\n\r\n        change_percent = ((new_rate - old_rate) \u002F old_rate * 100) if old_rate > 0 else 0\r\n\r\n        change = RateChange(\r\n            resource_code=work_item_code,\r\n            rate_type=rate_type,\r\n            old_rate=old_rate,\r\n            new_rate=new_rate,\r\n            change_percent=round(change_percent, 2),\r\n            change_date=datetime.now(),\r\n            reason=reason,\r\n            source=source\r\n        )\r\n\r\n        self.change_log.append(change)\r\n        return change\r\n\r\n    def _get_rate_column(self, rate_type: RateType) -> str:\r\n        \"\"\"Get column name for rate type.\"\"\"\r\n        mapping = {\r\n            RateType.LABOR: 'labor_rate',\r\n            RateType.MATERIAL: 'material_cost',\r\n            RateType.EQUIPMENT: 'equipment_cost',\r\n            RateType.SUBCONTRACT: 'subcontract_cost'\r\n        }\r\n        return mapping.get(rate_type, 'labor_rate')\r\n\r\n    def apply_percentage_adjustment(self,\r\n                                     rate_type: RateType,\r\n                                     percentage: float,\r\n                                     category: str = None,\r\n                                     reason: str = \"Percentage adjustment\") -> RateUpdateResult:\r\n        \"\"\"Apply percentage adjustment to rates.\"\"\"\r\n\r\n        rate_column = self._get_rate_column(rate_type)\r\n        if rate_column not in self.data.columns:\r\n            return RateUpdateResult(0, 0, 0, 1, [], {})\r\n\r\n        # Build mask\r\n        mask = pd.Series([True] * len(self.data))\r\n        if category and 'category' in self.data.columns:\r\n            mask = self.data['category'].str.contains(category, case=False, na=False)\r\n\r\n        # Store old values\r\n        old_values = self.data.loc[mask, rate_column].copy()\r\n\r\n        # Apply adjustment\r\n        multiplier = 1 + (percentage \u002F 100)\r\n        self.data.loc[mask, rate_column] = old_values * multiplier\r\n\r\n        # Record changes\r\n        changes = []\r\n        for idx in self.data[mask].index:\r\n            old_rate = float(old_values.loc[idx])\r\n            new_rate = float(self.data.loc[idx, rate_column])\r\n\r\n            if old_rate != new_rate:\r\n                change = RateChange(\r\n                    resource_code=str(self.data.loc[idx, 'work_item_code']),\r\n                    rate_type=rate_type,\r\n                    old_rate=old_rate,\r\n                    new_rate=new_rate,\r\n                    change_percent=percentage,\r\n                    change_date=datetime.now(),\r\n                    reason=reason,\r\n                    source=f\"Bulk {percentage}%\"\r\n                )\r\n                changes.append(change)\r\n                self.change_log.append(change)\r\n\r\n        return RateUpdateResult(\r\n            total_items=len(self.data[mask]),\r\n            updated=len(changes),\r\n            unchanged=len(self.data[mask]) - len(changes),\r\n            errors=0,\r\n            changes=changes,\r\n            summary={\r\n                'rate_type': rate_type.value,\r\n                'adjustment_percent': percentage,\r\n                'category': category,\r\n                'average_new_rate': self.data.loc[mask, rate_column].mean()\r\n            }\r\n        )\r\n\r\n    def apply_inflation_index(self,\r\n                               base_year: int,\r\n                               current_year: int,\r\n                               inflation_rates: Dict[int, float],\r\n                               rate_types: List[RateType] = None) -> RateUpdateResult:\r\n        \"\"\"Apply inflation index from base year to current.\"\"\"\r\n\r\n        if rate_types is None:\r\n            rate_types = [RateType.LABOR, RateType.MATERIAL, RateType.EQUIPMENT]\r\n\r\n        # Calculate cumulative multiplier\r\n        cumulative_multiplier = 1.0\r\n        for year in range(base_year, current_year):\r\n            rate = inflation_rates.get(year, 0.02)  # Default 2%\r\n            cumulative_multiplier *= (1 + rate)\r\n\r\n        total_changes = []\r\n\r\n        for rate_type in rate_types:\r\n            result = self.apply_percentage_adjustment(\r\n                rate_type=rate_type,\r\n                percentage=(cumulative_multiplier - 1) * 100,\r\n                reason=f\"Inflation {base_year}-{current_year}\"\r\n            )\r\n            total_changes.extend(result.changes)\r\n\r\n        return RateUpdateResult(\r\n            total_items=len(self.data),\r\n            updated=len(total_changes),\r\n            unchanged=len(self.data) - len(total_changes),\r\n            errors=0,\r\n            changes=total_changes,\r\n            summary={\r\n                'base_year': base_year,\r\n                'current_year': current_year,\r\n                'cumulative_multiplier': round(cumulative_multiplier, 4),\r\n                'total_adjustment_percent': round((cumulative_multiplier - 1) * 100, 2)\r\n            }\r\n        )\r\n\r\n    def import_external_rates(self,\r\n                               external_data: pd.DataFrame,\r\n                               code_column: str,\r\n                               rate_column: str,\r\n                               rate_type: RateType,\r\n                               match_on: str = 'work_item_code') -> RateUpdateResult:\r\n        \"\"\"Import rates from external data source.\"\"\"\r\n\r\n        changes = []\r\n        errors = 0\r\n        target_column = self._get_rate_column(rate_type)\r\n\r\n        for _, row in external_data.iterrows():\r\n            code = row[code_column]\r\n            new_rate = row[rate_column]\r\n\r\n            try:\r\n                change = self.update_rate(\r\n                    work_item_code=code,\r\n                    rate_type=rate_type,\r\n                    new_rate=new_rate,\r\n                    reason=\"External import\",\r\n                    source=\"External data\"\r\n                )\r\n                if change:\r\n                    changes.append(change)\r\n            except Exception:\r\n                errors += 1\r\n\r\n        return RateUpdateResult(\r\n            total_items=len(external_data),\r\n            updated=len(changes),\r\n            unchanged=len(external_data) - len(changes) - errors,\r\n            errors=errors,\r\n            changes=changes,\r\n            summary={\r\n                'source': 'External import',\r\n                'rate_type': rate_type.value\r\n            }\r\n        )\r\n\r\n    def apply_regional_factors(self,\r\n                                region_factors: Dict[str, float],\r\n                                default_factor: float = 1.0) -> RateUpdateResult:\r\n        \"\"\"Apply regional adjustment factors.\"\"\"\r\n\r\n        # This assumes region column exists or applies uniformly\r\n        factor = region_factors.get('default', default_factor)\r\n\r\n        labor_result = self.apply_percentage_adjustment(\r\n            RateType.LABOR,\r\n            (region_factors.get('labor', factor) - 1) * 100,\r\n            reason=\"Regional adjustment\"\r\n        )\r\n\r\n        material_result = self.apply_percentage_adjustment(\r\n            RateType.MATERIAL,\r\n            (region_factors.get('material', factor) - 1) * 100,\r\n            reason=\"Regional adjustment\"\r\n        )\r\n\r\n        equipment_result = self.apply_percentage_adjustment(\r\n            RateType.EQUIPMENT,\r\n            (region_factors.get('equipment', factor) - 1) * 100,\r\n            reason=\"Regional adjustment\"\r\n        )\r\n\r\n        all_changes = (labor_result.changes + material_result.changes +\r\n                       equipment_result.changes)\r\n\r\n        return RateUpdateResult(\r\n            total_items=len(self.data),\r\n            updated=len(all_changes),\r\n            unchanged=len(self.data) * 3 - len(all_changes),\r\n            errors=0,\r\n            changes=all_changes,\r\n            summary={\r\n                'region_factors': region_factors,\r\n                'labor_adjusted': len(labor_result.changes),\r\n                'material_adjusted': len(material_result.changes),\r\n                'equipment_adjusted': len(equipment_result.changes)\r\n            }\r\n        )\r\n\r\n    def get_change_log(self,\r\n                        start_date: datetime = None,\r\n                        rate_type: RateType = None) -> List[RateChange]:\r\n        \"\"\"Get change log, optionally filtered.\"\"\"\r\n\r\n        changes = self.change_log\r\n\r\n        if start_date:\r\n            changes = [c for c in changes if c.change_date >= start_date]\r\n\r\n        if rate_type:\r\n            changes = [c for c in changes if c.rate_type == rate_type]\r\n\r\n        return changes\r\n\r\n    def export_change_log(self, output_path: str) -> str:\r\n        \"\"\"Export change log to Excel.\"\"\"\r\n\r\n        df = pd.DataFrame([\r\n            {\r\n                'Resource Code': c.resource_code,\r\n                'Rate Type': c.rate_type.value,\r\n                'Old Rate': c.old_rate,\r\n                'New Rate': c.new_rate,\r\n                'Change %': c.change_percent,\r\n                'Date': c.change_date.strftime('%Y-%m-%d %H:%M'),\r\n                'Reason': c.reason,\r\n                'Source': c.source\r\n            }\r\n            for c in self.change_log\r\n        ])\r\n\r\n        df.to_excel(output_path, index=False)\r\n        return output_path\r\n\r\n    def rollback_changes(self,\r\n                          since: datetime = None) -> int:\r\n        \"\"\"Rollback changes since date (returns to original data).\"\"\"\r\n\r\n        if since is None:\r\n            # Full rollback\r\n            self.data = self.original_data.copy()\r\n            count = len(self.change_log)\r\n            self.change_log = []\r\n            return count\r\n\r\n        # Partial rollback - more complex, would need versioning\r\n        return 0\r\n\r\n    def export_updated_data(self, output_path: str) -> str:\r\n        \"\"\"Export updated CWICR data.\"\"\"\r\n\r\n        if output_path.endswith('.parquet'):\r\n            self.data.to_parquet(output_path)\r\n        else:\r\n            self.data.to_excel(output_path, index=False)\r\n\r\n        return output_path\r\n\r\n\r\nclass RateScheduler:\r\n    \"\"\"Schedule automatic rate updates.\"\"\"\r\n\r\n    def __init__(self, updater: CWICRRateUpdater):\r\n        self.updater = updater\r\n        self.schedules: List[Dict[str, Any]] = []\r\n\r\n    def add_annual_labor_increase(self,\r\n                                   percentage: float,\r\n                                   effective_date: date) -> Dict[str, Any]:\r\n        \"\"\"Schedule annual labor rate increase.\"\"\"\r\n\r\n        schedule = {\r\n            'id': len(self.schedules) + 1,\r\n            'type': 'annual_labor',\r\n            'percentage': percentage,\r\n            'effective_date': effective_date,\r\n            'rate_type': RateType.LABOR,\r\n            'status': 'scheduled'\r\n        }\r\n        self.schedules.append(schedule)\r\n        return schedule\r\n\r\n    def execute_due_updates(self, current_date: date = None) -> List[RateUpdateResult]:\r\n        \"\"\"Execute all updates that are due.\"\"\"\r\n\r\n        if current_date is None:\r\n            current_date = date.today()\r\n\r\n        results = []\r\n\r\n        for schedule in self.schedules:\r\n            if schedule['status'] == 'scheduled' and schedule['effective_date'] \u003C= current_date:\r\n                result = self.updater.apply_percentage_adjustment(\r\n                    rate_type=schedule['rate_type'],\r\n                    percentage=schedule['percentage'],\r\n                    reason=f\"Scheduled {schedule['type']}\"\r\n                )\r\n                schedule['status'] = 'executed'\r\n                schedule['executed_date'] = current_date\r\n                results.append(result)\r\n\r\n        return results\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 updater\r\nupdater = CWICRRateUpdater(cwicr)\r\n\r\n# Apply 5% labor rate increase\r\nresult = updater.apply_percentage_adjustment(\r\n    rate_type=RateType.LABOR,\r\n    percentage=5.0,\r\n    reason=\"2024 Annual Increase\"\r\n)\r\n\r\nprint(f\"Updated {result.updated} labor rates\")\r\nprint(f\"Average adjustment: {result.summary.get('adjustment_percent')}%\")\r\n```\r\n\r\n## Common Use Cases\r\n\r\n### 1. Inflation Adjustment\r\n```python\r\ninflation_rates = {\r\n    2020: 0.012, 2021: 0.047, 2022: 0.065, 2023: 0.034\r\n}\r\nresult = updater.apply_inflation_index(\r\n    base_year=2020,\r\n    current_year=2024,\r\n    inflation_rates=inflation_rates\r\n)\r\nprint(f\"Cumulative adjustment: {result.summary['total_adjustment_percent']}%\")\r\n```\r\n\r\n### 2. Regional Factors\r\n```python\r\nberlin_factors = {\r\n    'labor': 1.15,\r\n    'material': 0.95,\r\n    'equipment': 1.05\r\n}\r\nresult = updater.apply_regional_factors(berlin_factors)\r\n```\r\n\r\n### 3. Import External Prices\r\n```python\r\nmarket_prices = pd.read_excel(\"current_prices.xlsx\")\r\nresult = updater.import_external_rates(\r\n    external_data=market_prices,\r\n    code_column='item_code',\r\n    rate_column='price',\r\n    rate_type=RateType.MATERIAL\r\n)\r\n```\r\n\r\n### 4. Export Audit Trail\r\n```python\r\nupdater.export_change_log(\"rate_changes_2024.xlsx\")\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 - Rate Management\r\n","# CWICR Rate Updater\r\n\r\n## Business Case\r\n\r\n### Problem Statement\r\nResource rates become outdated:\r\n- Material prices fluctuate with market\r\n- Labor rates change annually\r\n- Equipment costs vary by region\r\n- Historical rates need adjustment\r\n\r\n### Solution\r\nSystematic rate updates integrating market data, inflation indices, and regional factors while maintaining audit trail.\r\n\r\n### Business Value\r\n- **Accuracy** - Current market pricing\r\n- **Flexibility** - Update specific resources or categories\r\n- **Audit trail** - Track rate changes over time\r\n- **Automation** - Integrate with price APIs\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, Callable\r\nfrom dataclasses import dataclass, field\r\nfrom datetime import datetime, date\r\nfrom enum import Enum\r\nimport json\r\n\r\n\r\nclass RateType(Enum):\r\n    \"\"\"Types of rates.\"\"\"\r\n    LABOR = \"labor\"\r\n    MATERIAL = \"material\"\r\n    EQUIPMENT = \"equipment\"\r\n    SUBCONTRACT = \"subcontract\"\r\n\r\n\r\nclass AdjustmentMethod(Enum):\r\n    \"\"\"Methods for rate adjustment.\"\"\"\r\n    FIXED_AMOUNT = \"fixed_amount\"\r\n    PERCENTAGE = \"percentage\"\r\n    MULTIPLIER = \"multiplier\"\r\n    REPLACEMENT = \"replacement\"\r\n\r\n\r\n@dataclass\r\nclass RateChange:\r\n    \"\"\"Record of rate change.\"\"\"\r\n    resource_code: str\r\n    rate_type: RateType\r\n    old_rate: float\r\n    new_rate: float\r\n    change_percent: float\r\n    change_date: datetime\r\n    reason: str\r\n    source: str\r\n\r\n\r\n@dataclass\r\nclass RateUpdateResult:\r\n    \"\"\"Result of rate update operation.\"\"\"\r\n    total_items: int\r\n    updated: int\r\n    unchanged: int\r\n    errors: int\r\n    changes: List[RateChange]\r\n    summary: Dict[str, Any]\r\n\r\n\r\nclass CWICRRateUpdater:\r\n    \"\"\"Update resource rates in CWICR data.\"\"\"\r\n\r\n    def __init__(self, cwicr_data: pd.DataFrame):\r\n        self.data = cwicr_data.copy()\r\n        self.change_log: List[RateChange] = []\r\n        self.original_data = cwicr_data.copy()\r\n\r\n    def get_current_rates(self,\r\n                          rate_type: RateType = None,\r\n                          category: str = None) -> pd.DataFrame:\r\n        \"\"\"Get current rates, optionally filtered.\"\"\"\r\n\r\n        df = self.data.copy()\r\n\r\n        # Filter by category if specified\r\n        if category and 'category' in df.columns:\r\n            df = df[df['category'].str.contains(category, case=False, na=False)]\r\n\r\n        # Select relevant columns based on rate type\r\n        rate_columns = {\r\n            RateType.LABOR: ['work_item_code', 'description', 'labor_rate', 'labor_cost'],\r\n            RateType.MATERIAL: ['work_item_code', 'description', 'material_cost'],\r\n            RateType.EQUIPMENT: ['work_item_code', 'description', 'equipment_cost', 'equipment_rate']\r\n        }\r\n\r\n        if rate_type and rate_type in rate_columns:\r\n            cols = [c for c in rate_columns[rate_type] if c in df.columns]\r\n            return df[cols]\r\n\r\n        return df\r\n\r\n    def update_rate(self,\r\n                    work_item_code: str,\r\n                    rate_type: RateType,\r\n                    new_rate: float,\r\n                    reason: str = \"Manual update\",\r\n                    source: str = \"User\") -> Optional[RateChange]:\r\n        \"\"\"Update single rate.\"\"\"\r\n\r\n        rate_column = self._get_rate_column(rate_type)\r\n        if rate_column not in self.data.columns:\r\n            return None\r\n\r\n        mask = self.data['work_item_code'] == work_item_code\r\n        if not mask.any():\r\n            return None\r\n\r\n        old_rate = float(self.data.loc[mask, rate_column].iloc[0])\r\n        self.data.loc[mask, rate_column] = new_rate\r\n\r\n        change_percent = ((new_rate - old_rate) \u002F old_rate * 100) if old_rate > 0 else 0\r\n\r\n        change = RateChange(\r\n            resource_code=work_item_code,\r\n            rate_type=rate_type,\r\n            old_rate=old_rate,\r\n            new_rate=new_rate,\r\n            change_percent=round(change_percent, 2),\r\n            change_date=datetime.now(),\r\n            reason=reason,\r\n            source=source\r\n        )\r\n\r\n        self.change_log.append(change)\r\n        return change\r\n\r\n    def _get_rate_column(self, rate_type: RateType) -> str:\r\n        \"\"\"Get column name for rate type.\"\"\"\r\n        mapping = {\r\n            RateType.LABOR: 'labor_rate',\r\n            RateType.MATERIAL: 'material_cost',\r\n            RateType.EQUIPMENT: 'equipment_cost',\r\n            RateType.SUBCONTRACT: 'subcontract_cost'\r\n        }\r\n        return mapping.get(rate_type, 'labor_rate')\r\n\r\n    def apply_percentage_adjustment(self,\r\n                                     rate_type: RateType,\r\n                                     percentage: float,\r\n                                     category: str = None,\r\n                                     reason: str = \"Percentage adjustment\") -> RateUpdateResult:\r\n        \"\"\"Apply percentage adjustment to rates.\"\"\"\r\n\r\n        rate_column = self._get_rate_column(rate_type)\r\n        if rate_column not in self.data.columns:\r\n            return RateUpdateResult(0, 0, 0, 1, [], {})\r\n\r\n        # Build mask\r\n        mask = pd.Series([True] * len(self.data))\r\n        if category and 'category' in self.data.columns:\r\n            mask = self.data['category'].str.contains(category, case=False, na=False)\r\n\r\n        # Store old values\r\n        old_values = self.data.loc[mask, rate_column].copy()\r\n\r\n        # Apply adjustment\r\n        multiplier = 1 + (percentage \u002F 100)\r\n        self.data.loc[mask, rate_column] = old_values * multiplier\r\n\r\n        # Record changes\r\n        changes = []\r\n        for idx in self.data[mask].index:\r\n            old_rate = float(old_values.loc[idx])\r\n            new_rate = float(self.data.loc[idx, rate_column])\r\n\r\n            if old_rate != new_rate:\r\n                change = RateChange(\r\n                    resource_code=str(self.data.loc[idx, 'work_item_code']),\r\n                    rate_type=rate_type,\r\n                    old_rate=old_rate,\r\n                    new_rate=new_rate,\r\n                    change_percent=percentage,\r\n                    change_date=datetime.now(),\r\n                    reason=reason,\r\n                    source=f\"Bulk {percentage}%\"\r\n                )\r\n                changes.append(change)\r\n                self.change_log.append(change)\r\n\r\n        return RateUpdateResult(\r\n            total_items=len(self.data[mask]),\r\n            updated=len(changes),\r\n            unchanged=len(self.data[mask]) - len(changes),\r\n            errors=0,\r\n            changes=changes,\r\n            summary={\r\n                'rate_type': rate_type.value,\r\n                'adjustment_percent': percentage,\r\n                'category': category,\r\n                'average_new_rate': self.data.loc[mask, rate_column].mean()\r\n            }\r\n        )\r\n\r\n    def apply_inflation_index(self,\r\n                               base_year: int,\r\n                               current_year: int,\r\n                               inflation_rates: Dict[int, float],\r\n                               rate_types: List[RateType] = None) -> RateUpdateResult:\r\n        \"\"\"Apply inflation index from base year to current.\"\"\"\r\n\r\n        if rate_types is None:\r\n            rate_types = [RateType.LABOR, RateType.MATERIAL, RateType.EQUIPMENT]\r\n\r\n        # Calculate cumulative multiplier\r\n        cumulative_multiplier = 1.0\r\n        for year in range(base_year, current_year):\r\n            rate = inflation_rates.get(year, 0.02)  # Default 2%\r\n            cumulative_multiplier *= (1 + rate)\r\n\r\n        total_changes = []\r\n\r\n        for rate_type in rate_types:\r\n            result = self.apply_percentage_adjustment(\r\n                rate_type=rate_type,\r\n                percentage=(cumulative_multiplier - 1) * 100,\r\n                reason=f\"Inflation {base_year}-{current_year}\"\r\n            )\r\n            total_changes.extend(result.changes)\r\n\r\n        return RateUpdateResult(\r\n            total_items=len(self.data),\r\n            updated=len(total_changes),\r\n            unchanged=len(self.data) - len(total_changes),\r\n            errors=0,\r\n            changes=total_changes,\r\n            summary={\r\n                'base_year': base_year,\r\n                'current_year': current_year,\r\n                'cumulative_multiplier': round(cumulative_multiplier, 4),\r\n                'total_adjustment_percent': round((cumulative_multiplier - 1) * 100, 2)\r\n            }\r\n        )\r\n\r\n    def import_external_rates(self,\r\n                               external_data: pd.DataFrame,\r\n                               code_column: str,\r\n                               rate_column: str,\r\n                               rate_type: RateType,\r\n                               match_on: str = 'work_item_code') -> RateUpdateResult:\r\n        \"\"\"Import rates from external data source.\"\"\"\r\n\r\n        changes = []\r\n        errors = 0\r\n        target_column = self._get_rate_column(rate_type)\r\n\r\n        for _, row in external_data.iterrows():\r\n            code = row[code_column]\r\n            new_rate = row[rate_column]\r\n\r\n            try:\r\n                change = self.update_rate(\r\n                    work_item_code=code,\r\n                    rate_type=rate_type,\r\n                    new_rate=new_rate,\r\n                    reason=\"External import\",\r\n                    source=\"External data\"\r\n                )\r\n                if change:\r\n                    changes.append(change)\r\n            except Exception:\r\n                errors += 1\r\n\r\n        return RateUpdateResult(\r\n            total_items=len(external_data),\r\n            updated=len(changes),\r\n            unchanged=len(external_data) - len(changes) - errors,\r\n            errors=errors,\r\n            changes=changes,\r\n            summary={\r\n                'source': 'External import',\r\n                'rate_type': rate_type.value\r\n            }\r\n        )\r\n\r\n    def apply_regional_factors(self,\r\n                                region_factors: Dict[str, float],\r\n                                default_factor: float = 1.0) -> RateUpdateResult:\r\n        \"\"\"Apply regional adjustment factors.\"\"\"\r\n\r\n        # This assumes region column exists or applies uniformly\r\n        factor = region_factors.get('default', default_factor)\r\n\r\n        labor_result = self.apply_percentage_adjustment(\r\n            RateType.LABOR,\r\n            (region_factors.get('labor', factor) - 1) * 100,\r\n            reason=\"Regional adjustment\"\r\n        )\r\n\r\n        material_result = self.apply_percentage_adjustment(\r\n            RateType.MATERIAL,\r\n            (region_factors.get('material', factor) - 1) * 100,\r\n            reason=\"Regional adjustment\"\r\n        )\r\n\r\n        equipment_result = self.apply_percentage_adjustment(\r\n            RateType.EQUIPMENT,\r\n            (region_factors.get('equipment', factor) - 1) * 100,\r\n            reason=\"Regional adjustment\"\r\n        )\r\n\r\n        all_changes = (labor_result.changes + material_result.changes +\r\n                       equipment_result.changes)\r\n\r\n        return RateUpdateResult(\r\n            total_items=len(self.data),\r\n            updated=len(all_changes),\r\n            unchanged=len(self.data) * 3 - len(all_changes),\r\n            errors=0,\r\n            changes=all_changes,\r\n            summary={\r\n                'region_factors': region_factors,\r\n                'labor_adjusted': len(labor_result.changes),\r\n                'material_adjusted': len(material_result.changes),\r\n                'equipment_adjusted': len(equipment_result.changes)\r\n            }\r\n        )\r\n\r\n    def get_change_log(self,\r\n                        start_date: datetime = None,\r\n                        rate_type: RateType = None) -> List[RateChange]:\r\n        \"\"\"Get change log, optionally filtered.\"\"\"\r\n\r\n        changes = self.change_log\r\n\r\n        if start_date:\r\n            changes = [c for c in changes if c.change_date >= start_date]\r\n\r\n        if rate_type:\r\n            changes = [c for c in changes if c.rate_type == rate_type]\r\n\r\n        return changes\r\n\r\n    def export_change_log(self, output_path: str) -> str:\r\n        \"\"\"Export change log to Excel.\"\"\"\r\n\r\n        df = pd.DataFrame([\r\n            {\r\n                'Resource Code': c.resource_code,\r\n                'Rate Type': c.rate_type.value,\r\n                'Old Rate': c.old_rate,\r\n                'New Rate': c.new_rate,\r\n                'Change %': c.change_percent,\r\n                'Date': c.change_date.strftime('%Y-%m-%d %H:%M'),\r\n                'Reason': c.reason,\r\n                'Source': c.source\r\n            }\r\n            for c in self.change_log\r\n        ])\r\n\r\n        df.to_excel(output_path, index=False)\r\n        return output_path\r\n\r\n    def rollback_changes(self,\r\n                          since: datetime = None) -> int:\r\n        \"\"\"Rollback changes since date (returns to original data).\"\"\"\r\n\r\n        if since is None:\r\n            # Full rollback\r\n            self.data = self.original_data.copy()\r\n            count = len(self.change_log)\r\n            self.change_log = []\r\n            return count\r\n\r\n        # Partial rollback - more complex, would need versioning\r\n        return 0\r\n\r\n    def export_updated_data(self, output_path: str) -> str:\r\n        \"\"\"Export updated CWICR data.\"\"\"\r\n\r\n        if output_path.endswith('.parquet'):\r\n            self.data.to_parquet(output_path)\r\n        else:\r\n            self.data.to_excel(output_path, index=False)\r\n\r\n        return output_path\r\n\r\n\r\nclass RateScheduler:\r\n    \"\"\"Schedule automatic rate updates.\"\"\"\r\n\r\n    def __init__(self, updater: CWICRRateUpdater):\r\n        self.updater = updater\r\n        self.schedules: List[Dict[str, Any]] = []\r\n\r\n    def add_annual_labor_increase(self,\r\n                                   percentage: float,\r\n                                   effective_date: date) -> Dict[str, Any]:\r\n        \"\"\"Schedule annual labor rate increase.\"\"\"\r\n\r\n        schedule = {\r\n            'id': len(self.schedules) + 1,\r\n            'type': 'annual_labor',\r\n            'percentage': percentage,\r\n            'effective_date': effective_date,\r\n            'rate_type': RateType.LABOR,\r\n            'status': 'scheduled'\r\n        }\r\n        self.schedules.append(schedule)\r\n        return schedule\r\n\r\n    def execute_due_updates(self, current_date: date = None) -> List[RateUpdateResult]:\r\n        \"\"\"Execute all updates that are due.\"\"\"\r\n\r\n        if current_date is None:\r\n            current_date = date.today()\r\n\r\n        results = []\r\n\r\n        for schedule in self.schedules:\r\n            if schedule['status'] == 'scheduled' and schedule['effective_date'] \u003C= current_date:\r\n                result = self.updater.apply_percentage_adjustment(\r\n                    rate_type=schedule['rate_type'],\r\n                    percentage=schedule['percentage'],\r\n                    reason=f\"Scheduled {schedule['type']}\"\r\n                )\r\n                schedule['status'] = 'executed'\r\n                schedule['executed_date'] = current_date\r\n                results.append(result)\r\n\r\n        return results\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 updater\r\nupdater = CWICRRateUpdater(cwicr)\r\n\r\n# Apply 5% labor rate increase\r\nresult = updater.apply_percentage_adjustment(\r\n    rate_type=RateType.LABOR,\r\n    percentage=5.0,\r\n    reason=\"2024 Annual Increase\"\r\n)\r\n\r\nprint(f\"Updated {result.updated} labor rates\")\r\nprint(f\"Average adjustment: {result.summary.get('adjustment_percent')}%\")\r\n```\r\n\r\n## Common Use Cases\r\n\r\n### 1. Inflation Adjustment\r\n```python\r\ninflation_rates = {\r\n    2020: 0.012, 2021: 0.047, 2022: 0.065, 2023: 0.034\r\n}\r\nresult = updater.apply_inflation_index(\r\n    base_year=2020,\r\n    current_year=2024,\r\n    inflation_rates=inflation_rates\r\n)\r\nprint(f\"Cumulative adjustment: {result.summary['total_adjustment_percent']}%\")\r\n```\r\n\r\n### 2. Regional Factors\r\n```python\r\nberlin_factors = {\r\n    'labor': 1.15,\r\n    'material': 0.95,\r\n    'equipment': 1.05\r\n}\r\nresult = updater.apply_regional_factors(berlin_factors)\r\n```\r\n\r\n### 3. Import External Prices\r\n```python\r\nmarket_prices = pd.read_excel(\"current_prices.xlsx\")\r\nresult = updater.import_external_rates(\r\n    external_data=market_prices,\r\n    code_column='item_code',\r\n    rate_column='price',\r\n    rate_type=RateType.MATERIAL\r\n)\r\n```\r\n\r\n### 4. Export Audit Trail\r\n```python\r\nupdater.export_change_log(\"rate_changes_2024.xlsx\")\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 - Rate Management\r\n",{"slug":5,"description":7,"display_name":33},"CWICR Rate Updater","main","e7e336ac628c3e3bd771aff1aaa469c93778f94a","skills\u002Fdatadrivenconstruction\u002Fcwicr-rate-updater",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":42,"prompt":52,"zipAvailable":53},[43,46,49],{"tool":44,"command":45},"npx","npx skills add kbarbel640-del\u002Fskills --skill cwicr-rate-updater",{"tool":47,"command":48},"bunx","bunx skills add kbarbel640-del\u002Fskills --skill cwicr-rate-updater",{"tool":50,"command":51},"pnpm","pnpm dlx skills add kbarbel640-del\u002Fskills --skill cwicr-rate-updater","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdatadrivenconstruction\u002Fcwicr-rate-updater\u002FSKILL.md",true,[55],{"path":56,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":59,"sourceHtmlUrl":60},"_meta.json","REFERENCE","参考文档",304,"https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdatadrivenconstruction\u002Fcwicr-rate-updater\u002F_meta.json",[62,92,121,142,148,168],{"skillNo":63,"name":64,"nameZh":6,"description":65,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":66,"categories":67,"keywords":73,"publishedAt":28},"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",[68],{"categoryNo":69,"categoryName":70,"taxonomyType":71,"taxonomyTypeDesc":72},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[74,75,76,27,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91],"crowd prompting","prompting","api","tokens","domain","world","prompts","operations","instructions","text","tool","system","improve","productivity","marketplace","https","content","ai agents",{"skillNo":93,"name":94,"nameZh":6,"description":95,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":96,"categories":97,"keywords":103,"publishedAt":28},"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",[98],{"categoryNo":99,"categoryName":100,"taxonomyType":101,"taxonomyTypeDesc":102},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[104,76,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,80,120],"dilemm ai","arena","analyze","browser","openclaw agent","create","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":28},"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":101,"taxonomyTypeDesc":102},"01JBSEEDCAT000000000000021","资料检索与研究",[131,132,133,134,135,136,137,27,138,118,120,139,76,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":28},"01M6G8HQJ7N7WMY6YV7D7SSJ16","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzxrys\u002Farxiv-paper-reviews\u002FSKILL.md",[146],{"categoryNo":128,"categoryName":129,"taxonomyType":101,"taxonomyTypeDesc":102},[134,133,132,131,141,136,137,27,135,118,120,138,139,76,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":28},"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":28},"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":99,"categoryName":100,"taxonomyType":101,"taxonomyTypeDesc":102},{"categoryNo":176,"categoryName":177,"taxonomyType":71,"taxonomyTypeDesc":72},"01JBSEEDCAT000000000000037","律师与法务",[179,180,181,182,27,183,184,34,185,139,186,187,188,189,190,89,191,192,193],"agent constitution","constitution","contracts","tree","query","base","log","check","Rules","projects","compliance","actions","state","governance","github"]