技能详情(站内镜像,无评论)
许可证:MIT-0
MIT-0 ·免费使用、修改和重新分发。无需归因。
版本:v2.1.0
统计:⭐ 0 · 1.1k · 1 current installs · 1 all-time installs
⭐ 0
安装量(当前) 1
🛡 VirusTotal :可疑 · OpenClaw :可疑
Package:datadrivenconstruction/auto-estimate-generator
安全扫描(ClawHub)
- VirusTotal :可疑
- OpenClaw :可疑
OpenClaw 评估
The skill's stated purpose (generating estimates from QTO/BIM data) aligns with its instructions, but there are inconsistencies and missing details (notably undeclared Python dependencies and a broad filesystem permission) that warrant caution before installation.
目的
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.
说明范围
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.
安装机制
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.
证书
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.
持久
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.
安装(复制给龙虾 AI)
将下方整段复制到龙虾中文库对话中,由龙虾按 SKILL.md 完成安装。
请把本段交给龙虾中文库(龙虾 AI)执行:为本机安装 OpenClaw 技能「Auto Estimate Generator」。简介:Automatically generate estimates from QTO data. Apply pricing rules to BIM quan…。
请 fetch 以下地址读取 SKILL.md 并按文档完成安装:https://raw.githubusercontent.com/openclaw/skills/refs/heads/main/skills/datadrivenconstruction/auto-estimate-generator/SKILL.md
(来源:yingzhi8.cn 技能库)
SKILL.md
---
name: "auto-estimate-generator"
description: "Automatically generate estimates from QTO data. Apply pricing rules to BIM quantities for cost estimates."
homepage: "https://datadrivenconstruction.io"
metadata: {"openclaw":{"emoji":"⚡","os":["darwin","linux","win32"],"homepage":"https://datadrivenconstruction.io","requires":{"bins":["python3"]}}}
---
# Auto Estimate Generator
## Business Case
### Problem Statement
Manual estimate creation challenges:
- Time-consuming quantity mapping
- Inconsistent pricing rules
- Errors in calculations
- Difficulty updating estimates
### Solution
Automated estimate generation from BIM/QTO data using configurable pricing rules and assembly mappings.
## Technical Implementation
```python
import pandas as pd
from typing import Dict, Any, List, Optional, Callable
from dataclasses import dataclass, field
from enum import Enum
class ElementType(Enum):
WALL = "wall"
FLOOR = "floor"
CEILING = "ceiling"
DOOR = "door"
WINDOW = "window"
COLUMN = "column"
BEAM = "beam"
FOUNDATION = "foundation"
ROOF = "roof"
STAIR = "stair"
MEP = "mep"
@dataclass
class QTOItem:
element_id: str
element_type: ElementType
name: str
quantity: float
unit: str
properties: Dict[str, Any] = field(default_factory=dict)
@dataclass
class PricingRule:
rule_id: str
name: str
element_type: ElementType
conditions: Dict[str, Any] = field(default_factory=dict)
unit_cost: float = 0
assembly_code: str = ""
cost_breakdown: Dict[str, float] = field(default_factory=dict)
@dataclass
class EstimateItem:
qto_element_id: str
description: str
quantity: float
unit: str
unit_cost: float
total_cost: float
rule_applied: str
wbs_code: str = ""
class AutoEstimateGenerator:
"""Generate estimates from QTO data automatically."""
def __init__(self, project_name: str):
self.project_name = project_name
self.pricing_rules: List[PricingRule] = []
self.qto_items: List[QTOItem] = []
self.estimate_items: List[EstimateItem] = []
self.unmapped_items: List[QTOItem] = []
def add_pricing_rule(self, rule: PricingRule):
"""Add pricing rule."""
self.pricing_rules.append(rule)
def load_pricing_rules_from_df(self, df: pd.DataFrame):
"""Load pricing rules from DataFrame."""
for _, row in df.iterrows():
conditions = {}
if 'material' in row:
conditions['material'] = row['material']
if 'thickness_min' in row:
conditions['thickness_min'] = row['thickness_min']
if 'thickness_max' in row:
conditions['thickness_max'] = row['thickness_max']
rule = PricingRule(
rule_id=row['rule_id'],
name=row['name'],
element_type=ElementType(row['element_type'].lower()),
conditions=conditions,
unit_cost=float(row['unit_cost']),
assembly_code=row.get('assembly_code', ''),
cost_breakdown={
'labor': float(row.get('labor_pct', 0.4)),
'material': float(row.get('material_pct', 0.5)),
'equipment': float(row.get('equipment_pct', 0.1))
}
)
self.add_pricing_rule(rule)
def load_qto_from_df(self, df: pd.DataFrame):
"""Load QTO items from DataFrame."""
for _, row in df.iterrows():
properties = {}
for col in df.columns:
if col not in ['element_id', 'element_type', 'name', 'quantity', 'unit']:
properties[col] = row[col]
qto = QTOItem(
element_id=str(row['element_id']),
element_type=ElementType(row['element_type'].lower()),
name=row['name'],
quantity=float(row['quantity']),
unit=row['unit'],
properties=properties
)
self.qto_items.append(qto)
def find_matching_rule(self, qto_item: QTOItem) -> Optional[PricingRule]:
"""Find pricing rule that matches QTO item."""
matching_rules = []
for rule in self.pricing_rules:
if rule.element_type != qto_item.element_type:
continue
# Check conditions
match = True
for key, value in rule.conditions.items():
if key.endswith('_min'):
prop_name = key[:-4]
if prop_name in qto_item.properties:
if qto_item.properties[prop_name] < value:
match = False
elif key.endswith('_max'):
prop_name = key[:-4]
if prop_name in qto_item.properties:
if qto_item.properties[prop_name] > value:
match = False
else:
if key in qto_item.properties:
if qto_item.properties[key] != value:
match = False
if match:
matching_rules.append(rule)
# Return most specific rule (most conditions)
if matching_rules:
return max(matching_rules, key=lambda r: len(r.conditions))
return None
def generate_estimate(self) -> Dict[str, Any]:
"""Generate estimate from QTO items."""
self.estimate_items = []
self.unmapped_items = []
total_cost = 0
for qto in self.qto_items:
rule = self.find_matching_rule(qto)
if rule:
item_cost = qto.quantity * rule.unit_cost
self.estimate_items.append(EstimateItem(
qto_element_id=qto.element_id,
description=f"{qto.name} ({rule.name})",
quantity=qto.quantity,
unit=qto.unit,
unit_cost=rule.unit_cost,
total_cost=round(item_cost, 2),
rule_applied=rule.rule_id,
wbs_code=rule.assembly_code
))
total_cost += item_cost
else:
self.unmapped_items.append(qto)
return {
'project': self.project_name,
'total_qto_items': len(self.qto_items),
'mapped_items': len(self.estimate_items),
'unmapped_items': len(self.unmapped_items),
'mapping_rate': round(len(self.estimate_items) / len(self.qto_items) * 100, 1) if self.qto_items else 0,
'total_cost': round(total_cost, 2),
'items': self.estimate_items
}
def get_cost_by_element_type(self) -> Dict[str, float]:
"""Get cost breakdown by element type."""
by_type = {}
for qto in self.qto_items:
for est_item in self.estimate_items:
if est_item.qto_element_id == qto.element_id:
type_name = qto.element_type.value
by_type[type_name] = by_type.get(type_name, 0) + est_item.total_cost
return {k: round(v, 2) for k, v in by_type.items()}
def get_unmapped_summary(self) -> pd.DataFrame:
"""Get summary of unmapped items."""
if not self.unmapped_items:
return pd.DataFrame()
data = []
for item in self.unmapped_items:
data.append({
'Element ID': item.element_id,
'Type': item.element_type.value,
'Name': item.name,
'Quantity': item.quantity,
'Unit': item.unit,
'Properties': str(item.properties)
})
return pd.DataFrame(data)
def export_to_excel(self, output_path: str) -> str:
"""Export estimate to Excel."""
result = self.generate_estimate()
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
# Summary
summary_df = pd.DataFrame([{
'Project': self.project_name,
'Total QTO Items': result['total_qto_items'],
'Mapped Items': result['mapped_items'],
'Unmapped Items': result['unmapped_items'],
'Mapping Rate %': result['mapping_rate'],
'Total Cost': result['total_cost']
}])
summary_df.to_excel(writer, sheet_name='Summary', index=False)
# Estimate items
items_df = pd.DataFrame([{
'Element ID': item.qto_element_id,
'Description': item.description,
'Quantity': item.quantity,
'Unit': item.unit,
'Unit Cost': item.unit_cost,
'Total Cost': item.total_cost,
'WBS': item.wbs_code,
'Rule': item.rule_applied
} for item in self.estimate_items])
items_df.to_excel(writer, sheet_name='Estimate', index=False)
# By element type
by_type_df = pd.DataFrame([
{'Element Type': k, 'Cost': v}
for k, v in self.get_cost_by_element_type().items()
])
by_type_df.to_excel(writer, sheet_name='By Type', index=False)
# Unmapped items
unmapped_df = self.get_unmapped_summary()
if not unmapped_df.empty:
unmapped_df.to_excel(writer, sheet_name='Unmapped', index=False)
return output_path
def suggest_missing_rules(self) -> List[Dict[str, Any]]:
"""Suggest pricing rules for unmapped items."""
suggestions = []
seen_types = set()
for item in self.unmapped_items:
key = (item.element_type.value, str(item.properties))
if key not in seen_types:
seen_types.add(key)
suggestions.append({
'element_type': item.element_type.value,
'sample_name': item.name,
'properties': item.properties,
'count': sum(1 for i in self.unmapped_items
if i.element_type == item.element_type
and str(i.properties) == str(item.properties))
})
return sorted(suggestions, key=lambda x: x['count'], reverse=True)
```
## Quick Start
```python
# Initialize generator
generator = AutoEstimateGenerator("Office Building A")
# Add pricing rules
generator.add_pricing_rule(PricingRule(
rule_id="W-001",
name="Interior Wall - Drywall",
element_type=ElementType.WALL,
conditions={"material": "Drywall"},
unit_cost=45.00,
assembly_code="09.29.10"
))
generator.add_pricing_rule(PricingRule(
rule_id="W-002",
name="Exterior Wall - Masonry",
element_type=ElementType.WALL,
conditions={"material": "Masonry"},
unit_cost=125.00,
assembly_code="04.21.13"
))
# Load QTO data
generator.qto_items = [
QTOItem("W-001", ElementType.WALL, "Interior Wall L1", 500, "SF", {"material": "Drywall"}),
QTOItem("W-002", ElementType.WALL, "Exterior Wall", 1200, "SF", {"material": "Masonry"})
]
# Generate estimate
result = generator.generate_estimate()
print(f"Total Cost: ${result['total_cost']:,.2f}")
print(f"Mapping Rate: {result['mapping_rate']}%")
```
## Common Use Cases
### 1. Cost by Element Type
```python
by_type = generator.get_cost_by_element_type()
for element_type, cost in by_type.items():
print(f"{element_type}: ${cost:,.2f}")
```
### 2. Unmapped Items
```python
unmapped = generator.get_unmapped_summary()
print(unmapped)
```
### 3. Rule Suggestions
```python
suggestions = generator.suggest_missing_rules()
for s in suggestions:
print(f"Need rule for: {s['element_type']} ({s['count']} items)")
```
## Resources
- **DDC Book**: Chapter 3.2 - QTO and Automated Estimates
- **Website**: https://datadrivenconstruction.io