Skip to content

Commit

Permalink
Merge branch 'RT/JG/ruletest_12_3' of https://github.com/pnnl/ruleset…
Browse files Browse the repository at this point in the history
…-checking-tool into RS/YJ/Rule_12-3
  • Loading branch information
yunjoonjung-PNNL committed Jul 30, 2024
2 parents d744aa7 + b61f494 commit 04cb834
Show file tree
Hide file tree
Showing 235 changed files with 1,039,464 additions and 1,261,304 deletions.
7 changes: 4 additions & 3 deletions docs/_toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,10 @@ These conventions are used in all RDS below, and the logic of evaluating rules f
* [10-14](section10/10-14.md): Baseline shall be modeled with the heating HVAC system efficiency per Tables G3.5.1-G3.5.6 (applies only to the heating efficiency of baseline furnaces and heat pumps). Where multiple HVAC zones or residential spaces are combined into a single thermal block the heating efficiencies (for baseline HVAC System Types 3 and 4) shall be based on the equipment capacity of the thermal block divided by the number of HVAC zones or residential spaces.

## Section 12 - Receptacles and Other Loads
* [12-1](section12/Rule12-1.md): Number of spaces modeled in User RMD and Baseline RMD are the same
* [12-2](section12/Rule12-2.md): Number of spaces modeled in User RMD and Proposed RMD are the same
* [12-3](section12/Rule12-3.md): User RMD Space Name in Proposed RMD?
* [12-1](section12/Rule12-1.md): Receptacle and process power shall be modeled as identical to the proposed design
* [12-2](section12/Rule12-2.md): Depending on the space type, receptacle controls may be required by 90.1 Section 8.4.2. Receptacle schedules shall be modeled identically to the proposed design except when receptacle controls are specified in the proposed design for spaces where not required by Section 8.4.2.
* [12-3](section12/Rule12-3.md): When receptacle controls are specified in the proposed building design for spaces where not required by Standard 90.1 2019 Section 8.4.2, the hourly receptacle schedule shall be reduced as specified in Standard 90.1-2019 Table G3.1 Section 12 Proposed Building Performance column.
* [12-4](section12/Rule12-4.md): Computer room equipment schedules shall be modeled as a constant fraction of the peak design load per the following monthly schedule: Months 1, 5, 9 — 25%; Months 2, 6, 10 — 50%; Months 3, 7, 11 — 75%; Months 4, 8, 12 — 100%.

## Section 15 - Distribution Transformers
* [15-1](section15/Rule15-1.md): Number of transformers modeled in User RMD and Baseline RMD are the same
Expand Down
2 changes: 1 addition & 1 deletion docs/section10/10-14.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
- Else, set the was_capacity_not_defined boolean variable equal to TRUE: `Else: was_cool_capacity_not_defined = TRUE`

- SYSTEM 4 <65000
- Check if cooling capacity is <65,000 Btuh OR the zone aggregation factor was defined and the capacity is less than 65000 Btuh: `if (zone_list_b[0].aggregation_factor != Null AND total_heating_capacity_b/zone_list_b[0].aggregation_factor < 65000) OR total_cooling_capacity_b < 65000 AND was_cool_capacity_not_defined == FALSE:`
- Check if cooling capacity is <65,000 Btuh OR the zone aggregation factor was defined and the capacity is less than 65000 Btuh: `if (zone_list_b[0].aggregation_factor != Null AND total_cooling_capacity_b/zone_list_b[0].aggregation_factor < 65000) OR total_cooling_capacity_b < 65000 AND was_cool_capacity_not_defined == FALSE:`
- Set the boolean variable to FALSE: `Else: zone_aggregation_factor_undefined_and_needed = FALSE`
- Set the expected baseline efficiency for <65000 (if the modeled part OR full load efficiency values equals 3.4 then it passes; this is captured in the subsequent logic): `expected_baseline_eff_b = 3.4`
- Loop through each of the efficiency_metric_types associated with the heating system until HEAT_PUMP_COEFFICIENT_OF_PERFORMANCE_HIGH_TEMPERATURE_NO_FAN is found. Get the associated value of the efficiency from the list of efficiency_metric_values associated with the heating_system_b. : `For x in range(len(heating_system_b.efficiency_metric_types)):`
Expand Down
70 changes: 36 additions & 34 deletions docs/section12/Rule12-1.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@

