from collections import OrderedDict import os import unittest from unittest.mock import MagicMock from nose.tools import eq_ from helix.constants.anchor_type import AnchorType from helix.constants.module_type import ModuleType from helix.constants.panel_type import PanelType from helix.constants.version import version from helix.doc_gen_params_builder import DocGenParamsBuilder from helix.models.coordinate import Coordinate from helix.models.panel import Panel from helix.models.sql.inverters import Inverter from helix.models.sql.power_stations import PowerStation from helix.models.sql.sites import Site from helix.models.sql.standalone_inverters import StandaloneInverter from helix.models.subarray import Subarray from helix.user_values import UserValues class DocGenParamsBuilderTest(unittest.TestCase): def setUp(self): self.site = Site(system_type='1') self.site.project_name = '' self.site.building_height = 0 self.site.building_width = 0 self.site.building_length = 0 self.site.parapet_height = 0 self.site.ballast_block_weight = 0 self.site.max_psf = 0 self.site.anchor_type = AnchorType.OMG_PowerGrip_Plus.value self.site.exposure_category = 'C' self.site.wind_speed = 0 self.site.spectral_response = 1 self.site.importance_factor = 1 self.site.module_type = ModuleType.Cell96.value self.user_values = UserValues(None, self.site) self.calculator = MagicMock() self.image_presenter = MagicMock() os.environ['SP_DOCGEN_API_KEY'] = 'TestDocGenApiKey' def tearDown(self): os.environ['SP_DOCGEN_API_KEY'] = '' def test_build_single_tilt(self): self.site.system_type = '0' subject = DocGenParamsBuilder(self.user_values, self.user_values.system_type(), self.calculator, self.image_presenter) self.calculator.L_B.return_value = 0.001 self.calculator.k_z.return_value = 0.949932 self.calculator.q_z.return_value = 0.12453 self.calculator.get_computed_csv_columns.return_value = [] self.calculator.documentation_summary_values.return_value = {'summary': 'foo'} self.calculator.summary_table.return_value = OrderedDict({PanelType.Corner: {'anchors': [1], 'ballast blocks': [6], 'pressure': ['4.46']}}) self.calculator.minimum_array_sizes.return_value = [123] self.calculator.documentation_bom.return_value = [] self.calculator.documentation_bom.return_value = [ ('88764', 9), ('modules', 0) ] self.image_presenter.generate_image.return_value = "".encode("utf-8") params = subject.build() expected = { 'apiKey': 'TestDocGenApiKey', 'templateName': 'Helix_Single_Tilt_Template', 'nameValuePairs': [ {'name': '1_a_anc', 'value': 1}, {'name': '1_a_bb', 'value': 6}, {'name': '1_a_psf', 'value': '4.46'}, {'name': 'anchor_type', 'value': 'OMG PowerGrip Plus'}, {'name': 'ballast_block_weight', 'value': 0.0}, {'name': 'building_height', 'value': 0.0}, {'name': 'building_length', 'value': 0.0}, {'name': 'building_width', 'value': 0.0}, {'name': 'exposure_category', 'value': 'C'}, {'name': 'exposure_category_transition_distance', 'value': 0}, {'name': 'kz', 'value': 0.95}, {'name': 'lb', 'value': 0.0}, {'name': 'max_allowable_system_pressure', 'value': 0.0}, {'name': 'min_a', 'value': 123}, {'name': 'module_type', 'value': '96 Cell'}, {'name': 'parapet_height', 'value': 0.0}, {'name': 'power_station_string', 'value': ''}, {'name': 'project_name', 'value': ''}, {'name': 'qz', 'value': 0.12}, {'name': 'seismic_importance_factor', 'value': 1.0}, {'name': 'spectral_response', 'value': 1.0}, {'name': 'standalone_inverter_string', 'value': ''}, {'name': 'subarrays_string', 'value': ''}, {'name': 'summary', 'value': 'foo'}, {'name': 'system_type', 'value': 'Single-Tilt'}, {'name': 'total_88764', 'value': 9}, {'name': 'total_modules', 'value': 0}, {'name': 'version', 'value': version()}, {'name': 'wind_speed', 'value': 0.0}, ], 'dynamicImages': [ {'imageKey': 'array_image', 'base64encodedImage': ''} ] } eq_(params, expected) def test_build_dual_tilt(self): self.site.system_type = '1' subject = DocGenParamsBuilder(self.user_values, self.user_values.system_type(), self.calculator, self.image_presenter) self.calculator.L_B.return_value = 0 self.calculator.k_z.return_value = 0 self.calculator.q_z.return_value = 0 self.calculator.get_computed_csv_columns.return_value = [] self.calculator.documentation_summary_values.return_value = {'summary': 'foo'} self.image_presenter.generate_image.return_value = "".encode("utf-8") params = subject.build() eq_(sorted(params.keys()), ['apiKey', 'dynamicImages', 'nameValuePairs', 'templateName']) eq_(params['apiKey'], 'TestDocGenApiKey') eq_(params['templateName'], 'Helix_Dual_Tilt_Template') def test_site_characterization(self): self.site.system_type = '0' self.site.project_name = 'Test Project Name' self.site.building_height = 100 self.site.building_width = 1000 self.site.building_length = 1500 self.site.parapet_height = 0 self.site.ballast_block_weight = 20 self.site.max_psf = 17 self.site.anchor_type = AnchorType.EcoFasten.value self.site.exposure_category = 'D' self.site.wind_speed = 110 self.site.spectral_response = 1 self.site.importance_factor = 1 self.site.module_type = ModuleType.Cell96.value self.calculator.L_B.return_value = 23 self.calculator.k_z.return_value = 34 self.calculator.q_z.return_value = 45 self.calculator.get_computed_csv_columns.return_value = [ Panel(), Panel() ] subject = DocGenParamsBuilder(self.user_values, self.user_values.system_type(), self.calculator, self.image_presenter) expected_site_characterization = { 'project_name': 'Test Project Name', 'building_height': 100.0, 'building_width': 1000.0, 'building_length': 1500.0, 'parapet_height': 0.0, 'ballast_block_weight': 20.0, 'max_allowable_system_pressure': 17, 'anchor_type': 'EcoFasten Eco 65', 'exposure_category': 'D', 'exposure_category_transition_distance': 0, 'wind_speed': 110, 'spectral_response': 1, 'seismic_importance_factor': 1, 'module_type': '96 Cell', 'system_type': 'Single-Tilt', 'total_modules': 2, 'version': version(), 'lb': 23, 'kz': 34, 'qz': 45 } eq_(subject.site_characterization(), expected_site_characterization) def test_build_panel_attributes_dual_tilt(self): self.site.system_type = '1' self.calculator.summary_table.return_value = OrderedDict({ PanelType.Corner: {'anchors': [1, 1, 0, 0, 0], 'ballast blocks': [6, 0, 13, 5, 0], 'pressure': ['4.46', '2.40', '6.86', '4.08', '2.40']}, PanelType.NorthSouth: {'anchors': [1, 0, 0, 0, 0], 'ballast blocks': [0, 19, 9, 3, 0], 'pressure': ['2.40', '8.94', '5.47', '3.41', '2.40']}, PanelType.EastWest: {'anchors': [1, 0, 0, 0, 0], 'ballast blocks': [0, 19, 9, 3, 0], 'pressure': ['2.26', '8.77', '5.35', '3.27', '2.26']}, PanelType.Middle: {'anchors': [0, 0, 0, 0, 0], 'ballast blocks': [16, 8, 3, 0, 0], 'pressure': ['7.76', '5.01', '3.27', '2.26', '2.26']} }) self.calculator.minimum_array_sizes.return_value = [22, 19, 23, 19, 6] subject = DocGenParamsBuilder(self.user_values, self.user_values.system_type(), self.calculator, self.image_presenter) expected = { '1_a_bb': 6, '1_b_bb': 0, '1_c_bb': 13, '1_d_bb': 5, '1_e_bb': 0, '1_a_anc': 1, '1_b_anc': 1, '1_c_anc': 0, '1_d_anc': 0, '1_e_anc': 0, '1_a_psf': '4.46', '1_b_psf': '2.40', '1_c_psf': '6.86', '1_d_psf': '4.08', '1_e_psf': '2.40', '2_a_bb': 0, '2_b_bb': 19, '2_c_bb': 9, '2_d_bb': 3, '2_e_bb': 0, '2_a_anc': 1, '2_b_anc': 0, '2_c_anc': 0, '2_d_anc': 0, '2_e_anc': 0, '2_a_psf': '2.40', '2_b_psf': '8.94', '2_c_psf': '5.47', '2_d_psf': '3.41', '2_e_psf': '2.40', '3_a_bb': 0, '3_b_bb': 19, '3_c_bb': 9, '3_d_bb': 3, '3_e_bb': 0, '3_a_anc': 1, '3_b_anc': 0, '3_c_anc': 0, '3_d_anc': 0, '3_e_anc': 0, '3_a_psf': '2.26', '3_b_psf': '8.77', '3_c_psf': '5.35', '3_d_psf': '3.27', '3_e_psf': '2.26', '4_a_bb': 16, '4_b_bb': 8, '4_c_bb': 3, '4_d_bb': 0, '4_e_bb': 0, '4_a_anc': 0, '4_b_anc': 0, '4_c_anc': 0, '4_d_anc': 0, '4_e_anc': 0, '4_a_psf': '7.76', '4_b_psf': '5.01', '4_c_psf': '3.27', '4_d_psf': '2.26', '4_e_psf': '2.26', 'min_a': 22, 'min_b': 19, 'min_c': 23, 'min_d': 19, 'min_e': 6 } eq_(subject.panel_attributes(), expected) def test_build_bom(self): self.calculator.documentation_bom.return_value = [ ('512200', 480), ('513832', 0), ('513833', 790), ('513836', 0), ('521794', 196), ('anchors', 262), ('ballast', 6786), ('modules', 1726) ] subject = DocGenParamsBuilder(self.user_values, self.user_values.system_type(), self.calculator, self.image_presenter) expected = { 'total_512200': 480, 'total_513832': 0, 'total_513833': 790, 'total_513836': 0, 'total_521794': 196, 'total_anchors': 262, 'total_ballast': 6786, 'total_modules': 1726 } eq_(subject.bom(), expected) def test_build_power_station(self): power_station_1 = PowerStation( description='1', quantity=1, ac_run_length=55, inverters=[ Inverter( model='4', strings_per_inverter=2, sunshade=True, dc_switch=False ), Inverter( model='5', strings_per_inverter=5, sunshade=False, dc_switch=False ) ] ) power_station_2 = PowerStation( description='2', quantity=3, ac_run_length=89, inverters=[ Inverter( model='6', strings_per_inverter=6, sunshade=False, dc_switch=True ), Inverter( model='8', strings_per_inverter=8, sunshade=False, dc_switch=False ) ] ) self.site.power_stations = [power_station_1, power_station_2] expected = { 'power_station_string': """ Description: 1 \tQuantity: 1 \tAC Run Length: 55 \tInverters: \t\tModel: 12kW SMA Tripower - 514686 \t\tStrings per inverter: 2 \t\tSunShade: Yes \t\tModel: 15kW SMA Tripower - 514687 \t\tStrings per inverter: 5 Description: 2 \tQuantity: 3 \tAC Run Length: 89 \tInverters: \t\tModel: 20kW SMA Tripower - 512676 \t\tStrings per inverter: 6 \t\tDC Switch: Yes \t\tModel: 24kW SMA Tripower - 514685 \t\tStrings per inverter: 8 """ } subject = DocGenParamsBuilder(self.user_values, self.user_values.system_type(), self.calculator, self.image_presenter) eq_(subject.power_stations(), expected) def test_standalone_inverters(self): power_station = PowerStation( description='My Power Station', quantity=1, ac_run_length=45, inverters=[ Inverter( model='8', strings_per_inverter=7, sunshade=True, dc_switch=True ), Inverter( model='4', strings_per_inverter=2, sunshade=False, dc_switch=False ) ] ) standalone_inverter_1 = StandaloneInverter( inverter=[Inverter(model='6', strings_per_inverter='5', sunshade=True, dc_switch=False)], ac_run_length=45, attachment_point=power_station, ) standalone_inverter_2 = StandaloneInverter( inverter=[Inverter(model='4', strings_per_inverter='3', sunshade=False, dc_switch=True)], ac_run_length=45, attachment_point=None, ) self.site.power_stations = [power_station] self.site.standalone_inverters = [standalone_inverter_1, standalone_inverter_2] subject = DocGenParamsBuilder(self.user_values, self.user_values.system_type(), self.calculator, self.image_presenter) """ AC Run Length: 3 Attachment Point: Switch Gear Model: 20kW SMA Tripower - 512676 Strings per inverter: 6 Sunshade: Yes DC Switch: Yes """ expected = { 'standalone_inverter_string': """ \tAC Run Length: 45 \tAttachment Point: My Power Station \tModel: 20kW SMA Tripower - 512676 \tStrings per inverter: 5 \tSunShade: Yes \tAC Run Length: 45 \tAttachment Point: Switch Gear \tModel: 12kW SMA Tripower - 514686 \tStrings per inverter: 3 \tDC Switch: Yes """ } eq_(subject.standalone_inverters(), expected) def test_subarray_summary(self): self.calculator.subarray_summary.return_value = [ Subarray(subarray_number=1, origin=Coordinate(0, 0), required_seismic_anchors=11, weight=23782.67, start_row=0, size=192), Subarray(subarray_number=2, origin=Coordinate(0, 0), required_seismic_anchors=0, weight=13113.05, start_row=192, size=96) ] subject = DocGenParamsBuilder(self.user_values, self.user_values.system_type(), self.calculator, self.image_presenter) expected = { 'subarrays_string': """ \tSubarray: 1 \tSeismic Anchors: 11 \tWeight: 23,783 lbs \tSubarray: 2 \tSeismic Anchors: 0 \tWeight: 13,113 lbs """ } eq_(subject.subarray_summary(), expected) def test_array_image(self): panels = [ Panel(id=1) ] subarrays = [ Subarray(subarray_number=1) ] self.calculator.get_computed_csv_columns.return_value = panels self.calculator.subarrays = subarrays self.image_presenter.generate_image.return_value = "hello world".encode("utf-8") subject = DocGenParamsBuilder(self.user_values, self.user_values.system_type(), self.calculator, self.image_presenter) expected = {'array_image': 'aGVsbG8gd29ybGQ='} eq_(subject.array_image(), expected) self.image_presenter.generate_image.assert_called_once_with(panels, subarrays) def test_convert_list_params_to_dict(self): list_params = [ { 'name': 'foo', 'value': 'bar' }, { 'name': 'cat', 'value': 'dog' } ] expected = { 'foo': 'bar', 'cat': 'dog' } eq_(DocGenParamsBuilder.convert_list_params_to_dict(list_params), expected) def test_convert_dict_params_to_list(self): dict_params = { 'foo': 'bar', 'cat': 'dog', 'fast': 'slow', } expected = sorted([ { 'name': 'foo', 'value': 'bar' }, { 'name': 'cat', 'value': 'dog' }, { 'name': 'fast', 'value': 'slow' } ], key=lambda k: k['name']) eq_(DocGenParamsBuilder.convert_dict_params_to_list(dict_params), expected) def test_convert_dict_parms_to_list_other_names(self): dict_params = { 'foo': 'bar', 'cat': 'dog', 'fast': 'slow', } expected = sorted([ { 'baz': 'foo', 'qux': 'bar' }, { 'baz': 'cat', 'qux': 'dog' }, { 'baz': 'fast', 'qux': 'slow' } ], key=lambda k: k['baz']) eq_(DocGenParamsBuilder.convert_dict_params_to_list(dict_params, key_name='baz', value_name='qux'), expected)