본문으로 건너뛰기
cccskills
무료GitHub에 공개

lien-waiver-tracker

Track and manage construction lien waivers. Monitor conditional and unconditional waivers, ensure compliance before payments, and prevent lien exposure.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

실행 점수 미검증

설치 방법 보기

포함된 파일(3)

  • SKILL.md16.6 KB
  • claw.json512 B
  • instructions.md1.2 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Lien Waiver Tracker

Overview

Track and manage lien waivers throughout the construction payment process. Ensure proper waivers are received before releasing payments, monitor waiver status by subcontractor, and minimize lien exposure.

Lien Waiver Types

┌─────────────────────────────────────────────────────────────────┐
│                    LIEN WAIVER TYPES                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  CONDITIONAL                        UNCONDITIONAL                │
│  ───────────                        ─────────────                │
│  📋 Progress - Conditional          ✅ Progress - Unconditional  │
│     Effective when paid                Immediately effective     │
│     Use with payment                   Use after check clears    │
│                                                                  │
│  📋 Final - Conditional             ✅ Final - Unconditional     │
│     For final payment                  For final payment         │
│     Upon receipt of funds              After funds received      │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Technical Implementation

from dataclasses import dataclass, field
from typing import List, Dict, Optional
from datetime import datetime, timedelta
from enum import Enum

class WaiverType(Enum):
    CONDITIONAL_PROGRESS = "conditional_progress"
    UNCONDITIONAL_PROGRESS = "unconditional_progress"
    CONDITIONAL_FINAL = "conditional_final"
    UNCONDITIONAL_FINAL = "unconditional_final"

class WaiverStatus(Enum):
    REQUESTED = "requested"
    RECEIVED = "received"
    VERIFIED = "verified"
    REJECTED = "rejected"
    MISSING = "missing"

class PaymentStatus(Enum):
    PENDING = "pending"
    APPROVED = "approved"
    HELD = "held"
    RELEASED = "released"

@dataclass
class Subcontractor:
    id: str
    name: str
    trade: str
    contract_amount: float
    contact_name: str
    contact_email: str
    tier: int = 1  # 1 = direct, 2 = sub-sub

@dataclass
class LienWaiver:
    id: str
    subcontractor_id: str
    waiver_type: WaiverType
    payment_application: int  # Pay app number
    through_date: datetime
    amount: float
    status: WaiverStatus = WaiverStatus.REQUESTED
    requested_date: datetime = field(default_factory=datetime.now)
    received_date: Optional[datetime] = None
    verified_by: str = ""
    file_path: str = ""
    notes: str = ""

@dataclass
class PaymentApplication:
    number: int
    period_end: datetime
    subcontractor_id: str
    amount_requested: float
    amount_approved: float
    retainage: float
    status: PaymentStatus = PaymentStatus.PENDING
    waivers_complete: bool = False
    payment_date: Optional[datetime] = None

@dataclass
class LienExposure:
    subcontractor_id: str
    subcontractor_name: str
    total_paid: float
    unconditional_waivers: float
    conditional_pending: float
    exposure: float

