Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RS/YJ/Rule 11-12 #1524

Open
wants to merge 25 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d2d0ff3
Initial commit
yunjoonjung-PNNL Sep 10, 2024
6173f28
Updated ashrae229 schema and json pointer enumerations to get things …
Sep 11, 2024
ce9e82b
First cut at rule test 11-6
Sep 11, 2024
a9fd6e2
Finished dev
yunjoonjung-PNNL Sep 12, 2024
fa2802b
Fix RDS line change
yunjoonjung-PNNL Sep 12, 2024
05909ea
Update ASHRAE229.schema.json
jugonzal07 Sep 12, 2024
3cf0ef4
Added service weater heating uses to enumerations and made adjustment…
Sep 12, 2024
962ee32
Merge branch 'RT/JG/ruletest_11_master' into RT/JG/ruletest_11_6
Sep 12, 2024
35fc2ef
minor typo correction.
Sep 12, 2024
36b1f34
Merge branch 'RT/JG/ruletest_11_master' into RT/JG/ruletest_11_6
Sep 12, 2024
d0132a8
Addressed issue where SHW distributions were not referenced by any sp…
Sep 12, 2024
0a5fc3c
Finished dev
yunjoonjung-PNNL Sep 12, 2024
828b682
Merge branch 'RT/JG/ruletest_11_6' of https://github.com/pnnl/ruleset…
yunjoonjung-PNNL Sep 12, 2024
571768c
Left one comment for future check
yunjoonjung-PNNL Sep 12, 2024
a1dc2bc
Include section11 init file
yunjoonjung-PNNL Sep 12, 2024
97c5b20
Added building segment level
yunjoonjung-PNNL Sep 12, 2024
ea3790e
Corrected the logic
yunjoonjung-PNNL Sep 17, 2024
c29b727
Flipped pass and fail cases for 11-6 ruletest
Sep 18, 2024
16bec25
First cut at 11-12 rule test
Sep 18, 2024
fae1f4e
Merge branch 'RT/JG/ruletest_11_6' of https://github.com/pnnl/ruleset…
yunjoonjung-PNNL Sep 18, 2024
2e67166
Merge branch 'RT/JG/ruletest_11_6' of https://github.com/pnnl/ruleset…
yunjoonjung-PNNL Sep 18, 2024
6f66505
Merge branch 'RS/YJ/Rule_11-6' of https://github.com/pnnl/ruleset-che…
yunjoonjung-PNNL Sep 18, 2024
697b754
Fixed the rule
yunjoonjung-PNNL Sep 18, 2024
5adc687
Fixed the rule after testing
yunjoonjung-PNNL Sep 19, 2024
88d9188
Merge branch 'develop' of https://github.com/pnnl/ruleset-checking-to…
yunjoonjung-PNNL Oct 1, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Inputs:
- **building_segment_id** - the id of the building segment

Returns:
- **swh_uses**: A list containing the ids of all service water heating uses associated with a building segment
- **swh_uses**: A list containing the distribution system ids of all service water heating uses associated with a building segment

Function Call:
- get_obj_by_id
Expand All @@ -18,7 +18,7 @@ Logic:
- get the building segment: `building_segment = get_obj_by_id(RMD, building_segment_id)`
- create a blank list: `swh_uses = []`
- look at each swh use: `for swh_use in building_segment.service_water_heating_uses:`
- append the use to the list: `swh_uses.append(swh_use)`
- append the use to the list: `swh_uses.append(swh_use.served_by_distribution_system)`


**Returns** swh_uses
Expand Down
4 changes: 2 additions & 2 deletions docs/section11/Rule11-6.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
**Schema Version:** 0.0.37
**Mandatory Rule:** True
**Rule ID:** 11-6
**Rule Description:** Piping losses shall not be modeled.
**Rule Description:** Piping losses shall not be modeled.
**Rule Assertion:** Options are PASS/FAIL/NOT_APPLICABLE/UNDETERMINED
**Appendix G Section Reference:** Table G3.1 #11, baseline column, i

