def economizer(DAT, OAT, HVACstat, elec_cost, area): """ Economizer takes in diffuser air temperature (DAT), outdoor air temperature (OAT), and HVAC status (HVACstat) and checks if it is economizing for more than 70% when it can be economizing. If it the data indicates otherwise, it will return a dictionary of diagnostics. Parameters: DAT, OAT, HVACstat - DAT: discharge air temperature - OAT: outdoor air temperature - HVACstat: HVAC status - HVAC: 0 - off 1 - fan 2 - compressor * Assume that each is a 2-D array with datetime and data * DAT, OAT, HVACstat should have the same number of points * Datetimes must match up - elec_cost: The electricity cost used to calculate savings. Returns: a dictionary of diagnostics or False """ # counts points when the economizer is on econ_on = 0 # counts points when the RTU is on RTU_on = 0 # iterate through all data points i = 0 while (i < len(DAT)): if ((DAT[i][1] < 70) and (OAT[i][1] <= 65)): # if the economizer on, increment econ_on if (HVACstat[i][1] == 1): econ_on += 1 # count when RTU is on if (HVACstat[i][1] != 0): RTU_on += 1 i += 1 # Percentage is when the economizer is on # if the RTU was never on, economizer was being used if (RTU_on == 0): return {} percentage = econ_on / RTU_on if (percentage < 0.7): return { 'Problem': "Under use of 'free cooling', i.e.,under-economizing.", 'Diagnostic': "More than 30 percent of the time, the " + \ "economizer is not taking advantage of 'free cooling' " + \ "when it is possible to do so.", 'Recommendation': "Ask an HVAC service contractor to check " + \ "the economizer control sequence, unless the RTU does" + \ "not have an economizer.", 'Savings': get_CBECS(area)[5] * 0.1 * elec_cost } else: return {}
def economizer(DAT, OAT, HVACstat, elec_cost, area): """ Economizer takes in diffuser air temperature (DAT), outdoor air temperature (OAT), and HVAC status (HVACstat) and checks if it is economizing for more than 70% when it can be economizing. If it the data indicates otherwise, it will return a dictionary of diagnostics. Parameters: DAT, OAT, HVACstat - DAT: discharge air temperature - OAT: outdoor air temperature - HVACstat: HVAC status - HVAC: 0 - off 1 - fan 2 - compressor * Assume that each is a 2-D array with datetime and data * DAT, OAT, HVACstat should have the same number of points * Datetimes must match up - elec_cost: The electricity cost used to calculate savings. Returns: a dictionary of diagnostics or False """ # counts points when the economizer is on econ_on = 0 # counts points when the RTU is on RTU_on = 0 # iterate through all data points i = 0 while (i < len(DAT)): if ((DAT[i][1] < 70) and (OAT[i][1] <= 65)): # if the economizer on, increment econ_on if (HVACstat[i][1] == 1): econ_on += 1 # count when RTU is on if (HVACstat[i][1] != 0): RTU_on += 1 i += 1 # Percentage is when the economizer is on # if the RTU was never on, economizer was being used if (RTU_on == 0): return {} percentage = econ_on/RTU_on if (percentage < 0.7): return { 'Problem': "Under use of 'free cooling', i.e.,under-economizing.", 'Diagnostic': "More than 30 percent of the time, the " + \ "economizer is not taking advantage of 'free cooling' " + \ "when it is possible to do so.", 'Recommendation': "Ask an HVAC service contractor to check " + \ "the economizer control sequence, unless the RTU does" + \ "not have an economizer.", 'Savings': get_CBECS(area)[5] * 0.1 * elec_cost } else: return {}
def short_cycling(HVAC_stat, elec_cost, area): """ Checks to see if the HVAC is short cycling. Parameter: - HVAC_stat: HVAC status - 2d array with [datetime, data] - data is 0 - off, 1 - fan is on, 2 - compressor on - elec_cost: The electricity cost used to calculate savings. Return: - True if the problem should be flagged """ compressor_on = [] for point in HVAC_stat: if (point[1] == 2): compressor_on.append(1) else: compressor_on.append(0) change_status = [] cycle_start = [] i = 0 while (i < (len(compressor_on) - 1)): status = compressor_on[i + 1] - compressor_on[i] if (status == 1): cycle_start.append(HVAC_stat[i + 1]) change_status.append(status) i += 1 fault_count = 0 i = 0 while (i < (len(cycle_start) - 1)): delta = (cycle_start[i + 1][0] - cycle_start[i][0]) if (delta.seconds < 300): fault_count += 1 i += 1 if (fault_count > 10): percent_l, percent_h, percent_c, med_num_op_hrs, per_hea_coo, \ percent_HVe = get_CBECS(area) return { 'Problem': "RTU cycling on and off too frequently, potentially " + \ "leading to equipment failure.", 'Diagnostic': "For more than 10 consecutive cycles during the " + \ "monitoring period the RTU switched from on to off in under " + \ "5 minutes.", 'Recommendation': "Ask HVAC service providers to check refrigerant " + \ "levels, thermostat location, and control sequences.", 'Savings': round((percent_h + percent_c) * 0.10 * elec_cost, 2)} else: return {}
def short_cycling(HVAC_stat, elec_cost, area): """ Checks to see if the HVAC is short cycling. Parameter: - HVAC_stat: HVAC status - 2d array with [datetime, data] - data is 0 - off, 1 - fan is on, 2 - compressor on - elec_cost: The electricity cost used to calculate savings. Return: - True if the problem should be flagged """ compressor_on = [] for point in HVAC_stat: if (point[1] == 2): compressor_on.append(1) else: compressor_on.append(0) change_status = [] cycle_start = [] i = 0 while (i < (len(compressor_on) - 1)): status = compressor_on[i+1] - compressor_on[i] if (status == 1): cycle_start.append(HVAC_stat[i+1]) change_status.append(status) i += 1 fault_count = 0 i = 0 while (i < (len(cycle_start) - 1)): delta = (cycle_start[i+1][0] - cycle_start[i][0]) if (delta.seconds < 300): fault_count += 1 i += 1 if (fault_count > 10): percent_l, percent_h, percent_c, med_num_op_hrs, per_hea_coo, \ percent_HVe = get_CBECS(area) return { 'Problem': "RTU cycling on and off too frequently, potentially " + \ "leading to equipment failure.", 'Diagnostic': "For more than 10 consecutive cycles during the " + \ "monitoring period the RTU switched from on to off in under " + \ "5 minutes.", 'Recommendation': "Ask HVAC service providers to check refrigerant " + \ "levels, thermostat location, and control sequences.", 'Savings': round((percent_h + percent_c) * 0.10 * elec_cost, 2)} else: return {}
def excessive_daylight(light_data, op_sched, area, elec_cost): """ Excessive Daylight checks to see a single sensor should be flagged. Parameters: - light_data: a 2d array with datetime and light status - lights are on (1, True) or off (0, False) - separate the data for operating hours and non-operating hours - op_sched: 2d array of operating schedule -[[operational hours], [days operating], [holidays]] - elec_cost: The electricity cost used to calculate savings. Returns: True or False (true meaning that this sensor should be flagged) """ occupied_data, nonoccupied_data = separate_hours(light_data, op_sched[0], op_sched[1], op_sched[2]) # Grabs the first time it starts logging so we know when the next day is day_marker = occupied_data[0][0] # counts times when lights go from on to off on_to_off = 0 # counts the seconds when the lights are on time_on = datetime.timedelta(0) # counts flagged days day_flag = 0 # counts the total number of days day_count = 0 # total operation hours operational_hours = op_sched[0][1] - op_sched[0][0] # accounts for the first point, checks if the lights are on, sets when # lights were last set to on to the first time if occupied_data[0][1] == 1: lights_on = True else: lights_on = False # Find the first index when lights are on. # FIXME: Is this a valid substitution? for light_dpt in occupied_data: if light_dpt[1]: last_on = light_dpt[0] break # FIXME: Separate the data during operational hours vs non-operational hours. # iterate through the light data i = 1 while i < len(occupied_data): # NOTE: Variable 'day_count' is incremented when the current date changes # from the previous date and at the end of the record. if occupied_data[i][0].date() != day_marker.date() or i == len(occupied_data) - 1: # Check if day should be flagged, time delta is in days and seconds if occupied_data[i][1] == 1: time_on += occupied_data[i][0] - last_on # if time_on.days != 0: time_on_hours = (24 * time_on.days) + time_on.seconds / 3600 else: time_on_hours = time_on.seconds / 3600 # if (on_to_off < 2) and ((time_on_hours / operational_hours) > 0.5): day_flag += 1 # NOTE: Reset flags each day and reinitialize day markers. The light control # diagnostic is concerned with checking whether the light turns off each # day. day_count += 1 day_marker = occupied_data[i][0] on_to_off = 0 time_on = datetime.timedelta(0) # Check lights were turned off, if so, increment on_to_off, lights # are now off, add time on to timeOn if (lights_on) and (occupied_data[i][1] == 0): on_to_off += 1 lights_on = False time_on += occupied_data[i][0] - last_on # Check if lights were turned on, set last_on to the current time elif (not lights_on) and (occupied_data[i][1] == 1): lights_on = True last_on = occupied_data[i][0] i += 1 # If more than half of the days are flagged, there's a problem. if day_flag / day_count > 0.5: percent_l, percent_h, percent_c, med_num_op_hrs, per_hea_coo, percent_HV = get_CBECS(area) total_time = occupied_data[-1][0] - occupied_data[0][0] total_weeks = ((total_time.days * 24) + (total_time.seconds / 3600)) / 168 avg_week = ((total_time.days * 24) + (total_time.seconds / 3600)) / total_weeks return { "Problem": "Excessive lighting during occupied/daytime hours.", "Diagnostic": "Even though these spaces are not continuously " + "occupied, for more than half of the monitoring period, the " + "lights were switched off less than three times a day.", "Recommendation": "Install occupancy sensors in locations with " + "intermittent occupancy, or engage occupants to turn the " + "lights off when they leave the area.", "Savings": round(elec_cost * percent_l * 0.6 * 0.1 * (avg_week / med_num_op_hrs), 2), } else: return {}
def excessive_nighttime(light_data, op_sched, area, elec_cost): """ Excessive Nighttime Lightingchecks to see a single sensor should be flagged. Parameters: - light_data: a 2d array with datetime and data - lights are on (1, True) or off (0, False) - assumes light_data is only for non-operational hours - op_sched: 2d array of operating schedule -[[operational hours], [days operating], [holidays]] - elec_cost: The electricity cost used to calculate savings. Returns: True or False (true meaning that this sensor should be flagged) """ occupied_data, nonoccupied_data = separate_hours(light_data, op_sched[0], op_sched[1], op_sched[2]) # Grabs the first time it starts logging so we know when the next day is day_marker = nonoccupied_data[0][0] # Counts the seconds when the lights are on time_on = datetime.timedelta(0) # Counts the total number of days day_count = 0 if (nonoccupied_data[0][1] == True): last_on = nonoccupied_data[0][0] else: last_on = None # Iterate through the light data i = 1 while (i < len(nonoccupied_data)): # NOTE: Assumes that status changes right when switches are turned. # If the lights are on or a change from 'on' to 'off' then calculate the difference # between the 'last_on' and the current date. if (nonoccupied_data[i][1] == True): if last_on != None: time_on += (nonoccupied_data[i][0] - last_on) last_on = nonoccupied_data[i][0] else: if last_on != None: time_on += (nonoccupied_data[i][0] - last_on) last_on = None # Check if it's a new day if (nonoccupied_data[i][0].date() != day_marker.date() or i == len(nonoccupied_data)-1): day_count += 1 day_marker = nonoccupied_data[i][0] i += 1 if (time_on.days != 0): total_time = (time_on.days * 24) + (time_on.seconds / 3600) else: total_time = (time_on.seconds / 3600) if ((total_time / day_count) > 3): percent_l, percent_h, percent_c, med_num_op_hrs, per_hea_coo, \ percent_HV = get_CBECS(area) total_time = nonoccupied_data[-1][0] - nonoccupied_data[0][0] total_weeks = ((total_time.days * 24) + (total_time.seconds / 3600)) \ / 168 avg_week = ((total_time.days * 24) + (total_time.seconds / 3600)) \ / total_weeks return { 'Problem': "Excessive lighting during unoccupied/nighttime " + \ "hours.", 'Diagnostic': "For more than half of the monitoring period, the " + \ "lights were on for more than three hours during " + \ "after-hours periods.", 'Recommendation': "Install occupancy sensors in locations where it " + \ "is not necessary or intended for the lights to be on all " + \ "night, or encourage occupants to turn the lights off upon " + \ "exit.", 'Savings': round((0.4 * 0.1 * elec_cost * percent_l * \ (avg_week/(24*7-med_num_op_hrs))),2) } else: return {}
def setback_non_op(ZAT, DAT, op_hours, elec_cost, area, HVACstat=None): """ Checks to see if a location is being cooled or heated during non-operational hours. Parameters: - ZAT: zone air temperature, 2D array of datetime and data - DAT: discharge air temperature, 2D array of datetime and data - HVACstat: HVAC status (optional), 2D array of datetime and data - 0 - off, 1 - ventilation, 3 - compressor - op_hours: operational hours - [[operational hours], [business days], [holidays]] - elec_cost: The electricity cost used to calculate savings. Returns: - flag indicating whether or not this should be flagged """ # separate hours of ZAT and DAT ZAT_op, ZAT_non_op = separate_hours(ZAT, op_hours[0], op_hours[1], op_hours[2]) DAT_op, DAT_non_op = separate_hours(DAT, op_hours[0], op_hours[1], op_hours[2]) # if HVAC status exists, separate that too if (HVACstat): HVAC_op, HVAC_non_op = separate_hours(HVACstat, op_hours[0], op_hours[1], op_hours[2]) else: HVAC_op = [] HVAC_non_op = [] # separate data into cooling, heating, and hvac op_cool_dat, op_heat_dat, op_hvac_dat = _grab_data(DAT_op, ZAT_op, DAT_op, \ HVAC_op) non_cool_dat, non_heat_dat, non_hvac_dat = _grab_data(DAT_non_op, \ ZAT_non_op, DAT_non_op, HVAC_non_op) # find the average cooling diffuser set temp avg_DAT_c_occ = np.mean(op_cool_dat) avg_DAT_h_occ = np.mean(op_heat_dat) # count to see if cooling is on c_flag = 0 for pt in non_cool_dat: if ((pt < avg_DAT_c_occ) or (abs(pt - avg_DAT_h_occ) < 0.1)): c_flag += 1 # h_flag = 0 for pt in non_heat_dat: if ((pt > avg_DAT_h_occ) or (abs(pt - avg_DAT_h_occ) < 0.1)): h_flag += 1 # non_op_data_len = len(DAT_non_op) c_val = c_flag / non_op_data_len h_val = h_flag / non_op_data_len vent_val = non_hvac_dat / non_op_data_len percent_unocc = non_op_data_len / len(DAT) non_cool_zat, non_heat_zat, non_hvac_zat = _grab_data(DAT_non_op, \ ZAT_non_op, ZAT_non_op, HVAC_non_op) # ZAT_cool_threshold = 80 ZAT_heat_threshold = 55 # if non_cool_zat != []: min_ZAT_c_unocc = np.min(non_cool_zat) else: min_ZAT_c_unocc = ZAT_cool_threshold if non_heat_zat != []: max_ZAT_h_unocc = np.max(non_heat_zat) else: max_ZAT_h_unocc = ZAT_heat_threshold # if ((c_val + h_val + vent_val) > 0.3): percent_l, percent_h, percent_c, med_num_op_hrs, per_hea_coo, \ percent_HV = get_CBECS(area) c_savings = (ZAT_cool_threshold-min_ZAT_c_unocc) * 0.03 * c_val * \ percent_c * elec_cost * percent_unocc h_savings = (max_ZAT_h_unocc-ZAT_heat_threshold) * 0.03 * h_val * \ percent_h * elec_cost * percent_unocc ven_savings = 0.06 * vent_val * elec_cost * percent_unocc return { 'Problem': "Nighttime thermostat setbacks are not enabled.", 'Diagnostic': "More than 30 percent of the data indicates that the " + \ "building is being conditioned or ventilated normally " + \ "during unoccupied hours.", 'Recommendation': "Program your thermostats to decrease the " + \ "heating setpoint, or increase the cooling setpoint during " + \ "unoccuppied times. Additionally, you may have a " + \ "contractor configure the RTU to reduce ventilation.", 'Savings': round((c_savings + h_savings + ven_savings), 2)} else: return {}
def excessive_nighttime(light_data, op_sched, area, elec_cost): """ Excessive Nighttime Lightingchecks to see a single sensor should be flagged. Parameters: - light_data: a 2d array with datetime and data - lights are on (1, True) or off (0, False) - assumes light_data is only for non-operational hours - op_sched: 2d array of operating schedule -[[operational hours], [days operating], [holidays]] - elec_cost: The electricity cost used to calculate savings. Returns: True or False (true meaning that this sensor should be flagged) """ occupied_data, nonoccupied_data = separate_hours(light_data, op_sched[0], op_sched[1], op_sched[2]) # Grabs the first time it starts logging so we know when the next day is day_marker = nonoccupied_data[0][0] # Counts the seconds when the lights are on time_on = datetime.timedelta(0) # Counts the total number of days day_count = 0 if nonoccupied_data[0][1] == True: last_on = nonoccupied_data[0][0] else: last_on = None # Iterate through the light data i = 1 while i < len(nonoccupied_data): # NOTE: Assumes that status changes right when switches are turned. # If the lights are on or a change from 'on' to 'off' then calculate the difference # between the 'last_on' and the current date. if nonoccupied_data[i][1] == True: if last_on != None: time_on += nonoccupied_data[i][0] - last_on last_on = nonoccupied_data[i][0] else: if last_on != None: time_on += nonoccupied_data[i][0] - last_on last_on = None # Check if it's a new day if nonoccupied_data[i][0].date() != day_marker.date() or i == len(nonoccupied_data) - 1: day_count += 1 day_marker = nonoccupied_data[i][0] i += 1 if time_on.days != 0: total_time = (time_on.days * 24) + (time_on.seconds / 3600) else: total_time = time_on.seconds / 3600 if (total_time / day_count) > 3: percent_l, percent_h, percent_c, med_num_op_hrs, per_hea_coo, percent_HV = get_CBECS(area) total_time = nonoccupied_data[-1][0] - nonoccupied_data[0][0] total_weeks = ((total_time.days * 24) + (total_time.seconds / 3600)) / 168 avg_week = ((total_time.days * 24) + (total_time.seconds / 3600)) / total_weeks return { "Problem": "Excessive lighting during unoccupied/nighttime " + "hours.", "Diagnostic": "For more than half of the monitoring period, the " + "lights were on for more than three hours during " + "after-hours periods.", "Recommendation": "Install occupancy sensors in locations where it " + "is not necessary or intended for the lights to be on all " + "night, or encourage occupants to turn the lights off upon " + "exit.", "Savings": round((0.4 * 0.1 * elec_cost * percent_l * (avg_week / (24 * 7 - med_num_op_hrs))), 2), } else: return {}
def setback_non_op(ZAT, DAT, op_hours, elec_cost, area, HVACstat=None): """ Checks to see if a location is being cooled or heated during non-operational hours. Parameters: - ZAT: zone air temperature, 2D array of datetime and data - DAT: discharge air temperature, 2D array of datetime and data - HVACstat: HVAC status (optional), 2D array of datetime and data - 0 - off, 1 - ventilation, 3 - compressor - op_hours: operational hours - [[operational hours], [business days], [holidays]] - elec_cost: The electricity cost used to calculate savings. Returns: - flag indicating whether or not this should be flagged """ # separate hours of ZAT and DAT ZAT_op, ZAT_non_op = separate_hours(ZAT, op_hours[0], op_hours[1], op_hours[2]) DAT_op, DAT_non_op = separate_hours(DAT, op_hours[0], op_hours[1], op_hours[2]) # if HVAC status exists, separate that too if (HVACstat): HVAC_op, HVAC_non_op = separate_hours(HVACstat, op_hours[0], op_hours[1], op_hours[2]) else: HVAC_op = [] HVAC_non_op = [] # separate data into cooling, heating, and hvac op_cool_dat, op_heat_dat, op_hvac_dat = _grab_data(DAT_op, ZAT_op, DAT_op, \ HVAC_op) non_cool_dat, non_heat_dat, non_hvac_dat = _grab_data(DAT_non_op, \ ZAT_non_op, DAT_non_op, HVAC_non_op) # find the average cooling diffuser set temp avg_DAT_c_occ = np.mean(op_cool_dat) avg_DAT_h_occ = np.mean(op_heat_dat) # count to see if cooling is on c_flag = 0 for pt in non_cool_dat: if ((pt < avg_DAT_c_occ) or (abs(pt - avg_DAT_h_occ) < 0.1)): c_flag += 1 # h_flag = 0 for pt in non_heat_dat: if ((pt > avg_DAT_h_occ) or (abs(pt - avg_DAT_h_occ) < 0.1)): h_flag += 1 # non_op_data_len = len(DAT_non_op) c_val = c_flag/non_op_data_len h_val = h_flag/non_op_data_len vent_val = non_hvac_dat/non_op_data_len percent_unocc = non_op_data_len/len(DAT) non_cool_zat, non_heat_zat, non_hvac_zat = _grab_data(DAT_non_op, \ ZAT_non_op, ZAT_non_op, HVAC_non_op) # ZAT_cool_threshold = 80 ZAT_heat_threshold = 55 # if non_cool_zat != []: min_ZAT_c_unocc = np.min(non_cool_zat) else: min_ZAT_c_unocc = ZAT_cool_threshold if non_heat_zat != []: max_ZAT_h_unocc = np.max(non_heat_zat) else: max_ZAT_h_unocc = ZAT_heat_threshold # if ((c_val + h_val + vent_val) > 0.3): percent_l, percent_h, percent_c, med_num_op_hrs, per_hea_coo, \ percent_HV = get_CBECS(area) c_savings = (ZAT_cool_threshold-min_ZAT_c_unocc) * 0.03 * c_val * \ percent_c * elec_cost * percent_unocc h_savings = (max_ZAT_h_unocc-ZAT_heat_threshold) * 0.03 * h_val * \ percent_h * elec_cost * percent_unocc ven_savings = 0.06* vent_val * elec_cost * percent_unocc return { 'Problem': "Nighttime thermostat setbacks are not enabled.", 'Diagnostic': "More than 30 percent of the data indicates that the " + \ "building is being conditioned or ventilated normally " + \ "during unoccupied hours.", 'Recommendation': "Program your thermostats to decrease the " + \ "heating setpoint, or increase the cooling setpoint during " + \ "unoccuppied times. Additionally, you may have a " + \ "contractor configure the RTU to reduce ventilation.", 'Savings': round((c_savings + h_savings + ven_savings), 2)} else: return {}
def excessive_daylight(light_data, op_sched, area, elec_cost): """ Excessive Daylight checks to see a single sensor should be flagged. Parameters: - light_data: a 2d array with datetime and light status - lights are on (1, True) or off (0, False) - separate the data for operating hours and non-operating hours - op_sched: 2d array of operating schedule -[[operational hours], [days operating], [holidays]] - elec_cost: The electricity cost used to calculate savings. Returns: True or False (true meaning that this sensor should be flagged) """ occupied_data, nonoccupied_data = separate_hours(light_data, op_sched[0], op_sched[1], op_sched[2]) # Grabs the first time it starts logging so we know when the next day is day_marker = occupied_data[0][0] # counts times when lights go from on to off on_to_off = 0 # counts the seconds when the lights are on time_on = datetime.timedelta(0) # counts flagged days day_flag = 0 # counts the total number of days day_count = 0 # total operation hours operational_hours = op_sched[0][1] - op_sched[0][0] # accounts for the first point, checks if the lights are on, sets when # lights were last set to on to the first time if (occupied_data[0][1] == 1): lights_on = True else: lights_on = False # Find the first index when lights are on. # FIXME: Is this a valid substitution? for light_dpt in occupied_data: if light_dpt[1]: last_on = light_dpt[0] break # FIXME: Separate the data during operational hours vs non-operational hours. # iterate through the light data i = 1 while (i < len(occupied_data)): # NOTE: Variable 'day_count' is incremented when the current date changes # from the previous date and at the end of the record. if (occupied_data[i][0].date() != day_marker.date() or i == len(occupied_data) - 1): # Check if day should be flagged, time delta is in days and seconds if (occupied_data[i][1] == 1): time_on += (occupied_data[i][0] - last_on) # if (time_on.days != 0): time_on_hours = (24 * time_on.days) + time_on.seconds / 3600 else: time_on_hours = time_on.seconds / 3600 # if ((on_to_off < 2) and ((time_on_hours / operational_hours) > 0.5)): day_flag += 1 # NOTE: Reset flags each day and reinitialize day markers. The light control # diagnostic is concerned with checking whether the light turns off each # day. day_count += 1 day_marker = occupied_data[i][0] on_to_off = 0 time_on = datetime.timedelta(0) # Check lights were turned off, if so, increment on_to_off, lights # are now off, add time on to timeOn if ((lights_on) and (occupied_data[i][1] == 0)): on_to_off += 1 lights_on = False time_on += (occupied_data[i][0] - last_on) # Check if lights were turned on, set last_on to the current time elif ((not lights_on) and (occupied_data[i][1] == 1)): lights_on = True last_on = occupied_data[i][0] i += 1 # If more than half of the days are flagged, there's a problem. if (day_flag / day_count > 0.5): percent_l, percent_h, percent_c, med_num_op_hrs, per_hea_coo, \ percent_HV = get_CBECS(area) total_time = occupied_data[-1][0] - occupied_data[0][0] total_weeks = ((total_time.days * 24) + (total_time.seconds / 3600)) \ / 168 avg_week = ((total_time.days * 24) + (total_time.seconds / 3600)) \ / total_weeks return { 'Problem': "Excessive lighting during occupied/daytime hours.", 'Diagnostic': "Even though these spaces are not continuously " + \ "occupied, for more than half of the monitoring period, the " + \ "lights were switched off less than three times a day.", 'Recommendation': "Install occupancy sensors in locations with " + \ "intermittent occupancy, or engage occupants to turn the " + \ "lights off when they leave the area.", 'Savings': round(elec_cost * percent_l * 0.6 * 0.1 * \ (avg_week/med_num_op_hrs), 2) } else: return {}
def comfort_and_setpoint(ZAT, DAT, op_hours, area, elec_cost, HVACstat=None): """ Checks if the building is comfortable and if the setpoints are too narrow. Parameters: - ZAT: Zone air temperature - 2d array with each element as [datetime, data] - DAT: Discharge air temperature - 2d array with each element as [datetime, data] - HVACstat (optional): HVAC status - 2d array with each element as [datetime, data] - op_hours: operational hours, list - [operational hours, [days operating], [holidays]] - elec_cost: The electricity cost used to calculate savings. Returns: - setpoint_flag: Dictionary of the problem, diagnostic, recommendation, and savings if there is an issue, otherwise is False. - comfort_flag: Dictionary of the problem, diagnostic, recommendation, and savings if there is an issue, otherwise is False. """ # separate data to get occupied data ZAT_op, ZAT_non_op = \ separate_hours(ZAT, op_hours[0], op_hours[1], op_hours[2]) DAT_op, DAT_non_op = \ separate_hours(DAT, op_hours[0], op_hours[1], op_hours[2]) # get data in which cooling/heating are considered on cool_on = [] heat_on = [] # count the times it is deamed uncomfortable over_cool = 0 under_cool = 0 over_heat = 0 under_heat = 0 # if there's HVAC status, separate that data if (HVACstat != None): HVAC_op, HVAC_non_op = \ separate_hours(HVACstat, op_hours[0], op_hours[1], op_hours[2]) # iterate through the data i = 0 while (i < len(DAT_op)): # if DAT is less than 90% of ZAT, it's cooling if (DAT_op[i][1] < (0.9 * ZAT_op[i][1])): # If there's HVAC, make sure it's actually cooling if (HVACstat): if (HVAC_op[i][1] == 0): i += 1 continue # if DAT is less than 75 F, it's over cooling if (DAT_op[i][1] < 75): over_cool += 1 # if DAT is greater than 80 F, it's under cooling elif (DAT_op[i][1] > 80): under_cool += 1 cool_on.append(ZAT_op[i][1]) # if DAT greater than 110% ZAT, then it's heating elif (DAT_op[i][1] > (1.1 * ZAT_op[i][1])): # if there's HVAC status, make sure it's actually heating if (HVACstat): if (HVAC_op[i][1] == 0): i += 1 continue # if DAT is less than 69 F, it's under heating if (DAT_op[i][1] < 69): under_heat += 1 # if DAT is over 72 F, it's over heating elif (DAT_op[i][1] > 72): over_heat += 1 heat_on.append(ZAT_op[i][1]) i += 1 cooling_threshold = 76. heating_threshold = 72. # Calculate the average if heat_on != []: heating_setpt = mean(heat_on) else: heating_setpt = heating_threshold if cool_on != []: cooling_setpt = mean(cool_on) else: cooling_setpt = cooling_threshold percent_l, percent_h, percent_c, med_num_op_hrs, per_hea_coo, \ percent_HV = get_CBECS(area) # percent_op = len(ZAT_op) / len(ZAT) over_cooling_perc = over_cool / len(DAT_op) over_heating_perc = over_heat / len(DAT_op) # percent_cooling = over_cooling_perc * percent_c * percent_op cooling_savings = (cooling_threshold - cooling_setpt) * 0.03 * \ percent_cooling *elec_cost # percent_heating = over_heating_perc * percent_h * percent_op heating_savings = (heating_setpt - heating_threshold) * 0.03 * \ percent_heating *elec_cost if ((heating_setpt > heating_threshold) and \ (cooling_setpt < cooling_threshold)): setpoint_flag = { 'Problem': "Overly narrow separation between heating " + \ "and cooling setpoints.", 'Diagnostic': "During occupied hours, the cooling setpoint was lower " + \ "than 76F and the heating setpoint was greater than 72F.", 'Recommendation': "Adjust the heating and cooling setpoints so that " + \ "they differ by more than four degrees.", 'Savings': round(cooling_savings + heating_savings, 2) } else: setpoint_flag = {} if (over_cooling_perc > 0.3): comfort_flag = { 'Problem': "Over-conditioning, thermostat cooling setpoint is low", 'Diagnostic': "More than 30 percent of the time, the cooling setpoint " + \ "during occupied hours was lower than 75F, a temperature that " + \ "is comfortable to most occupants", 'Recommendation': "Program your thermostats to increase the cooling " + \ "setpoint during occupied hours.", 'Savings': round(cooling_savings, 2) } elif (under_cool / len(DAT_op) > 0.3): comfort_flag = { 'Problem': "Under-conditioning, thermostat cooling " + \ "setpoint is high.", 'Diagnostic': "More than 30 percent of the time, the cooling setpoint " + \ "during occupied hours was greater than 80F.", 'Recommendation': "Program your thermostats to decrease the cooling " + \ "setpoint to improve building comfort during occupied hours." } elif (over_heating_perc > 0.3): comfort_flag = { 'Problem': "Over-conditioning, thermostat heating " + \ "setpoint is high.", 'Diagnostic': "More than 30 percent of the time, the heating setpoint " + \ "during occupied hours was greater than 72F, a temperature that " + \ "is comfortable to most occupants.", 'Recommendation': "Program your thermostats to decrease the heating " + \ "setpoint during occupied hours.", 'Savings': round(heating_savings, 2) } elif (under_heat / len(DAT_op) > 0.3): comfort_flag = { 'Problem': "Under-conditioning - thermostat heating " + \ "setpoint is low.", 'Diagnostic': "For more than 30% of the time, the cooling setpoint " + \ "during occupied hours was less than 69 degrees F.", 'Recommendation': "Program thermostats to increase the heating " + \ "setpoint to improve building comfort during occupied hours." } else: comfort_flag = {} return comfort_flag, setpoint_flag
def comfort_and_setpoint(ZAT, DAT, op_hours, area, elec_cost, HVACstat=None): """ Checks if the building is comfortable and if the setpoints are too narrow. Parameters: - ZAT: Zone air temperature - 2d array with each element as [datetime, data] - DAT: Discharge air temperature - 2d array with each element as [datetime, data] - HVACstat (optional): HVAC status - 2d array with each element as [datetime, data] - op_hours: operational hours, list - [operational hours, [days operating], [holidays]] - elec_cost: The electricity cost used to calculate savings. Returns: - setpoint_flag: Dictionary of the problem, diagnostic, recommendation, and savings if there is an issue, otherwise is False. - comfort_flag: Dictionary of the problem, diagnostic, recommendation, and savings if there is an issue, otherwise is False. """ # separate data to get occupied data ZAT_op, ZAT_non_op = \ separate_hours(ZAT, op_hours[0], op_hours[1], op_hours[2]) DAT_op, DAT_non_op = \ separate_hours(DAT, op_hours[0], op_hours[1], op_hours[2]) # get data in which cooling/heating are considered on cool_on = [] heat_on = [] # count the times it is deamed uncomfortable over_cool = 0 under_cool = 0 over_heat = 0 under_heat = 0 # if there's HVAC status, separate that data if (HVACstat != None): HVAC_op, HVAC_non_op = \ separate_hours(HVACstat, op_hours[0], op_hours[1], op_hours[2]) # iterate through the data i = 0 while (i < len(DAT_op)): # if DAT is less than 90% of ZAT, it's cooling if (DAT_op[i][1] < (0.9 * ZAT_op[i][1])): # If there's HVAC, make sure it's actually cooling if (HVACstat): if (HVAC_op[i][1] == 0): i += 1 continue # if DAT is less than 75 F, it's over cooling if (DAT_op[i][1] < 75): over_cool += 1 # if DAT is greater than 80 F, it's under cooling elif (DAT_op[i][1] > 80): under_cool += 1 cool_on.append(ZAT_op[i][1]) # if DAT greater than 110% ZAT, then it's heating elif (DAT_op[i][1] > (1.1 * ZAT_op[i][1])): # if there's HVAC status, make sure it's actually heating if (HVACstat): if (HVAC_op[i][1] == 0): i += 1 continue # if DAT is less than 69 F, it's under heating if (DAT_op[i][1] < 69): under_heat += 1 # if DAT is over 72 F, it's over heating elif (DAT_op[i][1] > 72): over_heat += 1 heat_on.append(ZAT_op[i][1]) i += 1 cooling_threshold = 76. heating_threshold = 72. # Calculate the average if heat_on != []: heating_setpt = mean(heat_on) else: heating_setpt = heating_threshold if cool_on != []: cooling_setpt = mean(cool_on) else: cooling_setpt = cooling_threshold percent_l, percent_h, percent_c, med_num_op_hrs, per_hea_coo, \ percent_HV = get_CBECS(area) # percent_op = len(ZAT_op)/len(ZAT) over_cooling_perc = over_cool/len(DAT_op) over_heating_perc = over_heat/len(DAT_op) # percent_cooling = over_cooling_perc * percent_c * percent_op cooling_savings = (cooling_threshold - cooling_setpt) * 0.03 * \ percent_cooling *elec_cost # percent_heating = over_heating_perc * percent_h * percent_op heating_savings = (heating_setpt - heating_threshold) * 0.03 * \ percent_heating *elec_cost if ((heating_setpt > heating_threshold) and \ (cooling_setpt < cooling_threshold)): setpoint_flag = { 'Problem': "Overly narrow separation between heating " + \ "and cooling setpoints.", 'Diagnostic': "During occupied hours, the cooling setpoint was lower " + \ "than 76F and the heating setpoint was greater than 72F.", 'Recommendation': "Adjust the heating and cooling setpoints so that " + \ "they differ by more than four degrees.", 'Savings': round(cooling_savings + heating_savings, 2) } else: setpoint_flag = {} if (over_cooling_perc > 0.3): comfort_flag = { 'Problem': "Over-conditioning, thermostat cooling setpoint is low", 'Diagnostic': "More than 30 percent of the time, the cooling setpoint " + \ "during occupied hours was lower than 75F, a temperature that " + \ "is comfortable to most occupants", 'Recommendation': "Program your thermostats to increase the cooling " + \ "setpoint during occupied hours.", 'Savings': round(cooling_savings, 2) } elif (under_cool/len(DAT_op) > 0.3): comfort_flag = { 'Problem': "Under-conditioning, thermostat cooling " + \ "setpoint is high.", 'Diagnostic': "More than 30 percent of the time, the cooling setpoint " + \ "during occupied hours was greater than 80F.", 'Recommendation': "Program your thermostats to decrease the cooling " + \ "setpoint to improve building comfort during occupied hours." } elif (over_heating_perc > 0.3): comfort_flag = { 'Problem': "Over-conditioning, thermostat heating " + \ "setpoint is high.", 'Diagnostic': "More than 30 percent of the time, the heating setpoint " + \ "during occupied hours was greater than 72F, a temperature that " + \ "is comfortable to most occupants.", 'Recommendation': "Program your thermostats to decrease the heating " + \ "setpoint during occupied hours.", 'Savings': round(heating_savings, 2) } elif (under_heat/len(DAT_op) > 0.3): comfort_flag = { 'Problem': "Under-conditioning - thermostat heating " + \ "setpoint is low.", 'Diagnostic': "For more than 30% of the time, the cooling setpoint " + \ "during occupied hours was less than 69 degrees F.", 'Recommendation': "Program thermostats to increase the heating " + \ "setpoint to improve building comfort during occupied hours." } else: comfort_flag = {} return comfort_flag, setpoint_flag