class LienWaiverTracker:
    """Track and manage construction lien waivers."""

    def __init__(self, project_id: str, project_name: str):
        self.project_id = project_id
        self.project_name = project_name
        self.subcontractors: Dict[str, Subcontractor] = {}
        self.waivers: Dict[str, LienWaiver] = {}
        self.pay_apps: Dict[str, PaymentApplication] = {}

    def add_subcontractor(self, id: str, name: str, trade: str,
                         contract_amount: float, contact_name: str,
                         contact_email: str, tier: int = 1) -> Subcontractor:
        """Add subcontractor to tracking."""
        sub = Subcontractor(
            id=id,
            name=name,
            trade=trade,
            contract_amount=contract_amount,
            contact_name=contact_name,
            contact_email=contact_email,
            tier=tier
        )
        self.subcontractors[id] = sub
        return sub

    def create_payment_application(self, number: int, period_end: datetime,
                                  subcontractor_id: str, amount_requested: float,
                                  retainage_rate: float = 0.10) -> PaymentApplication:
        """Create payment application record."""
        if subcontractor_id not in self.subcontractors:
            raise ValueError(f"Subcontractor {subcontractor_id} not found")

        retainage = amount_requested * retainage_rate
        amount_approved = amount_requested - retainage

        pay_app = PaymentApplication(
            number=number,
            period_end=period_end,
            subcontractor_id=subcontractor_id,
            amount_requested=amount_requested,
            amount_approved=amount_approved,
            retainage=retainage
        )

        key = f"{subcontractor_id}-{number}"
        self.pay_apps[key] = pay_app

        # Create waiver request
        self.request_waiver(subcontractor_id, number, period_end, amount_approved)

        return pay_app

    def request_waiver(self, subcontractor_id: str, pay_app_number: int,
                      through_date: datetime, amount: float,
                      waiver_type: WaiverType = WaiverType.CONDITIONAL_PROGRESS) -> LienWaiver:
        """Request lien waiver from subcontractor."""
        waiver_id = f"LW-{subcontractor_id}-{pay_app_number}"

        waiver = LienWaiver(
            id=waiver_id,
            subcontractor_id=subcontractor_id,
            waiver_type=waiver_type,
            payment_application=pay_app_number,
            through_date=through_date,
            amount=amount
        )

        self.waivers[waiver_id] = waiver
        return waiver

    def receive_waiver(self, waiver_id: str, file_path: str,
                      verified_by: str = "") -> LienWaiver:
        """Record receipt of lien waiver."""
        if waiver_id not in self.waivers:
            raise ValueError(f"Waiver {waiver_id} not found")

        waiver = self.waivers[waiver_id]
        waiver.status = WaiverStatus.RECEIVED
        waiver.received_date = datetime.now()
        waiver.file_path = file_path
        waiver.verified_by = verified_by

        # Check if all waivers for pay app complete
        self._check_pay_app_waivers(waiver.subcontractor_id, waiver.payment_application)

        return waiver

    def verify_waiver(self, waiver_id: str, verified_by: str) -> LienWaiver:
        """Verify waiver details are correct."""
        if waiver_id not in self.waivers:
            raise ValueError(f"Waiver {waiver_id} not found")

        waiver = self.waivers[waiver_id]
        waiver.status = WaiverStatus.VERIFIED
        waiver.verified_by = verified_by

        return waiver

    def reject_waiver(self, waiver_id: str, reason: str) -> LienWaiver:
        """Reject waiver (incorrect amount, wrong form, etc.)."""
        if waiver_id not in self.waivers:
            raise ValueError(f"Waiver {waiver_id} not found")

        waiver = self.waivers[waiver_id]
        waiver.status = WaiverStatus.REJECTED
        waiver.notes = f"Rejected: {reason}"

        return waiver

    def convert_to_unconditional(self, waiver_id: str, payment_date: datetime) -> LienWaiver:
        """Convert conditional waiver to unconditional after payment clears."""
        if waiver_id not in self.waivers:
            raise ValueError(f"Waiver {waiver_id} not found")

        waiver = self.waivers[waiver_id]

        # Create new unconditional waiver
        new_type = (WaiverType.UNCONDITIONAL_PROGRESS
                   if waiver.waiver_type == WaiverType.CONDITIONAL_PROGRESS
                   else WaiverType.UNCONDITIONAL_FINAL)

        return self.request_waiver(
            waiver.subcontractor_id,
            waiver.payment_application,
            waiver.through_date,
            waiver.amount,
            new_type
        )

    def _check_pay_app_waivers(self, subcontractor_id: str, pay_app_number: int):
        """Check if all waivers for pay app are received."""
        key = f"{subcontractor_id}-{pay_app_number}"

        if key not in self.pay_apps:
            return

        pay_app = self.pay_apps[key]

        # Check all related waivers
        related_waivers = [
            w for w in self.waivers.values()
            if w.subcontractor_id == subcontractor_id
            and w.payment_application == pay_app_number
        ]

        pay_app.waivers_complete = all(
            w.status in [WaiverStatus.RECEIVED, WaiverStatus.VERIFIED]
            for w in related_waivers
        )

    def calculate_exposure(self, subcontractor_id: str) -> LienExposure:
        """Calculate lien exposure for subcontractor."""
        if subcontractor_id not in self.subcontractors:
            raise ValueError(f"Subcontractor {subcontractor_id} not found")

        sub = self.subcontractors[subcontractor_id]

        # Sum payments
        total_paid = sum(
            pa.amount_approved for pa in self.pay_apps.values()
            if pa.subcontractor_id == subcontractor_id
            and pa.status == PaymentStatus.RELEASED
        )

        # Sum unconditional waivers
        unconditional = sum(
            w.amount for w in self.waivers.values()
            if w.subcontractor_id == subcontractor_id
            and w.waiver_type in [WaiverType.UNCONDITIONAL_PROGRESS, WaiverType.UNCONDITIONAL_FINAL]
            and w.status in [WaiverStatus.RECEIVED, WaiverStatus.VERIFIED]
        )

        # Sum conditional pending
        conditional = sum(
            w.amount for w in self.waivers.values()
            if w.subcontractor_id == subcontractor_id
            and w.waiver_type in [WaiverType.CONDITIONAL_PROGRESS, WaiverType.CONDITIONAL_FINAL]
            and w.status in [WaiverStatus.RECEIVED, WaiverStatus.VERIFIED]
        )

        # Exposure = Paid - Unconditional waivers
        exposure = total_paid - unconditional

        return LienExposure(
            subcontractor_id=subcontractor_id,
            subcontractor_name=sub.name,
            total_paid=total_paid,
            unconditional_waivers=unconditional,
            conditional_pending=conditional,
            exposure=exposure
        )

    def get_missing_waivers(self) -> List[Dict]:
        """Get list of missing or pending waivers."""
        missing = []

        for waiver in self.waivers.values():
            if waiver.status in [WaiverStatus.REQUESTED, WaiverStatus.MISSING]:
                sub = self.subcontractors.get(waiver.subcontractor_id)
                missing.append({
                    "waiver_id": waiver.id,
                    "subcontractor": sub.name if sub else waiver.subcontractor_id,
                    "pay_app": waiver.payment_application,
                    "amount": waiver.amount,
                    "type": waiver.waiver_type.value,
                    "requested_date": waiver.requested_date,
                    "days_outstanding": (datetime.now() - waiver.requested_date).days
                })

        return sorted(missing, key=lambda x: -x["days_outstanding"])

    def get_waiver_status_by_sub(self, subcontractor_id: str) -> Dict:
        """Get waiver status summary for subcontractor."""
        if subcontractor_id not in self.subcontractors:
            raise ValueError(f"Subcontractor {subcontractor_id} not found")

        sub = self.subcontractors[subcontractor_id]

        waivers = [w for w in self.waivers.values() if w.subcontractor_id == subcontractor_id]

        by_status = {}
        for w in waivers:
            s = w.status.value
            by_status[s] = by_status.get(s, 0) + 1

        return {
            "subcontractor": sub.name,
            "total_waivers": len(waivers),
            "by_status": by_status,
            "total_amount": sum(w.amount for w in waivers),
            "verified_amount": sum(w.amount for w in waivers if w.status == WaiverStatus.VERIFIED)
        }

    def can_release_payment(self, subcontractor_id: str, pay_app_number: int) -> Dict:
        """Check if payment can be released."""
        key = f"{subcontractor_id}-{pay_app_number}"

        if key not in self.pay_apps:
            return {"can_release": False, "reason": "Payment application not found"}

        pay_app = self.pay_apps[key]

        # Check for conditional waiver
        waivers = [
            w for w in self.waivers.values()
            if w.subcontractor_id == subcontractor_id
            and w.payment_application == pay_app_number
            and w.waiver_type == WaiverType.CONDITIONAL_PROGRESS
        ]

        if not waivers:
            return {"can_release": False, "reason": "No conditional waiver received"}

        for w in waivers:
            if w.status not in [WaiverStatus.RECEIVED, WaiverStatus.VERIFIED]:
                return {"can_release": False, "reason": f"Waiver {w.id} not verified"}

        return {"can_release": True, "reason": "All waivers verified", "amount": pay_app.amount_approved}

    def generate_report(self) -> str:
        """Generate lien waiver status report."""
        lines = [
            "# Lien Waiver Status Report",
            "",
            f"**Project:** {self.project_name}",
            f"**Date:** {datetime.now().strftime('%Y-%m-%d')}",
            "",
            "## Summary",
            "",
            f"- Total Subcontractors: {len(self.subcontractors)}",
            f"- Total Waivers Tracked: {len(self.waivers)}",
            f"- Missing Waivers: {len(self.get_missing_waivers())}",
            "",
            "## Exposure by Subcontractor",
            "",
            "| Subcontractor | Paid | Unconditional | Exposure |",
            "|---------------|------|---------------|----------|"
        ]

        total_exposure = 0
        for sub_id in self.subcontractors:
            exposure = self.calculate_exposure(sub_id)
            total_exposure += exposure.exposure
            lines.append(
                f"| {exposure.subcontractor_name} | ${exposure.total_paid:,.0f} | "
                f"${exposure.unconditional_waivers:,.0f} | ${exposure.exposure:,.0f} |"
            )

        lines.extend([
            f"| **TOTAL** | | | **${total_exposure:,.0f}** |",
            "",
            "## Missing Waivers",
            ""
        ])

        missing = self.get_missing_waivers()
        if missing:
            lines.append("| Subcontractor | Pay App | Amount | Days Outstanding |")
            lines.append("|---------------|---------|--------|------------------|")
            for m in missing[:10]:
                lines.append(
                    f"| {m['subcontractor']} | #{m['pay_app']} | "
                    f"${m['amount']:,.0f} | {m['days_outstanding']} |"
                )
        else:
            lines.append("*No missing waivers*")

        return "\n".join(lines)