Expand All @@ -26,7 +26,7 @@
- set the piping_losses_modeled to true and go directly to rule assertion: `piping_losses_modeled = true: GO TO RULE_ASSERTION`

- **Rule Assertion - Zone:**
- Case1: piping losses are not modeled, PASS: `if !piping_losses_modeled: PASS`
- Case1: piping losses are not modeled, PASS: `if piping_losses_modeled == False: PASS`
- Case2: piping losses are modeled, FAIL: `else: FAIL`


Expand Down
2 changes: 1 addition & 1 deletion rct229/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def cli():
Software test workflow, add sections to do test. \n
--ruleset or -rs: default is ashrae9012019, available: ashrae9012019\n
argument (optional): section string, \n
currently available: section1, section4, section5, section6, section10, section12, section16, section18, section19, section21, section22 and section23"""
currently available: section1, section4, section5, section6, section10, section11, section12, section16, section18, section19, section21, section22 and section23"""


@cli.command(
Expand Down
2 changes: 1 addition & 1 deletion rct229/rule_engine/rule_list_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def rule_check(self, context, calc_vals=None, data={}):
if self.list_filter(context_item, data)
]

# Evalutate the subrule for each item in the context list
# Evaluate the subrule for each item in the context list
outcomes = []
for ubp in filtered_context_list:
item_outcome = self.each_rule.evaluate(ubp, data)
Expand Down
1 change: 1 addition & 0 deletions rct229/rule_engine/rulesets.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class RuleSetTest:
"section5",
"section6",
"section10",
"section11",
"section12",
"section16",
"section18",
Expand Down
1 change: 1 addition & 0 deletions rct229/rulesets/ashrae9012019/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"section5",
"section6",
"section10",
"section11",
"section12",
"section16",
"section18",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def get_swh_uses_associated_with_each_building_segment(
) -> list[str]:
"""
This function gets all the SWH uses connected to a building segment or an empty list if no service water heating uses are found in the building segment. This function is primarily to encapsulate getting service water heating uses in one function so that if a change is made in the schema as to how service water heating use is specified,
the RCT only needs to change in one place .
the RCT only needs to change in one place.

Parameters
----------
Expand All @@ -18,11 +18,11 @@ def get_swh_uses_associated_with_each_building_segment(
Returns
-------
swh_uses_list: list
A list containing the ids of all service water heating uses associated with a building segment
A list containing the distribution system ids of all service water heating uses associated with a building segment
"""

swh_uses_list = find_all(
f'$.buildings[*].building_segments[*][?(@.id="{building_segment_id}")].zones[*].spaces[*].service_water_heating_uses[*].id',
f'$.buildings[*].building_segments[*][?(@.id="{building_segment_id}")].zones[*].spaces[*].service_water_heating_uses[*].served_by_distribution_system',
rmd,
)
return swh_uses_list
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,14 @@
{
"id": "Space 1",
"service_water_heating_uses": [
{"id": "service water heating uses 1"},
{"id": "service water heating uses 2"},
{
"id": "service water heating uses 1",
"served_by_distribution_system": "service water heating uses distribution 1",
},
{
"id": "service water heating uses 2",
"served_by_distribution_system": "service water heating uses distribution 2",
},
],
},
],
Expand Down Expand Up @@ -53,7 +59,10 @@ def test__TEST_RPD__is_valid():
def test__get_swh_uses_associated_with_each_building_segment__bldg_segment_ids_exist():
assert get_swh_uses_associated_with_each_building_segment(
TEST_RMD, "Building Segment 1"
) == ["service water heating uses 1", "service water heating uses 2"]
) == [
"service water heating uses distribution 1",
"service water heating uses distribution 2",
]


def test__get_swh_uses_associated_with_each_building_segment__bldg_segment_id_not_exist():
Expand Down
32 changes: 32 additions & 0 deletions rct229/rulesets/ashrae9012019/section11/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Add all available rule modules in __all__
import importlib

