| 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
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
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]
}
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
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
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
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()}"
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': {}
}
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
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
from datetime import date, timedelta
from typing import List, Dict, Optional
@dataclass
class MaintenanceSchedule:
schedule_id: str
task_description: str
frequency: str
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
manufacturer: str
model_number: str
serial_number: str
part_number: str = ""
building: str = ""
floor: str = ""
room: str = ""
coordinates: Tuple[float, float, float] = (0, 0, 0)
install_date: date = None
installed_by: str = ""
cost: float = 0
warranty_start: date = None
warranty_end: date = None
warranty_provider: str = ""
warranty_terms: str = ""
specifications: Dict = field(default_factory=dict)
manuals: List[str] = field(default_factory=list)
drawings: List[str] = field(default_factory=list)
photos: List[str] = field(default_factory=list)
maintenance_schedules: List[MaintenanceSchedule] = field(default_factory=list)
service_contacts: List[Dict] = field(default_factory=list)
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', '')
)
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': [],
'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
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"""
system_docs = [
d for d in self.docs.documents.values()
if d.system == system and d.status == DocumentStatus.FINAL
]
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': []
}
manual_content['sections'].append({
'title': 'System Overview',
'content': f"Overview of {system.value} system",
'subsections': []
})
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
})
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]
})
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
})
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
})
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())
})
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
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': []
}
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()
]
})
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'}
]
})
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'}
]
})
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