← Back to Skills Marketplace
datadrivenconstruction

Auto Estimate Generator

darwinlinuxwin32 ⚠ suspicious
1360
Downloads
0
Stars
2
Active Installs
3
Versions
Install in OpenClaw
/install auto-estimate-generator
Description
Automatically generate estimates from QTO data. Apply pricing rules to BIM quantities for cost estimates.
README (SKILL.md)

\r \r

Auto Estimate Generator\r

\r

Business Case\r

\r

Problem Statement\r

Manual estimate creation challenges:\r

  • Time-consuming quantity mapping\r
  • Inconsistent pricing rules\r
  • Errors in calculations\r
  • Difficulty updating estimates\r \r

Solution\r

Automated estimate generation from BIM/QTO data using configurable pricing rules and assembly mappings.\r \r

Technical Implementation\r

\r

import pandas as pd\r
from typing import Dict, Any, List, Optional, Callable\r
from dataclasses import dataclass, field\r
from enum import Enum\r
\r
\r
class ElementType(Enum):\r
    WALL = "wall"\r
    FLOOR = "floor"\r
    CEILING = "ceiling"\r
    DOOR = "door"\r
    WINDOW = "window"\r
    COLUMN = "column"\r
    BEAM = "beam"\r
    FOUNDATION = "foundation"\r
    ROOF = "roof"\r
    STAIR = "stair"\r
    MEP = "mep"\r
\r
\r
@dataclass\r
class QTOItem:\r
    element_id: str\r
    element_type: ElementType\r
    name: str\r
    quantity: float\r
    unit: str\r
    properties: Dict[str, Any] = field(default_factory=dict)\r
\r
\r
@dataclass\r
class PricingRule:\r
    rule_id: str\r
    name: str\r
    element_type: ElementType\r
    conditions: Dict[str, Any] = field(default_factory=dict)\r
    unit_cost: float = 0\r
    assembly_code: str = ""\r
    cost_breakdown: Dict[str, float] = field(default_factory=dict)\r
\r
\r
@dataclass\r
class EstimateItem:\r
    qto_element_id: str\r
    description: str\r
    quantity: float\r
    unit: str\r
    unit_cost: float\r
    total_cost: float\r
    rule_applied: str\r
    wbs_code: str = ""\r
\r
\r
class AutoEstimateGenerator:\r
    """Generate estimates from QTO data automatically."""\r
\r
    def __init__(self, project_name: str):\r
        self.project_name = project_name\r
        self.pricing_rules: List[PricingRule] = []\r
        self.qto_items: List[QTOItem] = []\r
        self.estimate_items: List[EstimateItem] = []\r
        self.unmapped_items: List[QTOItem] = []\r
\r
    def add_pricing_rule(self, rule: PricingRule):\r
        """Add pricing rule."""\r
        self.pricing_rules.append(rule)\r
\r
    def load_pricing_rules_from_df(self, df: pd.DataFrame):\r
        """Load pricing rules from DataFrame."""\r
\r
        for _, row in df.iterrows():\r
            conditions = {}\r
            if 'material' in row:\r
                conditions['material'] = row['material']\r
            if 'thickness_min' in row:\r
                conditions['thickness_min'] = row['thickness_min']\r
            if 'thickness_max' in row:\r
                conditions['thickness_max'] = row['thickness_max']\r
\r
            rule = PricingRule(\r
                rule_id=row['rule_id'],\r
                name=row['name'],\r
                element_type=ElementType(row['element_type'].lower()),\r
                conditions=conditions,\r
                unit_cost=float(row['unit_cost']),\r
                assembly_code=row.get('assembly_code', ''),\r
                cost_breakdown={\r
                    'labor': float(row.get('labor_pct', 0.4)),\r
                    'material': float(row.get('material_pct', 0.5)),\r
                    'equipment': float(row.get('equipment_pct', 0.1))\r
                }\r
            )\r
            self.add_pricing_rule(rule)\r
\r
    def load_qto_from_df(self, df: pd.DataFrame):\r
        """Load QTO items from DataFrame."""\r
\r
        for _, row in df.iterrows():\r
            properties = {}\r
            for col in df.columns:\r
                if col not in ['element_id', 'element_type', 'name', 'quantity', 'unit']:\r
                    properties[col] = row[col]\r
\r
            qto = QTOItem(\r
                element_id=str(row['element_id']),\r
                element_type=ElementType(row['element_type'].lower()),\r
                name=row['name'],\r
                quantity=float(row['quantity']),\r
                unit=row['unit'],\r
                properties=properties\r
            )\r
            self.qto_items.append(qto)\r
