{
  "schemaVersion": "1.0",
  "item": {
    "slug": "open-construction-estimate",
    "name": "Open Construction Estimate",
    "source": "tencent",
    "type": "skill",
    "category": "数据分析",
    "sourceUrl": "https://clawhub.ai/datadrivenconstruction/open-construction-estimate",
    "canonicalUrl": "https://clawhub.ai/datadrivenconstruction/open-construction-estimate",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/open-construction-estimate",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=open-construction-estimate",
    "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/open-construction-estimate"
    },
    "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/open-construction-estimate",
    "agentPageUrl": "https://openagent3.xyz/skills/open-construction-estimate/agent",
    "manifestUrl": "https://openagent3.xyz/skills/open-construction-estimate/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/open-construction-estimate/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": "Overview",
        "body": "This skill leverages open construction pricing databases for automated cost estimation. Match project elements to standardized work items and calculate costs using publicly available unit prices.\n\nData Sources:\n\nOpenConstructionEstimate (55,000+ work items)\nRSMeans Online (subscription)\nGovernment pricing databases\nRegional cost indexes\n\n\"Открытые базы данных расценок содержат более 55,000 позиций работ, что позволяет автоматизировать сметные расчеты для большинства проектов.\"\n— DDC LinkedIn"
      },
      {
        "title": "Quick Start",
        "body": "import pandas as pd\nfrom sklearn.feature_extraction.text import TfidfVectorizer\nfrom sklearn.metrics.pairwise import cosine_similarity\n\n# Load work items database\nwork_items = pd.read_csv(\"open_construction_estimate.csv\")\nprint(f\"Loaded {len(work_items)} work items\")\n\n# Simple matching function\nvectorizer = TfidfVectorizer(ngram_range=(1, 2))\nitem_vectors = vectorizer.fit_transform(work_items['description'])\n\ndef find_matching_items(query, top_n=5):\n    query_vec = vectorizer.transform([query])\n    similarities = cosine_similarity(query_vec, item_vectors)[0]\n    top_indices = similarities.argsort()[-top_n:][::-1]\n\n    return work_items.iloc[top_indices][['code', 'description', 'unit', 'unit_price']]\n\n# Find matches\nmatches = find_matching_items(\"reinforced concrete wall 300mm\")\nprint(matches)"
      },
      {
        "title": "Database Schema",
        "body": "# Standard work items database structure\nWORK_ITEMS_SCHEMA = {\n    'code': 'Work item code (e.g., 03.31.13.13)',\n    'description': 'Full description of work',\n    'short_description': 'Abbreviated description',\n    'unit': 'Unit of measure (m³, m², ton, pcs)',\n    'unit_price': 'Base unit price',\n    'labor_cost': 'Labor component per unit',\n    'material_cost': 'Material component per unit',\n    'equipment_cost': 'Equipment component per unit',\n    'labor_hours': 'Labor hours per unit',\n    'crew_size': 'Typical crew size',\n    'productivity': 'Units per day',\n    'category_l1': 'Primary category (CSI Division)',\n    'category_l2': 'Secondary category',\n    'category_l3': 'Detailed category',\n    'region': 'Geographic region',\n    'year': 'Price year',\n    'source': 'Data source'\n}\n\n# CSI MasterFormat Divisions\nCSI_DIVISIONS = {\n    '03': 'Concrete',\n    '04': 'Masonry',\n    '05': 'Metals',\n    '06': 'Wood, Plastics, Composites',\n    '07': 'Thermal and Moisture Protection',\n    '08': 'Openings',\n    '09': 'Finishes',\n    '10': 'Specialties',\n    '21': 'Fire Suppression',\n    '22': 'Plumbing',\n    '23': 'HVAC',\n    '26': 'Electrical',\n    '31': 'Earthwork',\n    '32': 'Exterior Improvements',\n    '33': 'Utilities'\n}"
      },
      {
        "title": "Semantic Matching System",
        "body": "import pandas as pd\nimport numpy as np\nfrom sklearn.feature_extraction.text import TfidfVectorizer\nfrom sklearn.metrics.pairwise import cosine_similarity\nfrom sentence_transformers import SentenceTransformer\nfrom typing import List, Dict, Optional, Tuple\nimport re\n\nclass WorkItemMatcher:\n    \"\"\"Match BIM elements to standardized work items\"\"\"\n\n    def __init__(self, database_path: str, use_embeddings: bool = True):\n        self.db = pd.read_csv(database_path)\n\n        # TF-IDF for fast initial filtering\n        self.tfidf = TfidfVectorizer(\n            ngram_range=(1, 3),\n            max_features=10000,\n            stop_words='english'\n        )\n        self.tfidf_matrix = self.tfidf.fit_transform(self.db['description'])\n\n        # Sentence embeddings for semantic matching\n        self.use_embeddings = use_embeddings\n        if use_embeddings:\n            self.embedder = SentenceTransformer('all-MiniLM-L6-v2')\n            self.embeddings = self.embedder.encode(\n                self.db['description'].tolist(),\n                show_progress_bar=True\n            )\n\n    def match(self, query: str, top_n: int = 5,\n              category: str = None) -> List[Dict]:\n        \"\"\"Find matching work items for a query\"\"\"\n        # Filter by category if specified\n        if category:\n            mask = self.db['category_l1'].str.contains(category, case=False, na=False)\n            search_db = self.db[mask]\n            search_matrix = self.tfidf_matrix[mask]\n        else:\n            search_db = self.db\n            search_matrix = self.tfidf_matrix\n\n        if self.use_embeddings:\n            return self._semantic_match(query, search_db, top_n)\n        else:\n            return self._tfidf_match(query, search_db, search_matrix, top_n)\n\n    def _tfidf_match(self, query: str, db: pd.DataFrame,\n                     matrix, top_n: int) -> List[Dict]:\n        \"\"\"TF-IDF based matching\"\"\"\n        query_vec = self.tfidf.transform([query])\n        similarities = cosine_similarity(query_vec, matrix)[0]\n\n        top_indices = similarities.argsort()[-top_n:][::-1]\n\n        results = []\n        for idx in top_indices:\n            row = db.iloc[idx]\n            results.append({\n                'code': row['code'],\n                'description': row['description'],\n                'unit': row['unit'],\n                'unit_price': row['unit_price'],\n                'similarity': float(similarities[idx]),\n                'category': row.get('category_l1', '')\n            })\n\n        return results\n\n    def _semantic_match(self, query: str, db: pd.DataFrame,\n                        top_n: int) -> List[Dict]:\n        \"\"\"Semantic embedding based matching\"\"\"\n        query_embedding = self.embedder.encode([query])\n\n        # Get indices for filtered db\n        indices = db.index.tolist()\n        filtered_embeddings = self.embeddings[indices]\n\n        similarities = cosine_similarity(query_embedding, filtered_embeddings)[0]\n        top_indices = similarities.argsort()[-top_n:][::-1]\n\n        results = []\n        for i, idx in enumerate(top_indices):\n            row = db.iloc[idx]\n            results.append({\n                'code': row['code'],\n                'description': row['description'],\n                'unit': row['unit'],\n                'unit_price': row['unit_price'],\n                'similarity': float(similarities[idx]),\n                'category': row.get('category_l1', '')\n            })\n\n        return results\n\n    def match_bim_element(self, element: Dict) -> List[Dict]:\n        \"\"\"Match a BIM element to work items\"\"\"\n        # Build query from element properties\n        query_parts = []\n\n        if element.get('material'):\n            query_parts.append(element['material'])\n        if element.get('category'):\n            query_parts.append(element['category'])\n        if element.get('description'):\n            query_parts.append(element['description'])\n\n        # Add dimensions if available\n        if element.get('thickness'):\n            query_parts.append(f\"{element['thickness']}mm thick\")\n        if element.get('height'):\n            query_parts.append(f\"{element['height']}m high\")\n\n        query = ' '.join(query_parts)\n\n        # Determine category from element type\n        category = self._get_category_from_element(element)\n\n        return self.match(query, top_n=3, category=category)\n\n    def _get_category_from_element(self, element: Dict) -> Optional[str]:\n        \"\"\"Map BIM element type to CSI category\"\"\"\n        element_mapping = {\n            'IfcWall': 'Concrete|Masonry',\n            'IfcSlab': 'Concrete',\n            'IfcColumn': 'Concrete|Metals',\n            'IfcBeam': 'Concrete|Metals',\n            'IfcDoor': 'Openings',\n            'IfcWindow': 'Openings',\n            'IfcRoof': 'Thermal',\n            'IfcStair': 'Concrete',\n            'IfcPipeSegment': 'Plumbing',\n            'IfcDuctSegment': 'HVAC'\n        }\n\n        elem_type = element.get('ifc_type', '')\n        return element_mapping.get(elem_type)"
      },
      {
        "title": "Automated Estimator",
        "body": "class OpenConstructionEstimator:\n    \"\"\"Generate cost estimates using open databases\"\"\"\n\n    def __init__(self, matcher: WorkItemMatcher, region: str = 'default'):\n        self.matcher = matcher\n        self.region = region\n        self.regional_factors = self._load_regional_factors()\n        self.estimates = []\n\n    def _load_regional_factors(self) -> Dict[str, float]:\n        \"\"\"Load regional cost adjustment factors\"\"\"\n        return {\n            'default': 1.0,\n            'northeast_us': 1.15,\n            'southeast_us': 0.92,\n            'midwest_us': 0.95,\n            'west_us': 1.08,\n            'moscow': 1.20,\n            'spb': 1.10,\n            'regions_ru': 0.85\n        }\n\n    def estimate_element(self, element: Dict) -> Dict:\n        \"\"\"Estimate cost for a single element\"\"\"\n        # Get matching work items\n        matches = self.matcher.match_bim_element(element)\n\n        if not matches:\n            return {\n                'element_id': element.get('id'),\n                'status': 'no_match',\n                'estimated_cost': 0\n            }\n\n        best_match = matches[0]\n        quantity = element.get('quantity', 1)\n        unit_price = best_match['unit_price']\n\n        # Apply regional factor\n        regional_factor = self.regional_factors.get(self.region, 1.0)\n        adjusted_price = unit_price * regional_factor\n\n        # Calculate total\n        total_cost = adjusted_price * quantity\n\n        estimate = {\n            'element_id': element.get('id'),\n            'element_type': element.get('ifc_type'),\n            'element_description': element.get('description', ''),\n            'matched_code': best_match['code'],\n            'matched_description': best_match['description'],\n            'match_confidence': best_match['similarity'],\n            'unit': best_match['unit'],\n            'quantity': quantity,\n            'unit_price': unit_price,\n            'regional_factor': regional_factor,\n            'adjusted_unit_price': adjusted_price,\n            'total_cost': total_cost\n        }\n\n        self.estimates.append(estimate)\n        return estimate\n\n    def estimate_project(self, elements: List[Dict]) -> Dict:\n        \"\"\"Estimate entire project\"\"\"\n        for element in elements:\n            self.estimate_element(element)\n\n        df = pd.DataFrame(self.estimates)\n\n        # Summary by category\n        if not df.empty:\n            summary = df.groupby('element_type').agg({\n                'total_cost': 'sum',\n                'element_id': 'count',\n                'match_confidence': 'mean'\n            }).rename(columns={'element_id': 'count'})\n        else:\n            summary = pd.DataFrame()\n\n        total = df['total_cost'].sum() if not df.empty else 0\n\n        return {\n            'total_cost': total,\n            'element_count': len(elements),\n            'matched_count': len(df[df['match_confidence'] > 0.5]) if not df.empty else 0,\n            'summary_by_type': summary.to_dict() if not summary.empty else {},\n            'details': self.estimates\n        }\n\n    def export_estimate(self, output_path: str) -> str:\n        \"\"\"Export estimate to Excel\"\"\"\n        df = pd.DataFrame(self.estimates)\n\n        with pd.ExcelWriter(output_path, engine='openpyxl') as writer:\n            # Summary\n            summary = pd.DataFrame({\n                'Metric': ['Total Cost', 'Elements', 'Matched', 'Avg Confidence'],\n                'Value': [\n                    df['total_cost'].sum() if not df.empty else 0,\n                    len(df),\n                    len(df[df['match_confidence'] > 0.5]) if not df.empty else 0,\n                    df['match_confidence'].mean() if not df.empty else 0\n                ]\n            })\n            summary.to_excel(writer, sheet_name='Summary', index=False)\n\n            # Details\n            if not df.empty:\n                df.to_excel(writer, sheet_name='Details', index=False)\n\n                # By type\n                by_type = df.groupby('element_type')['total_cost'].sum()\n                by_type.to_excel(writer, sheet_name='By_Type')\n\n        return output_path\n\n    def get_missing_items(self) -> List[Dict]:\n        \"\"\"Get elements that couldn't be matched\"\"\"\n        df = pd.DataFrame(self.estimates)\n        if df.empty:\n            return []\n\n        low_confidence = df[df['match_confidence'] < 0.5]\n        return low_confidence.to_dict('records')"
      },
      {
        "title": "Creating and Updating Database",
        "body": "class OpenDatabaseManager:\n    \"\"\"Manage open construction pricing database\"\"\"\n\n    def __init__(self, db_path: str):\n        self.db_path = db_path\n        self.db = self._load_or_create()\n\n    def _load_or_create(self) -> pd.DataFrame:\n        \"\"\"Load existing or create new database\"\"\"\n        try:\n            return pd.read_csv(self.db_path)\n        except FileNotFoundError:\n            return pd.DataFrame(columns=list(WORK_ITEMS_SCHEMA.keys()))\n\n    def add_items(self, items: List[Dict]):\n        \"\"\"Add new work items\"\"\"\n        new_df = pd.DataFrame(items)\n        self.db = pd.concat([self.db, new_df], ignore_index=True)\n        self.db.drop_duplicates(subset=['code'], keep='last', inplace=True)\n\n    def update_prices(self, updates: pd.DataFrame, year: int):\n        \"\"\"Update prices with new data\"\"\"\n        for _, row in updates.iterrows():\n            mask = self.db['code'] == row['code']\n            if mask.any():\n                self.db.loc[mask, 'unit_price'] = row['unit_price']\n                self.db.loc[mask, 'year'] = year\n\n    def apply_inflation(self, rate: float):\n        \"\"\"Apply inflation adjustment\"\"\"\n        self.db['unit_price'] = self.db['unit_price'] * (1 + rate)\n\n    def export_subset(self, category: str, output_path: str):\n        \"\"\"Export subset of database\"\"\"\n        subset = self.db[\n            self.db['category_l1'].str.contains(category, case=False, na=False)\n        ]\n        subset.to_csv(output_path, index=False)\n\n    def save(self):\n        \"\"\"Save database\"\"\"\n        self.db.to_csv(self.db_path, index=False)\n\n    def get_statistics(self) -> Dict:\n        \"\"\"Get database statistics\"\"\"\n        return {\n            'total_items': len(self.db),\n            'categories': self.db['category_l1'].nunique(),\n            'avg_price': self.db['unit_price'].mean(),\n            'price_range': (self.db['unit_price'].min(), self.db['unit_price'].max()),\n            'latest_year': self.db['year'].max() if 'year' in self.db else None\n        }"
      },
      {
        "title": "Quick Reference",
        "body": "CategoryCSI DivisionTypical ItemsConcrete03Walls, slabs, columns, beamsMasonry04Brick, block, stoneMetals05Structural steel, misc metalsFinishes09Drywall, paint, flooringMEP21-26Plumbing, HVAC, electricalSitework31-33Excavation, paving, utilities"
      },
      {
        "title": "Resources",
        "body": "OpenConstructionEstimate: Open database initiative\nCSI MasterFormat: https://www.csiresources.org/standards/masterformat\nDDC Website: https://datadrivenconstruction.io"
      },
      {
        "title": "Next Steps",
        "body": "See vector-search for semantic item matching\nSee cost-prediction for ML-based estimation\nSee qto-report for quantity extraction"
      }
    ],
    "body": "Open Construction Estimate\nOverview\n\nThis skill leverages open construction pricing databases for automated cost estimation. Match project elements to standardized work items and calculate costs using publicly available unit prices.\n\nData Sources:\n\nOpenConstructionEstimate (55,000+ work items)\nRSMeans Online (subscription)\nGovernment pricing databases\nRegional cost indexes\n\n\"Открытые базы данных расценок содержат более 55,000 позиций работ, что позволяет автоматизировать сметные расчеты для большинства проектов.\" — DDC LinkedIn\n\nQuick Start\nimport pandas as pd\nfrom sklearn.feature_extraction.text import TfidfVectorizer\nfrom sklearn.metrics.pairwise import cosine_similarity\n\n# Load work items database\nwork_items = pd.read_csv(\"open_construction_estimate.csv\")\nprint(f\"Loaded {len(work_items)} work items\")\n\n# Simple matching function\nvectorizer = TfidfVectorizer(ngram_range=(1, 2))\nitem_vectors = vectorizer.fit_transform(work_items['description'])\n\ndef find_matching_items(query, top_n=5):\n    query_vec = vectorizer.transform([query])\n    similarities = cosine_similarity(query_vec, item_vectors)[0]\n    top_indices = similarities.argsort()[-top_n:][::-1]\n\n    return work_items.iloc[top_indices][['code', 'description', 'unit', 'unit_price']]\n\n# Find matches\nmatches = find_matching_items(\"reinforced concrete wall 300mm\")\nprint(matches)\n\nOpen Database Structure\nDatabase Schema\n# Standard work items database structure\nWORK_ITEMS_SCHEMA = {\n    'code': 'Work item code (e.g., 03.31.13.13)',\n    'description': 'Full description of work',\n    'short_description': 'Abbreviated description',\n    'unit': 'Unit of measure (m³, m², ton, pcs)',\n    'unit_price': 'Base unit price',\n    'labor_cost': 'Labor component per unit',\n    'material_cost': 'Material component per unit',\n    'equipment_cost': 'Equipment component per unit',\n    'labor_hours': 'Labor hours per unit',\n    'crew_size': 'Typical crew size',\n    'productivity': 'Units per day',\n    'category_l1': 'Primary category (CSI Division)',\n    'category_l2': 'Secondary category',\n    'category_l3': 'Detailed category',\n    'region': 'Geographic region',\n    'year': 'Price year',\n    'source': 'Data source'\n}\n\n# CSI MasterFormat Divisions\nCSI_DIVISIONS = {\n    '03': 'Concrete',\n    '04': 'Masonry',\n    '05': 'Metals',\n    '06': 'Wood, Plastics, Composites',\n    '07': 'Thermal and Moisture Protection',\n    '08': 'Openings',\n    '09': 'Finishes',\n    '10': 'Specialties',\n    '21': 'Fire Suppression',\n    '22': 'Plumbing',\n    '23': 'HVAC',\n    '26': 'Electrical',\n    '31': 'Earthwork',\n    '32': 'Exterior Improvements',\n    '33': 'Utilities'\n}\n\nWork Item Matching Engine\nSemantic Matching System\nimport pandas as pd\nimport numpy as np\nfrom sklearn.feature_extraction.text import TfidfVectorizer\nfrom sklearn.metrics.pairwise import cosine_similarity\nfrom sentence_transformers import SentenceTransformer\nfrom typing import List, Dict, Optional, Tuple\nimport re\n\nclass WorkItemMatcher:\n    \"\"\"Match BIM elements to standardized work items\"\"\"\n\n    def __init__(self, database_path: str, use_embeddings: bool = True):\n        self.db = pd.read_csv(database_path)\n\n        # TF-IDF for fast initial filtering\n        self.tfidf = TfidfVectorizer(\n            ngram_range=(1, 3),\n            max_features=10000,\n            stop_words='english'\n        )\n        self.tfidf_matrix = self.tfidf.fit_transform(self.db['description'])\n\n        # Sentence embeddings for semantic matching\n        self.use_embeddings = use_embeddings\n        if use_embeddings:\n            self.embedder = SentenceTransformer('all-MiniLM-L6-v2')\n            self.embeddings = self.embedder.encode(\n                self.db['description'].tolist(),\n                show_progress_bar=True\n            )\n\n    def match(self, query: str, top_n: int = 5,\n              category: str = None) -> List[Dict]:\n        \"\"\"Find matching work items for a query\"\"\"\n        # Filter by category if specified\n        if category:\n            mask = self.db['category_l1'].str.contains(category, case=False, na=False)\n            search_db = self.db[mask]\n            search_matrix = self.tfidf_matrix[mask]\n        else:\n            search_db = self.db\n            search_matrix = self.tfidf_matrix\n\n        if self.use_embeddings:\n            return self._semantic_match(query, search_db, top_n)\n        else:\n            return self._tfidf_match(query, search_db, search_matrix, top_n)\n\n    def _tfidf_match(self, query: str, db: pd.DataFrame,\n                     matrix, top_n: int) -> List[Dict]:\n        \"\"\"TF-IDF based matching\"\"\"\n        query_vec = self.tfidf.transform([query])\n        similarities = cosine_similarity(query_vec, matrix)[0]\n\n        top_indices = similarities.argsort()[-top_n:][::-1]\n\n        results = []\n        for idx in top_indices:\n            row = db.iloc[idx]\n            results.append({\n                'code': row['code'],\n                'description': row['description'],\n                'unit': row['unit'],\n                'unit_price': row['unit_price'],\n                'similarity': float(similarities[idx]),\n                'category': row.get('category_l1', '')\n            })\n\n        return results\n\n    def _semantic_match(self, query: str, db: pd.DataFrame,\n                        top_n: int) -> List[Dict]:\n        \"\"\"Semantic embedding based matching\"\"\"\n        query_embedding = self.embedder.encode([query])\n\n        # Get indices for filtered db\n        indices = db.index.tolist()\n        filtered_embeddings = self.embeddings[indices]\n\n        similarities = cosine_similarity(query_embedding, filtered_embeddings)[0]\n        top_indices = similarities.argsort()[-top_n:][::-1]\n\n        results = []\n        for i, idx in enumerate(top_indices):\n            row = db.iloc[idx]\n            results.append({\n                'code': row['code'],\n                'description': row['description'],\n                'unit': row['unit'],\n                'unit_price': row['unit_price'],\n                'similarity': float(similarities[idx]),\n                'category': row.get('category_l1', '')\n            })\n\n        return results\n\n    def match_bim_element(self, element: Dict) -> List[Dict]:\n        \"\"\"Match a BIM element to work items\"\"\"\n        # Build query from element properties\n        query_parts = []\n\n        if element.get('material'):\n            query_parts.append(element['material'])\n        if element.get('category'):\n            query_parts.append(element['category'])\n        if element.get('description'):\n            query_parts.append(element['description'])\n\n        # Add dimensions if available\n        if element.get('thickness'):\n            query_parts.append(f\"{element['thickness']}mm thick\")\n        if element.get('height'):\n            query_parts.append(f\"{element['height']}m high\")\n\n        query = ' '.join(query_parts)\n\n        # Determine category from element type\n        category = self._get_category_from_element(element)\n\n        return self.match(query, top_n=3, category=category)\n\n    def _get_category_from_element(self, element: Dict) -> Optional[str]:\n        \"\"\"Map BIM element type to CSI category\"\"\"\n        element_mapping = {\n            'IfcWall': 'Concrete|Masonry',\n            'IfcSlab': 'Concrete',\n            'IfcColumn': 'Concrete|Metals',\n            'IfcBeam': 'Concrete|Metals',\n            'IfcDoor': 'Openings',\n            'IfcWindow': 'Openings',\n            'IfcRoof': 'Thermal',\n            'IfcStair': 'Concrete',\n            'IfcPipeSegment': 'Plumbing',\n            'IfcDuctSegment': 'HVAC'\n        }\n\n        elem_type = element.get('ifc_type', '')\n        return element_mapping.get(elem_type)\n\nCost Estimation Engine\nAutomated Estimator\nclass OpenConstructionEstimator:\n    \"\"\"Generate cost estimates using open databases\"\"\"\n\n    def __init__(self, matcher: WorkItemMatcher, region: str = 'default'):\n        self.matcher = matcher\n        self.region = region\n        self.regional_factors = self._load_regional_factors()\n        self.estimates = []\n\n    def _load_regional_factors(self) -> Dict[str, float]:\n        \"\"\"Load regional cost adjustment factors\"\"\"\n        return {\n            'default': 1.0,\n            'northeast_us': 1.15,\n            'southeast_us': 0.92,\n            'midwest_us': 0.95,\n            'west_us': 1.08,\n            'moscow': 1.20,\n            'spb': 1.10,\n            'regions_ru': 0.85\n        }\n\n    def estimate_element(self, element: Dict) -> Dict:\n        \"\"\"Estimate cost for a single element\"\"\"\n        # Get matching work items\n        matches = self.matcher.match_bim_element(element)\n\n        if not matches:\n            return {\n                'element_id': element.get('id'),\n                'status': 'no_match',\n                'estimated_cost': 0\n            }\n\n        best_match = matches[0]\n        quantity = element.get('quantity', 1)\n        unit_price = best_match['unit_price']\n\n        # Apply regional factor\n        regional_factor = self.regional_factors.get(self.region, 1.0)\n        adjusted_price = unit_price * regional_factor\n\n        # Calculate total\n        total_cost = adjusted_price * quantity\n\n        estimate = {\n            'element_id': element.get('id'),\n            'element_type': element.get('ifc_type'),\n            'element_description': element.get('description', ''),\n            'matched_code': best_match['code'],\n            'matched_description': best_match['description'],\n            'match_confidence': best_match['similarity'],\n            'unit': best_match['unit'],\n            'quantity': quantity,\n            'unit_price': unit_price,\n            'regional_factor': regional_factor,\n            'adjusted_unit_price': adjusted_price,\n            'total_cost': total_cost\n        }\n\n        self.estimates.append(estimate)\n        return estimate\n\n    def estimate_project(self, elements: List[Dict]) -> Dict:\n        \"\"\"Estimate entire project\"\"\"\n        for element in elements:\n            self.estimate_element(element)\n\n        df = pd.DataFrame(self.estimates)\n\n        # Summary by category\n        if not df.empty:\n            summary = df.groupby('element_type').agg({\n                'total_cost': 'sum',\n                'element_id': 'count',\n                'match_confidence': 'mean'\n            }).rename(columns={'element_id': 'count'})\n        else:\n            summary = pd.DataFrame()\n\n        total = df['total_cost'].sum() if not df.empty else 0\n\n        return {\n            'total_cost': total,\n            'element_count': len(elements),\n            'matched_count': len(df[df['match_confidence'] > 0.5]) if not df.empty else 0,\n            'summary_by_type': summary.to_dict() if not summary.empty else {},\n            'details': self.estimates\n        }\n\n    def export_estimate(self, output_path: str) -> str:\n        \"\"\"Export estimate to Excel\"\"\"\n        df = pd.DataFrame(self.estimates)\n\n        with pd.ExcelWriter(output_path, engine='openpyxl') as writer:\n            # Summary\n            summary = pd.DataFrame({\n                'Metric': ['Total Cost', 'Elements', 'Matched', 'Avg Confidence'],\n                'Value': [\n                    df['total_cost'].sum() if not df.empty else 0,\n                    len(df),\n                    len(df[df['match_confidence'] > 0.5]) if not df.empty else 0,\n                    df['match_confidence'].mean() if not df.empty else 0\n                ]\n            })\n            summary.to_excel(writer, sheet_name='Summary', index=False)\n\n            # Details\n            if not df.empty:\n                df.to_excel(writer, sheet_name='Details', index=False)\n\n                # By type\n                by_type = df.groupby('element_type')['total_cost'].sum()\n                by_type.to_excel(writer, sheet_name='By_Type')\n\n        return output_path\n\n    def get_missing_items(self) -> List[Dict]:\n        \"\"\"Get elements that couldn't be matched\"\"\"\n        df = pd.DataFrame(self.estimates)\n        if df.empty:\n            return []\n\n        low_confidence = df[df['match_confidence'] < 0.5]\n        return low_confidence.to_dict('records')\n\nDatabase Management\nCreating and Updating Database\nclass OpenDatabaseManager:\n    \"\"\"Manage open construction pricing database\"\"\"\n\n    def __init__(self, db_path: str):\n        self.db_path = db_path\n        self.db = self._load_or_create()\n\n    def _load_or_create(self) -> pd.DataFrame:\n        \"\"\"Load existing or create new database\"\"\"\n        try:\n            return pd.read_csv(self.db_path)\n        except FileNotFoundError:\n            return pd.DataFrame(columns=list(WORK_ITEMS_SCHEMA.keys()))\n\n    def add_items(self, items: List[Dict]):\n        \"\"\"Add new work items\"\"\"\n        new_df = pd.DataFrame(items)\n        self.db = pd.concat([self.db, new_df], ignore_index=True)\n        self.db.drop_duplicates(subset=['code'], keep='last', inplace=True)\n\n    def update_prices(self, updates: pd.DataFrame, year: int):\n        \"\"\"Update prices with new data\"\"\"\n        for _, row in updates.iterrows():\n            mask = self.db['code'] == row['code']\n            if mask.any():\n                self.db.loc[mask, 'unit_price'] = row['unit_price']\n                self.db.loc[mask, 'year'] = year\n\n    def apply_inflation(self, rate: float):\n        \"\"\"Apply inflation adjustment\"\"\"\n        self.db['unit_price'] = self.db['unit_price'] * (1 + rate)\n\n    def export_subset(self, category: str, output_path: str):\n        \"\"\"Export subset of database\"\"\"\n        subset = self.db[\n            self.db['category_l1'].str.contains(category, case=False, na=False)\n        ]\n        subset.to_csv(output_path, index=False)\n\n    def save(self):\n        \"\"\"Save database\"\"\"\n        self.db.to_csv(self.db_path, index=False)\n\n    def get_statistics(self) -> Dict:\n        \"\"\"Get database statistics\"\"\"\n        return {\n            'total_items': len(self.db),\n            'categories': self.db['category_l1'].nunique(),\n            'avg_price': self.db['unit_price'].mean(),\n            'price_range': (self.db['unit_price'].min(), self.db['unit_price'].max()),\n            'latest_year': self.db['year'].max() if 'year' in self.db else None\n        }\n\nQuick Reference\nCategory\tCSI Division\tTypical Items\nConcrete\t03\tWalls, slabs, columns, beams\nMasonry\t04\tBrick, block, stone\nMetals\t05\tStructural steel, misc metals\nFinishes\t09\tDrywall, paint, flooring\nMEP\t21-26\tPlumbing, HVAC, electrical\nSitework\t31-33\tExcavation, paving, utilities\nResources\nOpenConstructionEstimate: Open database initiative\nCSI MasterFormat: https://www.csiresources.org/standards/masterformat\nDDC Website: https://datadrivenconstruction.io\nNext Steps\nSee vector-search for semantic item matching\nSee cost-prediction for ML-based estimation\nSee qto-report for quantity extraction"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/datadrivenconstruction/open-construction-estimate",
    "publisherUrl": "https://clawhub.ai/datadrivenconstruction/open-construction-estimate",
    "owner": "datadrivenconstruction",
    "version": "2.0.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/open-construction-estimate",
    "downloadUrl": "https://openagent3.xyz/downloads/open-construction-estimate",
    "agentUrl": "https://openagent3.xyz/skills/open-construction-estimate/agent",
    "manifestUrl": "https://openagent3.xyz/skills/open-construction-estimate/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/open-construction-estimate/agent.md"
  }
}