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 {}
Exemplo n.º 2
0
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 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 {}
Exemplo n.º 5
0
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 {}
Exemplo n.º 6
0
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 {}
Exemplo n.º 7
0
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