mcpbeat

Labor Productivity Analyzer

datadrivenconstruction/labor-productivity-analyzer

Analyze labor productivity by trade, activity, and location. Track efficiency and identify improvement opportunities.

2k tokens
context cost
the whole folder, loaded on every use
3
files
instructions only
0
copies elsewhere
how many repositories repackaged it
264
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/datadrivenconstruction/DDC_Skills_for_AI_Agents_in_Construction --skill labor-productivity-analyzer

The instruction itself

4 sections, as written by the author

Labor Productivity Analyzer

Technical Implementation

import pandas as pd
from datetime import date
from typing import Dict, Any, List
from dataclasses import dataclass, field
from enum import Enum


class ProductivityStatus(Enum):
    EXCEEDING = "exceeding"
    ON_TARGET = "on_target"
    BELOW_TARGET = "below_target"
    CRITICAL = "critical"


@dataclass
class ProductivityEntry:
    entry_id: str
    date: date
    trade: str
    activity_code: str
    activity_description: str
    location: str
    crew_size: int
    hours_worked: float
    quantity_installed: float
    unit: str
    target_productivity: float  # units per hour

    @property
    def actual_productivity(self) -> float:
        if self.hours_worked == 0:
            return 0
        return self.quantity_installed / self.hours_worked

    @property
    def productivity_factor(self) -> float:
        if self.target_productivity == 0:
            return 0
        return self.actual_productivity / self.target_productivity

    @property
    def status(self) -> ProductivityStatus:
        pf = self.productivity_factor
        if pf >= 1.1:
            return ProductivityStatus.EXCEEDING
        elif pf >= 0.9:
            return ProductivityStatus.ON_TARGET
        elif pf >= 0.7:
            return ProductivityStatus.BELOW_TARGET
        return ProductivityStatus.CRITICAL


class LaborProductivityAnalyzer:
    def __init__(self, project_name: str):
        self.project_name = project_name
        self.entries: List[ProductivityEntry] = []
        self.targets: Dict[str, float] = {}  # activity_code: target_productivity
        self._counter = 0

    def set_target(self, activity_code: str, target_productivity: float):
        self.targets[activity_code] = target_productivity

    def add_entry(self, entry_date: date, trade: str, activity_code: str,
                 activity_description: str, location: str, crew_size: int,
                 hours_worked: float, quantity_installed: float,
                 unit: str) -> ProductivityEntry:
        self._counter += 1
        entry_id = f"PROD-{self._counter:05d}"

        target = self.targets.get(activity_code, 1.0)

        entry = ProductivityEntry(
            entry_id=entry_id,
            date=entry_date,
            trade=trade,
            activity_code=activity_code,
            activity_description=activity_description,
            location=location,
            crew_size=crew_size,
            hours_worked=hours_worked,
            quantity_installed=quantity_installed,
            unit=unit,
            target_productivity=target
        )
        self.entries.append(entry)
        return entry

    def get_productivity_by_trade(self) -> Dict[str, Dict[str, Any]]:
        by_trade = {}
        for entry in self.entries:
            if entry.trade not in by_trade:
                by_trade[entry.trade] = {'hours': 0, 'quantity': 0, 'entries': 0}
            by_trade[entry.trade]['hours'] += entry.hours_worked
            by_trade[entry.trade]['quantity'] += entry.quantity_installed
            by_trade[entry.trade]['entries'] += 1

        for trade in by_trade:
            hours = by_trade[trade]['hours']
            qty = by_trade[trade]['quantity']
            by_trade[trade]['avg_productivity'] = qty / hours if hours > 0 else 0

        return by_trade

    def get_productivity_by_activity(self) -> Dict[str, Dict[str, Any]]:
        by_activity = {}
        for entry in self.entries:
            code = entry.activity_code
            if code not in by_activity:
                by_activity[code] = {
                    'description': entry.activity_description,
                    'hours': 0, 'quantity': 0, 'target': entry.target_productivity
                }
            by_activity[code]['hours'] += entry.hours_worked
            by_activity[code]['quantity'] += entry.quantity_installed

        for code in by_activity:
            hours = by_activity[code]['hours']
            qty = by_activity[code]['quantity']
            by_activity[code]['actual'] = qty / hours if hours > 0 else 0
            by_activity[code]['factor'] = (
                by_activity[code]['actual'] / by_activity[code]['target']
                if by_activity[code]['target'] > 0 else 0
            )

        return by_activity

    def get_low_performers(self) -> List[ProductivityEntry]:
        return [e for e in self.entries
                if e.status in [ProductivityStatus.BELOW_TARGET, ProductivityStatus.CRITICAL]]

    def get_summary(self) -> Dict[str, Any]:
        if not self.entries:
            return {'total_entries': 0}

        total_hours = sum(e.hours_worked for e in self.entries)
        factors = [e.productivity_factor for e in self.entries]
        avg_factor = sum(factors) / len(factors)

        return {
            'total_entries': len(self.entries),
            'total_hours': total_hours,
            'average_productivity_factor': round(avg_factor, 2),
            'exceeding': sum(1 for e in self.entries if e.status == ProductivityStatus.EXCEEDING),
            'on_target': sum(1 for e in self.entries if e.status == ProductivityStatus.ON_TARGET),
            'below_target': sum(1 for e in self.entries if e.status == ProductivityStatus.BELOW_TARGET),
            'critical': sum(1 for e in self.entries if e.status == ProductivityStatus.CRITICAL)
        }

    def export_report(self, output_path: str):
        data = [{
            'Date': e.date,
            'Trade': e.trade,
            'Activity': e.activity_code,
            'Location': e.location,
            'Crew': e.crew_size,
            'Hours': e.hours_worked,
            'Quantity': e.quantity_installed,
            'Unit': e.unit,
            'Target': e.target_productivity,
            'Actual': round(e.actual_productivity, 2),
            'Factor': round(e.productivity_factor, 2),
            'Status': e.status.value
        } for e in self.entries]
        pd.DataFrame(data).to_excel(output_path, index=False)

Quick Start

analyzer = LaborProductivityAnalyzer("Office Tower")

# Set targets
analyzer.set_target("CONC-001", 2.5)  # m3 per hour

# Add entry
entry = analyzer.add_entry(
    entry_date=date.today(),
    trade="Concrete",
    activity_code="CONC-001",
    activity_description="Pour concrete slab",
    location="Level 3",
    crew_size=8,
    hours_worked=80,
    quantity_installed=180,
    unit="m3"
)

print(f"Productivity factor: {entry.productivity_factor:.2f}")
print(f"Status: {entry.status.value}")

Resources

  • DDC Book: Chapter 3.2 - Resource Management

How to use it

Copy the folder

Take datadrivenconstruction/labor-productivity-analyzer from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.