schedule-delay-analyzer

SKILL.md

Schedule Delay Analyzer

Technical Implementation

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


class DelayType(Enum):
    EXCUSABLE_COMPENSABLE = "excusable_compensable"
    EXCUSABLE_NON_COMPENSABLE = "excusable_non_compensable"
    NON_EXCUSABLE = "non_excusable"
    CONCURRENT = "concurrent"


class DelayCause(Enum):
    OWNER_CHANGE = "owner_change"
    DESIGN_ERROR = "design_error"
    WEATHER = "weather"
    DIFFERING_CONDITIONS = "differing_conditions"
    CONTRACTOR_ISSUE = "contractor_issue"
    MATERIAL_DELAY = "material_delay"
    LABOR_SHORTAGE = "labor_shortage"
    PERMIT_DELAY = "permit_delay"
    OTHER = "other"


@dataclass
class DelayEvent:
    delay_id: str
    activity_id: str
    activity_name: str
    delay_type: DelayType
    cause: DelayCause
    start_date: date
    end_date: date
    delay_days: int
    on_critical_path: bool
    description: str
    documentation: List[str] = field(default_factory=list)
    cost_impact: float = 0.0


@dataclass
class ScheduleBaseline:
    baseline_date: date
    planned_completion: date
    activities: Dict[str, Dict[str, date]]  # activity_id: {start, end}


class ScheduleDelayAnalyzer:
    def __init__(self, project_name: str, contract_completion: date):
        self.project_name = project_name
        self.contract_completion = contract_completion
        self.baselines: List[ScheduleBaseline] = []
        self.delays: Dict[str, DelayEvent] = {}
        self._counter = 0

    def add_baseline(self, baseline_date: date, planned_completion: date,
                    activities: Dict[str, Dict[str, date]]):
        baseline = ScheduleBaseline(baseline_date, planned_completion, activities)
        self.baselines.append(baseline)

    def record_delay(self, activity_id: str, activity_name: str,
                    delay_type: DelayType, cause: DelayCause,
                    start_date: date, end_date: date,
                    on_critical_path: bool, description: str,
                    cost_impact: float = 0) -> DelayEvent:
        self._counter += 1
        delay_id = f"DLY-{self._counter:04d}"

        delay = DelayEvent(
            delay_id=delay_id,
            activity_id=activity_id,
            activity_name=activity_name,
            delay_type=delay_type,
            cause=cause,
            start_date=start_date,
            end_date=end_date,
            delay_days=(end_date - start_date).days,
            on_critical_path=on_critical_path,
            description=description,
            cost_impact=cost_impact
        )
        self.delays[delay_id] = delay
        return delay

    def calculate_project_delay(self) -> int:
        """Calculate total critical path delay."""
        critical_delays = [d for d in self.delays.values() if d.on_critical_path]
        return sum(d.delay_days for d in critical_delays)

    def analyze_by_type(self) -> Dict[str, Dict[str, Any]]:
        analysis = {}
        for delay in self.delays.values():
            dtype = delay.delay_type.value
            if dtype not in analysis:
                analysis[dtype] = {'count': 0, 'days': 0, 'cost': 0}
            analysis[dtype]['count'] += 1
            analysis[dtype]['days'] += delay.delay_days
            analysis[dtype]['cost'] += delay.cost_impact
        return analysis

    def analyze_by_cause(self) -> Dict[str, int]:
        by_cause = {}
        for delay in self.delays.values():
            cause = delay.cause.value
            by_cause[cause] = by_cause.get(cause, 0) + delay.delay_days
        return by_cause

    def calculate_time_extension_claim(self) -> Dict[str, Any]:
        """Calculate basis for time extension claim."""
        excusable = [d for d in self.delays.values()
                    if d.delay_type in [DelayType.EXCUSABLE_COMPENSABLE,
                                        DelayType.EXCUSABLE_NON_COMPENSABLE]
                    and d.on_critical_path]

        compensable = [d for d in excusable
                      if d.delay_type == DelayType.EXCUSABLE_COMPENSABLE]

        return {
            'excusable_delays': len(excusable),
            'excusable_days': sum(d.delay_days for d in excusable),
            'compensable_delays': len(compensable),
            'compensable_days': sum(d.delay_days for d in compensable),
            'total_cost_impact': sum(d.cost_impact for d in compensable),
            'recommended_extension': sum(d.delay_days for d in excusable)
        }

    def get_summary(self) -> Dict[str, Any]:
        critical_delay = self.calculate_project_delay()
        projected_completion = self.contract_completion + timedelta(days=critical_delay)

        return {
            'project': self.project_name,
            'contract_completion': self.contract_completion,
            'projected_completion': projected_completion,
            'total_delays': len(self.delays),
            'critical_path_delays': sum(1 for d in self.delays.values() if d.on_critical_path),
            'total_delay_days': critical_delay,
            'by_type': self.analyze_by_type(),
            'by_cause': self.analyze_by_cause()
        }

    def export_analysis(self, output_path: str):
        data = [{
            'ID': d.delay_id,
            'Activity': d.activity_name,
            'Type': d.delay_type.value,
            'Cause': d.cause.value,
            'Start': d.start_date,
            'End': d.end_date,
            'Days': d.delay_days,
            'Critical': d.on_critical_path,
            'Cost Impact': d.cost_impact,
            'Description': d.description
        } for d in self.delays.values()]
        pd.DataFrame(data).to_excel(output_path, index=False)

Quick Start

analyzer = ScheduleDelayAnalyzer("Office Tower", date(2024, 12, 31))

delay = analyzer.record_delay(
    activity_id="A-300",
    activity_name="Foundation Work",
    delay_type=DelayType.EXCUSABLE_COMPENSABLE,
    cause=DelayCause.OWNER_CHANGE,
    start_date=date(2024, 3, 1),
    end_date=date(2024, 3, 15),
    on_critical_path=True,
    description="Owner requested additional scope",
    cost_impact=50000
)

summary = analyzer.get_summary()
print(f"Project delayed by {summary['total_delay_days']} days")

claim = analyzer.calculate_time_extension_claim()
print(f"Recommended extension: {claim['recommended_extension']} days")

Resources

  • DDC Book: Chapter 3.3 - Schedule Management
Weekly Installs
2
GitHub Stars
55
First Seen
11 days ago
Installed on
amp2
cline2
opencode2
cursor2
kimi-cli2
codex2