Skip to content
Closed
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
33 changes: 18 additions & 15 deletions docs/ashrae_90p1_2019/section6/Rule6-8.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@

**Rule ID:** 6-8
**Rule Description:** Additional occupancy sensor controls in the proposed building are modeled through schedule adjustments based on factors defined in Table G3.7.
**Rule Assertion:** Proposed RMR = expected value
**Rule Assertion:** Proposed RMD = expected value
**Appendix G Section:** Section G3.1-6(i) Modeling Requirements for the Proposed design
**Appendix G Section Reference:**

- Table G3.7, Performance Rating Method Lighting Power Density Allowances and Occupancy Sensor Reductions Using the Space-by-Space Method

**Applicability:** All required data elements exist for P_RMR
**Applicability Checks:** None
**Applicability:** All required data elements exist for P_RMD
**Applicability Checks:** None

**Manual Check:** Yes
**Evaluation Context:** Each Data Element
Expand All @@ -22,32 +22,35 @@

## Rule Logic:

- Get building open schedule in the proposed model: `building_open_schedule_p = P_RMR.building.building_open_schedule`
- Get building open schedule in the proposed model: `building_open_schedule_p = P_RMD.building.building_open_schedule`

- For each building segment in building: `for building_segment_p in P_RMR.building.building_segments:`
- For each space in building: `for space_p in P_RMD...spaces:`

- Get matching space from B_RMD: `space_b = match_data_element(B_RMD, Spaces, space_p.id)`

- For each zone in building_segments: `zone_p in building_segment_p.zones:`
- Get total lighting power density in the baseline: `total_space_LPD_b = sum(interior_lighting.power_per_area for interior_lighting in space_b.interior_lighting)`

- For each space in zone: `space_p in zone_p.spaces:`
- Skip evaluating plenums, crawlspaces, and interstitial spaces that have no lighting power in the baseline: `if total_space_LPD_b == 0 and space_b.function in [PLENUM, CRAWL_SPACE, INTERSTITIAL_SPACE]: continue`

- Get matching space from B_RMR: `space_b = match_data_element(B_RMR, Spaces, space_p.id)`
- Get normalized space lighting schedule for B_RMD: `normalized_schedule_b = normalize_space_schedules(space_b.interior_lighting)`

- Get normalized space lighting schedule for B_RMR: `normalized_schedule_b = normalize_space_schedules(space_b.interior_lighting)`
- Get normalized space lighting schedule: `normalized_schedule_p = normalize_space_schedules(space_p.interior_lighting)`

- Get normalized space lighting schedule: `normalized_schedule_p = normalize_space_schedules(space_p.interior_lighting)`
- Compare lighting schedules in P_RMD and B_RMD: `schedule_comparison_result = compare_schedules(normalized_schedule_p, normalized_schedule_b, building_open_schedule_p)`

- Compare lighting schedules in P_RMR and B_RMR: `schedule_comparison_result = compare_schedules(normalized_schedule_p, normalized_schedule_b, building_open_schedule_p)`
**Rule Assertion:**

**Rule Assertion:**
- Case 1: If the space is a crawl space, plenum or interstitial space with lighting power in the baseline model: `if space_p.function in [PLENUM, CRAWL_SPACE, INTERSTITIAL_SPACE] AND total_space_LPD_b > 0: UNDETERMINED and raise_warning "Space is a crawl space, plenum or interstitial space with lighting power in the baseline model. Verify that occupancy sensor control requirements are followed correctly, if applicable."`

- Case 1: For all hours, for each lighting, if lighting schedule in P_RMR is equal to lighting schedule in B_RMR times adjusted lighting occupancy sensor reduction factor: `if schedule_comparison_result == "MATCH": PASS`
- Case 2: For all hours, for each lighting, if lighting schedule in P_RMD is equal to lighting schedule in B_RMD times adjusted lighting occupancy sensor reduction factor: `if schedule_comparison_result == "MATCH": PASS`

- Case 2: Else if lighting schedule in P_RMR is lower than or equal to lighting schedule in B_RMR times adjusted lighting occupancy sensor reduction factor: `if schedule_comparison_result == "EQUAL AND LESS": FAIL and raise_warning "SCHEDULE ADJUSTMENT MAY BE CORRECT IF SPACE INCLUDES DAYLIGHT CONTROL MODELED BY SCHEDULE ADJUSTMENT OR INDIVIDUAL WORKSTATIONS WITH LIGHTING CONTROLLED BY OCCUPANCY SENSORS (TABLE G3.7 FOOTNOTE C)."`
- Case 3: Else if lighting schedule in P_RMD is lower than or equal to lighting schedule in B_RMD times adjusted lighting occupancy sensor reduction factor: `if schedule_comparison_result == "EQUAL AND LESS": FAIL and raise_warning "Schedule adjustment may be correct if space includes daylight control modeled by schedule adjustment or individual workstations with lighting controlled by occupancy sensors (Table G3.7 Footnote C)."`

