Quality Control Workflow
Overview
Automate construction quality control workflows from inspection planning through defect resolution. Track quality metrics, manage non-conformance reports (NCRs), and ensure specification compliance.
"Structured QC workflows reduce rework by 40% and improve first-time quality" — DDC Community
QC Workflow Stages
┌─────────────────────────────────────────────────────────────────┐
│ QC WORKFLOW PIPELINE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Plan → Inspect → Document → Resolve │
│ ──── ─────── ──────── ─────── │
│ 📋 ITP 🔍 Check 📝 NCR 🔧 Fix │
│ 📅 Schedule 📸 Photo 📊 Log ✅ Verify │
│ 👥 Assign 📏 Measure 📧 Notify 📈 Close │
│ │
└─────────────────────────────────────────────────────────────────┘
Technical Implementation
from dataclasses import dataclass, field
from typing import List, Dict, Optional
from enum import Enum
from datetime import datetime, timedelta
import json
class QCStatus(Enum):
PENDING = "pending"
IN_PROGRESS = "in_progress"
PASSED = "passed"
FAILED = "failed"
ON_HOLD = "on_hold"
class DefectSeverity(Enum):
CRITICAL = "critical" # Stop work, structural/safety issue
MAJOR = "major" # Must fix before cover-up
MINOR = "minor" # Fix before completion
COSMETIC = "cosmetic" # Punch list item
class NCRStatus(Enum):
DRAFT = "draft"
ISSUED = "issued"
ACCEPTED = "accepted"
DISPUTED = "disputed"
IN_REMEDIATION = "in_remediation"
VERIFIED = "verified"
CLOSED = "closed"
@dataclass
class InspectionPoint:
id: str
name: str
spec_reference: str
acceptance_criteria: str
inspection_method: str
hold_point: bool = False # Requires sign-off before proceeding
witness_point: bool = False # Client/engineer may witness
@dataclass
class QCInspection:
id: str
inspection_point: InspectionPoint
location: str
scheduled_date: datetime
inspector: str
status: QCStatus = QCStatus.PENDING
actual_date: Optional[datetime] = None
result: str = ""
measurements: Dict[str, float] = field(default_factory=dict)
photos: List[str] = field(default_factory=list)
notes: str = ""
sign_off_by: str = ""
@dataclass
class Defect:
id: str
inspection_id: str
description: str
location: str
severity: DefectSeverity
spec_reference: str
photos: List[str] = field(default_factory=list)
assigned_to: str = ""
due_date: Optional[datetime] = None
status: str = "open"
root_cause: str = ""
corrective_action: str = ""
verified_by: str = ""
closed_date: Optional[datetime] = None
@dataclass
class NonConformanceReport:
id: str
defect_ids: List[str]
title: str
description: str
contractor: str
spec_sections: List[str]
status: NCRStatus = NCRStatus.DRAFT
issued_date: Optional[datetime] = None
response_due: Optional[datetime] = None
contractor_response: str = ""
remediation_plan: str = ""
cost_impact: float = 0.0
schedule_impact_days: int = 0
class QualityControlManager:
"""Manage construction quality control workflow."""
def __init__(self, project_id: str, project_name: str):
self.project_id = project_id
self.project_name = project_name
self.inspection_points: Dict[str, InspectionPoint] = {}
self.inspections: Dict[str, QCInspection] = {}
self.defects: Dict[str, Defect] = {}
self.ncrs: Dict[str, NonConformanceReport] = {}
def create_itp(self, work_package: str, inspection_points: List[Dict]) -> List[InspectionPoint]:
"""Create Inspection and Test Plan (ITP)."""
created = []
for idx, point in enumerate(inspection_points):
ip = InspectionPoint(
id=f"{work_package}-{idx+1:03d}",
name=point['name'],
spec_reference=point.get('spec_ref', ''),
acceptance_criteria=point.get('criteria', ''),
inspection_method=point.get('method', 'Visual'),
hold_point=point.get('hold_point', False),
witness_point=point.get('witness_point', False)
)
self.inspection_points[ip.id] = ip
created.append(ip)
return created
def schedule_inspection(self, inspection_point_id: str, location: str,
scheduled_date: datetime, inspector: str) -> QCInspection:
"""Schedule a QC inspection."""
if inspection_point_id not in self.inspection_points:
raise ValueError(f"Inspection point {inspection_point_id} not found")
inspection_id = f"QCI-{datetime.now().strftime('%Y%m%d%H%M%S')}"
inspection = QCInspection(
id=inspection_id,
inspection_point=self.inspection_points[inspection_point_id],
location=location,
scheduled_date=scheduled_date,
inspector=inspector
)
self.inspections[inspection_id] = inspection
return inspection
def conduct_inspection(self, inspection_id: str, result: str,
measurements: Dict[str, float] = None,
photos: List[str] = None,
notes: str = "") -> QCInspection:
"""Record inspection results."""
if inspection_id not in self.inspections:
raise ValueError(f"Inspection {inspection_id} not found")
inspection = self.inspections[inspection_id]
inspection.actual_date = datetime.now()
inspection.result = result
inspection.measurements = measurements or {}
inspection.photos = photos or []
inspection.notes = notes
inspection.status = QCStatus.PASSED if result == "pass" else QCStatus.FAILED
return inspection
def record_defect(self, inspection_id: str, description: str,
location: str, severity: DefectSeverity,
spec_reference: str, photos: List[str] = None,
assigned_to: str = "") -> Defect:
"""Record a defect from inspection."""
defect_id = f"DEF-{datetime.now().strftime('%Y%m%d%H%M%S')}"
# Set due date based on severity
due_days = {
DefectSeverity.CRITICAL: 1,
DefectSeverity.MAJOR: 3,
DefectSeverity.MINOR: 7,
DefectSeverity.COSMETIC: 14
}
defect = Defect(
id=defect_id,
inspection_id=inspection_id,
description=description,
location=location,
severity=severity,
spec_reference=spec_reference,
photos=photos or [],
assigned_to=assigned_to,
due_date=datetime.now() + timedelta(days=due_days[severity])
)
self.defects[defect_id] = defect
return defect
def create_ncr(self, defect_ids: List[str], title: str,
contractor: str, description: str = "") -> NonConformanceReport:
"""Create Non-Conformance Report from defects."""
ncr_id = f"NCR-{datetime.now().strftime('%Y%m%d%H%M%S')}"
# Collect spec references from defects
spec_sections = []
for def_id in defect_ids:
if def_id in self.defects:
spec_sections.append(self.defects[def_id].spec_reference)
ncr = NonConformanceReport(
id=ncr_id,
defect_ids=defect_ids,
title=title,
description=description or self._generate_ncr_description(defect_ids),
contractor=contractor,
spec_sections=list