# Receptacle - Rule 12-1

# Receptacles – Rule 12-1
**Schema Version:** 0.0.37
**Mandatory Rule:** True
**Rule ID:** 12-1
**Rule Description:** Number of spaces modeled in User RMR and Baseline RMR are the same
**Rule Assertion:** Baseline RMR = User RMR
**Appendix G Section:** Section Table G3.1-12 Modeling Requirements for the Proposed design
**Appendix G Section Reference:** None

**Applicability:** All required data elements exist for B_RMR and U_RMR
**Applicability Checks:** None
**Manual Check:** None
**Evaluation Context:** Each Data Element
**Rule Description:** Receptacle and process power shall be modeled as identical to the proposed design
**Rule Assertion:** B-RMD = P-RMD
**Appendix G Section:** G3.1
**Appendix G Section Reference:** Table G3.1-12
**Data Lookup:** None

## Rule Logic:

- Get the total number of spaces in the building segment in the User model: ```for building_segment_user in U_RMR.building.building_segments:```

- For each thermal_block in building segment: ```thermal_block_user in building_segment_user.thermal_blocks:```

- For each zone in thermal block: ```zone_user in thermal_block_user.zones:```

- For each space in zone, add to the total number of spaces in the User model: ```for space_user in zone_user.spaces: num_of_spaces_user += 1```

- Get the total number of spaces in the building segment in the Baseline model: ```for building_segment_baseline in B_RMR.building.building_segments:```

- For each thermal_block in building segment: ```thermal_block_baseline in building_segment.thermal_blocks:```

- For each zone in thermal block: ```zone_baseline in thermal_block_baseline.zones:```

- For each space in zone, add to the total number of spaces in the Baseline model: ```for space_baseline in zone_baseline.spaces: num_of_spaces_baseline += 1```

**Rule Assertion:** ```num_of_spaces_user == num_of_spaces_baseline```

**[Back](../_toc.md)**
**Evaluation Context:** Each RPD

**Applicability Checks:**
N/A

**Function Call:**
match_data_element

## Rule Logic:
- Determine the project's compliance path, if available: `compliance_path = RPD.get("compliance_path")`
- Store a list of any miscellaneous equipment power where proposed is greater than baseline: `unexpected_misc_equipment_power = []`
- Store a list of any miscellaneous equipment power where proposed is less than baseline: `reduced_misc_equipment_power = []`
- For each miscellaneous equipment load in the baseline RMD: `for misc_equipment_b in B_RMD...miscellaneous_equipment:`
- Get the corresponding miscellaneous equipment load in the proposed RMD: `misc_equipment_p = match_data_element(P_RMD, MiscellaneousEquipment, misc_equipment_b.id)`
- Get the miscellaneous equipment power in the proposed RMD: `misc_equipment_power_p = misc_equipment_p.power`
- Get the miscellaneous equipment power in the baseline RMD: `misc_equipment_power_b = misc_equipment_b.power`
- If the miscellaneous equipment power in the baseline RMD is greater than the miscellaneous equipment power in the proposed RMD: `if misc_equipment_power_b > misc_equipment_power_p:`
- Append the miscellaneous equipment info for reporting: `reduced_misc_equipment_power.append({"id": misc_equipment_b.id, "baseline_power": misc_equipment_power_b, "proposed_power": misc_equipment_power_p})`
- If the miscellaneous equipment power in the baseline RMD is less than the miscellaneous equipment power in the proposed RMD: `elif misc_equipment_power_b < misc_equipment_power_p:`
- Append the miscellaneous equipment info for reporting: `unexpected_misc_equipment_power.append({"id": misc_equipment_b.id, "baseline_power": misc_equipment_power_b, "proposed_power": misc_equipment_power_p})`
**Rule Assertion:**
- Case 1: If all equipment power was modeled identically: PASS `if len(unexpected_misc_equipment_power) == 0 and len(reduced_misc_equipment_power) == 0: PASS`
- Case 2: Else if any equipment power is reduced in the proposed and the compliance path is not "CODE_COMPLIANT" (includes when compliance path is not specified): UNDETERMINED: `elif len(unexpected_misc_equipment_power) == 0 and compliance_path != "CODE_COMPLIANT": UNDETERMINED and raise_message="The proposed building miscellaneous equipment load is less than the baseline, which is only permitted when the model is being used to quantify performance that exceeds the requirements of Standard 90.1."`
- Case 3: Else, proposed equipment power is greater than the baseline, or the compliance path is code-compliant and proposed equipment power is less than the baseline: `else: FAIL`