\r
    def find_matching_rule(self, qto_item: QTOItem) -> Optional[PricingRule]:\r
        """Find pricing rule that matches QTO item."""\r
\r
        matching_rules = []\r
\r
        for rule in self.pricing_rules:\r
            if rule.element_type != qto_item.element_type:\r
                continue\r
\r
            # Check conditions\r
            match = True\r
            for key, value in rule.conditions.items():\r
                if key.endswith('_min'):\r
                    prop_name = key[:-4]\r
                    if prop_name in qto_item.properties:\r
                        if qto_item.properties[prop_name] \x3C value:\r
                            match = False\r
                elif key.endswith('_max'):\r
                    prop_name = key[:-4]\r
                    if prop_name in qto_item.properties:\r
                        if qto_item.properties[prop_name] > value:\r
                            match = False\r
                else:\r
                    if key in qto_item.properties:\r
                        if qto_item.properties[key] != value:\r
                            match = False\r
\r
            if match:\r
                matching_rules.append(rule)\r
\r
        # Return most specific rule (most conditions)\r
        if matching_rules:\r
            return max(matching_rules, key=lambda r: len(r.conditions))\r
        return None\r
\r
    def generate_estimate(self) -> Dict[str, Any]:\r
        """Generate estimate from QTO items."""\r
\r
        self.estimate_items = []\r
        self.unmapped_items = []\r
        total_cost = 0\r
\r
        for qto in self.qto_items:\r
            rule = self.find_matching_rule(qto)\r
\r
            if rule:\r
                item_cost = qto.quantity * rule.unit_cost\r
\r
                self.estimate_items.append(EstimateItem(\r
                    qto_element_id=qto.element_id,\r
                    description=f"{qto.name} ({rule.name})",\r
                    quantity=qto.quantity,\r
                    unit=qto.unit,\r
                    unit_cost=rule.unit_cost,\r
                    total_cost=round(item_cost, 2),\r
                    rule_applied=rule.rule_id,\r
                    wbs_code=rule.assembly_code\r
                ))\r
                total_cost += item_cost\r
            else:\r
                self.unmapped_items.append(qto)\r
\r
        return {\r
            'project': self.project_name,\r
            'total_qto_items': len(self.qto_items),\r
            'mapped_items': len(self.estimate_items),\r
            'unmapped_items': len(self.unmapped_items),\r
            'mapping_rate': round(len(self.estimate_items) / len(self.qto_items) * 100, 1) if self.qto_items else 0,\r
            'total_cost': round(total_cost, 2),\r
            'items': self.estimate_items\r
        }\r
\r
    def get_cost_by_element_type(self) -> Dict[str, float]:\r
        """Get cost breakdown by element type."""\r
\r
        by_type = {}\r
        for qto in self.qto_items:\r
            for est_item in self.estimate_items:\r
                if est_item.qto_element_id == qto.element_id:\r
                    type_name = qto.element_type.value\r
                    by_type[type_name] = by_type.get(type_name, 0) + est_item.total_cost\r
\r
        return {k: round(v, 2) for k, v in by_type.items()}\r
\r
    def get_unmapped_summary(self) -> pd.DataFrame:\r
        """Get summary of unmapped items."""\r
\r
        if not self.unmapped_items:\r
            return pd.DataFrame()\r
\r
        data = []\r
        for item in self.unmapped_items:\r
            data.append({\r
                'Element ID': item.element_id,\r
                'Type': item.element_type.value,\r
                'Name': item.name,\r
                'Quantity': item.quantity,\r
                'Unit': item.unit,\r
                'Properties': str(item.properties)\r
            })\r
\r
        return pd.DataFrame(data)\r
\r
    def export_to_excel(self, output_path: str) -> str:\r
        """Export estimate to Excel."""\r
\r
        result = self.generate_estimate()\r
\r
        with pd.ExcelWriter(output_path, engine='openpyxl') as writer:\r
            # Summary\r
            summary_df = pd.DataFrame([{\r
                'Project': self.project_name,\r
                'Total QTO Items': result['total_qto_items'],\r
                'Mapped Items': result['mapped_items'],\r
                'Unmapped Items': result['unmapped_items'],\r
                'Mapping Rate %': result['mapping_rate'],\r
                'Total Cost': result['total_cost']\r
            }])\r
            summary_df.to_excel(writer, sheet_name='Summary', index=False)\r
