Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ BOSS sits between BuildingSync XML data and OpenStudio workflow execution. Sibli

- `../schema` — BuildingSync schema source and examples. Use it to verify XML structure, element names, `ScenarioType`, `PackageOfMeasures`, `Measure`, `MeasureID`, and schema-version behavior. Do not edit it unless the task asks for schema work.
- `../BuildingSync-gem` — legacy Ruby translator. Use it for workflow concepts and mapping ideas, but verify every behavior against current BOSS dependencies and BuildingSync v2.7 fixtures.
- `../buildingsync-measures-gem` — related BuildingSync/OpenStudio measure work. Use it as reference for measure patterns.
- `../buildingsync-measures-gem` — custom BuildingSync OpenStudio measures. Preferred measure source for scenario mappings when a measure fits. New measures BOSS needs that do not exist elsewhere belong here, not in BOSS. Only map to measures merged into the ref BOSS loads.
- `../openstudio-standards` — OpenStudio standards logic used by the `openstudio-standards` gem. Do not edit it from BOSS unless the issue is proven upstream and the user asks for that change.
- OpenStudio measure gems declared in `BOSS.gemspec` — `openstudio-common-measures`, `openstudio-ee`, `openstudio-extension`, `openstudio-model-articulation`, and `openstudio-standards`. Verify scenario mapping measure names and arguments against the installed versions used by BOSS.

Expand Down
31 changes: 26 additions & 5 deletions docs/scenario_workflow_translation_plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,17 @@ Lookup order:
1. `SystemCategoryAffected` plus `MeasureName`.
2. Technology category element plus `MeasureName`, when `SystemCategoryAffected` is missing or not useful.

The legacy `../BuildingSync-gem` mapping can be used as background, but do not copy it directly. Every measure directory and argument added to BOSS must be checked against the OpenStudio gems declared in `BOSS.gemspec`.
The legacy `../BuildingSync-gem` mapping can be used as background, but do not copy it directly.

Measure source order for new mappings:

1. `../buildingsync-measures-gem` custom BuildingSync measures, when one fits the BuildingSync measure semantics.
2. OpenStudio measure gems declared in `BOSS.gemspec`.
3. If neither has an applicable measure, add one to `buildingsync-measures-gem` instead of BOSS.

Verify every measure directory and argument against the source BOSS will load: the `BOSS.gemspec` gem versions, or the merged `buildingsync-measures-gem` ref. Do not map to measures that exist only on an unmerged branch.

Keep relative-change mappings (for example "reduce by percentage") on relative measures. Do not swap them for absolute-target measures unless the measure skips models that already meet the target.

Conditional arguments should be data-driven. Use context from the reader, such as building type or principal HVAC system type, instead of per-measure Ruby condition chains.

Expand Down Expand Up @@ -171,8 +181,8 @@ Use this roadmap as the tracker. Keep `Status` and `Delivered by` current when a

### PR 5: Initial Mapping JSON

- Status: Planned
- Delivered by: -
- Status: Done
- Delivered by: PR #10 branch
- Scope: Add `lib/BOSS/scenario_measure_map.json` with a small verified mapping set from `building_151.xml`.
- Done when: The JSON is valid and each entry has a source category/name, target `measure_dir_name`, and arguments.
- Avoid: Copying legacy mappings without checking current measure directories and arguments.
Expand Down Expand Up @@ -229,8 +239,8 @@ Use this roadmap as the tracker. Keep `Status` and `Delivered by` current when a

- Status: Planned
- Delivered by: -
- Scope: Deep-copy baseline OSW and append mapped retrofit steps in deterministic order.
- Done when: OSW tests assert baseline steps plus expected mapped measure steps and arguments.
- Scope: Deep-copy baseline OSW and append mapped retrofit steps in deterministic order. Make sure `buildingsync-measures-gem` measures are on the OSW `measure_paths`; `develop` already provides `config/external_measure_repos.yml` for this.
- Done when: OSW tests assert baseline steps plus expected mapped measure steps and arguments, and every mapped `measure_dir_name` resolves from the OSW `measure_paths`.
- Avoid: Mutating the baseline OSW, omitting baseline creation steps, or using nondeterministic step order.

### PR 13: Generic OSW Step Helpers
Expand Down Expand Up @@ -334,6 +344,17 @@ Use this section for stable facts future PRs need. Keep entries short and tied t
- Verification: `C:\Ruby32-x64\bin\ruby.exe -S bundle exec rubocop lib/BOSS/buildingsync_reader/buildingsync_reader.rb spec/tests/unit/buildingsync_reader_spec.rb` - still reports existing style debt and new-cop configuration warnings.
- Follow-up: PR 5 can add mapping JSON. Mapping-specific warnings remain PR 6/PR 9 scope.

