diff --git a/bpeng/reports/__init__.py b/bpeng/reports/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7bf47562d20e347208fffbfb4bac3c53f8942114 --- /dev/null +++ b/bpeng/reports/__init__.py @@ -0,0 +1 @@ +from .cbra_diag import CbraDiagnostic diff --git a/bpeng/reports/cbra_diag.py b/bpeng/reports/cbra_diag.py new file mode 100644 index 0000000000000000000000000000000000000000..6348315077b475e7eb858c74033504f0e5a5e952 --- /dev/null +++ b/bpeng/reports/cbra_diag.py @@ -0,0 +1,276 @@ +""" + Code is designed to take template for CBRA EEDR and fill in engineering data, charts, graphs + for easy presentation creation. +""" + +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.enum.chart import (XL_CHART_TYPE, XL_LABEL_POSITION, + XL_LEGEND_POSITION, XL_TICK_LABEL_POSITION) +from pptx.enum.text import MSO_AUTO_SIZE, PP_ALIGN +from pptx.util import Inches, Pt + + +class CbraDiagnostic: + """ + Download template file from https://s3.amazonaws.com/bp-dev-assets/EEDR_CBRA.pptx + """ + + @staticmethod + def _generate_report(template_file, input_file): + """ + Generate powerpoint file + + Args: + + input_file (str): The path of the report content + + Returns: + + Presentation: the presenation generated + + """ + file_input = pd.ExcelFile(input_file) + sheet_input = file_input.parse("Inputs", header=None) + + input_list = sheet_input[0] + input_values = sheet_input[1] + + # Define presentation to edit as the finished template + prs = Presentation(template_file) + + # Slide 1: cover slide + cover_slide_layout = prs.slide_layouts[0] + cover_slide = prs.slides[0] + cover_title = cover_slide.shapes.title + cover_subtitle = cover_slide.placeholders[1] + + client_name = sheet_input.loc[1, 1] + project_address = sheet_input.loc[2, 1] + + cover_title.text = "{} - {}".format(client_name, project_address) + cover_subtitle.text = "Energy Efficiency Diagnostic Report" + cover_report_date = cover_subtitle.text_frame.add_paragraph() + cover_report_date.text = str(datetime.now().strftime('%B %Y')) + + # Slide 2: Executive Summary + Table of Contents + # shapes[0] = slide #, 1 = executive summary text box, 2 = title , 3 = table of contents + exec_summary_slide = prs.slides[1] + exec_results_box = exec_summary_slide.shapes[1] + exec_results_box.text_frame.auto_size = MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT + exec_results_box.text_frame.alignment = PP_ALIGN.LEFT + exec_title_run = exec_results_box.text_frame.paragraphs[0].add_run() + exec_title_run.text = "Executive Summary:" + exec_title_run.alignment = PP_ALIGN.LEFT + exec_title_run.font.name = 'Arial' + exec_title_run.font.size = Pt(20) + exec_title_run.font.underline = True + + exec_results_box.text_frame.add_paragraph() + exec_results_box.text_frame.add_paragraph() + + crnyc_intro_run = exec_results_box.text_frame.paragraphs[2].add_run() + exec_results_box.text_frame.paragraphs[2].alignment = PP_ALIGN.LEFT + + crnyc_intro_run.text = 'Community Retrofit NYC’s engineering feasibility study provides ' \ + 'a cost effective and energy efficient solution for {}. ' \ + 'Implementation of the recommended ECMs would ' \ + 'result in the following:'.format(project_address) + + crnyc_intro_run.font.name = 'Arial' + crnyc_intro_run.font.size = Pt(14) + exec_results_box.text_frame.add_paragraph() + + annual_energy_percent = int(sheet_input.loc[3, 1]) + annual_dollar_save = int(sheet_input.loc[4, 1]) + co2_tonnage = int(sheet_input.loc[5, 1]) + jobs_created = int(sheet_input.loc[6, 1]) + + exec_energy_savings_line = exec_results_box.text_frame.add_paragraph() + exec_energy_savings_run = exec_energy_savings_line.add_run() + + exec_energy_savings_run.text = '{}\N{PERCENT SIGN} decrease in energy ' \ + 'consumption'.format(annual_energy_percent) + + exec_results_box.text_frame.paragraphs[4].alignment = PP_ALIGN.LEFT + exec_energy_savings_run.font.name = 'Arial' + exec_energy_savings_run.font.size = Pt(14) + exec_energy_savings_run.font.bold = True + exec_results_box.text_frame.add_paragraph() + + exec_dollar_savings_line = exec_results_box.text_frame.add_paragraph() + exec_dollar_savings_run = exec_dollar_savings_line.add_run() + exec_dollar_savings_run.text = 'Estimated annual savings of ' \ + '\N{DOLLAR SIGN}{}'.format(annual_dollar_save) + exec_results_box.text_frame.paragraphs[6].alignment = PP_ALIGN.LEFT + exec_dollar_savings_run.font.name = 'Arial' + exec_dollar_savings_run.font.size = Pt(14) + exec_dollar_savings_run.font.bold = True + exec_results_box.text_frame.add_paragraph() + + exec_emissions_line = exec_results_box.text_frame.add_paragraph() + exec_emissions_run = exec_emissions_line.add_run() + exec_emissions_run.text = "{} less tonnes of CO\u2082 generated".format(co2_tonnage) + exec_results_box.text_frame.paragraphs[8].alignment = PP_ALIGN.LEFT + exec_emissions_run.font.name = 'Arial' + exec_emissions_run.font.size = Pt(14) + exec_emissions_run.font.bold = True + exec_results_box.text_frame.add_paragraph() + + exec_job_add_line = exec_results_box.text_frame.add_paragraph() + exec_job_add_run = exec_job_add_line.add_run() + exec_job_add_run.text = "{} jobs created".format(jobs_created) + exec_results_box.text_frame.paragraphs[10].alignment = PP_ALIGN.LEFT + exec_job_add_run.font.name = 'Arial' + exec_job_add_run.font.size = Pt(14) + exec_job_add_run.font.bold = True + exec_results_box.text_frame.add_paragraph() + + # Slide 3: Energy Analysis, includes heatload pie chart + image from + # BlocMaps (might be static) + efficiency % from BlocMaps + + heat_load_slide = prs.slides[2] + energy_analyze_box = heat_load_slide.shapes[3] + + # BlocMaps Efficiency Comparison + blocmaps_est = sheet_input.loc[7, 1] + eab_run = energy_analyze_box.text_frame.paragraphs[0].add_run() + eab_run.text = 'Your building is {}\N{PERCENT SIGN} less efficient than buildings of ' \ + 'similar age, size, and type.'.format(blocmaps_est) + eab_run.font.name = 'Arial' + eab_run.font.size = Pt(14) + eab_run.font.color.rgb = RGBColor(0xff, 0xff, 0xff) + + # For using w/ input file + heat_loss_cat = input_list[9:15] + heat_loss_val = input_values[9:15] + + total_heat_loss = sum(heat_loss_val) + heat_loss_break = [hl_frac/total_heat_loss for hl_frac in heat_loss_val] + + heat_load_pie_data = ChartData() + heat_load_pie_data.categories = heat_loss_cat + heat_load_pie_data.add_series('Heat Load Breakdown', heat_loss_break) + + # Set chart location and formatting + chart_horz_pos, chart_vert_pos = Inches(4.2), Inches(1.3) + chart_width, chart_height = Inches(5.27), Inches(3.47) + heat_load_pie = heat_load_slide.shapes.add_chart(XL_CHART_TYPE.PIE, chart_horz_pos, + chart_vert_pos, chart_width, chart_height, + heat_load_pie_data).chart + + heat_load_pie.has_legend = True + heat_load_pie.legend.include_in_layout = False + + heat_load_pie.plots[0].has_data_labels = True + heat_loss_labels = heat_load_pie.plots[0].data_labels + heat_loss_labels.number_format = '0%' + heat_loss_labels.font.size = Pt(22) + + # Slide 4: Potential Savings, includes utility expenses stacked bar charts for + # pre+post-retrofit, ECM table w/ names, savings for elec and gas, and total savings + + # Slide 4: Utility breakdown (chart is in bottom right corner) + utilityProjectionSlide = prs.slides[3] + + # Define stacked bar chart data + util_cat = ["Existing", "Projected"] + util_break_sh = sheet_input.loc[16:17, 1] + util_break_dhw = sheet_input.loc[16:17, 2] + util_break_light = sheet_input.loc[16:17, 3] + util_break_plug = sheet_input.loc[16:17, 4] + util_break_h2o = sheet_input.loc[16:17, 5] + + + util_break_data = ChartData() + util_break_data.categories = util_cat + util_break_data.add_series("Space Heating", util_break_sh) + util_break_data.add_series("DHW", util_break_dhw) + util_break_data.add_series("Lighting", util_break_light) + util_break_data.add_series("Plug Load", util_break_plug) + util_break_data.add_series("Water", util_break_h2o) + + # Set chart location and formatting + chart_horz_pos, chart_vert_pos = Inches(0.05), Inches(2.4) + chart_width, chart_height = Inches(3.5), Inches(2.5) + util_break_chart = utilityProjectionSlide.shapes.add_chart(XL_CHART_TYPE.COLUMN_STACKED, + chart_horz_pos, chart_vert_pos, + chart_width, chart_height, + util_break_data).chart + + # Formatting for chart + # Legend + util_break_chart.has_legend = True + util_break_chart.legend.include_in_layout = False + util_break_chart.legend.position = XL_LEGEND_POSITION.LEFT + util_break_chart.legend.font.size = Pt(11) + util_break_chart.legend.font.color.rgb = RGBColor(0xff, 0xff, 0xff) + + # Data Labels + util_break_chart.plots[0].has_data_labels = True + util_labels = util_break_chart.plots[0].data_labels + util_labels.number_format = '$0' + util_labels.font.size = Pt(8) + + # Axis + util_break_chart.has_axis = True + util_break_chart.has_axis_title = True + util_break_chart.category_axis.tick_labels.font.size = Pt(11) + util_break_chart.value_axis.has_major_gridlines = False + util_break_chart.value_axis.tick_label_position = XL_TICK_LABEL_POSITION.NONE + + # Create ECM summary table + ecm_count = input_values.str.contains('Y').sum() + ecm_table = utilityProjectionSlide.shapes.add_table(ecm_count+2, 3, Inches(3.81), + Inches(0.25), Inches(5.83), + Inches(3.76)) + ecm_table.last_row = True + ecm_table.horz_banding = False + ecm_table_head = ['ECM', "Heating Savings *", "Electricity Savings *"] + + for j in range(len(ecm_table_head)): + ecm_table.table.cell(0, j).text_frame.paragraphs[0].font.size = Pt(12) + ecm_table.table.cell(0, j).text = ecm_table_head[j] + + ecm_list = [] + ecm_heat_save = [] + ecm_elec_save = [] + + for j in range(len(input_values)): + if input_values[j] == 'Y': + ecm_list.append(input_list[j]) + ecm_heat_save.append(input_values[j+1]) + ecm_elec_save.append(input_values[j+2]) + + for k, j in zip(ecm_list, range(len(ecm_list))): + ecm_table.table.cell(j+1, 0).text_frame.paragraphs[0].font.size = Pt(10) + ecm_table.table.cell(j+1, 0).text = k + ecm_table.table.cell(j+1, 1).text_frame.paragraphs[0].font.size = Pt(10) + ecm_table.table.cell(j+1, 1).text = str(ecm_heat_save[j]) + ecm_table.table.cell(j+1, 2).text_frame.paragraphs[0].font.size = Pt(10) + ecm_table.table.cell(j+1, 2).text = str(ecm_elec_save[j]) + + # Slides 5-end: removing slides based on recommendations + counter = 0 + for j in range(len(input_values)): + if input_values[j] == 'N': + slide_idx = int(((j-6)/3) - counter) + prs.slides.delete_slide(prs, slide_idx) + counter = counter + 1 + + + return project_address, prs + + @staticmethod + def generate_report(): + """ + 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): ') + project_address, prs = CbraDiagnostic._generate_report(template_file, file_name) + prs.save('EEDR {}.pptx'.format(project_address)) diff --git a/requirements-dev.txt b/requirements-dev.txt index eba0caa2c03bda2c62d451d0a1118c0a315a6633..9213341e0ad1bacd71100ac67f2570c5d1606f2c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ -r requirements.txt autopep8==1.3 -matplotlib==1.5.3 +matplotlib==2.0.0 prospector==0.12.4 pycodestyle==2.0.0 pydocstyle==1.0.0 diff --git a/requirements.txt b/requirements.txt index 0b8b9686a0eccc658645248d63e45cfb1f4d8345..f39d0749a0b6fae99420acd51957d7ef84b72100 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ numpy==1.12.0 pandas==0.19.2 xlrd==1.0.0 +git+ssh://git@github.com/Blocp/python-pptx.git