Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Build validation rules for construction data. Create RegEx and logic-based validation for BIM elements, cost codes, and schedule data.
.claude/skills/datadrivenconstruction-validation-rules-builder/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-22 | ✗→✓ | ▲ Improved | 282% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 186% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 528% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 238% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 264% | 0% |
Construction data quality challenges:
Rule-based validation engine using RegEx and logic rules to ensure data quality across construction systems.
pythonimport re from typing import Dict, Any, List, Optional, Callable from dataclasses import dataclass, field from enum import Enum from datetime import date class RuleType(Enum): REGEX = "regex" RANGE = "range" ENUM = "enum" CUSTOM = "custom" REQUIRED = "required" DATE = "date" REFERENCE = "reference" class Severity(Enum): ERROR = "error" WARNING = "warning" INFO = "info" @dataclass class ValidationResult: field: str is_valid: bool message: str severity: Severity value: Any = None @dataclass class ValidationRule: name: str field: str rule_type: RuleType pattern: str = "" min_value: float = None max_value: float = None allowed_values: List[Any] = field(default_factory=list) custom_func: Callable = None severity: Severity = Severity.ERROR message: str = "" enabled: bool = True class ValidationRulesBuilder: """Build and execute validation rules for construction data.""" # Pre-defined patterns for construction data PATTERNS = { 'wbs_code': r'^[0-9]{2}\.[0-9]{2}\.[0-9]{2}(\.[0-9]{2})?$', 'cost_code': r'^[A-Z]{1,3}-[0-9]{3,6}$', 'activity_id': r'^[A-Z]{1,3}[0-9]{4,6}$', 'drawing_number': r'^[A-Z]{1,2}-[0-9]{3}-[A-Z0-9]{2,4}$', 'specification_section': r'^[0-9]{2}\s?[0-9]{2}\s?[0-9]{2}(\.[0-9]{2})?$', 'level_name': r'^(Level|L|FL)\s?[-_]?\s?([0-9]{1,3}|B[0-9]|R|G|M)$', 'grid_line': r'^[A-Z]\.?[0-9]?$|^[0-9]{1,2}\.?[A-Z]?$', 'revision': r'^[A-Z]$|^[0-9]{1,2}$|^Rev\.?\s?[A-Z0-9]+$', 'date_iso': r'^\d{4}-\d{2}-\d{2}$', 'email': r'^[\w\.-]+@[\w\.-]+\.\w+$', 'phone': r'^\+?[0-9]{1,3}[-.\s]?[0-9]{3,4}[-.\s]?[0-9]{4}$', } def __init__(self): self.rules: List[ValidationRule] = [] self.custom_patterns: Dict[str, str] = {} def add_regex_rule(self, name: str, field: str, pattern: str, message: str = "", severity: Severity = Severity.ERROR) -> 'ValidationRulesBuilder': """Add regex validation rule.""" self.rules.append(ValidationRule( name=name, field=field, rule_type=RuleType.REGEX, pattern=pattern, message=message or f"Field '{field}' does not match pattern", severity=severity )) return self def add_range_rule(self, name: str, field: str, min_value: float = None, max_value: float = None, message: str = "", severity: Severity = Severity.ERROR) -> 'ValidationRulesBuilder': """Add numeric range validation rule.""" self.rules.append(ValidationRule( name=name, field=field, rule_type=RuleType.RANGE, min_value=min_value, max_value=max_value, message=message or f"Field '{field}' out of range [{min_value}, {max_value}]", severity=severity )) return self def add_enum_rule(self, name: str, field: str, allowed_values: List[Any], message: str = "", severity: Severity = Severity.ERROR) -> 'ValidationRulesBuilder': """Add enumeration validation rule.""" self.rules.append(ValidationRule( name=name, field=field, rule_type=RuleType.ENUM, allowed_values=allowed_values, message=message or f"Field '{field}' must be one of: {allowed_values}", severity=severity )) return self def add_required_rule(self, name: str, field: str, message: str = "", severity: Severity = Severity.ERROR) -> 'ValidationRulesBuilder': """Add required field validation rule.""" self.rules.append(ValidationRule( name=name, field=field, rule_type=RuleType.REQUIRED, message=message or f"Field '{field}' is required", severity=severity )) return self def add_custom_rule(self, name: str, field: str, func: Callable[[Any], bool], message: str = "", severity: Severity = Severity.ERROR) -> 'ValidationRulesBuilder': """Add custom validation function.""" self.rules.append(ValidationRule( name=name, field=field, rule_type=RuleType.CUSTOM, custom_func=func, message=message or f"Field '{field}' failed custom validation", severity=severity )) return self def add_pattern(self, name: str, pattern: str): """Add custom pattern for reuse.""" self.custom_patterns[name] = pattern def use_pattern(self, rule_name: str, field: str, pattern_name: str, message: str = "", severity: Severity = Severity.ERROR) -> 'ValidationRulesBuilder': """Use pre-defined or custom pattern.""" pattern = self.custom_patterns.get(pattern_name) or self.PATTERNS.get(pattern_name) if not pattern: raise ValueError(f"Pattern '{pattern_name}' not found") return self.add_regex_rule(rule_name, field, pattern, message, severity) def validate_record(self, record: Dict[str, Any]) -> List[ValidationResult]: """Validate a single record against all rules.""" results = [] for rule in self.rules: if not rule.enabled: continue value = record.get(rule.field) result = self._apply_rule(rule, value) results.append(result) return results def validate_records(self, records: List[Dict[str, Any]]) -> Dict[str, Any]: """Validate multiple records and return summary.""" all_results = [] error_count = 0 warning_count = 0 for i, record in enumerate(records): record_results = self.validate_record(record) for result in record_results: if not result.is_valid: result_dict = { 'record_index': i, 'field': result.field, 'message': result.message, 'severity': result.severity.value, 'value': result.value } all_results.append(result_dict) if result.severity == Severity.ERROR: error_count += 1 elif result.severity == Severity.WARNING: warning_count += 1 return { 'total_records': len(records), 'valid_records': len(records) - len(set(r['record_index'] for r in all_results if r['severity'] == 'error')), 'error_count': error_count, 'warning_count': warning_count, 'issues': all_results } def _apply_rule(self, rule: ValidationRule, value: Any) -> ValidationResult: """Apply single validation rule.""" if rule.rule_type == RuleType.REQUIRED: is_valid = value is not None and value != "" and value != [] return ValidationResult( field=rule.field, is_valid=is_valid, message="" if is_valid else rule.message, severity=rule.severity, value=value ) # Skip other validations if value is None/empty if value is None or value == "": return ValidationResult( field=rule.field, is_valid=True, message="", severity=rule.severity, value=value ) if rule.rule_type == RuleType.REGEX: is_valid = bool(re.match(rule.pattern, str(value))) elif rule.rule_type == RuleType.RANGE: try: num_value = float(value) is_valid = True if rule.min_value is not None and num_value < rule.min_value: is_valid = False if rule.max_value is not None and num_value > rule.max_value: is_valid = False except (ValueError, TypeError): is_valid = False elif rule.rule_type == RuleType.ENUM: is_valid = value in rule.allowed_values elif rule.rule_type == RuleType.CUSTOM: try: is_valid = rule.custom_func(value) except Exception: is_valid = False else: is_valid = True return ValidationResult( field=rule.field, is_valid=is_valid, message="" if is_valid else rule.message, severity=rule.severity, value=value ) def get_rules_summary(self) -> List[Dict]: """Get summary of all rules.""" return [{ 'name': r.name, 'field': r.field, 'type': r.rule_type.value, 'severity': r.severity.value, 'enabled': r.enabled } for r in self.rules] # Construction-specific validators class ConstructionValidators: """Pre-built validators for construction data.""" @staticmethod def wbs_validator() -> ValidationRulesBuilder: """Validator for WBS codes.""" return (ValidationRulesBuilder() .add_required_rule("wbs_required", "wbs_code") .use_pattern("wbs_format", "wbs_code", "wbs_code", "Invalid WBS format (expected: XX.XX.XX)") ) @staticmethod def cost_item_validator() -> ValidationRulesBuilder: """Validator for cost items.""" return (ValidationRulesBuilder() .add_required_rule("code_required", "cost_code") .add_required_rule("desc_required", "description") .use_pattern("code_format", "cost_code", "cost_code") .add_range_rule("quantity_positive", "quantity", min_value=0) .add_range_rule("unit_cost_positive", "unit_cost", min_value=0) .add_enum_rule("unit_valid", "unit", ["EA", "LF", "SF", "CY", "TON", "HR", "LS"]) ) @staticmethod def schedule_activity_validator() -> ValidationRulesBuilder: """Validator for schedule activities.""" def dates_valid(record): start = record.get('start_date') end = record.get('end_date') if start and end: return start <= end return True return (ValidationRulesBuilder() .add_required_rule("id_required", "activity_id") .add_required_rule("name_required", "activity_name") .use_pattern("id_format", "activity_id", "activity_id") .add_range_rule("duration_positive", "duration", min_value=0) .add_range_rule("progress_range", "percent_complete", min_value=0, max_value=100) ) @staticmethod def bim_element_validator() -> ValidationRulesBuilder: """Validator for BIM elements.""" return (ValidationRulesBuilder() .add_required_rule("guid_required", "element_guid") .add_required_rule("type_required", "element_type") .add_required_rule("level_required", "level") .use_pattern("level_format", "level", "level_name", severity=Severity.WARNING) .add_enum_rule("status_valid", "status", ["New", "Existing", "Demolished", "Temporary"]) )
python# Create validator validator = ValidationRulesBuilder() # Add rules validator.add_required_rule("id_required", "item_id") validator.use_pattern("wbs_valid", "wbs_code", "wbs_code") validator.add_range_rule("cost_range", "total_cost", min_value=0, max_value=10000000) validator.add_enum_rule("status_valid", "status", ["Active", "Completed", "Cancelled"]) # Validate records records = [ {"item_id": "001", "wbs_code": "01.02.03", "total_cost": 50000, "status": "Active"}, {"item_id": "", "wbs_code": "invalid", "total_cost": -100, "status": "Unknown"} ] results = validator.validate_records(records) print(f"Valid: {results['valid_records']}/{results['total_records']}") print(f"Errors: {results['error_count']}, Warnings: {results['warning_count']}")
pythoncost_validator = ConstructionValidators.cost_item_validator() results = cost_validator.validate_records(cost_items)
pythonschedule_validator = ConstructionValidators.schedule_activity_validator() results = schedule_validator.validate_records(activities)
pythonbim_validator = ConstructionValidators.bim_element_validator() results = bim_validator.validate_records(elements)
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-22 | fail→pass | 6,127 | 3,069 | -50% | 1 | 1 | 0% | 1,109 | 4,239 | +282% | 0 | 0 | — |
case-01 | fail→fail | 24,607 | 14,220 | -42% | 1 | 1 | 0% | 5,907 | 6,925 | +17% | 0 | 0 | — |
case-02 | fail→fail | 11,525 | 13,319 | +16% | 1 | 1 | 0% | 2,239 | 6,823 | +205% | 0 | 0 | — |
case-03 | fail→fail | 16,476 | 9,058 | -45% | 1 | 1 | 0% | 3,622 | 5,536 | +53% | 0 | 0 | — |
case-04 | fail→fail | 20,897 | 21,463 | +3% | 1 | 1 | 0% | 3,951 | 8,125 | +106% | 0 | 0 | — |
case-05 | fail→fail | 20,142 | 21,242 | +5% | 1 | 1 | 0% | 3,676 | 7,840 | +113% | 0 | 0 | — |
case-06 | fail→fail | 19,874 | 19,070 | -4% | 1 | 1 | 0% | 4,075 | 7,695 | +89% | 0 | 0 | — |
case-07 | fail→pass | 9,094 | 5,924 | -35% | 1 | 1 | 0% | 1,710 | 4,885 | +186% | 0 | 0 | — |
case-08 | fail→pass | 3,893 | 5,253 | +35% | 1 | 1 | 0% | 767 | 4,818 | +528% | 0 | 0 | — |
case-09 | fail→pass | 7,258 | 6,824 | -6% | 1 | 1 | 0% | 1,484 | 5,016 | +238% | 0 | 0 | — |
case-10 | fail→fail | 10,812 | 10,143 | -6% | 1 | 1 | 0% | 2,034 | 5,679 | +179% | 0 | 0 | — |
case-11 | fail→fail | 8,101 | 7,020 | -13% | 1 | 1 | 0% | 1,505 | 5,066 | +237% | 0 | 0 | — |
case-12 | fail→fail | 9,105 | 8,481 | -7% | 1 | 1 | 0% | 1,756 | 5,364 | +205% | 0 | 0 | — |
case-13 | fail→pass | 6,966 | 5,334 | -23% | 1 | 1 | 0% | 1,277 | 4,647 | +264% | 0 | 0 | — |
case-14 | fail→pass | 9,410 | 3,888 | -59% | 1 | 1 | 0% | 1,663 | 4,387 | +164% | 0 | 0 | — |
case-15 | pass→pass | 8,402 | 2,282 | -73% | 1 | 1 | 0% | 1,532 | 4,106 | +168% | 0 | 0 | — |
case-16 | fail→pass | 13,182 | 5,925 | -55% | 1 | 1 | 0% | 2,558 | 4,792 | +87% | 0 | 0 | — |
case-17 | pass→pass | 11,936 | 5,457 | -54% | 1 | 1 | 0% | 1,864 | 4,655 | +150% | 0 | 0 | — |
case-18 | pass→pass | 14,175 | 7,628 | -46% | 1 | 1 | 0% | 2,433 | 5,045 | +107% | 0 | 0 | — |
case-19 | fail→pass | 18,972 | 3,155 | -83% | 1 | 1 | 0% | 3,027 | 4,309 | +42% | 0 | 0 | — |
case-20 | fail→pass | 9,200 | 3,818 | -59% | 1 | 1 | 0% | 1,592 | 4,331 | +172% | 0 | 0 | — |
case-21 | fail→pass | 16,103 | 3,059 | -81% | 1 | 1 | 0% | 2,572 | 4,278 | +66% | 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 +45 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.