Quick Start

# Initialize tracker
tracker = LienWaiverTracker("PRJ-001", "Office Tower")

# Add subcontractors
tracker.add_subcontractor(
    "SUB-001", "ABC Mechanical", "HVAC",
    contract_amount=500000,
    contact_name="John Smith",
    contact_email="john@abcmech.com"
)

tracker.add_subcontractor(
    "SUB-002", "XYZ Electric", "Electrical",
    contract_amount=350000,
    contact_name="Jane Doe",
    contact_email="jane@xyzelectric.com"
)

# Create payment applications
pa1 = tracker.create_payment_application(
    number=1,
    period_end=datetime.now(),
    subcontractor_id="SUB-001",
    amount_requested=50000
)

# Receive waiver
waiver_id = f"LW-SUB-001-1"
tracker.receive_waiver(waiver_id, "/waivers/sub001_pa1.pdf", "PM")

# Check if can release payment
result = tracker.can_release_payment("SUB-001", 1)
print(f"Can release: {result['can_release']} - {result['reason']}")

# Calculate exposure
exposure = tracker.calculate_exposure("SUB-001")
print(f"Lien exposure: ${exposure.exposure:,.2f}")

# Generate report
print(tracker.generate_report())

Requirements

pip install (no external dependencies)

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Create 4D construction simulations by linking BIM elements to project schedules. Generate time-based visualizations, sequence analysis, and construction phasing with Gantt integration.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

