excel-to-rvt

excel-to-rvt is a skill for Claude Code, Codex from jdmorag97-rgb/DDC_Skills_for_AI_Agents_in_Construction. It costs 26 tokens per session (2,628 once invoked), scanned A, a copy of excel-to-rvt, MIT.

An importer moves data from Excel spreadsheets into RVT files, the project format used by Autodesk Revit, to update model elements and create schedules.

In plain words
What is it for?
Use it to match spreadsheet rows to Revit element IDs, update writable parameters in batches, and keep external data aligned with a model.
Why use it?
It avoids entering costs, classifications, specifications, and other parameter values manually, which can be slow and error-prone.

Skill for Claude CodeCodex

Which agent this was written for is unclear — built for openclaw. Also seen: built for openclaw.

Good fit Use it to match spreadsheet rows to Revit element IDs, update writable parameters in batches, and keep external data aligned with a model.

Compare 6 skills from other repositories ↓
Install with agentmods
npx agentmods add skills/jdmorag97-rgb/ddc_skills_for_ai_agents_in_construction/excel-to-rvt
Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

Any agent
npx skills add jdmorag97-rgb/DDC_Skills_for_AI_Agents_in_Construction --skill excel-to-rvt
Clone the repo
git clone --depth 1 https://github.com/jdmorag97-rgb/DDC_Skills_for_AI_Agents_in_Construction

Made for: Claude Code, Codex.

Wrote this? Show the measurements

A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.

agentmods badge for excel-to-rvt