**Notes/Questions:**
None

**[Back](../_toc.md)**
56 changes: 30 additions & 26 deletions docs/section12/Rule12-2.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,40 @@

# Receptacle - Rule 12-2

# Receptacles - Rule 12-2
**Rule ID:** 12-2
**Rule Description:** Number of spaces modeled in User RMR and Proposed RMR are the same
**Rule Assertion:** Proposed RMR = User RMR
**Appendix G Section:** Section Table G3.1-12 Modeling Requirements for the Proposed design
**Rule Description:** Depending on the space type, receptacle controls may be required by 90.1 Section 8.4.2. Receptacle schedules shall be modeled identically to the proposed design except when receptacle controls are specified in the proposed design for spaces where not required by Section 8.4.2.
**Rule Assertion:** Baseline RMD = Proposed RMD
**Appendix G Section:** Table G3.1-12 Proposed Building Performance column
**Appendix G Section Reference:** None

**Applicability:** All required data elements exist for P_RMR and U_RMR
**Applicability Checks:** None
**Manual Check:** None
**Evaluation Context:** Each Data Element
**Data Lookup:** None
**Evaluation Context:** Each miscellaneous equipment load

## Rule Logic:

- Get the total number of spaces in the building segment in the User model: ```for building_segment_user in U_RMR.building.building_segments:```
**Applicability Checks:** None

- For each thermal_block in building segment: ```thermal_block in building_segment.thermal_blocks:```
**Function Call**
match_data_element, compare_schedules

- For each zone in thermal block: ```zone_user in thermal_block_user.zones:```

- For each space in zone, add to the total number of spaces in the User model: ```for space_user in thermal_zone_user.spaces: num_of_spaces_user += 1```

- Get the total number of spaces in the building segment in the Proposed model: ```for building_segment_proposed in P_RMR.building.building_segments:```

- For each thermal_block in building segment: ```thermal_block_proposed in building_segment_proposed.thermal_blocks:```

- For each zone in thermal block: ```zone_proposed in thermal_block_proposed.zones:```

- For each space in zone, add to the total number of spaces in the Proposed model: ```for space_proposed in zone_proposed.spaces: num_of_spaces_proposed += 1```
## Rule Logic:
- Create a list of the lighting space types that correspond to space types that may have receptacle control requirements in Section 8.4.1: `EXPECTED_RECEPTACLE_CONTROL_SPACE_TYPES = ["OFFICE_ENCLOSED", "CONFERENCE_MEETING_MULTIPURPOSE_ROOM", "COPY_PRINT_ROOM", "LOUNGE_BREAKROOM_HEALTH_CARE_FACILITY", "LOUNGE_BREAKROOM_ALL_OTHERS", "CLASSROOM_LECTURE_HALL_TRAINING_ROOM_PENITENTIARY", "CLASSROOM_LECTURE_HALL_TRAINING_ROOM_SCHOOL", "CLASSROOM_LECTURE_HALL_TRAINING_ROOM_ALL_OTHER", "OFFICE_OPEN_PLAN"]`
- For each space in the baseline RMD: `for space_b in B_RMD.spaces:`
- Get the lighting space type: `space_type_b = space_b.lighting_space_type`
- For each miscellaneous equipment load in the space: `for misc_equip_b in space_b.miscellaneous_equipment:`
- Get the corresponding miscellaneous equipment load from the proposed RMD: `misc_equip_p = match_data_element(P_RMD, MiscellaneousEquipment, misc_equip_b.id)`
- Get the baseline miscellaneous equipment load schedule: `misc_equip_schedule_b = misc_equip_b.multiplier_schedule`
- Get the proposed miscellaneous equipment load schedule: `misc_equip_schedule_p = misc_equip_p.multiplier_schedule`
- Get the baseline automatic receptacle control: `auto_receptacle_control_b = misc_equip_b.has_automatic_control`
- Get the proposed automatic receptacle control: `auto_receptacle_control_p = misc_equip_p.has_automatic_control`
- If it is a leap year, set the schedule comparison mask to 8784 hours, else set it to 8760 hours: `mask_schedule = [1] * 8784 if is_leap_year else [1] * 8760`
- Compare the baseline miscellaneous equipment load schedule to the proposed miscellaneous equipment load schedule: `comparison_data = compare_schedules(misc_equip_schedule_b, misc_equip_schedule_p, mask_schedule)`

