Skip to content

Commit f14c4f7

Browse files
authored
Merge pull request #34 from NatLabRockies/optimize
AI-assisted measure authoring + CooledBeam terminals + zone equipment priority
2 parents fe2a5f2 + b245baf commit f14c4f7

58 files changed

Lines changed: 3642 additions & 4028 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
name: measure-authoring
3+
description: Create, test, and apply custom OpenStudio measures. Use when user asks to "write a measure", "create a custom measure", "modify the model with Ruby/Python code", or needs logic beyond what existing tools provide.
4+
disable-model-invocation: true
5+
---
6+
7+
# Measure Authoring
8+
9+
Create custom OpenStudio ModelMeasures with user-provided logic, test them, and apply them to models.
10+
11+
## When to Use
12+
13+
Use measure authoring when:
14+
- No existing MCP tool does what the user needs
15+
- User explicitly asks for a "custom measure" or "Ruby/Python measure"
16+
- Complex model modifications requiring iteration over many objects
17+
- User wants reusable, repeatable model transformations
18+
19+
Do NOT use when an existing tool already does the job (e.g., `replace_air_terminals` for terminal swaps, `adjust_thermostat_setpoints` for thermostat changes).
20+
21+
## Workflow
22+
23+
### 1. Create the Measure
24+
```
25+
create_measure(
26+
name="set_lights_8w",
27+
description="Set all lights to 8 W/m2",
28+
language="Ruby",
29+
run_body=" model.getLightsDefinitions.each { |ld| ld.setWattsperSpaceFloorArea(8.0) }\n runner.registerFinalCondition('Done')"
30+
)
31+
```
32+
33+
### 2. Test It
34+
```
35+
test_measure(measure_dir="/runs/custom_measures/set_lights_8w")
36+
```
37+
38+
### 3. Apply to Model
39+
```
40+
apply_measure(measure_dir="/runs/custom_measures/set_lights_8w")
41+
```
42+
43+
### 4. Verify Results (Before/After Comparison)
44+
For rigorous validation, run a baseline simulation BEFORE applying the measure:
45+
```
46+
save_osm_model(save_path="/runs/baseline.osm")
47+
run_simulation(osm_path="/runs/baseline.osm", epw_path="<epw>")
48+
extract_summary_metrics(run_id=<baseline_id>) # record baseline EUI
49+
50+
# reload, apply measure, re-simulate
51+
load_osm_model(osm_path="<original>")
52+
apply_measure(measure_dir="/runs/custom_measures/set_lights_8w")
53+
save_osm_model(save_path="/runs/retrofit.osm")
54+
run_simulation(osm_path="/runs/retrofit.osm", epw_path="<epw>")
55+
extract_summary_metrics(run_id=<retrofit_id>) # compare to baseline
56+
```
57+
58+
## Language Choice
59+
60+
- **Ruby**: Preferred for most measures. Matches OpenStudio SDK documentation and existing measure libraries.
61+
- **Python**: Works but less common. Use when user prefers Python.
62+
63+
## Common run_body Patterns (Ruby)
64+
65+
### Envelope
66+
```ruby
67+
model.getLightsDefinitions.each { |ld| ld.setWattsperSpaceFloorArea(8.0) }
68+
model.getSpaceInfiltrationDesignFlowRates.each { |inf| inf.setFlowperExteriorSurfaceArea(0.0003) }
69+
model.getSurfaces.each { |s| next unless s.outsideBoundaryCondition == 'Outdoors'; ... }
70+
```
71+
72+
### HVAC
73+
```ruby
74+
model.getAirLoopHVACs.each do |loop|
75+
loop.thermalZones.each do |zone|
76+
loop.removeBranchForZone(zone)
77+
# create new terminal...
78+
loop.addBranchForZone(zone, terminal.to_StraightComponent.get)
79+
end
80+
end
81+
```
82+
83+
### Zone Equipment
84+
```ruby
85+
model.getThermalZones.each do |zone|
86+
bb = OpenStudio::Model::ZoneHVACBaseboardConvectiveElectric.new(model)
87+
bb.setName("#{zone.name} Baseboard")
88+
bb.addToThermalZone(zone)
89+
end
90+
```
91+
92+
### Air Terminals (beams)
93+
```ruby
94+
# CooledBeam (2-pipe, cooling only)
95+
coil = OpenStudio::Model::CoilCoolingCooledBeam.new(model)
96+
terminal = OpenStudio::Model::AirTerminalSingleDuctConstantVolumeCooledBeam.new(model, sch, coil)
97+
98+
# FourPipeBeam (4-pipe, heating + cooling)
99+
cc = OpenStudio::Model::CoilCoolingFourPipeBeam.new(model)
100+
hc = OpenStudio::Model::CoilHeatingFourPipeBeam.new(model)
101+
terminal = OpenStudio::Model::AirTerminalSingleDuctConstantVolumeFourPipeBeam.new(model, cc, hc)
102+
```
103+
104+
WARNING: Beams are AIR TERMINALS (connect via `air_loop.addBranchForZone`), NOT zone equipment (`addToThermalZone`).
105+
106+
## Notes
107+
108+
- `run_body` indentation matters: Ruby = 4 spaces, Python = 8 spaces
109+
- Always call `runner.registerFinalCondition("msg")` at end of run body
110+
- `create_measure` is idempotent (overwrites existing measure with same name)
111+
- Use `edit_measure` to modify an existing measure without recreating from scratch
112+
- Use `list_custom_measures` to see all created measures