### PR 5: Initial Mapping JSON

- Status: Done
- Delivered by: PR #10 branch
- Handoff: `lib/BOSS/scenario_measure_map.json` maps 12 `building_151.xml` category/name pairs to OSW-shaped steps with `measure_dir_name` and an argument hash that includes `__SKIP__`. Source strings match reader output exactly.
- Handoff: Roof and wall absolute insulation targets use `buildingsync-measures-gem` `modify_envelope_insulation` with SI R-values (R-30 IP = 5.28, R-25 IP = 4.40 m²K/W). Other mappings use `openstudio-ee` or `openstudio-common-measures`. Relative mappings such as air sealing, ceiling insulation by percentage, and thermal bypasses stay on relative measures.
- Deferred: Daylight controls, package unit replacement, burner replacement, and boiler replacement. The `buildingsync-measures-gem` split HVAC measures (`modify_existing_hvac_equipment_efficiencies`, `modify_existing_plant_equipment`, `hvac_packaged_rooftop_*`) need exact model object or zone names. Map them in PR 8 with context rules after `buildingsync-measures-gem` PR #3 merges.
- Verification: JSON parse passed; every mapped measure directory and argument was checked against installed `BOSS.gemspec` gems and `buildingsync-measures-gem` `develop`.
- Verification: `C:\Ruby32-x64\bin\ruby.exe -S bundle exec rspec spec/tests/unit/scenario_measure_map_spec.rb`.
- Follow-up: PR 6 adds `BOSS::ScenarioMeasureMapper`. PR 12 must put `buildingsync-measures-gem` on OSW `measure_paths`.

## Verification Commands

Use the narrowest command that proves the current PR. Broaden only when the change justifies it.
Expand Down
177 changes: 177 additions & 0 deletions lib/BOSS/scenario_measure_map.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
{
"schema_version": "1.0",
"lookup_order": [
"system_category_affected_and_measure_name"
],
"mappings": [
{
"system_category_affected": "Air Distribution",
"technology_category_element_name": "OtherHVAC",
"measure_name": "Add or repair economizer",
"steps": [
{
"measure_dir_name": "EnableEconomizerControl",
"arguments": {
"__SKIP__": false,
"economizer_type": "FixedDryBulb"
}
}
]
},
{
"system_category_affected": "Air Distribution",
"technology_category_element_name": "OtherHVAC",
"measure_name": "Install demand control ventilation",
"steps": [
{
"measure_dir_name": "EnableDemandControlledVentilation",
"arguments": {
"__SKIP__": false,
"dcv_type": "EnableDCV"
}
}
]
},
{
"system_category_affected": "Ceiling",
"technology_category_element_name": "BuildingEnvelopeModifications",
"measure_name": "Increase ceiling insulation",
"steps": [
{
"measure_dir_name": "IncreaseInsulationRValueForRoofsByPercentage",
"arguments": {
"__SKIP__": false,
"r_value": 20
}
}
]
},
{
"system_category_affected": "General Controls and Operations",
"technology_category_element_name": "OtherHVAC",
"measure_name": "Upgrade operating protocols, calibration, and/or sequencing",
"steps": [
{
"measure_dir_name": "AdjustThermostatSetpointsByDegrees",
"arguments": {
"__SKIP__": false,
"cooling_adjustment": 1.0,
"heating_adjustment": -1.0
}
}
]
},
{
"system_category_affected": "Lighting",
"technology_category_element_name": "LightingImprovements",
"measure_name": "Add occupancy sensors",
"steps": [
{
"measure_dir_name": "ReduceLightingLoadsByPercentage",
"arguments": {
"__SKIP__": false,
"lighting_power_reduction_percent": 5
}
}
]
},
{
"system_category_affected": "Lighting",
"technology_category_element_name": "LightingImprovements",
"measure_name": "Retrofit with light emitting diode technologies",
"steps": [
{
"measure_dir_name": "SetLightingLoadsByLPD",
"arguments": {
"__SKIP__": false,
"lpd": 0.6
}
}
]
},
{
"system_category_affected": "Plug Load",
"technology_category_element_name": "PlugLoadReductions",
"measure_name": "Install plug load controls",
"steps": [
{
"measure_dir_name": "ReduceElectricEquipmentLoadsByPercentage",
"arguments": {
"__SKIP__": false,
"elecequip_power_reduction_percent": 20.0
}
}
]
},
{
"system_category_affected": "Refrigeration",
"technology_category_element_name": "Refrigeration",
"measure_name": "Replace ice/refrigeration equipment with high efficiency units",
"steps": [
{
"measure_dir_name": "ReduceElectricEquipmentLoadsByPercentage",
"arguments": {
"__SKIP__": false,
"elecequip_power_reduction_percent": 5
}
}
]
},
{
"system_category_affected": "Roof",
"technology_category_element_name": "BuildingEnvelopeModifications",
"measure_name": "Increase roof insulation",
"steps": [
{
"measure_dir_name": "modify_envelope_insulation",
"arguments": {
"__SKIP__": false,
"roof_target_rvalue": 5.28
}
}
]
},
{
"system_category_affected": "Wall",
"technology_category_element_name": "BuildingEnvelopeModifications",
"measure_name": "Air seal envelope",
"steps": [
{
"measure_dir_name": "ReduceSpaceInfiltrationByPercentage",
"arguments": {
"__SKIP__": false,
"space_infiltration_reduction_percent": 30.0
}
}
]
},
{
"system_category_affected": "Wall",
"technology_category_element_name": "BuildingEnvelopeModifications",
"measure_name": "Increase wall insulation",
"steps": [
{
"measure_dir_name": "modify_envelope_insulation",
"arguments": {
"__SKIP__": false,
"wall_target_rvalue": 4.4
}
}
]
},
{
"system_category_affected": "Wall",
"technology_category_element_name": "BuildingEnvelopeModifications",
"measure_name": "Insulate thermal bypasses",
"steps": [
{
"measure_dir_name": "IncreaseInsulationRValueForExteriorWallsByPercentage",
"arguments": {
"__SKIP__": false,
"r_value": 20
}
}
]
}
]
}
72 changes: 72 additions & 0 deletions spec/tests/unit/scenario_measure_map_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# frozen_string_literal: true