**Rule Assertion:**
Case 1: The baseline and proposed miscelleaneous equipment schedules match for all hours: PASS `if comparison_data['total_hours_matched'] == len(misc_equip_schedule_b == len(misc_equip_schedule_p): PASS`
Case 2: The baseline and proposed miscelleaneous equipment both have automatic controls, but schedules have unequal equivalent full load hours: FAIL and raise message `elif auto_receptacle_control_p and auto_receptacle_control_b and comparison_data["eflh_difference"] != 0: FAIL and raise_message = "The baseline miscellaneous equipment schedule has automatic receptacle controls indicating that there is an applicable requirement for automatic controls for the space in Section 8.4.2. Miscellaneous equipment schedules may only differ when the proposed design has automatic receptacle controls and there are no applicable requirements in Section 8.4.2 for the space."`
Case 3: The proposed miscellaneous equipment schedule has fewer equivalent full load hours than the baseline miscellaneous equipment schedule, the proposed has automatic receptacle control, and the space type is not expected to have receptacle control requirements in Section 8.4.2 : PASS `elif comparison_data["eflh_difference"] > 0 and auto_receptacle_controls_p and space_type_b not in EXPECTED_RECEPTACLE_CONTROL_SPACE_TYPES: PASS`
Case 4: The proposed miscellaneous equipment schedule has fewer equivalent full load hours than the baseline miscellaneous equipment schedule, the proposed has automatic receptacle control, and the space type may have receptacle control requirements in Section 8.4.2, or the lighting space type was not defined : UNDETERMINED and raise message`elif (comparison_data["eflh_difference"] > 0 and auto_receptacle_controls_p and space_type_b in EXPECTED_RECEPTACLE_CONTROL_SPACE_TYPES) or space_type_b is None: UNDETERMINED and raise_message="A reduced schedule and automatic receptacle controls are present in the proposed design. The space type may have receptacle control requirements in Section 8.4.2. If that is the case, there should be no reduced schedule modeled."`
Case 5: The proposed miscellaneous equipment schedule has fewer equivalent full load hours than the baseline miscellaneous equipment schedule, the proposed does not have automatic receptacle control : FAIL `elif comparison_data["eflh_difference"] > 0 and not auto_receptacle_controls_p: FAIL`
Case 6: The proposed miscellaneous equipment schedule has more equivalent full load hours than the baseline miscellaneous equipment schedule: FAIL `elif comparison_data["eflh_difference"] < 0: FAIL and raise_msg = "Rule evaluation fails with a conservative outcome. The proposed schedule equivalent full load hours is greater than the baseline."`
Case 7: The proposed automatic receptacle control was not specified: UNDETERMINED and raise message`elif auto_receptacle_control_p == Null: UNDETERMINED and raise_message = "The proposed miscellaneous equipment schedule has reduced equivalent full load hours compared the baseline but it could not be determined if automatic receptacle controls are present in the proposed design to justify the credit."`
Case 8: Else: FAIL `else: FAIL`

**Rule Assertion:** ```num_of_spaces_user == num_of_spaces_proposed```

**[Back](../_toc.md)**
52 changes: 52 additions & 0 deletions docs/section12/Rule12-4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Receptacle - Rule 12-4
**Schema Version** 0.0.37
**Primary Rule:** True
**Rule ID:** 12-4
**Rule Description:** Computer room equipment schedules shall be modeled as a constant fraction of the peak design load per the following monthly schedule: Months 1, 5, 9 — 25%; Months 2, 6, 10 — 50%; Months 3, 7, 11 — 75%; Months 4, 8, 12 — 100%
**Appendix G Section:** G3.1.3.16
**Appendix G Section Reference:** None

