{
  "schemaVersion": "1.0",
  "item": {
    "slug": "auto-estimate-generator",
    "name": "Auto Estimate Generator",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/datadrivenconstruction/auto-estimate-generator",
    "canonicalUrl": "https://clawhub.ai/datadrivenconstruction/auto-estimate-generator",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/auto-estimate-generator",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=auto-estimate-generator",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "claw.json",
      "instructions.md",
      "SKILL.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-23T16:43:11.935Z",
      "expiresAt": "2026-04-30T16:43:11.935Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=4claw-imageboard",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=4claw-imageboard",
        "contentDisposition": "attachment; filename=\"4claw-imageboard-1.0.1.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/auto-estimate-generator"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/auto-estimate-generator",
    "agentPageUrl": "https://openagent3.xyz/skills/auto-estimate-generator/agent",
    "manifestUrl": "https://openagent3.xyz/skills/auto-estimate-generator/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/auto-estimate-generator/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Problem Statement",
        "body": "Manual estimate creation challenges:\n\nTime-consuming quantity mapping\nInconsistent pricing rules\nErrors in calculations\nDifficulty updating estimates"
      },
      {
        "title": "Solution",
        "body": "Automated estimate generation from BIM/QTO data using configurable pricing rules and assembly mappings."
      },
      {
        "title": "Technical Implementation",
        "body": "import pandas as pd\nfrom typing import Dict, Any, List, Optional, Callable\nfrom dataclasses import dataclass, field\nfrom enum import Enum\n\n\nclass ElementType(Enum):\n    WALL = \"wall\"\n    FLOOR = \"floor\"\n    CEILING = \"ceiling\"\n    DOOR = \"door\"\n    WINDOW = \"window\"\n    COLUMN = \"column\"\n    BEAM = \"beam\"\n    FOUNDATION = \"foundation\"\n    ROOF = \"roof\"\n    STAIR = \"stair\"\n    MEP = \"mep\"\n\n\n@dataclass\nclass QTOItem:\n    element_id: str\n    element_type: ElementType\n    name: str\n    quantity: float\n    unit: str\n    properties: Dict[str, Any] = field(default_factory=dict)\n\n\n@dataclass\nclass PricingRule:\n    rule_id: str\n    name: str\n    element_type: ElementType\n    conditions: Dict[str, Any] = field(default_factory=dict)\n    unit_cost: float = 0\n    assembly_code: str = \"\"\n    cost_breakdown: Dict[str, float] = field(default_factory=dict)\n\n\n@dataclass\nclass EstimateItem:\n    qto_element_id: str\n    description: str\n    quantity: float\n    unit: str\n    unit_cost: float\n    total_cost: float\n    rule_applied: str\n    wbs_code: str = \"\"\n\n\nclass AutoEstimateGenerator:\n    \"\"\"Generate estimates from QTO data automatically.\"\"\"\n\n    def __init__(self, project_name: str):\n        self.project_name = project_name\n        self.pricing_rules: List[PricingRule] = []\n        self.qto_items: List[QTOItem] = []\n        self.estimate_items: List[EstimateItem] = []\n        self.unmapped_items: List[QTOItem] = []\n\n    def add_pricing_rule(self, rule: PricingRule):\n        \"\"\"Add pricing rule.\"\"\"\n        self.pricing_rules.append(rule)\n\n    def load_pricing_rules_from_df(self, df: pd.DataFrame):\n        \"\"\"Load pricing rules from DataFrame.\"\"\"\n\n        for _, row in df.iterrows():\n            conditions = {}\n            if 'material' in row:\n                conditions['material'] = row['material']\n            if 'thickness_min' in row:\n                conditions['thickness_min'] = row['thickness_min']\n            if 'thickness_max' in row:\n                conditions['thickness_max'] = row['thickness_max']\n\n            rule = PricingRule(\n                rule_id=row['rule_id'],\n                name=row['name'],\n                element_type=ElementType(row['element_type'].lower()),\n                conditions=conditions,\n                unit_cost=float(row['unit_cost']),\n                assembly_code=row.get('assembly_code', ''),\n                cost_breakdown={\n                    'labor': float(row.get('labor_pct', 0.4)),\n                    'material': float(row.get('material_pct', 0.5)),\n                    'equipment': float(row.get('equipment_pct', 0.1))\n                }\n            )\n            self.add_pricing_rule(rule)\n\n    def load_qto_from_df(self, df: pd.DataFrame):\n        \"\"\"Load QTO items from DataFrame.\"\"\"\n\n        for _, row in df.iterrows():\n            properties = {}\n            for col in df.columns:\n                if col not in ['element_id', 'element_type', 'name', 'quantity', 'unit']:\n                    properties[col] = row[col]\n\n            qto = QTOItem(\n                element_id=str(row['element_id']),\n                element_type=ElementType(row['element_type'].lower()),\n                name=row['name'],\n                quantity=float(row['quantity']),\n                unit=row['unit'],\n                properties=properties\n            )\n            self.qto_items.append(qto)\n\n    def find_matching_rule(self, qto_item: QTOItem) -> Optional[PricingRule]:\n        \"\"\"Find pricing rule that matches QTO item.\"\"\"\n\n        matching_rules = []\n\n        for rule in self.pricing_rules:\n            if rule.element_type != qto_item.element_type:\n                continue\n\n            # Check conditions\n            match = True\n            for key, value in rule.conditions.items():\n                if key.endswith('_min'):\n                    prop_name = key[:-4]\n                    if prop_name in qto_item.properties:\n                        if qto_item.properties[prop_name] < value:\n                            match = False\n                elif key.endswith('_max'):\n                    prop_name = key[:-4]\n                    if prop_name in qto_item.properties:\n                        if qto_item.properties[prop_name] > value:\n                            match = False\n                else:\n                    if key in qto_item.properties:\n                        if qto_item.properties[key] != value:\n                            match = False\n\n            if match:\n                matching_rules.append(rule)\n\n        # Return most specific rule (most conditions)\n        if matching_rules:\n            return max(matching_rules, key=lambda r: len(r.conditions))\n        return None\n\n    def generate_estimate(self) -> Dict[str, Any]:\n        \"\"\"Generate estimate from QTO items.\"\"\"\n\n        self.estimate_items = []\n        self.unmapped_items = []\n        total_cost = 0\n\n        for qto in self.qto_items:\n            rule = self.find_matching_rule(qto)\n\n            if rule:\n                item_cost = qto.quantity * rule.unit_cost\n\n                self.estimate_items.append(EstimateItem(\n                    qto_element_id=qto.element_id,\n                    description=f\"{qto.name} ({rule.name})\",\n                    quantity=qto.quantity,\n                    unit=qto.unit,\n                    unit_cost=rule.unit_cost,\n                    total_cost=round(item_cost, 2),\n                    rule_applied=rule.rule_id,\n                    wbs_code=rule.assembly_code\n                ))\n                total_cost += item_cost\n            else:\n                self.unmapped_items.append(qto)\n\n        return {\n            'project': self.project_name,\n            'total_qto_items': len(self.qto_items),\n            'mapped_items': len(self.estimate_items),\n            'unmapped_items': len(self.unmapped_items),\n            'mapping_rate': round(len(self.estimate_items) / len(self.qto_items) * 100, 1) if self.qto_items else 0,\n            'total_cost': round(total_cost, 2),\n            'items': self.estimate_items\n        }\n\n    def get_cost_by_element_type(self) -> Dict[str, float]:\n        \"\"\"Get cost breakdown by element type.\"\"\"\n\n        by_type = {}\n        for qto in self.qto_items:\n            for est_item in self.estimate_items:\n                if est_item.qto_element_id == qto.element_id:\n                    type_name = qto.element_type.value\n                    by_type[type_name] = by_type.get(type_name, 0) + est_item.total_cost\n\n        return {k: round(v, 2) for k, v in by_type.items()}\n\n    def get_unmapped_summary(self) -> pd.DataFrame:\n        \"\"\"Get summary of unmapped items.\"\"\"\n\n        if not self.unmapped_items:\n            return pd.DataFrame()\n\n        data = []\n        for item in self.unmapped_items:\n            data.append({\n                'Element ID': item.element_id,\n                'Type': item.element_type.value,\n                'Name': item.name,\n                'Quantity': item.quantity,\n                'Unit': item.unit,\n                'Properties': str(item.properties)\n            })\n\n        return pd.DataFrame(data)\n\n    def export_to_excel(self, output_path: str) -> str:\n        \"\"\"Export estimate to Excel.\"\"\"\n\n        result = self.generate_estimate()\n\n        with pd.ExcelWriter(output_path, engine='openpyxl') as writer:\n            # Summary\n            summary_df = pd.DataFrame([{\n                'Project': self.project_name,\n                'Total QTO Items': result['total_qto_items'],\n                'Mapped Items': result['mapped_items'],\n                'Unmapped Items': result['unmapped_items'],\n                'Mapping Rate %': result['mapping_rate'],\n                'Total Cost': result['total_cost']\n            }])\n            summary_df.to_excel(writer, sheet_name='Summary', index=False)\n\n            # Estimate items\n            items_df = pd.DataFrame([{\n                'Element ID': item.qto_element_id,\n                'Description': item.description,\n                'Quantity': item.quantity,\n                'Unit': item.unit,\n                'Unit Cost': item.unit_cost,\n                'Total Cost': item.total_cost,\n                'WBS': item.wbs_code,\n                'Rule': item.rule_applied\n            } for item in self.estimate_items])\n            items_df.to_excel(writer, sheet_name='Estimate', index=False)\n\n            # By element type\n            by_type_df = pd.DataFrame([\n                {'Element Type': k, 'Cost': v}\n                for k, v in self.get_cost_by_element_type().items()\n            ])\n            by_type_df.to_excel(writer, sheet_name='By Type', index=False)\n\n            # Unmapped items\n            unmapped_df = self.get_unmapped_summary()\n            if not unmapped_df.empty:\n                unmapped_df.to_excel(writer, sheet_name='Unmapped', index=False)\n\n        return output_path\n\n    def suggest_missing_rules(self) -> List[Dict[str, Any]]:\n        \"\"\"Suggest pricing rules for unmapped items.\"\"\"\n\n        suggestions = []\n        seen_types = set()\n\n        for item in self.unmapped_items:\n            key = (item.element_type.value, str(item.properties))\n            if key not in seen_types:\n                seen_types.add(key)\n                suggestions.append({\n                    'element_type': item.element_type.value,\n                    'sample_name': item.name,\n                    'properties': item.properties,\n                    'count': sum(1 for i in self.unmapped_items\n                                if i.element_type == item.element_type\n                                and str(i.properties) == str(item.properties))\n                })\n\n        return sorted(suggestions, key=lambda x: x['count'], reverse=True)"
      },
      {
        "title": "Quick Start",
        "body": "# Initialize generator\ngenerator = AutoEstimateGenerator(\"Office Building A\")\n\n# Add pricing rules\ngenerator.add_pricing_rule(PricingRule(\n    rule_id=\"W-001\",\n    name=\"Interior Wall - Drywall\",\n    element_type=ElementType.WALL,\n    conditions={\"material\": \"Drywall\"},\n    unit_cost=45.00,\n    assembly_code=\"09.29.10\"\n))\n\ngenerator.add_pricing_rule(PricingRule(\n    rule_id=\"W-002\",\n    name=\"Exterior Wall - Masonry\",\n    element_type=ElementType.WALL,\n    conditions={\"material\": \"Masonry\"},\n    unit_cost=125.00,\n    assembly_code=\"04.21.13\"\n))\n\n# Load QTO data\ngenerator.qto_items = [\n    QTOItem(\"W-001\", ElementType.WALL, \"Interior Wall L1\", 500, \"SF\", {\"material\": \"Drywall\"}),\n    QTOItem(\"W-002\", ElementType.WALL, \"Exterior Wall\", 1200, \"SF\", {\"material\": \"Masonry\"})\n]\n\n# Generate estimate\nresult = generator.generate_estimate()\nprint(f\"Total Cost: ${result['total_cost']:,.2f}\")\nprint(f\"Mapping Rate: {result['mapping_rate']}%\")"
      },
      {
        "title": "1. Cost by Element Type",
        "body": "by_type = generator.get_cost_by_element_type()\nfor element_type, cost in by_type.items():\n    print(f\"{element_type}: ${cost:,.2f}\")"
      },
      {
        "title": "2. Unmapped Items",
        "body": "unmapped = generator.get_unmapped_summary()\nprint(unmapped)"
      },
      {
        "title": "3. Rule Suggestions",
        "body": "suggestions = generator.suggest_missing_rules()\nfor s in suggestions:\n    print(f\"Need rule for: {s['element_type']} ({s['count']} items)\")"
      },
      {
        "title": "Resources",
        "body": "DDC Book: Chapter 3.2 - QTO and Automated Estimates\nWebsite: https://datadrivenconstruction.io"
      }
    ],
    "body": "Auto Estimate Generator\nBusiness Case\nProblem Statement\n\nManual estimate creation challenges:\n\nTime-consuming quantity mapping\nInconsistent pricing rules\nErrors in calculations\nDifficulty updating estimates\nSolution\n\nAutomated estimate generation from BIM/QTO data using configurable pricing rules and assembly mappings.\n\nTechnical Implementation\nimport pandas as pd\nfrom typing import Dict, Any, List, Optional, Callable\nfrom dataclasses import dataclass, field\nfrom enum import Enum\n\n\nclass ElementType(Enum):\n    WALL = \"wall\"\n    FLOOR = \"floor\"\n    CEILING = \"ceiling\"\n    DOOR = \"door\"\n    WINDOW = \"window\"\n    COLUMN = \"column\"\n    BEAM = \"beam\"\n    FOUNDATION = \"foundation\"\n    ROOF = \"roof\"\n    STAIR = \"stair\"\n    MEP = \"mep\"\n\n\n@dataclass\nclass QTOItem:\n    element_id: str\n    element_type: ElementType\n    name: str\n    quantity: float\n    unit: str\n    properties: Dict[str, Any] = field(default_factory=dict)\n\n\n@dataclass\nclass PricingRule:\n    rule_id: str\n    name: str\n    element_type: ElementType\n    conditions: Dict[str, Any] = field(default_factory=dict)\n    unit_cost: float = 0\n    assembly_code: str = \"\"\n    cost_breakdown: Dict[str, float] = field(default_factory=dict)\n\n\n@dataclass\nclass EstimateItem:\n    qto_element_id: str\n    description: str\n    quantity: float\n    unit: str\n    unit_cost: float\n    total_cost: float\n    rule_applied: str\n    wbs_code: str = \"\"\n\n\nclass AutoEstimateGenerator:\n    \"\"\"Generate estimates from QTO data automatically.\"\"\"\n\n    def __init__(self, project_name: str):\n        self.project_name = project_name\n        self.pricing_rules: List[PricingRule] = []\n        self.qto_items: List[QTOItem] = []\n        self.estimate_items: List[EstimateItem] = []\n        self.unmapped_items: List[QTOItem] = []\n\n    def add_pricing_rule(self, rule: PricingRule):\n        \"\"\"Add pricing rule.\"\"\"\n        self.pricing_rules.append(rule)\n\n    def load_pricing_rules_from_df(self, df: pd.DataFrame):\n        \"\"\"Load pricing rules from DataFrame.\"\"\"\n\n        for _, row in df.iterrows():\n            conditions = {}\n            if 'material' in row:\n                conditions['material'] = row['material']\n            if 'thickness_min' in row:\n                conditions['thickness_min'] = row['thickness_min']\n            if 'thickness_max' in row:\n                conditions['thickness_max'] = row['thickness_max']\n\n            rule = PricingRule(\n                rule_id=row['rule_id'],\n                name=row['name'],\n                element_type=ElementType(row['element_type'].lower()),\n                conditions=conditions,\n                unit_cost=float(row['unit_cost']),\n                assembly_code=row.get('assembly_code', ''),\n                cost_breakdown={\n                    'labor': float(row.get('labor_pct', 0.4)),\n                    'material': float(row.get('material_pct', 0.5)),\n                    'equipment': float(row.get('equipment_pct', 0.1))\n                }\n            )\n            self.add_pricing_rule(rule)\n\n    def load_qto_from_df(self, df: pd.DataFrame):\n        \"\"\"Load QTO items from DataFrame.\"\"\"\n\n        for _, row in df.iterrows():\n            properties = {}\n            for col in df.columns:\n                if col not in ['element_id', 'element_type', 'name', 'quantity', 'unit']:\n                    properties[col] = row[col]\n\n            qto = QTOItem(\n                element_id=str(row['element_id']),\n                element_type=ElementType(row['element_type'].lower()),\n                name=row['name'],\n                quantity=float(row['quantity']),\n                unit=row['unit'],\n                properties=properties\n            )\n            self.qto_items.append(qto)\n\n    def find_matching_rule(self, qto_item: QTOItem) -> Optional[PricingRule]:\n        \"\"\"Find pricing rule that matches QTO item.\"\"\"\n\n        matching_rules = []\n\n        for rule in self.pricing_rules:\n            if rule.element_type != qto_item.element_type:\n                continue\n\n            # Check conditions\n            match = True\n            for key, value in rule.conditions.items():\n                if key.endswith('_min'):\n                    prop_name = key[:-4]\n                    if prop_name in qto_item.properties:\n                        if qto_item.properties[prop_name] < value:\n                            match = False\n                elif key.endswith('_max'):\n                    prop_name = key[:-4]\n                    if prop_name in qto_item.properties:\n                        if qto_item.properties[prop_name] > value:\n                            match = False\n                else:\n                    if key in qto_item.properties:\n                        if qto_item.properties[key] != value:\n                            match = False\n\n            if match:\n                matching_rules.append(rule)\n\n        # Return most specific rule (most conditions)\n        if matching_rules:\n            return max(matching_rules, key=lambda r: len(r.conditions))\n        return None\n\n    def generate_estimate(self) -> Dict[str, Any]:\n        \"\"\"Generate estimate from QTO items.\"\"\"\n\n        self.estimate_items = []\n        self.unmapped_items = []\n        total_cost = 0\n\n        for qto in self.qto_items:\n            rule = self.find_matching_rule(qto)\n\n            if rule:\n                item_cost = qto.quantity * rule.unit_cost\n\n                self.estimate_items.append(EstimateItem(\n                    qto_element_id=qto.element_id,\n                    description=f\"{qto.name} ({rule.name})\",\n                    quantity=qto.quantity,\n                    unit=qto.unit,\n                    unit_cost=rule.unit_cost,\n                    total_cost=round(item_cost, 2),\n                    rule_applied=rule.rule_id,\n                    wbs_code=rule.assembly_code\n                ))\n                total_cost += item_cost\n            else:\n                self.unmapped_items.append(qto)\n\n        return {\n            'project': self.project_name,\n            'total_qto_items': len(self.qto_items),\n            'mapped_items': len(self.estimate_items),\n            'unmapped_items': len(self.unmapped_items),\n            'mapping_rate': round(len(self.estimate_items) / len(self.qto_items) * 100, 1) if self.qto_items else 0,\n            'total_cost': round(total_cost, 2),\n            'items': self.estimate_items\n        }\n\n    def get_cost_by_element_type(self) -> Dict[str, float]:\n        \"\"\"Get cost breakdown by element type.\"\"\"\n\n        by_type = {}\n        for qto in self.qto_items:\n            for est_item in self.estimate_items:\n                if est_item.qto_element_id == qto.element_id:\n                    type_name = qto.element_type.value\n                    by_type[type_name] = by_type.get(type_name, 0) + est_item.total_cost\n\n        return {k: round(v, 2) for k, v in by_type.items()}\n\n    def get_unmapped_summary(self) -> pd.DataFrame:\n        \"\"\"Get summary of unmapped items.\"\"\"\n\n        if not self.unmapped_items:\n            return pd.DataFrame()\n\n        data = []\n        for item in self.unmapped_items:\n            data.append({\n                'Element ID': item.element_id,\n                'Type': item.element_type.value,\n                'Name': item.name,\n                'Quantity': item.quantity,\n                'Unit': item.unit,\n                'Properties': str(item.properties)\n            })\n\n        return pd.DataFrame(data)\n\n    def export_to_excel(self, output_path: str) -> str:\n        \"\"\"Export estimate to Excel.\"\"\"\n\n        result = self.generate_estimate()\n\n        with pd.ExcelWriter(output_path, engine='openpyxl') as writer:\n            # Summary\n            summary_df = pd.DataFrame([{\n                'Project': self.project_name,\n                'Total QTO Items': result['total_qto_items'],\n                'Mapped Items': result['mapped_items'],\n                'Unmapped Items': result['unmapped_items'],\n                'Mapping Rate %': result['mapping_rate'],\n                'Total Cost': result['total_cost']\n            }])\n            summary_df.to_excel(writer, sheet_name='Summary', index=False)\n\n            # Estimate items\n            items_df = pd.DataFrame([{\n                'Element ID': item.qto_element_id,\n                'Description': item.description,\n                'Quantity': item.quantity,\n                'Unit': item.unit,\n                'Unit Cost': item.unit_cost,\n                'Total Cost': item.total_cost,\n                'WBS': item.wbs_code,\n                'Rule': item.rule_applied\n            } for item in self.estimate_items])\n            items_df.to_excel(writer, sheet_name='Estimate', index=False)\n\n            # By element type\n            by_type_df = pd.DataFrame([\n                {'Element Type': k, 'Cost': v}\n                for k, v in self.get_cost_by_element_type().items()\n            ])\n            by_type_df.to_excel(writer, sheet_name='By Type', index=False)\n\n            # Unmapped items\n            unmapped_df = self.get_unmapped_summary()\n            if not unmapped_df.empty:\n                unmapped_df.to_excel(writer, sheet_name='Unmapped', index=False)\n\n        return output_path\n\n    def suggest_missing_rules(self) -> List[Dict[str, Any]]:\n        \"\"\"Suggest pricing rules for unmapped items.\"\"\"\n\n        suggestions = []\n        seen_types = set()\n\n        for item in self.unmapped_items:\n            key = (item.element_type.value, str(item.properties))\n            if key not in seen_types:\n                seen_types.add(key)\n                suggestions.append({\n                    'element_type': item.element_type.value,\n                    'sample_name': item.name,\n                    'properties': item.properties,\n                    'count': sum(1 for i in self.unmapped_items\n                                if i.element_type == item.element_type\n                                and str(i.properties) == str(item.properties))\n                })\n\n        return sorted(suggestions, key=lambda x: x['count'], reverse=True)\n\nQuick Start\n# Initialize generator\ngenerator = AutoEstimateGenerator(\"Office Building A\")\n\n# Add pricing rules\ngenerator.add_pricing_rule(PricingRule(\n    rule_id=\"W-001\",\n    name=\"Interior Wall - Drywall\",\n    element_type=ElementType.WALL,\n    conditions={\"material\": \"Drywall\"},\n    unit_cost=45.00,\n    assembly_code=\"09.29.10\"\n))\n\ngenerator.add_pricing_rule(PricingRule(\n    rule_id=\"W-002\",\n    name=\"Exterior Wall - Masonry\",\n    element_type=ElementType.WALL,\n    conditions={\"material\": \"Masonry\"},\n    unit_cost=125.00,\n    assembly_code=\"04.21.13\"\n))\n\n# Load QTO data\ngenerator.qto_items = [\n    QTOItem(\"W-001\", ElementType.WALL, \"Interior Wall L1\", 500, \"SF\", {\"material\": \"Drywall\"}),\n    QTOItem(\"W-002\", ElementType.WALL, \"Exterior Wall\", 1200, \"SF\", {\"material\": \"Masonry\"})\n]\n\n# Generate estimate\nresult = generator.generate_estimate()\nprint(f\"Total Cost: ${result['total_cost']:,.2f}\")\nprint(f\"Mapping Rate: {result['mapping_rate']}%\")\n\nCommon Use Cases\n1. Cost by Element Type\nby_type = generator.get_cost_by_element_type()\nfor element_type, cost in by_type.items():\n    print(f\"{element_type}: ${cost:,.2f}\")\n\n2. Unmapped Items\nunmapped = generator.get_unmapped_summary()\nprint(unmapped)\n\n3. Rule Suggestions\nsuggestions = generator.suggest_missing_rules()\nfor s in suggestions:\n    print(f\"Need rule for: {s['element_type']} ({s['count']} items)\")\n\nResources\nDDC Book: Chapter 3.2 - QTO and Automated Estimates\nWebsite: https://datadrivenconstruction.io"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/datadrivenconstruction/auto-estimate-generator",
    "publisherUrl": "https://clawhub.ai/datadrivenconstruction/auto-estimate-generator",
    "owner": "datadrivenconstruction",
    "version": "2.1.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/auto-estimate-generator",
    "downloadUrl": "https://openagent3.xyz/downloads/auto-estimate-generator",
    "agentUrl": "https://openagent3.xyz/skills/auto-estimate-generator/agent",
    "manifestUrl": "https://openagent3.xyz/skills/auto-estimate-generator/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/auto-estimate-generator/agent.md"
  }
}