--- name: as-built-documentation description: "Automate as-built documentation and digital handover for construction. Compile project records, generate O&M manuals, create asset databases, and ensure complete project closeout." --- # As-Built Documentation ## Overview This skill implements automated as-built documentation and digital handover for construction projects. Compile accurate records, generate operation manuals, and ensure complete documentation for facility management. **Capabilities:** - As-built drawing management - O&M manual generation - Asset data compilation - Warranty tracking - Document organization - BIM-to-FM handover ## Quick Start ```python from dataclasses import dataclass, field from datetime import date, datetime from typing import List, Dict, Optional from enum import Enum class DocumentType(Enum): DRAWING = "drawing" SPECIFICATION = "specification" SUBMITTAL = "submittal" WARRANTY = "warranty" CERTIFICATE = "certificate" MANUAL = "manual" TEST_REPORT = "test_report" COMMISSIONING = "commissioning" PHOTO = "photo" class DocumentStatus(Enum): DRAFT = "draft" REVIEWED = "reviewed" APPROVED = "approved" AS_BUILT = "as_built" FINAL = "final" @dataclass class HandoverDocument: doc_id: str doc_type: DocumentType title: str file_path: str version: str status: DocumentStatus system: str # Building system (HVAC, Electrical, etc.) uploaded_date: date approved_by: str = "" @dataclass class AssetRecord: asset_id: str asset_name: str asset_type: str manufacturer: str model: str serial_number: str location: str install_date: date warranty_end: date documents: List[str] = field(default_factory=list) def check_handover_completeness(documents: List[HandoverDocument], required_types: List[DocumentType]) -> Dict: """Check if all required documents are present""" present_types = {doc.doc_type for doc in documents if doc.status == DocumentStatus.FINAL} missing = set(required_types) - present_types return { 'complete': len(missing) == 0, 'total_required': len(required_types), 'total_present': len(present_types), 'missing_types': [t.value for t in missing] } # Example documents = [ HandoverDocument("DOC-001", DocumentType.DRAWING, "Floor Plans As-Built", "/docs/floorplans.pdf", "3.0", DocumentStatus.FINAL, "Architecture", date.today()), HandoverDocument("DOC-002", DocumentType.MANUAL, "HVAC O&M Manual", "/docs/hvac_om.pdf", "1.0", DocumentStatus.FINAL, "HVAC", date.today()), ] required = [DocumentType.DRAWING, DocumentType.MANUAL, DocumentType.WARRANTY] status = check_handover_completeness(documents, required) print(f"Complete: {status['complete']}, Missing: {status['missing_types']}") ``` ## Comprehensive Handover System ### Document Management ```python from dataclasses import dataclass, field from datetime import date, datetime, timedelta from typing import List, Dict, Optional, Tuple from enum import Enum import uuid import os class BuildingSystem(Enum): ARCHITECTURAL = "architectural" STRUCTURAL = "structural" MECHANICAL = "mechanical" ELECTRICAL = "electrical" PLUMBING = "plumbing" FIRE_PROTECTION = "fire_protection" CONTROLS = "controls" ELEVATOR = "elevator" CIVIL = "civil" LANDSCAPE = "landscape" @dataclass class DocumentRequirement: requirement_id: str doc_type: DocumentType system: BuildingSystem description: str is_mandatory: bool = True quantity: int = 1 # How many documents of this type needed format_requirements: str = "PDF" @dataclass class ProjectDocument: doc_id: str requirement_id: Optional[str] doc_type: DocumentType system: BuildingSystem title: str description: str file_path: str file_size_mb: float format: str version: str revision_date: date author: str reviewer: str status: DocumentStatus metadata: Dict = field(default_factory=dict) related_assets: List[str] = field(default_factory=list) supersedes: Optional[str] = None # Previous version doc_id class DocumentManager: """Manage project handover documents""" STANDARD_REQUIREMENTS = { BuildingSystem.MECHANICAL: [ DocumentRequirement("REQ-M01", DocumentType.DRAWING, BuildingSystem.MECHANICAL, "HVAC As-Built Drawings"), DocumentRequirement("REQ-M02", DocumentType.MANUAL, BuildingSystem.MECHANICAL, "HVAC Operation & Maintenance Manual"), DocumentRequirement("REQ-M03", DocumentType.WARRANTY, BuildingSystem.MECHANICAL, "HVAC Equipment Warranties"), DocumentRequirement("REQ-M04", DocumentType.TEST_REPORT, BuildingSystem.MECHANICAL, "TAB Report - Testing, Adjusting, Balancing"), DocumentRequirement("REQ-M05", DocumentType.COMMISSIONING, BuildingSystem.MECHANICAL, "Commissioning Report"), ], BuildingSystem.ELECTRICAL: [ DocumentRequirement("REQ-E01", DocumentType.DRAWING, BuildingSystem.ELECTRICAL, "Electrical As-Built Drawings"), DocumentRequirement("REQ-E02", DocumentType.MANUAL, BuildingSystem.ELECTRICAL, "Electrical O&M Manual"), DocumentRequirement("REQ-E03", DocumentType.WARRANTY, BuildingSystem.ELECTRICAL, "Electrical Equipment Warranties"), DocumentRequirement("REQ-E04", DocumentType.TEST_REPORT, BuildingSystem.ELECTRICAL, "Electrical Test Reports"), DocumentRequirement("REQ-E05", DocumentType.CERTIFICATE, BuildingSystem.ELECTRICAL, "Electrical Inspection Certificate"), ], BuildingSystem.PLUMBING: [ DocumentRequirement("REQ-P01", DocumentType.DRAWING, BuildingSystem.PLUMBING, "Plumbing As-Built Drawings"), DocumentRequirement("REQ-P02", DocumentType.MANUAL, BuildingSystem.PLUMBING, "Plumbing O&M Manual"), DocumentRequirement("REQ-P03", DocumentType.TEST_REPORT, BuildingSystem.PLUMBING, "Pressure Test Reports"), ], BuildingSystem.FIRE_PROTECTION: [ DocumentRequirement("REQ-F01", DocumentType.DRAWING, BuildingSystem.FIRE_PROTECTION, "Fire Protection As-Built Drawings"), DocumentRequirement("REQ-F02", DocumentType.MANUAL, BuildingSystem.FIRE_PROTECTION, "Fire Systems O&M Manual"), DocumentRequirement("REQ-F03", DocumentType.CERTIFICATE, BuildingSystem.FIRE_PROTECTION, "Fire Marshal Approval"), DocumentRequirement("REQ-F04", DocumentType.TEST_REPORT, BuildingSystem.FIRE_PROTECTION, "Fire Alarm Acceptance Test"), ], BuildingSystem.ARCHITECTURAL: [ DocumentRequirement("REQ-A01", DocumentType.DRAWING, BuildingSystem.ARCHITECTURAL, "Architectural As-Built Drawings"), DocumentRequirement("REQ-A02", DocumentType.SPECIFICATION, BuildingSystem.ARCHITECTURAL, "Finish Schedule"), DocumentRequirement("REQ-A03", DocumentType.WARRANTY, BuildingSystem.ARCHITECTURAL, "Roofing Warranty"), ] } def __init__(self, project_id: str, project_name: str): self.project_id = project_id self.project_name = project_name self.requirements: Dict[str, DocumentRequirement] = {} self.documents: Dict[str, ProjectDocument] = {} self._load_standard_requirements() def _load_standard_requirements(self): """Load standard document requirements""" for system, reqs in self.STANDARD_REQUIREMENTS.items(): for req in reqs: self.requirements[req.requirement_id] = req def add_requirement(self, requirement: DocumentRequirement): """Add custom requirement""" self.requirements[requirement.requirement_id] = requirement def upload_document(self, doc_type: DocumentType, system: BuildingSystem, title: str, file_path: str, author: str, requirement_id: str = None, related_assets: List[str] = None) -> ProjectDocument: """Upload new document""" doc_id = f"DOC-{uuid.uuid4().hex[:8].upper()}" # Get file info file_size = os.path.getsize(file_path) / (1024 * 1024) if os.path.exists(file_path) else 0 file_format = os.path.splitext(file_path)[1].upper().replace('.', '') doc = ProjectDocument( doc_id=doc_id, requirement_id=requirement_id, doc_type=doc_type, system=system, title=title, description="", file_path=file_path, file_size_mb=file_size, format=file_format, version="1.0", revision_date=date.today(), author=author, reviewer="", status=DocumentStatus.DRAFT, related_assets=related_assets or [] ) self.documents[doc_id] = doc return doc def approve_document(self, doc_id: str, reviewer: str) -> bool: """Approve document""" doc = self.documents.get(doc_id) if doc: doc.status = DocumentStatus.APPROVED doc.reviewer = reviewer return True return False def finalize_document(self, doc_id: str) -> bool: """Finalize document for handover""" doc = self.documents.get(doc_id) if doc and doc.status == DocumentStatus.APPROVED: doc.status = DocumentStatus.FINAL return True return False def get_status_summary(self) -> Dict: """Get document status summary""" summary = { 'total_requirements': len(self.requirements), 'total_documents': len(self.documents), 'by_status': {}, 'by_system': {}, 'completion': {} } # Count by status for doc in self.documents.values(): status = doc.status.value summary['by_status'][status] = summary['by_status'].get(status, 0) + 1 system = doc.system.value if system not in summary['by_system']: summary['by_system'][system] = {'uploaded': 0, 'final': 0} summary['by_system'][system]['uploaded'] += 1 if doc.status == DocumentStatus.FINAL: summary['by_system'][system]['final'] += 1 # Check completion against requirements for req_id, req in self.requirements.items(): matching_docs = [ d for d in self.documents.values() if d.requirement_id == req_id and d.status == DocumentStatus.FINAL ] summary['completion'][req_id] = { 'description': req.description, 'system': req.system.value, 'required': req.quantity, 'submitted': len(matching_docs), 'complete': len(matching_docs) >= req.quantity } return summary def get_missing_documents(self) -> List[DocumentRequirement]: """Get list of missing required documents""" missing = [] for req_id, req in self.requirements.items(): if not req.is_mandatory: continue matching_docs = [ d for d in self.documents.values() if d.requirement_id == req_id and d.status == DocumentStatus.FINAL ] if len(matching_docs) < req.quantity: missing.append(req) return missing ``` ### Asset Registry ```python from datetime import date, timedelta from typing import List, Dict, Optional @dataclass class MaintenanceSchedule: schedule_id: str task_description: str frequency: str # daily, weekly, monthly, quarterly, annual next_due: date responsible_party: str estimated_duration_hours: float @dataclass class AssetDetails: asset_id: str asset_tag: str asset_name: str asset_type: str category: str # equipment, fixture, system # Identification manufacturer: str model_number: str serial_number: str part_number: str = "" # Location building: str = "" floor: str = "" room: str = "" coordinates: Tuple[float, float, float] = (0, 0, 0) # Installation install_date: date = None installed_by: str = "" cost: float = 0 # Warranty warranty_start: date = None warranty_end: date = None warranty_provider: str = "" warranty_terms: str = "" # Specifications specifications: Dict = field(default_factory=dict) # Documents manuals: List[str] = field(default_factory=list) drawings: List[str] = field(default_factory=list) photos: List[str] = field(default_factory=list) # Maintenance maintenance_schedules: List[MaintenanceSchedule] = field(default_factory=list) service_contacts: List[Dict] = field(default_factory=list) # BIM reference ifc_guid: str = "" revit_element_id: str = "" class AssetRegistry: """Manage building assets for handover""" def __init__(self, project_id: str): self.project_id = project_id self.assets: Dict[str, AssetDetails] = {} def register_asset(self, asset: AssetDetails): """Register new asset""" self.assets[asset.asset_id] = asset def import_from_bim(self, bim_data: List[Dict]): """Import assets from BIM model""" for item in bim_data: asset = AssetDetails( asset_id=f"AST-{uuid.uuid4().hex[:8].upper()}", asset_tag=item.get('tag', ''), asset_name=item.get('name', ''), asset_type=item.get('type', ''), category=item.get('category', 'equipment'), manufacturer=item.get('manufacturer', ''), model_number=item.get('model', ''), serial_number=item.get('serial', ''), building=item.get('building', ''), floor=item.get('floor', ''), room=item.get('room', ''), ifc_guid=item.get('ifc_guid', ''), revit_element_id=item.get('revit_id', '') ) # Add specifications for key, value in item.get('parameters', {}).items(): asset.specifications[key] = value self.assets[asset.asset_id] = asset def add_warranty(self, asset_id: str, start_date: date, duration_years: int, provider: str, terms: str = ""): """Add warranty information""" asset = self.assets.get(asset_id) if asset: asset.warranty_start = start_date asset.warranty_end = start_date + timedelta(days=365 * duration_years) asset.warranty_provider = provider asset.warranty_terms = terms def add_maintenance_schedule(self, asset_id: str, schedule: MaintenanceSchedule): """Add maintenance schedule""" asset = self.assets.get(asset_id) if asset: asset.maintenance_schedules.append(schedule) def get_warranty_report(self) -> Dict: """Get warranty status report""" today = date.today() report = { 'total_assets': len(self.assets), 'with_warranty': 0, 'active_warranties': 0, 'expiring_soon': [], # Within 90 days 'expired': [] } for asset in self.assets.values(): if asset.warranty_end: report['with_warranty'] += 1 if asset.warranty_end >= today: report['active_warranties'] += 1 days_remaining = (asset.warranty_end - today).days if days_remaining <= 90: report['expiring_soon'].append({ 'asset_id': asset.asset_id, 'asset_name': asset.asset_name, 'warranty_end': asset.warranty_end.isoformat(), 'days_remaining': days_remaining }) else: report['expired'].append({ 'asset_id': asset.asset_id, 'asset_name': asset.asset_name, 'warranty_end': asset.warranty_end.isoformat() }) return report def export_to_cmms(self) -> List[Dict]: """Export assets for CMMS import""" export_data = [] for asset in self.assets.values(): export_data.append({ 'asset_id': asset.asset_id, 'asset_tag': asset.asset_tag, 'name': asset.asset_name, 'type': asset.asset_type, 'category': asset.category, 'manufacturer': asset.manufacturer, 'model': asset.model_number, 'serial': asset.serial_number, 'location': f"{asset.building}/{asset.floor}/{asset.room}", 'install_date': asset.install_date.isoformat() if asset.install_date else '', 'warranty_end': asset.warranty_end.isoformat() if asset.warranty_end else '', 'specifications': asset.specifications }) return export_data ``` ### O&M Manual Generator ```python class OMManualGenerator: """Generate O&M manuals from project data""" def __init__(self, doc_manager: DocumentManager, asset_registry: AssetRegistry): self.docs = doc_manager self.assets = asset_registry def generate_system_manual(self, system: BuildingSystem, output_path: str) -> Dict: """Generate O&M manual for building system""" # Collect system documents system_docs = [ d for d in self.docs.documents.values() if d.system == system and d.status == DocumentStatus.FINAL ] # Collect system assets system_assets = [ a for a in self.assets.assets.values() if a.asset_type.lower() in system.value.lower() or system.value.lower() in a.category.lower() ] manual_content = { 'system': system.value, 'generated_date': date.today().isoformat(), 'sections': [] } # Section 1: System Overview manual_content['sections'].append({ 'title': 'System Overview', 'content': f"Overview of {system.value} system", 'subsections': [] }) # Section 2: Equipment List equipment_list = [] for asset in system_assets: equipment_list.append({ 'tag': asset.asset_tag, 'name': asset.asset_name, 'manufacturer': asset.manufacturer, 'model': asset.model_number, 'location': f"{asset.building}/{asset.floor}/{asset.room}" }) manual_content['sections'].append({ 'title': 'Equipment Schedule', 'equipment': equipment_list }) # Section 3: Operation Procedures manual_content['sections'].append({ 'title': 'Operation Procedures', 'content': 'Standard operating procedures', 'reference_docs': [d.doc_id for d in system_docs if d.doc_type == DocumentType.MANUAL] }) # Section 4: Maintenance Requirements maintenance_tasks = [] for asset in system_assets: for schedule in asset.maintenance_schedules: maintenance_tasks.append({ 'asset': asset.asset_name, 'task': schedule.task_description, 'frequency': schedule.frequency, 'duration': schedule.estimated_duration_hours }) manual_content['sections'].append({ 'title': 'Preventive Maintenance', 'tasks': maintenance_tasks }) # Section 5: Warranty Information warranties = [] for asset in system_assets: if asset.warranty_end: warranties.append({ 'asset': asset.asset_name, 'provider': asset.warranty_provider, 'expires': asset.warranty_end.isoformat(), 'terms': asset.warranty_terms }) manual_content['sections'].append({ 'title': 'Warranty Information', 'warranties': warranties }) # Section 6: Service Contacts contacts = [] for asset in system_assets: contacts.extend(asset.service_contacts) manual_content['sections'].append({ 'title': 'Service Contacts', 'contacts': list({c['name']: c for c in contacts}.values()) }) # Section 7: Reference Documents manual_content['sections'].append({ 'title': 'Reference Documents', 'documents': [ {'id': d.doc_id, 'title': d.title, 'type': d.doc_type.value} for d in system_docs ] }) return manual_content def generate_building_manual(self, output_path: str) -> Dict: """Generate complete building O&M manual""" building_manual = { 'project': self.docs.project_name, 'generated': date.today().isoformat(), 'systems': {} } for system in BuildingSystem: building_manual['systems'][system.value] = self.generate_system_manual( system, output_path ) return building_manual ``` ### Handover Checklist ```python def generate_handover_checklist(doc_manager: DocumentManager, asset_registry: AssetRegistry) -> Dict: """Generate comprehensive handover checklist""" checklist = { 'generated': date.today().isoformat(), 'project': doc_manager.project_name, 'overall_status': 'incomplete', 'categories': [] } # Documents category doc_status = doc_manager.get_status_summary() missing_docs = doc_manager.get_missing_documents() checklist['categories'].append({ 'name': 'Documents', 'complete': len(missing_docs) == 0, 'items': [ { 'item': req.description, 'system': req.system.value, 'status': 'complete' if req.requirement_id not in [m.requirement_id for m in missing_docs] else 'missing' } for req in doc_manager.requirements.values() ] }) # Assets category warranty_report = asset_registry.get_warranty_report() checklist['categories'].append({ 'name': 'Asset Registration', 'complete': warranty_report['total_assets'] > 0, 'items': [ {'item': 'All major equipment registered', 'status': 'complete' if warranty_report['total_assets'] > 0 else 'incomplete'}, {'item': 'Warranty information entered', 'status': 'complete' if warranty_report['with_warranty'] > 0 else 'incomplete'}, {'item': 'Maintenance schedules defined', 'status': 'complete' if any(a.maintenance_schedules for a in asset_registry.assets.values()) else 'incomplete'} ] }) # Training category checklist['categories'].append({ 'name': 'Training', 'complete': False, 'items': [ {'item': 'Operations staff training completed', 'status': 'pending'}, {'item': 'Maintenance staff training completed', 'status': 'pending'}, {'item': 'Safety systems training completed', 'status': 'pending'} ] }) # Determine overall status all_complete = all(cat['complete'] for cat in checklist['categories']) checklist['overall_status'] = 'complete' if all_complete else 'incomplete' return checklist ``` ## Quick Reference | Document Type | Typical Source | When Required | |---------------|---------------|---------------| | As-Built Drawings | Contractor | Substantial completion | | O&M Manuals | Manufacturer/Contractor | Before training | | Warranties | Manufacturers | At installation | | Test Reports | Testing agency | After testing | | Certificates | Authorities | Final inspection | | Training Records | Contractor | Before handover | ## Resources - **COBie Standard**: Construction Operations Building Information Exchange - **ASHRAE Guideline 0**: Commissioning process - **IFMA**: Facility Management resources - **DDC Website**: https://datadrivenconstruction.io ## Next Steps - See `bim-validation-pipeline` for BIM handover - See `document-classification-nlp` for document processing - See `digital-twin-sync` for FM integration