From bf030b64dbd95cdee7909f603afd70bcc3611360 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Fri, 2 Jun 2017 09:42:01 -0400 Subject: [PATCH 01/31] Initial commit. --- bpeng/reports/customer_pns.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 bpeng/reports/customer_pns.py diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py new file mode 100644 index 0000000..c138fb4 --- /dev/null +++ b/bpeng/reports/customer_pns.py @@ -0,0 +1,7 @@ +""" +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 +""" + -- GitLab From f01f6568c64db75885d079181fce1867b1cdc9d2 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Fri, 2 Jun 2017 11:56:49 -0400 Subject: [PATCH 02/31] Import functions for input sheet parsing, slide deletion and table creation from diagnostic report generator --- bpeng/reports/customer_pns.py | 208 ++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index c138fb4..4ac85dc 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -5,3 +5,211 @@ 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.chart.data import ChartData +from pptx.dml.color import RGBColor +# from pptx.enum.chart import ( +# XL_CHART_TYPE, +# XL_LEGEND_POSITION, +# XL_TICK_LABEL_POSITION +# ) +from pptx.enum.text import ( # pylint: disable=no-name-in-module + MSO_AUTO_SIZE, + MSO_ANCHOR, + PP_ALIGN +) +from pptx.util import Inches, Pt + +from .constants import ECM_TO_SLIDE, RETROFIT_TO_CBRA_RETROFIT + + +class CustomerPns: + """ + pptx template + excel template + """ + + @staticmethod + def _generate_pns( + template_file, + project_summary_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 + 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'] + + 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'], + # 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 = ECM_TO_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 ECM_TO_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']) + + # 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) + + return project_address, client_pns + + @staticmethod + def generate_pns(): + """ + Generate report with finding file in current working directory + """ + template_file = input('Enter Template Name (with .pptx extension): ') + file_name = input('Enter File Name (with .xlsx extension): ') + file_input = pd.ExcelFile(file_name) + sheet_input = file_input.parse("Inputs", header=None) + ( + project_summary_data, + retrofit_data + ) = CustomerPns.parse_arguments(sheet_input) + project_address, client_pns = CustomerPns._generate_report( + template_file, + project_summary_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 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], + } + + input_list = sheet_input[0] + input_values = sheet_input[1] + input_values_more = sheet_input[2] + + retrofit_list = [] + retrofit_heat_min_save = [] + retrofit_elec_min_save = [] + retrofit_water_min_save = [] + for j in range(len(input_values)): # pylint: disable=C0200 + if input_values[j] == 'Y': + # Convert to database string + retrofit_list.append( + RETROFIT_TO_CBRA_RETROFIT[input_list[j]] + ) + retrofit_heat_min_save.append(input_values[j+1]) + retrofit_elec_min_save.append(input_values[j+2]) + retrofit_water_min_save.append(input_values[j+3]) + + retrofit_heat_max_save = [] + retrofit_elec_max_save = [] + retrofit_water_max_save = [] + for j in range(len(input_values)): # pylint: disable=C0200 + if input_values[j] == 'Y': + # Convert to database string + retrofit_list.append( + RETROFIT_TO_CBRA_RETROFIT[input_list[j]] + ) + retrofit_heat_max_save.append(input_values_more[j+1]) + retrofit_elec_max_save.append(input_values_more[j+2]) + retrofit_water_max_save.append(input_values_more[j+3]) + + 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, + } + + return project_summary_data, retrofit_data -- GitLab From 1970832071ad1d960a6dc1441655bc802e5ea3ae Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Wed, 7 Jun 2017 14:42:10 -0400 Subject: [PATCH 03/31] Add retrofit to slide mapping to the constants file. Change customer pns to use only the appropriate mapping --- bpeng/reports/constants.py | 368 +++++++++++++++++++++++++++++++++- bpeng/reports/customer_pns.py | 10 +- 2 files changed, 372 insertions(+), 6 deletions(-) diff --git a/bpeng/reports/constants.py b/bpeng/reports/constants.py index 1e661b3..7930189 100644 --- a/bpeng/reports/constants.py +++ b/bpeng/reports/constants.py @@ -1,3 +1,5 @@ +# For CBRA diagnostic report generator + ECM_TO_SLIDE = { 'Weatherstripping (Exterior Doors)': { 'slide_index': 4, @@ -295,7 +297,312 @@ ECM_TO_SLIDE = { }, } -# Convert template ECM names to the names that are in the database +RETROFIT_TO_PNS_SLIDE = { + 'Weatherstripping (Exterior Doors)': { + 'slide_index': 8, + 'slide_name': 'Weather-stripping (A/C Units, Exterior Doors, Windows)', + }, + 'Weatherstripping (Windows)': { + 'slide_index': 8, + 'slide_name': 'Weather-stripping (A/C Units, Exterior Doors, Windows)', + }, + 'Weatherstripping (A/C Units)': { + 'slide_index': 8, + 'slide_name': 'Weather-stripping (A/C Units, Exterior Doors, Windows)', + }, + 'Window Replacement': { + 'slide_index': 9, + 'slide_name': 'Window Replacement', + }, + 'Insulation (Roof)': { + 'slide_index': 6, + 'slide_name': 'Insulation (Roof)', + }, + 'Insulation (Wall)': { + 'slide_index': 7, + 'slide_name': 'Insulation (Wall)', + }, + 'Cool Roof': { + 'slide_index': 4, + 'slide_name': 'Cool Roof', + }, + 'Boiler Control (Indoor Feedback)': { + 'slide_index': 11, + 'slide_name': 'Boiler Control (Indoor Feedback)', + }, + 'Boiler Control (Outdoor Reset)': { + 'slide_index': 12, + 'slide_name': 'Boiler Control (Outdoor Reset)', + }, + 'Smart Thermostat w/ Indoor Sensor': { + 'slide_index': 18, + 'slide_name': 'Smart Thermostat with Indoor Sensors', + }, + 'Building Management System/ Energy Management system': { + 'slide_index': 13, + 'slide_name': 'Building/Energy Management System', + }, + 'Oil to Gas Conversion': { + 'slide_index': 52, + 'slide_name': 'Oil to Gas Conversion', + }, + 'Boiler Replacement': { + 'slide_index': 47, + 'slide_name': 'Boiler Replacement', + }, + 'Burner Replacement': { + 'slide_index': 48, + 'slide_name': 'Burner Replacement', + }, + 'Modulating Burner Controls': { + 'slide_index': 17, + 'slide_name': 'Modulating Burner Controls', + }, + 'Sealed Combustion/Power Burner Installation': { + 'slide_index': 54, + 'slide_name': 'Sealed Combustion/Power Burner Installation', + }, + 'Pipe Insulation': { + 'slide_index': 29, + 'slide_name': 'Install Pipe Insulation', + }, + 'Smart Pumps': { + 'slide_index': 27, + 'slide_name': 'Smart Pumps', + }, + 'Variable Frequency Drive Pumps': { + 'slide_index': 28, + 'slide_name': 'Variable Frequency Drive Pumps', + }, + 'TRVs (Steam, 1 pipe)': { + 'slide_index': 16, + 'slide_name': 'Thermostatic Radiator Valves (TRVs)', + }, + 'TRVs (Steam, 2 pipe)': { + 'slide_index': 16, + 'slide_name': 'Thermostatic Radiator Valves (TRVs)', + }, + 'TRVs (Hydronic)': { + 'slide_index': 16, + 'slide_name': 'Thermostatic Radiator Valves (TRVs)', + }, + 'Baseboard Replacement': { + 'slide_index': 24, + 'slide_name': 'Baseboard Replacement', + }, + 'Hydronic Conversion': { + 'slide_index': 51, + 'slide_name': 'Hydronic Conversion', + }, + 'Master Venting': { + 'slide_index': 33, + 'slide_name': 'Master Venting', + }, + 'Steam Boiler Setting Optimization': { # NOTE: this is not currently in the retrofit master list + 'slide_index': 20, + 'slide_name': 'Boiler Setting Optimization', + }, + 'Air Vent Installation/Replacement': { + 'slide_index': 30, + 'slide_name': 'Air Vent Installation/Replacement', + }, + 'Steam Trap Installation/Replacement (1 pipe)': { + 'slide_index': 36, + 'slide_name': 'Steam Trap Installation/Replacement', + }, + 'Steam Trap Installation/Replacement (2 pipe)': { + 'slide_index': 36, + 'slide_name': 'Steam Trap Installation/Replacement', + }, + 'Condensate Removal System Upgrade': { + 'slide_index': 31, + 'slide_name': 'Condensate Removal System Upgrade', + }, + 'Vented Condensate Pumps': { + 'slide_index': 38, + 'slide_name': 'Vented Condensate Pumps', + }, + 'Steam Boiler Anode Bars': { + 'slide_index': 56, + 'slide_name': 'Boiler Anode Bars', + }, + 'Rooftop Unit Replacement': { + 'slide_index': 53, + 'slide_name': 'Rooftop Unit Replacement', + }, + 'DHW Temperature Controls': { + 'slide_index': 14, + 'slide_name': 'Domestic Hot Water Temperature Control', + }, + 'Tankless Water Heater': { + 'slide_index': 59, + 'slide_name': 'Tankless Water Heater', + }, + 'Tanked Water Heater': { + 'slide_index': 58, + 'slide_name': 'Tanked Water Heater', + }, + 'Solar Water Heater': { + 'slide_index': 55, + 'slide_name': 'Solar Water Heater', + }, + 'Low-Flow Water Fixtures': { + 'slide_index': 62, + 'slide_name': 'Low-Flow Water Fixtures', + }, + 'Automatic Water Meter Reader': { + 'slide_index': 61, + 'slide_name': 'Automatic Water Meter Reader', + }, + 'LED Lighting Install': { + 'slide_index': 40, + 'slide_name': 'LED Lighting Install', + }, + 'Lighting Controls (Motion Sensors/Timer)': { + 'slide_index': 41, + 'slide_name': 'Lighting Controls (Motion Sensors/Timer)', + }, + 'Solar Panels': { + 'slide_index': 45, + 'slide_name': 'Solar Panels', + }, + 'Replace Unit Appliances w/ Energy Star Models': { + 'slide_index': 44, + 'slide_name': 'Replace Unit Appliances with Energy Star Models', + }, + 'Replace Laundry Room Appliances w/ Energy Star Models': { + 'slide_index': 43, + 'slide_name': 'Replace Laundry Room Appliances with Energy Star Models', + }, + 'DHW/Space Heating System Separation': { + 'slide_index': 49, + 'slide_name': 'DHW/Space Heating System Separation', + }, + 'Master Metering to Submetering Conversion': { + 'slide_index': 42, + 'slide_name': 'Master Metering to Submetering Conversion', + }, + 'High Efficiency Furnace Installation': { + 'slide_index': 50, + 'slide_name': 'High Efficiency Furnace Installation', + }, + 'Duct Sealing/Insulation': { + 'slide_index': 25, + 'slide_name': 'Duct Sealing/Insulation', + }, + 'Temperature Control Optimization: Impose Min/Max Temp Rule': { + 'slide_index': 15, + 'slide_name': 'Impose Min/Max Temp. Optimization', + }, + 'Boiler Blowdown Smart Controls': { + 'slide_index': 19, + 'slide_name': 'Boiler Blowdown Smart Controls', + }, + 'Modulating Zone Valves': { + 'slide_index': 21, + 'slide_name': 'Modulating Zone Valves', + }, + 'Drip Leg Installation': { + 'slide_index': 32, + 'slide_name': 'Drip Leg Installation', + }, + 'Radiant Barriers for Wall/Enclosed Radiators': { + 'slide_index': 35, + 'slide_name': 'Radiant Barriers for Wall/Enclosed Radiators', + }, + 'Orifice Plates': { + 'slide_index': 34, + 'slide_name': 'Orifice Plates', + }, + 'Smart Boiler Feed Pump': { + 'slide_index': 57, + 'slide_name': 'Smart Boiler Feed Pump', + }, + 'Vacuum Pumps': { + 'slide_index': 37, + 'slide_name': 'Vacuum Pumps', + }, + 'Flow Balancing Valve Installation': { + 'slide_index': 26, + 'slide_name': 'Flow Balancing Valve Installation', + }, + 'Green Roof': { + 'slide_index': 5, + 'slide_name': 'Green Roof', + }, + 'Pressure Reducing Valve': { + 'slide_index': 23, + 'slide_name': 'Add Pressure Reducing Valve', + }, + # These do not have a slide, putting them with the last slide + # They will be dealt with later + 'HVAC Filter Replacement': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, + 'Portable Fan Installation': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, + 'Exhaust Fan Replacement': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, + 'Wall-mounted HEPA filter': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, + 'Mold Damage Repair': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, + 'Carpet/Floor-Tile Replacement': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, + 'Painted Surface Repair (Re-paint or paint removal)': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, + 'Ceiling Tile Replacement': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, + 'Heat Recovery Ventilation System Installation': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, + 'Energy Recovery Ventilation System Installation': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, + 'Dedicated Outdoor Air System Installation': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, + 'Adjustable Grille/Register Installation': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, + 'Cascading Air Filter System': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, + 'Blower Fan Replacement': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, + 'Zone Damper Controls': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, + 'Roof or Attic Sheathing': { + 'slide_index': 35, + 'slide_name': 'Replace Appliances with Energy Star Appliances', + }, +} + +# 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 = { @@ -332,3 +639,62 @@ 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 Feedback)': 'Boiler Control (Outdoor Feedback)', + '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 Frequency Drive 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 Appliances with Energy Star Appliances', + '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', +} diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index 4ac85dc..47f52f8 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -24,7 +24,7 @@ from pptx.enum.text import ( # pylint: disable=no-name-in-module ) from pptx.util import Inches, Pt -from .constants import ECM_TO_SLIDE, RETROFIT_TO_CBRA_RETROFIT +from .constants import RETROFIT_TO_PNS_SLIDE, RETROFIT_TO_CBRA_PNS class CustomerPns: @@ -91,7 +91,7 @@ class CustomerPns: retrofit_elec_max_save, retrofit_water_max_save ): - presentation_name = ECM_TO_SLIDE[name]['slide_name'] + 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. @@ -121,7 +121,7 @@ class CustomerPns: # Create a delete list by adding everything not in the retrofit_list retrofit_delete_list = [] - for slide in ECM_TO_SLIDE.values(): + 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']) @@ -183,7 +183,7 @@ class CustomerPns: if input_values[j] == 'Y': # Convert to database string retrofit_list.append( - RETROFIT_TO_CBRA_RETROFIT[input_list[j]] + RETROFIT_TO_CBRA_PNS[input_list[j]] ) retrofit_heat_min_save.append(input_values[j+1]) retrofit_elec_min_save.append(input_values[j+2]) @@ -196,7 +196,7 @@ class CustomerPns: if input_values[j] == 'Y': # Convert to database string retrofit_list.append( - RETROFIT_TO_CBRA_RETROFIT[input_list[j]] + RETROFIT_TO_CBRA_PNS[input_list[j]] ) retrofit_heat_max_save.append(input_values_more[j+1]) retrofit_elec_max_save.append(input_values_more[j+2]) -- GitLab From 47091e054b47ce1e2e78ff70c63a673ed8d5efdd Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Mon, 19 Jun 2017 11:47:26 -0400 Subject: [PATCH 04/31] Update outdoor reset dictionary key --- bpeng/reports/constants.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpeng/reports/constants.py b/bpeng/reports/constants.py index 7930189..158455c 100644 --- a/bpeng/reports/constants.py +++ b/bpeng/reports/constants.py @@ -651,7 +651,7 @@ RETROFIT_TO_CBRA_PNS = { '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 Feedback)': 'Boiler Control (Outdoor 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', @@ -697,4 +697,5 @@ RETROFIT_TO_CBRA_PNS = { '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', } -- GitLab From 564e795008a51d66be233fead5dfbc9d041d3254 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Tue, 27 Jun 2017 17:20:09 -0400 Subject: [PATCH 05/31] Add text changes for cover slide. Format retrofit recommendation table --- bpeng/reports/customer_pns.py | 60 +++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index 47f52f8..c136da1 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -7,21 +7,21 @@ Based on code for the CBRA diagnostic report generator using python pptx from datetime import datetime -import os +# import os import pandas as pd from pptx import Presentation # from pptx.chart.data import ChartData -from pptx.dml.color import RGBColor +# from pptx.dml.color import RGBColor # from pptx.enum.chart import ( # XL_CHART_TYPE, # XL_LEGEND_POSITION, # XL_TICK_LABEL_POSITION # ) -from pptx.enum.text import ( # pylint: disable=no-name-in-module - MSO_AUTO_SIZE, - MSO_ANCHOR, - PP_ALIGN -) +# from pptx.enum.text import ( # pylint: disable=no-name-in-module +# MSO_AUTO_SIZE, +# MSO_ANCHOR, +# PP_ALIGN +# ) from pptx.util import Inches, Pt from .constants import RETROFIT_TO_PNS_SLIDE, RETROFIT_TO_CBRA_PNS @@ -58,6 +58,8 @@ class CustomerPns: 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'] + # square_footage = project_address['total_gross_area'] retrofit_list = retrofit_data['retrofit_list'] retrofit_heat_min_save = retrofit_data['retrofit_heat_min_save'], @@ -89,8 +91,8 @@ class CustomerPns: retrofit_water_min_save, retrofit_heat_max_save, retrofit_elec_max_save, - retrofit_water_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) @@ -131,8 +133,48 @@ class CustomerPns: # 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')) + return project_address, client_pns + # Slide 2: Community Retrofit pitch + # Slide 3: Disclaimer Slide + + # Slide 4: Retrofit Recommendations + retrofit_recs_slide = client_pns.slides[3] + recs_table_head = [ + 'Retrofit Measure', + "Heating Savings", + "Electricity Savings", + "Water Savings" + ] + recs = retrofit_recs_slide.shapes.add_table( + len(retrofit_list), + len(recs_table_head), + Inches(0.23), + Inches(0.69), + Inches(9.56), + Inches(4.66) + ) + recs.last_row = True + recs.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) + recs.table.rows[0].height = Inches(0.47) + @staticmethod def generate_pns(): """ -- GitLab From ab6c19e8aa0807ce6a015ed23daabdfb14e33d55 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Tue, 27 Jun 2017 17:56:34 -0400 Subject: [PATCH 06/31] Change recommendations title text. Add column headers to table. Add savings min and max in each cell, assuming non-zero savings --- bpeng/reports/customer_pns.py | 61 +++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index c136da1..7e692df 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -17,11 +17,11 @@ from pptx import Presentation # XL_LEGEND_POSITION, # XL_TICK_LABEL_POSITION # ) -# from pptx.enum.text import ( # pylint: disable=no-name-in-module -# MSO_AUTO_SIZE, -# MSO_ANCHOR, -# PP_ALIGN -# ) +from pptx.enum.text import ( # pylint: disable=no-name-in-module + # MSO_AUTO_SIZE, + MSO_ANCHOR, + PP_ALIGN +) from pptx.util import Inches, Pt from .constants import RETROFIT_TO_PNS_SLIDE, RETROFIT_TO_CBRA_PNS @@ -146,13 +146,14 @@ class CustomerPns: cover_report_date = cover_subtitle.text_frame.add_paragraph() cover_report_date.text = str(datetime.now().strftime('%B %Y')) - return project_address, client_pns - # Slide 2: Community Retrofit pitch # Slide 3: Disclaimer Slide # Slide 4: Retrofit Recommendations retrofit_recs_slide = client_pns.slides[3] + + retrofit_recs_slide.shapes.title.text = "Recommendations for {}".format(project_address) + recs_table_head = [ 'Retrofit Measure', "Heating Savings", @@ -175,6 +176,52 @@ class CustomerPns: recs.table.columns[3].width = Inches(1.73) recs.table.rows[0].height = Inches(0.47) + 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 + + 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).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).text_frame.paragraphs[0].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)) + \ + '\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).text_frame.paragraphs[0].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)) + \ + '\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).text_frame.paragraphs[0].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)) + \ + '\N{PERCENT SIGN}' + else: + recs.table.cell(j+1, 3).text = '' + + return project_address, client_pns + @staticmethod def generate_pns(): """ -- GitLab From 09a359c9444662f56f270791f903c0d142d82209 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Wed, 28 Jun 2017 11:51:02 -0400 Subject: [PATCH 07/31] Edit retrofit metrics slide title. Add retrofit metrics table headers and formatting. --- bpeng/reports/customer_pns.py | 38 ++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index 7e692df..bab6f2b 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -168,13 +168,12 @@ class CustomerPns: Inches(9.56), Inches(4.66) ) - recs.last_row = True - recs.horz_banding = False + 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) - recs.table.rows[0].height = Inches(0.47) for j in range(len(recs_table_head)): # pylint: disable=consider-using-enumerate recs.table.cell(0, j).text = recs_table_head[j] @@ -220,6 +219,39 @@ class CustomerPns: else: recs.table.cell(j+1, 3).text = '' + # Slide 5: Retrofit metrics + metrics_slide = client_pns.slides[4] + metrics_slide.shapes.title.text = "Estimated Savings for {}".format(project_address) + + metrics_table_head = [ + 'Retrofit Measure', + 'Difficulty of Install', + 'Minimum Cost', + 'Maximum Cost', + 'Payback [Years]', + ] + metrics = metrics_slide.shapes.add_table( + len(retrofit_list), + 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(2.16) + 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) + + # Filling out the table will involve parsing additional + # data from the input sheet for difficulty install, min/ + # max costs, and payback. + # difficulty install symbol unicode: u'\U0001F6E0' + return project_address, client_pns @staticmethod -- GitLab From edd0e27fb41529e8a449d1064f693bda80e2f226 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Wed, 28 Jun 2017 11:55:22 -0400 Subject: [PATCH 08/31] Add pseudocode for template slide deletion method. --- bpeng/reports/customer_pns.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index bab6f2b..ab2be77 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -130,6 +130,11 @@ class CustomerPns: # Put delete list in reverse order so we delete from the back retrofit_delete_list.sort(reverse=True) + # TODO: possible method for deletion of category slides + # check lines w/ 'Y' to get the category in column I. + # Delete the slide indexes for the categories that have + # no selected retrofits + # Define presentation to edit as the finished template client_pns = Presentation(template_file) -- GitLab From 5a952ef5bb1a9a3081f572c5bfa1a6462504aaca Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Wed, 28 Jun 2017 16:47:31 -0400 Subject: [PATCH 09/31] Change parse function to take all savings numbers and retrofit metrics in the sheet --- bpeng/reports/customer_pns.py | 46 +++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index ab2be77..d0da695 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -297,6 +297,8 @@ class CustomerPns: 'project_city_state_zip': sheet_input.loc[3, 1], } + # FIXME: this whole bit involving "input_list" needs to be changed + # the necessary input now spans from column A to column I input_list = sheet_input[0] input_values = sheet_input[1] input_values_more = sheet_input[2] @@ -305,28 +307,34 @@ class CustomerPns: retrofit_heat_min_save = [] retrofit_elec_min_save = [] retrofit_water_min_save = [] - for j in range(len(input_values)): # pylint: disable=C0200 - if input_values[j] == 'Y': - # Convert to database string - retrofit_list.append( - RETROFIT_TO_CBRA_PNS[input_list[j]] - ) - retrofit_heat_min_save.append(input_values[j+1]) - retrofit_elec_min_save.append(input_values[j+2]) - retrofit_water_min_save.append(input_values[j+3]) - retrofit_heat_max_save = [] retrofit_elec_max_save = [] retrofit_water_max_save = [] - for j in range(len(input_values)): # pylint: disable=C0200 - if input_values[j] == 'Y': + 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[input_list[j]] + RETROFIT_TO_CBRA_PNS[sheet_input.loc[j, 0]] ) - retrofit_heat_max_save.append(input_values_more[j+1]) - retrofit_elec_max_save.append(input_values_more[j+2]) - retrofit_water_max_save.append(input_values_more[j+3]) + 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, @@ -336,6 +344,12 @@ class CustomerPns: '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, retrofit_data -- GitLab From 18af81580f4ef8e5eb6a6479dbf670f938b35300 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Wed, 28 Jun 2017 17:16:24 -0400 Subject: [PATCH 10/31] Revise excel sheet parsing variables. Add note in constants file to update the slide indices for when the slide deck is finished --- bpeng/reports/constants.py | 1 + bpeng/reports/customer_pns.py | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/bpeng/reports/constants.py b/bpeng/reports/constants.py index 158455c..c877a67 100644 --- a/bpeng/reports/constants.py +++ b/bpeng/reports/constants.py @@ -297,6 +297,7 @@ ECM_TO_SLIDE = { }, } +# FIXME: Need to update this when the complete template is ready w/ slides RETROFIT_TO_PNS_SLIDE = { 'Weatherstripping (Exterior Doors)': { 'slide_index': 8, diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index d0da695..b081eda 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -299,9 +299,6 @@ class CustomerPns: # FIXME: this whole bit involving "input_list" needs to be changed # the necessary input now spans from column A to column I - input_list = sheet_input[0] - input_values = sheet_input[1] - input_values_more = sheet_input[2] retrofit_list = [] retrofit_heat_min_save = [] -- GitLab From fe921facb4410107ff5f2b610a3659180d241b25 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Wed, 28 Jun 2017 18:22:25 -0400 Subject: [PATCH 11/31] Add note to add categories to constants file. Add pseudocode for potential method to delete divider slides --- bpeng/reports/constants.py | 1 + bpeng/reports/customer_pns.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/bpeng/reports/constants.py b/bpeng/reports/constants.py index c877a67..ebe157e 100644 --- a/bpeng/reports/constants.py +++ b/bpeng/reports/constants.py @@ -298,6 +298,7 @@ ECM_TO_SLIDE = { } # FIXME: Need to update this when the complete template is ready w/ slides +# FIXME: Can add category to constants to determine divider slide deletion RETROFIT_TO_PNS_SLIDE = { 'Weatherstripping (Exterior Doors)': { 'slide_index': 8, diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index b081eda..abda14a 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -127,6 +127,11 @@ class CustomerPns: 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: + # take retrofit name, check RETROFIT_TO_PNS_SLIDE for category + # add indices of the divider slides for categories with no + # retrofits suggested + # Put delete list in reverse order so we delete from the back retrofit_delete_list.sort(reverse=True) -- GitLab From e3243075ec62707c47fa86fd75b6d71f574fc33c Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Thu, 29 Jun 2017 13:20:39 -0400 Subject: [PATCH 12/31] add in formatting for title text for retrofit measure metric slides --- bpeng/reports/customer_pns.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index abda14a..69da189 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -11,7 +11,7 @@ from datetime import datetime import pandas as pd from pptx import Presentation # from pptx.chart.data import ChartData -# from pptx.dml.color import RGBColor +from pptx.dml.color import RGBColor # from pptx.enum.chart import ( # XL_CHART_TYPE, # XL_LEGEND_POSITION, @@ -163,6 +163,9 @@ class CustomerPns: retrofit_recs_slide = client_pns.slides[3] retrofit_recs_slide.shapes.title.text = "Recommendations for {}".format(project_address) + retrofit_recs_slide.shapes.title.text_frame.paragraphs[0].font.color.rgb = RGBColor(0xff, 0xff, 0xff) + retrofit_recs_slide.shapes.title.text_frame.paragraphs[0].font.name = 'Arial' + retrofit_recs_slide.shapes.title.text_frame.paragraphs[0].font.size = Pt(14) recs_table_head = [ 'Retrofit Measure', @@ -232,6 +235,9 @@ class CustomerPns: # Slide 5: Retrofit metrics metrics_slide = client_pns.slides[4] metrics_slide.shapes.title.text = "Estimated Savings for {}".format(project_address) + metrics_slide.shapes.title.text_frame.paragraphs[0].font.color.rgb = RGBColor(0xff, 0xff, 0xff) + metrics_slide.shapes.title.text_frame.paragraphs[0].font.name = 'Arial' + metrics_slide.shapes.title.text_frame.paragraphs[0].font.size = Pt(14) metrics_table_head = [ 'Retrofit Measure', -- GitLab From a6b46cee431cb8f0da542cc85f55c848c75fa245 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Thu, 29 Jun 2017 18:46:09 -0400 Subject: [PATCH 13/31] Populate metrics table. --- bpeng/reports/customer_pns.py | 163 +++++++++++++++++++++------------- tests/reports/custPNS_test.py | 2 + 2 files changed, 104 insertions(+), 61 deletions(-) create mode 100644 tests/reports/custPNS_test.py diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index 69da189..4bfb187 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -34,7 +34,7 @@ class CustomerPns: """ @staticmethod - def _generate_pns( + def _generate_report( template_file, project_summary_data, retrofit_data, @@ -58,16 +58,20 @@ class CustomerPns: 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'] - # square_footage = project_address['total_gross_area'] 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_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 @@ -128,18 +132,17 @@ class CustomerPns: retrofit_delete_list.append(slide['slide_index']) # for rm in retrofit_list: - # take retrofit name, check RETROFIT_TO_PNS_SLIDE for category - # add indices of the divider slides for categories with no - # retrofits suggested + # for slide in RETROFIT_TO_PNS_SLIDE.values(): + # # take retrofit name, check RETROFIT_TO_PNS_SLIDE for category + # if slide['category'] not in retrofit_category: + # # add indices of the divider slides for categories with no + # # retrofits suggested + # # FIXME: need to link the category with the divider slide + # retrofit_delete_list.append() # Put delete list in reverse order so we delete from the back retrofit_delete_list.sort(reverse=True) - # TODO: possible method for deletion of category slides - # check lines w/ 'Y' to get the category in column I. - # Delete the slide indexes for the categories that have - # no selected retrofits - # Define presentation to edit as the finished template client_pns = Presentation(template_file) @@ -162,10 +165,10 @@ class CustomerPns: # Slide 4: Retrofit Recommendations retrofit_recs_slide = client_pns.slides[3] - retrofit_recs_slide.shapes.title.text = "Recommendations for {}".format(project_address) - retrofit_recs_slide.shapes.title.text_frame.paragraphs[0].font.color.rgb = RGBColor(0xff, 0xff, 0xff) - retrofit_recs_slide.shapes.title.text_frame.paragraphs[0].font.name = 'Arial' - retrofit_recs_slide.shapes.title.text_frame.paragraphs[0].font.size = Pt(14) + retrofit_recs_slide.shapes[3].text_frame.paragraphs[0].text = "Recommendations 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.size = Pt(14) recs_table_head = [ 'Retrofit Measure', @@ -173,8 +176,9 @@ class CustomerPns: "Electricity Savings", "Water Savings" ] + recs = retrofit_recs_slide.shapes.add_table( - len(retrofit_list), + len(retrofit_list) + 1, len(recs_table_head), Inches(0.23), Inches(0.69), @@ -189,55 +193,60 @@ class CustomerPns: recs.table.columns[3].width = Inches(1.73) for j in range(len(recs_table_head)): # pylint: disable=consider-using-enumerate + print(j) 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].vertical_anchor = MSO_ANCHOR.MIDDLE + recs.table.cell(0, j).text_frame.paragraphs[0].font.color.rgb = RGBColor(0xff, 0xff, 0xff) + print(recs_table_head[j]) 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).text = k + 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).text_frame.paragraphs[0].vertical_anchor = MSO_ANCHOR.MIDDLE - recs.table.cell(j+1, 1).text = str(int(retrofit_heat_min_save[j])) + \ + 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)) + \ + str(int(retrofit_heat_max_save[j])) + \ '\N{PERCENT SIGN}' else: - recs.table.cell(j+1, 1).text = '' + 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).text_frame.paragraphs[0].vertical_anchor = MSO_ANCHOR.MIDDLE - recs.table.cell(j+1, 2).text = str(int(retrofit_elec_min_save[j])) + \ + 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)) + \ + str(int(retrofit_elec_max_save[j])) + \ '\N{PERCENT SIGN}' else: - recs.table.cell(j+1, 2).text = '' + 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).text_frame.paragraphs[0].vertical_anchor = MSO_ANCHOR.MIDDLE - recs.table.cell(j+1, 3).text = str(int(retrofit_water_min_save[j])) + \ + 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).text_frame.paragraphs[0].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)) + \ + str(int(retrofit_water_max_save[j])) + \ '\N{PERCENT SIGN}' else: - recs.table.cell(j+1, 3).text = '' + recs.table.cell(j + 1, 3).text = '' # Slide 5: Retrofit metrics metrics_slide = client_pns.slides[4] - metrics_slide.shapes.title.text = "Estimated Savings for {}".format(project_address) - metrics_slide.shapes.title.text_frame.paragraphs[0].font.color.rgb = RGBColor(0xff, 0xff, 0xff) - metrics_slide.shapes.title.text_frame.paragraphs[0].font.name = 'Arial' - metrics_slide.shapes.title.text_frame.paragraphs[0].font.size = Pt(14) + metrics_slide.shapes[2].text_frame.paragraphs[0].text = "Estimated Savings 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.size = Pt(14) metrics_table_head = [ 'Retrofit Measure', @@ -247,7 +256,7 @@ class CustomerPns: 'Payback [Years]', ] metrics = metrics_slide.shapes.add_table( - len(retrofit_list), + len(retrofit_list) + 1, len(metrics_table_head), Inches(0.23), Inches(0.68), @@ -257,16 +266,49 @@ class CustomerPns: metrics.table.last_row = False metrics.table.horz_banding = False - metrics.table.columns[0].width = Inches(2.16) + 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) - # Filling out the table will involve parsing additional - # data from the input sheet for difficulty install, min/ - # max costs, and payback. - # difficulty install symbol unicode: u'\U0001F6E0' + # 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'\U0001F6E0' + 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).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).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(retrofit_min_cost[j]) + 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(retrofit_max_cost[j]) + 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])) return project_address, client_pns @@ -275,8 +317,8 @@ class CustomerPns: """ Generate report with finding file in current working directory """ - template_file = input('Enter Template Name (with .pptx extension): ') - file_name = input('Enter File Name (with .xlsx extension): ') + template_file = 'PNS_SlideDeck.pptx' # TODO: input('Enter Template Name (with .pptx extension): ') + file_name = 'PNS_test.xlsx' # TODO: input('Enter File Name (with .xlsx extension): ') file_input = pd.ExcelFile(file_name) sheet_input = file_input.parse("Inputs", header=None) ( @@ -306,11 +348,10 @@ class CustomerPns: '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], } - # FIXME: this whole bit involving "input_list" needs to be changed - # the necessary input now spans from column A to column I - retrofit_list = [] retrofit_heat_min_save = [] retrofit_elec_min_save = [] diff --git a/tests/reports/custPNS_test.py b/tests/reports/custPNS_test.py new file mode 100644 index 0000000..3913adb --- /dev/null +++ b/tests/reports/custPNS_test.py @@ -0,0 +1,2 @@ +from bpeng.reports.customer_pns import CustomerPns +CustomerPns.generate_pns() -- GitLab From 1abe7fc1dd4ff708892a4a61228991c1bca1a7f7 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Fri, 30 Jun 2017 16:02:18 -0400 Subject: [PATCH 14/31] Add divider slide indices to constants file. Add category key to RETROFIT_TO_PNS_SLIDE dictionary. Delete divider slides when no retrofits in that category are selected --- bpeng/reports/constants.py | 285 +++++++++++++++++++++++----------- bpeng/reports/customer_pns.py | 24 +-- 2 files changed, 206 insertions(+), 103 deletions(-) diff --git a/bpeng/reports/constants.py b/bpeng/reports/constants.py index ebe157e..aa568dc 100644 --- a/bpeng/reports/constants.py +++ b/bpeng/reports/constants.py @@ -297,310 +297,384 @@ ECM_TO_SLIDE = { }, } -# FIXME: Need to update this when the complete template is ready w/ slides # FIXME: Can add category to constants to determine divider slide deletion RETROFIT_TO_PNS_SLIDE = { 'Weatherstripping (Exterior Doors)': { - 'slide_index': 8, + 'slide_index': 10, 'slide_name': 'Weather-stripping (A/C Units, Exterior Doors, Windows)', + 'category': 'Envelope', }, 'Weatherstripping (Windows)': { - 'slide_index': 8, + 'slide_index': 10, 'slide_name': 'Weather-stripping (A/C Units, Exterior Doors, Windows)', + 'category': 'Envelope', }, 'Weatherstripping (A/C Units)': { - 'slide_index': 8, + 'slide_index': 10, 'slide_name': 'Weather-stripping (A/C Units, Exterior Doors, Windows)', + 'category': 'Envelope', }, 'Window Replacement': { - 'slide_index': 9, + 'slide_index': 11, 'slide_name': 'Window Replacement', + 'category': 'Envelope', }, 'Insulation (Roof)': { - 'slide_index': 6, + 'slide_index': 8, 'slide_name': 'Insulation (Roof)', + 'category': 'Envelope', }, 'Insulation (Wall)': { - 'slide_index': 7, + 'slide_index': 9, 'slide_name': 'Insulation (Wall)', + 'category': 'Envelope', }, 'Cool Roof': { - 'slide_index': 4, + 'slide_index': 6, 'slide_name': 'Cool Roof', + 'category': 'Envelope', }, 'Boiler Control (Indoor Feedback)': { - 'slide_index': 11, + 'slide_index': 13, 'slide_name': 'Boiler Control (Indoor Feedback)', + 'category': 'Control', }, 'Boiler Control (Outdoor Reset)': { - 'slide_index': 12, + 'slide_index': 14, 'slide_name': 'Boiler Control (Outdoor Reset)', + 'category': 'Control', }, 'Smart Thermostat w/ Indoor Sensor': { - 'slide_index': 18, + 'slide_index': 20, 'slide_name': 'Smart Thermostat with Indoor Sensors', + 'category': 'Control', }, 'Building Management System/ Energy Management system': { - 'slide_index': 13, + 'slide_index': 15, 'slide_name': 'Building/Energy Management System', + 'category': 'Control', }, 'Oil to Gas Conversion': { - 'slide_index': 52, + 'slide_index': 54, 'slide_name': 'Oil to Gas Conversion', + 'category': 'Source', }, 'Boiler Replacement': { - 'slide_index': 47, + 'slide_index': 49, 'slide_name': 'Boiler Replacement', + 'category': 'Source', }, 'Burner Replacement': { - 'slide_index': 48, + 'slide_index': 50, 'slide_name': 'Burner Replacement', + 'category': 'Source', }, 'Modulating Burner Controls': { - 'slide_index': 17, + 'slide_index': 19, 'slide_name': 'Modulating Burner Controls', + 'category': 'Control', }, 'Sealed Combustion/Power Burner Installation': { - 'slide_index': 54, + 'slide_index': 56, 'slide_name': 'Sealed Combustion/Power Burner Installation', + 'category': 'Source', }, 'Pipe Insulation': { - 'slide_index': 29, + 'slide_index': 31, 'slide_name': 'Install Pipe Insulation', + 'category': 'Distribution', }, 'Smart Pumps': { - 'slide_index': 27, + 'slide_index': 29, 'slide_name': 'Smart Pumps', + 'category': 'Distribution', }, 'Variable Frequency Drive Pumps': { - 'slide_index': 28, + 'slide_index': 30, 'slide_name': 'Variable Frequency Drive Pumps', + 'category': 'Distribution', }, 'TRVs (Steam, 1 pipe)': { - 'slide_index': 16, + 'slide_index': 18, 'slide_name': 'Thermostatic Radiator Valves (TRVs)', + 'category': 'Control', }, 'TRVs (Steam, 2 pipe)': { - 'slide_index': 16, + 'slide_index': 18, 'slide_name': 'Thermostatic Radiator Valves (TRVs)', + 'category': 'Control', }, 'TRVs (Hydronic)': { - 'slide_index': 16, + 'slide_index': 18, 'slide_name': 'Thermostatic Radiator Valves (TRVs)', + 'category': 'Control', }, 'Baseboard Replacement': { - 'slide_index': 24, + 'slide_index': 26, 'slide_name': 'Baseboard Replacement', + 'category': 'Distribution', }, 'Hydronic Conversion': { - 'slide_index': 51, + 'slide_index': 53, 'slide_name': 'Hydronic Conversion', + 'category': 'Source', }, 'Master Venting': { - 'slide_index': 33, + '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': 20, + 'slide_index': 22, 'slide_name': 'Boiler Setting Optimization', + 'category': 'Control', }, 'Air Vent Installation/Replacement': { - 'slide_index': 30, + 'slide_index': 32, 'slide_name': 'Air Vent Installation/Replacement', + 'category': 'Distribution', }, 'Steam Trap Installation/Replacement (1 pipe)': { - 'slide_index': 36, + 'slide_index': 38, 'slide_name': 'Steam Trap Installation/Replacement', + 'category': 'Distribution', }, 'Steam Trap Installation/Replacement (2 pipe)': { - 'slide_index': 36, + 'slide_index': 38, 'slide_name': 'Steam Trap Installation/Replacement', + 'category': 'Distribution', }, 'Condensate Removal System Upgrade': { - 'slide_index': 31, + 'slide_index': 33, 'slide_name': 'Condensate Removal System Upgrade', + 'category': 'Distribution', }, 'Vented Condensate Pumps': { - 'slide_index': 38, + 'slide_index': 40, 'slide_name': 'Vented Condensate Pumps', + 'category': 'Distribution', }, 'Steam Boiler Anode Bars': { - 'slide_index': 56, + 'slide_index': 58, 'slide_name': 'Boiler Anode Bars', + 'category': 'Source', }, 'Rooftop Unit Replacement': { - 'slide_index': 53, + 'slide_index': 55, 'slide_name': 'Rooftop Unit Replacement', + 'category': 'Source', }, 'DHW Temperature Controls': { - 'slide_index': 14, + 'slide_index': 16, 'slide_name': 'Domestic Hot Water Temperature Control', + 'category': 'Control', }, 'Tankless Water Heater': { - 'slide_index': 59, + 'slide_index': 61, 'slide_name': 'Tankless Water Heater', + 'category': 'Source', }, 'Tanked Water Heater': { - 'slide_index': 58, + 'slide_index': 60, 'slide_name': 'Tanked Water Heater', + 'category': 'Source', }, 'Solar Water Heater': { - 'slide_index': 55, + 'slide_index': 57, 'slide_name': 'Solar Water Heater', + 'category': 'Source', }, 'Low-Flow Water Fixtures': { - 'slide_index': 62, + 'slide_index': 64, 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Automatic Water Meter Reader': { - 'slide_index': 61, + 'slide_index': 63, 'slide_name': 'Automatic Water Meter Reader', + 'category': 'Water', }, 'LED Lighting Install': { - 'slide_index': 40, + 'slide_index': 42, 'slide_name': 'LED Lighting Install', + 'category': 'Electricity', }, 'Lighting Controls (Motion Sensors/Timer)': { - 'slide_index': 41, + 'slide_index': 43, 'slide_name': 'Lighting Controls (Motion Sensors/Timer)', + 'category': 'Electricity', }, 'Solar Panels': { - 'slide_index': 45, + 'slide_index': 47, 'slide_name': 'Solar Panels', + 'category': 'Electricity', }, 'Replace Unit Appliances w/ Energy Star Models': { - 'slide_index': 44, + 'slide_index': 46, 'slide_name': 'Replace Unit Appliances with Energy Star Models', + 'category': 'Electricity', }, 'Replace Laundry Room Appliances w/ Energy Star Models': { - 'slide_index': 43, + 'slide_index': 45, 'slide_name': 'Replace Laundry Room Appliances with Energy Star Models', + 'category': 'Electricity', }, 'DHW/Space Heating System Separation': { - 'slide_index': 49, + 'slide_index': 51, 'slide_name': 'DHW/Space Heating System Separation', + 'category': 'Source', }, 'Master Metering to Submetering Conversion': { - 'slide_index': 42, + 'slide_index': 44, 'slide_name': 'Master Metering to Submetering Conversion', + 'category': 'Electricity', }, 'High Efficiency Furnace Installation': { - 'slide_index': 50, + 'slide_index': 52, 'slide_name': 'High Efficiency Furnace Installation', + 'category': 'Source', }, 'Duct Sealing/Insulation': { - 'slide_index': 25, + 'slide_index': 27, 'slide_name': 'Duct Sealing/Insulation', + 'category': 'Distribution', }, 'Temperature Control Optimization: Impose Min/Max Temp Rule': { - 'slide_index': 15, + 'slide_index': 17, 'slide_name': 'Impose Min/Max Temp. Optimization', + 'category': 'Control', }, 'Boiler Blowdown Smart Controls': { - 'slide_index': 19, + 'slide_index': 21, 'slide_name': 'Boiler Blowdown Smart Controls', + 'category': 'Control', }, 'Modulating Zone Valves': { - 'slide_index': 21, + 'slide_index': 23, 'slide_name': 'Modulating Zone Valves', + 'category': 'Control', }, 'Drip Leg Installation': { - 'slide_index': 32, + 'slide_index': 34, 'slide_name': 'Drip Leg Installation', + 'category': 'Distribution', }, 'Radiant Barriers for Wall/Enclosed Radiators': { - 'slide_index': 35, + 'slide_index': 37, 'slide_name': 'Radiant Barriers for Wall/Enclosed Radiators', + 'category': 'Distribution', }, 'Orifice Plates': { - 'slide_index': 34, + 'slide_index': 36, 'slide_name': 'Orifice Plates', + 'category': 'Distribution', }, 'Smart Boiler Feed Pump': { - 'slide_index': 57, + 'slide_index': 59, 'slide_name': 'Smart Boiler Feed Pump', + 'category': 'Source', }, 'Vacuum Pumps': { - 'slide_index': 37, + 'slide_index': 39, 'slide_name': 'Vacuum Pumps', + 'category': 'Distribution', }, 'Flow Balancing Valve Installation': { - 'slide_index': 26, + 'slide_index': 28, 'slide_name': 'Flow Balancing Valve Installation', + 'category': 'Distribution', }, 'Green Roof': { - 'slide_index': 5, + 'slide_index': 7, 'slide_name': 'Green Roof', + 'category': 'Envelope', }, 'Pressure Reducing Valve': { - 'slide_index': 23, + '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': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Portable Fan Installation': { - 'slide_index': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Exhaust Fan Replacement': { - 'slide_index': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Wall-mounted HEPA filter': { - 'slide_index': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Mold Damage Repair': { - 'slide_index': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Carpet/Floor-Tile Replacement': { - 'slide_index': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Painted Surface Repair (Re-paint or paint removal)': { - 'slide_index': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Ceiling Tile Replacement': { - 'slide_index': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Heat Recovery Ventilation System Installation': { - 'slide_index': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Energy Recovery Ventilation System Installation': { - 'slide_index': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Dedicated Outdoor Air System Installation': { - 'slide_index': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Adjustable Grille/Register Installation': { - 'slide_index': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Cascading Air Filter System': { - 'slide_index': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Blower Fan Replacement': { - 'slide_index': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Zone Damper Controls': { - 'slide_index': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, 'Roof or Attic Sheathing': { - 'slide_index': 35, - 'slide_name': 'Replace Appliances with Energy Star Appliances', + 'slide_index': 64, + 'slide_name': 'Low-Flow Water Fixtures', + 'category': 'Water', }, } @@ -701,3 +775,30 @@ RETROFIT_TO_CBRA_PNS = { '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 index 4bfb187..dacb868 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -24,7 +24,7 @@ from pptx.enum.text import ( # pylint: disable=no-name-in-module ) from pptx.util import Inches, Pt -from .constants import RETROFIT_TO_PNS_SLIDE, RETROFIT_TO_CBRA_PNS +from .constants import RETROFIT_TO_PNS_SLIDE, RETROFIT_TO_CBRA_PNS, PNS_DIVIDER_SLIDE class CustomerPns: @@ -132,17 +132,15 @@ class CustomerPns: retrofit_delete_list.append(slide['slide_index']) # for rm in retrofit_list: - # for slide in RETROFIT_TO_PNS_SLIDE.values(): - # # take retrofit name, check RETROFIT_TO_PNS_SLIDE for category - # if slide['category'] not in retrofit_category: - # # add indices of the divider slides for categories with no - # # retrofits suggested - # # FIXME: need to link the category with the divider slide - # retrofit_delete_list.append() + for slide in RETROFIT_TO_PNS_SLIDE.values(): + category = slide['category'] + # take retrofit name, check RETROFIT_TO_PNS_SLIDE for 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) - + print(retrofit_delete_list) # Define presentation to edit as the finished template client_pns = Presentation(template_file) @@ -193,13 +191,11 @@ class CustomerPns: recs.table.columns[3].width = Inches(1.73) for j in range(len(recs_table_head)): # pylint: disable=consider-using-enumerate - print(j) 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].vertical_anchor = MSO_ANCHOR.MIDDLE recs.table.cell(0, j).text_frame.paragraphs[0].font.color.rgb = RGBColor(0xff, 0xff, 0xff) - print(recs_table_head[j]) 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) @@ -310,6 +306,12 @@ class CustomerPns: '-' + \ 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 -- GitLab From a745e72a697345bb5f9515257342c1a43baa1086 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Fri, 30 Jun 2017 17:30:28 -0400 Subject: [PATCH 15/31] remove print statements --- bpeng/reports/customer_pns.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index dacb868..1c1e699 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -134,13 +134,12 @@ class CustomerPns: # for rm in retrofit_list: for slide in RETROFIT_TO_PNS_SLIDE.values(): category = slide['category'] - # take retrofit name, check RETROFIT_TO_PNS_SLIDE for 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) - print(retrofit_delete_list) + # Define presentation to edit as the finished template client_pns = Presentation(template_file) -- GitLab From 900f4f8646f493633e532f829fc1758379c10c3b Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Fri, 30 Jun 2017 17:32:43 -0400 Subject: [PATCH 16/31] Ask for input excel name. --- bpeng/reports/customer_pns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index 1c1e699..64bf7b7 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -319,7 +319,7 @@ class CustomerPns: Generate report with finding file in current working directory """ template_file = 'PNS_SlideDeck.pptx' # TODO: input('Enter Template Name (with .pptx extension): ') - file_name = 'PNS_test.xlsx' # TODO: input('Enter File Name (with .xlsx extension): ') + file_name = input('Enter File Name (with .xlsx extension): ') file_input = pd.ExcelFile(file_name) sheet_input = file_input.parse("Inputs", header=None) ( -- GitLab From 978d335a1cda6aab79dfc53b5080bcf48357a252 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Fri, 30 Jun 2017 17:48:15 -0400 Subject: [PATCH 17/31] rename test file --- tests/reports/{custPNS_test.py => test_cust_pns.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/reports/{custPNS_test.py => test_cust_pns.py} (100%) diff --git a/tests/reports/custPNS_test.py b/tests/reports/test_cust_pns.py similarity index 100% rename from tests/reports/custPNS_test.py rename to tests/reports/test_cust_pns.py -- GitLab From 299c73d8eb99e68f4b61ea20470c0a799fea995d Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Wed, 5 Jul 2017 12:06:43 -0400 Subject: [PATCH 18/31] Fix "line too long" pylint warning. --- bpeng/reports/customer_pns.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index 64bf7b7..9cfda8c 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -134,7 +134,8 @@ class CustomerPns: # 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: + 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 -- GitLab From 51c4d4ce3c7d2c0b98b637cfdce715b78c8fa943 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Thu, 6 Jul 2017 15:56:55 -0400 Subject: [PATCH 19/31] Replace hammer/screw symbol with hammers. Change alignment on metrics slide --- bpeng/reports/customer_pns.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index 9cfda8c..afd0e6b 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -273,7 +273,7 @@ class CustomerPns: 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'\U0001F6E0' + hammers[j] = hammers[j] + u'\U0001F528' counter += 1 for j in range(len(metrics_table_head)): # pylint: disable=consider-using-enumerate @@ -284,6 +284,7 @@ class CustomerPns: 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) -- GitLab From 12bedf7fb10e13baac3a33350430b15f7a2527cd Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Thu, 6 Jul 2017 16:31:13 -0400 Subject: [PATCH 20/31] Round cost numbers in metrics table to nearest 100 --- bpeng/reports/customer_pns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index afd0e6b..f0240ec 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -295,11 +295,11 @@ class CustomerPns: 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(retrofit_min_cost[j]) + 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(retrofit_max_cost[j]) + 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 -- GitLab From a15d2994d1c52866d8f0a5deb88bce57cc9840a0 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Thu, 6 Jul 2017 16:48:19 -0400 Subject: [PATCH 21/31] Customize retrofit metrics table subtitle text to include building sq footage and unit count --- bpeng/reports/customer_pns.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index f0240ec..542664b 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -46,7 +46,8 @@ class CustomerPns: template_file (str): path to pptx template project_summary_data (dict): python dictionary of client name, project street address, city, state, - and ZIP code + and ZIP code, unit count, and building + square footage retrofit_data (dict): python dictionary of selected retrofit, minimum and maximum percent savings on utility bill [$] @@ -58,6 +59,8 @@ class CustomerPns: 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'] retrofit_list = retrofit_data['retrofit_list'] retrofit_heat_min_save = retrofit_data['retrofit_heat_min_save'] @@ -244,6 +247,16 @@ class CustomerPns: metrics_slide.shapes[2].text_frame.paragraphs[0].font.name = 'Arial' 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', -- GitLab From d5e7db4a61eb1a004857950d29397db479273937 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Wed, 12 Jul 2017 15:14:52 -0400 Subject: [PATCH 22/31] Comment out slide deletion feature. --- bpeng/reports/customer_pns.py | 44 +++++++++++++++++------------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index 542664b..c940ad7 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -128,21 +128,21 @@ class CustomerPns: 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) + # # 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) @@ -163,19 +163,19 @@ class CustomerPns: # Slide 2: Community Retrofit pitch # Slide 3: Disclaimer Slide - # Slide 4: Retrofit Recommendations - retrofit_recs_slide = client_pns.slides[3] + # Slide 5: Retrofit Recommendations + retrofit_recs_slide = client_pns.slides[4] - retrofit_recs_slide.shapes[3].text_frame.paragraphs[0].text = "Recommendations for {}".format(project_address) + 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.size = Pt(14) recs_table_head = [ - 'Retrofit Measure', - "Heating Savings", - "Electricity Savings", - "Water Savings" + "Retrofit Measure", + "Heating Savings*", + "Electricity Savings*", + "Water Savings*" ] recs = retrofit_recs_slide.shapes.add_table( -- GitLab From fde291a93f48e37f1de366f3d12e6e5721e160a5 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Wed, 12 Jul 2017 16:06:10 -0400 Subject: [PATCH 23/31] Revise to match format of V3 template sent on 7/11. Missing tables on slide 4 still. --- bpeng/reports/customer_pns.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index c940ad7..ae95f93 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -24,7 +24,7 @@ from pptx.enum.text import ( # pylint: disable=no-name-in-module ) from pptx.util import Inches, Pt -from .constants import RETROFIT_TO_PNS_SLIDE, RETROFIT_TO_CBRA_PNS, PNS_DIVIDER_SLIDE +from .constants import RETROFIT_TO_PNS_SLIDE, RETROFIT_TO_CBRA_PNS class CustomerPns: @@ -74,7 +74,7 @@ class CustomerPns: 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'] + # 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 @@ -162,13 +162,16 @@ class CustomerPns: # Slide 2: Community Retrofit pitch # Slide 3: Disclaimer Slide + # Slide 4: Building Profile and Needs - # Slide 5: Retrofit Recommendations + # 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].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 = [ @@ -197,7 +200,8 @@ class CustomerPns: 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].vertical_anchor = MSO_ANCHOR.MIDDLE + 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))): @@ -241,10 +245,12 @@ class CustomerPns: recs.table.cell(j + 1, 3).text = '' # Slide 5: Retrofit metrics - metrics_slide = client_pns.slides[4] - metrics_slide.shapes[2].text_frame.paragraphs[0].text = "Estimated Savings for {}".format(project_address) + 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" \ @@ -262,7 +268,7 @@ class CustomerPns: 'Difficulty of Install', 'Minimum Cost', 'Maximum Cost', - 'Payback [Years]', + 'Payback (Years)', ] metrics = metrics_slide.shapes.add_table( len(retrofit_list) + 1, @@ -291,6 +297,7 @@ class CustomerPns: 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) @@ -321,11 +328,11 @@ class CustomerPns: 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, - ) + # for rm_index in retrofit_delete_list: + # client_pns.slides.delete_slide( + # client_pns, + # rm_index, + # ) return project_address, client_pns @staticmethod -- GitLab From 96f85dc0d8e51453e32564909619dbd4ca071847 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Wed, 12 Jul 2017 18:05:57 -0400 Subject: [PATCH 24/31] Add building info table on slide 4. Add operating needs table on slide 4 --- bpeng/reports/customer_pns.py | 102 ++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 4 deletions(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index ae95f93..dbb1db7 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -27,6 +27,33 @@ from pptx.util import Inches, Pt from .constants import RETROFIT_TO_PNS_SLIDE, RETROFIT_TO_CBRA_PNS +# merge cells vertically +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 c in column_cells[1:]: + c._tc.set('vMerge', '1') + + +# merge cells horizontally +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 c in row_cells[1:]: + c._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 @@ -163,6 +190,73 @@ class CustomerPns: # 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", + "Boiler Age", + "DHW Age", + "Window Age", + "Window Type", + "Roof Age", + "Separate DHW", + "Recent Boiler Tuneup", + "Water Hammer", + "Master Venting", + ] + + 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 + + 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(12) + 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_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" # Slide 5: Estimated Energy and Water Savings retrofit_recs_slide = client_pns.slides[4] @@ -244,7 +338,7 @@ class CustomerPns: else: recs.table.cell(j + 1, 3).text = '' - # Slide 5: Retrofit metrics + # 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) @@ -266,9 +360,9 @@ class CustomerPns: metrics_table_head = [ 'Retrofit Measure', 'Difficulty of Install', - 'Minimum Cost', - 'Maximum Cost', - 'Payback (Years)', + 'Minimum Cost*', + 'Maximum Cost*', + 'Payback* (Years)', ] metrics = metrics_slide.shapes.add_table( len(retrofit_list) + 1, -- GitLab From 48116864c6b9f52a7050c8341a345316b694ca50 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Wed, 12 Jul 2017 19:06:30 -0400 Subject: [PATCH 25/31] Parse building information input. Populate tables on slide 4 --- bpeng/reports/customer_pns.py | 73 ++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index dbb1db7..c38f4b6 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -64,6 +64,7 @@ class CustomerPns: def _generate_report( template_file, project_summary_data, + building_information_data, retrofit_data, ): """ @@ -89,6 +90,22 @@ class CustomerPns: 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'] @@ -193,7 +210,7 @@ class CustomerPns: needs_slide = client_pns.slides[3] needs_slide.shapes.title.text_frame.paragraphs[0].text = "What You Told Us About" \ - " {}".format(project_address) + " {}".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 @@ -203,15 +220,13 @@ class CustomerPns: "Heating System", "Tenant Complaints", "Boiler Controls", + "Thermostat Location", + "Separate DHW", "Boiler Age", "DHW Age", + "Roof Age", "Window Age", "Window Type", - "Roof Age", - "Separate DHW", - "Recent Boiler Tuneup", - "Water Hammer", - "Master Venting", ] bldg_info = needs_slide.shapes.add_table( @@ -233,15 +248,27 @@ class CustomerPns: 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(12) + 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_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, @@ -258,6 +285,12 @@ class CustomerPns: 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] @@ -440,11 +473,13 @@ class CustomerPns: 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)) @@ -459,6 +494,7 @@ class CustomerPns: 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 = { @@ -469,6 +505,25 @@ class CustomerPns: '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 = [] @@ -518,4 +573,4 @@ class CustomerPns: 'retrofit_category': retrofit_category, } - return project_summary_data, retrofit_data + return project_summary_data, building_information_data, retrofit_data -- GitLab From 28cc6799583802230c0d78a9e80281167814587f Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Thu, 13 Jul 2017 18:02:35 -0400 Subject: [PATCH 26/31] Add documentation for generate powerpoint function. --- bpeng/reports/customer_pns.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index c38f4b6..d01dbe5 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -28,13 +28,15 @@ from .constants import RETROFIT_TO_PNS_SLIDE, RETROFIT_TO_CBRA_PNS # merge cells vertically + +# 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 c in column_cells[1:]: - c._tc.set('vMerge', '1') + for cell in column_cells[1:]: + cell._tc.set('vMerge', '1') # merge cells horizontally @@ -42,8 +44,8 @@ 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 c in row_cells[1:]: - c._tc.set('hMerge', '1') + for cell in row_cells[1:]: + cell._tc.set('hMerge', '1') # the workaround function to merge cells in a table @@ -76,6 +78,9 @@ class CustomerPns: 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 [$] -- GitLab From e833c06f51d6ed372b78eb084ddf6cc3145eb6a3 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Tue, 18 Jul 2017 10:39:44 -0400 Subject: [PATCH 27/31] Update constants dictionaries for changed retrofit measure names. --- bpeng/reports/constants.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bpeng/reports/constants.py b/bpeng/reports/constants.py index 96a933b..8ce0ca7 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', }, @@ -346,9 +346,9 @@ RETROFIT_TO_PNS_SLIDE = { 'slide_name': 'Smart Thermostat with Indoor Sensors', 'category': 'Control', }, - 'Building Management System/ Energy Management system': { + 'Building Management System/Energy Management System': { 'slide_index': 15, - 'slide_name': 'Building/Energy Management System', + 'slide_name': 'Building/ Energy Management System', 'category': 'Control', }, 'Oil to Gas Conversion': { @@ -506,7 +506,7 @@ RETROFIT_TO_PNS_SLIDE = { 'slide_name': 'Solar Panels', 'category': 'Electricity', }, - 'Replace Unit Appliances w/ Energy Star Models': { + 'Replace Unit Appliances with Energy Star Models': { 'slide_index': 46, 'slide_name': 'Replace Unit Appliances with Energy Star Models', 'category': 'Electricity', @@ -686,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', @@ -725,7 +725,7 @@ RETROFIT_TO_CBRA_PNS = { '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', + '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)', @@ -739,7 +739,7 @@ RETROFIT_TO_CBRA_PNS = { 'Duct Sealing/Insulation': 'Duct Sealing/Insulation', 'Flow Balancing Valve Installation': 'Flow Balancing Valve Installation', 'Smart Pumps': 'Smart Pumps', - 'Variable Frequency Drive Pumps': 'Variable Frequency Drive 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', @@ -754,7 +754,7 @@ RETROFIT_TO_CBRA_PNS = { '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 Appliances with Energy Star Appliances', + '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', -- GitLab From fd0627e19185876ca088cf04821e5584b8c8629b Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Thu, 20 Jul 2017 14:17:26 -0400 Subject: [PATCH 28/31] Fix vertical alignment for water savings column --- bpeng/reports/customer_pns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index d01dbe5..2fa8a1d 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -368,7 +368,7 @@ class CustomerPns: 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).text_frame.paragraphs[0].vertical_anchor = MSO_ANCHOR.MIDDLE + 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])) + \ -- GitLab From 32ba0b27c304783481e65fbc10bf952602ab2bf2 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Mon, 24 Jul 2017 16:15:14 -0400 Subject: [PATCH 29/31] Remove test file. --- tests/reports/test_cust_pns.py | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 tests/reports/test_cust_pns.py diff --git a/tests/reports/test_cust_pns.py b/tests/reports/test_cust_pns.py deleted file mode 100644 index 3913adb..0000000 --- a/tests/reports/test_cust_pns.py +++ /dev/null @@ -1,2 +0,0 @@ -from bpeng.reports.customer_pns import CustomerPns -CustomerPns.generate_pns() -- GitLab From 3327f4fbae74febd5ae6e2e8b963dd7abe8dca30 Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Mon, 24 Jul 2017 16:37:38 -0400 Subject: [PATCH 30/31] Remove unused imports and extraneous comments --- bpeng/reports/customer_pns.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index 2fa8a1d..a1d3158 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -10,15 +10,8 @@ from datetime import datetime # import os import pandas as pd from pptx import Presentation -# from pptx.chart.data import ChartData from pptx.dml.color import RGBColor -# from pptx.enum.chart import ( -# XL_CHART_TYPE, -# XL_LEGEND_POSITION, -# XL_TICK_LABEL_POSITION -# ) from pptx.enum.text import ( # pylint: disable=no-name-in-module - # MSO_AUTO_SIZE, MSO_ANCHOR, PP_ALIGN ) @@ -27,8 +20,6 @@ from pptx.util import Inches, Pt from .constants import RETROFIT_TO_PNS_SLIDE, RETROFIT_TO_CBRA_PNS -# merge cells vertically - # 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 @@ -39,7 +30,6 @@ def merge_cells_vertically(table, start_row_idx, end_row_idx, col_idx): cell._tc.set('vMerge', '1') -# merge cells horizontally 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] -- GitLab From 28700e489fe6572cbd3c31635d10aeec3b77048f Mon Sep 17 00:00:00 2001 From: areza-blocpower Date: Mon, 24 Jul 2017 17:49:18 -0400 Subject: [PATCH 31/31] Change duplicate code --- bpeng/reports/customer_pns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpeng/reports/customer_pns.py b/bpeng/reports/customer_pns.py index a1d3158..2faaf47 100644 --- a/bpeng/reports/customer_pns.py +++ b/bpeng/reports/customer_pns.py @@ -463,7 +463,7 @@ class CustomerPns: 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 File Name (with .xlsx 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) ( -- GitLab