README.md
[![agentmods](https://agentmods.dev/badge/skills/jdmorag97-rgb/ddc_skills_for_ai_agents_in_construction/excel-to-rvt/github.svg)](https://agentmods.dev/skills/jdmorag97-rgb/ddc_skills_for_ai_agents_in_construction/excel-to-rvt)
Your own site
<a href="https://agentmods.dev/skills/jdmorag97-rgb/ddc_skills_for_ai_agents_in_construction/excel-to-rvt"><img src="https://agentmods.dev/badge/skills/jdmorag97-rgb/ddc_skills_for_ai_agents_in_construction/excel-to-rvt/github.svg" alt="Measured on agentmods" height="20"></a>

Or the 80×15 button, for a site that already has a row of RSS and ATOM ones. Only the verdict fits; the numbers stay here.

agentmods 80×15 button for excel-to-rvt

Your own site · 80×15
<a href="https://agentmods.dev/skills/jdmorag97-rgb/ddc_skills_for_ai_agents_in_construction/excel-to-rvt"><img src="https://agentmods.dev/badge/skills/jdmorag97-rgb/ddc_skills_for_ai_agents_in_construction/excel-to-rvt.svg" alt="Reviewed on agentmods" width="80" height="20"></a>
Per session 26 Skills are progressive disclosure: only the name and description are preloaded; the body loads when the skill is used.
When invoked 2,628 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 1 finding. A grade says what 26 rules found in the file — not that it is safe.
Origin 100% copy Near-identical to another mod in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5.1 $0.00026 $0.02628
Opus 5 $0.00013 $0.01314
Sonnet 5 $0.00005 $0.00526
Haiku 4.5 $0.00003 $0.00263

Measured 12d ago against content hash 4f941dca0adc, method: parsed. Prices are Anthropic first-party input rates as of 2026-09-12, from the pricing page.

Security

Grade A, and why

excel-to-rvt scanned grade A with 1 finding against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 12d ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Runs shell commandslowCapability

Expected in a hook, worth knowing in a rule or an instructions file.

result = subprocess.run(cmd, capture_output=True, text=True)
Origin

This is a copy

100% identical to excel-to-rvt — 0 lines differ, which has more behind it and is treated as the original. This page carries a canonical link to it rather than competing with it.

1_DDC_Toolkit/CAD-Converters/excel-to-rvt/SKILL.md · 417 lines

How it starts

The opening of the file, as written. The whole thing — 417 lines — stays where its author put it; the contents beside it link to each section on GitHub.

Excel to RVT Import

Note: RVT is the file format. Examples may reference Autodesk® Revit® APIs. Autodesk and Revit are registered trademarks of Autodesk, Inc.

Business Case

Problem Statement

External data (costs, specifications, classifications) lives in Excel but needs to update Revit:

  • Cost estimates need to link to model elements
  • Classification codes need assignment
  • Custom parameters need population
  • Manual entry is slow and error-prone

Solution

Automated import of Excel data into Revit using the DDC ImportExcelToRevit tool and Dynamo workflows.

Business Value

  • Automation - Batch update thousands of parameters
  • Accuracy - Eliminate manual data entry errors
  • Sync - Keep external data in sync with model
  • Flexibility - Update any writable parameter

Technical Implementation

Methods

  1. ImportExcelToRevit CLI - Direct command-line update
  2. Dynamo Script - Visual programming approach
  3. Revit API - Full programmatic control

ImportExcelToRevit CLI

ImportExcelToRevit.exe <model.rvt> <data.xlsx> [options]
Option Description
-sheet Excel sheet name
-idcol Element ID column
-mapping Parameter mapping file

Python Implementation

import subprocess
import pandas as pd
from pathlib import Path
from typing import Dict, Any, List, Optional, Tuple
from dataclasses import dataclass
import json


@dataclass
class ImportResult:
    """Result of Excel import to Revit."""
    elements_processed: int
    elements_updated: int
    elements_failed: int
    parameters_updated: int
    errors: List[str]


class ExcelToRevitImporter:
    """Import Excel data into Revit models."""

    def __init__(self, tool_path: str = "ImportExcelToRevit.exe"):
        self.tool_path = Path(tool_path)

    def import_data(self, revit_file: str,
                    excel_file: str,
                    sheet_name: str = "Elements",
                    id_column: str = "ElementId",
                    parameter_mapping: Dict[str, str] = None) -> ImportResult:
        """Import Excel data into Revit."""

        # Build command
        cmd = [
            str(self.tool_path),
            revit_file,
            excel_file,
            "-sheet", sheet_name,
            "-idcol", id_column
        ]

        # Add mapping file if provided
        if parameter_mapping:
            mapping_file = self._create_mapping_file(parameter_mapping)
            cmd.extend(["-mapping", mapping_file])

        # Execute
        result = subprocess.run(cmd, capture_output=True, text=True)

        # Parse result (format depends on tool)
        return self._parse_result(result)

    def _create_mapping_file(self, mapping: Dict[str, str]) -> str:
        """Create temporary mapping file."""
        mapping_path = Path("temp_mapping.json")
        with open(mapping_path, 'w') as f:
            json.dump(mapping, f)
        return str(mapping_path)

    def _parse_result(self, result: subprocess.CompletedProcess) -> ImportResult:
        """Parse CLI result."""
        # This is placeholder - actual parsing depends on tool output
        if result.returncode == 0:
            return ImportResult(
                elements_processed=0,
                elements_updated=0,
                elements_failed=0,
                parameters_updated=0,
                errors=[]
            )
        else:
            return ImportResult(
                elements_processed=0,
                elements_updated=0,
                elements_failed=0,
                parameters_updated=0,
                errors=[result.stderr]
            )


class DynamoScriptGenerator:
    """Generate Dynamo scripts for Revit data import."""

    def generate_parameter_update_script(self,
                                         mappings: Dict[str, str],
                                         excel_path: str,
                                         output_path: str) -> str:
        """Generate Dynamo Python script for parameter updates."""

        mappings_json = json.dumps(mappings)

        script = f'''
# Dynamo Python Script - Excel to Revit Parameter Update
# Generated by DDC

import clr
import sys
sys.path.append(r'C:\\Program Files (x86)\\IronPython 2.7\\Lib')

clr.AddReference('RevitAPI')
clr.AddReference('RevitServices')
clr.AddReference('Microsoft.Office.Interop.Excel')

from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
from Autodesk.Revit.DB import *
import Microsoft.Office.Interop.Excel as Excel

# Configuration
excel_path = r'{excel_path}'
mappings = {mappings_json}

# Open Excel
excel_app = Excel.ApplicationClass()
excel_app.Visible = False
workbook = excel_app.Workbooks.Open(excel_path)
worksheet = workbook.Worksheets[1]

# Get Revit document
doc = DocumentManager.Instance.CurrentDBDocument

# Read Excel data
used_range = worksheet.UsedRange
rows = used_range.Rows.Count
cols = used_range.Columns.Count

# Find column indices
headers = {{}}
for col in range(1, cols + 1):
    header = str(worksheet.Cells[1, col].Value2 or '')
    headers[header] = col

# Process rows
TransactionManager.Instance.EnsureInTransaction(doc)

updated_count = 0
error_count = 0

for row in range(2, rows + 1):
    try:
        # Get element ID
        element_id_col = headers.get('ElementId', 1)
        element_id = int(worksheet.Cells[row, element_id_col].Value2 or 0)

        element = doc.GetElement(ElementId(element_id))
        if not element:
            continue

        # Update mapped parameters
        for excel_col, revit_param in mappings.items():
            if excel_col in headers:
                col_idx = headers[excel_col]
                value = worksheet.Cells[row, col_idx].Value2

                if value is not None:
                    param = element.LookupParameter(revit_param)
                    if param and not param.IsReadOnly:
                        if param.StorageType == StorageType.Double:
                            param.Set(float(value))
                        elif param.StorageType == StorageType.Integer:
                            param.Set(int(value))
                        elif param.StorageType == StorageType.String:
                            param.Set(str(value))

        updated_count += 1

    except Exception as e:
        error_count += 1

TransactionManager.Instance.TransactionTaskDone()

# Cleanup
workbook.Close(False)
excel_app.Quit()

OUT = f"Updated: {{updated_count}}, Errors: {{error_count}}"
'''

        with open(output_path, 'w') as f:
            f.write(script)

        return output_path

    def generate_schedule_creator(self,
                                  schedule_name: str,
                                  category: str,
                                  fields: List[str],
                                  output_path: str) -> str:
        """Generate script to create Revit schedule from Excel structure."""

        fields_json = json.dumps(fields)

        script = f'''
# Dynamo Python Script - Create Schedule
# Generated by DDC

import clr
clr.AddReference('RevitAPI')
clr.AddReference('RevitServices')

from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
from Autodesk.Revit.DB import *

doc = DocumentManager.Instance.CurrentDBDocument
fields = {fields_json}

# Get category
category = Category.GetCategory(doc, BuiltInCategory.OST_{category})

TransactionManager.Instance.EnsureInTransaction(doc)

# Create schedule
schedule = ViewSchedule.CreateSchedule(doc, category.Id)
schedule.Name = "{schedule_name}"

# Add fields
definition = schedule.Definition

for field_name in fields:
    # Find schedulable field
    for sf in definition.GetSchedulableFields():
        if sf.GetName(doc) == field_name:
            definition.AddField(sf)
            break

TransactionManager.Instance.TransactionTaskDone()

OUT = schedule
'''

        with open(output_path, 'w') as f:
            f.write(script)

        return output_path


class ExcelDataValidator:
    """Validate Excel data before Revit import."""

    def __init__(self, revit_elements: pd.DataFrame):
        """Initialize with exported Revit elements."""
        self.revit_data = revit_elements
        self.valid_ids = set(revit_elements['ElementId'].astype(str).tolist())

    def validate_import_data(self, import_df: pd.DataFrame,
                             id_column: str = 'ElementId') -> Dict[str, Any]:
        """Validate import data against Revit export."""

        results = {
            'valid': True,
            'total_rows': len(import_df),
            'matching_ids': 0,
            'missing_ids': [],
            'invalid_ids': [],
            'warnings': []
        }

        import_ids = import_df[id_column].astype(str).tolist()

        for import_id in import_ids:
            if import_id in self.valid_ids:
                results['matching_ids'] += 1
            else:
                results['invalid_ids'].append(import_id)

        if results['invalid_ids']:
            results['valid'] = False
            results['warnings'].append(
                f"{len(results['invalid_ids'])} element IDs not found in Revit model"
            )

        results['match_rate'] = round(
            results['matching_ids'] / results['total_rows'] * 100, 1
        ) if results['total_rows'] > 0 else 0

        return results

    def check_parameter_types(self, import_df: pd.DataFrame,
                              type_definitions: Dict[str, str]) -> List[str]:
        """Check if values match expected parameter types."""

        errors = []

        for column, expected_type in type_definitions.items():
            if column not in import_df.columns:
                continue

            for idx, value in import_df[column].items():
                if pd.isna(value):
                    continue

                if expected_type == 'number':
                    try:
                        float(value)
                    except ValueError:
                        errors.append(f"Row {idx}: '{column}' should be number, got '{value}'")

                elif expected_type == 'integer':
                    try:
                        int(value)
                    except ValueError:
                        errors.append(f"Row {idx}: '{column}' should be integer, got '{value}'")

        return errors

Read the full file on GitHub · 417 lines

Files

What ships with it

2 files beside SKILL.md in the same directory: the scripts, references and assets a skill reads on demand. Not counted in the per-session cost; read them before you install if any of them is executable.

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 12d ago First seen · 417 lines · 26 tokens per session scan A 4f941dca0adc

Subscribe to this mod's changes

excel-to-rvt is a skill published in the GitHub repository jdmorag97-rgb/DDC_Skills_for_AI_Agents_in_Construction (2 stars, last pushed 6mo ago), licensed MIT. It adds 26 tokens to every session and 2,628 once invoked, about $0.0001 per session on Opus 5. A static security scan graded it A with 1 finding (runs shell commands). It is 100% identical to excel-to-rvt, differing in 0 lines, and is treated as a copy.