Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Generate comprehensive BIM model validation reports. Check data quality, completeness, and compliance with standards.
.claude/skills/datadrivenconstruction-bim-validation-report/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 71% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 62% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 51% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 89% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 448% | 0% |
BIM models often have quality issues:
Automated BIM validation system that checks models against configurable rules and generates detailed compliance reports.
pythonimport pandas as pd from datetime import datetime from typing import Dict, Any, List, Optional, Callable from dataclasses import dataclass, field from enum import Enum class ValidationSeverity(Enum): """Validation issue severity.""" ERROR = "error" WARNING = "warning" INFO = "info" class ValidationStatus(Enum): """Overall validation status.""" PASSED = "passed" PASSED_WITH_WARNINGS = "passed_with_warnings" FAILED = "failed" class RuleCategory(Enum): """Validation rule categories.""" REQUIRED_PROPERTIES = "required_properties" DATA_FORMAT = "data_format" NAMING_CONVENTION = "naming_convention" GEOMETRIC = "geometric" CLASSIFICATION = "classification" RELATIONSHIPS = "relationships" @dataclass class ValidationRule: """Single validation rule.""" rule_id: str name: str category: RuleCategory description: str severity: ValidationSeverity check_function: Callable applicable_categories: List[str] = field(default_factory=list) enabled: bool = True @dataclass class ValidationIssue: """Single validation issue.""" issue_id: str rule_id: str rule_name: str element_id: str element_name: str element_category: str severity: ValidationSeverity message: str details: Dict[str, Any] = field(default_factory=dict) def to_dict(self) -> Dict[str, Any]: return { 'issue_id': self.issue_id, 'rule_id': self.rule_id, 'rule_name': self.rule_name, 'element_id': self.element_id, 'element_name': self.element_name, 'element_category': self.element_category, 'severity': self.severity.value, 'message': self.message } @dataclass class ValidationReport: """Complete validation report.""" project_name: str model_name: str validated_at: datetime status: ValidationStatus total_elements: int elements_with_issues: int issues: List[ValidationIssue] rules_checked: int summary_by_severity: Dict[str, int] summary_by_category: Dict[str, int] class BIMValidationEngine: """BIM model validation engine.""" def __init__(self, project_name: str, model_name: str): self.project_name = project_name self.model_name = model_name self.rules: List[ValidationRule] = [] self.issues: List[ValidationIssue] = [] self._issue_counter = 0 # Load default rules self._load_default_rules() def _load_default_rules(self): """Load standard validation rules.""" # Required properties rules self.add_rule(ValidationRule( rule_id="REQ-001", name="Element Name Required", category=RuleCategory.REQUIRED_PROPERTIES, description="All elements must have a name", severity=ValidationSeverity.ERROR, check_function=lambda e: bool(e.get('name')) )) self.add_rule(ValidationRule( rule_id="REQ-002", name="Level Assignment Required", category=RuleCategory.REQUIRED_PROPERTIES, description="Elements must be assigned to a level", severity=ValidationSeverity.WARNING, check_function=lambda e: bool(e.get('level')), applicable_categories=["Walls", "Floors", "Doors", "Windows"] )) self.add_rule(ValidationRule( rule_id="REQ-003", name="Material Required", category=RuleCategory.REQUIRED_PROPERTIES, description="Structural elements must have material defined", severity=ValidationSeverity.ERROR, check_function=lambda e: bool(e.get('material')), applicable_categories=["Structural Columns", "Structural Framing", "Floors"] )) # Naming convention rules self.add_rule(ValidationRule( rule_id="NAM-001", name="No Special Characters", category=RuleCategory.NAMING_CONVENTION, description="Names should not contain special characters", severity=ValidationSeverity.WARNING, check_function=self._check_no_special_chars )) self.add_rule(ValidationRule( rule_id="NAM-002", name="Name Length Check", category=RuleCategory.NAMING_CONVENTION, description="Names should be between 3 and 100 characters", severity=ValidationSeverity.INFO, check_function=lambda e: 3 <= len(e.get('name', '')) <= 100 )) # Classification rules self.add_rule(ValidationRule( rule_id="CLS-001", name="Classification Code Present", category=RuleCategory.CLASSIFICATION, description="Elements should have classification code", severity=ValidationSeverity.WARNING, check_function=lambda e: bool(e.get('classification_code') or e.get('uniformat')) )) # Geometric rules self.add_rule(ValidationRule( rule_id="GEO-001", name="Non-Zero Volume", category=RuleCategory.GEOMETRIC, description="3D elements must have non-zero volume", severity=ValidationSeverity.ERROR, check_function=lambda e: float(e.get('volume', 0)) > 0, applicable_categories=["Walls", "Floors", "Structural Columns", "Structural Framing"] )) self.add_rule(ValidationRule( rule_id="GEO-002", name="Valid Bounding Box", category=RuleCategory.GEOMETRIC, description="Elements must have valid bounding box", severity=ValidationSeverity.ERROR, check_function=self._check_valid_bbox )) def _check_no_special_chars(self, element: Dict[str, Any]) -> bool: """Check name for special characters.""" import re name = element.get('name', '') return bool(re.match(r'^[\w\s\-\.]+$', name)) def _check_valid_bbox(self, element: Dict[str, Any]) -> bool: """Check for valid bounding box.""" try: min_x = float(element.get('min_x', 0)) max_x = float(element.get('max_x', 0)) min_y = float(element.get('min_y', 0)) max_y = float(element.get('max_y', 0)) min_z = float(element.get('min_z', 0)) max_z = float(element.get('max_z', 0)) return max_x > min_x and max_y > min_y and max_z > min_z except (ValueError, TypeError): return False def add_rule(self, rule: ValidationRule): """Add validation rule.""" self.rules.append(rule) def add_custom_rule(self, rule_id: str, name: str, category: RuleCategory, check_function: Callable, severity: ValidationSeverity = ValidationSeverity.WARNING, description: str = "", categories: List[str] = None): """Add custom validation rule.""" rule = ValidationRule( rule_id=rule_id, name=name, category=category, description=description, severity=severity, check_function=check_function, applicable_categories=categories or [] ) self.add_rule(rule) def validate_element(self, element: Dict[str, Any]) -> List[ValidationIssue]: """Validate single element against all rules.""" issues = [] element_category = element.get('category', '') for rule in self.rules: if not rule.enabled: continue # Check if rule applies to this category if rule.applicable_categories and element_category not in rule.applicable_categories: continue try: passed = rule.check_function(element) if not passed: self._issue_counter += 1 issue = ValidationIssue( issue_id=f"ISS-{self._issue_counter:05d}", rule_id=rule.rule_id, rule_name=rule.name, element_id=str(element.get('element_id', '')), element_name=str(element.get('name', '')), element_category=element_category, severity=rule.severity, message=rule.description ) issues.append(issue) except Exception as e: # Rule check failed self._issue_counter += 1 issue = ValidationIssue( issue_id=f"ISS-{self._issue_counter:05d}", rule_id=rule.rule_id, rule_name=rule.name, element_id=str(element.get('element_id', '')), element_name=str(element.get('name', '')), element_category=element_category, severity=ValidationSeverity.ERROR, message=f"Rule check error: {str(e)}" ) issues.append(issue) return issues def validate_model(self, elements_df: pd.DataFrame) -> ValidationReport: """Validate entire BIM model.""" self.issues = [] elements_with_issues = set() for _, row in elements_df.iterrows(): element = row.to_dict() element_issues = self.validate_element(element) if element_issues: elements_with_issues.add(element.get('element_id')) self.issues.extend(element_issues) # Calculate summaries summary_by_severity = { 'error': sum(1 for i in self.issues if i.severity == ValidationSeverity.ERROR), 'warning': sum(1 for i in self.issues if i.severity == ValidationSeverity.WARNING), 'info': sum(1 for i in self.issues if i.severity == ValidationSeverity.INFO) } summary_by_category = {} for issue in self.issues: cat = issue.element_category summary_by_category[cat] = summary_by_category.get(cat, 0) + 1 # Determine overall status if summary_by_severity['error'] > 0: status = ValidationStatus.FAILED elif summary_by_severity['warning'] > 0: status = ValidationStatus.PASSED_WITH_WARNINGS else: status = ValidationStatus.PASSED return ValidationReport( project_name=self.project_name, model_name=self.model_name, validated_at=datetime.now(), status=status, total_elements=len(elements_df), elements_with_issues=len(elements_with_issues), issues=self.issues, rules_checked=len([r for r in self.rules if r.enabled]), summary_by_severity=summary_by_severity, summary_by_category=summary_by_category ) def export_report(self, report: ValidationReport, output_path: str): """Export validation report to Excel.""" with pd.ExcelWriter(output_path, engine='openpyxl') as writer: # Summary sheet summary_data = { 'Metric': ['Project', 'Model', 'Validated At', 'Status', 'Total Elements', 'Elements with Issues', 'Rules Checked', 'Errors', 'Warnings', 'Info'], 'Value': [report.project_name, report.model_name, report.validated_at.isoformat(), report.status.value, report.total_elements, report.elements_with_issues, report.rules_checked, report.summary_by_severity['error'], report.summary_by_severity['warning'], report.summary_by_severity['info']] } pd.DataFrame(summary_data).to_excel(writer, sheet_name='Summary', index=False) # Issues sheet issues_df = pd.DataFrame([i.to_dict() for i in report.issues]) if not issues_df.empty: issues_df.to_excel(writer, sheet_name='Issues', index=False) # By Category sheet cat_df = pd.DataFrame([ {'Category': k, 'Issue Count': v} for k, v in report.summary_by_category.items() ]) if not cat_df.empty: cat_df.to_excel(writer, sheet_name='By Category', index=False) return output_path def generate_validation_report(elements_df: pd.DataFrame, project_name: str, model_name: str, output_path: str = None) -> ValidationReport: """Quick function to generate validation report.""" engine = BIMValidationEngine(project_name, model_name) report = engine.validate_model(elements_df) if output_path: engine.export_report(report, output_path) return report
python# Load BIM elements elements = pd.read_excel("bim_elements.xlsx") # Run validation report = generate_validation_report( elements, project_name="Office Tower", model_name="Architectural Model v3.2", output_path="validation_report.xlsx" ) print(f"Status: {report.status.value}") print(f"Errors: {report.summary_by_severity['error']}") print(f"Warnings: {report.summary_by_severity['warning']}")
pythonengine = BIMValidationEngine("Project", "Model") # Add custom rule engine.add_custom_rule( rule_id="CUSTOM-001", name="Fire Rating Required", category=RuleCategory.REQUIRED_PROPERTIES, check_function=lambda e: bool(e.get('fire_rating')), severity=ValidationSeverity.ERROR, categories=["Walls", "Doors"] )
python# Get only errors errors = [i for i in report.issues if i.severity == ValidationSeverity.ERROR] # Get issues for specific category wall_issues = [i for i in report.issues if i.element_category == "Walls"]
pythonreport = engine.validate_model(elements) if report.status == ValidationStatus.FAILED: send_notification("BIM validation failed", report.summary_by_severity)
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 17,290 | 10,029 | -42% | 1 | 1 | 0% | 3,347 | 5,734 | +71% | 0 | 0 | — |
case-02 | fail→pass | 17,712 | 9,920 | -44% | 1 | 1 | 0% | 3,611 | 5,833 | +62% | 0 | 0 | — |
case-03 | fail→pass | 20,450 | 11,520 | -44% | 1 | 1 | 0% | 4,066 | 6,156 | +51% | 0 | 0 | — |
case-04 | pass→pass | 16,421 | 16,580 | +1% | 1 | 1 | 0% | 3,113 | 6,893 | +121% | 0 | 0 | — |
case-05 | pass→pass | 19,896 | 24,612 | +24% | 1 | 1 | 0% | 3,670 | 8,445 | +130% | 0 | 0 | — |
case-06 | pass→pass | 19,541 | 31,312 | +60% | 1 | 1 | 0% | 3,495 | 9,348 | +167% | 0 | 0 | — |
case-07 | fail→pass | 14,268 | 2,723 | -81% | 1 | 1 | 0% | 2,265 | 4,275 | +89% | 0 | 0 | — |
case-08 | pass→pass | 11,081 | 1,843 | -83% | 1 | 1 | 0% | 1,736 | 4,092 | +136% | 0 | 0 | — |
case-09 | fail→pass | 6,208 | 2,490 | -60% | 1 | 1 | 0% | 761 | 4,172 | +448% | 0 | 0 | — |
case-10 | fail→pass | 10,247 | 2,320 | -77% | 1 | 1 | 0% | 1,576 | 4,169 | +165% | 0 | 0 | — |
case-11 | pass→pass | 16,440 | 4,352 | -74% | 1 | 1 | 0% | 3,167 | 4,608 | +46% | 0 | 0 | — |
case-12 | pass→pass | 12,274 | 4,767 | -61% | 1 | 1 | 0% | 2,023 | 4,601 | +127% | 0 | 0 | — |
case-13 | fail→pass | 17,719 | 2,913 | -84% | 1 | 1 | 0% | 2,737 | 4,312 | +58% | 0 | 0 | — |
case-14 | fail→pass | 7,868 | 4,750 | -40% | 1 | 1 | 0% | 1,428 | 4,620 | +224% | 0 | 0 | — |
case-15 | fail→pass | 9,686 | 7,437 | -23% | 1 | 1 | 0% | 1,564 | 5,278 | +237% | 0 | 0 | — |
case-16 | fail→pass | 12,368 | 2,379 | -81% | 1 | 1 | 0% | 2,063 | 4,208 | +104% | 0 | 0 | — |
case-17 | pass→pass | 11,626 | 2,224 | -81% | 1 | 1 | 0% | 1,841 | 4,103 | +123% | 0 | 0 | — |
case-18 | pass→pass | 9,952 | 3,554 | -64% | 1 | 1 | 0% | 1,704 | 4,415 | +159% | 0 | 0 | — |
case-19 | fail→pass | 12,818 | 2,888 | -77% | 1 | 1 | 0% | 1,968 | 4,230 | +115% | 0 | 0 | — |
case-20 | pass→pass | 11,311 | 2,476 | -78% | 1 | 1 | 0% | 1,747 | 4,104 | +135% | 0 | 0 | — |
case-21 | fail→pass | 9,098 | 2,198 | -76% | 1 | 1 | 0% | 1,446 | 4,173 | +189% | 0 | 0 | — |
case-22 | fail→pass | 15,654 | 3,463 | -78% | 1 | 1 | 0% | 2,655 | 4,359 | +64% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +59 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.