\r
            # Estimate items\r
            items_df = pd.DataFrame([{\r
                'Element ID': item.qto_element_id,\r
                'Description': item.description,\r
                'Quantity': item.quantity,\r
                'Unit': item.unit,\r
                'Unit Cost': item.unit_cost,\r
                'Total Cost': item.total_cost,\r
                'WBS': item.wbs_code,\r
                'Rule': item.rule_applied\r
            } for item in self.estimate_items])\r
            items_df.to_excel(writer, sheet_name='Estimate', index=False)\r
\r
            # By element type\r
            by_type_df = pd.DataFrame([\r
                {'Element Type': k, 'Cost': v}\r
                for k, v in self.get_cost_by_element_type().items()\r
            ])\r
            by_type_df.to_excel(writer, sheet_name='By Type', index=False)\r
\r
            # Unmapped items\r
            unmapped_df = self.get_unmapped_summary()\r
            if not unmapped_df.empty:\r
                unmapped_df.to_excel(writer, sheet_name='Unmapped', index=False)\r
\r
        return output_path\r
\r
    def suggest_missing_rules(self) -> List[Dict[str, Any]]:\r
        """Suggest pricing rules for unmapped items."""\r
\r
        suggestions = []\r
        seen_types = set()\r
\r
        for item in self.unmapped_items:\r
            key = (item.element_type.value, str(item.properties))\r
            if key not in seen_types:\r
                seen_types.add(key)\r
                suggestions.append({\r
                    'element_type': item.element_type.value,\r
                    'sample_name': item.name,\r
                    'properties': item.properties,\r
                    'count': sum(1 for i in self.unmapped_items\r
                                if i.element_type == item.element_type\r
                                and str(i.properties) == str(item.properties))\r
                })\r
\r
        return sorted(suggestions, key=lambda x: x['count'], reverse=True)\r
```\r
\r
## Quick Start\r
\r
```python\r
# Initialize generator\r
generator = AutoEstimateGenerator("Office Building A")\r
\r
# Add pricing rules\r
generator.add_pricing_rule(PricingRule(\r
    rule_id="W-001",\r
    name="Interior Wall - Drywall",\r
    element_type=ElementType.WALL,\r
    conditions={"material": "Drywall"},\r
    unit_cost=45.00,\r
    assembly_code="09.29.10"\r
))\r
\r
generator.add_pricing_rule(PricingRule(\r
    rule_id="W-002",\r
    name="Exterior Wall - Masonry",\r
    element_type=ElementType.WALL,\r
    conditions={"material": "Masonry"},\r
    unit_cost=125.00,\r
    assembly_code="04.21.13"\r
))\r
\r
# Load QTO data\r
generator.qto_items = [\r
    QTOItem("W-001", ElementType.WALL, "Interior Wall L1", 500, "SF", {"material": "Drywall"}),\r
    QTOItem("W-002", ElementType.WALL, "Exterior Wall", 1200, "SF", {"material": "Masonry"})\r
]\r
\r
# Generate estimate\r
result = generator.generate_estimate()\r
print(f"Total Cost: ${result['total_cost']:,.2f}")\r
print(f"Mapping Rate: {result['mapping_rate']}%")\r
```\r
\r
## Common Use Cases\r
\r
### 1. Cost by Element Type\r
```python\r
by_type = generator.get_cost_by_element_type()\r
for element_type, cost in by_type.items():\r
    print(f"{element_type}: ${cost:,.2f}")\r
```\r
\r
### 2. Unmapped Items\r
```python\r
unmapped = generator.get_unmapped_summary()\r
print(unmapped)\r
```\r
\r
### 3. Rule Suggestions\r
```python\r
suggestions = generator.suggest_missing_rules()\r
for s in suggestions:\r
    print(f"Need rule for: {s['element_type']} ({s['count']} items)")\r