.claude/skills/retrofit/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,4 @@ Present side-by-side comparison:
7575
- Always save to a new path before re-simulating to preserve the baseline
7676
- Multiple ECMs can be stacked before re-simulating
7777
- Some measures modify the model in-memory (thermostat, window); others run as OpenStudio measures
78+
- For custom ECMs not covered by existing tools, use `create_measure` to write a custom measure (see `measure-authoring` skill)

.claude/skills/tool-workflows/SKILL.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,33 @@ apply_measure(measure_dir="/inputs/measures/my_measure",
9393

9494
Note: All measure arguments are strings. Booleans → `"true"` / `"false"`. Numbers → `"42"`.
9595

96+
## Write and Apply a Custom Measure
97+
98+
Full chain: create → test → apply → simulate → compare results.
99+
100+
```
101+
# 1. Baseline simulation
102+
save_osm_model(save_path="/runs/baseline.osm")
103+
run_simulation(osm_path="/runs/baseline.osm", epw_path="<epw>")
104+
extract_summary_metrics(run_id=<baseline_id>)
105+
106+
# 2. Create custom measure
107+
create_measure(name="my_measure", description="...",
108+
language="Ruby", run_body=" model.get...each { |x| ... }")
109+
test_measure(measure_dir="/runs/custom_measures/my_measure")
110+
111+
# 3. Reload original model, apply measure, re-simulate
112+
load_osm_model(osm_path="<original>")
113+
apply_measure(measure_dir="/runs/custom_measures/my_measure")
114+
save_osm_model(save_path="/runs/retrofit.osm")
115+
run_simulation(osm_path="/runs/retrofit.osm", epw_path="<epw>")
116+
extract_summary_metrics(run_id=<retrofit_id>)
117+
118+
# 4. Compare baseline vs retrofit EUI
119+
```
120+
121+
See the `measure-authoring` skill for run_body patterns and language guidance.
122+
96123
## Object Cleanup
97124

98125
```

.github/workflows/ci.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,23 +61,23 @@ jobs:
6161
mkdir -p runs
6262
case ${{ matrix.shard }} in
6363
1)
64-
# sim test + component/weather + loop ops + skill_simulate + skill_retrofit
65-
FILES="tests/test_example_workflows.py tests/test_component_properties.py tests/test_comstock.py tests/test_weather.py tests/test_mcp_seb4.py tests/test_create_constructions.py tests/test_loop_operations.py tests/test_plant_loop_demand.py tests/test_sizing_properties.py tests/test_skill_simulate.py tests/test_skill_retrofit.py tests/test_integration.py"
64+
# sim test + component/weather + loop ops + skill_retrofit
65+
FILES="tests/test_example_workflows.py tests/test_component_properties.py tests/test_comstock.py tests/test_weather.py tests/test_mcp_seb4.py tests/test_create_constructions.py tests/test_loop_operations.py tests/test_plant_loop_demand.py tests/test_sizing_properties.py tests/test_skill_retrofit.py tests/test_integration.py"
6666
EXTRA_ENV="-e MCP_OSW_PATH=tests/assets/SEB_model/SEB4_baseboard/workflow.osw -e EXPECTED_EUI=1.8750760248144998 -e EXPECTED_EUI_RTOL=0.02 -e EXPECTED_EUI_ATOL=0.0"
6767
;;
6868
2)
69-
# common_measures (20 tests), hvac_systems, geometry, zone terminal, skill_energy_report, skill_new_building, validation sys1-4 + sys7-8
70-
FILES="tests/test_common_measures.py tests/test_hvac_systems.py tests/test_replace_zone_terminal.py tests/test_geometry.py tests/test_bar_building.py tests/test_skill_energy_report.py tests/test_skill_new_building.py tests/test_hvac_validation_sys1_4.py tests/test_hvac_validation_sys7_8.py"
69+
# common_measures, hvac_systems, geometry, zone terminal, skill_energy_report, hvac_validation (consolidated)
70+
FILES="tests/test_common_measures.py tests/test_hvac_systems.py tests/test_replace_zone_terminal.py tests/test_geometry.py tests/test_bar_building.py tests/test_skill_energy_report.py tests/test_hvac_validation.py"
7171
EXTRA_ENV=""
7272
;;
7373
3)
74-
# controls, object mgmt, loads, building, doas, hvac, measures, skill_qaqc, skill_add_hvac, validation sys5-6
75-
FILES="tests/test_component_controls.py tests/test_object_management.py tests/test_generic_access.py tests/test_create_loads.py tests/test_building.py tests/test_doas_system.py tests/test_hvac.py tests/test_measures.py tests/test_skill_qaqc.py tests/test_skill_add_hvac.py tests/test_hvac_validation_sys5_6.py tests/test_hvac_supply_wiring.py"
74+
# controls, object mgmt, loads, building, doas, hvac, measures, measure_authoring, skill_qaqc, hvac_supply_wiring
75+
FILES="tests/test_component_controls.py tests/test_object_management.py tests/test_generic_access.py tests/test_create_loads.py tests/test_building.py tests/test_doas_system.py tests/test_hvac.py tests/test_measures.py tests/test_measure_authoring.py tests/test_skill_qaqc.py tests/test_hvac_supply_wiring.py"
7676
EXTRA_ENV=""
7777
;;
7878
4)
79-
# vrf, radiant, query skills, creation, air terminals, results extraction, validation sys9-10
80-
FILES="tests/test_vrf_system.py tests/test_radiant_system.py tests/test_loads.py tests/test_spaces.py tests/test_space_types.py tests/test_schedules.py tests/test_constructions.py tests/test_create_space.py tests/test_create_thermal_zone.py tests/test_add_air_loop.py tests/test_create_schedule_ruleset.py tests/test_add_output_variable.py tests/test_add_output_meter.py tests/test_load_save_model.py tests/test_versions.py tests/test_create_example_osm.py tests/test_inspect_osm_summary.py tests/test_copy_file.py tests/test_replace_air_terminals.py tests/test_results_extraction.py tests/test_swig_memleak_cleanup.py tests/test_skill_view.py tests/test_skill_tools_integration.py tests/test_hvac_validation_sys9_10.py tests/test_stdio_smoke.py tests/test_response_sizes.py"
79+
# vrf, radiant, query skills, creation, air terminals, results extraction
80+
FILES="tests/test_vrf_system.py tests/test_radiant_system.py tests/test_loads.py tests/test_spaces.py tests/test_space_types.py tests/test_schedules.py tests/test_constructions.py tests/test_create_space.py tests/test_create_thermal_zone.py tests/test_add_air_loop.py tests/test_create_schedule_ruleset.py tests/test_add_output_variable.py tests/test_add_output_meter.py tests/test_load_save_model.py tests/test_versions.py tests/test_create_example_osm.py tests/test_inspect_osm_summary.py tests/test_copy_file.py tests/test_replace_air_terminals.py tests/test_results_extraction.py tests/test_swig_memleak_cleanup.py tests/test_skill_tools_integration.py tests/test_stdio_smoke.py tests/test_response_sizes.py"
8181
EXTRA_ENV=""
8282
;;
8383
5)

CLAUDE.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
# CLAUDE.md — Instructions for Claude Code
22

33
## Project: openstudio-mcp
4-
Building energy simulation MCP server using OpenStudio SDK Python bindings and Measures.
4+
MCP server that gives AI agents full control of building energy modeling —
5+
create buildings, configure HVAC, run EnergyPlus simulations, and extract
6+
results — all through 134 MCP tools backed by the OpenStudio SDK.
57

6-
**Template project:** This codebase is designed as a reference implementation
7-
that other building energy simulation engines (EnergyPlus, TRNSYS, DOE-2, etc.)
8-
can use as a template. Code must be explicit, well-commented, and easy for
9-
contributors unfamiliar with OpenStudio to understand and adapt.
8+
**Who it's for:** Building energy modelers who want to use AI assistants
9+
(Claude, GPT, etc.) to do real modeling work — not just chat about it.
10+
The MCP tools replace manual GUI workflows in the OpenStudio Application
11+
with tool calls an LLM can chain together autonomously.
12+
13+
**Template project:** Designed as a reference implementation that other
14+
building energy simulation engines (EnergyPlus, TRNSYS, DOE-2, etc.)
15+
can fork and adapt. Code is explicit, well-commented, and accessible
16+
to contributors unfamiliar with OpenStudio.
1017

1118
## Critical: Use MCP Tools — Do Not Reinvent
1219
Always use openstudio-mcp tools for BEM tasks:
@@ -41,6 +48,7 @@ Always use openstudio-mcp tools for BEM tasks:
4148
| Phase 6 | ✅ COMPLETE | Loads, object management, weather & measures (3 skills, 13 tools) |
4249
| Phase 7 | 📋 FUTURE | Advanced creation (geometry, space type wizard) |
4350
| Phase 8 | ✅ COMPLETE | Bundle common-measures-gem (20 measures, 11 tools: reporting, thermostat, envelope, PV, visualization) |
51+
| Phase 9 | ✅ COMPLETE | AI-assisted measure authoring (3 tools: create, test, edit custom measures) |
4452

4553
## Current Skills
4654
| Skill | Tools | Phase |
@@ -60,15 +68,16 @@ Always use openstudio-mcp tools for BEM tasks:
6068
| `simulation_outputs` | `add_output_variable`, `add_output_meter` | Phase 3 |
6169
| `hvac_systems` | `add_baseline_system`, `list_baseline_systems`, `get_baseline_system_info`, `replace_air_terminals`, `replace_zone_terminal`, `add_doas_system`, `add_vrf_system`, `add_radiant_system` | Phase 4 |
6270
| `component_properties` | `get_component_properties`, `set_component_properties`, `set_economizer_properties`, `set_sizing_properties`, `set_sizing_system_properties`, `get_sizing_system_properties`, `set_sizing_zone_properties`, `get_sizing_zone_properties`, `get_setpoint_manager_properties`, `set_setpoint_manager_properties` | Phase 5 |
63-
| `loop_operations` | `create_plant_loop`, `add_supply_equipment`, `remove_supply_equipment`, `add_demand_component`, `remove_demand_component`, `add_zone_equipment`, `remove_zone_equipment`, `remove_all_zone_equipment` | Phase 5 |
71+
| `loop_operations` | `create_plant_loop`, `add_supply_equipment`, `remove_supply_equipment`, `add_demand_component`, `remove_demand_component`, `add_zone_equipment`, `remove_zone_equipment`, `remove_all_zone_equipment`, `set_zone_equipment_priority` | Phase 5 |
6472
| `object_management` | `delete_object`, `rename_object`, `list_model_objects`, `get_object_fields`, `set_object_property` | Phase 6B |
6573
| `weather` | `get_weather_info`, `add_design_day`, `get_simulation_control`, `set_simulation_control`, `get_run_period`, `set_run_period` | Phase 6C |
6674
| `measures` | `list_measure_arguments`, `apply_measure` | Phase 6D |
6775
| `comstock` | `list_comstock_measures`, `create_bar_building`, `create_typical_building`, `create_new_building` | ComStock |
6876
| `common_measures` | `list_common_measures`, `view_model`, `view_simulation_data`, `generate_results_report`, `run_qaqc_checks`, `adjust_thermostat_setpoints`, `replace_window_constructions`, `enable_ideal_air_loads`, `clean_unused_objects`, `change_building_location`, `set_thermostat_schedules`, `replace_thermostat_schedules`, `shift_schedule_time`, `add_rooftop_pv`, `add_pv_to_shading`, `add_ev_load`, `add_zone_ventilation`, `set_lifecycle_cost_params`, `add_cost_per_floor_area`, `set_adiabatic_boundaries` | Phase 8 |
77+
| `measure_authoring` | `list_custom_measures`, `create_measure`, `test_measure`, `edit_measure` | Phase 9 |
6978
| `skill_discovery` | `list_skills`, `get_skill` ||
7079

71-
**Total: 22 skills, 131 MCP tools, ~310 integration tests**
80+
**Total: 23 skills, 134 MCP tools, ~390 integration tests**
7281

7382
## Model Query Pattern
7483
```python
@@ -186,11 +195,6 @@ To add a new SPM type to `set_setpoint_manager_properties`:
186195
3. Add an entry to `SPM_TYPES` registry with getter method name and get/set functions.
187196
4. Add a test in `tests/test_component_controls.py`.
188197

189-
## Active Fix Plan
190-
See `docs/debug/fix-plan.md` for current work items (11 items from Claude Desktop
191-
debug sessions). Key changes: remove inject_idf, rename read_run_artifact→read_file,
192-
list_files redesign, sizing system/zone tools, 6 new SPM types.
193-
194198
## Rules
195199
1. Keep files small where practical — aim for under 250 lines, but don't split artificially just to hit a number
196200
2. Every MCP tool must have a test in `tests/skills/` (Phase 2+) or `tests/` (existing)

0 commit comments

Comments
 (0)