Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Check BIM data against IDS (Information Delivery Specification). Validate model information requirements and compliance.
.claude/skills/datadrivenconstruction-ids-checker/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 253% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 23% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 131% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 118% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 245% | 0% |
BIM data validation challenges:
Automated IDS (Information Delivery Specification) checking system to validate BIM models against defined requirements.
pythonimport pandas as pd from typing import Dict, Any, List, Optional from dataclasses import dataclass, field from datetime import datetime from enum import Enum import re class RequirementType(Enum): PROPERTY = "property" CLASSIFICATION = "classification" MATERIAL = "material" ATTRIBUTE = "attribute" RELATION = "relation" class Facet(Enum): ENTITY = "entity" PROPERTY_SET = "property_set" PROPERTY = "property" CLASSIFICATION = "classification" MATERIAL = "material" PART_OF = "part_of" class Cardinality(Enum): REQUIRED = "required" OPTIONAL = "optional" PROHIBITED = "prohibited" class CheckResult(Enum): PASS = "pass" FAIL = "fail" WARNING = "warning" NOT_APPLICABLE = "n/a" @dataclass class IDSRequirement: req_id: str name: str description: str applicability: Dict[str, Any] # Which elements this applies to requirements: List[Dict[str, Any]] # What is required cardinality: Cardinality = Cardinality.REQUIRED @dataclass class ValidationResult: element_id: str element_type: str requirement_id: str result: CheckResult message: str details: Dict[str, Any] = field(default_factory=dict) @dataclass class IDSSpecification: spec_id: str name: str version: str purpose: str requirements: List[IDSRequirement] = field(default_factory=list) class IDSChecker: """Check BIM data against IDS (Information Delivery Specification).""" def __init__(self, spec_name: str): self.spec_name = spec_name self.specifications: Dict[str, IDSSpecification] = {} self.results: List[ValidationResult] = [] def create_specification(self, spec_id: str, name: str, version: str = "1.0", purpose: str = "") -> IDSSpecification: """Create new IDS specification.""" spec = IDSSpecification( spec_id=spec_id, name=name, version=version, purpose=purpose ) self.specifications[spec_id] = spec return spec def add_requirement(self, spec_id: str, requirement: IDSRequirement): """Add requirement to specification.""" if spec_id in self.specifications: self.specifications[spec_id].requirements.append(requirement) def add_property_requirement(self, spec_id: str, req_id: str, name: str, entity_type: str, property_set: str, property_name: str, data_type: str = None, value_pattern: str = None, cardinality: Cardinality = Cardinality.REQUIRED): """Add property requirement.""" requirement = IDSRequirement( req_id=req_id, name=name, description=f"Property {property_name} in {property_set}", applicability={'entity': entity_type}, requirements=[{ 'type': RequirementType.PROPERTY.value, 'property_set': property_set, 'property_name': property_name, 'data_type': data_type, 'value_pattern': value_pattern }], cardinality=cardinality ) self.add_requirement(spec_id, requirement) def add_classification_requirement(self, spec_id: str, req_id: str, name: str, entity_type: str, system: str, value_pattern: str = None, cardinality: Cardinality = Cardinality.REQUIRED): """Add classification requirement.""" requirement = IDSRequirement( req_id=req_id, name=name, description=f"Classification from {system}", applicability={'entity': entity_type}, requirements=[{ 'type': RequirementType.CLASSIFICATION.value, 'system': system, 'value_pattern': value_pattern }], cardinality=cardinality ) self.add_requirement(spec_id, requirement) def create_standard_cobie_spec(self) -> str: """Create standard COBie specification.""" spec = self.create_specification( "COBIE_BASIC", "COBie Basic Requirements", "1.0", "Basic COBie data requirements for facility handover" ) # Space requirements self.add_property_requirement("COBIE_BASIC", "CB-SP-01", "Space Name", "IfcSpace", "Pset_SpaceCommon", "Name") self.add_property_requirement("COBIE_BASIC", "CB-SP-02", "Space Number", "IfcSpace", "COBie_Space", "SpaceNumber") self.add_property_requirement("COBIE_BASIC", "CB-SP-03", "Room Tag", "IfcSpace", "COBie_Space", "RoomTag") # Component requirements self.add_property_requirement("COBIE_BASIC", "CB-CO-01", "Component Name", "IfcElement", "COBie_Component", "Name") self.add_property_requirement("COBIE_BASIC", "CB-CO-02", "Component Type", "IfcElement", "COBie_Component", "TypeName") self.add_property_requirement("COBIE_BASIC", "CB-CO-03", "Serial Number", "IfcElement", "COBie_Component", "SerialNumber", cardinality=Cardinality.OPTIONAL) # Type requirements self.add_property_requirement("COBIE_BASIC", "CB-TY-01", "Type Name", "IfcTypeObject", "COBie_Type", "Name") self.add_property_requirement("COBIE_BASIC", "CB-TY-02", "Manufacturer", "IfcTypeObject", "COBie_Type", "Manufacturer") self.add_property_requirement("COBIE_BASIC", "CB-TY-03", "Model Number", "IfcTypeObject", "COBie_Type", "ModelNumber") return "COBIE_BASIC" def create_standard_lod_spec(self, lod_level: int = 300) -> str: """Create standard LOD specification.""" spec_id = f"LOD_{lod_level}" spec = self.create_specification( spec_id, f"LOD {lod_level} Requirements", "1.0", f"Level of Development {lod_level} requirements" ) if lod_level >= 200: self.add_property_requirement(spec_id, f"LOD-{lod_level}-01", "Element must have type", "IfcElement", "Pset_ElementCommon", "Type") if lod_level >= 300: self.add_property_requirement(spec_id, f"LOD-{lod_level}-02", "Element must have dimensions", "IfcElement", "BaseQuantities", "Length") self.add_property_requirement(spec_id, f"LOD-{lod_level}-03", "Material assignment", "IfcElement", "Pset_MaterialCommon", "Material") if lod_level >= 350: self.add_property_requirement(spec_id, f"LOD-{lod_level}-04", "Fire rating", "IfcElement", "Pset_ElementCommon", "FireRating", cardinality=Cardinality.OPTIONAL) self.add_classification_requirement(spec_id, f"LOD-{lod_level}-05", "Uniformat classification", "IfcElement", "Uniformat") return spec_id def check_element(self, element: Dict[str, Any], spec_id: str) -> List[ValidationResult]: """Check single element against specification.""" results = [] if spec_id not in self.specifications: return results spec = self.specifications[spec_id] for req in spec.requirements: # Check applicability if not self._matches_applicability(element, req.applicability): continue # Check requirements for req_def in req.requirements: result = self._check_requirement(element, req, req_def) results.append(result) return results def _matches_applicability(self, element: Dict[str, Any], applicability: Dict[str, Any]) -> bool: """Check if element matches applicability criteria.""" entity_filter = applicability.get('entity') if entity_filter: element_type = element.get('type', '') if entity_filter not in element_type: return False return True def _check_requirement(self, element: Dict[str, Any], req: IDSRequirement, req_def: Dict[str, Any]) -> ValidationResult: """Check single requirement.""" req_type = req_def.get('type') if req_type == RequirementType.PROPERTY.value: return self._check_property_requirement(element, req, req_def) elif req_type == RequirementType.CLASSIFICATION.value: return self._check_classification_requirement(element, req, req_def) return ValidationResult( element_id=element.get('id', ''), element_type=element.get('type', ''), requirement_id=req.req_id, result=CheckResult.NOT_APPLICABLE, message="Unknown requirement type" ) def _check_property_requirement(self, element: Dict[str, Any], req: IDSRequirement, req_def: Dict[str, Any]) -> ValidationResult: """Check property requirement.""" pset_name = req_def.get('property_set') prop_name = req_def.get('property_name') value_pattern = req_def.get('value_pattern') # Get property value properties = element.get('properties', {}) pset = properties.get(pset_name, {}) value = pset.get(prop_name) result = ValidationResult( element_id=element.get('id', ''), element_type=element.get('type', ''), requirement_id=req.req_id, result=CheckResult.PASS, message="", details={'property_set': pset_name, 'property': prop_name, 'value': value} ) # Check if property exists if value is None: if req.cardinality == Cardinality.REQUIRED: result.result = CheckResult.FAIL result.message = f"Missing required property: {pset_name}.{prop_name}" elif req.cardinality == Cardinality.PROHIBITED: result.result = CheckResult.PASS result.message = "Prohibited property correctly absent" else: result.result = CheckResult.WARNING result.message = f"Optional property missing: {pset_name}.{prop_name}" return result # Check if property should not exist if req.cardinality == Cardinality.PROHIBITED: result.result = CheckResult.FAIL result.message = f"Prohibited property exists: {pset_name}.{prop_name}" return result # Check value pattern if value_pattern: if not re.match(value_pattern, str(value)): result.result = CheckResult.FAIL result.message = f"Value '{value}' does not match pattern '{value_pattern}'" return result result.message = f"Property {prop_name} = {value}" return result def _check_classification_requirement(self, element: Dict[str, Any], req: IDSRequirement, req_def: Dict[str, Any]) -> ValidationResult: """Check classification requirement.""" system = req_def.get('system') value_pattern = req_def.get('value_pattern') classifications = element.get('classifications', {}) value = classifications.get(system) result = ValidationResult( element_id=element.get('id', ''), element_type=element.get('type', ''), requirement_id=req.req_id, result=CheckResult.PASS, message="", details={'system': system, 'value': value} ) if value is None: if req.cardinality == Cardinality.REQUIRED: result.result = CheckResult.FAIL result.message = f"Missing classification: {system}" else: result.result = CheckResult.WARNING result.message = f"Optional classification missing: {system}" return result if value_pattern and not re.match(value_pattern, str(value)): result.result = CheckResult.FAIL result.message = f"Classification '{value}' does not match pattern" return result result.message = f"Classification {system} = {value}" return result def check_model(self, elements: List[Dict[str, Any]], spec_id: str) -> Dict[str, Any]: """Check all elements against specification.""" self.results = [] for element in elements: element_results = self.check_element(element, spec_id) self.results.extend(element_results) # Summarize results pass_count = sum(1 for r in self.results if r.result == CheckResult.PASS) fail_count = sum(1 for r in self.results if r.result == CheckResult.FAIL) warning_count = sum(1 for r in self.results if r.result == CheckResult.WARNING) return { 'specification': spec_id, 'elements_checked': len(elements), 'total_checks': len(self.results), 'passed': pass_count, 'failed': fail_count, 'warnings': warning_count, 'compliance_rate': round(pass_count / len(self.results) * 100, 1) if self.results else 0, 'status': 'COMPLIANT' if fail_count == 0 else 'NON-COMPLIANT' } def get_failed_checks(self) -> List[Dict[str, Any]]: """Get list of failed checks.""" return [ { 'element_id': r.element_id, 'element_type': r.element_type, 'requirement': r.requirement_id, 'message': r.message, 'details': r.details } for r in self.results if r.result == CheckResult.FAIL ] def export_to_excel(self, output_path: str) -> str: """Export validation results to Excel.""" with pd.ExcelWriter(output_path, engine='openpyxl') as writer: # Summary summary = { 'Total Checks': len(self.results), 'Passed': sum(1 for r in self.results if r.result == CheckResult.PASS), 'Failed': sum(1 for r in self.results if r.result == CheckResult.FAIL), 'Warnings': sum(1 for r in self.results if r.result == CheckResult.WARNING) } summary_df = pd.DataFrame([summary]) summary_df.to_excel(writer, sheet_name='Summary', index=False) # All results results_df = pd.DataFrame([{ 'Element ID': r.element_id, 'Element Type': r.element_type, 'Requirement': r.requirement_id, 'Result': r.result.value, 'Message': r.message } for r in self.results]) results_df.to_excel(writer, sheet_name='All Results', index=False) # Failed only failed_df = pd.DataFrame(self.get_failed_checks()) if not failed_df.empty: failed_df.to_excel(writer, sheet_name='Failed', index=False) return output_path
python# Create IDS checker checker = IDSChecker("Project BIM Validation") # Create COBie specification cobie_spec = checker.create_standard_cobie_spec() # Sample BIM elements elements = [ { 'id': 'Space_001', 'type': 'IfcSpace', 'properties': { 'Pset_SpaceCommon': {'Name': 'Office 101'}, 'COBie_Space': {'SpaceNumber': 'SP-101', 'RoomTag': 'A101'} } }, { 'id': 'Door_001', 'type': 'IfcDoor', 'properties': { 'COBie_Component': {'Name': 'Door 1', 'TypeName': 'Single Door'} } } ] # Check model results = checker.check_model(elements, cobie_spec) print(f"Compliance: {results['compliance_rate']}%") print(f"Status: {results['status']}")
pythonlod_spec = checker.create_standard_lod_spec(350) results = checker.check_model(elements, lod_spec)
pythonchecker.add_property_requirement( "COBIE_BASIC", "CB-CUSTOM-01", "Fire Rating Required", "IfcWall", "Pset_WallCommon", "FireRating", value_pattern=r"^\d+\s*hr$" )
pythonfailed = checker.get_failed_checks() for item in failed: print(f"{item['element_id']}: {item['message']}")
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 11,936 | 45,460 | +281% | 1 | 1 | 0% | 2,558 | 9,040 | +253% | 0 | 0 | — |
case-02 | fail→pass | 36,659 | 15,591 | -57% | 1 | 1 | 0% | 6,207 | 7,665 | +23% | 0 | 0 | — |
case-03 | fail→pass | 16,587 | 13,670 | -18% | 1 | 1 | 0% | 3,255 | 7,505 | +131% | 0 | 0 | — |
case-04 | pass→pass | 17,882 | 17,049 | -5% | 1 | 1 | 0% | 3,142 | 7,610 | +142% | 0 | 0 | — |
case-05 | pass→pass | 21,901 | 26,266 | +20% | 1 | 1 | 0% | 4,011 | 9,774 | +144% | 0 | 0 | — |
case-06 | pass→pass | 19,012 | 19,167 | +1% | 1 | 1 | 0% | 3,189 | 8,130 | +155% | 0 | 0 | — |
case-07 | fail→pass | 19,362 | 15,987 | -17% | 1 | 1 | 0% | 3,328 | 7,253 | +118% | 0 | 0 | — |
case-08 | fail→pass | 10,019 | 7,094 | -29% | 1 | 1 | 0% | 1,686 | 5,817 | +245% | 0 | 0 | — |
case-09 | fail→pass | 19,356 | 9,822 | -49% | 1 | 1 | 0% | 2,862 | 6,553 | +129% | 0 | 0 | — |
case-10 | pass→pass | 12,845 | 4,483 | -65% | 1 | 1 | 0% | 1,926 | 5,317 | +176% | 0 | 0 | — |
case-11 | pass→pass | 11,001 | 4,766 | -57% | 1 | 1 | 0% | 1,748 | 5,534 | +217% | 0 | 0 | — |
case-12 | pass→pass | 5,724 | 4,580 | -20% | 1 | 1 | 0% | 776 | 5,489 | +607% | 0 | 0 | — |
case-13 | pass→pass | 8,355 | 4,609 | -45% | 1 | 1 | 0% | 1,311 | 5,420 | +313% | 0 | 0 | — |
case-14 | pass→pass | 11,282 | 4,615 | -59% | 1 | 1 | 0% | 1,673 | 5,394 | +222% | 0 | 0 | — |
case-15 | pass→fail | 11,967 | 15,066 | +26% | 1 | 1 | 0% | 1,767 | 7,347 | +316% | 0 | 0 | — |
case-16 | fail→pass | 5,992 | 3,858 | -36% | 1 | 1 | 0% | 852 | 5,251 | +516% | 0 | 0 | — |
case-17 | fail→pass | 10,563 | 6,114 | -42% | 1 | 1 | 0% | 1,842 | 5,783 | +214% | 0 | 0 | — |
case-18 | fail→pass | 13,611 | 4,474 | -67% | 1 | 1 | 0% | 2,059 | 5,383 | +161% | 0 | 0 | — |
case-19 | fail→pass | 16,679 | 5,912 | -65% | 1 | 1 | 0% | 2,673 | 5,723 | +114% | 0 | 0 | — |
case-20 | pass→pass | 10,486 | 1,943 | -81% | 1 | 1 | 0% | 1,709 | 4,900 | +187% | 0 | 0 | — |
case-21 | pass→pass | 7,618 | 1,918 | -75% | 1 | 1 | 0% | 1,186 | 4,869 | +311% | 0 | 0 | — |
case-22 | pass→pass | 10,185 | 6,393 | -37% | 1 | 1 | 0% | 1,560 | 5,672 | +264% | 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 +41 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
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.