{
  "schemaVersion": "1.0",
  "item": {
    "slug": "qto-report",
    "name": "Qto Report",
    "source": "tencent",
    "type": "skill",
    "category": "数据分析",
    "sourceUrl": "https://clawhub.ai/datadrivenconstruction/qto-report",
    "canonicalUrl": "https://clawhub.ai/datadrivenconstruction/qto-report",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/qto-report",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=qto-report",
    "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/qto-report"
    },
    "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/qto-report",
    "agentPageUrl": "https://openagent3.xyz/skills/qto-report/agent",
    "manifestUrl": "https://openagent3.xyz/skills/qto-report/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/qto-report/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": "Based on DDC methodology (Chapter 3.2), this skill automates the extraction and grouping of quantities from BIM/CAD data. QTO is the foundation for cost estimation, scheduling, and project planning in construction.\n\nBook Reference: \"Quantity Take-Off и автоматическое создание смет\" / \"QTO and Automated Estimates\"\n\n\"QTO Quantity Take-Off: группировка данных по атрибутам позволяет автоматически извлекать объемы и количества из BIM-моделей для расчета стоимости.\"\n— DDC Book, Chapter 3.2"
      },
      {
        "title": "5D BIM Concept",
        "body": "The QTO process is central to 5D BIM:\n\n3D: Geometry (volume, area, length)\n4D: Time (schedule integration)\n5D: Cost (quantity × unit price)"
      },
      {
        "title": "Quick Start",
        "body": "import pandas as pd\n\n# Load BIM element data\ndf = pd.read_csv(\"revit_export.csv\")\n\n# Generate QTO by category\nqto = df.groupby('Category').agg({\n    'Volume': 'sum',\n    'Area': 'sum',\n    'ElementId': 'count'\n}).rename(columns={'ElementId': 'Count'})\n\n# Calculate cost (if unit prices available)\nqto['Unit_Price'] = [150, 80, 450, 200]  # $/m³\nqto['Total_Cost'] = qto['Volume'] * qto['Unit_Price']\n\nqto.to_excel(\"qto_report.xlsx\")"
      },
      {
        "title": "Basic QTO by Category",
        "body": "import pandas as pd\n\ndef generate_qto(df, group_by='Category'):\n    \"\"\"\n    Generate Quantity Take-Off grouped by specified column\n\n    Args:\n        df: DataFrame with BIM elements\n        group_by: Column(s) to group by\n\n    Returns:\n        QTO summary DataFrame\n    \"\"\"\n    # Define aggregations based on available columns\n    agg_dict = {}\n\n    if 'Volume' in df.columns:\n        agg_dict['Volume'] = 'sum'\n    if 'Area' in df.columns:\n        agg_dict['Area'] = 'sum'\n    if 'Length' in df.columns:\n        agg_dict['Length'] = 'sum'\n    if 'Count' in df.columns:\n        agg_dict['Count'] = 'sum'\n    else:\n        agg_dict['ElementId'] = 'count'\n\n    qto = df.groupby(group_by).agg(agg_dict)\n\n    if 'ElementId' in agg_dict:\n        qto = qto.rename(columns={'ElementId': 'Count'})\n\n    return qto.round(2)\n\n# Usage\nqto = generate_qto(df, group_by='Category')\nprint(qto)"
      },
      {
        "title": "Multi-Level QTO",
        "body": "def generate_multi_level_qto(df):\n    \"\"\"Generate QTO grouped by multiple levels\"\"\"\n    qto = df.groupby(['Level', 'Category', 'Material']).agg({\n        'Volume': ['sum', 'count'],\n        'Area': 'sum'\n    }).round(2)\n\n    # Flatten column names\n    qto.columns = ['Volume_m3', 'Element_Count', 'Area_m2']\n\n    # Add percentages\n    qto['Volume_Pct'] = (qto['Volume_m3'] /\n                          qto['Volume_m3'].sum() * 100).round(1)\n\n    return qto.sort_values('Volume_m3', ascending=False)\n\n# Usage\nqto = generate_multi_level_qto(df)\nqto.to_excel(\"qto_multi_level.xlsx\")"
      },
      {
        "title": "QTO with Pivot Table",
        "body": "def generate_qto_pivot(df, values='Volume', index='Level', columns='Category'):\n    \"\"\"Generate QTO as pivot table\"\"\"\n    pivot = pd.pivot_table(\n        df,\n        values=values,\n        index=index,\n        columns=columns,\n        aggfunc='sum',\n        fill_value=0,\n        margins=True,\n        margins_name='TOTAL'\n    ).round(2)\n\n    return pivot\n\n# Usage - Volume by Level and Category\nqto_pivot = generate_qto_pivot(df, values='Volume')\nqto_pivot.to_excel(\"qto_pivot.xlsx\")"
      },
      {
        "title": "Apply Unit Prices",
        "body": "def calculate_cost_from_qto(qto_df, prices_df, quantity_col='Volume'):\n    \"\"\"\n    Calculate costs by applying unit prices to quantities\n\n    Args:\n        qto_df: QTO DataFrame with quantities\n        prices_df: DataFrame with Category and Unit_Price\n        quantity_col: Column containing quantities\n    \"\"\"\n    # Merge with prices\n    result = qto_df.reset_index().merge(\n        prices_df, on='Category', how='left'\n    )\n\n    # Calculate costs\n    result['Total_Cost'] = result[quantity_col] * result['Unit_Price']\n    result['Cost_Pct'] = (result['Total_Cost'] /\n                          result['Total_Cost'].sum() * 100).round(1)\n\n    # Summary\n    grand_total = result['Total_Cost'].sum()\n    print(f\"Grand Total: ${grand_total:,.2f}\")\n\n    return result\n\n# Unit prices database\nprices = pd.DataFrame({\n    'Category': ['Wall', 'Floor', 'Column', 'Beam', 'Foundation'],\n    'Unit_Price': [150, 80, 450, 200, 120],  # $/m³\n    'Unit': ['m³', 'm³', 'm³', 'm³', 'm³']\n})\n\n# Calculate\ncost_estimate = calculate_cost_from_qto(qto, prices)\ncost_estimate.to_excel(\"cost_estimate.xlsx\", index=False)"
      },
      {
        "title": "Apply Rules from Excel",
        "body": "def apply_excel_rules(df, rules_path):\n    \"\"\"\n    Apply calculation rules defined in Excel file\n\n    Excel format:\n    | Category | Formula_Type | Factor | Unit |\n    | Wall     | volume       | 1.05   | m³   |\n    | Floor    | area         | 1.10   | m²   |\n    \"\"\"\n    rules = pd.read_excel(rules_path)\n\n    results = []\n    for _, rule in rules.iterrows():\n        category = rule['Category']\n        formula_type = rule['Formula_Type']\n        factor = rule['Factor']\n\n        category_data = df[df['Category'] == category].copy()\n\n        if formula_type == 'volume':\n            category_data['Quantity'] = category_data['Volume'] * factor\n        elif formula_type == 'area':\n            category_data['Quantity'] = category_data['Area'] * factor\n        elif formula_type == 'length':\n            category_data['Quantity'] = category_data['Length'] * factor\n        elif formula_type == 'count':\n            category_data['Quantity'] = category_data.groupby('Category').ngroup() + 1\n\n        category_data['Unit'] = rule['Unit']\n        results.append(category_data)\n\n    return pd.concat(results, ignore_index=True)\n\n# Usage\ndf_with_quantities = apply_excel_rules(df, \"calculation_rules.xlsx\")"
      },
      {
        "title": "From Revit Export (CSV)",
        "body": "def process_revit_export(csv_path):\n    \"\"\"Process standard Revit schedule export\"\"\"\n    df = pd.read_csv(csv_path)\n\n    # Standardize column names\n    column_mapping = {\n        'Family and Type': 'Type',\n        'Volume': 'Volume',\n        'Area': 'Area',\n        'Count': 'Count',\n        'Level': 'Level',\n        'Category': 'Category'\n    }\n\n    df = df.rename(columns={\n        k: v for k, v in column_mapping.items()\n        if k in df.columns\n    })\n\n    # Convert volume from cubic feet to cubic meters (if needed)\n    if 'Volume' in df.columns:\n        # Revit exports in cubic feet by default\n        df['Volume_m3'] = df['Volume'] * 0.0283168\n\n    return df\n\n# Usage\ndf = process_revit_export(\"revit_schedule.csv\")\nqto = generate_qto(df)"
      },
      {
        "title": "From IFC Export",
        "body": "# Using IfcOpenShell\nimport ifcopenshell\nimport pandas as pd\n\ndef extract_qto_from_ifc(ifc_path):\n    \"\"\"Extract quantities from IFC file\"\"\"\n    ifc = ifcopenshell.open(ifc_path)\n\n    elements = []\n    for element in ifc.by_type(\"IfcBuildingElement\"):\n        # Get properties\n        props = {\n            'GlobalId': element.GlobalId,\n            'Name': element.Name,\n            'Type': element.is_a(),\n            'Material': None,\n            'Volume': None,\n            'Area': None\n        }\n\n        # Extract quantities from property sets\n        for definition in element.IsDefinedBy:\n            if definition.is_a('IfcRelDefinesByProperties'):\n                pset = definition.RelatingPropertyDefinition\n                if pset.is_a('IfcElementQuantity'):\n                    for qty in pset.Quantities:\n                        if qty.is_a('IfcQuantityVolume'):\n                            props['Volume'] = qty.VolumeValue\n                        elif qty.is_a('IfcQuantityArea'):\n                            props['Area'] = qty.AreaValue\n\n        elements.append(props)\n\n    return pd.DataFrame(elements)\n\n# Usage\ndf = extract_qto_from_ifc(\"model.ifc\")\nqto = generate_qto(df, group_by='Type')"
      },
      {
        "title": "Detailed Material Breakdown",
        "body": "def material_breakdown_qto(df):\n    \"\"\"Detailed breakdown by material type\"\"\"\n    breakdown = df.groupby(['Category', 'Material', 'Type']).agg({\n        'Volume': 'sum',\n        'Area': 'sum',\n        'ElementId': 'nunique'\n    }).rename(columns={'ElementId': 'Unique_Elements'})\n\n    # Add subtotals for each category\n    category_totals = df.groupby('Category').agg({\n        'Volume': 'sum',\n        'Area': 'sum'\n    })\n\n    breakdown['Category_Volume_Pct'] = breakdown.apply(\n        lambda row: (row['Volume'] /\n                    category_totals.loc[row.name[0], 'Volume'] * 100),\n        axis=1\n    ).round(1)\n\n    return breakdown\n\n# Usage\nmaterial_qto = material_breakdown_qto(df)\nmaterial_qto.to_excel(\"material_breakdown.xlsx\")"
      },
      {
        "title": "QTO with Waste Factors",
        "body": "def qto_with_waste(df, waste_factors):\n    \"\"\"\n    Apply waste factors to quantities\n\n    Args:\n        waste_factors: dict like {'Concrete': 1.05, 'Steel': 1.03}\n    \"\"\"\n    qto = df.groupby(['Category', 'Material']).agg({\n        'Volume': 'sum'\n    }).reset_index()\n\n    # Apply waste factors\n    qto['Waste_Factor'] = qto['Material'].map(waste_factors).fillna(1.0)\n    qto['Net_Volume'] = qto['Volume']\n    qto['Gross_Volume'] = qto['Volume'] * qto['Waste_Factor']\n    qto['Waste_Volume'] = qto['Gross_Volume'] - qto['Net_Volume']\n\n    return qto\n\n# Usage\nwaste = {'Concrete': 1.05, 'Brick': 1.08, 'Steel': 1.03}\nqto = qto_with_waste(df, waste)"
      },
      {
        "title": "QTO Comparison (Design vs As-Built)",
        "body": "def compare_qto(design_df, asbuilt_df, group_by='Category'):\n    \"\"\"Compare designed vs as-built quantities\"\"\"\n    design_qto = design_df.groupby(group_by)['Volume'].sum()\n    asbuilt_qto = asbuilt_df.groupby(group_by)['Volume'].sum()\n\n    comparison = pd.DataFrame({\n        'Design': design_qto,\n        'AsBuilt': asbuilt_qto\n    })\n\n    comparison['Difference'] = comparison['AsBuilt'] - comparison['Design']\n    comparison['Variance_%'] = (\n        (comparison['AsBuilt'] - comparison['Design']) /\n        comparison['Design'] * 100\n    ).round(1)\n\n    return comparison\n\n# Usage\ncomparison = compare_qto(design_df, asbuilt_df)\nprint(comparison)"
      },
      {
        "title": "Export to Multiple Formats",
        "body": "def export_qto_report(qto_df, base_name, include_charts=True):\n    \"\"\"Export QTO to Excel with formatting and charts\"\"\"\n    from openpyxl import Workbook\n    from openpyxl.chart import BarChart, Reference\n\n    # Excel with multiple sheets\n    with pd.ExcelWriter(f\"{base_name}.xlsx\", engine='openpyxl') as writer:\n        # Summary sheet\n        qto_df.to_excel(writer, sheet_name='Summary')\n\n        # Detailed data\n        if hasattr(qto_df, 'reset_index'):\n            qto_df.reset_index().to_excel(\n                writer, sheet_name='Details', index=False\n            )\n\n    # CSV for integration\n    qto_df.to_csv(f\"{base_name}.csv\")\n\n    # JSON for API\n    qto_df.reset_index().to_json(\n        f\"{base_name}.json\", orient='records', indent=2\n    )\n\n    print(f\"Exported: {base_name}.xlsx, .csv, .json\")\n\n# Usage\nexport_qto_report(qto, \"project_qto\")"
      },
      {
        "title": "Quick Reference",
        "body": "TaskCodeBasic QTOdf.groupby('Category')['Volume'].sum()Multi-column QTOdf.groupby(['Level', 'Category']).agg({...})Pivot QTOpd.pivot_table(df, values='Volume', ...)Apply pricesqto.merge(prices, on='Category')Calculate costdf['Cost'] = df['Volume'] * df['Unit_Price']Add waste factordf['Gross'] = df['Net'] * waste_factor"
      },
      {
        "title": "Resources",
        "body": "Book: \"Data-Driven Construction\" by Artem Boiko, Chapter 3.2\nWebsite: https://datadrivenconstruction.io\nIfcOpenShell: https://ifcopenshell.org"
      },
      {
        "title": "Next Steps",
        "body": "See cost-estimation-resource for detailed cost calculations\nSee auto-estimate-generator for automated estimate creation\nSee gantt-chart for 4D scheduling integration\nSee co2-estimation for carbon footprint calculations"
      }
    ],
    "body": "Quantity Take-Off (QTO) Report Generation\nOverview\n\nBased on DDC methodology (Chapter 3.2), this skill automates the extraction and grouping of quantities from BIM/CAD data. QTO is the foundation for cost estimation, scheduling, and project planning in construction.\n\nBook Reference: \"Quantity Take-Off и автоматическое создание смет\" / \"QTO and Automated Estimates\"\n\n\"QTO Quantity Take-Off: группировка данных по атрибутам позволяет автоматически извлекать объемы и количества из BIM-моделей для расчета стоимости.\" — DDC Book, Chapter 3.2\n\n5D BIM Concept\n\nThe QTO process is central to 5D BIM:\n\n3D: Geometry (volume, area, length)\n4D: Time (schedule integration)\n5D: Cost (quantity × unit price)\nQuick Start\nimport pandas as pd\n\n# Load BIM element data\ndf = pd.read_csv(\"revit_export.csv\")\n\n# Generate QTO by category\nqto = df.groupby('Category').agg({\n    'Volume': 'sum',\n    'Area': 'sum',\n    'ElementId': 'count'\n}).rename(columns={'ElementId': 'Count'})\n\n# Calculate cost (if unit prices available)\nqto['Unit_Price'] = [150, 80, 450, 200]  # $/m³\nqto['Total_Cost'] = qto['Volume'] * qto['Unit_Price']\n\nqto.to_excel(\"qto_report.xlsx\")\n\nCore QTO Functions\nBasic QTO by Category\nimport pandas as pd\n\ndef generate_qto(df, group_by='Category'):\n    \"\"\"\n    Generate Quantity Take-Off grouped by specified column\n\n    Args:\n        df: DataFrame with BIM elements\n        group_by: Column(s) to group by\n\n    Returns:\n        QTO summary DataFrame\n    \"\"\"\n    # Define aggregations based on available columns\n    agg_dict = {}\n\n    if 'Volume' in df.columns:\n        agg_dict['Volume'] = 'sum'\n    if 'Area' in df.columns:\n        agg_dict['Area'] = 'sum'\n    if 'Length' in df.columns:\n        agg_dict['Length'] = 'sum'\n    if 'Count' in df.columns:\n        agg_dict['Count'] = 'sum'\n    else:\n        agg_dict['ElementId'] = 'count'\n\n    qto = df.groupby(group_by).agg(agg_dict)\n\n    if 'ElementId' in agg_dict:\n        qto = qto.rename(columns={'ElementId': 'Count'})\n\n    return qto.round(2)\n\n# Usage\nqto = generate_qto(df, group_by='Category')\nprint(qto)\n\nMulti-Level QTO\ndef generate_multi_level_qto(df):\n    \"\"\"Generate QTO grouped by multiple levels\"\"\"\n    qto = df.groupby(['Level', 'Category', 'Material']).agg({\n        'Volume': ['sum', 'count'],\n        'Area': 'sum'\n    }).round(2)\n\n    # Flatten column names\n    qto.columns = ['Volume_m3', 'Element_Count', 'Area_m2']\n\n    # Add percentages\n    qto['Volume_Pct'] = (qto['Volume_m3'] /\n                          qto['Volume_m3'].sum() * 100).round(1)\n\n    return qto.sort_values('Volume_m3', ascending=False)\n\n# Usage\nqto = generate_multi_level_qto(df)\nqto.to_excel(\"qto_multi_level.xlsx\")\n\nQTO with Pivot Table\ndef generate_qto_pivot(df, values='Volume', index='Level', columns='Category'):\n    \"\"\"Generate QTO as pivot table\"\"\"\n    pivot = pd.pivot_table(\n        df,\n        values=values,\n        index=index,\n        columns=columns,\n        aggfunc='sum',\n        fill_value=0,\n        margins=True,\n        margins_name='TOTAL'\n    ).round(2)\n\n    return pivot\n\n# Usage - Volume by Level and Category\nqto_pivot = generate_qto_pivot(df, values='Volume')\nqto_pivot.to_excel(\"qto_pivot.xlsx\")\n\nCost Calculation from QTO\nApply Unit Prices\ndef calculate_cost_from_qto(qto_df, prices_df, quantity_col='Volume'):\n    \"\"\"\n    Calculate costs by applying unit prices to quantities\n\n    Args:\n        qto_df: QTO DataFrame with quantities\n        prices_df: DataFrame with Category and Unit_Price\n        quantity_col: Column containing quantities\n    \"\"\"\n    # Merge with prices\n    result = qto_df.reset_index().merge(\n        prices_df, on='Category', how='left'\n    )\n\n    # Calculate costs\n    result['Total_Cost'] = result[quantity_col] * result['Unit_Price']\n    result['Cost_Pct'] = (result['Total_Cost'] /\n                          result['Total_Cost'].sum() * 100).round(1)\n\n    # Summary\n    grand_total = result['Total_Cost'].sum()\n    print(f\"Grand Total: ${grand_total:,.2f}\")\n\n    return result\n\n# Unit prices database\nprices = pd.DataFrame({\n    'Category': ['Wall', 'Floor', 'Column', 'Beam', 'Foundation'],\n    'Unit_Price': [150, 80, 450, 200, 120],  # $/m³\n    'Unit': ['m³', 'm³', 'm³', 'm³', 'm³']\n})\n\n# Calculate\ncost_estimate = calculate_cost_from_qto(qto, prices)\ncost_estimate.to_excel(\"cost_estimate.xlsx\", index=False)\n\nApply Rules from Excel\ndef apply_excel_rules(df, rules_path):\n    \"\"\"\n    Apply calculation rules defined in Excel file\n\n    Excel format:\n    | Category | Formula_Type | Factor | Unit |\n    | Wall     | volume       | 1.05   | m³   |\n    | Floor    | area         | 1.10   | m²   |\n    \"\"\"\n    rules = pd.read_excel(rules_path)\n\n    results = []\n    for _, rule in rules.iterrows():\n        category = rule['Category']\n        formula_type = rule['Formula_Type']\n        factor = rule['Factor']\n\n        category_data = df[df['Category'] == category].copy()\n\n        if formula_type == 'volume':\n            category_data['Quantity'] = category_data['Volume'] * factor\n        elif formula_type == 'area':\n            category_data['Quantity'] = category_data['Area'] * factor\n        elif formula_type == 'length':\n            category_data['Quantity'] = category_data['Length'] * factor\n        elif formula_type == 'count':\n            category_data['Quantity'] = category_data.groupby('Category').ngroup() + 1\n\n        category_data['Unit'] = rule['Unit']\n        results.append(category_data)\n\n    return pd.concat(results, ignore_index=True)\n\n# Usage\ndf_with_quantities = apply_excel_rules(df, \"calculation_rules.xlsx\")\n\nBIM Data Extraction Patterns\nFrom Revit Export (CSV)\ndef process_revit_export(csv_path):\n    \"\"\"Process standard Revit schedule export\"\"\"\n    df = pd.read_csv(csv_path)\n\n    # Standardize column names\n    column_mapping = {\n        'Family and Type': 'Type',\n        'Volume': 'Volume',\n        'Area': 'Area',\n        'Count': 'Count',\n        'Level': 'Level',\n        'Category': 'Category'\n    }\n\n    df = df.rename(columns={\n        k: v for k, v in column_mapping.items()\n        if k in df.columns\n    })\n\n    # Convert volume from cubic feet to cubic meters (if needed)\n    if 'Volume' in df.columns:\n        # Revit exports in cubic feet by default\n        df['Volume_m3'] = df['Volume'] * 0.0283168\n\n    return df\n\n# Usage\ndf = process_revit_export(\"revit_schedule.csv\")\nqto = generate_qto(df)\n\nFrom IFC Export\n# Using IfcOpenShell\nimport ifcopenshell\nimport pandas as pd\n\ndef extract_qto_from_ifc(ifc_path):\n    \"\"\"Extract quantities from IFC file\"\"\"\n    ifc = ifcopenshell.open(ifc_path)\n\n    elements = []\n    for element in ifc.by_type(\"IfcBuildingElement\"):\n        # Get properties\n        props = {\n            'GlobalId': element.GlobalId,\n            'Name': element.Name,\n            'Type': element.is_a(),\n            'Material': None,\n            'Volume': None,\n            'Area': None\n        }\n\n        # Extract quantities from property sets\n        for definition in element.IsDefinedBy:\n            if definition.is_a('IfcRelDefinesByProperties'):\n                pset = definition.RelatingPropertyDefinition\n                if pset.is_a('IfcElementQuantity'):\n                    for qty in pset.Quantities:\n                        if qty.is_a('IfcQuantityVolume'):\n                            props['Volume'] = qty.VolumeValue\n                        elif qty.is_a('IfcQuantityArea'):\n                            props['Area'] = qty.AreaValue\n\n        elements.append(props)\n\n    return pd.DataFrame(elements)\n\n# Usage\ndf = extract_qto_from_ifc(\"model.ifc\")\nqto = generate_qto(df, group_by='Type')\n\nAdvanced QTO Reports\nDetailed Material Breakdown\ndef material_breakdown_qto(df):\n    \"\"\"Detailed breakdown by material type\"\"\"\n    breakdown = df.groupby(['Category', 'Material', 'Type']).agg({\n        'Volume': 'sum',\n        'Area': 'sum',\n        'ElementId': 'nunique'\n    }).rename(columns={'ElementId': 'Unique_Elements'})\n\n    # Add subtotals for each category\n    category_totals = df.groupby('Category').agg({\n        'Volume': 'sum',\n        'Area': 'sum'\n    })\n\n    breakdown['Category_Volume_Pct'] = breakdown.apply(\n        lambda row: (row['Volume'] /\n                    category_totals.loc[row.name[0], 'Volume'] * 100),\n        axis=1\n    ).round(1)\n\n    return breakdown\n\n# Usage\nmaterial_qto = material_breakdown_qto(df)\nmaterial_qto.to_excel(\"material_breakdown.xlsx\")\n\nQTO with Waste Factors\ndef qto_with_waste(df, waste_factors):\n    \"\"\"\n    Apply waste factors to quantities\n\n    Args:\n        waste_factors: dict like {'Concrete': 1.05, 'Steel': 1.03}\n    \"\"\"\n    qto = df.groupby(['Category', 'Material']).agg({\n        'Volume': 'sum'\n    }).reset_index()\n\n    # Apply waste factors\n    qto['Waste_Factor'] = qto['Material'].map(waste_factors).fillna(1.0)\n    qto['Net_Volume'] = qto['Volume']\n    qto['Gross_Volume'] = qto['Volume'] * qto['Waste_Factor']\n    qto['Waste_Volume'] = qto['Gross_Volume'] - qto['Net_Volume']\n\n    return qto\n\n# Usage\nwaste = {'Concrete': 1.05, 'Brick': 1.08, 'Steel': 1.03}\nqto = qto_with_waste(df, waste)\n\nQTO Comparison (Design vs As-Built)\ndef compare_qto(design_df, asbuilt_df, group_by='Category'):\n    \"\"\"Compare designed vs as-built quantities\"\"\"\n    design_qto = design_df.groupby(group_by)['Volume'].sum()\n    asbuilt_qto = asbuilt_df.groupby(group_by)['Volume'].sum()\n\n    comparison = pd.DataFrame({\n        'Design': design_qto,\n        'AsBuilt': asbuilt_qto\n    })\n\n    comparison['Difference'] = comparison['AsBuilt'] - comparison['Design']\n    comparison['Variance_%'] = (\n        (comparison['AsBuilt'] - comparison['Design']) /\n        comparison['Design'] * 100\n    ).round(1)\n\n    return comparison\n\n# Usage\ncomparison = compare_qto(design_df, asbuilt_df)\nprint(comparison)\n\nExport Functions\nExport to Multiple Formats\ndef export_qto_report(qto_df, base_name, include_charts=True):\n    \"\"\"Export QTO to Excel with formatting and charts\"\"\"\n    from openpyxl import Workbook\n    from openpyxl.chart import BarChart, Reference\n\n    # Excel with multiple sheets\n    with pd.ExcelWriter(f\"{base_name}.xlsx\", engine='openpyxl') as writer:\n        # Summary sheet\n        qto_df.to_excel(writer, sheet_name='Summary')\n\n        # Detailed data\n        if hasattr(qto_df, 'reset_index'):\n            qto_df.reset_index().to_excel(\n                writer, sheet_name='Details', index=False\n            )\n\n    # CSV for integration\n    qto_df.to_csv(f\"{base_name}.csv\")\n\n    # JSON for API\n    qto_df.reset_index().to_json(\n        f\"{base_name}.json\", orient='records', indent=2\n    )\n\n    print(f\"Exported: {base_name}.xlsx, .csv, .json\")\n\n# Usage\nexport_qto_report(qto, \"project_qto\")\n\nQuick Reference\nTask\tCode\nBasic QTO\tdf.groupby('Category')['Volume'].sum()\nMulti-column QTO\tdf.groupby(['Level', 'Category']).agg({...})\nPivot QTO\tpd.pivot_table(df, values='Volume', ...)\nApply prices\tqto.merge(prices, on='Category')\nCalculate cost\tdf['Cost'] = df['Volume'] * df['Unit_Price']\nAdd waste factor\tdf['Gross'] = df['Net'] * waste_factor\nResources\nBook: \"Data-Driven Construction\" by Artem Boiko, Chapter 3.2\nWebsite: https://datadrivenconstruction.io\nIfcOpenShell: https://ifcopenshell.org\nNext Steps\nSee cost-estimation-resource for detailed cost calculations\nSee auto-estimate-generator for automated estimate creation\nSee gantt-chart for 4D scheduling integration\nSee co2-estimation for carbon footprint calculations"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/datadrivenconstruction/qto-report",
    "publisherUrl": "https://clawhub.ai/datadrivenconstruction/qto-report",
    "owner": "datadrivenconstruction",
    "version": "2.1.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/qto-report",
    "downloadUrl": "https://openagent3.xyz/downloads/qto-report",
    "agentUrl": "https://openagent3.xyz/skills/qto-report/agent",
    "manifestUrl": "https://openagent3.xyz/skills/qto-report/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/qto-report/agent.md"
  }
}