[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6FK3AMF9N488ERYTXCHCSVM":3,"skill-same:01M6FK3AMF9N488ERYTXCHCSVM":77},{"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":27,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":50,"commitSha":51,"skillDirectory":52,"forksCount":53,"repositoryHtmlUrl":54,"repositoryHomepageUrl":6,"ownerHtmlUrl":55,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":56,"install":57,"resources":70},"01M6FK3AMF9N488ERYTXCHCSVM","project-kpi-dashboard",null,"Create interactive KPI dashboards for construction projects. Track schedule, cost, quality, and safety metrics in real-time.","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\u002Fproject-kpi-dashboard\u002FSKILL.md",[17,22],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000013","分析与可视化","SCENARIO","使用场景",{"categoryNo":23,"categoryName":24,"taxonomyType":25,"taxonomyTypeDesc":26},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"dashboard","kpi","project","kpi dashboard","project kpi dashboard","create","metrics","real-time","Safety","dashboards","projects","quality","schedule","interactive","cost","time","2026-11-10 10:12:30",false,"---\r\nslug: \"project-kpi-dashboard\"\r\ndisplay_name: \"Project KPI Dashboard\"\r\ndescription: \"Create interactive KPI dashboards for construction projects. Track schedule, cost, quality, and safety metrics in real-time.\"\r\n---\r\n\r\n# Project KPI Dashboard\r\n\r\n## Business Case\r\n\r\n### Problem Statement\r\nProject stakeholders struggle with:\r\n- Scattered data across multiple systems\r\n- Delayed reporting on project health\r\n- No real-time visibility into KPIs\r\n- Inconsistent metric definitions\r\n\r\n### Solution\r\nCentralized KPI dashboard that aggregates data from multiple sources and presents key metrics with drill-down capabilities.\r\n\r\n### Business Value\r\n- **Real-time visibility** - Live project health status\r\n- **Data-driven decisions** - Actionable insights\r\n- **Stakeholder alignment** - Single source of truth\r\n- **Early warning** - Proactive issue detection\r\n\r\n## Technical Implementation\r\n\r\n```python\r\nimport pandas as pd\r\nfrom datetime import datetime, date, timedelta\r\nfrom typing import Dict, Any, List, Optional\r\nfrom dataclasses import dataclass, field\r\nfrom enum import Enum\r\n\r\n\r\nclass KPIStatus(Enum):\r\n    \"\"\"KPI health status.\"\"\"\r\n    ON_TRACK = \"on_track\"\r\n    AT_RISK = \"at_risk\"\r\n    CRITICAL = \"critical\"\r\n    UNKNOWN = \"unknown\"\r\n\r\n\r\nclass KPICategory(Enum):\r\n    \"\"\"KPI categories.\"\"\"\r\n    SCHEDULE = \"schedule\"\r\n    COST = \"cost\"\r\n    QUALITY = \"quality\"\r\n    SAFETY = \"safety\"\r\n    PRODUCTIVITY = \"productivity\"\r\n    SUSTAINABILITY = \"sustainability\"\r\n\r\n\r\n@dataclass\r\nclass KPIMetric:\r\n    \"\"\"Single KPI metric.\"\"\"\r\n    name: str\r\n    category: KPICategory\r\n    current_value: float\r\n    target_value: float\r\n    unit: str\r\n    status: KPIStatus\r\n    trend: str  # up, down, stable\r\n    last_updated: datetime\r\n    description: str = \"\"\r\n\r\n    @property\r\n    def variance(self) -> float:\r\n        \"\"\"Calculate variance from target.\"\"\"\r\n        if self.target_value == 0:\r\n            return 0\r\n        return ((self.current_value - self.target_value) \u002F self.target_value) * 100\r\n\r\n    @property\r\n    def achievement(self) -> float:\r\n        \"\"\"Calculate achievement percentage.\"\"\"\r\n        if self.target_value == 0:\r\n            return 0\r\n        return (self.current_value \u002F self.target_value) * 100\r\n\r\n\r\n@dataclass\r\nclass DashboardConfig:\r\n    \"\"\"Dashboard configuration.\"\"\"\r\n    project_name: str\r\n    project_code: str\r\n    start_date: date\r\n    end_date: date\r\n    budget: float\r\n    currency: str = \"USD\"\r\n    refresh_interval_minutes: int = 15\r\n\r\n\r\nclass ProjectKPIDashboard:\r\n    \"\"\"Construction project KPI dashboard.\"\"\"\r\n\r\n    # Standard thresholds for RAG status\r\n    THRESHOLDS = {\r\n        'schedule': {'green': 0.95, 'amber': 0.85},\r\n        'cost': {'green': 1.05, 'amber': 1.15},\r\n        'quality': {'green': 0.98, 'amber': 0.95},\r\n        'safety': {'green': 0, 'amber': 1}  # incident count\r\n    }\r\n\r\n    def __init__(self, config: DashboardConfig):\r\n        self.config = config\r\n        self.metrics: Dict[str, KPIMetric] = {}\r\n        self.history: List[Dict[str, Any]] = []\r\n\r\n    def add_metric(self, metric: KPIMetric):\r\n        \"\"\"Add or update a KPI metric.\"\"\"\r\n        self.metrics[metric.name] = metric\r\n        self._record_history(metric)\r\n\r\n    def _record_history(self, metric: KPIMetric):\r\n        \"\"\"Record metric history for trending.\"\"\"\r\n        self.history.append({\r\n            'name': metric.name,\r\n            'value': metric.current_value,\r\n            'timestamp': metric.last_updated,\r\n            'status': metric.status.value\r\n        })\r\n\r\n    def calculate_schedule_kpis(self,\r\n                                 planned_activities: int,\r\n                                 completed_activities: int,\r\n                                 planned_duration_days: int,\r\n                                 actual_duration_days: int) -> List[KPIMetric]:\r\n        \"\"\"Calculate schedule-related KPIs.\"\"\"\r\n\r\n        # Schedule Performance Index (SPI)\r\n        spi = completed_activities \u002F planned_activities if planned_activities > 0 else 0\r\n        spi_status = self._get_status(spi, 'schedule')\r\n\r\n        # Schedule Variance\r\n        sv = completed_activities - planned_activities\r\n\r\n        # Percent Complete\r\n        pct_complete = (completed_activities \u002F planned_activities * 100) if planned_activities > 0 else 0\r\n\r\n        metrics = [\r\n            KPIMetric(\r\n                name=\"Schedule Performance Index\",\r\n                category=KPICategory.SCHEDULE,\r\n                current_value=round(spi, 2),\r\n                target_value=1.0,\r\n                unit=\"ratio\",\r\n                status=spi_status,\r\n                trend=self._calculate_trend(\"Schedule Performance Index\"),\r\n                last_updated=datetime.now(),\r\n                description=\"SPI = Earned Value \u002F Planned Value\"\r\n            ),\r\n            KPIMetric(\r\n                name=\"Percent Complete\",\r\n                category=KPICategory.SCHEDULE,\r\n                current_value=round(pct_complete, 1),\r\n                target_value=100,\r\n                unit=\"%\",\r\n                status=spi_status,\r\n                trend=self._calculate_trend(\"Percent Complete\"),\r\n                last_updated=datetime.now()\r\n            ),\r\n            KPIMetric(\r\n                name=\"Schedule Variance\",\r\n                category=KPICategory.SCHEDULE,\r\n                current_value=sv,\r\n                target_value=0,\r\n                unit=\"activities\",\r\n                status=spi_status,\r\n                trend=self._calculate_trend(\"Schedule Variance\"),\r\n                last_updated=datetime.now()\r\n            )\r\n        ]\r\n\r\n        for m in metrics:\r\n            self.add_metric(m)\r\n\r\n        return metrics\r\n\r\n    def calculate_cost_kpis(self,\r\n                            budgeted_cost: float,\r\n                            actual_cost: float,\r\n                            earned_value: float) -> List[KPIMetric]:\r\n        \"\"\"Calculate cost-related KPIs.\"\"\"\r\n\r\n        # Cost Performance Index (CPI)\r\n        cpi = earned_value \u002F actual_cost if actual_cost > 0 else 0\r\n        cpi_status = self._get_status(cpi, 'cost', inverse=True)\r\n\r\n        # Cost Variance\r\n        cv = earned_value - actual_cost\r\n\r\n        # Budget utilization\r\n        budget_used = (actual_cost \u002F budgeted_cost * 100) if budgeted_cost > 0 else 0\r\n\r\n        metrics = [\r\n            KPIMetric(\r\n                name=\"Cost Performance Index\",\r\n                category=KPICategory.COST,\r\n                current_value=round(cpi, 2),\r\n                target_value=1.0,\r\n                unit=\"ratio\",\r\n                status=cpi_status,\r\n                trend=self._calculate_trend(\"Cost Performance Index\"),\r\n                last_updated=datetime.now(),\r\n                description=\"CPI = Earned Value \u002F Actual Cost\"\r\n            ),\r\n            KPIMetric(\r\n                name=\"Cost Variance\",\r\n                category=KPICategory.COST,\r\n                current_value=round(cv, 2),\r\n                target_value=0,\r\n                unit=self.config.currency,\r\n                status=cpi_status,\r\n                trend=self._calculate_trend(\"Cost Variance\"),\r\n                last_updated=datetime.now()\r\n            ),\r\n            KPIMetric(\r\n                name=\"Budget Utilization\",\r\n                category=KPICategory.COST,\r\n                current_value=round(budget_used, 1),\r\n                target_value=100,\r\n                unit=\"%\",\r\n                status=cpi_status,\r\n                trend=self._calculate_trend(\"Budget Utilization\"),\r\n                last_updated=datetime.now()\r\n            )\r\n        ]\r\n\r\n        for m in metrics:\r\n            self.add_metric(m)\r\n\r\n        return metrics\r\n\r\n    def calculate_quality_kpis(self,\r\n                               total_inspections: int,\r\n                               passed_inspections: int,\r\n                               rework_items: int,\r\n                               total_items: int) -> List[KPIMetric]:\r\n        \"\"\"Calculate quality-related KPIs.\"\"\"\r\n\r\n        # First Pass Yield\r\n        fpy = passed_inspections \u002F total_inspections if total_inspections > 0 else 0\r\n        fpy_status = self._get_status(fpy, 'quality')\r\n\r\n        # Rework Rate\r\n        rework_rate = rework_items \u002F total_items * 100 if total_items > 0 else 0\r\n\r\n        metrics = [\r\n            KPIMetric(\r\n                name=\"First Pass Yield\",\r\n                category=KPICategory.QUALITY,\r\n                current_value=round(fpy * 100, 1),\r\n                target_value=98,\r\n                unit=\"%\",\r\n                status=fpy_status,\r\n                trend=self._calculate_trend(\"First Pass Yield\"),\r\n                last_updated=datetime.now()\r\n            ),\r\n            KPIMetric(\r\n                name=\"Rework Rate\",\r\n                category=KPICategory.QUALITY,\r\n                current_value=round(rework_rate, 1),\r\n                target_value=2,\r\n                unit=\"%\",\r\n                status=fpy_status,\r\n                trend=self._calculate_trend(\"Rework Rate\"),\r\n                last_updated=datetime.now()\r\n            )\r\n        ]\r\n\r\n        for m in metrics:\r\n            self.add_metric(m)\r\n\r\n        return metrics\r\n\r\n    def calculate_safety_kpis(self,\r\n                              incidents: int,\r\n                              near_misses: int,\r\n                              worked_hours: float,\r\n                              safety_observations: int) -> List[KPIMetric]:\r\n        \"\"\"Calculate safety-related KPIs.\"\"\"\r\n\r\n        # TRIR (Total Recordable Incident Rate)\r\n        trir = (incidents * 200000) \u002F worked_hours if worked_hours > 0 else 0\r\n        trir_status = KPIStatus.ON_TRACK if incidents == 0 else (\r\n            KPIStatus.AT_RISK if incidents \u003C= 2 else KPIStatus.CRITICAL\r\n        )\r\n\r\n        # LTIR (Lost Time Incident Rate)\r\n        ltir = (incidents * 1000000) \u002F worked_hours if worked_hours > 0 else 0\r\n\r\n        metrics = [\r\n            KPIMetric(\r\n                name=\"TRIR\",\r\n                category=KPICategory.SAFETY,\r\n                current_value=round(trir, 2),\r\n                target_value=0,\r\n                unit=\"per 200k hrs\",\r\n                status=trir_status,\r\n                trend=self._calculate_trend(\"TRIR\"),\r\n                last_updated=datetime.now(),\r\n                description=\"Total Recordable Incident Rate\"\r\n            ),\r\n            KPIMetric(\r\n                name=\"Safety Observations\",\r\n                category=KPICategory.SAFETY,\r\n                current_value=safety_observations,\r\n                target_value=50,\r\n                unit=\"count\",\r\n                status=KPIStatus.ON_TRACK if safety_observations >= 50 else KPIStatus.AT_RISK,\r\n                trend=self._calculate_trend(\"Safety Observations\"),\r\n                last_updated=datetime.now()\r\n            ),\r\n            KPIMetric(\r\n                name=\"Near Miss Reports\",\r\n                category=KPICategory.SAFETY,\r\n                current_value=near_misses,\r\n                target_value=10,\r\n                unit=\"count\",\r\n                status=KPIStatus.ON_TRACK,\r\n                trend=self._calculate_trend(\"Near Miss Reports\"),\r\n                last_updated=datetime.now()\r\n            )\r\n        ]\r\n\r\n        for m in metrics:\r\n            self.add_metric(m)\r\n\r\n        return metrics\r\n\r\n    def _get_status(self, value: float, category: str, inverse: bool = False) -> KPIStatus:\r\n        \"\"\"Determine RAG status based on thresholds.\"\"\"\r\n        thresholds = self.THRESHOLDS.get(category, {'green': 0.95, 'amber': 0.85})\r\n\r\n        if inverse:\r\n            if value >= thresholds['green']:\r\n                return KPIStatus.ON_TRACK\r\n            elif value >= thresholds['amber']:\r\n                return KPIStatus.AT_RISK\r\n            else:\r\n                return KPIStatus.CRITICAL\r\n        else:\r\n            if value >= thresholds['green']:\r\n                return KPIStatus.ON_TRACK\r\n            elif value >= thresholds['amber']:\r\n                return KPIStatus.AT_RISK\r\n            else:\r\n                return KPIStatus.CRITICAL\r\n\r\n    def _calculate_trend(self, metric_name: str) -> str:\r\n        \"\"\"Calculate trend based on historical data.\"\"\"\r\n        history = [h for h in self.history if h['name'] == metric_name]\r\n        if len(history) \u003C 2:\r\n            return \"stable\"\r\n\r\n        recent = history[-1]['value']\r\n        previous = history[-2]['value']\r\n\r\n        if recent > previous * 1.02:\r\n            return \"up\"\r\n        elif recent \u003C previous * 0.98:\r\n            return \"down\"\r\n        return \"stable\"\r\n\r\n    def get_dashboard_summary(self) -> Dict[str, Any]:\r\n        \"\"\"Generate dashboard summary.\"\"\"\r\n        by_category = {}\r\n        for metric in self.metrics.values():\r\n            cat = metric.category.value\r\n            if cat not in by_category:\r\n                by_category[cat] = []\r\n            by_category[cat].append({\r\n                'name': metric.name,\r\n                'value': metric.current_value,\r\n                'target': metric.target_value,\r\n                'unit': metric.unit,\r\n                'status': metric.status.value,\r\n                'trend': metric.trend,\r\n                'variance': round(metric.variance, 1)\r\n            })\r\n\r\n        # Overall health\r\n        statuses = [m.status for m in self.metrics.values()]\r\n        critical_count = sum(1 for s in statuses if s == KPIStatus.CRITICAL)\r\n        at_risk_count = sum(1 for s in statuses if s == KPIStatus.AT_RISK)\r\n\r\n        if critical_count > 0:\r\n            overall = \"CRITICAL\"\r\n        elif at_risk_count > 2:\r\n            overall = \"AT_RISK\"\r\n        else:\r\n            overall = \"ON_TRACK\"\r\n\r\n        return {\r\n            'project': self.config.project_name,\r\n            'project_code': self.config.project_code,\r\n            'generated_at': datetime.now().isoformat(),\r\n            'overall_health': overall,\r\n            'metrics_count': len(self.metrics),\r\n            'critical_count': critical_count,\r\n            'at_risk_count': at_risk_count,\r\n            'kpis_by_category': by_category\r\n        }\r\n\r\n    def export_to_dataframe(self) -> pd.DataFrame:\r\n        \"\"\"Export all KPIs to DataFrame.\"\"\"\r\n        data = []\r\n        for metric in self.metrics.values():\r\n            data.append({\r\n                'KPI': metric.name,\r\n                'Category': metric.category.value,\r\n                'Current': metric.current_value,\r\n                'Target': metric.target_value,\r\n                'Unit': metric.unit,\r\n                'Variance %': round(metric.variance, 1),\r\n                'Status': metric.status.value,\r\n                'Trend': metric.trend,\r\n                'Last Updated': metric.last_updated\r\n            })\r\n        return pd.DataFrame(data)\r\n```\r\n\r\n## Quick Start\r\n\r\n```python\r\nfrom datetime import date\r\n\r\n# Configure dashboard\r\nconfig = DashboardConfig(\r\n    project_name=\"Office Tower Construction\",\r\n    project_code=\"PRJ-2024-001\",\r\n    start_date=date(2024, 1, 1),\r\n    end_date=date(2025, 12, 31),\r\n    budget=50000000,\r\n    currency=\"USD\"\r\n)\r\n\r\n# Initialize dashboard\r\ndashboard = ProjectKPIDashboard(config)\r\n\r\n# Calculate schedule KPIs\r\ndashboard.calculate_schedule_kpis(\r\n    planned_activities=100,\r\n    completed_activities=85,\r\n    planned_duration_days=180,\r\n    actual_duration_days=195\r\n)\r\n\r\n# Calculate cost KPIs\r\ndashboard.calculate_cost_kpis(\r\n    budgeted_cost=25000000,\r\n    actual_cost=24500000,\r\n    earned_value=24000000\r\n)\r\n\r\n# Get summary\r\nsummary = dashboard.get_dashboard_summary()\r\nprint(f\"Overall Health: {summary['overall_health']}\")\r\n```\r\n\r\n## Common Use Cases\r\n\r\n### 1. Weekly Executive Report\r\n```python\r\ndf = dashboard.export_to_dataframe()\r\ncritical = df[df['Status'] == 'critical']\r\nprint(f\"Critical KPIs requiring attention: {len(critical)}\")\r\n```\r\n\r\n### 2. Trend Analysis\r\n```python\r\n# Get historical data for a metric\r\nspi_history = [h for h in dashboard.history if h['name'] == 'Schedule Performance Index']\r\n```\r\n\r\n### 3. Multi-Project Dashboard\r\n```python\r\nprojects = []\r\nfor project_config in project_configs:\r\n    dash = ProjectKPIDashboard(project_config)\r\n    # ... calculate KPIs\r\n    projects.append(dash.get_dashboard_summary())\r\n```\r\n\r\n## Resources\r\n- **DDC Book**: Chapter 4.1 - Construction Analytics\r\n- **Reference**: PMI Earned Value Management\r\n","# Project KPI Dashboard\r\n\r\n## Business Case\r\n\r\n### Problem Statement\r\nProject stakeholders struggle with:\r\n- Scattered data across multiple systems\r\n- Delayed reporting on project health\r\n- No real-time visibility into KPIs\r\n- Inconsistent metric definitions\r\n\r\n### Solution\r\nCentralized KPI dashboard that aggregates data from multiple sources and presents key metrics with drill-down capabilities.\r\n\r\n### Business Value\r\n- **Real-time visibility** - Live project health status\r\n- **Data-driven decisions** - Actionable insights\r\n- **Stakeholder alignment** - Single source of truth\r\n- **Early warning** - Proactive issue detection\r\n\r\n## Technical Implementation\r\n\r\n```python\r\nimport pandas as pd\r\nfrom datetime import datetime, date, timedelta\r\nfrom typing import Dict, Any, List, Optional\r\nfrom dataclasses import dataclass, field\r\nfrom enum import Enum\r\n\r\n\r\nclass KPIStatus(Enum):\r\n    \"\"\"KPI health status.\"\"\"\r\n    ON_TRACK = \"on_track\"\r\n    AT_RISK = \"at_risk\"\r\n    CRITICAL = \"critical\"\r\n    UNKNOWN = \"unknown\"\r\n\r\n\r\nclass KPICategory(Enum):\r\n    \"\"\"KPI categories.\"\"\"\r\n    SCHEDULE = \"schedule\"\r\n    COST = \"cost\"\r\n    QUALITY = \"quality\"\r\n    SAFETY = \"safety\"\r\n    PRODUCTIVITY = \"productivity\"\r\n    SUSTAINABILITY = \"sustainability\"\r\n\r\n\r\n@dataclass\r\nclass KPIMetric:\r\n    \"\"\"Single KPI metric.\"\"\"\r\n    name: str\r\n    category: KPICategory\r\n    current_value: float\r\n    target_value: float\r\n    unit: str\r\n    status: KPIStatus\r\n    trend: str  # up, down, stable\r\n    last_updated: datetime\r\n    description: str = \"\"\r\n\r\n    @property\r\n    def variance(self) -> float:\r\n        \"\"\"Calculate variance from target.\"\"\"\r\n        if self.target_value == 0:\r\n            return 0\r\n        return ((self.current_value - self.target_value) \u002F self.target_value) * 100\r\n\r\n    @property\r\n    def achievement(self) -> float:\r\n        \"\"\"Calculate achievement percentage.\"\"\"\r\n        if self.target_value == 0:\r\n            return 0\r\n        return (self.current_value \u002F self.target_value) * 100\r\n\r\n\r\n@dataclass\r\nclass DashboardConfig:\r\n    \"\"\"Dashboard configuration.\"\"\"\r\n    project_name: str\r\n    project_code: str\r\n    start_date: date\r\n    end_date: date\r\n    budget: float\r\n    currency: str = \"USD\"\r\n    refresh_interval_minutes: int = 15\r\n\r\n\r\nclass ProjectKPIDashboard:\r\n    \"\"\"Construction project KPI dashboard.\"\"\"\r\n\r\n    # Standard thresholds for RAG status\r\n    THRESHOLDS = {\r\n        'schedule': {'green': 0.95, 'amber': 0.85},\r\n        'cost': {'green': 1.05, 'amber': 1.15},\r\n        'quality': {'green': 0.98, 'amber': 0.95},\r\n        'safety': {'green': 0, 'amber': 1}  # incident count\r\n    }\r\n\r\n    def __init__(self, config: DashboardConfig):\r\n        self.config = config\r\n        self.metrics: Dict[str, KPIMetric] = {}\r\n        self.history: List[Dict[str, Any]] = []\r\n\r\n    def add_metric(self, metric: KPIMetric):\r\n        \"\"\"Add or update a KPI metric.\"\"\"\r\n        self.metrics[metric.name] = metric\r\n        self._record_history(metric)\r\n\r\n    def _record_history(self, metric: KPIMetric):\r\n        \"\"\"Record metric history for trending.\"\"\"\r\n        self.history.append({\r\n            'name': metric.name,\r\n            'value': metric.current_value,\r\n            'timestamp': metric.last_updated,\r\n            'status': metric.status.value\r\n        })\r\n\r\n    def calculate_schedule_kpis(self,\r\n                                 planned_activities: int,\r\n                                 completed_activities: int,\r\n                                 planned_duration_days: int,\r\n                                 actual_duration_days: int) -> List[KPIMetric]:\r\n        \"\"\"Calculate schedule-related KPIs.\"\"\"\r\n\r\n        # Schedule Performance Index (SPI)\r\n        spi = completed_activities \u002F planned_activities if planned_activities > 0 else 0\r\n        spi_status = self._get_status(spi, 'schedule')\r\n\r\n        # Schedule Variance\r\n        sv = completed_activities - planned_activities\r\n\r\n        # Percent Complete\r\n        pct_complete = (completed_activities \u002F planned_activities * 100) if planned_activities > 0 else 0\r\n\r\n        metrics = [\r\n            KPIMetric(\r\n                name=\"Schedule Performance Index\",\r\n                category=KPICategory.SCHEDULE,\r\n                current_value=round(spi, 2),\r\n                target_value=1.0,\r\n                unit=\"ratio\",\r\n                status=spi_status,\r\n                trend=self._calculate_trend(\"Schedule Performance Index\"),\r\n                last_updated=datetime.now(),\r\n                description=\"SPI = Earned Value \u002F Planned Value\"\r\n            ),\r\n            KPIMetric(\r\n                name=\"Percent Complete\",\r\n                category=KPICategory.SCHEDULE,\r\n                current_value=round(pct_complete, 1),\r\n                target_value=100,\r\n                unit=\"%\",\r\n                status=spi_status,\r\n                trend=self._calculate_trend(\"Percent Complete\"),\r\n                last_updated=datetime.now()\r\n            ),\r\n            KPIMetric(\r\n                name=\"Schedule Variance\",\r\n                category=KPICategory.SCHEDULE,\r\n                current_value=sv,\r\n                target_value=0,\r\n                unit=\"activities\",\r\n                status=spi_status,\r\n                trend=self._calculate_trend(\"Schedule Variance\"),\r\n                last_updated=datetime.now()\r\n            )\r\n        ]\r\n\r\n        for m in metrics:\r\n            self.add_metric(m)\r\n\r\n        return metrics\r\n\r\n    def calculate_cost_kpis(self,\r\n                            budgeted_cost: float,\r\n                            actual_cost: float,\r\n                            earned_value: float) -> List[KPIMetric]:\r\n        \"\"\"Calculate cost-related KPIs.\"\"\"\r\n\r\n        # Cost Performance Index (CPI)\r\n        cpi = earned_value \u002F actual_cost if actual_cost > 0 else 0\r\n        cpi_status = self._get_status(cpi, 'cost', inverse=True)\r\n\r\n        # Cost Variance\r\n        cv = earned_value - actual_cost\r\n\r\n        # Budget utilization\r\n        budget_used = (actual_cost \u002F budgeted_cost * 100) if budgeted_cost > 0 else 0\r\n\r\n        metrics = [\r\n            KPIMetric(\r\n                name=\"Cost Performance Index\",\r\n                category=KPICategory.COST,\r\n                current_value=round(cpi, 2),\r\n                target_value=1.0,\r\n                unit=\"ratio\",\r\n                status=cpi_status,\r\n                trend=self._calculate_trend(\"Cost Performance Index\"),\r\n                last_updated=datetime.now(),\r\n                description=\"CPI = Earned Value \u002F Actual Cost\"\r\n            ),\r\n            KPIMetric(\r\n                name=\"Cost Variance\",\r\n                category=KPICategory.COST,\r\n                current_value=round(cv, 2),\r\n                target_value=0,\r\n                unit=self.config.currency,\r\n                status=cpi_status,\r\n                trend=self._calculate_trend(\"Cost Variance\"),\r\n                last_updated=datetime.now()\r\n            ),\r\n            KPIMetric(\r\n                name=\"Budget Utilization\",\r\n                category=KPICategory.COST,\r\n                current_value=round(budget_used, 1),\r\n                target_value=100,\r\n                unit=\"%\",\r\n                status=cpi_status,\r\n                trend=self._calculate_trend(\"Budget Utilization\"),\r\n                last_updated=datetime.now()\r\n            )\r\n        ]\r\n\r\n        for m in metrics:\r\n            self.add_metric(m)\r\n\r\n        return metrics\r\n\r\n    def calculate_quality_kpis(self,\r\n                               total_inspections: int,\r\n                               passed_inspections: int,\r\n                               rework_items: int,\r\n                               total_items: int) -> List[KPIMetric]:\r\n        \"\"\"Calculate quality-related KPIs.\"\"\"\r\n\r\n        # First Pass Yield\r\n        fpy = passed_inspections \u002F total_inspections if total_inspections > 0 else 0\r\n        fpy_status = self._get_status(fpy, 'quality')\r\n\r\n        # Rework Rate\r\n        rework_rate = rework_items \u002F total_items * 100 if total_items > 0 else 0\r\n\r\n        metrics = [\r\n            KPIMetric(\r\n                name=\"First Pass Yield\",\r\n                category=KPICategory.QUALITY,\r\n                current_value=round(fpy * 100, 1),\r\n                target_value=98,\r\n                unit=\"%\",\r\n                status=fpy_status,\r\n                trend=self._calculate_trend(\"First Pass Yield\"),\r\n                last_updated=datetime.now()\r\n            ),\r\n            KPIMetric(\r\n                name=\"Rework Rate\",\r\n                category=KPICategory.QUALITY,\r\n                current_value=round(rework_rate, 1),\r\n                target_value=2,\r\n                unit=\"%\",\r\n                status=fpy_status,\r\n                trend=self._calculate_trend(\"Rework Rate\"),\r\n                last_updated=datetime.now()\r\n            )\r\n        ]\r\n\r\n        for m in metrics:\r\n            self.add_metric(m)\r\n\r\n        return metrics\r\n\r\n    def calculate_safety_kpis(self,\r\n                              incidents: int,\r\n                              near_misses: int,\r\n                              worked_hours: float,\r\n                              safety_observations: int) -> List[KPIMetric]:\r\n        \"\"\"Calculate safety-related KPIs.\"\"\"\r\n\r\n        # TRIR (Total Recordable Incident Rate)\r\n        trir = (incidents * 200000) \u002F worked_hours if worked_hours > 0 else 0\r\n        trir_status = KPIStatus.ON_TRACK if incidents == 0 else (\r\n            KPIStatus.AT_RISK if incidents \u003C= 2 else KPIStatus.CRITICAL\r\n        )\r\n\r\n        # LTIR (Lost Time Incident Rate)\r\n        ltir = (incidents * 1000000) \u002F worked_hours if worked_hours > 0 else 0\r\n\r\n        metrics = [\r\n            KPIMetric(\r\n                name=\"TRIR\",\r\n                category=KPICategory.SAFETY,\r\n                current_value=round(trir, 2),\r\n                target_value=0,\r\n                unit=\"per 200k hrs\",\r\n                status=trir_status,\r\n                trend=self._calculate_trend(\"TRIR\"),\r\n                last_updated=datetime.now(),\r\n                description=\"Total Recordable Incident Rate\"\r\n            ),\r\n            KPIMetric(\r\n                name=\"Safety Observations\",\r\n                category=KPICategory.SAFETY,\r\n                current_value=safety_observations,\r\n                target_value=50,\r\n                unit=\"count\",\r\n                status=KPIStatus.ON_TRACK if safety_observations >= 50 else KPIStatus.AT_RISK,\r\n                trend=self._calculate_trend(\"Safety Observations\"),\r\n                last_updated=datetime.now()\r\n            ),\r\n            KPIMetric(\r\n                name=\"Near Miss Reports\",\r\n                category=KPICategory.SAFETY,\r\n                current_value=near_misses,\r\n                target_value=10,\r\n                unit=\"count\",\r\n                status=KPIStatus.ON_TRACK,\r\n                trend=self._calculate_trend(\"Near Miss Reports\"),\r\n                last_updated=datetime.now()\r\n            )\r\n        ]\r\n\r\n        for m in metrics:\r\n            self.add_metric(m)\r\n\r\n        return metrics\r\n\r\n    def _get_status(self, value: float, category: str, inverse: bool = False) -> KPIStatus:\r\n        \"\"\"Determine RAG status based on thresholds.\"\"\"\r\n        thresholds = self.THRESHOLDS.get(category, {'green': 0.95, 'amber': 0.85})\r\n\r\n        if inverse:\r\n            if value >= thresholds['green']:\r\n                return KPIStatus.ON_TRACK\r\n            elif value >= thresholds['amber']:\r\n                return KPIStatus.AT_RISK\r\n            else:\r\n                return KPIStatus.CRITICAL\r\n        else:\r\n            if value >= thresholds['green']:\r\n                return KPIStatus.ON_TRACK\r\n            elif value >= thresholds['amber']:\r\n                return KPIStatus.AT_RISK\r\n            else:\r\n                return KPIStatus.CRITICAL\r\n\r\n    def _calculate_trend(self, metric_name: str) -> str:\r\n        \"\"\"Calculate trend based on historical data.\"\"\"\r\n        history = [h for h in self.history if h['name'] == metric_name]\r\n        if len(history) \u003C 2:\r\n            return \"stable\"\r\n\r\n        recent = history[-1]['value']\r\n        previous = history[-2]['value']\r\n\r\n        if recent > previous * 1.02:\r\n            return \"up\"\r\n        elif recent \u003C previous * 0.98:\r\n            return \"down\"\r\n        return \"stable\"\r\n\r\n    def get_dashboard_summary(self) -> Dict[str, Any]:\r\n        \"\"\"Generate dashboard summary.\"\"\"\r\n        by_category = {}\r\n        for metric in self.metrics.values():\r\n            cat = metric.category.value\r\n            if cat not in by_category:\r\n                by_category[cat] = []\r\n            by_category[cat].append({\r\n                'name': metric.name,\r\n                'value': metric.current_value,\r\n                'target': metric.target_value,\r\n                'unit': metric.unit,\r\n                'status': metric.status.value,\r\n                'trend': metric.trend,\r\n                'variance': round(metric.variance, 1)\r\n            })\r\n\r\n        # Overall health\r\n        statuses = [m.status for m in self.metrics.values()]\r\n        critical_count = sum(1 for s in statuses if s == KPIStatus.CRITICAL)\r\n        at_risk_count = sum(1 for s in statuses if s == KPIStatus.AT_RISK)\r\n\r\n        if critical_count > 0:\r\n            overall = \"CRITICAL\"\r\n        elif at_risk_count > 2:\r\n            overall = \"AT_RISK\"\r\n        else:\r\n            overall = \"ON_TRACK\"\r\n\r\n        return {\r\n            'project': self.config.project_name,\r\n            'project_code': self.config.project_code,\r\n            'generated_at': datetime.now().isoformat(),\r\n            'overall_health': overall,\r\n            'metrics_count': len(self.metrics),\r\n            'critical_count': critical_count,\r\n            'at_risk_count': at_risk_count,\r\n            'kpis_by_category': by_category\r\n        }\r\n\r\n    def export_to_dataframe(self) -> pd.DataFrame:\r\n        \"\"\"Export all KPIs to DataFrame.\"\"\"\r\n        data = []\r\n        for metric in self.metrics.values():\r\n            data.append({\r\n                'KPI': metric.name,\r\n                'Category': metric.category.value,\r\n                'Current': metric.current_value,\r\n                'Target': metric.target_value,\r\n                'Unit': metric.unit,\r\n                'Variance %': round(metric.variance, 1),\r\n                'Status': metric.status.value,\r\n                'Trend': metric.trend,\r\n                'Last Updated': metric.last_updated\r\n            })\r\n        return pd.DataFrame(data)\r\n```\r\n\r\n## Quick Start\r\n\r\n```python\r\nfrom datetime import date\r\n\r\n# Configure dashboard\r\nconfig = DashboardConfig(\r\n    project_name=\"Office Tower Construction\",\r\n    project_code=\"PRJ-2024-001\",\r\n    start_date=date(2024, 1, 1),\r\n    end_date=date(2025, 12, 31),\r\n    budget=50000000,\r\n    currency=\"USD\"\r\n)\r\n\r\n# Initialize dashboard\r\ndashboard = ProjectKPIDashboard(config)\r\n\r\n# Calculate schedule KPIs\r\ndashboard.calculate_schedule_kpis(\r\n    planned_activities=100,\r\n    completed_activities=85,\r\n    planned_duration_days=180,\r\n    actual_duration_days=195\r\n)\r\n\r\n# Calculate cost KPIs\r\ndashboard.calculate_cost_kpis(\r\n    budgeted_cost=25000000,\r\n    actual_cost=24500000,\r\n    earned_value=24000000\r\n)\r\n\r\n# Get summary\r\nsummary = dashboard.get_dashboard_summary()\r\nprint(f\"Overall Health: {summary['overall_health']}\")\r\n```\r\n\r\n## Common Use Cases\r\n\r\n### 1. Weekly Executive Report\r\n```python\r\ndf = dashboard.export_to_dataframe()\r\ncritical = df[df['Status'] == 'critical']\r\nprint(f\"Critical KPIs requiring attention: {len(critical)}\")\r\n```\r\n\r\n### 2. Trend Analysis\r\n```python\r\n# Get historical data for a metric\r\nspi_history = [h for h in dashboard.history if h['name'] == 'Schedule Performance Index']\r\n```\r\n\r\n### 3. Multi-Project Dashboard\r\n```python\r\nprojects = []\r\nfor project_config in project_configs:\r\n    dash = ProjectKPIDashboard(project_config)\r\n    # ... calculate KPIs\r\n    projects.append(dash.get_dashboard_summary())\r\n```\r\n\r\n## Resources\r\n- **DDC Book**: Chapter 4.1 - Construction Analytics\r\n- **Reference**: PMI Earned Value Management\r\n",{"slug":5,"description":7,"display_name":49},"Project KPI Dashboard","main","e7e336ac628c3e3bd771aff1aaa469c93778f94a","skills\u002Fdatadrivenconstruction\u002Fproject-kpi-dashboard",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":58,"prompt":68,"zipAvailable":69},[59,62,65],{"tool":60,"command":61},"npx","npx skills add kbarbel640-del\u002Fskills --skill project-kpi-dashboard",{"tool":63,"command":64},"bunx","bunx skills add kbarbel640-del\u002Fskills --skill project-kpi-dashboard",{"tool":66,"command":67},"pnpm","pnpm dlx skills add kbarbel640-del\u002Fskills --skill project-kpi-dashboard","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdatadrivenconstruction\u002Fproject-kpi-dashboard\u002FSKILL.md",true,[71],{"path":72,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":75,"sourceHtmlUrl":76},"_meta.json","REFERENCE","参考文档",310,"https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdatadrivenconstruction\u002Fproject-kpi-dashboard\u002F_meta.json",[78,107,133,154,160,180],{"skillNo":79,"name":80,"nameZh":6,"description":81,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":82,"categories":83,"keywords":87,"publishedAt":44},"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",[84],{"categoryNo":85,"categoryName":86,"taxonomyType":25,"taxonomyTypeDesc":26},"01JBSEEDCAT000000000000036","内容创作者",[88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106],"crowd prompting","prompting","api","with","tokens","domain","world","prompts","operations","instructions","text","tool","system","improve","productivity","marketplace","https","content","ai agents",{"skillNo":108,"name":109,"nameZh":6,"description":110,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":111,"categories":112,"keywords":116,"publishedAt":44},"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",[113],{"categoryNo":114,"categoryName":115,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000023","Skill与智能体开发",[117,90,118,119,120,121,33,122,123,124,125,126,127,128,129,130,131,95,132],"dilemm ai","arena","analyze","browser","openclaw agent","automation","browser automation","bot","openclaw","design","strategy","headless","compete","submit","matchmaking","use",{"skillNo":134,"name":135,"nameZh":6,"description":136,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":137,"categories":138,"keywords":142,"publishedAt":44},"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",[139],{"categoryNo":140,"categoryName":141,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000021","资料检索与研究",[143,144,145,146,147,148,149,91,150,130,132,151,90,152,153],"arxiv paper reviews","reviews","arxiv","paper","crawler","papers","comments","fetch","read","http","via",{"skillNo":155,"name":135,"nameZh":6,"description":136,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":156,"categories":157,"keywords":159,"publishedAt":44},"01M6G8HQJ7N7WMY6YV7D7SSJ16","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzxrys\u002Farxiv-paper-reviews\u002FSKILL.md",[158],{"categoryNo":140,"categoryName":141,"taxonomyType":20,"taxonomyTypeDesc":21},[146,145,144,143,153,148,149,91,147,130,132,150,151,90,152],{"skillNo":161,"name":162,"nameZh":6,"description":163,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":164,"categories":165,"keywords":166,"publishedAt":44},"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",[],[167,168,169,170,171,172,173,174,175,176,177,178,179],"git","summary","git summary","commits","repository","win32","status","linux","contributors","true","quick","get","branches",{"skillNo":181,"name":182,"nameZh":6,"description":183,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":184,"categories":185,"keywords":190,"publishedAt":44},"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",[186,187],{"categoryNo":114,"categoryName":115,"taxonomyType":20,"taxonomyTypeDesc":21},{"categoryNo":188,"categoryName":189,"taxonomyType":25,"taxonomyTypeDesc":26},"01JBSEEDCAT000000000000037","律师与法务",[191,192,193,194,91,195,196,50,197,151,198,199,38,200,201,104,202,203,204],"agent constitution","constitution","contracts","tree","query","base","log","check","Rules","compliance","actions","state","governance","github"]