diff --git a/bpeng/reports/constants.py b/bpeng/reports/constants.py index 7ccdfef248860bb11149d8ca91c662d17d1de799..8ce0ca7dbe54c3b0f710993eaa4fcce01eb82bbe 100644 --- a/bpeng/reports/constants.py +++ b/bpeng/reports/constants.py @@ -39,7 +39,7 @@ MASTER_TO_SLIDE = { 'slide_index': 10, 'slide_name': 'Install Smart Thermostat with Indoor Sensors', }, - 'Building Management System/ Energy Management system': { + 'Building Management System/ Energy Management System': { 'slide_index': 11, 'slide_name': 'Integrate EMS/BMS Systems', }, @@ -295,7 +295,387 @@ MASTER_TO_SLIDE = { }, } -# Convert template ECM names to the names that are in the database +RETROFIT_TO_PNS_SLIDE = { + 'Weatherstripping (Exterior Doors)': { + 'slide_index': 10, + 'slide_name': 'Weather-stripping (A/C Units, Exterior Doors, Windows)', + 'category': 'Envelope', + }, + 'Weatherstripping (Windows)': { + 'slide_index': 10, + 'slide_name': 'Weather-stripping (A/C Units, Exterior Doors, Windows)', + 'category': 'Envelope', + }, + 'Weatherstripping (A/C Units)': { + 'slide_index': 10, + 'slide_name': 'Weather-stripping (A/C Units, Exterior Doors, Windows)', + 'category': 'Envelope', + }, + 'Window Replacement': { + 'slide_index': 11, + 'slide_name': 'Window Replacement', + 'category': 'Envelope', + }, + 'Insulation (Roof)': { + 'slide_index': 8, + 'slide_name': 'Insulation (Roof)', + 'category': 'Envelope', + }, + 'Insulation (Wall)': { + 'slide_index': 9, + 'slide_name': 'Insulation (Wall)', + 'category': 'Envelope', + }, + 'Cool Roof': { + 'slide_index': 6, + 'slide_name': 'Cool Roof', + 'category': 'Envelope', + }, + 'Boiler Control (Indoor Feedback)': { + 'slide_index': 13, + 'slide_name': 'Boiler Control (Indoor Feedback)', + 'category': 'Control', + }, + 'Boiler Control (Outdoor Reset)': { + 'slide_index': 14, + 'slide_name': 'Boiler Control (Outdoor Reset)', + 'category': 'Control', + }, + 'Smart Thermostat w/ Indoor Sensor': { + 'slide_index': 20, + 'slide_name': 'Smart Thermostat with Indoor Sensors', + 'category': 'Control', + }, + 'Building Management System/Energy Management System': { + 'slide_index': 15, + 'slide_name': 'Building/ Energy Management System', + 'category': 'Control', + }, + 'Oil to Gas Conversion': { + 'slide_index': 54, + 'slide_name': 'Oil to Gas Conversion', + 'category': 'Source', + }, + 'Boiler Replacement': { + 'slide_index': 49, + 'slide_name': 'Boiler Replacement', + 'category': 'Source', + }, + 'Burner Replacement': { + 'slide_index': 50, + 'slide_name': 'Burner Replacement', + 'category': 'Source', + }, + 'Modulating Burner Controls': { + 'slide_index': 19, + 'slide_name': 'Modulating Burner Controls', + 'category': 'Control', + }, + 'Sealed Combustion/Power Burner Installation': { + 'slide_index': 56, + 'slide_name': 'Sealed Combustion/Power Burner Installation', + 'category': 'Source', + }, + 'Pipe Insulation': { + 'slide_index': 31, + 'slide_name': 'Install Pipe Insulation', + 'category': 'Distribution', + }, + 'Smart Pumps': { + 'slide_index': 29, + 'slide_name': 'Smart Pumps', + 'category': 'Distribution', + }, + 'Variable Frequency Drive Pumps': { + 'slide_index': 30, + 'slide_name': 'Variable Frequency Drive Pumps', + 'category': 'Distribution', + }, + 'TRVs (Steam, 1 pipe)': { + 'slide_index': 18, + 'slide_name': 'Thermostatic Radiator Valves (TRVs)', + 'category': 'Control', + }, + 'TRVs (Steam, 2 pipe)': { + 'slide_index': 18, + 'slide_name': 'Thermostatic Radiator Valves (TRVs)', + 'category': 'Control', + }, + 'TRVs (Hydronic)': { + 'slide_index': 18, + 'slide_name': 'Thermostatic Radiator Valves (TRVs)', + 'category': 'Control', + }, + 'Baseboard Replacement': { + 'slide_index': 26, + 'slide_name': 'Baseboard Replacement', + 'category': 'Distribution', + }, + 'Hydronic Conversion': { + 'slide_index': 53, + 'slide_name': 'Hydronic Conversion', + 'category': 'Source', + }, + 'Master Venting': { + 'slide_index': 35, + 'slide_name': 'Master Venting', + 'category': 'Distribution', + }, + 'Steam Boiler Setting Optimization': { # NOTE: this is not currently in the retrofit master list + 'slide_index': 22, + 'slide_name': 'Boiler Setting Optimization', + 'category': 'Control', + }, + 'Air Vent Installation/Replacement': { + 'slide_index': 32, + 'slide_name': 'Air Vent Installation/Replacement', + 'category': 'Distribution', + }, + 'Steam Trap Installation/Replacement (1 pipe)': { + 'slide_index': 38, + 'slide_name': 'Steam Trap Installation/Replacement', + 'category': 'Distribution', + }, + 'Steam Trap Installation/Replacement (2 pipe)': { + 'slide_index': 38, + 'slide_name': 'Steam Trap Installation/Replacement', + 'category': 'Distribution', + }, + 'Condensate Removal System Upgrade': { + 'slide_index': 33, + 'slide_name': 'Condensate Removal System Upgrade', + 'category': 'Distribution', + }, + 'Vented Condensate Pumps': { + 'slide_index': 40, + 'slide_name': 'Vented Condensate Pumps', + 'category': 'Distribution', + }, + 'Steam Boiler Anode Bars': { + 'slide_index': 58, + 'slide_name': 'Boiler Anode Bars', + 'category': 'Source', + }, + 'Rooftop Unit Replacement': { + 'slide_index': 55, + 'slide_name': 'Rooftop Unit Replacement', + 'category': 'Source', + }, + 'DHW Temperature Controls': { + 'slide_index': 16, + 'slide_name': 'Domestic Hot Water Temperature Control', + 'category': 'Control', + }, + 'Tankless Water Heater': { + 'slide_index': 61, + 'slide_name': 'Tankless Water Heater', + 'category': 'Source', + }, + 'Tanked Water Heater': { + 'slide_index': 60, + 'slide_name': 'Tanked Water Heater', + 'category': 'Source', + }, + 'Solar Water Heater': { + 'slide_index': 57, + 'slide_name': 'Solar Water Heater', + 'category': 'Source', + }, + 'Low-Flow Water Fixtures': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Automatic Water Meter Reader': { + 'slide_index': 63, + 'slide_name': 'Automatic Water Meter Reader', + 'category': 'Water', + }, + 'LED Lighting Install': { + 'slide_index': 42, + 'slide_name': 'LED Lighting Install', + 'category': 'Electricity', + }, + 'Lighting Controls (Motion Sensors/Timer)': { + 'slide_index': 43, + 'slide_name': 'Lighting Controls (Motion Sensors/Timer)', + 'category': 'Electricity', + }, + 'Solar Panels': { + 'slide_index': 47, + 'slide_name': 'Solar Panels', + 'category': 'Electricity', + }, + 'Replace Unit Appliances with Energy Star Models': { + 'slide_index': 46, + 'slide_name': 'Replace Unit Appliances with Energy Star Models', + 'category': 'Electricity', + }, + 'Replace Laundry Room Appliances w/ Energy Star Models': { + 'slide_index': 45, + 'slide_name': 'Replace Laundry Room Appliances with Energy Star Models', + 'category': 'Electricity', + }, + 'DHW/Space Heating System Separation': { + 'slide_index': 51, + 'slide_name': 'DHW/Space Heating System Separation', + 'category': 'Source', + }, + 'Master Metering to Submetering Conversion': { + 'slide_index': 44, + 'slide_name': 'Master Metering to Submetering Conversion', + 'category': 'Electricity', + }, + 'High Efficiency Furnace Installation': { + 'slide_index': 52, + 'slide_name': 'High Efficiency Furnace Installation', + 'category': 'Source', + }, + 'Duct Sealing/Insulation': { + 'slide_index': 27, + 'slide_name': 'Duct Sealing/Insulation', + 'category': 'Distribution', + }, + 'Temperature Control Optimization: Impose Min/Max Temp Rule': { + 'slide_index': 17, + 'slide_name': 'Impose Min/Max Temp. Optimization', + 'category': 'Control', + }, + 'Boiler Blowdown Smart Controls': { + 'slide_index': 21, + 'slide_name': 'Boiler Blowdown Smart Controls', + 'category': 'Control', + }, + 'Modulating Zone Valves': { + 'slide_index': 23, + 'slide_name': 'Modulating Zone Valves', + 'category': 'Control', + }, + 'Drip Leg Installation': { + 'slide_index': 34, + 'slide_name': 'Drip Leg Installation', + 'category': 'Distribution', + }, + 'Radiant Barriers for Wall/Enclosed Radiators': { + 'slide_index': 37, + 'slide_name': 'Radiant Barriers for Wall/Enclosed Radiators', + 'category': 'Distribution', + }, + 'Orifice Plates': { + 'slide_index': 36, + 'slide_name': 'Orifice Plates', + 'category': 'Distribution', + }, + 'Smart Boiler Feed Pump': { + 'slide_index': 59, + 'slide_name': 'Smart Boiler Feed Pump', + 'category': 'Source', + }, + 'Vacuum Pumps': { + 'slide_index': 39, + 'slide_name': 'Vacuum Pumps', + 'category': 'Distribution', + }, + 'Flow Balancing Valve Installation': { + 'slide_index': 28, + 'slide_name': 'Flow Balancing Valve Installation', + 'category': 'Distribution', + }, + 'Green Roof': { + 'slide_index': 7, + 'slide_name': 'Green Roof', + 'category': 'Envelope', + }, + 'Pressure Reducing Valve': { + 'slide_index': 25, + 'slide_name': 'Add Pressure Reducing Valve', + 'category': 'Distribution', + }, + # These do not have a slide, putting them with the last slide + # They will be dealt with later + 'HVAC Filter Replacement': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Portable Fan Installation': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Exhaust Fan Replacement': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Wall-mounted HEPA filter': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Mold Damage Repair': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Carpet/Floor-Tile Replacement': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Painted Surface Repair (Re-paint or paint removal)': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Ceiling Tile Replacement': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Heat Recovery Ventilation System Installation': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Energy Recovery Ventilation System Installation': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Dedicated Outdoor Air System Installation': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Adjustable Grille/Register Installation': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Cascading Air Filter System': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Blower Fan Replacement': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Zone Damper Controls': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, + 'Roof or Attic Sheathing': { + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', + }, +} + +# Convert excel template ECM names to the names that are in the database # Template ECM to database ECM is a 1 to n mapping so we just take the first one RETROFIT_TO_CBRA_RETROFIT = { @@ -306,7 +686,7 @@ RETROFIT_TO_CBRA_RETROFIT = { 'Install Boiler Control (Indoor Feedback)': 'Boiler Control (Indoor Feedback)', 'Install Boiler Control (Outdoor Reset)': 'Boiler Control (Outdoor Reset)', 'Install Smart Thermostat with Indoor Sensors': 'Smart Thermostat w/ Indoor Sensor', - 'Integrate EMS/BMS Systems': 'Building Management System/ Energy Management system', + 'Integrate EMS/BMS Systems': 'Building Management System/ Energy Management System', 'Convert from Oil to Gas': 'Oil to Gas Conversion', 'Replace Boiler': 'Boiler Replacement', 'Replace Burner & Controls': 'Burner Replacement', @@ -332,3 +712,90 @@ RETROFIT_TO_CBRA_RETROFIT = { 'Install Solar Panels': 'Solar Panels', 'Replace Appliances with Energy Star Appliances': 'Replace Unit Appliances w/ Energy Star Models', } + +# For CBRA customer facing PNS + +RETROFIT_TO_CBRA_PNS = { + # excel name : retrofit master list name + 'Cool Roof': 'Cool Roof', + 'Green Roof': 'Green Roof', + 'Insulation (Roof)': 'Insulation (Roof)', + 'Insulation (Wall)': 'Insulation (Wall)', + 'Weather-stripping (A/C Units, Exterior Doors, Windows)': 'Weatherstripping (Exterior Doors)', + 'Window Replacement': 'Window Replacement', + 'Boiler Control (Indoor Feedback)': 'Boiler Control (Indoor Feedback)', + 'Boiler Control (Outdoor Reset)': 'Boiler Control (Outdoor Reset)', + 'Building/Energy Management System': 'Building Management System/Energy Management System', + 'DHW Temperature Controls': 'DHW Temperature Controls', + 'Impose Min/Max Temperature Optimization': 'Temperature Control Optimization: Impose Min/Max Temp Rule', + 'Thermostatic Radiator Valves (TRVs)': 'TRVs (Steam, 1 pipe)', + 'Modulating Burner Controls': 'Modulating Burner Controls', + 'Smart Thermostat with Indoor Sensors': 'Smart Thermostat w/ Indoor Sensor', + 'Boiler Blowdown Smart Controls': 'Boiler Blowdown Smart Controls', + 'Boiler Setting Optimization': 'Steam Boiler Setting Optimization', + 'Modulating Zone Valves': 'Modulating Zone Valves', + 'Add Pressure Reducing Valve': 'Pressure Reducing Valve', + 'Baseboard Replacement': 'Baseboard Replacement', + 'Duct Sealing/Insulation': 'Duct Sealing/Insulation', + 'Flow Balancing Valve Installation': 'Flow Balancing Valve Installation', + 'Smart Pumps': 'Smart Pumps', + 'Variable Speed Pumps': 'Variable Frequency Drive Pumps', + 'Pipe Insulation': 'Pipe Insulation', + 'Air Vent Installation/Replacement': 'Air Vent Installation/Replacement', + 'Condensate Removal System Upgrade': 'Condensate Removal System Upgrade', + 'Drip Leg Installation': 'Drip Leg Installation', + 'Master Venting': 'Master Venting', + 'Orifice Plates': 'Orifice Plates', + 'Radiant Barriers for Wall/Enclosed Radiators': 'Radiant Barriers for Wall/Enclosed Radiators', + 'Steam Trap Installation/Replacement': 'Steam Trap Installation/Replacement (1 pipe)', + 'Vacuum Pumps': 'Vacuum Pumps', + 'Vented Condensate Pumps': 'Vented Condensate Pumps', + 'LED Lighting Install': 'LED Lighting Install', + 'Lighting Controls (Motion Sensors/Timer)': 'Lighting Controls (Motion Sensors/Timer)', + 'Master Metering to Submetering Conversion': 'Master Metering to Submetering Conversion', + 'Replace Laundry Room Appliances with Energy Star Models': 'Replace Laundry Room Appliances w/ Energy Star Models', + 'Replace Unit Appliances with Energy Star Models': 'Replace Unit Appliances with Energy Star Models', + 'Solar Panels': 'Solar Panels', + 'Boiler Replacement': 'Boiler Replacement', + 'Burner Replacement': 'Burner Replacement', + 'DHW/Space Heating System Separation': 'DHW/Space Heating System Separation', + 'High Efficiency Furnace Installation': 'High Efficiency Furnace Installation', + 'Hydronic Conversion': 'Hydronic Conversion', + 'Oil to Gas Conversion': 'Oil to Gas Conversion', + 'Rooftop Unit Replacement': 'Rooftop Unit Replacement', + 'Sealed Combustion/Power Burner Installation': 'Sealed Combustion/Power Burner Installation', + 'Solar Water Heater': 'Solar Water Heater', + 'Boiler Anode Bars': 'Steam Boiler Anode Bars', + 'Smart Boiler Feed Pump': 'Smart Boiler Feed Pump', + 'Tanked Water Heater': 'Tanked Water Heater', + 'Tankless Water Heater': 'Tankless Water Heater', + 'Low-Flow Water Fixtures': 'Low-Flow Water Fixtures', + 'Automatic Water Meter Reader': 'Automatic Water Meter Reader', +} + +PNS_DIVIDER_SLIDE = { + 'Envelope': { + 'slide_index': 5, + 'slide_name': 'Building Envelope', + }, + 'Control': { + 'slide_index': 12, + 'slide_name': 'Control', + }, + 'Distribution': { + 'slide_index': 24, + 'slide_name': 'Distribution', + }, + 'Electricity': { + 'slide_index': 41, + 'slide_name': 'Electricity', + }, + 'Source': { + 'slide_index': 48, + 'slide_name': 'Source', + }, + 'Water': { + 'slide_index': 62, + 'slide_name': 'Water', + }, +} diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py new file mode 100644 index 0000000000000000000000000000000000000000..2faaf4701b7f6a0271ce8cb5e31948f8283026a4 --- /dev/null +++ b/bpeng/reports/customer_pns.py @@ -0,0 +1,571 @@ +""" +Code is designed to generate the client facing preliminary needs survey +from a filled out excel template. + +Based on code for the CBRA diagnostic report generator using python pptx +""" + +from datetime import datetime + +# import os +import pandas as pd +from pptx import Presentation +from pptx.dml.color import RGBColor +from pptx.enum.text import ( # pylint: disable=no-name-in-module + MSO_ANCHOR, + PP_ALIGN +) +from pptx.util import Inches, Pt + +from .constants import RETROFIT_TO_PNS_SLIDE, RETROFIT_TO_CBRA_PNS + + +# pylint: disable=protected-access,too-many-locals,too-many-branches,too-many-statements +def merge_cells_vertically(table, start_row_idx, end_row_idx, col_idx): + row_count = end_row_idx - start_row_idx + 1 + column_cells = [r.cells[col_idx] for r in table.rows][start_row_idx:] + + column_cells[0]._tc.set('rowSpan', str(row_count)) + for cell in column_cells[1:]: + cell._tc.set('vMerge', '1') + + +def merge_cells_horizontally(table, row_idx, start_col_idx, end_col_idx): + col_count = end_col_idx - start_col_idx + 1 + row_cells = [c for c in table.rows[row_idx].cells][start_col_idx:end_col_idx] + row_cells[0]._tc.set('gridSpan', str(col_count)) + for cell in row_cells[1:]: + cell._tc.set('hMerge', '1') + + +# the workaround function to merge cells in a table +def merge_cells(table, start_row_idx, end_row_idx, start_col_idx, end_col_idx): + for col_idx in range(start_col_idx, end_col_idx+1): + merge_cells_vertically(table, start_row_idx, end_row_idx, col_idx) + for row_idx in range(start_row_idx, end_row_idx+1): + merge_cells_horizontally(table, row_idx, start_col_idx, end_col_idx) + + +class CustomerPns: + """ + pptx template + excel template + """ + + @staticmethod + def _generate_report( + template_file, + project_summary_data, + building_information_data, + retrofit_data, + ): + """ + Generate powerpoint file + + Args: + template_file (str): path to pptx template + project_summary_data (dict): python dictionary of client name, + project street address, city, state, + and ZIP code, unit count, and building + square footage + building_information_data (dict): python dictionary containing client + responses to questions regarding + building conditions + retrofit_data (dict): python dictionary of selected retrofit, minimum + and maximum percent savings on utility bill [$] + + Returns: + str: project address and street name + object: presentation file of the customer PNS + """ + # Put all of the arguments into variables + client_name = project_summary_data['client_name'] + project_address = project_summary_data['project_address'] + project_city_state_zip = project_summary_data['project_city_state_zip'] + unit_count = project_summary_data['unit_count'] + total_gross_area = project_summary_data['total_gross_area'] + + sys_type = building_information_data['heating_system'] + complaint = building_information_data['tenant_complaints'] + boiler_control = building_information_data['boiler_controls'] + thermostat_location = building_information_data['thermostat_loc'] + standalone_dhw = building_information_data['dhw_status'] + age_boiler = building_information_data['boiler_age'] + age_dhw = building_information_data['dhw_age'] + age_roof = building_information_data['roof_age'] + age_window = building_information_data['window_age'] + window = building_information_data['window_type'] + op_need1 = building_information_data['spec_main1'] + op_need2 = building_information_data['spec_main2'] + op_need3 = building_information_data['spec_main3'] + op_need4 = building_information_data['spec_main4'] + op_need5 = building_information_data['spec_main5'] + + retrofit_list = retrofit_data['retrofit_list'] + retrofit_heat_min_save = retrofit_data['retrofit_heat_min_save'] + retrofit_elec_min_save = retrofit_data['retrofit_elec_min_save'] + retrofit_water_min_save = retrofit_data['retrofit_water_min_save'] + retrofit_heat_max_save = retrofit_data['retrofit_heat_max_save'] + retrofit_elec_max_save = retrofit_data['retrofit_elec_max_save'] + retrofit_water_max_save = retrofit_data['retrofit_water_max_save'] + retrofit_min_cost = retrofit_data['retrofit_min_cost'] + retrofit_max_cost = retrofit_data['retrofit_max_cost'] + retrofit_min_payback = retrofit_data['retrofit_min_payback'] + retrofit_max_payback = retrofit_data['retrofit_max_payback'] + retrofit_difficulty = retrofit_data['retrofit_difficulty'] + # retrofit_category = retrofit_data['retrofit_category'] + # Convert database strings to presentation strings + # Also calculate new heat or elec save in cases where + # database retrofit's are combined to a single presentation retrofit + new_retrofit_list = [] + new_retrofit_heat_min_save = [] + new_retrofit_elec_min_save = [] + new_retrofit_water_min_save = [] + new_retrofit_heat_max_save = [] + new_retrofit_elec_max_save = [] + new_retrofit_water_max_save = [] + for name, \ + heat_min, \ + elec_min, \ + water_min, \ + heat_max, \ + elec_max, \ + water_max \ + in zip(retrofit_list, + retrofit_heat_min_save, + retrofit_elec_min_save, + retrofit_water_min_save, + retrofit_heat_max_save, + retrofit_elec_max_save, + retrofit_water_max_save): + + presentation_name = RETROFIT_TO_PNS_SLIDE[name]['slide_name'] + if presentation_name in new_retrofit_list: + combine_index = new_retrofit_list.index(presentation_name) + # Right now we just add together heat and elec. + # In the future we might want to calculate the combined value for + # heat and elec differently based on the retrofit + new_retrofit_heat_min_save[combine_index] += heat_min + new_retrofit_elec_min_save[combine_index] += elec_min + new_retrofit_water_min_save[combine_index] += water_min + new_retrofit_heat_max_save[combine_index] += heat_max + new_retrofit_elec_max_save[combine_index] += elec_max + new_retrofit_water_max_save[combine_index] += water_max + else: + new_retrofit_list.append(presentation_name) + new_retrofit_heat_min_save.append(heat_min) + new_retrofit_elec_min_save.append(elec_min) + new_retrofit_water_min_save.append(water_min) + new_retrofit_heat_max_save.append(heat_max) + new_retrofit_elec_max_save.append(elec_max) + new_retrofit_water_max_save.append(water_max) + retrofit_list = new_retrofit_list + retrofit_heat_min_save = new_retrofit_heat_min_save + retrofit_elec_min_save = new_retrofit_elec_min_save + retrofit_water_min_save = new_retrofit_water_min_save + retrofit_heat_max_save = new_retrofit_heat_max_save + retrofit_elec_max_save = new_retrofit_elec_max_save + retrofit_water_max_save = new_retrofit_water_max_save + + # # Create a delete list by adding everything not in the retrofit_list + # retrofit_delete_list = [] + # for slide in RETROFIT_TO_PNS_SLIDE.values(): + # if slide['slide_name'] not in retrofit_list and slide['slide_index'] not in retrofit_delete_list: + # retrofit_delete_list.append(slide['slide_index']) + + # # for rm in retrofit_list: + # for slide in RETROFIT_TO_PNS_SLIDE.values(): + # category = slide['category'] + # if slide['category'] not in retrofit_category and \ + # PNS_DIVIDER_SLIDE[category]['slide_index'] not in retrofit_delete_list: + # retrofit_delete_list.append(PNS_DIVIDER_SLIDE[category]['slide_index']) + + # # Put delete list in reverse order so we delete from the back + # retrofit_delete_list.sort(reverse=True) + + # Define presentation to edit as the finished template + client_pns = Presentation(template_file) + + # Slide 1: cover slide + cover_slide = client_pns.slides[0] + cover_title = cover_slide.shapes.title + cover_subtitle = cover_slide.placeholders[1] + + cover_title.text = "{}".format(client_name) + cover_subtitle.text = "Preliminary Needs Survey Report \n for \n {}, {}".format( + project_address, + project_city_state_zip + ) + cover_report_date = cover_subtitle.text_frame.add_paragraph() + cover_report_date.text = str(datetime.now().strftime('%B %Y')) + + # Slide 2: Community Retrofit pitch + # Slide 3: Disclaimer Slide + # Slide 4: Building Profile and Needs + needs_slide = client_pns.slides[3] + + needs_slide.shapes.title.text_frame.paragraphs[0].text = "What You Told Us About" \ + " {}".format(project_address) + needs_slide.shapes.title.text_frame.paragraphs[0].font.color.rgb = RGBColor(0xff, 0xff, 0xff) + needs_slide.shapes.title.text_frame.paragraphs[0].font.name = 'Arial' + needs_slide.shapes.title.text_frame.paragraphs[0].font.bold = False + needs_slide.shapes.title.text_frame.paragraphs[0].font.size = Pt(22) + + bldg_info_head = [ + "Heating System", + "Tenant Complaints", + "Boiler Controls", + "Thermostat Location", + "Separate DHW", + "Boiler Age", + "DHW Age", + "Roof Age", + "Window Age", + "Window Type", + ] + + bldg_info = needs_slide.shapes.add_table( + len(bldg_info_head) + 1, + 2, + Inches(0.81), + Inches(0.75), + Inches(3.5), + Inches(4.55) + ) + bldg_info.table.first_row = True + bldg_info.table.rows.height = Inches(0.5) + bldg_info.table.columns[0].width = Inches(1.75) + bldg_info.table.columns[1].width = Inches(1.75) + merge_cells_horizontally(table=bldg_info.table, row_idx=0, start_col_idx=0, end_col_idx=1) + bldg_info.table.cell(0, 0).text = 'Building Information' + bldg_info.table.cell(0, 0).text_frame.paragraphs[0].font.size = Pt(14) + bldg_info.table.cell(0, 0).text_frame.paragraphs[0].font.bold = True + bldg_info.table.cell(0, 0).text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + bldg_info.table.cell(0, 0).vertical_anchor = MSO_ANCHOR.MIDDLE + + bldg_info.table.cell(1, 1).text = str(sys_type) + bldg_info.table.cell(2, 1).text = str(complaint) + bldg_info.table.cell(3, 1).text = str(boiler_control) + bldg_info.table.cell(4, 1).text = str(thermostat_location) + bldg_info.table.cell(5, 1).text = str(standalone_dhw) + bldg_info.table.cell(6, 1).text = str(age_boiler) + bldg_info.table.cell(7, 1).text = str(age_dhw) + bldg_info.table.cell(8, 1).text = str(age_roof) + bldg_info.table.cell(9, 1).text = str(age_window) + bldg_info.table.cell(10, 1).text = str(window) + + for j in range(len(bldg_info_head)): # pylint: disable=consider-using-enumerate + bldg_info.table.cell(j + 1, 0).text = bldg_info_head[j] + bldg_info.table.cell(j + 1, 0).text_frame.paragraphs[0].font.size = Pt(14) + bldg_info.table.cell(j + 1, 0).text_frame.paragraphs[0].font.bold = False + bldg_info.table.cell(j + 1, 0).text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + bldg_info.table.cell(j + 1, 0).vertical_anchor = MSO_ANCHOR.MIDDLE + bldg_info.table.rows[j + 1].height = Inches(0.3) + + op_needs = [op_need1, op_need2, op_need3, op_need4, op_need5] + op_needs = [x for x in op_needs if str(x) != 'nan'] + + op_needs_table = needs_slide.shapes.add_table( + len(op_needs) + 1, + 1, + Inches(5.76), + Inches(0.96), + Inches(3.4), + Inches(4.18) + ) + + op_needs_table.table.cell(0, 0).text_frame.paragraphs[0].font.size = Pt(14) + op_needs_table.table.cell(0, 0).text_frame.paragraphs[0].font.bold = True + op_needs_table.table.cell(0, 0).text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + op_needs_table.table.cell(0, 0).vertical_anchor = MSO_ANCHOR.MIDDLE + op_needs_table.table.cell(0, 0).text_frame.paragraphs[0].text = "Operating Needs" + + for j in range(len(op_needs)): # pylint: disable=consider-using-enumerate + op_needs_table.table.cell(j + 1, 0).text = op_needs[j] + op_needs_table.table.cell(j + 1, 0).text_frame.paragraphs[0].font.size = Pt(14) + op_needs_table.table.cell(j + 1, 0).text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + op_needs_table.table.cell(j + 1, 0).vertical_anchor = MSO_ANCHOR.MIDDLE + + # Slide 5: Estimated Energy and Water Savings + retrofit_recs_slide = client_pns.slides[4] + + retrofit_recs_slide.shapes[3].text_frame.paragraphs[0].text = "Estimated Energy and Water Savings" \ + " for {}".format(project_address) + retrofit_recs_slide.shapes[3].text_frame.paragraphs[0].font.color.rgb = RGBColor(0xff, 0xff, 0xff) + retrofit_recs_slide.shapes[3].text_frame.paragraphs[0].font.name = 'Arial' + retrofit_recs_slide.shapes[3].text_frame.paragraphs[0].font.bold = True + retrofit_recs_slide.shapes[3].text_frame.paragraphs[0].font.size = Pt(14) + + recs_table_head = [ + "Retrofit Measure", + "Heating Savings*", + "Electricity Savings*", + "Water Savings*" + ] + + recs = retrofit_recs_slide.shapes.add_table( + len(retrofit_list) + 1, + len(recs_table_head), + Inches(0.23), + Inches(0.69), + Inches(9.56), + Inches(4.66) + ) + recs.table.last_row = False + recs.table.horz_banding = False + recs.table.columns[0].width = Inches(3.77) + recs.table.columns[1].width = Inches(1.9) + recs.table.columns[2].width = Inches(2.17) + recs.table.columns[3].width = Inches(1.73) + + for j in range(len(recs_table_head)): # pylint: disable=consider-using-enumerate + recs.table.cell(0, j).text = recs_table_head[j] + recs.table.cell(0, j).text_frame.paragraphs[0].font.size = Pt(14) + recs.table.cell(0, j).text_frame.paragraphs[0].font.bold = True + recs.table.cell(0, j).text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + recs.table.cell(0, j).vertical_anchor = MSO_ANCHOR.MIDDLE + recs.table.cell(0, j).text_frame.paragraphs[0].font.color.rgb = RGBColor(0xff, 0xff, 0xff) + + for k, j in zip(retrofit_list, range(len(retrofit_list))): + recs.table.cell(j + 1, 0).text_frame.paragraphs[0].font.size = Pt(10) + recs.table.cell(j + 1, 0).text_frame.paragraphs[0].font.bold = True + recs.table.cell(j + 1, 0).vertical_anchor = MSO_ANCHOR.MIDDLE + recs.table.cell(j + 1, 0).text = k + + if retrofit_heat_max_save[j] != 0: + recs.table.cell(j + 1, 1).text_frame.paragraphs[0].font.size = Pt(10) + recs.table.cell(j + 1, 1).text_frame.paragraphs[0].font.bold = True + recs.table.cell(j + 1, 1).text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + recs.table.cell(j + 1, 1).vertical_anchor = MSO_ANCHOR.MIDDLE + recs.table.cell(j + 1, 1).text = str(int(retrofit_heat_min_save[j])) + \ + '-' + \ + str(int(retrofit_heat_max_save[j])) + \ + '\N{PERCENT SIGN}' + else: + recs.table.cell(j + 1, 1).text = '' + if retrofit_elec_max_save[j] != 0: + recs.table.cell(j + 1, 2).text_frame.paragraphs[0].font.size = Pt(10) + recs.table.cell(j + 1, 2).text_frame.paragraphs[0].font.bold = True + recs.table.cell(j + 1, 2).text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + recs.table.cell(j + 1, 2).vertical_anchor = MSO_ANCHOR.MIDDLE + recs.table.cell(j + 1, 2).text = str(int(retrofit_elec_min_save[j])) + \ + '-' + \ + str(int(retrofit_elec_max_save[j])) + \ + '\N{PERCENT SIGN}' + else: + recs.table.cell(j + 1, 2).text = '' + if retrofit_water_max_save[j] != 0: + recs.table.cell(j + 1, 3).text_frame.paragraphs[0].font.size = Pt(10) + recs.table.cell(j + 1, 3).text_frame.paragraphs[0].font.bold = True + recs.table.cell(j + 1, 3).text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + recs.table.cell(j + 1, 3).vertical_anchor = MSO_ANCHOR.MIDDLE + recs.table.cell(j + 1, 3).text = str(int(retrofit_water_min_save[j])) + \ + '-' + \ + str(int(retrofit_water_max_save[j])) + \ + '\N{PERCENT SIGN}' + else: + recs.table.cell(j + 1, 3).text = '' + + # Slide 6: Retrofit metrics + metrics_slide = client_pns.slides[5] + metrics_slide.shapes[2].text_frame.paragraphs[0].text = "Estimated Retrofit Implementation Costs" \ + " for {}".format(project_address) + metrics_slide.shapes[2].text_frame.paragraphs[0].font.color.rgb = RGBColor(0xff, 0xff, 0xff) + metrics_slide.shapes[2].text_frame.paragraphs[0].font.name = 'Arial' + metrics_slide.shapes[2].text_frame.paragraphs[0].font.bold = True + metrics_slide.shapes[2].text_frame.paragraphs[0].font.size = Pt(14) + + metrics_slide.shapes[1].text_frame.paragraphs[0].text = "These estimates are based on your building size" \ + " of {:,.0f} square feet and {} units.".format( + total_gross_area, + unit_count + ) + metrics_slide.shapes[1].text_frame.paragraphs[0].font.color.rgb = RGBColor(0xff, 0xff, 0xff) + metrics_slide.shapes[1].text_frame.paragraphs[0].font.name = 'Arial' + metrics_slide.shapes[1].text_frame.paragraphs[0].font.italics = True + metrics_slide.shapes[1].text_frame.paragraphs[0].font.size = Pt(10) + + metrics_table_head = [ + 'Retrofit Measure', + 'Difficulty of Install', + 'Minimum Cost*', + 'Maximum Cost*', + 'Payback* (Years)', + ] + metrics = metrics_slide.shapes.add_table( + len(retrofit_list) + 1, + len(metrics_table_head), + Inches(0.23), + Inches(0.68), + Inches(9.58), + Inches(4.66) + ) + + metrics.table.last_row = False + metrics.table.horz_banding = False + metrics.table.columns[0].width = Inches(3.22) + metrics.table.columns[1].width = Inches(1.18) + metrics.table.columns[2].width = Inches(1.79) + metrics.table.columns[3].width = Inches(1.79) + metrics.table.columns[4].width = Inches(1.48) + + # change numbers from input sheet for difficulty into strings of unicode + hammers = [''] * len(retrofit_difficulty) + for j in range(len(retrofit_difficulty)): # pylint: disable=consider-using-enumerate + counter = 0 + while counter < int(retrofit_difficulty[j]): + hammers[j] = hammers[j] + u'\U0001F528' + counter += 1 + + for j in range(len(metrics_table_head)): # pylint: disable=consider-using-enumerate + metrics.table.cell(0, j).text = metrics_table_head[j] + metrics.table.cell(0, j).vertical_anchor = MSO_ANCHOR.MIDDLE + metrics.table.cell(0, j).text_frame.paragraphs[0].font.size = Pt(14) + metrics.table.cell(0, j).text_frame.paragraphs[0].font.bold = True + metrics.table.cell(0, j).text_frame.paragraphs[0].font.color.rgb = RGBColor(0xff, 0xff, 0xff) + + for k, j in zip(retrofit_list, range(len(retrofit_list))): + metrics.table.cell(j + 1, 0).text_frame.paragraphs[0].font.size = Pt(10) + metrics.table.cell(j + 1, 0).vertical_anchor = MSO_ANCHOR.MIDDLE + metrics.table.cell(j + 1, 0).text_frame.paragraphs[0].font.bold = True + metrics.table.cell(j + 1, 0).text = k + metrics.table.cell(j + 1, 1).text_frame.paragraphs[0].font.size = Pt(12) + metrics.table.cell(j + 1, 1).text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + metrics.table.cell(j + 1, 1).text_frame.paragraphs[0].font.bold = True + metrics.table.cell(j + 1, 1).vertical_anchor = MSO_ANCHOR.MIDDLE + metrics.table.cell(j + 1, 1).text = hammers[j] + metrics.table.cell(j + 1, 2).text_frame.paragraphs[0].font.size = Pt(12) + metrics.table.cell(j + 1, 2).text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + metrics.table.cell(j + 1, 2).vertical_anchor = MSO_ANCHOR.MIDDLE + metrics.table.cell(j + 1, 2).text = '${:,.0f}'.format(int(round(retrofit_min_cost[j], -2))) + metrics.table.cell(j + 1, 3).text_frame.paragraphs[0].font.size = Pt(12) + metrics.table.cell(j + 1, 3).text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + metrics.table.cell(j + 1, 3).vertical_anchor = MSO_ANCHOR.MIDDLE + metrics.table.cell(j + 1, 3).text = '${:,.0f}'.format(int(round(retrofit_max_cost[j], -2))) + metrics.table.cell(j + 1, 4).text_frame.paragraphs[0].font.size = Pt(12) + metrics.table.cell(j + 1, 4).text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + metrics.table.cell(j + 1, 4).vertical_anchor = MSO_ANCHOR.MIDDLE + metrics.table.cell(j + 1, 4).text = str(int(retrofit_min_payback[j])) + \ + '-' + \ + str(int(retrofit_max_payback[j])) + + # Slides 5-end: removing slides based on recommendations + # for rm_index in retrofit_delete_list: + # client_pns.slides.delete_slide( + # client_pns, + # rm_index, + # ) + return project_address, client_pns + + @staticmethod + def generate_pns(): + """ + Generate report with finding file in current working directory + """ + template_file = 'PNS_SlideDeck.pptx' # TODO: input('Enter Template Name (with .pptx extension): ') + file_name = input('Enter PNS file name (with .xlsx extension): ') + file_input = pd.ExcelFile(file_name) + sheet_input = file_input.parse("Inputs", header=None) + ( + project_summary_data, + building_information_data, + retrofit_data + ) = CustomerPns.parse_arguments(sheet_input) + project_address, client_pns = CustomerPns._generate_report( + template_file, + project_summary_data, + building_information_data, + retrofit_data, + ) + client_pns.save('PNS {}.pptx'.format(project_address)) + + @staticmethod + def parse_arguments(sheet_input): + """ + Parse an excel file to generate arguments for the _generate_report function + + Args: + sheet_input (dataframe): The sheet where the data is located + + Returns: + dict: A dictionary containing all of the project summary data + dict: A dictionary containing all of the building information data + dict: A dictionary containing all of the retrofit data + """ + project_summary_data = { + 'client_name': sheet_input.loc[1, 1], + 'project_address': sheet_input.loc[2, 1], + 'project_city_state_zip': sheet_input.loc[3, 1], + 'unit_count': sheet_input.loc[4, 1], + 'total_gross_area': sheet_input.loc[5, 1], + } + + building_information_data = { + 'heating_system': sheet_input.loc[1, 3], + 'tenant_complaints': sheet_input.loc[2, 3], + 'boiler_controls': sheet_input.loc[3, 3], + 'thermostat_loc': sheet_input.loc[4, 3], + 'dhw_status': sheet_input.loc[5, 3], + 'boiler_age': sheet_input.loc[1, 5], + 'dhw_age': sheet_input.loc[2, 5], + 'roof_age': sheet_input.loc[3, 5], + 'window_age': sheet_input.loc[4, 5], + 'window_type': sheet_input.loc[5, 5], + 'spec_main1': sheet_input.loc[1, 7], + 'spec_main2': sheet_input.loc[2, 7], + 'spec_main3': sheet_input.loc[3, 7], + 'spec_main4': sheet_input.loc[4, 7], + 'spec_main5': sheet_input.loc[5, 7], + + } + + retrofit_list = [] + retrofit_heat_min_save = [] + retrofit_elec_min_save = [] + retrofit_water_min_save = [] + retrofit_heat_max_save = [] + retrofit_elec_max_save = [] + retrofit_water_max_save = [] + retrofit_min_cost = [] + retrofit_max_cost = [] + retrofit_min_payback = [] + retrofit_max_payback = [] + retrofit_category = [] + retrofit_difficulty = [] + + for j in range(len(sheet_input[1])): # pylint: disable=C0200 + if sheet_input.loc[j, 1] == 'Y': + # Convert to database string + retrofit_list.append( + RETROFIT_TO_CBRA_PNS[sheet_input.loc[j, 0]] + ) + retrofit_heat_min_save.append(sheet_input.loc[j+1, 1]) + retrofit_elec_min_save.append(sheet_input.loc[j+2, 1]) + retrofit_water_min_save.append(sheet_input.loc[j+3, 1]) + retrofit_heat_max_save.append(sheet_input.loc[j+1, 2]) + retrofit_elec_max_save.append(sheet_input.loc[j+2, 2]) + retrofit_water_max_save.append(sheet_input.loc[j+3, 2]) + retrofit_min_cost.append(sheet_input.loc[j, 3]) + retrofit_max_cost.append(sheet_input.loc[j, 4]) + retrofit_min_payback.append(sheet_input.loc[j, 5]) + retrofit_max_payback.append(sheet_input.loc[j, 6]) + retrofit_difficulty.append(sheet_input.loc[j, 7]) + retrofit_category.append(sheet_input.loc[j, 8]) + + retrofit_data = { + 'retrofit_list': retrofit_list, + 'retrofit_heat_min_save': retrofit_heat_min_save, + 'retrofit_elec_min_save': retrofit_elec_min_save, + 'retrofit_water_min_save': retrofit_water_min_save, + 'retrofit_heat_max_save': retrofit_heat_max_save, + 'retrofit_elec_max_save': retrofit_elec_max_save, + 'retrofit_water_max_save': retrofit_water_max_save, + 'retrofit_min_cost': retrofit_min_cost, + 'retrofit_max_cost': retrofit_max_cost, + 'retrofit_min_payback': retrofit_min_payback, + 'retrofit_max_payback': retrofit_max_payback, + 'retrofit_difficulty': retrofit_difficulty, + 'retrofit_category': retrofit_category, + } + + return project_summary_data, building_information_data, retrofit_data