datadrivenconstruction/DDC_Skills_for_AI_Agents_in_Construction3682026년 10월 20일 업데이트

Analyze 5000+ IFC and Revit projects at scale for patterns, benchmarks, and insights. Big data analysis for construction.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

datadrivenconstruction/DDC_Skills_for_AI_Agents_in_Construction3682026년 10월 20일 업데이트

Check construction AI systems against the EU AI Act (Regulation 2024/1689): classify risk of estimation, scheduling, CV and agent tools, document transparency, keep human oversight. Use when deploying or auditing AI in construction.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

datadrivenconstruction/DDC_Skills_for_AI_Agents_in_Construction3682026년 10월 20일 업데이트

Orchestrate multiple AI agents for construction workflows: estimator, scheduler, document, QA and safety agents coordinated by a supervisor agent, with human checkpoints. Use when automating end-to-end project processes with agentic AI.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

datadrivenconstruction/DDC_Skills_for_AI_Agents_in_Construction3682026년 10월 20일 업데이트

Create Apache Airflow DAGs for construction data pipelines. Orchestrate ETL, validation, and reporting workflows.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

datadrivenconstruction/DDC_Skills_for_AI_Agents_in_Construction3682026년 10월 20일 업데이트

Automate as-built documentation and digital handover for construction. Compile project records, generate O&M manuals, create asset databases, and ensure complete project closeout.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

datadrivenconstruction/DDC_Skills_for_AI_Agents_in_Construction3682026년 10월 20일 업데이트

datadrivenconstruction의 스킬 모두 보기

이 스킬의 문제 신고