- Case 3: Else, lighting schedule in P_RMR is higher than lighting schedule in B_RMR times adjusted lighting occupancy sensor reduction factor: `if schedule_comparison_result == "EQUAL AND MORE": UNDETERMINED and raise_message "LIGHTING SCHEDULE IN P-RMR INCLUDING ADJUSTED LIGHTING OCCUPANCY SENSOR REDUCTION FACTOR IS HIGHER THAN THAT IN B-RMR. VERIFY ADDITIONAL OCCUPANCY SENSOR CONTROL IS MODELED CORRECTLY IN P-RMR."`
- Case 4: Else, lighting schedule in P_RMD is higher than lighting schedule in B_RMD times adjusted lighting occupancy sensor reduction factor: `if schedule_comparison_result == "EQUAL AND MORE": UNDETERMINED and raise_message "Lighting schedule in the proposed model, including adjusted lighting occupancy sensor reduction, is higher than that in the baseline model. Verify additional occupancy sensor control is modeled correctly in the proposed model."`

**Notes:**
1. Updated the Rule ID from 6-13 to 6-9 on 6/3/2022
2. Updated the Rule ID from 6-9 to 6-8 on 6/8/2022
3. Updated to exclude crawl space, plenum or interstitial space on 9/29/2025

**[Back](../_toc.md)**
86 changes: 86 additions & 0 deletions rct229/rulesets/ashrae9012019/section6/section6rule8.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from rct229.rulesets.ashrae9012019.ruleset_functions.normalize_interior_lighting_schedules import (
normalize_interior_lighting_schedules,
)
from rct229.schema.schema_enums import SchemaEnums
from rct229.utils.assertions import getattr_
from rct229.utils.jsonpath_utils import find_all, find_exactly_one_with_field_value
from rct229.utils.pint_utils import ZERO
Expand All @@ -21,6 +22,17 @@
"Schedule adjustment may be correct if space includes daylight control modeled by schedule adjustment or "
"individual workstation with lighting controlled by occupancy sensors (TABLE G3.7 Footnote c). "
)
NA_BASELINE_LIGHTING_MSG = (
"Space is a crawl space, plenum or interstitial space with lighting power in the baseline model. "
"Verify that occupancy sensor control requirements are followed correctly, if applicable."
)

SpaceFunctionOptions = SchemaEnums.schema_enums["SpaceFunctionOptions"]
NA_SPACE_FUNCTIONS = [
SpaceFunctionOptions.PLENUM,
SpaceFunctionOptions.CRAWL_SPACE,
SpaceFunctionOptions.INTERSTITIAL_SPACE,
]


class PRM9012019Rule16x33(RuleDefinitionListIndexedBase):
Expand Down Expand Up @@ -89,6 +101,29 @@ def create_data(self, context, data=None):
),
}

def list_filter(self, context_item, data):
"""
exclude plenum/crawl/interstitial spaces that have no lighting power.
Also excludes NONE lighting-space-type spaces with zero lighting.
"""
space_b = context_item.BASELINE_0

total_space_lpd_b = sum(
find_all("$.interior_lighting[*].power_per_area", space_b),
start=ZERO.POWER_PER_AREA,
)

space_function_b = space_b.get("function")
lighting_space_type_b = space_b.get("lighting_space_type")

is_na_or_none = (
space_function_b in NA_SPACE_FUNCTIONS
or lighting_space_type_b == "NONE"
)

# Exclude only NA or NONE spaces with zero baseline lighting
return not (is_na_or_none and total_space_lpd_b == ZERO.POWER_PER_AREA)

class ZoneRule(RuleDefinitionListIndexedBase):
def __init__(self):
super(
Expand Down Expand Up @@ -164,12 +199,63 @@ def get_calc_vals(self, context, data=None):
"eflh_difference": schedule_comparison_result[
"eflh_difference"
],
"space_function_b": space_b.get("function"),
"lighting_space_type_b": getattr_(
space_b, "space", "lighting_space_type"
),
"total_space_lpd_b": sum(
find_all("interior_lighting[*].power_per_area", space_b)
),
}

def manual_check_required(self, context, calc_vals=None, data=None):
eflh_difference = calc_vals["eflh_difference"]
space_function_b = calc_vals["space_function_b"]
lighting_space_type_b = calc_vals["lighting_space_type_b"]
total_space_lpd_b = calc_vals["total_space_lpd_b"]

# Plenum, crawlspace, interstitial space with lighting
if (
space_function_b in NA_SPACE_FUNCTIONS
and total_space_lpd_b > ZERO.POWER_PER_AREA
):
return True

# NONE space type with lighting
if (
lighting_space_type_b == "NONE"
and total_space_lpd_b > ZERO.POWER_PER_AREA
):
return True

# Case 4: Proposed schedule higher than Baseline-adjusted
return eflh_difference > 0

def get_manual_check_required_msg(
self, context, calc_vals=None, data=None
):
space_function_b = calc_vals["space_function_b"]
lighting_space_type_b = calc_vals["lighting_space_type_b"]
total_space_lpd_b = calc_vals["total_space_lpd_b"]
eflh_difference = calc_vals["eflh_difference"]

if (
space_function_b in NA_SPACE_FUNCTIONS
and total_space_lpd_b > ZERO.POWER_PER_AREA
):
return NA_BASELINE_LIGHTING_MSG

if (
lighting_space_type_b == "NONE"
and total_space_lpd_b > ZERO.POWER_PER_AREA
):
return NA_BASELINE_LIGHTING_MSG

if eflh_difference > 0:
return MANUAL_CHECK_MSG

return ""

def rule_check(self, context, calc_vals=None, data=None):
total_hours_compared = calc_vals["total_hours_compared"]
total_hours_matched = calc_vals["total_hours_matched"]
Expand Down
Loading