```\r
\r
## Resources\r
- **DDC Book**: Chapter 3.2 - QTO and Automated Estimates\r
- **Website**: https://datadrivenconstruction.io\r
Usage Guidance
This skill appears to do what it says (generate cost estimates from QTO data), but there are a few things to check before installing: - Dependencies: The included Python code uses pandas (and may need Excel libraries). Ask the publisher for a dependency list or an install spec, or run the skill in a sandboxed environment to avoid unexpected package installs. - Filesystem access: The skill metadata requests filesystem permission. Ensure the agent's runtime will restrict the skill to only the files you explicitly provide (do not grant unrestricted access to your home or system directories). - Source and provenance: The source is listed as 'unknown' and the homepage is an external domain. If you plan to use this in production, verify the publisher and ask for a canonical package/release or a git repo so you can review the complete code. - Test with non-sensitive data first: Run the skill on synthetic or redacted QTO data to confirm behavior and outputs before feeding real project files. If the publisher can provide: (1) a full dependency list and install instructions, (2) confirmation of no external network calls, and (3) a narrower filesystem access scope or explicit instructions for expected file paths, the concerns above would be largely resolved.
Capability Analysis
Type: OpenClaw Skill Name: auto-estimate-generator Version: 2.1.0 The skill is classified as suspicious due to an arbitrary file write vulnerability in the `export_to_excel` function within `SKILL.md`. This function takes an `output_path` argument, which, if user-controlled and unsanitized by the OpenClaw agent, could allow an attacker to overwrite arbitrary files on the system. The `claw.json` file explicitly requests `filesystem` permissions, which enables this risky operation. While there is no clear evidence of intentional malicious behavior like data exfiltration or backdoors, this vulnerability presents a significant security risk.
Capability Assessment
Purpose & Capability
Name/description match the instructions: it expects QTO/BIM input and applies pricing rules. However, the embedded Python implementation uses pandas (and likely other Python libraries) but the declared requirements only list python3; those additional runtime dependencies are not declared in the metadata or an install spec.
Instruction Scope
Instructions limit the agent to parsing user-provided QTO data, mapping to pricing rules, and producing structured estimates. There are no instructions to read unrelated system files, call external endpoints, or exfiltrate data in the visible content.
Install Mechanism
This is an instruction-only skill with no install spec (lowest install risk). That said, the runtime code shown requires Python libraries (pandas, possibly openpyxl, etc.) but no installation steps are provided — an operational gap that could lead an agent to attempt installing packages at runtime or fail unexpectedly.
Credentials
No environment variables or external credentials are requested, which is appropriate for an offline estimate generator that uses user-supplied QTO data. The lack of secret access is proportionate to the stated purpose.
Persistence & Privilege
claw.json includes a filesystem permission. That is reasonable for reading user-supplied CSV/Excel files, but it is broad: the skill metadata does not constrain which paths the agent may access. Because no install step or explicit file-path constraints are provided, confirm how filesystem access will be limited in your environment before enabling the skill.
How to Use
  1. Make sure OpenClaw is installed (local or Docker)
  2. Run the install command in chat: /install auto-estimate-generator
  3. After installation, invoke the skill by name or use /auto-estimate-generator
  4. Provide required inputs per the skill's parameter spec and get structured output
Version History
v2.1.0
- Added "homepage" and "metadata" fields to SKILL.md for improved integration and discoverability. - No functional Python or business logic changes.
v2.0.0
Version 2.0.0 - Added business case and detailed technical documentation to SKILL.md. - Introduced support for configurable pricing rules and assembly mappings. - Enables automated generation of estimates from BIM/QTO data. - Improves error handling and mapping rates reporting for unmapped items. - Provides cost breakdown by element type and export to Excel features.
v1.0.0
Auto Estimate Generator 1.0.0 – Initial release - Automatically generates cost estimates from QTO/BIM data using configurable pricing rules and assemblies. - Supports loading data and rules from pandas DataFrames. - Maps QTO elements to pricing rules based on type and condition matching. - Outputs detailed estimate items, unmapped item summaries, and cost breakdowns by element type. - Includes method to export results to Excel.
Metadata
Slug auto-estimate-generator
Version 2.1.0
License
All-time Installs 2
Active Installs 2
Total Versions 3
Frequently Asked Questions

What is Auto Estimate Generator?

Automatically generate estimates from QTO data. Apply pricing rules to BIM quantities for cost estimates. It is an AI Agent Skill for Claude Code / OpenClaw, with 1360 downloads so far.

How do I install Auto Estimate Generator?

Run "/install auto-estimate-generator" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.

Is Auto Estimate Generator free?

Yes, Auto Estimate Generator is completely free (open-source). You can download, install and use it at no cost.

Which platforms does Auto Estimate Generator support?

Auto Estimate Generator is cross-platform and runs anywhere OpenClaw / Claude Code is available (darwin, linux, win32).

Who created Auto Estimate Generator?

It is built and maintained by datadrivenconstruction (@datadrivenconstruction); the current version is v2.1.0.

💬 Comments