**Applicability:** All required data elements exist for B_RMR
**Applicability Checks:**
1. Computer rooms are present in the baseline model

**Manual Check:** None
**Evaluation Context:** Each Miscellaneous Equipment object in the baseline model
**Data Lookup:** None
**Function Call:**
1) find_all
2) is_space_a_computer_room
3) compare_schedules

## Applicability Checks:
- Iterate through the spaces in the baseline RMD: `for space_b in find_all("$.buildings[*].building_segments[*].zones[*].spaces[*]", B_RMD):`
- Check if the space is a computer room: `if is_space_a_computer_room(B_RMD, space_b.id):`
- Iterate through the miscelleneous equipment loads: `for misc_equip_b in space_b.miscellaneous_equipment:`
- Check if the equipment is specified as anything other than IT equipment: `if misc_equip_b.type and misc_equip_b.type != "INFORMATION_TECHNOLOGY_EQUIPMENT":`
- Rule is not applicable: `return False`
- Else, the equipment is IT equipment or the type was not specified so we assume that all miscellaneous equipment in the computer room is computer room equipment: `else:`
- Rule is applicable: `return True`

## Rule Logic:
- Determine if it is a leap year: `is_leap_year = RPD.calendar.is_leap_year`
- Create a dictionary to map the month numbers to their respective fractions: `month_fractions = {1: 0.25, 2: 0.5, 3: 0.75, 4: 1, 5: 0.25, 6: 0.5, 7: 0.75, 8: 1, 9: 0.25, 10: 0.5, 11: 0.75, 12: 1}`
- Create a dictionary to map the number of days in each month: `days_in_month = {1: 31, 2: 29 if is_leap_year else 28, 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31}`
- Create an empty list to store the expected hourly values of the schedule: `expected_hourly_values = []`
- Iterate through the months of the year: `for month in range(1, 13):`
- Get the fraction for the month: `fraction = month_fractions[month]`
- Get the number of days in the month: `days = days_in_month[month]`
- Append the hourly fraction to the list: `expected_hourly_values.extend([fraction] * days * 24)`
- Create the mask schedule to be used for comparison: `mask_schedule = [1] * 8784 if is_leap_year else [1] * 8760`
- Get the multiplier schedule that was modeled for the equipment: `multiplier_schedule_b = misc_equip_b.multiplier_schedule.hourly_values`
- Compare the modeled schedule with the expected schedule: `comparison_data = compare_schedules(multiplier_schedule_b, expected_hourly_values, mask_schedule)`

**Rule Assertion:**
- Case 1: If the modeled schedule matches the expected schedule for all hours, PASS:`if comparison_data["total_hours_matched"] == 8784 if is_leap_year else 8760: PASS`
- Case 2: Else, FAIL:`else: FAIL`


**Notes:**
1. It was noted that the is_space_a_computer_room function needs to be updated to better align with the definition of a computer room in the standard. Applicability will depend on the correct implementation so that computer rooms are only recognized if design electronic data equipment power density
exceeds 20 W/ft2 of conditioned floor area

- **[Back](../_toc.md)**
2 changes: 1 addition & 1 deletion rct229/report_engine/rct_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def generate(self, rct_outcome, report_dir):
-------
"""
invalid_msg = rct_outcome["invalid_rmrs"]
invalid_msg = rct_outcome["invalid_rmds"]
# Sort the outcomes by id or rule_id
if "id" in rct_outcome["outcomes"][0]:
id_key = "id"
Expand Down
6 changes: 3 additions & 3 deletions rct229/reports/project_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ def print_rule_report(report):


def print_summary_report(report):
invalid_rmrs = report["invalid_rmrs"]
if invalid_rmrs:
invalid_rmds = report["invalid_rmds"]
if invalid_rmds:
print("----------------------------------")
print(f"Invalid RMDs: {str(invalid_rmrs)}")
print(f"Invalid RMDs: {str(invalid_rmds)}")
else:
outcomes = report["outcomes"]
summary_dict = aggregate_outcomes(outcomes)
Expand Down
Loading

0 comments on commit 04cb834

Please sign in to comment.