require 'json'

SCENARIO_MEASURE_MAP_PATH = File.expand_path('../../../lib/BOSS/scenario_measure_map.json', __dir__)

RSpec.describe 'scenario_measure_map.json' do
let(:scenario_measure_map) { JSON.parse(File.read(SCENARIO_MEASURE_MAP_PATH)) }
let(:mappings) { scenario_measure_map.fetch('mappings') }

it 'defines required mapping fields for each entry' do
expect(scenario_measure_map.fetch('schema_version')).to eq '1.0'
expect(mappings).not_to be_empty

mappings.each do |mapping|
expect(mapping.fetch('system_category_affected')).not_to be_empty
expect(mapping.fetch('measure_name')).not_to be_empty
expect(mapping.fetch('steps')).not_to be_empty

mapping.fetch('steps').each do |step|
expect(step.fetch('measure_dir_name')).not_to be_empty
expect(step.fetch('arguments')).to be_a Hash
end
end
end

it 'uses OSW-shaped argument hashes without conditional mapping rules' do
mappings.each do |mapping|
mapping.fetch('steps').each do |step|
expect(step.fetch('arguments')).to include('__SKIP__')
expect(step.fetch('arguments')).not_to include('condition')
end
end
end

it 'covers the verified initial building_151 measure set' do
expected_sources = [
['Air Distribution', 'Add or repair economizer'],
['Air Distribution', 'Install demand control ventilation'],
['Ceiling', 'Increase ceiling insulation'],
['General Controls and Operations', 'Upgrade operating protocols, calibration, and/or sequencing'],
['Lighting', 'Add occupancy sensors'],
['Lighting', 'Retrofit with light emitting diode technologies'],
['Plug Load', 'Install plug load controls'],
['Refrigeration', 'Replace ice/refrigeration equipment with high efficiency units'],
['Roof', 'Increase roof insulation'],
['Wall', 'Air seal envelope'],
['Wall', 'Increase wall insulation'],
['Wall', 'Insulate thermal bypasses']
]

actual_sources = mappings.map do |mapping|
[mapping.fetch('system_category_affected'), mapping.fetch('measure_name')]
end

expect(actual_sources).to eq expected_sources
end

it 'uses buildingsync-measures-gem envelope insulation targets in SI units' do
targets = mappings.to_h do |mapping|
[[mapping.fetch('system_category_affected'), mapping.fetch('measure_name')], mapping.fetch('steps').first]
end

roof_step = targets.fetch(['Roof', 'Increase roof insulation'])
wall_step = targets.fetch(['Wall', 'Increase wall insulation'])

expect(roof_step.fetch('measure_dir_name')).to eq 'modify_envelope_insulation'
expect(roof_step.fetch('arguments')).to include('roof_target_rvalue' => 5.28)
expect(wall_step.fetch('measure_dir_name')).to eq 'modify_envelope_insulation'
expect(wall_step.fetch('arguments')).to include('wall_target_rvalue' => 4.4)
end
end
Loading