diff --git a/bpeng/pna/discrete_bar_graph.py b/bpeng/pna/discrete_bar_graph.py index 678c894cfe1b06fef0886b127344c5e5b2394970..bb5caced91c94042bea2ce2dd107a7ffbc6db8dd 100644 --- a/bpeng/pna/discrete_bar_graph.py +++ b/bpeng/pna/discrete_bar_graph.py @@ -29,7 +29,4 @@ def create_graph_factory(name_in_ppt, num_colored, total=5, horizontal=True, bla i += 1 if i > num_colored: cell.fill.fore_color.rgb = RGBColor(blank_color[0], blank_color[1], blank_color[2]) - print("ayy") - else: - print("vertical not programmed yet sorry") return graph_factory diff --git a/bpeng/pna/pna.py b/bpeng/pna/pna.py index 858503434ed82abc155e3eae2ea2da90bc452daa..f59e5eca456a64c19c1f52c016e99ce6aa04656d 100644 --- a/bpeng/pna/pna.py +++ b/bpeng/pna/pna.py @@ -6,12 +6,12 @@ from pptx.enum.text import ( # pylint: disable=no-name-in-module MSO_ANCHOR, PP_ALIGN ) - from .template import * from .field_format import * from .discrete_bar_graph import create_graph_factory -def generate_ashp_pna(template_path, building_info, hvac, cost_estimates, scores, contact_info, + +def generate_ashp_pna(template_path, building_info, hvac, cost_estimates, scores, contact_info=None, external=False): """ Generate an ASHP PNA from dicts @@ -35,7 +35,7 @@ def generate_ashp_pna(template_path, building_info, hvac, cost_estimates, scores Returns a python-pptx presentation object """ - pna_generator = TemplateInstantiator(template_path) + pna_generator = TemplateInstantiator('template_path') addr = building_info['address'] # Provide template with address, client name, and date for title page @@ -99,17 +99,19 @@ def generate_ashp_pna(template_path, building_info, hvac, cost_estimates, scores if scores[i][0].lower().find('utility') != -1: new_crit_name = scores[i][0] + '*' # Put an asterisk/footnote on utility scores, for legal reasons scores[i] = (new_crit_name, scores[i][1]) - + addr_sub_2a = Substitution('ADDRESS', addr) criteria_name_sub1 = Substitution('CRITERIA_NAME_0', scores[0][0], font_size=18) criteria_name_sub2 = Substitution('CRITERIA_NAME_1', scores[1][0], font_size=18) criteria_name_sub3 = Substitution('CRITERIA_NAME_2', scores[2][0], font_size=18) criteria_name_sub4 = Substitution('CRITERIA_NAME_3', scores[3][0], font_size=18) - graph_factory1 = create_graph_factory('CSCORE0', scores[0][1], total=5) - graph_factory2 = create_graph_factory('CSCORE1', scores[1][1], total=5) - graph_factory3 = create_graph_factory('CSCORE2', scores[2][1], total=5) - graph_factory4 = create_graph_factory('CSCORE3', scores[3][1], total=5) + num_list = calculate_bar_color_number(scores) + + graph_factory1 = create_graph_factory('CSCORE0', num_list[0], total=5) + graph_factory2 = create_graph_factory('CSCORE1', num_list[1], total=5) + graph_factory3 = create_graph_factory('CSCORE2', num_list[2], total=5) + graph_factory4 = create_graph_factory('CSCORE3', num_list[3], total=5) graph_slide = pna_generator.templateSlideNumber(4) @@ -151,7 +153,7 @@ def generate_ashp_pna(template_path, building_info, hvac, cost_estimates, scores name = ' ' email = 'BusinessDevelopment@blocpower.io' phone = '(718) 924-2873' # TODO: don't hardcode these values - + pronoun_sub = Substitution( 'PRONOUN', pronoun, @@ -186,3 +188,278 @@ def generate_ashp_pna(template_path, building_info, hvac, cost_estimates, scores pna_generator.export().save(save_to) return save_to + +def calculate_func(building_sq_feet, + number_of_apts, + number_of_bedrooms, + heated_basement, + number_of_heated_stairwell, + number_of_building_floors, + heated_hallways + ): + material_unit_cost = { + 'outdoor': 2100, + 'indoor': 475, + 'ancillary_equipment':500, + 'refrigerant_line_run': 5, + 'outdoor_electric_connection': 400 + } + + # Static labor cost + labor_unit_cost = { + 'outdoor': 750, + 'indoor': 350, + 'refrigerant_line_run': 25, + 'outdoor_electric_connection': 25 + } + + # Assumptions + assumptions = { + 'feet_refrigerant_line_indoor_unit' : 30, + 'feet_electric_wire_outdoor_unit' : 30, + 'spread_cost_range_percentage' : 0.15 + } + + #Difficulty of installation + installation_difficulty = { + 'weather_stripping': 2, + 'ashp': 4, + 'solar': 2 + } + + #Paybacks + payback = { + 'weather_stripping': '2 - 3', + 'ashp': '10 +', + 'solar': '4 - 5' + } + + #Solar PV estimated cost + solar_cost = { + 'low': 21000, + 'high': 39000 + } + + #Material costs + material_costs = { + 'indoor': 0, + 'outdoor': 0, + 'ancillary_equipment': 0, + 'refrigerant_line_run': 0, + 'outdoor_electric_connection': 0, + } + + #Labor costs + labor_costs = { + 'outdoor': 0, + 'indoor': 0, + 'refrigerant_line_run': 0, + 'outdoor_electric_connection': 0 + } + + #Total costs + total_costs = { + 'outdoor': 0, + 'indoor': 0, + 'ancillary_equipment': 0, + 'refrigerant_line_run': 0, + 'outdoor_electric_connection': 0 + } + + #Apartment + apartment = { + 'material_cost': 0, + 'labor_cost': 0, + 'total_cost': 0, + 'avg_cost': 0, + } + + #Basement + basement = { + 'material_cost': 0, + 'labor_cost': 0, + 'total_cost': 0, + 'avg_cost': 0, + } + + #Stairewells + stairwells = { + 'material_cost': 0, + 'labor_cost': 0, + 'total_cost': 0, + 'avg_cost': 0, + } + + #Hallways + hallways = { + 'material_cost': 0, + 'labor_cost': 0, + 'total_cost': 0, + 'avg_cost': 0, + } + + #Project costs + project = { + 'material_cost': 0, + 'labor_cost': 0, + 'total_cost': 0, + 'avg_cost': 0, + } + + data = { + 'weather_stripping': { + 'difficulty_of_installation': 2, + 'estimated_cost_low': 0, + 'estimated_cost_high': 0, + 'payback_years': '2 - 3', + }, + 'ashp_replacement': { + 'difficulty_of_installation': 4, + 'estimated_cost_low': 0, + 'estimated_cost_high': 0, + 'payback_years': '10 +', + }, + 'solar_pv': { + 'difficulty_of_installation': 2, + 'estimated_cost_low': 21000, + 'estimated_cost_high': 39000, + 'payback_years': '4 - 5', + }, + } + + def ashp_cost(outdoor_units, indoor_units): + key = 'outdoor' + material_costs[key] = outdoor_units * material_unit_cost[key] + labor_costs[key] = outdoor_units * labor_unit_cost[key] + total_costs[key] = material_costs[key] + labor_costs[key] + + key = 'indoor' + material_costs[key] = indoor_units * material_unit_cost[key] + labor_costs[key] = indoor_units * labor_unit_cost[key] + total_costs[key] = material_costs[key] + labor_costs[key] + + key = 'ancillary_equipment' + material_costs[key] = outdoor_units * material_unit_cost[key] + total_costs[key] = material_costs[key] + + key = 'refrigerant_line_run' + refrigerant_line_run = indoor_units * assumptions['feet_refrigerant_line_indoor_unit'] + material_costs[key] = refrigerant_line_run * material_unit_cost[key] + labor_costs[key] = refrigerant_line_run * labor_unit_cost[key] + total_costs[key] = material_costs[key] + labor_costs[key] + + key = 'outdoor_electric_connection' + outdoor_electric_connection = outdoor_units * assumptions['feet_electric_wire_outdoor_unit'] + material_costs[key] = outdoor_units * material_unit_cost[key] + labor_costs[key] = outdoor_electric_connection * labor_unit_cost[key] + total_costs[key] = material_costs[key] + labor_costs[key] + + return { + 'material_cost': sum(material_costs.values()), \ + 'labor_cost': sum(labor_costs.values()), \ + 'total_cost': sum(total_costs.values()), \ + 'avg_cost': sum(total_costs.values())/number_of_apts + } + + #Apartments + outdoor_units = number_of_apts + indoor_units = number_of_apts + number_of_bedrooms + apartment = ashp_cost(outdoor_units, indoor_units) + + #Basement + outdoor_units = 2 + indoor_units = 6 + if heated_basement == 1: + basement = ashp_cost(outdoor_units, indoor_units) + + #Stairewell + outdoor_units = number_of_heated_stairwell + indoor_units = number_of_heated_stairwell * number_of_building_floors + stairwells = ashp_cost(outdoor_units, indoor_units) + + #Hallways + outdoor_units = 1 + indoor_units = number_of_building_floors + if heated_hallways == 1: + hallways = ashp_cost(outdoor_units, indoor_units) + + #Total Project Costs + for cost in ['material_cost', 'labor_cost', 'avg_cost', 'total_cost']: + project[cost] = apartment[cost] + basement[cost] + stairwells[cost] + hallways[cost] + + #ASHP Cost Range + data['ashp_replacement']['estimated_cost_low'] = 1000*round(project['total_cost']*(1-assumptions['spread_cost_range_percentage'])/1000) + data['ashp_replacement']['estimated_cost_high'] = 1000*round(project['total_cost']*(1+assumptions['spread_cost_range_percentage'])/1000) + + #Weatherstripping Cost Range + data['weather_stripping']['estimated_cost_low'] = building_sq_feet * 0.33 + data['weather_stripping']['estimated_cost_high'] = building_sq_feet * 0.4 + + return data + +def calculate_bar_color_number(scores): + + num0 = 0 + num1 = 0 + num2 = 0 + num3 = 0 + num_list = [] + + if scores[0][1] in range(0, 4): + num0 = 0 + elif scores[0][1] in range(4, 7): + num0 = 1 + elif scores[0][1] in range(7, 9): + num0 = 2 + elif scores[0][1] in range(9, 11): + num0 = 3 + elif scores[0][1] in range(11, 13): + num0 = 4 + elif scores[0][1] in range(13, 17): + num0 = 5 + + if scores[1][1] in range(0, 15): + num1 = 0 + elif scores[1][1] in range(15, 20): + num1 = 1 + elif scores[1][1] in range(20, 23): + num1 = 2 + elif scores[1][1] in range(23, 27): + num1 = 3 + elif scores[1][1] in range(27, 31): + num1 = 4 + elif scores[1][1] in range(31, 35): + num1 = 5 + + if scores[2][1] == 0: + num2 = 0 + elif scores[2][1] in range(1, 3): + num2 = 1 + elif scores[2][1] in range(3, 6): + num2 = 2 + elif scores[2][1] in range(6, 10): + num2 = 3 + elif scores[2][1] in range(10, 13): + num2 = 4 + elif scores[2][1] in range(13, 16): + num2 = 5 + + if scores[3][1] in range(0, 12): + num3 = 0 + elif scores[3][1] in range(12, 18): + num3 = 0 + elif scores[3][1] in range(18, 22): + num3 = 0 + elif scores[3][1] in range(22, 26): + num3 = 0 + elif scores[3][1] in range(26, 31): + num3 = 0 + elif scores[3][1] in range(31, 36): + num3 = 0 + + num_list.append(num0) + num_list.append(num1) + num_list.append(num2) + num_list.append(num3) + + return num_list diff --git a/bpeng/pna/score_calculation.py b/bpeng/pna/score_calculation.py new file mode 100644 index 0000000000000000000000000000000000000000..c097eae93dd79475b4034f4ca4797bed8a17c31e --- /dev/null +++ b/bpeng/pna/score_calculation.py @@ -0,0 +1,64 @@ +from .discrete_bar_graph import create_graph_factory + +def calculate_score_weight(weights_list, details_list): + + score_weight_1 = 0 + score_weight_2 = 0 + score_weight_3 = 0 + score_weight_4 = 0 + + final_criteria_score = [] + + for i in weights_list: + if i[0] == 1: + score_weight_1 += i[1] + elif i[0] == 2: + score_weight_2 += i[1] + elif i[0] == 3: + score_weight_3 += i[1] + elif i[0] == 4: + score_weight_4 += i[1] + elif i[0] == 5: + continue + + if details_list['num_of_heating_violations'] == 'None': + score_weight_1 += 0 + elif details_list['num_of_heating_violations'] in range(1, 3): + score_weight_1 += 1 + elif details_list['num_of_heating_violations'] in range(3, 11): + score_weight_1 += 3 + elif details_list['num_of_heating_violations'] >= 11: + score_weight_1 += 5 + else: + score_weight_1 += 0 + + if details_list['num_of_dob_violations'] == 'None': + score_weight_4 += 5 + elif details_list['num_of_dob_violations'] in range(1, 4): + score_weight_4 += 4 + elif details_list['num_of_dob_violations'] in range(4, 20): + score_weight_4 += 3 + elif details_list['num_of_dob_violations'] in range(20, 100): + score_weight_4 += 4 + elif details_list['num_of_dob_violations'] >= 100: + score_weight_4 += 5 + else: + score_weight_4 += 0 + + if details_list['legal_ownership'] == 'Single': + score_weight_4 += 3 + elif details_list['legal_ownership'] == 'LLC': + score_weight_4 += 5 + elif details_list['legal_ownership'] == 'Corporate': + score_weight_4 += 5 + elif details_list['legal_ownership'] == 'Non-Profit': + score_weight_4 += 3 + else: + score_weight_4 += 0 + + final_criteria_score.append((1, score_weight_1)) + final_criteria_score.append((2, score_weight_2)) + final_criteria_score.append((3, score_weight_3)) + final_criteria_score.append((4, score_weight_4)) + + return final_criteria_score diff --git a/bpeng/pna/template.py b/bpeng/pna/template.py index e911e4f1ac834b4157fe88ecc0fc69427e6614ec..500b734439f4b66c5f90c09d1c8f99e5a75e7c02 100644 --- a/bpeng/pna/template.py +++ b/bpeng/pna/template.py @@ -93,11 +93,6 @@ class Substitution(): self.formatter = lambda x: x else: self.formatter = formatter - - # if external_value is None: - # self.exteternal_value = value - # else: - # self.exteternal_value = external_value def ___repr___(self): return "[" + self.name_in_ppt + ", " + self.formatter(self.value) + "]" @@ -116,17 +111,12 @@ class Substitution(): for shape in slide.shapes: if shape is None: continue - if shape.has_text_frame: - print("text frame found: " + shape.text) - print("searching for: " + self.name_in_ppt) - print(shape.text.find(self.name_in_ppt)) if shape.has_text_frame and shape.text.find(self.name_in_ppt) != -1: new_text = shape.text new_text = new_text.replace( self.name_in_ppt, # underscores from constructor self.formatter(self.value) ) - print("did update") shape.text = new_text for p in shape.text_frame.paragraphs: @@ -139,7 +129,6 @@ class Substitution(): if shape.has_table: for row in shape.table.rows: for cell in row.cells: - #cell = shape.table.cell(r,c) if cell.text is not None and cell.text.find(self.name_in_ppt) != -1: cell.text = cell.text.replace( self.name_in_ppt, # underscores from constructor