__all__ = [
# "section11rule1",
# "section11rule2",
# "section11rule3",
# "section11rule4",
# "section11rule5",
"section11rule6",
# "section11rule7",
# "section11rule8",
# "section11rule9",
# "section11rule10",
# "section11rule11",
"section11rule12",
# "section11rule13",
# "section11rule14",
# "section11rule15",
# "section11rule16",
# "section11rule17",
]


def __getattr__(name):
if name in __all__:
return importlib.import_module("." + name, __name__)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


def __dir__():
return sorted(__all__)
156 changes: 156 additions & 0 deletions rct229/rulesets/ashrae9012019/section11/section11rule12.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
from rct229.rule_engine.partial_rule_definition import PartialRuleDefinition
from rct229.rule_engine.rule_list_indexed_base import RuleDefinitionListIndexedBase
from rct229.rule_engine.ruleset_model_factory import produce_ruleset_model_description
from rct229.rule_engine.rulesets import LeapYear
from rct229.rulesets.ashrae9012019 import BASELINE_0
from rct229.rulesets.ashrae9012019.ruleset_functions.get_swh_uses_associated_with_each_building_segment import (
get_swh_uses_associated_with_each_building_segment,
)
from rct229.utils.assertions import getattr_
from rct229.utils.utility_functions import find_exactly_one_schedule

APPLICABILITY_MSG = (
"This building is a 24hr-facility with service water heating loads. If the building meets the prescriptive criteria for use of condenser heat recovery systems described in 90.1 Section 6.5.6.2, a system meeting the requirements of that section shall be included in the baseline building design regardless of the exceptions to Section 6.5.6.2. "
"(Exceptions: 1. Facilities that employ condenser heat recovery for space heating with a heat recovery design exceeding 30% of the peak water-cooled condenser load at design conditions."
" 2. Facilities that provide 60% of their service water heating from site-solar energy or siterecovered energy or from other sources.) Recommend manual review to determine if project complies."
)


class Section11Rule12(RuleDefinitionListIndexedBase):
"""Rule 12 of ASHRAE 90.1-2019 Appendix G Section 11 (Service Water Heating)"""

def __init__(self):
super(Section11Rule12, self).__init__(
rmds_used=produce_ruleset_model_description(
USER=False, BASELINE_0=True, PROPOSED=True
),
each_rule=Section11Rule12.RMDRule(),
index_rmd=BASELINE_0,
id="11-12",
description="For large, 24-hour-per-day facilities that meet the prescriptive criteria for use of condenser heat recovery systems described in Section 6.5.6.2, a system meeting the requirements of that section shall be included in the baseline building design regardless of the exceptions to Section 6.5.6.2.",
ruleset_section_title="Service Water Heating",
standard_section="Table G3.1 #11, baseline column, d + exception",
is_primary_rule=False,
list_path="ruleset_model_descriptions[0]",
data_items={"is_leap_year_b": (BASELINE_0, "calendar/is_leap_year")},
)

class RMDRule(RuleDefinitionListIndexedBase):
def __init__(self):
super(Section11Rule12.RMDRule, self).__init__(
rmds_used=produce_ruleset_model_description(
USER=False, BASELINE_0=True, PROPOSED=True
),
each_rule=Section11Rule12.RMDRule.BuildingRule(),
index_rmd=BASELINE_0,
list_path="$.buildings[*]",
)

def create_data(self, context, data):
rmd_b = context.BASELINE_0
rmd_p = context.PROPOSED

return {"rmd_b": rmd_b, "rmd_p": rmd_p}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can make a create_data function and add data:

return {
    "is_leap_year_b": find_one("$.calendar.is_leap_year", rmd_b),
    "schedules_b": find_all("$.schedules[*], rmd_b),
    "schedules_p": find_all("$.schedules[*], rmd_p)
}


class BuildingRule(RuleDefinitionListIndexedBase):
def __init__(self):
super(Section11Rule12.RMDRule.BuildingRule, self).__init__(
rmds_used=produce_ruleset_model_description(
USER=False, BASELINE_0=True, PROPOSED=True
),
each_rule=Section11Rule12.RMDRule.BuildingRule.BuildingSegmentRule(),
index_rmd=BASELINE_0,
list_path="$.building_segments[*]",
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to add a is_applicable here to check the logic check that there is SWH in the proposed model AND that the building has a 24hr operating schedule

In RDS logic section, it is also said: A building must both have SWH loads and a 24hr operating schedule for this rule to be applicable. So it is appropriate to add is_applicable check under building level.


def create_data(self, context, data):
building_b = context.BASELINE_0
rmd_b = data["rmd_b"]
is_leap_year_b = data["is_leap_year_b"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_leap_year_b is not in the data with the current implementation.


bldg_open_sch_id_b = getattr_(
building_b, "buildings", "building_open_schedule"
)

bldg_open_sch_b = find_exactly_one_schedule(rmd_b, bldg_open_sch_id_b)[
"hourly_values"
]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the above create_data suggestion, you can use find_exactly_one_with_field_value("$.schedules_b[*]", "id", bldg_open_sch_id_b, data) to get the bldg_open_sch_b.


hours_this_year = (
LeapYear.LEAP_YEAR_HOURS
if is_leap_year_b
else LeapYear.REGULAR_YEAR_HOURS
)

return {
"bldg_open_sch_b": bldg_open_sch_b,
"hours_this_year": hours_this_year,
}

def list_filter(self, context_item, data):
bldg_open_sch_b = data["bldg_open_sch_b"]
hours_this_year = data["hours_this_year"]

return sum(bldg_open_sch_b) == hours_this_year
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what is this filtering.
If the intention is just to make sure the building has 24 hours operation, then we should put this logic to is applicable because list_filter is a silence pass - no outcome returned.


class BuildingSegmentRule(RuleDefinitionListIndexedBase):
def __init__(self):
super(
Section11Rule12.RMDRule.BuildingRule.BuildingSegmentRule, self
).__init__(
rmds_used=produce_ruleset_model_description(
USER=False, BASELINE_0=True, PROPOSED=True
),
each_rule=Section11Rule12.RMDRule.BuildingRule.BuildingSegmentRule.SWHRule(),
index_rmd=BASELINE_0,
list_path="$.zones[*].spaces[*].service_water_heating_uses[*]",
)

def create_data(self, context, data):
building_segment_p = context.PROPOSED
rmd_p = data["rmd_p"]

service_water_heating_use_ids_list_p = (
get_swh_uses_associated_with_each_building_segment(
rmd_p, building_segment_p["id"]
)
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will move this logic to RMD level. for example, creating a

swh_uses_associated_with_each_building_segment_p: 
{
    building_segment_id: get_swh_uses_associated_with_each_building_segment(
                            rmd_p, building_segment_p["id"]
                        )
    for building_segment_id in find_all("$.buildings[*].building_segments[*].id, rmd_p
}

I can skip this create data here.


return {
"service_water_heating_use_ids_list_p": service_water_heating_use_ids_list_p
}

def list_filter(self, context_item, data):
swh_use_p = context_item.PROPOSED
served_by_distribution_system_p = getattr_(
swh_use_p,
"service_water_heating_uses",
"served_by_distribution_system",
)
service_water_heating_use_ids_list_p = data[
"service_water_heating_use_ids_list_p"
]

return (
served_by_distribution_system_p
in service_water_heating_use_ids_list_p
)

class SWHRule(PartialRuleDefinition):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the rule shall be evaluated at service water use level. Instead, I think it should be at the Building level.
if you read the applicable message, it starts with the building not the water use.

Also, if we check the logic - a great amount of logic is to verify whether the building has SWH load and it is operate 24 hours.

def __init__(self):
super(
Section11Rule12.RMDRule.BuildingRule.BuildingSegmentRule.SWHRule,
self,
).__init__(
rmds_used=produce_ruleset_model_description(
USER=False, BASELINE_0=False, PROPOSED=True
),
manual_check_required_msg=APPLICABILITY_MSG,
)

def applicability_check(self, context, calc_vals, data):
swh_use_p = context.PROPOSED

return (
getattr_(swh_use_p, "service_water_heating_uses", "use") > 0
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this part, please see the email between Juan and me (Sep 19th - title: RCT, Question about ServiceWaterHeatingUse-USE unit). Please let me know if you think differently.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to care about the unit for now.
Checking the requirement of rule, it says check to see if the use has SWH loads
This implies we need to verify if the use actually has load - so your implementation here is good.
Perhaps you don't need to use getattr_. We can assume missing data is 0 load.

)
85 changes: 85 additions & 0 deletions rct229/rulesets/ashrae9012019/section11/section11rule6.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from rct229.rule_engine.rule_base import RuleDefinitionBase
from rct229.rule_engine.rule_list_indexed_base import RuleDefinitionListIndexedBase
from rct229.rule_engine.ruleset_model_factory import produce_ruleset_model_description
from rct229.rulesets.ashrae9012019 import BASELINE_0
from rct229.rulesets.ashrae9012019.ruleset_functions.get_swh_uses_associated_with_each_building_segment import (
get_swh_uses_associated_with_each_building_segment,
)
from rct229.utils.jsonpath_utils import find_all, find_exactly_one_with_field_value


class Section11Rule6(RuleDefinitionListIndexedBase):
"""Rule 6 of ASHRAE 90.1-2019 Appendix G Section 11 (Service Water Heating)"""

def __init__(self):
super(Section11Rule6, self).__init__(
rmds_used=produce_ruleset_model_description(
USER=False, BASELINE_0=True, PROPOSED=False
),
each_rule=Section11Rule6.RMDRule(),
index_rmd=BASELINE_0,
id="11-6",
description="Piping losses shall not be modeled.",
ruleset_section_title="Service Water Heating",
standard_section="Table G3.1 #11, baseline column, i",
is_primary_rule=True,
list_path="ruleset_model_descriptions[0]",
)

class RMDRule(RuleDefinitionListIndexedBase):
def __init__(self):
super(Section11Rule6.RMDRule, self).__init__(
rmds_used=produce_ruleset_model_description(
USER=False, BASELINE_0=True, PROPOSED=False
),
each_rule=Section11Rule6.RMDRule.BuildingSegmentRule(),
index_rmd=BASELINE_0,
list_path="buildings[*].building_segments[*]",
)

def create_data(self, context, data):
rmd_b = context.BASELINE_0

return {"rmd_b": rmd_b}

class BuildingSegmentRule(RuleDefinitionBase):
def __init__(self):
super(Section11Rule6.RMDRule.BuildingSegmentRule, self).__init__(
rmds_used=produce_ruleset_model_description(
USER=False,
BASELINE_0=True,
PROPOSED=False,
),
)

def get_calc_vals(self, context, data=None):
rmd_b = data["rmd_b"]
building_segment_b = context.BASELINE_0

swh_distribution_and_eq_list = (
get_swh_uses_associated_with_each_building_segment(
rmd_b, building_segment_b["id"]
)
)

for piping_id in swh_distribution_and_eq_list:
service_water_piping_b = find_exactly_one_with_field_value(
"$.service_water_heating_distribution_systems[*]",
"id",
piping_id,
rmd_b,
)

piping_losses_modeled_b = any(
find_all(
"$.service_water_piping[*].are_thermal_losses_modeled",
service_water_piping_b,
)
)

return {"piping_losses_modeled_b": piping_losses_modeled_b}

def rule_check(self, context, calc_vals=None, data=None):
piping_losses_modeled_b = calc_vals["piping_losses_modeled_b"]

return not piping_losses_modeled_b
Loading
Loading