15 Commits

Author SHA1 Message Date
Senad Uka
fe35b0aa91 syncing with upstream 2018-01-15 13:31:00 +01:00
Senad Uka
bc646b86c2 Merge pull request #13 from senaduka/extract-corner-values-based-on-building-outline-dxf
extract corner values from DXF file
2018-01-12 15:35:03 +01:00
Senad Uka
f4c19dec04 merging with upstream 2017-12-27 16:24:50 +01:00
GotPPay
627e31ef37 print to browser console ; remove magic numbers ; improve tests 2017-12-26 20:58:00 +01:00
GotPPay
b71eb244af angle calculation fix ; created tests 2017-12-25 22:24:24 +01:00
GotPPay
85dc45a326 remove corners wider than 135 degrees 2017-12-25 18:44:18 +01:00
GotPPay
7e7b7f6467 remove angles wider than 135 degrees 2017-12-25 18:39:57 +01:00
GotPPay
ef186a3754 added length in cw direction 2017-12-25 10:39:02 +01:00
GotPPay
cc490fbc6a extract informations about corners 2017-12-24 23:42:06 +01:00
GotPPay
7daa9db4ed extract informations about corners 2017-12-24 23:35:11 +01:00
Senad Uka
db7453f438 Merge pull request #12 from senaduka/building-outline-inverted
outline fix
2017-12-21 15:59:07 +01:00
GotPPay
edcfcb4aac outline fix 2017-12-21 02:20:52 +01:00
Senad Uka
2ea9e2e702 merge with upstream 2017-12-20 20:27:55 +01:00
Senad Uka
8beef5faea merge with upstream 2017-12-19 16:53:58 +01:00
Senad Uka
197db1003b merge with upstream master 2017-12-19 15:18:35 +01:00
103 changed files with 3113 additions and 793 deletions

View File

@@ -1,4 +1,4 @@
from python:3.5
from python:3.6
RUN apt-get update

View File

@@ -1 +1 @@
web: python helix/main.py
web: gunicorn -c gunicorn_config.py --pythonpath helix main:app

View File

@@ -1,3 +1,6 @@
[![CircleCI](https://circleci.com/gh/SunPower/Helix_Roof_Calculator.svg?style=svg&circle-token=aacf2ae59dae99075992ed10d1e27f119e223e75)](https://circleci.com/gh/SunPower/Helix_Roof_Calculator)
## Helix Calculator
- [Staging](https://sp-helix-staging.herokuapp.com)
@@ -31,6 +34,23 @@
| google-chrome-stable/stable,now 62.0.3202.94-1 amd64 |
| nodejs/unknown,now 6.12.0-1nodesource1 amd64 |
##### Environment Variables
Set the environment variables for your specific environment. Use the `.env` file below as reference:
```
AWS_S3_BUCKET="..."
AWS_ACCESS_KEY_ID="..."
AWS_SECRET_ACCESS_KEY="..."
SFDC_BASE_URL="https://test.salesforce.com"
SFDC_ACCESS_KEY_ID="..."
SFDC_SECRET_ACCESS_KEY="..."
SFDC_API_URL="https://sunpower--qa.cs8.my.salesforce.com"
```
##### Set up for Develop and Testing
- Run the docker container for the 1st time
@@ -47,7 +67,7 @@
- Log in the docker container
```docker exec -t -i helix /bin/bash```
```docker exec -ti helix bash```
- Run this commands inside the docker conatiner
@@ -367,3 +387,9 @@ docker exec -it helixroofcalculator_helix_1 invoke db_migrate
```
Debugging is possible directly from PyCharm, all code changes are transparent between host and container.
## Database schema
![Database Schema](documentation/db_schema.png)

View File

@@ -1,51 +0,0 @@
version: 2
jobs:
build:
docker:
- image: ivannnn/heroku_cedar14:2.0
environment:
PGUSER: pivotal
PGPASSWORD: password
PASSWORD: password
environment:
PATH: /usr/local/rvm/gems/ruby-2.3.4/bin:/usr/local/rvm/gems/ruby-2.3.4@global/bin:/usr/local/rvm/rubies/ruby-2.3.4/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/rvm/bin
steps:
- checkout
- run:
name: Set up and Run tests
command: |
source /etc/profile.d/rvm.sh
rvm use 2.3.4
/etc/init.d/postgresql start
python3.4 -m venv env
source env/bin/activate
pip install invoke
invoke install
npm install
invoke db_migrate
invoke test_ci
- deploy:
name: staging
command: |
invoke update_heroku_version staging
"[[ ! -s \"$(git rev-parse --git-dir)/shallow\" ]] || git fetch --unshallow"
git push -f git@heroku.com:sp-helix-staging.git $CIRCLE_SHA1:refs/heads/master
heroku run --app sp-helix-staging invoke db_migrate
rake ci:deliver
name: preprod
command: |
invoke update_heroku_version preprod
"[[ ! -s \"$(git rev-parse --git-dir)/shallow\" ]] || git fetch --unshallow"
git push -f git@heroku.com:sp-helix-preprod.git $CIRCLE_SHA1:refs/heads/master
heroku run --app sp-helix-preprod invoke db_migrate
rake ci:deliver
name: production
commands: |
invoke update_heroku_version production
"[[ ! -s \"$(git rev-parse --git-dir)/shallow\" ]] || git fetch --unshallow"
git push -f git@heroku.com:sp-helix-production.git $CIRCLE_SHA1:refs/heads/master
heroku run --app sp-helix-production invoke db_migrate
rake ci:deliver
notify:
webhooks:
- url: http://pulse.pivotallabs.com/projects/03ba990f-b8f5-4508-b4c1-19038b2cb791/status

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
documentation/db_schema.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

BIN
dump.rdb Normal file

Binary file not shown.

13
gunicorn_config.py Normal file
View File

@@ -0,0 +1,13 @@
"""gunicorn WSGI server configuration."""
import multiprocessing
import os
bind = '0.0.0.0:' + os.getenv('PORT', '5000')
max_requests = 1000
worker_class = 'meinheld.gmeinheld.MeinheldWorker'
workers = multiprocessing.cpu_count()
timeout = os.getenv('TIMEOUT', 120)
graceful_timeout = os.getenv('TIMEOUT', 120)
keepalive = 5
daemon = False

View File

@@ -234,24 +234,43 @@ class DXFHelper(object):
node_store.add_node(node)
# Optimization: avoid creating thousands of replicated objects
graph_directions_cache = {}
for x in (0, 1, -1):
for y in (0, 1, -1):
if x == y == 0:
continue
graph_directions_cache[(x, y)] = GraphDirection((x, y))
for node in nodes:
if len(node.neighboring_nodes()) == 8:
continue
rotation = math.radians(node.coordinate.rotation)
# Optimization: Avoid replicate calculations thousands of times
x_spacing_cos_rotation = (node.x_spacing * math.cos(rotation))
y_spacing_sin_rotation = (node.y_spacing * math.sin(rotation))
x_spacing_sin_rotation = (node.x_spacing * math.sin(rotation))
y_spacing_cos_rotation = (node.y_spacing * math.cos(rotation))
for x in (0, 1, -1):
for y in (0, 1, -1):
if x == y == 0:
continue
rotation = math.radians(node.coordinate.rotation)
x_spacing = (x * node.x_spacing * math.cos(rotation)) - (y * node.y_spacing * math.sin(rotation))
y_spacing = (x * node.x_spacing * math.sin(rotation)) + (y * node.y_spacing * math.cos(rotation))
x_spacing = (x * x_spacing_cos_rotation) - (y * y_spacing_sin_rotation)
y_spacing = (x * x_spacing_sin_rotation) + (y * y_spacing_cos_rotation)
coordinate = Coordinate(node.coordinate.x + x_spacing, node.coordinate.y + y_spacing, node.coordinate.rotation)
if coordinate.x < 0 or coordinate.y < 0:
continue
direction = GraphDirection((x, y))
# Optimization for `direction = GraphDirection((x, y))`
direction = graph_directions_cache[(x, y)]
if node.has_existing_neighbor(direction):
continue
# FIXME: This is the bottleneck of the loop
# Calling this ~10000 times needs ~20 seconds
neighbor = node_store.find_coordinate(coordinate)
if neighbor:
node.add_neighbor(neighbor, direction)
@@ -417,7 +436,7 @@ class DXFHelper(object):
def __compute_segment_direction(p1, p2):
"""
Computes direction of a segment. Points taken from building outline are assumed to be in counterclockwise order.
:param p1: first point
:param p2: second point
:return: tuple representing orientation ('north', 'south', 'east', 'west') and variation in degrees from ideally
@@ -438,7 +457,7 @@ class DXFHelper(object):
def compute_corner_directions(vertex, prev, next, angle_correction):
"""
Determines if point is located in north/east corner
:param vertex: point located in the corner
:param prev: point previous to vertex, assuming counterclockwise order
:param next: point next to vertex, assuming counterclockwise order
@@ -510,7 +529,7 @@ class DXFHelper(object):
@staticmethod
def __generate_wind_zone__(buildings, scaling_factor, points_callback, panel_orientation):
"""
Important: polygons representing buildings are expected to have points in counterclockwise order
Important: polygons representing buildings are expected to have points in counterclockwise order
"""
wind_zones = []

View File

@@ -2,9 +2,6 @@ import io
import dxfgrabber
from helix.constants.file_validation_error import FileValidationMessage
from helix.models.dxf.dxf_error import OldDxfFormatException
class DXFService(object):
"""
@@ -51,6 +48,7 @@ class DXFService(object):
panels = dxf_helper.generate_panels(modules, translated_modules)
# FIXME: Building a graph with many entities is very slow
node_graph = dxf_helper.build_node_graph(panels, module_constants.panel_spacing)
subarrays = dxf_helper.detect_subarrays(node_graph, panels)
for subarray in subarrays:

View File

@@ -0,0 +1,27 @@
import boto3
import os
import uuid
def s3_upload(bytes_or_file_like, filename=None, file_extension=None):
'''
@bytes_or_file_like: bytes(), open('filepath') or io.StringIO('')
'''
if filename is None:
filename = uuid.uuid4().hex
if file_extension:
filename += file_extension
s3 = boto3.resource('s3',
aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY'))
# Default: test environment
bucket_name = os.getenv('AWS_S3_BUCKET', 'sunpower-test-dgplatform-spectrum')
# Assuming bucket already exists
s3.Bucket(bucket_name).put_object(Key=filename, Body=bytes_or_file_like.read(), ACL='public-read')
# file_url = 'https://s3.amazonaws.com/{}/{}'.format(bucket_name, filename) # PermanentRedirect error
file_url = 'https://{}.s3.amazonaws.com/{}'.format(bucket_name, filename)
print('Uploaded filename {} to S3: {}'.format(filename, file_url))
return file_url

View File

@@ -5,7 +5,6 @@ from helix.presenters.panel_presenter import ProjectPresenter
from helix.session_manager import SessionManager
from helix.constants import redis_constant, sql_constant
from helix.seismic_validator_user_values import SeismicValidatorUserValues
from helix.validators.file_validator import FileValidator
from helix.validators.seismic_anchor_validator import SeismicAnchorValidator
api = Blueprint('api', __name__, template_folder='templates')

View File

@@ -50,7 +50,7 @@ class BomCalculator(object):
row_count = sum(subarray.row_count for subarray in self.subarrays)
column_count = sum(subarray.column_count for subarray in self.subarrays)
parts_list = MechanicalBomCalculator(self.values, self.panels, self.subarrays).mechanical_bom()
ebom_parts_list = EbomCalculator(self.values, ceil(row_count), ceil(column_count), parts_list.get(module)).compute_ebom()
ebom_parts_list = EbomCalculator(self.values, ceil(row_count), ceil(column_count), parts_list.get(module), self.subarrays).compute_ebom()
add_parts_to_list(parts_list, ebom_parts_list)

View File

@@ -1,4 +1,3 @@
from math import ceil, floor
import copy
from helix.Repositories.graph_repository import GraphRepository
from helix.calculators.ballast_calculator import BallastCalculator

View File

@@ -6,15 +6,15 @@ from helix.constants.ebom_parts import *
from helix.constants.parts import wire_clip_large, cable_support, cable_support_lid, channel_nut, sunshade
from helix.constants.system_type import SystemType
from helix.constants.inverter_brand import InverterBrand
from helix.forms.ebom_form import InverterBrandForm
class EbomCalculator(object):
def __init__(self, user_values, row_count, column_count, modules_count = None):
def __init__(self, user_values, row_count, column_count, modules_count = None, panels = None):
self.values = user_values
self.row_count = row_count
self.column_count = column_count
self.modules_count = modules_count
self.panels = panels
def resolve_power_monitor_type(self):
module_type = self.values.module_type()
@@ -30,6 +30,14 @@ class EbomCalculator(object):
else:
return monitor_controller_240_v
def resolve_is_delta(self):
if len(self.values.inverter_brands()) > 0:
return self.values.inverter_brands()[0]['inverter_brand_id'] == InverterBrand.DELTA.value
if len(self.values.standalone_inverters()) > 0:
return self.values.standalone_inverters()[0]['model'].get_type == InverterBrand.DELTA.label
# can't determine without data
return False
def compute_ebom(self):
part_list = {}
@@ -38,25 +46,14 @@ class EbomCalculator(object):
monitors = self.values.power_monitors()
module_type = self.values.module_type()
system_type = self.values.system_type()
is_delta=None
try:
is_delta=(self.values.inverter_brands()[0]['inverter_brand_id']==InverterBrand.DELTA.value)
except IndexError :
#Some tests are calculating bom without providing inverter brand so inverter_brands is empty
#for those tests, inverter brand is irrelevant
is_delta=False
is_delta = self.resolve_is_delta()
inverter_count = 0
total_ac_run_length = 0
panel_board_counts = [0, 0]
proper_monitor_controller = self.resolve_power_monitor_type()
try:
is_delta = (self.values.inverter_brands()[0]['inverter_brand_id']==InverterBrand.DELTA.value)
except IndexError:
is_delta = False
for power_station in power_stations:
power_station_count = power_station['power_station_quantity']
total_ac_run_length += power_station['ac_run_length']
@@ -91,15 +88,18 @@ class EbomCalculator(object):
for monitor in monitors:
if monitor['power_source'][0] == 'Switch Gear/External':
add_parts_to_list(part_list, {proper_monitor_controller: 1}, 1)
if (is_delta):
add_parts_to_list(part_list, {ethernet_plug: 2},1)
add_parts_to_list(part_list, {wire_clip_large: inverter_count}, self.row_count)
if is_delta:
clips_amount = inverter_count * self.row_count * 1.15
clips_rounded = int(ceil(clips_amount / 10.0)) * 10
add_parts_to_list(part_list, {wire_clip_large: clips_rounded})
else:
add_parts_to_list(part_list, {wire_clip_large: inverter_count}, self.row_count)
add_parts_to_list(part_list, {stump: 1}, ceil(total_ac_run_length / 4.0))
cable_supports = self.calculate_cable_supports(panel_board_counts, len(standalone_inverters))
cable_supports = self.calculate_cable_supports(panel_board_counts, len(standalone_inverters), is_delta)
add_parts_to_list(part_list, {cable_support: 1, cable_support_lid: 1}, cable_supports)
add_parts_to_list(part_list, {rear_skirt: -1}, ceil(cable_supports*.38))
add_parts_to_list(part_list, {rear_skirt_1_1: -1}, ceil(cable_supports*.38))
dependent_part_list = {}
@@ -121,21 +121,37 @@ class EbomCalculator(object):
if inverter['dc_switch']:
add_parts_to_list(part_list, dc_switch_parts, multiplier)
def calculate_cable_supports(self, panel_board_counts, standalone_inverter_count):
if sum(panel_board_counts) == 0:
def calculate_cable_supports(self, panel_board_counts, standalone_inverter_count, is_delta):
# There are some tests that don't have panels
if is_delta and self.panels is None:
return 0
if self.values.system_type() == SystemType.dualTilt:
dimension1 = self.column_count
dimension2 = self.row_count
if is_delta:
panels_count = len(self.panels)
if panels_count == 0:
return 0
if self.values.system_type() == SystemType.dualTilt:
Avg_Columns = self.column_count / panels_count
Col2Row_Ratio = self.column_count / self.row_count
Col2Row_Ratio = 1 if Col2Row_Ratio < 1 else Col2Row_Ratio
return ceil(standalone_inverter_count * 1.25 * Avg_Columns * Col2Row_Ratio + Avg_Columns)
else:
Avg_Rows = self.row_count / panels_count
Row2Col_Ratio = self.row_count / self.column_count
Row2Col_Ratio = 1 if Row2Col_Ratio < 1 else Row2Col_Ratio
return ceil(standalone_inverter_count * 1.25 * Avg_Rows * Row2Col_Ratio + Avg_Rows)
else:
dimension1 = self.row_count
dimension2 = self.column_count
result = (standalone_inverter_count + panel_board_counts[0] + (2 * panel_board_counts[1])) / sum(panel_board_counts)
result *= dimension1 * max(dimension1 / dimension2, 1)
result += dimension1
return ceil(result)
if sum(panel_board_counts) == 0:
return 0
if self.values.system_type() == SystemType.dualTilt:
dimension1 = self.column_count
dimension2 = self.row_count
else:
dimension1 = self.row_count
dimension2 = self.column_count
result = (standalone_inverter_count + panel_board_counts[0] + (2 * panel_board_counts[1])) / sum(panel_board_counts)
result *= dimension1 * max(dimension1 / dimension2, 1)
result += dimension1
return ceil(result)
def get_standalone_inverters(self, power_station):
standalone_inverters = self.values.standalone_inverters()

View File

@@ -5,7 +5,7 @@ from helix.calculators.bom_helper import add_parts_to_list, apply_fudge_factors,
from helix.calculators.subarray_helper import extract_subarray
from helix.constants.module_type import ModuleType
from helix.constants.panel_type import PanelType
from helix.constants.parts import link_tray, cross_tray, ballast, cross_tray_1_1, leading_tray
from helix.constants.parts import link_tray, cross_tray, ballast, cross_tray_1_1
from helix.constants.system_type import SystemType

View File

@@ -1,13 +1,14 @@
from math import ceil, floor
from helix.calculators.subarray_graph import SubarrayGraph
from helix.calculators.subarray_helper import extract_subarray
from helix.constants.global_constants import minimum_racking_capacity
from helix.constants.panel_type import PanelType
from helix.constants.file_validation_error import FileValidationMessage,FileValidationException
from helix.models.subarray import Subarray
class SeismicCalculator(object):
def __init__(self, values, graph_repository):
self.values = values
@@ -38,13 +39,20 @@ class SeismicCalculator(object):
more_anchors_needed = True
perimeter_covered = sds < 1.0
anchor_threshold = 0
was_rung_empty = False
while more_anchors_needed:
rung = graph.pop_rung()
interval = int(self.seismic_anchor_interval())
nodes_since_last_anchor = interval
if len(rung) == 0:
if was_rung_empty:
# detected an infinite loop
# something is wrong with the input file
# probably panels overlapping
raise FileValidationException(FileValidationMessage.PanelsTooClose.value)
graph.reset()
anchor_threshold += 1
was_rung_empty = True
continue
while more_anchors_needed and interval >= 0:
for node in rung:

View File

@@ -1,4 +1,3 @@
import copy
from enum import Enum
from helix.constants.system_type import SystemType

View File

@@ -5,6 +5,7 @@ from helix.constants.global_constants import system_force_capacity
class AnchorType(Enum):
# These values are being used by Salesforce integration, do not change it
OMG_PowerGrip = 'OMG PowerGrip'
OMG_PowerGrip_Plus = 'OMG PowerGrip Plus'
EcoFasten = 'EcoFasten Eco 65'

View File

@@ -49,3 +49,7 @@ class FileValidationError(object):
if self.__class__ != other.__class__:
return False
return self.row_number == other.row_number and self.validation_message == other.validation_message
class FileValidationException(Exception):
def __init__(self, message):
self.message = message

View File

@@ -7,3 +7,5 @@ parapet_coefficients = 0.88, 1.2
system_force_capacity = 418.
minimum_racking_capacity = 226
max_corner_angle = 135

View File

@@ -7,6 +7,10 @@ class InverterTypeSMA(IntEnum):
MODEL_20KW = 6
MODEL_24KW = 8
@property
def get_type(self):
return "SMA"
@property
def default_string(self):
return {
@@ -49,6 +53,10 @@ class InverterTypeDelta(IntEnum):
MODEL_60KW = 11
MODEL_80KW = 12
@property
def get_type(self):
return "Delta"
@property
def label(self):
return {

View File

@@ -2,6 +2,7 @@ from enum import Enum
class ModuleType(Enum):
# These values are being used by Salesforce integration, do not change it
Cell96 = '96 Cell'
Cell128 = '128 Cell'
PSeries = 'P-Series'

View File

@@ -60,14 +60,37 @@ class DualTilt128CellConstants(object):
return 1, 1
def base_weight(self, panel_type, tray_count):
if panel_type == PanelType.Corner:
return [108.66, 110.96, 112.11, 116.44, 119.62, 122.80, 125.98][tray_count]
elif panel_type == PanelType.NorthSouth:
return [107.58, 109.88, 111.03, 114.21, 117.39, 120.57, 123.75][tray_count]
elif panel_type == PanelType.EastWest:
return [103.19, 105.49, 105.49, 108.67, 111.85, 115.03, 118.21][tray_count]
else:
return [102.11, 104.41, 104.41, 107.59, 110.77, 113.95, 117.13][tray_count]
values_per_panel_type = {
PanelType.Corner: [122.70,
124.55,
126.40,
129.55,
132.71,
135.86,
139.01][tray_count],
PanelType.NorthSouth: [121.63,
123.48,
125.33,
128.48,
131.64,
134.79,
137.94][tray_count],
PanelType.EastWest: [118.28,
120.13,
121.99,
125.14,
128.29,
131.45,
134.60][tray_count],
PanelType.Middle: [117.21,
119.06,
120.92,
124.07,
127.22,
130.38,
133.53][tray_count],
}
return values_per_panel_type.get(panel_type)
def link_tray_thresholds(self, panel_type):
if panel_type == PanelType.Corner or panel_type == PanelType.NorthSouth:

View File

@@ -60,23 +60,38 @@ class DualTilt96CellConstants(object):
return 1, 1
def base_weight(self, panel_type, tray_count):
if panel_type == PanelType.Corner or panel_type == PanelType.NorthSouth:
return [92.58,
94.31,
96.03,
98.33,
100.63,
102.93,
105.23][tray_count]
else:
return [87.11,
88.84,
89.41,
91.71,
94.01,
96.31,
98.61][tray_count]
values_per_panel_type = {
PanelType.Corner: [92.41,
94.26,
96.12,
98.54,
100.97,
103.39,
105.82][tray_count],
PanelType.NorthSouth: [91.63,
93.48,
95.33,
97.76,
100.18,
102.61,
105.03][tray_count],
PanelType.EastWest: [88.00,
89.85,
91.70,
94.13,
96.55,
98.98,
101.40][tray_count],
PanelType.Middle: [87.21,
89.06,
90.92,
93.34,
95.77,
98.19,
100.62][tray_count],
}
return values_per_panel_type.get(panel_type)
def link_tray_thresholds(self, panel_type):
if panel_type == PanelType.Corner or panel_type == PanelType.NorthSouth:
return [7.5, 10, 15]

View File

@@ -60,14 +60,37 @@ class DualTiltPSeriesConstants(object):
return 1, 1
def base_weight(self, panel_type, tray_count):
if panel_type == PanelType.Corner:
return [103.66, 105.96, 107.11, 111.44, 114.62, 117.80, 120.98][tray_count]
elif panel_type == PanelType.NorthSouth:
return [102.58, 104.88, 106.03, 109.21, 112.39, 115.57, 118.75][tray_count]
elif panel_type == PanelType.EastWest:
return [98.19, 100.49, 100.49, 103.67, 106.85, 110.03, 113.21][tray_count]
else:
return [97.11, 99.41, 99.41, 102.59, 105.77, 108.95, 112.13][tray_count]
values_per_panel_type = {
PanelType.Corner: [116.70,
118.55,
120.40,
123.55,
126.71,
129.86,
133.01][tray_count],
PanelType.NorthSouth: [115.63,
117.48,
119.33,
122.48,
125.64,
128.79,
131.94][tray_count],
PanelType.EastWest: [112.28,
114.13,
115.99,
119.14,
122.29,
125.45,
128.60][tray_count],
PanelType.Middle: [111.21,
113.06,
114.92,
118.07,
121.22,
124.38,
127.53][tray_count],
}
return values_per_panel_type.get(panel_type)
def link_tray_thresholds(self, panel_type):
if panel_type == PanelType.Corner or panel_type == PanelType.NorthSouth:

View File

@@ -131,14 +131,10 @@ class SingleTilt128CellConstants(object):
return 1, 1
def base_weight(self, panel_type, tray_count):
if panel_type == PanelType.Corner:
return [71.91, 71.91, 75.09, 78.27][tray_count]
elif panel_type == PanelType.NorthSouth:
return [65.8, 65.8, 68.98, 72.16][tray_count]
elif panel_type == PanelType.EastWest:
return [69.75, 72.05, 75.23, 78.41][tray_count]
else:
return [65.08, 67.38, 70.56, 73.74][tray_count]
return [[68.02, 68.02, 71.17, 74.32],
[65.05, 65.05, 68.20, 71.35],
[65.87, 67.73, 70.88, 74.03],
[63.26, 65.11, 68.26, 71.41]][panel_type.index()][tray_count]
def link_tray_thresholds(self, panel_type):
return [[0, 13.0],

View File

@@ -130,10 +130,10 @@ class SingleTilt96CellConstants(object):
return 1, 1
def base_weight(self, panel_type, tray_count):
return [[54.50, 54.50, 56.80, 59.10],
[49.47, 49.47, 51.77, 54.07],
[53.42, 55.72, 58.02, 60.32],
[48.75, 51.05, 53.35, 55.65]][panel_type.index()][tray_count]
return [[51.10, 51.10, 53.52, 55.95],
[48.13, 48.13, 50.55, 52.98],
[49.24, 51.09, 53.52, 55.94],
[48.33, 50.19, 52.61, 55.04]][panel_type.index()][tray_count]
def link_tray_thresholds(self, panel_type):
return [[0, 12.0],

View File

@@ -130,14 +130,10 @@ class SingleTiltPSeriesConstants(object):
return 1, 1
def base_weight(self, panel_type, tray_count):
if panel_type == PanelType.Corner:
return [66.91, 66.91, 70.09, 73.27][tray_count]
elif panel_type == PanelType.NorthSouth:
return [60.8, 60.8, 63.98, 67.16][tray_count]
elif panel_type == PanelType.EastWest:
return [64.75, 67.05, 70.23, 73.41][tray_count]
else:
return [60.08, 62.38, 65.56, 68.74][tray_count]
return [[65.02, 65.02, 68.17, 71.32],
[62.05, 62.05, 65.20, 68.35],
[62.87, 64.73, 67.88, 71.03],
[60.26, 62.11, 65.26, 68.41]][panel_type.index()][tray_count]
def link_tray_thresholds(self, panel_type):
return [[0, 13.0],

View File

@@ -55,7 +55,7 @@ class SingleTiltParts(object):
def module(self, module_type):
if module_type == ModuleType.Cell96:
rear_skirt_parts = rear_skirt
rear_skirt_parts = rear_skirt_1_1
spoiler_parts = spoiler
else:
rear_skirt_parts = rear_skirt_1_1

View File

@@ -0,0 +1,16 @@
import re
def convert_camel_case_to_snake_case(name):
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
def convert_dict_keys_to_snake_case(a_dict):
new_dict = {}
for old_key in a_dict.keys():
new_key = convert_camel_case_to_snake_case(old_key)
new_dict[new_key] = a_dict[old_key]
return new_dict

View File

@@ -130,8 +130,14 @@ class NodeQuadTree():
self.nodeList = toKeep
# Return a list of all possible nodes that can be near this point
def retrieve(self, nearPoint):
retNodes = list(self.nodeList)
# Optimization `only_quads_related = True`:
# Avoid replicate a large self.nodeList. Get only the other elements.
# Call this in complement of `self.nodeList`
def retrieve(self, nearPoint, only_quads_related=False):
if only_quads_related:
retNodes = []
else:
retNodes = self.nodeList[:] # = list(self.nodeList)
if self.quads[0] is not None:
index = self.getIndex(nearPoint)

View File

@@ -1,22 +1,26 @@
let AutoUpload = () => {
$("#file_upload").change((e) => {
var ten_megabyte_max_upload = 10000000;
$("#error_container_txt").empty();
if(e.currentTarget.files[0].size < ten_megabyte_max_upload){
e.currentTarget.form.submit();
}else{
$("#error_container_txt").append('<span class="error_message centered_error" id="error_message_txt">The system configuration you have uploaded is too large. Try splitting your design into two separate text files and run the tool twice.</span>');
}
var ten_megabyte_max_upload = 10000000;
$("#error_container_txt").empty();
if (e.currentTarget.files[0].size < ten_megabyte_max_upload) {
// $('#spinner-panel').show();
$('#spinner-panel').css('width', '100%'); // Workaround for Safari issue
e.currentTarget.form.submit();
} else {
$("#error_container_txt").append('<span class="error_message centered_error" id="error_message_txt">The system configuration you have uploaded is too large. Try splitting your design into two separate text files and run the tool twice.</span>');
}
});
$("#dxf_upload").change((e) => {
var ten_megabyte_max_upload = 10000000;
$("#error_container_dxf").empty();
if(e.currentTarget.files[0].size < ten_megabyte_max_upload){
e.currentTarget.form.submit();
}else{
$("#error_container_dxf").append('<span class="error_message centered_error" id="error_message_dxf">The system configuration you have uploaded is too large. Try splitting your design into two separate text files and run the tool twice.</span>');
}
var ten_megabyte_max_upload = 10000000;
$("#error_container_dxf").empty();
if (e.currentTarget.files[0].size < ten_megabyte_max_upload) {
// $('#spinner-panel').show();
$('#spinner-panel').css('width', '100%'); // Workaround for Safari issue
e.currentTarget.form.submit();
} else {
$("#error_container_dxf").append('<span class="error_message centered_error" id="error_message_dxf">The system configuration you have uploaded is too large. Try splitting your design into two separate text files and run the tool twice.</span>');
}
});
};

View File

@@ -8,13 +8,16 @@ import AutoUpload from './auto_upload';
$(document).ready(function () {
AutoUpload();
let subarrayDisplay = new SubarrayDisplay();
subarrayDisplay.init($('#current_anchors'), $('#needed_anchors'), $('#subarray_weight'), panel_data);
let arrayVisualization = new ArrayVisualization(panel_data, is_dual_tilt, subarrayDisplay, buildings_coordinates);
arrayVisualization.init();
new ZoomControl(arrayVisualization).init($('#zoom_control'));
new OverlayControl(arrayVisualization).init($('#overlay_control'), $('#legend_container'));
new SeismicControl(arrayVisualization, subarrayDisplay).init($('.seismic_anchor_control'), $("#seismic_save"));
window.arrayVisualization = arrayVisualization;
if (is_csv_available) {
let subarrayDisplay = new SubarrayDisplay();
subarrayDisplay.init($('#current_anchors'), $('#needed_anchors'), $('#subarray_weight'), panel_data);
let arrayVisualization = new ArrayVisualization(panel_data, is_dual_tilt, subarrayDisplay, buildings_coordinates);
arrayVisualization.init();
new ZoomControl(arrayVisualization).init($('#zoom_control'));
new OverlayControl(arrayVisualization).init($('#overlay_control'), $('#legend_container'));
new SeismicControl(arrayVisualization, subarrayDisplay).init($('.seismic_anchor_control'), $("#seismic_save"));
window.arrayVisualization = arrayVisualization;
}
});

22
helix/json_builder.py Normal file
View File

@@ -0,0 +1,22 @@
try:
import ujson as json
except ImportError:
import json
class JsonBuilder:
def build_bom(self, rows):
data = []
headers = ['itemId', 'description', 'quantity']
for row in rows:
d = {}
for i, value in enumerate(row):
d[headers[i]] = value
data.append(d)
return data
def bom_to_json(self, data):
return json.dumps(data)
def build_bom_output(self, rows):
return self.bom_to_json(self.build_bom(rows))

View File

@@ -1,19 +1,23 @@
import os
import requests
from urllib.parse import urlparse
import rollbar
import rollbar.contrib.flask
from flask import Flask, request, make_response, session, render_template, \
redirect, url_for
redirect, url_for, jsonify, flash
from flask import got_request_exception
from flask.ext import assets
from flask_oauthlib.client import OAuth
from webassets.filter import get_filter
from helix.sales_force import tasks as sf_tasks
from helix.Services.doc_gen_service import DocGenService
from helix.Services.dxf_helper import DXFHelper
from helix.Services.dxf_service import DXFService
from helix.api.api import api
from helix.calculators.calculator import Calculator
from helix.constants import redis_constant, sql_constant
from helix.constants.file_validation_error import FileValidationError, FileValidationException
from helix.constants.inverter_type import InverterType
from helix.constants.system_type import SystemType
from helix.csv_builder import CsvBuilder
@@ -25,15 +29,49 @@ from helix.forms.input_form import InputForm, ArrayForm, TestDXFForm
from helix.models.dxf.dxf_error import DXFError, OldDxfFormatException
from helix.presenters.image_presenter import ImagePresenter
from helix.presenters.panel_presenter import ProjectPresenter
from helix.qa_helper import QAScenario
from helix.session_manager import SessionManager
from helix.validators.file_validator import FileValidator, FileType
from helix.validators.subarray_validator import SubarrayValidator
from flask_featureflags import FeatureFlag
import flask_featureflags as feature
import pprint
app = Flask(__name__)
app.register_blueprint(api, url_prefix='/api')
app.secret_key = os.getenv('SECRET_KEY', 'verysecretkey')
app.config['PROFILE'] = True
app.config['FEATURE_FLAGS'] = {
'ff_dummy_feature': True,
'ff_cpp': True
}
FeatureFlag(app) # initializes feature flags
if feature.is_active('ff_dummy_feature'):
app.logger.info('Feature flags working!')
app.logger.info('Current state: ')
app.logger.info(pprint.PrettyPrinter(width=41, compact=True).pformat(app.config['FEATURE_FLAGS']))
# Salesforce integrations
oauth = OAuth()
SFDC_BASE_URL = os.getenv('SFDC_BASE_URL', 'https://test.salesforce.com')
try:
sales_force = oauth.remote_app('sales_force',
consumer_key=os.getenv('SFDC_ACCESS_KEY_ID'),
consumer_secret=os.getenv('SFDC_SECRET_ACCESS_KEY'),
base_url=SFDC_BASE_URL,
request_token_url=None, # OAuth 2
access_token_method='POST', # Sales Force requirement
access_token_url=SFDC_BASE_URL + '/services/oauth2/token',
authorize_url=SFDC_BASE_URL + '/services/oauth2/authorize',
)
except TypeError:
print('Salesforce integration disabled')
sales_force = None
assets_env = assets.Environment(app)
assets_env.init_app(app)
assets_env.load_path = [
@@ -67,6 +105,10 @@ def init_rollbar():
got_request_exception.connect(rollbar.contrib.flask.report_exception, app)
def is_sales_force_session():
return 'sf_session' in session
@app.route("/")
def index():
return redirect(url_for('site_characterization'))
@@ -109,6 +151,9 @@ def test_dxf():
# wizard steps
@app.route("/site_characterization/", methods=['GET', 'POST'])
def site_characterization():
if is_sales_force_session():
return redirect('/summary/')
db_session = sql_constant.sql_session_maker()
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
site_info_form = InputForm()
@@ -136,6 +181,7 @@ def summary():
context['current_step'] = 2
if context['site_data_available']:
context['project_name'] = session_manager.site.project_name
user_values = session_manager.user_values()
calculator = Calculator(user_values, calculate_panel_data=False)
context['wind_zones'] = user_values.system_type().system_constants().wind_zones
@@ -153,10 +199,51 @@ def summary():
else:
context['no_proceed'] = True
if is_sales_force_session():
context['hide_back'] = True
db_session.close()
return render_template('site_summary.html.jinja', context=context)
def handle_dxf_file(session_manager, file_contents, filename=None):
errors = []
user_values = session_manager.user_values()
validator = FileValidator(user_values)
calculator = Calculator(user_values, calculate_panel_data=False)
extension = os.path.splitext(filename)[1] or 'dxf'
extension = extension.split('.')[-1]
validation_error = validator.validate(file_contents, FileType.AuroraDxf, extension=extension)
if validation_error:
error_msg = validation_error.format_error_message()
errors.append(error_msg)
else:
try:
module_constants = user_values.module_system_constants()
# FIXME: parsing a file with many entities is very slow
dxf_data = DXFService().parse(file_contents,
module_constants,
user_values.system_type(),
calculator.L_B() * 12,
DXFHelper(),
SubarrayValidator())
csv = CsvBuilder().build_cad_output(dxf_data['panels'])
session_manager.save_uploaded_file(csv, dxf_file_name=filename)
buildings = dxf_data['buildings']
session_manager.save_buildings_polygons(buildings)
session_manager.save_is_drawing_inaccurate(dxf_data['is_panel_drawing_inaccurate'])
return True, errors
except DXFError as error:
errors.append(error.message)
except OldDxfFormatException as error:
errors.append(error.message)
return False, errors
@app.route("/array_summary/", methods=['GET', 'POST'])
def array_summary():
"""This endpoint allows you to upload a file.
@@ -175,8 +262,7 @@ def array_summary():
validator = FileValidator(session_manager.user_values())
file = request.files['file_upload']
file_contents = validator.obtain_stream(file)
validation_error = validator.validate(file_contents, file,
FileType.Csv)
validation_error = validator.validate(file_contents, FileType.Csv, file)
if not validation_error:
session_manager.save_uploaded_file(file_contents,
cad_file_name=file.filename)
@@ -188,35 +274,15 @@ def array_summary():
elif 'dxf_upload' in request.files and request.files['dxf_upload'].filename:
file = request.files['dxf_upload']
user_values = session_manager.user_values()
calculator = Calculator(user_values, calculate_panel_data=False)
validator = FileValidator(user_values)
file_contents = validator.obtain_stream(file)
validation_error = validator.validate(file_contents, file,
FileType.AuroraDxf)
if validation_error:
error_msg = validation_error.format_error_message()
array_form.dxf_upload.errors.append(error_msg)
success, errors = handle_dxf_file(session_manager, file_contents, filename=file.filename)
if success:
if is_sales_force_session():
session['sf_session']['new_dxf_file'] = True
return redirect(url_for('array_summary'))
else:
try:
module_constants = user_values.module_system_constants()
dxf_data = DXFService().parse(file_contents,
module_constants,
user_values.system_type(),
calculator.L_B() * 12,
DXFHelper(),
SubarrayValidator())
csv = CsvBuilder().build_cad_output(dxf_data['panels'])
session_manager.save_uploaded_file(csv, dxf_file_name=file.filename)
buildings = dxf_data['buildings']
session_manager.save_buildings_polygons(buildings)
session_manager.save_is_drawing_inaccurate(dxf_data['is_panel_drawing_inaccurate'])
return redirect(url_for('array_summary'))
except DXFError as error:
array_form.dxf_upload.errors.append(error.message)
except OldDxfFormatException as error:
array_form.dxf_upload.errors.append(error.message)
array_form.dxf_upload.errors.extend(errors)
elif context['csv_available']:
return redirect(url_for('power_station_configuration'))
else:
@@ -228,39 +294,91 @@ def array_summary():
context['dxf_file_name'] = ''
if context['site_data_available'] and context['csv_available']:
user_values = session_manager.user_values()
calculator = Calculator(user_values)
system_type = user_values.system_type()
module_type = user_values.module_type()
project_presenter = ProjectPresenter(system_type, module_type)
try:
calculator = Calculator(user_values)
system_type = user_values.system_type()
module_type = user_values.module_type()
project_presenter = ProjectPresenter(system_type, module_type)
context['wind_zones'] = system_type.system_constants().wind_zones
context['summary_table'] = calculator.summary_table()
context['minimum_array_sizes'] = calculator.minimum_array_sizes()
context['seismic_anchors'] = calculator.subarray_summary()
context['summary_values'] = calculator.summary_values()
context['wind_zones'] = system_type.system_constants().wind_zones
context['summary_table'] = calculator.summary_table()
context['minimum_array_sizes'] = calculator.minimum_array_sizes()
context['seismic_anchors'] = calculator.subarray_summary()
context['summary_values'] = calculator.summary_values()
panels = calculator.get_computed_csv_columns()
context['panel_array'] = project_presenter.get_panel_data(panels,
calculator.subarrays,
project_presenter.get_max_y(
calculator.buildings_for_drawing,
panels))
context['buildings'] = project_presenter.get_buildings(calculator.buildings_for_drawing)
context['override_form'] = True
context['cad_file_name'] = session_manager.site.cad_file_name or 'Upload System Text Data'
context['dxf_file_name'] = session_manager.site.dxf_file_name or 'Upload System DXF'
context['is_drawing_inaccurate'] = session_manager.user_values().is_panel_drawing_inaccurate()
context['inaccurate_drawing_warning'] = 'The subarrays in this design are not parallel to each other, \
panels = calculator.get_computed_csv_columns()
max_y = project_presenter.get_max_y(calculator.buildings_for_drawing,panels)
context['panel_array'] = project_presenter.get_panel_data(panels,
calculator.subarrays,
max_y)
context['buildings'] = project_presenter.get_buildings(calculator.buildings_for_drawing, max_y)
context['corners'] = project_presenter.get_corners(calculator.buildings)
context['override_form'] = True
context['cad_file_name'] = session_manager.site.cad_file_name or 'Upload System Text Data'
context['dxf_file_name'] = session_manager.site.dxf_file_name or 'Upload System DXF'
context['is_drawing_inaccurate'] = session_manager.user_values().is_panel_drawing_inaccurate()
context['inaccurate_drawing_warning'] = 'The subarrays in this design are not parallel to each other, \
and the graphical representation on this page may not be accurate.'
except FileValidationException as error:
# when calculator is about to enter infinte loop
# it throws an exception - it is supplied wrong data
context['site_data_available'] = False
context['csv_available'] = False
context['no_proceed'] = True
context['cad_file_name'] = ''
context['dxf_file_name'] = ''
context['infinite_loop_detection_message'] = error.message
elif not context['site_data_available']:
context['no_proceed'] = True
if is_sales_force_session() and \
session['sf_session'].get('dxf_link', None) and \
not session['sf_session'].get('dxf_link_loaded', None):
context['javascripts'].append('auto_dxf_load')
db_session.close()
return render_template('array_summary.html.jinja', context=context, form=array_form)
@app.route("/load_dxf/", methods=['GET', 'POST'])
def load_dxf_file():
if (not is_sales_force_session()) or session['sf_session'].get('dxf_link', None) is None:
errors = ['DXF link not found']
response = jsonify({'errors': errors})
response.status_code = 404
return response
dxf_link = session['sf_session'].get('dxf_link', None)
if not dxf_link:
session['sf_session']['dxf_link_loaded'] = True
response = jsonify({'status': 'success', 'messages': ['No DXF file to load from Salesforce.']})
response.status_code = 202
return response
print('Loading DXF file from {}'.format(dxf_link))
response = requests.get(dxf_link, timeout=30)
if response.status_code == 200:
file_contents = response.content.decode('utf-8')
filename = urlparse(dxf_link).path.strip('/')
db_session = sql_constant.sql_session_maker()
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
success, errors = handle_dxf_file(session_manager, file_contents, filename=filename)
session['sf_session']['dxf_link_loaded'] = True
if success:
return jsonify({'status': 'success', 'messages': ['DXF from Salesforce loaded successfully.']})
else:
errors = ['Unable to download DXF file from Salesforce ({})'.format(response.status_code)]
response = jsonify({'errors': errors})
response.status_code = 400
return response
@app.route("/power_station_configuration/", methods=['GET', 'POST'])
def power_station_configuration():
db_session = sql_constant.sql_session_maker()
@@ -339,6 +457,11 @@ def download():
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
context = session_manager.context()
context['current_step'] = 5
if is_sales_force_session():
error, data = session['sf_session'].pop('export_urls', (None, None))
if data is not None:
context['sfdc_export_error'] = error
context['sfdc_export_urls'] = data
db_session.close()
return render_template('download.html.jinja', context=context)
@@ -428,6 +551,141 @@ def helix_documentation():
return render_template('helix_documentation.jinja', context=context)
# Salesforce Integration
@app.route('/sales_force_login')
def sales_force_login():
# To test it locally: https://localhost:8443/sales_force_login?sfid=a3cL00000004QsQIAU
sfid = request.args.get('sfid')
if sfid:
session.clear()
session['sf_session'] = {}
session['sf_session']['sfid'] = sfid
return sales_force.authorize(callback=url_for('sales_force_authorized', _external=True, sfid=sfid), sfid=sfid)
else:
flash('Missing sfid')
return redirect('/')
@app.route('/sales_force_authorized')
def sales_force_authorized():
if not is_sales_force_session():
flash('This is not a Salesforce session')
return redirect('/')
next_url = url_for('summary')
try:
resp = sales_force.authorized_response()
except Exception as e:
flash('Error on authenticating to Salesforce.')
print('Error on authenticating to Salesforce.')
print(str(e))
return sales_force_logout()
if resp is None:
flash('Unknown response from Salesforce.')
print('Unknown response from Salesforce.')
return redirect(next_url)
print('New Salesforce - OAuth2 login')
db_session = sql_constant.sql_session_maker()
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
session['sf_session']['access_token'] = resp['access_token']
helix_session_id = session['id']
access_token = session['sf_session']['access_token']
sfid = session['sf_session']['sfid']
try:
data, status_code = sf_tasks.get_site_characterization_from_sales_force(helix_session_id, access_token, sfid)
if data and status_code == 200:
session['sf_session']['dxf_link'] = data['dxf_link']
session_manager.save_form_submission(data)
flash('Connected to Salesforce.')
return redirect(next_url)
else:
flash('Unable to get site characterization from Salesforce. ({})'.format(status_code))
return sales_force_logout()
except Exception as e:
print(str(e))
flash('Unable to get site characterization from Salesforce.')
return sales_force_logout()
@app.route("/export-sfdc")
def export_sfdc():
if not is_sales_force_session():
flash('This is not a Salesforce session')
return redirect('/')
db_session = sql_constant.sql_session_maker()
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
helix_session_id = session_manager.session['id']
sf_session = session['sf_session']
access_token = sf_session['access_token']
sfid = sf_session['sfid']
new_dxf_file = sf_session.get('new_dxf_file', False)
error, data = sf_tasks.export_to_sfdc(helix_session_id, access_token, sfid, new_dxf_file=new_dxf_file, qa_test=session.get('qa_test', None))
data.pop('bom', None)
data['dxfUrlFromSF'] = sf_session['dxf_link']
sf_session['export_urls'] = (error, data)
db_session.close()
return redirect('/download')
if sales_force:
@sales_force.tokengetter
def get_sales_force_token(token=None):
return session.get('sf_session', {}).get('access_token', None)
@app.route('/sales_force_logout')
def sales_force_logout():
session.pop('sf_session', None)
return redirect('/')
# End of Salesforce Integration
# QA scenario simulations
@app.route('/qa')
def qa_simulations():
test = request.args.get('test', None)
bad_request = None
if not test:
bad_request = 'Missing `test` parameter.'
elif test not in QAScenario.all():
bad_request = 'Unknown test scenario: <b>' + test + '</b>.'
if bad_request:
tests = ''.join(['<li><a href="/qa?test=' + s + '">' + s + '</a></li>' for s in QAScenario.all()])
tests = '<ul>' + tests + '</ul>'
return bad_request + tests, 400
if test.startswith('sf_') and 'sf_session' not in session:
return 'Please, connect to Salesforce first.', 400
url = None
if test == QAScenario.SF_RESET_ALL.value:
session['sf_session']['dxf_link_loaded'] = False
session['qa_test'] = None
url = request.url_root
elif test == QAScenario.SF_NO_DXF.value:
session['sf_session']['dxf_link'] = None
session['sf_session']['dxf_link_loaded'] = False
url = request.url_root + 'array_summary/'
elif test == QAScenario.SF_VALID_DXF.value:
session['sf_session']['dxf_link'] = 'https://sunpower-test-dgplatform-spectrum.s3.amazonaws.com/a5FL00000008gKcMAI-DXF.dxf'
session['sf_session']['dxf_link_loaded'] = False
url = request.url_root + 'array_summary/'
elif test in (QAScenario.SF_ERROR_UPLOAD_S3.value, QAScenario.SF_ERROR_NOTIFY_SF.value):
session['qa_test'] = test
url = request.url_root + 'export-sfdc'
msg = '<b>' + test + '</b> test enabled.'
if url:
msg += ' Access: <a href="' + url + '">' + url + '</a> to check.'
return msg, 200
@app.template_filter('format_number')
def format_number(number):
return "{:,g}".format(number)
@@ -457,8 +715,13 @@ def enum():
def main():
host = '0.0.0.0'
port = int(os.getenv('PORT', 5000))
app.run(host=host, port=port, debug=bool(os.getenv("FLASK_DEBUG", False)))
debug = bool(os.getenv("FLASK_DEBUG", False))
if os.getenv('FLASK_DEBUG_SSL', None):
port = int(os.getenv('PORT', 8443))
app.run(host=host, port=port, debug=debug, ssl_context='adhoc')
else:
port = int(os.getenv('PORT', 5000))
app.run(host=host, port=port, debug=debug)
@app.route("/fail-test")

13
helix/models/corner.py Normal file
View File

@@ -0,0 +1,13 @@
class Corner(object):
def __init__(self, x, y, length_ccw, length_cw, angle):
self.x = x
self.y = y
self.length_ccw = length_ccw
self.length_cw = length_cw
self.angle = angle
@property
def dictionary(self):
return {"x": self.x, "y": self.y, "length_ccw": self.length_ccw, "length_cw": self.length_cw, "angle": self.angle}

View File

@@ -25,6 +25,7 @@ class GraphNodeStore(list):
dy = node.coordinate.y - coordinate.y
return dx * dx + dy * dy
# FIXME: This is slow if called thousands of times. Do not add any overhead in this method.
def find_coordinate(self, coordinate):
# create and populate the quadtree on first request
if self.quadTree is None:
@@ -33,9 +34,15 @@ class GraphNodeStore(list):
self.quadTree.insert(node)
del self[:]
possibilities = self.quadTree.retrieve(coordinate)
variance_square = self.variance ** 2
# Optimization: avoid creating a copy of the big list `self.quadTree.nodeList`
# Old: possibilities = self.quadTree.retrieve(coordinate)
possibilities = self.quadTree.nodeList
for node in possibilities:
if self.distance_squared(node, coordinate) <= self.variance ** 2:
if self.distance_squared(node, coordinate) <= variance_square:
return node
else:
return None
more_possibilities = self.quadTree.retrieve(coordinate, only_quads_related=True)
for node in more_possibilities:
if self.distance_squared(node, coordinate) <= variance_square:
return node
return None

View File

@@ -1,7 +1,8 @@
import math
from helix.models.coordinate import Coordinate
import sys
from math import sqrt, atan, degrees
from helix.models.corner import Corner
from helix.constants.global_constants import max_corner_angle
import flask_featureflags as feature
class ProjectPresenter(object):
def __init__(self, system_type, module_type):
@@ -21,8 +22,6 @@ class ProjectPresenter(object):
spacing_x, spacing_y = module_constants.presenter_spacing
wind_zones = system_constants.wind_zones
for panel in panels:
subarray = [x for x in subarrays if x.subarray_number == panel.subarray][0]
origin = subarray.origin
@@ -53,9 +52,10 @@ class ProjectPresenter(object):
self.offset = height
for panel in table_data:
panel['y'] = height - panel['y'] + first_cell
return table_data
def get_buildings(self, buildings):
def get_buildings(self, buildings, max_y):
if self.offset is None:
self.offset = 0
@@ -70,11 +70,51 @@ class ProjectPresenter(object):
# origin = self.find_origin(building)
for point in building:
point.x = point.x * spacing_x
point.y = point.y * spacing_y
point.y = abs((point.y * spacing_y) - max_y)
presentable_building.append(point.__dict__)
return result
def get_corners(self, buildings):
result = []
if feature.is_active('ff_cpp'):
for building in buildings:
presentable_building = []
result.append(presentable_building)
previous_corner = building[-1]
for i, corner in enumerate(building):
if (i+1 == len(building)):
next_corner = building[0]
else:
next_corner = building[i+1]
#x coordinate is stored as first element of corner variable
#y coordinate is stored as second element of corner variable
corner_length_ccw = sqrt((next_corner[0] - corner[0])**2 + (next_corner[1] - corner[1])**2)
corner_length_cw = sqrt((previous_corner[0] - corner[0])**2 + (previous_corner[1] - corner[1])**2)
k1 = float('Inf') if (corner[0]==previous_corner[0]) else (corner[1] - previous_corner[1]) / (corner[0] - previous_corner[0])
k2 = float('Inf') if (corner[0]==next_corner[0]) else (next_corner[1] - corner[1]) / (next_corner[0] - corner[0])
theta2 = degrees(atan(k2))
theta1 = degrees(atan(k1)) - theta2
if (theta1 < 0):
if (k1 < 0 and k2 < 0):
theta1 = 360 + theta1
elif (k1 <= 0 and k2 >= 0):
theta1 = 180 + theta1
else:
if (k1 > 0 and k2 > 0):
theta1 = 180 + theta1
if (theta1 < max_corner_angle):
presentable_building.append(Corner(corner[0], corner[1], corner_length_ccw,corner_length_cw, theta1).__dict__)
previous_corner = corner
return result
def get_max_y(self,buildings, panels):
module_constants = self.system_type.module_constants(self.module_type)

20
helix/qa_helper.py Normal file
View File

@@ -0,0 +1,20 @@
from enum import Enum
class QAException(Exception):
pass
class QAScenario(Enum):
SF_NO_DXF = 'sf_no_dxf'
SF_ERROR_UPLOAD_S3 = 'sf_error_upload_s3'
SF_ERROR_NOTIFY_SF = 'sf_error_notify_sf'
SF_VALID_DXF = 'sf_valid_dxf'
SF_RESET_ALL = 'sf_reset_all'
def __repr__(self):
return '<%s.%s>' % (self.__class__.__name__, self.name)
@classmethod
def all(cls):
return [s.value for s in list(cls)]

View File

138
helix/sales_force/tasks.py Normal file
View File

@@ -0,0 +1,138 @@
import io
import os
import requests
from helix.calculators.calculator import Calculator
from helix.constants import redis_constant, sql_constant
from helix.constants.system_type import SystemType
from helix.csv_builder import CsvBuilder
from helix.doc_gen_params_builder import DocGenParamsBuilder
from helix.helpers.camel_case import convert_dict_keys_to_snake_case
from helix.json_builder import JsonBuilder
from helix.presenters.image_presenter import ImagePresenter
from helix.qa_helper import QAScenario, QAException
from helix.Services.doc_gen_service import DocGenService
from helix.Services.s3_helper import s3_upload
from helix.session_manager import SessionManager
def get_site_characterization_from_sales_force(helix_session_id, access_token, sfid):
SFDC_API_URL = os.getenv('SFDC_API_URL', 'https://sunpower--qa.cs8.my.salesforce.com')
url = SFDC_API_URL + '/services/apexrest/v1/HelixRoofDetails'
headers = {'Authorization': 'Bearer {}'.format(access_token)}
params = {'sfid': sfid, 'helix_session_id': helix_session_id}
result = requests.get(url, headers=headers, params=params, timeout=30)
if result.status_code == 200:
data = result.json()
if data:
data = convert_sales_force_data_format_to_helix(data)
return data, 200
else:
print('Error while getting data from Salesforce ({})'.format(result.status_code))
print(url, params)
print(result.content)
# print(result.request.headers)
return None, result.status_code
def convert_sales_force_data_format_to_helix(data):
data = convert_dict_keys_to_snake_case(data)
if data['system_type'] in ('Single-Tilt', 'Single Tilt'):
data['system_type'] = SystemType.singleTilt.value
elif data['system_type'] == ('Dual-Tilt', 'Dual Tilt'):
data['system_type'] = SystemType.dualTilt.value
if data['anchor_type'] == 'OMG Power Grip Plus':
data['anchor_type'] = 'OMG PowerGrip Plus'
data['ballast_block_weight'] = data['ballast_weight']
data['max_system_pressure'] = data['max_psf'] = data['system_pressure']
return data
# data['spectral_response_acceleration']
def export_to_sfdc(helix_session_id, access_token, sfid, new_dxf_file=False, qa_test=None):
step = 'Exporting to Salesforce'
try:
# 1. Load User Values
step = 'Loading User Values'
session = {'id': helix_session_id}
db_session = sql_constant.sql_session_maker()
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
user_values = session_manager.user_values()
calculator = Calculator(user_values)
# 2. Generate BOM CSV file
step = 'Generating BOM'
bom = calculator.compute_bom()
csv_file = CsvBuilder().build_bom_output(bom)
# 2.1 Generate BOM CSV file
step = 'Generating BOM as JSON'
json_builder = JsonBuilder()
bom_list = json_builder.build_bom(bom)
bom_list_json = json_builder.bom_to_json(bom_list)
# 3. Generate DOCUMENTATION PDF file
step = 'Generating Documentation'
image_presenter = ImagePresenter(user_values.system_type(), user_values.module_type())
doc_gen_service = DocGenService(requests, DocGenParamsBuilder(user_values, user_values.system_type(), calculator, image_presenter))
document = doc_gen_service.generate() # Call external service
# 4. Get Uploaded DXF file in the Helix system
step = 'Loading uploaded DXF'
dxf_contents = session_manager.site.dxf_file or session_manager.site.cad_file
# dxf_filename = session_manager.site.dxf_file_name
# 5. Save CSV/PDF/DXF files into AWS-S3
step = 'Uploading to S3'
if qa_test == QAScenario.SF_ERROR_UPLOAD_S3.value:
raise QAException(qa_test)
bom_csv_url = s3_upload(io.StringIO(csv_file), filename=sfid + '-csv.csv')
doc_url = s3_upload(io.BytesIO(document), filename=sfid + '-pdf.pdf')
bom_json_url = s3_upload(io.StringIO(bom_list_json), filename=sfid + '-json.json')
# Send only if the file DXF file was uploaded in Helix
if new_dxf_file and dxf_contents: # Optional
dxf_url = s3_upload(io.StringIO(dxf_contents), filename=sfid + '-dxf.dxf')
else:
dxf_url = None
# 6. Notify SFDC system with an API request
step = 'Notifying Salesforce'
if qa_test == QAScenario.SF_ERROR_NOTIFY_SF.value:
raise QAException(qa_test)
data = {
'dxfUrl': dxf_url,
'bomCsvUrl': bom_csv_url,
'documentationUrl': doc_url,
'bom': bom_list,
'helixSessionId': helix_session_id,
'sfid': sfid,
}
headers = {'Authorization': 'Bearer {}'.format(access_token)}
SFDC_API_URL = os.getenv('SFDC_API_URL', 'https://sunpower--qa.cs8.my.salesforce.com')
url = SFDC_API_URL + '/services/apexrest/v1/HelixRoofDetails'
result = requests.post(url, headers=headers, json=data, timeout=30)
# 7. Internal logs
if result.status_code <= 299:
print('Salesforce notified successfully for session {}.'.format(helix_session_id))
# print(result.content)
error = None
else:
error = 'Helix Calculator was not able to notify Salesforce ({})'.format(result.status_code)
print(error)
print(result.content)
# Only Helix need this information for audit
data['bomJsonUrl'] = bom_json_url
db_session.close()
return error, data
except Exception as e:
print('Error while {} for session {}'.format(step, helix_session_id))
error = 'Error while {}'.format(step)
return error, {}

View File

@@ -72,6 +72,12 @@ i.icon-info-circled {
margin: 0 10px;
}
.info_message {
color: black;
padding-left: 5px;
margin: 0 auto;
}
.error_message {
color: red;
padding-left: 5px;
@@ -151,7 +157,7 @@ a.back {
a {
text-decoration: none;
width: auto;
margin: 0 5px;
margin: 2px 5px;
}
.button {

View File

@@ -59,3 +59,35 @@ h1 {
.spacer {
flex-grow: 1;
}
.spinner-panel {
position: fixed;
margin: 0 auto;
top: 0;
left: 0;
width: 0; /* It will be updated to 100% in JS. Workaround for Safari issue with display:none; */
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1;
display: flex;
flex-flow: column nowrap;
align-items: center;
justify-content: center;
color: #fff;
font-weight: bold;
font-size: 24px;
overflow: hidden;
}
.msg-container {
background-color: $off-white;
border: 1px solid $medium-border-color;
margin: 20px;
padding: 10px;
padding-left: 30px;
}
.msg-container li {
padding-left: 0 !important;
}

View File

@@ -81,17 +81,17 @@ class SessionManager(object):
building_height=form_data.get('building_height', 0),
building_width=form_data.get('building_width', 0),
building_length=form_data.get('building_length', 0),
parapet_height=form_data.get('building_parapet_height', 0),
parapet_height=form_data.get('parapet_height', form_data.get('building_parapet_height', 0)),
wind_speed=form_data.get('wind_speed', 0),
exposure_category=form_data.get('exposure_category', 'C'),
exposure_transition_distance=form_data.get('exposure_category_transition_distance', 0),
ballast_block_weight=form_data.get('ballast_block_weight', 0),
max_psf=form_data.get('max_system_pressure', 0),
ballast_block_weight=form_data.get('ballast_block_weight', form_data.get('ballast_weight', 0)),
max_psf=form_data.get('system_pressure', form_data.get('max_system_pressure', form_data.get('max_psf', 0))),
system_type=form_data.get('system_type', SystemType.dualTilt.value),
module_type=form_data.get('module_type', ModuleType.Cell96.value),
anchor_type=form_data.get('anchor_type', AnchorType.OMG_PowerGrip_Plus.value),
spectral_response=form_data.get('design_spectral_response', 1),
seismic_importance_factor=form_data.get('importance_factor', 1)
spectral_response=form_data.get('design_spectral_response', form_data.get('spectral_response_acceleration', form_data.get('spectral_response', 1))),
seismic_importance_factor=form_data.get('seismic_importance_factor', form_data.get('importance_factor', 1))
)
self.db_session.add(self.site)
self.db_session.commit()

85
helix/static/css/animation.css Executable file
View File

@@ -0,0 +1,85 @@
/*
Animation example, for spinners
*/
.animate-spin {
-moz-animation: spin 2s infinite linear;
-o-animation: spin 2s infinite linear;
-webkit-animation: spin 2s infinite linear;
animation: spin 2s infinite linear;
display: inline-block;
}
@-moz-keyframes spin {
0% {
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(359deg);
-o-transform: rotate(359deg);
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@-webkit-keyframes spin {
0% {
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(359deg);
-o-transform: rotate(359deg);
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@-o-keyframes spin {
0% {
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(359deg);
-o-transform: rotate(359deg);
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@-ms-keyframes spin {
0% {
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(359deg);
-o-transform: rotate(359deg);
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes spin {
0% {
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(359deg);
-o-transform: rotate(359deg);
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}

View File

@@ -1,11 +1,11 @@
@font-face {
font-family: 'fontello';
src: url('../font/fontello.eot?10976371');
src: url('../font/fontello.eot?10976371#iefix') format('embedded-opentype'),
url('../font/fontello.woff2?10976371') format('woff2'),
url('../font/fontello.woff?10976371') format('woff'),
url('../font/fontello.ttf?10976371') format('truetype'),
url('../font/fontello.svg?10976371#fontello') format('svg');
src: url('../font/fontello.eot?24283821');
src: url('../font/fontello.eot?24283821#iefix') format('embedded-opentype'),
url('../font/fontello.woff2?24283821') format('woff2'),
url('../font/fontello.woff?24283821') format('woff'),
url('../font/fontello.ttf?24283821') format('truetype'),
url('../font/fontello.svg?24283821#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
@@ -15,7 +15,7 @@
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'fontello';
src: url('../font/fontello.svg?10976371#fontello') format('svg');
src: url('../font/fontello.svg?24283821#fontello') format('svg');
}
}
*/
@@ -69,4 +69,5 @@
.icon-info:before { content: '\e80b'; } /* '' */
.icon-close:before { content: '\e80c'; } /* '' */
.icon-sunpower-logo:before { content: '\e80d'; } /* '' */
.icon-upload-cloud:before { content: '\e80e'; } /* '' */
.icon-upload-cloud:before { content: '\e80e'; } /* '' */
.icon-spin6:before { content: '\e839'; } /* '' */

View File

@@ -616,13 +616,20 @@ i.icon-info-circled {
}
/* line 75 */
.info_message {
color: black;
padding-left: 5px;
margin: 0 auto;
}
/* line 81 */
.error_message {
color: red;
padding-left: 5px;
margin: 0 auto;
}
/* line 81 */
/* line 87 */
input, select {
width: calc(100% - 20px);
padding: 10px;
@@ -633,7 +640,7 @@ input, select {
letter-spacing: 2px;
}
/* line 91 */
/* line 97 */
select {
-webkit-appearance: none;
-moz-appearance: none;
@@ -641,14 +648,14 @@ select {
background: #F4F4F4 url("https://cdn1.iconfinder.com/data/icons/cc_mono_icon_set/blacks/16x16/br_down.png") no-repeat calc(100% - 15px);
}
/* line 98 */
/* line 104 */
.submit, .download {
padding: 20px;
text-align: center;
border-top: 1px solid #EAEAEA;
}
/* line 104 */
/* line 110 */
.button, button {
background: #5199F5;
color: white;
@@ -660,7 +667,7 @@ select {
letter-spacing: 2px;
}
/* line 115 */
/* line 121 */
.navigation_buttons {
display: flex;
justify-content: flex-end;
@@ -668,18 +675,18 @@ select {
border-bottom: 1px solid #EAEAEA;
padding: 20px;
}
/* line 122 */
/* line 128 */
.navigation_buttons a {
text-decoration: none;
width: auto;
margin: 0 20px;
}
/* line 128 */
/* line 134 */
.navigation_buttons .button {
width: auto;
}
/* line 133 */
/* line 139 */
.back {
border: 2px solid #5199F5;
color: #5199F5;
@@ -687,12 +694,12 @@ select {
margin-left: 10px;
}
/* line 140 */
/* line 146 */
a.back {
text-decoration: none;
}
/* line 144 */
/* line 150 */
.sample_images {
display: flex;
justify-content: flex-end;
@@ -700,18 +707,18 @@ a.back {
border-bottom: 1px solid #EAEAEA;
padding: 15px;
}
/* line 151 */
/* line 157 */
.sample_images a {
text-decoration: none;
width: auto;
margin: 2px 5px;
}
/* line 157 */
/* line 163 */
.sample_images .button {
width: auto;
}
/* line 162 */
/* line 168 */
.image_button, button {
background: #5199F5;
color: white;
@@ -832,6 +839,41 @@ h1 {
flex-grow: 1;
}
/* line 63 */
.spinner-panel {
position: fixed;
margin: 0 auto;
top: 0;
left: 0;
width: 0;
/* It will be updated to 100% in JS. Workaround for Safari issue with display:none; */
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1;
display: flex;
flex-flow: column nowrap;
align-items: center;
justify-content: center;
color: #fff;
font-weight: bold;
font-size: 24px;
overflow: hidden;
}
/* line 84 */
.msg-container {
background-color: #FDFDFD;
border: 1px solid #D8D8D8;
margin: 20px;
padding: 10px;
padding-left: 30px;
}
/* line 91 */
.msg-container li {
padding-left: 0 !important;
}
/* line 3 */
.navigation_header {
display: flex;

Binary file not shown.

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2016 by original authors @ fontello.com</metadata>
<metadata>Copyright (C) 2017 by original authors @ fontello.com</metadata>
<defs>
<font id="fontello" horiz-adv-x="1000" >
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
@@ -35,6 +35,8 @@
<glyph glyph-name="sunpower-logo" unicode="&#xe80d;" d="M466 633c-35 61-77 96-152 96-78 0-146-56-146-137 0-76 78-111 138-138l59-26c114-50 211-107 211-248 0-154-124-272-277-272-141 0-247 91-275 228l96 27c13-90 82-164 177-164 94 0 181 73 181 172 0 103-81 138-163 175l-53 24c-103 47-192 99-192 225 0 137 116 225 247 225 98 0 181-50 228-137l-79-50 0 0z m537-353c0-66-3-140 38-197 39-53 114-84 178-84 63 0 132 29 173 79 47 56 43 133 43 202l0 525 99 0 0-552c0-96-7-172-78-244-60-66-148-101-237-101-83 0-168 32-228 90-77 74-86 154-86 255l0 552 98 0 0-525 0 0z m874 562l688-720 0 683 98 0 0-922-688 720 0-680-98 0 0 919z m1255-128l36 0c114 0 230-11 230-155 0-126-92-159-201-159l-65 0 0 314 0 0z m0-405l71 0c74 0 150 9 209 58 55 46 84 119 84 190 0 78-33 155-99 201-64 44-143 47-219 47l-145 0 0-882 99 0 0 386 0 0z m1949-165l285 706 285-706 232 661 106 0-338-927-285 709-285-709-338 927 106 0 232-661z m1128 661l457 0 0-91-359 0 0-262 348 0 0-91-348 0 0-347 359 0 0-91-457 0 0 882z m886-91l29 0c118 0 225-14 225-159 0-137-113-158-224-158l-30 0 0 317 0 0z m0-404l24 0 267-387 120 0-280 395c136 12 221 108 221 244 0 198-156 243-323 243l-127 0 0-882 98 0 0 387 0 0z m-2487 53c0-257-204-453-463-453-253 0-456 205-456 451 0 249 201 454 456 454 259 0 463-195 463-452m-816-2c0-188 150-354 348-354 204 0 363 154 363 354 0 203-157 357-363 357-200 0-348-166-348-357m3755 309c0 24 6 46 18 67 12 21 29 38 50 50 21 12 43 18 67 18 24 0 47-6 68-18 21-12 37-29 49-49 12-21 18-44 18-68 0-24-6-46-17-66-12-21-28-38-49-50-21-13-44-19-69-19-24 0-47 6-68 19-21 12-37 29-49 49-12 21-18 43-18 67l0 0z m17 0c0-21 5-41 16-59 11-18 25-33 43-43 18-11 38-16 59-16 22 0 41 5 59 16 19 10 33 25 44 43 10 18 16 38 16 59 0 21-6 40-16 58-10 18-24 33-43 44-18 11-38 16-60 16-21 0-40-5-59-16-18-10-32-25-43-43-11-19-16-38-16-59l0 0z m166 33c0-9-2-17-6-25-5-7-12-12-20-17l42-70-22 0-37 65-30 0 0-65-18 0 0 158 37 0c18 0 31-4 40-11 10-8 14-19 14-35l0 0z m-73-33l20 0c11 0 19 3 25 8 7 6 10 14 10 25 0 20-12 30-36 30l-19 0 0-63 0 0z" horiz-adv-x="7828" />
<glyph glyph-name="upload-cloud" unicode="&#xe80e;" d="M760 494q100 0 170-68t70-166-70-166-170-68l-190 0 0 190 106 0-176 230-174-230 104 0 0-190-248 0q-74 0-128 52t-54 124q0 74 53 126t129 52q14 0 20-2-2 12-2 38 0 108 78 184t188 76q90 0 160-52t94-134q28 4 40 4z" horiz-adv-x="1000" />
<glyph glyph-name="spin6" unicode="&#xe839;" d="M855 9c-189-190-520-172-705 13-190 190-200 494-28 695 11 13 21 26 35 34 36 23 85 18 117-13 30-31 35-76 16-112-5-9-9-15-16-22-140-151-145-379-8-516 153-153 407-121 542 34 106 122 142 297 77 451-83 198-305 291-510 222l0 1c236 82 492-24 588-252 71-167 37-355-72-493-11-15-23-29-36-42z" horiz-adv-x="1000" />
</font>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -73,15 +73,18 @@
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
$(document).ready(function () {
(0, _auto_upload2.default)();
var subarrayDisplay = new _subarray_display2.default();
subarrayDisplay.init($('#current_anchors'), $('#needed_anchors'), $('#subarray_weight'), panel_data);
var arrayVisualization = new _array_visualization2.default(panel_data, is_dual_tilt, subarrayDisplay, buildings_coordinates);
arrayVisualization.init();
new _zoom_control2.default(arrayVisualization).init($('#zoom_control'));
new _overlay_control2.default(arrayVisualization).init($('#overlay_control'), $('#legend_container'));
new _seismic_control2.default(arrayVisualization, subarrayDisplay).init($('.seismic_anchor_control'), $("#seismic_save"));
window.arrayVisualization = arrayVisualization;
(0, _auto_upload2.default)();
if (is_csv_available) {
var subarrayDisplay = new _subarray_display2.default();
subarrayDisplay.init($('#current_anchors'), $('#needed_anchors'), $('#subarray_weight'), panel_data);
var arrayVisualization = new _array_visualization2.default(panel_data, is_dual_tilt, subarrayDisplay, buildings_coordinates);
arrayVisualization.init();
new _zoom_control2.default(arrayVisualization).init($('#zoom_control'));
new _overlay_control2.default(arrayVisualization).init($('#overlay_control'), $('#legend_container'));
new _seismic_control2.default(arrayVisualization, subarrayDisplay).init($('.seismic_anchor_control'), $("#seismic_save"));
window.arrayVisualization = arrayVisualization;
}
});
/***/ }),
@@ -24730,28 +24733,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
value: true
});
var AutoUpload = function AutoUpload() {
$("#file_upload").change(function (e) {
var ten_megabyte_max_upload = 10000000;
$("#error_container_txt").empty();
if (e.currentTarget.files[0].size < ten_megabyte_max_upload) {
e.currentTarget.form.submit();
} else {
$("#error_container_txt").append('<span class="error_message centered_error" id="error_message_txt">The system configuration you have uploaded is too large. Try splitting your design into two separate text files and run the tool twice.</span>');
}
});
$("#file_upload").change(function (e) {
var ten_megabyte_max_upload = 10000000;
$("#error_container_txt").empty();
if (e.currentTarget.files[0].size < ten_megabyte_max_upload) {
// $('#spinner-panel').show();
$('#spinner-panel').css('width', '100%'); // Workaround for Safari issue
e.currentTarget.form.submit();
} else {
$("#error_container_txt").append('<span class="error_message centered_error" id="error_message_txt">The system configuration you have uploaded is too large. Try splitting your design into two separate text files and run the tool twice.</span>');
}
});
$("#dxf_upload").change(function (e) {
var ten_megabyte_max_upload = 10000000;
$("#error_container_dxf").empty();
if (e.currentTarget.files[0].size < ten_megabyte_max_upload) {
e.currentTarget.form.submit();
} else {
$("#error_container_dxf").append('<span class="error_message centered_error" id="error_message_dxf">The system configuration you have uploaded is too large. Try splitting your design into two separate text files and run the tool twice.</span>');
}
});
$("#dxf_upload").change(function (e) {
var ten_megabyte_max_upload = 10000000;
$("#error_container_dxf").empty();
if (e.currentTarget.files[0].size < ten_megabyte_max_upload) {
// $('#spinner-panel').show();
$('#spinner-panel').css('width', '100%'); // Workaround for Safari issue
e.currentTarget.form.submit();
} else {
$("#error_container_dxf").append('<span class="error_message centered_error" id="error_message_dxf">The system configuration you have uploaded is too large. Try splitting your design into two separate text files and run the tool twice.</span>');
}
});
};
exports.default = AutoUpload;

View File

@@ -0,0 +1,30 @@
"use strict";
$(document).ready(function () {
$("#sf_msg_container").empty();
$('#sf-spinner-panel').css('width', '100%'); // Workaround for Safari issue
$.ajax({
type: 'POST',
url: '/load_dxf/'
}).done(function(data, textStatus, jqXHR) { // 200 or 202
if (data && data.messages) {
data.messages.forEach((msg) => {
$("#sf_msg_container").append('<li class="info_message">' + msg + '</li>');
})
$("#sf_msg_container").show();
}
if (jqXHR.status == 200) {
setTimeout(function() { window.location.reload() }, 1000);
}
}).fail(function(jqXHR, textStatus, errorThrown) { // 400, 404, 5xx
// console.log(jqXHR, textStatus, errorThrown)
if (jqXHR.responseJSON && jqXHR.responseJSON.errors) {
jqXHR.responseJSON.errors.forEach((msg) => {
$("#sf_msg_container").append('<li class="error_message">' + msg + '</li>');
})
$("#sf_msg_container").show();
}
}).always(function(r) {
$('#sf-spinner-panel').css('width', '0%'); // Workaround for Safari issue
});
});

View File

@@ -73,7 +73,7 @@
}
});
inverterStringsPerInverter.val(inverterStringsPerInverterValue);
inverterStringsPerInverterForm.css('display', 'inherit');
inverterStringsPerInverterForm.css('display', 'block');
} else {
inverterStringsPerInverter.append($('<option selected value="0"></option>'));
inverterStringsPerInverterForm.css('display', 'none');
@@ -160,7 +160,8 @@
if (data) {
$('#standalone_inverter_id').val(uuid);
$('#standalone_ac_run_length').val(data['ac_run_length']);
$('#attachment-point').val(data['attachment_point'][0]);
var attach_point = data['attachment_point'][1];
$('#attachment_point option[value=' + attach_point + ']').attr('selected','selected');
fillInverterData(data, '#inverter');

View File

@@ -1,6 +1,35 @@
{% extends "layout.html.jinja" %}
{% set title = "Helix Calculator" %}
{% block contents %}
<script>
{%if context['corners'] and 'ff_cpp' is active_feature %}
var corners_data = {{ context['corners']|tojson}};
if (corners_data){
console.log("Corners data :");
console.log(corners_data);
}
{%endif%}
</script>
<script>
var is_csv_available = {{ context['csv_available']|tojson|safe }};
</script>
<div id="spinner-panel" class="spinner-panel">
<p>Uploading files. Please wait, this may take a while.</p>
<i class="icon-spin6 animate-spin"></i>
</div>
{% if 'sf_session' in session %}
<ul id="sf_msg_container" class="msg-container" style="display:none;">
</ul>
<div id="sf-spinner-panel" class="spinner-panel">
<p>Loading DXF file from Salesforce. Please wait, this may take a while.</p>
<i class="icon-spin6 animate-spin"></i>
</div>
{% endif %}
{% if not context['csv_available'] %}
<form action="" method="post" enctype="multipart/form-data">
{{ form.csrf_token }}
@@ -24,6 +53,11 @@
<span id="error_message_txt" class="error_message centered_error">{{ field.errors[0] }}</span>
{% endif %}
{% endfor %}
{% if context['infinite_loop_detection_message'] %}
<span id="error_message_txt_inf_loop" class="error_message centered_error">{{ context['infinite_loop_detection_message'] }}</span>
{% endif %}
</div>
</div>

View File

@@ -1,6 +1,25 @@
{% extends "layout.html.jinja" %}
{% set title = "Helix Calculator" %}
{% block contents %}
{% if 'sfdc_export_urls' in context %}
{% if context['sfdc_export_error'] %}
<div class="summary_warning" style="margin-top: 20px;">{{ context['sfdc_export_error'] }}</div>
{% else %}
<div class="msg-container">Documents exported to Salesforce successfully.</div>
{% endif %}
<ul class="msg-container">
{% if context['sfdc_export_urls']['dxfUrl'] %}
<li><a href="{{ context['sfdc_export_urls']['dxfUrl'] }}">DXF file</a> (uploaded on Helix)</li>
{% else %}
<li><a href="{{ context['sfdc_export_urls']['dxfUrlFromSF'] }}">DXF file</a> (uploaded on Salesforce)</li>
{% endif %}
<li><a href="{{ context['sfdc_export_urls']['documentationUrl'] }}">Documentation file</a></li>
<li><a href="{{ context['sfdc_export_urls']['bomCsvUrl'] }}">BOM CSV file</a></li>
<li><a href="{{ context['sfdc_export_urls']['bomJsonUrl'] }}">BOM JSON file</a></li>
</ul>
{% endif %}
<div class="form_section">
<h3>Download</h3>
</div>
@@ -20,6 +39,14 @@
<button>Download BOM (.txt)</button>
</a>
</div>
{% if 'sf_session' in session %}
<div class="download">
<a href="/export-sfdc">
<button>Export documents to Salesforce</button>
</a>
</div>
{% endif %}
{% else %}
Please complete previous steps first!
{% endif %}

View File

@@ -15,8 +15,7 @@
<div>Inverter Brand</div>
</div>
<form name="inverter_brand_form" action="" method="post" enctype="multipart/form-data">
<div id="inverter_brand_form"
class="form_section ebom_form">
<div id="inverter_brand_form" class="form_section ebom_form">
{{ inverter_brand_form.csrf_token }}
{% for field in inverter_brand_form.group('hidden') %}
{{ field }}

View File

@@ -8,6 +8,7 @@
<link href="//cdn.rawgit.com/noelboss/featherlight/1.7.6/release/featherlight.min.css" type="text/css" rel="stylesheet" />
<link rel="shortcut icon" href="https://us.sunpower.com/sites/sunpower/files/favicon.ico">
<link rel="stylesheet" type="text/css" href={{ url_for('static', filename='css/fontello.css') }}>
<link rel="stylesheet" type="text/css" href={{ url_for('static', filename='css/animation.css') }}>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"
integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="
crossorigin="anonymous"></script>
@@ -58,7 +59,23 @@
{% endif %}
{% endfor %}
</div>
{% with messages = get_flashed_messages() %}
{% if messages %}
<br>
<div class="summary_warning">
{% for message in messages %}
<div>{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% block contents %}{% endblock %}
{% if 'sf_session' in session %}
<small><i>Salesforce project</i> (<a href="/sales_force_logout">Log out</a>)</small>
{% endif %}
</div>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>

View File

@@ -1,5 +1,9 @@
<div class="navigation_buttons">
<a class="back button" href="{{ context['steps'][context['current_step'] - 2][2] }}">Back</a>
{% if context['hide_back'] %}
{% else %}
<a class="back button" href="{{ context['steps'][context['current_step'] - 2][2] }}">Back</a>
{% endif %}
{% if context['steps']|length > context['current_step'] and not context.get('no_proceed') %}
{% if not form or context['override_form'] %}
<a class="button" href="{{ context['steps'][context['current_step']][2] }}" value="Next">Next</a>

View File

@@ -3,10 +3,11 @@
{% block contents %}
{% if context['site_data_available'] %}
<div class="form_section">
<h3>Summary</h3>
<h3>Summary <small>({{ context['project_name'] }})</small></h3>
{% for warning in context['warning_messages'] %}
<div class="summary_warning">{{ warning.value }}</div>
{% endfor %}
<table class="summary_table" id="summary_table">
<tr>
<td colspan="2" class="table_meta_headers">OUTPUTS</td>
@@ -80,8 +81,10 @@
</tr>
</table>
</div>
{% endif %}
{% include "navigation_buttons.html.jinja" %}
{% endblock %}

View File

@@ -8,7 +8,7 @@ from helix.constants.panel_type import PanelType
from helix.constants.system_type import SystemType
from helix.models.coordinate import Coordinate
from helix.models.panel import PanelData, Panel
from helix.models.sql.inverter_brands import InverterBrand
class UserValues(object):
def __init__(self, store, site):

Binary file not shown.

View File

@@ -30,7 +30,7 @@ class CsvInputValidator(object):
file_validation_chain = [
CsvInputValidator.validate_file_for_panel_types,
CsvInputValidator.validate_for_spacing,
# CsvInputValidator.validate_for_spacing, # disabling this feature for the moment
]
result = self.run_validation_chain(headers, rows, file_validation_chain)
if result:

View File

@@ -1,5 +1,3 @@
from helix.constants.file_validation_error import FileValidationMessage, FileValidationError
class DxfInputValidator(object):
def __init__(self, _):

View File

@@ -100,13 +100,13 @@ class FileValidator(object):
finally:
return content
def validate(self, stream, file, expected):
def validate(self, stream, expected, file=None, extension=None):
"""Validates the uploaded file by extension
and content
Arguments;
stream (string): File content
file (FileObject): A file object provided from the ui
file (FileObject): A file object provided from the ui. Used only to get the file extension.
"""
@@ -114,7 +114,8 @@ class FileValidator(object):
file_type = self.identify_file_type(stream)
assert file_type == expected
validator = file_type.validator(self.values)
extension = self.obtain_extension(file)
if extension is None:
extension = self.obtain_extension(file)
file_type.valid_mapping(extension)
return validator.validate(stream)
except AssertionError:

View File

@@ -2,8 +2,12 @@ nose==1.3.7
selenium==2.53.1
splinter==0.7.3
cssselect==0.9.1
lxml==3.6.0
lxml==4.1.1
mockredispy==2.9.0.11
Flask-Testing==0.4.2
splinter[flask]
pillow==3.3.1
eralchemy==1.1.0
locust==0.8
pylint==1.7.4
pyopenssl==17.5.0

View File

@@ -1,3 +1,5 @@
gunicorn==19.7.1
meinheld==0.6.1
Flask==0.10.1
itsdangerous==0.24
Jinja2==2.8
@@ -18,3 +20,8 @@ invoke==0.13.0
dxfgrabber==0.8.1
rollbar==0.13.11
blinker==1.4
Flask-OAuthlib==0.9.4
boto3==1.4.8
ujson==1.35
Flask-FeatureFlags==0.6
mock==2.0.0

View File

@@ -1 +1,2 @@
python-3.5.1

View File

@@ -1,4 +1,5 @@
from invoke import run, task
import os
@task
@@ -34,9 +35,10 @@ def test_js(ctx):
def update_version(ctx):
run('echo "import os\n\n\ndef version():\n if os.getenv(\'VERSION\'):\n return os.getenv(\'VERSION\')\n return \'$(git describe --tags)\'" > helix/constants/version.py')
@task
def serve(ctx):
run('PYTHONPATH=. SP_DOCGEN_API_KEY=DC97-20AF-567E python helix/main.py')
run('SP_DOCGEN_API_KEY=DC97-20AF-567E gunicorn -c gunicorn_config.py --pythonpath helix main:app')
@task
@@ -52,3 +54,23 @@ def db_migrate(ctx):
@task
def serve_debug(ctx):
run('PYTHONPATH=. FLASK_DEBUG=1 SP_DOCGEN_API_KEY=DC97-20AF-567E python helix/main.py')
@task
def db_diagram(ctx):
db_uri = os.getenv('DATABASE_URL', 'postgres://pivotal:@localhost:5432/pivotal')
run('eralchemy -i "{}" -o documentation/db_schema.png -x temp audit'.format(db_uri))
@task
def classes_diagrams(ctx):
run('pyreverse -k -o calculators-diagram.png helix/calculators')
run('pyreverse -k -o constants-diagram.png helix/constants')
run('pyreverse -k -o forms-diagram.png helix/forms')
run('pyreverse -k -o models-diagram.png helix/models')
run('pyreverse -k -o validators-diagram.png helix/validators')
run('rm -rf packages*.png ; mv classes.*-diagram.png documentation/')
@task
def serve_ssl(ctx):
run('FLASK_DEBUG_SSL=true PORT=8443 PYTHONPATH=. FLASK_DEBUG=1 SP_DOCGEN_API_KEY=DC97-20AF-567E python helix/main.py')

View File

@@ -28,7 +28,7 @@ class BallastAndTrayCountTest(unittest.TestCase):
eq_(result.ballast_count, 0)
eq_(result.link_tray_count, 0)
eq_(result.cross_tray_count, 0)
eq_(result.system_weight, 69.75)
eq_(result.system_weight, 65.87)
eq_(result.needs_anchor, False)
def test_ballast_no_trays(self):
@@ -38,27 +38,27 @@ class BallastAndTrayCountTest(unittest.TestCase):
eq_(result.ballast_count, 4)
eq_(result.link_tray_count, 0)
eq_(result.cross_tray_count, 0)
eq_(result.system_weight, 69.75)
eq_(result.system_weight, 65.87)
eq_(result.needs_anchor, False)
def test_ballast_and_link_trays(self):
self.force_to_resist = 230
result = self.subject.ballast_and_tray_count(self.force_to_resist, self.panel_type, self.ballast_block_weight, self.anchor_count)
eq_(result.ballast_count, 8)
eq_(result.ballast_count, 9)
eq_(result.link_tray_count, 1)
eq_(result.cross_tray_count, 0)
eq_(result.system_weight, 72.05)
eq_(result.system_weight, 67.73)
eq_(result.needs_anchor, False)
def test_ballast_and_oscillate_between_0_and_1_link_trays(self):
self.force_to_resist = 210
result = self.subject.ballast_and_tray_count(self.force_to_resist, self.panel_type, self.ballast_block_weight, self.anchor_count)
eq_(result.ballast_count, 7)
eq_(result.ballast_count, 8)
eq_(result.link_tray_count, 1)
eq_(result.cross_tray_count, 0)
eq_(result.system_weight, 72.05)
eq_(result.system_weight, 67.73)
eq_(result.needs_anchor, False)
def test_ballast_and_link_tray_and_cross_tray(self):
@@ -68,17 +68,17 @@ class BallastAndTrayCountTest(unittest.TestCase):
eq_(result.ballast_count, 16)
eq_(result.link_tray_count, 1)
eq_(result.cross_tray_count, 1)
eq_(result.system_weight, 75.23)
eq_(result.system_weight, 70.88)
eq_(result.needs_anchor, False)
def test_ballast_and_link_tray_and_oscillate_between_0_and_1_cross_trays(self):
self.force_to_resist = 330
result = self.subject.ballast_and_tray_count(self.force_to_resist, self.panel_type, self.ballast_block_weight, self.anchor_count)
eq_(result.ballast_count, 13)
eq_(result.ballast_count, 14)
eq_(result.link_tray_count, 1)
eq_(result.cross_tray_count, 0)
eq_(result.system_weight, 72.05)
eq_(result.system_weight, 67.73)
eq_(result.needs_anchor, False)
def test_ballast_and_link_tray_and_2_cross_trays(self):
@@ -88,17 +88,17 @@ class BallastAndTrayCountTest(unittest.TestCase):
eq_(result.ballast_count, 25)
eq_(result.link_tray_count, 1)
eq_(result.cross_tray_count, 2)
eq_(result.system_weight, 78.41)
eq_(result.system_weight, 74.03)
eq_(result.needs_anchor, False)
def test_ballast_and_link_tray_and_at_first_2_then_1_cross_trays(self):
self.force_to_resist = 515
result = self.subject.ballast_and_tray_count(self.force_to_resist, self.panel_type, self.ballast_block_weight, self.anchor_count)
eq_(result.ballast_count, 22)
eq_(result.ballast_count, 23)
eq_(result.link_tray_count, 1)
eq_(result.cross_tray_count, 1)
eq_(result.system_weight, 75.23)
eq_(result.cross_tray_count, 2)
eq_(result.system_weight, 74.03)
eq_(result.needs_anchor, False)
def test_ballast_and_all_trays_and_anchor(self):
@@ -108,15 +108,15 @@ class BallastAndTrayCountTest(unittest.TestCase):
eq_(result.ballast_count, 35)
eq_(result.link_tray_count, 1)
eq_(result.cross_tray_count, 2)
eq_(result.system_weight, 78.41)
eq_(result.system_weight, 74.03)
eq_(result.needs_anchor, True)
def test_ballast_and_all_trays_and_at_first_anchor_then_no_anchor(self):
self.force_to_resist = 675
result = self.subject.ballast_and_tray_count(self.force_to_resist, self.panel_type, self.ballast_block_weight, self.anchor_count)
eq_(result.ballast_count, 30)
eq_(result.ballast_count, 31)
eq_(result.link_tray_count, 1)
eq_(result.cross_tray_count, 2)
eq_(result.system_weight, 78.41)
eq_(result.needs_anchor, False)
eq_(result.system_weight, 74.03)
eq_(result.needs_anchor, True)

View File

@@ -48,23 +48,23 @@ class BallastCalculatorWhenDualTiltAnd128CellTest(unittest.TestCase):
expected_value = {
PanelType.Corner: {
'anchors': [3, 2, 0, 0, 0],
'ballast blocks': [15, 14, 25, 12, 0],
'pressure': ['8.14', '7.74', '12.11', '6.88', '2.12']
'ballast blocks': [14, 13, 25, 12, 0],
'pressure': ['8.00', '7.61', '12.36', '7.16', '2.40']
},
PanelType.NorthSouth: {
'anchors': [3, 2, 0, 0, 0],
'ballast blocks': [7, 8, 22, 10, 0],
'pressure': ['4.90', '5.29', '10.83', '6.05', '2.10']
'ballast blocks': [6, 7, 21, 9, 0],
'pressure': ['4.79', '5.18', '10.72', '5.93', '2.38']
},
PanelType.EastWest: {
'anchors': [3, 2, 0, 0, 0],
'ballast blocks': [8, 8, 22, 10, 0],
'pressure': ['5.25', '5.25', '10.78', '5.97', '2.02']
'ballast blocks': [7, 7, 21, 10, 0],
'pressure': ['5.18', '5.18', '10.71', '6.29', '2.31']
},
PanelType.Middle: {
'anchors': [2, 0, 0, 0, 0],
'ballast blocks': [15, 32, 18, 8, 0],
'pressure': ['8.02', '14.73', '9.13', '5.17', '1.99']
'ballast blocks': [14, 31, 17, 7, 0],
'pressure': ['7.89', '14.66', '9.07', '5.10', '2.29']
},
}
@@ -89,19 +89,19 @@ class BallastCalculatorWhenDualTiltAnd128CellTest(unittest.TestCase):
expected_value = [
Panel(wind_zone=2, panel_type=PanelType.Corner, ballast=25, link_tray=2, cross_tray=2, wind_anchors=0,
pressure=12.11),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, ballast=22, link_tray=2, cross_tray=1, wind_anchors=0,
pressure=10.83),
pressure=12.360800772863891),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, ballast=21, link_tray=2, cross_tray=1, wind_anchors=0,
pressure=10.715259768140834),
Panel(wind_zone=4, panel_type=PanelType.EastWest, ballast=0, link_tray=0, cross_tray=0, wind_anchors=0,
pressure=2.02),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=15, link_tray=2, cross_tray=1, wind_anchors=3,
pressure=8.14),
Panel(wind_zone=1, panel_type=PanelType.Middle, ballast=32, link_tray=2, cross_tray=3, wind_anchors=0,
pressure=14.73),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, ballast=10, link_tray=1, cross_tray=0, wind_anchors=0,
pressure=6.05),
pressure=2.3107513954486905),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=14, link_tray=2, cross_tray=1, wind_anchors=3,
pressure=8.001084156290254),
Panel(wind_zone=1, panel_type=PanelType.Middle, ballast=31, link_tray=2, cross_tray=3, wind_anchors=0,
pressure=14.659635036496352),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, ballast=9, link_tray=1, cross_tray=0, wind_anchors=0,
pressure=5.928870759982826),
Panel(wind_zone=1, panel_type=PanelType.Corner, ballast=44, link_tray=2, cross_tray=4, wind_anchors=0,
pressure=19.65)
pressure=19.907666380420782)
]
assert_array_is_close(self.subject.ballast_and_trays_matrix(self.c_p_matrix, self.q_z, raw_data),
@@ -120,20 +120,20 @@ class BallastCalculatorWhenDualTiltAnd128CellTest(unittest.TestCase):
self.values.ballast_block_weight.return_value = 20
expected_value = [
Panel(wind_zone=2, panel_type=PanelType.Corner, ballast=30, link_tray=2, cross_tray=2, wind_anchors=0,
pressure=14.06, fuzzy_wind_zone=True),
Panel(wind_zone=2, panel_type=PanelType.Corner, ballast=29, link_tray=2, cross_tray=2, wind_anchors=0,
pressure=13.923703, fuzzy_wind_zone=True),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, ballast=25, link_tray=2, cross_tray=2, wind_anchors=0,
pressure=12.06, fuzzy_wind_zone=True),
Panel(wind_zone=4, panel_type=PanelType.EastWest, ballast=1, link_tray=0, cross_tray=0, wind_anchors=0,
pressure=2.41, fuzzy_wind_zone=True),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=10, link_tray=2, cross_tray=1, wind_anchors=4,
pressure=6.18, fuzzy_wind_zone=True, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=1, panel_type=PanelType.Middle, ballast=37, link_tray=2, cross_tray=4, wind_anchors=0,
pressure=16.75, fuzzy_wind_zone=True),
pressure=12.339897, fuzzy_wind_zone=True),
Panel(wind_zone=4, panel_type=PanelType.EastWest, ballast=0, link_tray=0, cross_tray=0, wind_anchors=0,
pressure=2.310751, fuzzy_wind_zone=True),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=9, link_tray=2, cross_tray=1, wind_anchors=4,
pressure=6.047456, fuzzy_wind_zone=True, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=1, panel_type=PanelType.Middle, ballast=36, link_tray=2, cross_tray=4, wind_anchors=0,
pressure=16.674802, fuzzy_wind_zone=True),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, ballast=12, link_tray=2, cross_tray=0, wind_anchors=0,
pressure=6.86, fuzzy_wind_zone=True),
Panel(wind_zone=1, panel_type=PanelType.Corner, ballast=7, link_tray=2, cross_tray=0, wind_anchors=3,
pressure=4.93, fuzzy_wind_zone=True)
pressure=7.137190, fuzzy_wind_zone=True),
Panel(wind_zone=1, panel_type=PanelType.Corner, ballast=6, link_tray=2, cross_tray=0, wind_anchors=3,
pressure=4.813740, fuzzy_wind_zone=True)
]
assert_array_is_close(self.subject.ballast_and_trays_matrix(self.c_p_matrix, self.q_z, raw_data),
@@ -155,19 +155,19 @@ class BallastCalculatorWhenDualTiltAnd128CellTest(unittest.TestCase):
expected = [
Panel(wind_zone=2, panel_type=PanelType.Corner, wind_anchors=0, seismic_anchors=1,
ballast=11, link_tray=2, cross_tray=0, pressure=6.49),
ballast=10, link_tray=2, cross_tray=0, pressure=6.376642335766423),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, wind_anchors=0, seismic_anchors=1,
ballast=6, link_tray=1, cross_tray=0, pressure=4.49),
ballast=6, link_tray=1, cross_tray=0, pressure=4.756693860025763),
Panel(wind_zone=4, panel_type=PanelType.EastWest, wind_anchors=0, seismic_anchors=1,
ballast=0, link_tray=0, cross_tray=0, pressure=2.02),
ballast=0, link_tray=0, cross_tray=0, pressure=2.3107513954486905),
Panel(wind_zone=0, panel_type=PanelType.Corner, wind_anchors=3, seismic_anchors=0,
ballast=15, link_tray=2, cross_tray=1, pressure=8.14),
ballast=14, link_tray=2, cross_tray=1, pressure=8.001084156290254),
Panel(wind_zone=1, panel_type=PanelType.Middle, wind_anchors=0, seismic_anchors=1,
ballast=17, link_tray=2, cross_tray=2, pressure=8.81),
ballast=16, link_tray=2, cross_tray=1, pressure=8.675476599398884),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, wind_anchors=0, seismic_anchors=1,
ballast=0, link_tray=0, cross_tray=0, pressure=2.10),
ballast=0, link_tray=0, cross_tray=0, pressure=2.3761979390296264),
Panel(wind_zone=1, panel_type=PanelType.Corner, wind_anchors=2, seismic_anchors=0,
ballast=14, link_tray=2, cross_tray=1, pressure=7.74),
ballast=13, link_tray=2, cross_tray=1, pressure=7.610358522971233),
]
result = self.subject.update_ballast(self.c_p_matrix, self.q_z, panels)
@@ -211,35 +211,37 @@ class BallastCalculatorWhenDualTiltAnd128CellTest(unittest.TestCase):
]
expected_value = [
Panel(wind_zone=2, panel_type=PanelType.Corner, ballast=25, link_tray=2, cross_tray=2, wind_anchors=0, pressure=12.11),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, ballast=22, link_tray=2, cross_tray=1, wind_anchors=0, pressure=10.83),
Panel(wind_zone=4, panel_type=PanelType.EastWest, ballast=0, link_tray=0, cross_tray=0, wind_anchors=0, pressure=2.02),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=15, link_tray=2, cross_tray=1, wind_anchors=3, pressure=8.14),
Panel(wind_zone=1, panel_type=PanelType.Middle, ballast=32, link_tray=2, cross_tray=3, wind_anchors=0, pressure=14.73),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, ballast=10, link_tray=1, cross_tray=0, wind_anchors=0, pressure=6.05),
Panel(wind_zone=1, panel_type=PanelType.Corner, ballast=14, link_tray=2, cross_tray=1, wind_anchors=2, pressure=7.74)
Panel(wind_zone=2, panel_type=PanelType.Corner, ballast=25, link_tray=2, cross_tray=2, wind_anchors=0, pressure=12.360800772863891),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, ballast=21, link_tray=2, cross_tray=1, wind_anchors=0, pressure=10.715259768140834),
Panel(wind_zone=4, panel_type=PanelType.EastWest, ballast=0, link_tray=0, cross_tray=0, wind_anchors=0, pressure=2.3107513954486905),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=14, link_tray=2, cross_tray=1, wind_anchors=3, pressure=8.001084156290254),
Panel(wind_zone=1, panel_type=PanelType.Middle, ballast=31, link_tray=2, cross_tray=3, wind_anchors=0, pressure=14.659635036496352),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, ballast=9, link_tray=1, cross_tray=0, wind_anchors=0, pressure=5.928870759982826),
Panel(wind_zone=1, panel_type=PanelType.Corner, ballast=13, link_tray=2, cross_tray=1, wind_anchors=2, pressure=7.610358522971233)
]
print("===\r\n")
print(self.subject.ballast_and_trays_matrix(self.c_p_matrix, self.q_z, panel_data))
assert_array_is_close(self.subject.ballast_and_trays_matrix(self.c_p_matrix, self.q_z, panel_data), expected_value, decimal=2)
def test_ballast_count_when_base_weight_greater_than_uplift(self):
expected = Panel(wind_zone=4, panel_type=PanelType.Middle, ballast=0, link_tray=0, cross_tray=0, wind_anchors=0, pressure=1.99)
expected = Panel(wind_zone=4, panel_type=PanelType.Middle, ballast=0, link_tray=0, cross_tray=0, wind_anchors=0, pressure=2.289848)
assert self.subject.ballast_tray_and_anchor_count(4, PanelType.Middle, 14, 100, self.c_p_matrix, self.q_z).almost_equal(expected, decimal=2)
def test_individual_ballast_block_uplift_greater_than_base_weight(self):
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=21, link_tray=2, cross_tray=2, wind_anchors=2, pressure=7.91)
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=19, link_tray=2, cross_tray=2, wind_anchors=2, pressure=7.682057)
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Middle, 14, 100, self.c_p_matrix, self.q_z).almost_equal(expected, decimal=2)
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=15, link_tray=2, cross_tray=2, wind_anchors=2, pressure=8.02)
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=14, link_tray=2, cross_tray=1, wind_anchors=2, pressure=7.894025)
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Middle, 20, 100, self.c_p_matrix, self.q_z).almost_equal(expected, decimal=2)
def test_ballast_blocks_exceeding_tray_capacity(self):
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=24, link_tray=2, cross_tray=3, wind_anchors=2, pressure=7.85)
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=22, link_tray=2, cross_tray=3, wind_anchors=2, pressure=7.704719)
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Middle, 12, 100, self.c_p_matrix, self.q_z).almost_equal(expected, decimal=2)
def test_when_max_system_pressure_is_lower_than_base_weight_pressure(self):
max_system_pressure = 0
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=0, link_tray=0, cross_tray=0, wind_anchors=3, pressure=1.99)
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=0, link_tray=0, cross_tray=0, wind_anchors=3, pressure=2.289848)
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Middle, 12, max_system_pressure, self.c_p_matrix, self.q_z).almost_equal(
expected,

View File

@@ -49,22 +49,22 @@ class BallastCalculatorWhenDualTiltAnd96CellTest(unittest.TestCase):
PanelType.Corner: {
'anchors': [3, 2, 0, 0, 0],
'ballast blocks': [2, 5, 20, 10, 0],
'pressure': ['3.48', '5.03', '12.90', '7.62', '2.40']
'pressure': ['3.48', '5.03', '12.91', '7.62', '2.39']
},
PanelType.NorthSouth: {
'anchors': [2, 1, 0, 0, 0],
'ballast blocks': [11, 15, 17, 8, 0],
'pressure': ['8.24', '10.32', '11.35', '6.59', '2.40']
'ballast blocks': [12, 15, 17, 8, 0],
'pressure': ['8.75', '10.30', '11.34', '6.56', '2.37']
},
PanelType.EastWest: {
'anchors': [2, 1, 0, 0, 0],
'ballast blocks': [10, 14, 17, 8, 0],
'pressure': ['7.55', '9.63', '11.18', '6.46', '2.26']
'pressure': ['7.62', '9.69', '11.24', '6.52', '2.28']
},
PanelType.Middle: {
'anchors': [2, 0, 0, 0, 0],
'ballast blocks': [2, 22, 12, 5, 0],
'pressure': ['3.34', '13.83', '8.59', '4.85', '2.26']
'pressure': ['3.34', '13.87', '8.63', '4.85', '2.26']
},
}
@@ -90,15 +90,14 @@ class BallastCalculatorWhenDualTiltAnd96CellTest(unittest.TestCase):
# Ballast, link tray, cross tray, anchor count, psf
expected_value = [
Panel(wind_zone=2, panel_type=PanelType.Corner, ballast=20, link_tray=2, cross_tray=1, wind_anchors=0, pressure=12.90),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, ballast=17, link_tray=2, cross_tray=1, wind_anchors=0, pressure=11.35),
Panel(wind_zone=4, panel_type=PanelType.EastWest, ballast=0, link_tray=0, cross_tray=0, wind_anchors=0, pressure=2.26),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=47, link_tray=2, cross_tray=4, wind_anchors=0, pressure=27.07),
Panel(wind_zone=1, panel_type=PanelType.Middle, ballast=22, link_tray=2, cross_tray=2, wind_anchors=0, pressure=13.82),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, ballast=8, link_tray=1, cross_tray=0, wind_anchors=0, pressure=6.59),
Panel(wind_zone=1, panel_type=PanelType.Corner, ballast=35, link_tray=2, cross_tray=3, wind_anchors=0, pressure=20.79)
Panel(wind_zone=2, panel_type=PanelType.Corner, ballast=20, link_tray=2, cross_tray=1, wind_anchors=0, pressure=12.910398406374503),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, ballast=17, link_tray=2, cross_tray=1, wind_anchors=0, pressure=11.336414342629482),
Panel(wind_zone=4, panel_type=PanelType.EastWest, ballast=0, link_tray=0, cross_tray=0, wind_anchors=0, pressure=2.2788844621513946),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=47, link_tray=2, cross_tray=4, wind_anchors=0, pressure=27.082988047808765),
Panel(wind_zone=1, panel_type=PanelType.Middle, ballast=22, link_tray=2, cross_tray=2, wind_anchors=0, pressure=13.874521912350598),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, ballast=8, link_tray=1, cross_tray=0, wind_anchors=0, pressure=6.564223107569722),
Panel(wind_zone=1, panel_type=PanelType.Corner, ballast=35, link_tray=2, cross_tray=3, wind_anchors=0, pressure=20.8049203187251)
]
assert_array_is_close(self.subject.ballast_and_trays_matrix(self.c_p_matrix, self.q_z, panels), expected_value, decimal=2)
def test_update_ballast(self):
@@ -116,16 +115,19 @@ class BallastCalculatorWhenDualTiltAnd96CellTest(unittest.TestCase):
]
expected_value = [
Panel(wind_zone=2, panel_type=PanelType.Corner, wind_anchors=0, seismic_anchors=1, ballast=5, link_tray=0, cross_tray=0, pressure=4.99),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, wind_anchors=0, seismic_anchors=1, ballast=2, link_tray=0, cross_tray=0, pressure=3.43),
Panel(wind_zone=4, panel_type=PanelType.EastWest, wind_anchors=0, seismic_anchors=1, ballast=0, link_tray=0, cross_tray=0, pressure=2.26),
Panel(wind_zone=0, panel_type=PanelType.Corner, wind_anchors=3, seismic_anchors=0, ballast=2, link_tray=1, cross_tray=0, pressure=3.48),
Panel(wind_zone=1, panel_type=PanelType.Middle, wind_anchors=0, seismic_anchors=1, ballast=7, link_tray=2, cross_tray=0, pressure=5.94),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, wind_anchors=0, seismic_anchors=1, ballast=0, link_tray=0, cross_tray=0, pressure=2.40),
Panel(wind_zone=1, panel_type=PanelType.Corner, wind_anchors=2, seismic_anchors=0, ballast=5, link_tray=1, cross_tray=0, pressure=5.03)
Panel(wind_zone=2, panel_type=PanelType.Corner, wind_anchors=0, seismic_anchors=1, ballast=5, link_tray=0, cross_tray=0, pressure=4.982729083665339),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, wind_anchors=0, seismic_anchors=1, ballast=2, link_tray=0, cross_tray=0, pressure=3.408745019920319),
Panel(wind_zone=4, panel_type=PanelType.EastWest, wind_anchors=0, seismic_anchors=1, ballast=0, link_tray=0, cross_tray=0, pressure=2.2788844621513946),
Panel(wind_zone=0, panel_type=PanelType.Corner, wind_anchors=3, seismic_anchors=0, ballast=2, link_tray=1, cross_tray=0, pressure=3.4768525896414344),
Panel(wind_zone=1, panel_type=PanelType.Middle, wind_anchors=0, seismic_anchors=1, ballast=7, link_tray=2, cross_tray=0, pressure=5.98),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, wind_anchors=0, seismic_anchors=1, ballast=0, link_tray=0, cross_tray=0, pressure=2.3728884462151396),
Panel(wind_zone=1, panel_type=PanelType.Corner, wind_anchors=2, seismic_anchors=0, ballast=5, link_tray=1, cross_tray=0, pressure=5.030637450199203)
]
result = self.subject.update_ballast(self.c_p_matrix, self.q_z, panels)
print("===")
print(result)
print("===")
assert_array_is_close(result, expected_value, decimal=2)
def test_update_ballast_with_fuzzy_wind_zone(self):
@@ -143,13 +145,13 @@ class BallastCalculatorWhenDualTiltAnd96CellTest(unittest.TestCase):
]
expected_value = [
Panel(wind_zone=2, panel_type=PanelType.Corner, wind_anchors=0, seismic_anchors=1, ballast=9, link_tray=2, cross_tray=0, pressure=7.15, fuzzy_wind_zone=True),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, wind_anchors=0, seismic_anchors=1, ballast=5, link_tray=0, cross_tray=0, pressure=4.99, fuzzy_wind_zone=True),
Panel(wind_zone=4, panel_type=PanelType.EastWest, wind_anchors=0, seismic_anchors=1, ballast=0, link_tray=0, cross_tray=0, pressure=2.26, fuzzy_wind_zone=True),
Panel(wind_zone=0, panel_type=PanelType.Corner, wind_anchors=3, seismic_anchors=0, ballast=9, link_tray=2, cross_tray=0, pressure=7.15, fuzzy_wind_zone=True),
Panel(wind_zone=1, panel_type=PanelType.Middle, wind_anchors=0, seismic_anchors=1, ballast=11, link_tray=2, cross_tray=1, pressure=8.07, fuzzy_wind_zone=True),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, wind_anchors=0, seismic_anchors=1, ballast=0, link_tray=0, cross_tray=0, pressure=2.40, fuzzy_wind_zone=True),
Panel(wind_zone=1, panel_type=PanelType.Corner, wind_anchors=2, seismic_anchors=0, ballast=11, link_tray=2, cross_tray=0, pressure=8.18, fuzzy_wind_zone=True)
Panel(wind_zone=2, panel_type=PanelType.Corner, wind_anchors=0, seismic_anchors=1, ballast=9, link_tray=2, cross_tray=0, pressure=7.150517928286853, fuzzy_wind_zone=True),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, wind_anchors=0, seismic_anchors=1, ballast=5, link_tray=0, cross_tray=0, pressure=4.962529880478088, fuzzy_wind_zone=True),
Panel(wind_zone=4, panel_type=PanelType.EastWest, wind_anchors=0, seismic_anchors=1, ballast=0, link_tray=0, cross_tray=0, pressure=2.2788844621513946, fuzzy_wind_zone=True),
Panel(wind_zone=0, panel_type=PanelType.Corner, wind_anchors=3, seismic_anchors=0, ballast=9, link_tray=2, cross_tray=0, pressure=7.150517928286853, fuzzy_wind_zone=True),
Panel(wind_zone=1, panel_type=PanelType.Middle, wind_anchors=0, seismic_anchors=1, ballast=11, link_tray=2, cross_tray=1, pressure=8.114382470119523, fuzzy_wind_zone=True),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, wind_anchors=0, seismic_anchors=1, ballast=0, link_tray=0, cross_tray=0, pressure=2.3728884462151396, fuzzy_wind_zone=True),
Panel(wind_zone=1, panel_type=PanelType.Corner, wind_anchors=2, seismic_anchors=0, ballast=11, link_tray=2, cross_tray=0, pressure=8.186374501992033, fuzzy_wind_zone=True)
]
result = self.subject.update_ballast(self.c_p_matrix, self.q_z, panels)
@@ -192,15 +194,14 @@ class BallastCalculatorWhenDualTiltAnd96CellTest(unittest.TestCase):
]
expected_value = [
Panel(wind_zone=2, panel_type=PanelType.Corner, ballast=20, link_tray=2, cross_tray=1, wind_anchors=0, pressure=12.90),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, ballast=17, link_tray=2, cross_tray=1, wind_anchors=0, pressure=11.35),
Panel(wind_zone=4, panel_type=PanelType.EastWest, ballast=0, link_tray=0, cross_tray=0, wind_anchors=0, pressure=2.26),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=2, link_tray=1, cross_tray=0, wind_anchors=3, pressure=3.48),
Panel(wind_zone=1, panel_type=PanelType.Middle, ballast=22, link_tray=2, cross_tray=2, wind_anchors=0, pressure=13.83),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, ballast=8, link_tray=1, cross_tray=0, wind_anchors=0, pressure=6.59),
Panel(wind_zone=1, panel_type=PanelType.Corner, ballast=5, link_tray=1, cross_tray=0, wind_anchors=2, pressure=5.03)
Panel(wind_zone=2, panel_type=PanelType.Corner, ballast=20, link_tray=2, cross_tray=1, wind_anchors=0, pressure=12.910398406374503),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, ballast=17, link_tray=2, cross_tray=1, wind_anchors=0, pressure=11.336414342629482),
Panel(wind_zone=4, panel_type=PanelType.EastWest, ballast=0, link_tray=0, cross_tray=0, wind_anchors=0, pressure=2.2788844621513946),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=2, link_tray=1, cross_tray=0, wind_anchors=3, pressure=3.4768525896414344),
Panel(wind_zone=1, panel_type=PanelType.Middle, ballast=22, link_tray=2, cross_tray=2, wind_anchors=0, pressure=13.874521912350598),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, ballast=8, link_tray=1, cross_tray=0, wind_anchors=0, pressure=6.564223107569722),
Panel(wind_zone=1, panel_type=PanelType.Corner, ballast=5, link_tray=1, cross_tray=0, wind_anchors=2, pressure=5.030637450199203)
]
assert_array_is_close(self.subject.ballast_and_trays_matrix(self.c_p_matrix, self.q_z, panels), expected_value, decimal=2)
def test_ballast_count_when_base_weight_greater_than_uplift(self):
@@ -212,11 +213,11 @@ class BallastCalculatorWhenDualTiltAnd96CellTest(unittest.TestCase):
def test_individual_ballast_block_uplift_greater_than_base_weight(self):
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Middle, 14, 100, self.c_p_matrix,
self.q_z).almost_equal(
Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=45, link_tray=2, cross_tray=4, wind_anchors=0, pressure=18.87),
Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=45, link_tray=2, cross_tray=4, wind_anchors=0, pressure=18.920438),
decimal=2)
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Middle, 20, 100, self.c_p_matrix,
self.q_z).almost_equal(
Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=32, link_tray=2, cross_tray=3, wind_anchors=0, pressure=19.07),
Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=32, link_tray=2, cross_tray=3, wind_anchors=0, pressure=19.116474),
decimal=2)
def test_ballast_blocks_exceeding_tray_capacity(self):

View File

@@ -48,23 +48,23 @@ class BallastCalculatorWhenDualTiltAndPSeriesTest(unittest.TestCase):
expected_value = {
PanelType.Corner: {
'anchors': [4, 3, 0, 0, 0],
'ballast blocks': [15, 10, 33, 16, 0],
'pressure': ['8.43', '6.38', '15.93', '8.83', '2.12']
'ballast blocks': [14, 10, 32, 16, 0],
'pressure': ['8.26', '6.63', '15.76', '9.08', '2.39']
},
PanelType.NorthSouth: {
'anchors': [4, 3, 0, 0, 0],
'ballast blocks': [5, 2, 28, 14, 0],
'pressure': ['4.22', '2.97', '13.77', '7.90', '2.10']
'ballast blocks': [4, 2, 27, 13, 0],
'pressure': ['4.08', '3.22', '13.63', '7.77', '2.37']
},
PanelType.EastWest: {
'anchors': [4, 3, 0, 0, 0],
'ballast blocks': [6, 3, 28, 14, 0],
'pressure': ['4.58', '3.29', '13.72', '7.86', '2.01']
'ballast blocks': [5, 2, 27, 13, 0],
'pressure': ['4.49', '3.19', '13.63', '7.76', '2.30']
},
PanelType.Middle: {
'anchors': [3, 2, 0, 0, 0],
'ballast blocks': [11, 10, 23, 11, 0],
'pressure': ['6.61', '6.20', '11.59', '6.61', '1.99']
'ballast blocks': [10, 10, 23, 11, 0],
'pressure': ['6.51', '6.51', '11.90', '6.92', '2.28']
},
}
@@ -104,27 +104,28 @@ class BallastCalculatorWhenDualTiltAndPSeriesTest(unittest.TestCase):
expected_value = {
PanelType.Corner: {
'anchors': [0, 0, 0, 0, 0],
'ballast blocks': [8, 3, 0, 0, 0],
'pressure': ['4.79', '3.11', '2.12', '2.12', '2.12']
'ballast blocks': [7, 3, 0, 0, 0],
'pressure': ['4.68', '3.37', '2.39', '2.39', '2.39']
},
PanelType.NorthSouth: {
'anchors': [0, 0, 0, 0, 0],
'ballast blocks': [3, 0, 0, 0, 0],
'pressure': ['3.08', '2.10', '2.10', '2.10', '2.10']
'pressure': ['3.35', '2.37', '2.37', '2.37', '2.37']
},
PanelType.EastWest: {
'anchors': [0, 0, 0, 0, 0],
'ballast blocks': [4, 0, 0, 0, 0],
'pressure': ['3.32', '2.01', '2.01', '2.01', '2.01']
'ballast blocks': [3, 0, 0, 0, 0],
'pressure': ['3.28', '2.30', '2.30', '2.30', '2.30']
},
PanelType.Middle: {
'anchors': [0, 0, 0, 0, 0],
'ballast blocks': [1, 0, 0, 0, 0],
'pressure': ['2.32', '1.99', '1.99', '1.99', '1.99']
'ballast blocks': [0, 0, 0, 0, 0],
'pressure': ['2.28', '2.28', '2.28', '2.28', '2.28']
},
}
received_table = self.subject.summary_table(self.c_p_matrix, self.q_z)
print("====\r\n", received_table)
eq_(received_table.keys(), expected_value.keys())
for key in expected_value.keys():
received_table[key].pop('warnings')
@@ -143,20 +144,20 @@ class BallastCalculatorWhenDualTiltAndPSeriesTest(unittest.TestCase):
self.values.ballast_block_weight.return_value = 20
expected_value = [
Panel(wind_zone=2, panel_type=PanelType.Corner, ballast=33, link_tray=2, cross_tray=3, wind_anchors=0,
pressure=15.93),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, ballast=28, link_tray=2, cross_tray=2, wind_anchors=0,
pressure=13.77),
Panel(wind_zone=2, panel_type=PanelType.Corner, ballast=32, link_tray=2, cross_tray=3, wind_anchors=0,
pressure=15.764460),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, ballast=27, link_tray=2, cross_tray=2, wind_anchors=0,
pressure=13.630342),
Panel(wind_zone=4, panel_type=PanelType.EastWest, ballast=0, link_tray=0, cross_tray=0, wind_anchors=0,
pressure=2.01),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=15, link_tray=2, cross_tray=1, wind_anchors=4,
pressure=8.43, warnings=[PanelWarnings.MaxPsf]),
pressure=2.299163),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=14, link_tray=2, cross_tray=1, wind_anchors=4,
pressure=8.263513, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=1, panel_type=PanelType.Middle, ballast=40, link_tray=2, cross_tray=4, wind_anchors=0,
pressure=18.68),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, ballast=14, link_tray=2, cross_tray=0, wind_anchors=0,
pressure=7.90),
pressure=18.993076),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, ballast=13, link_tray=2, cross_tray=0, wind_anchors=0,
pressure=7.767559),
Panel(wind_zone=1, panel_type=PanelType.Corner, ballast=10, link_tray=2, cross_tray=1, wind_anchors=3,
pressure=6.38),
pressure=6.625349),
]
assert_array_is_close(self.subject.ballast_and_trays_matrix(self.c_p_matrix, self.q_z, raw_data),
@@ -178,19 +179,19 @@ class BallastCalculatorWhenDualTiltAndPSeriesTest(unittest.TestCase):
expected = [
Panel(wind_zone=2, panel_type=PanelType.Corner, wind_anchors=0, seismic_anchors=1,
ballast=18, link_tray=2, cross_tray=1, pressure=9.65),
ballast=17, link_tray=2, cross_tray=1, pressure=9.492135463546356),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, wind_anchors=0, seismic_anchors=1,
ballast=13, link_tray=2, cross_tray=0, pressure=7.50),
ballast=12, link_tray=2, cross_tray=0, pressure=7.358017551755176),
Panel(wind_zone=4, panel_type=PanelType.EastWest, wind_anchors=0, seismic_anchors=1,
ballast=0, link_tray=0, cross_tray=0, pressure=2.01),
ballast=0, link_tray=0, cross_tray=0, pressure=2.299162916291629),
Panel(wind_zone=0, panel_type=PanelType.Corner, wind_anchors=4, seismic_anchors=0,
ballast=15, link_tray=2, cross_tray=1, pressure=8.43),
ballast=14, link_tray=2, cross_tray=1, pressure=8.263512601260127),
Panel(wind_zone=1, panel_type=PanelType.Middle, wind_anchors=0, seismic_anchors=1,
ballast=25, link_tray=2, cross_tray=3, pressure=12.47),
ballast=24, link_tray=2, cross_tray=3, pressure=12.375918091809181),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, wind_anchors=0, seismic_anchors=1,
ballast=0, link_tray=0, cross_tray=0, pressure=2.10),
ballast=0, link_tray=0, cross_tray=0, pressure=2.3677610261026105),
Panel(wind_zone=1, panel_type=PanelType.Corner, wind_anchors=3, seismic_anchors=0,
ballast=10, link_tray=2, cross_tray=1, pressure=6.38),
ballast=10, link_tray=2, cross_tray=1, pressure=6.625348784878488),
]
result = self.subject.update_ballast(self.c_p_matrix, self.q_z, panels)
@@ -234,13 +235,13 @@ class BallastCalculatorWhenDualTiltAndPSeriesTest(unittest.TestCase):
]
expected_value = [
Panel(wind_zone=2, panel_type=PanelType.Corner, ballast=33, link_tray=2, cross_tray=3, wind_anchors=0, pressure=15.93),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, ballast=28, link_tray=2, cross_tray=2, wind_anchors=0, pressure=13.77),
Panel(wind_zone=4, panel_type=PanelType.EastWest, ballast=0, link_tray=0, cross_tray=0, wind_anchors=0, pressure=2.01),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=15, link_tray=2, cross_tray=1, wind_anchors=4, pressure=8.43, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=1, panel_type=PanelType.Middle, ballast=10, link_tray=2, cross_tray=1, wind_anchors=2, pressure=6.20),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, ballast=14, link_tray=2, cross_tray=0, wind_anchors=0, pressure=7.90),
Panel(wind_zone=1, panel_type=PanelType.Corner, ballast=10, link_tray=2, cross_tray=1, wind_anchors=3, pressure=6.38)
Panel(wind_zone=2, panel_type=PanelType.Corner, ballast=32, link_tray=2, cross_tray=3, wind_anchors=0, pressure=15.764460),
Panel(wind_zone=2, panel_type=PanelType.NorthSouth, ballast=27, link_tray=2, cross_tray=2, wind_anchors=0, pressure=13.630342),
Panel(wind_zone=4, panel_type=PanelType.EastWest, ballast=0, link_tray=0, cross_tray=0, wind_anchors=0, pressure=2.299163),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=14, link_tray=2, cross_tray=1, wind_anchors=4, pressure= 8.263513, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=1, panel_type=PanelType.Middle, ballast=10, link_tray=2, cross_tray=1, wind_anchors=2, pressure=6.513135),
Panel(wind_zone=3, panel_type=PanelType.NorthSouth, ballast=13, link_tray=2, cross_tray=0, wind_anchors=0, pressure=7.767559),
Panel(wind_zone=1, panel_type=PanelType.Corner, ballast=10, link_tray=2, cross_tray=1, wind_anchors=3, pressure=6.625349)
]
assert_array_is_close(self.subject.ballast_and_trays_matrix(self.c_p_matrix, self.q_z, panel_data), expected_value, decimal=2)
@@ -272,28 +273,28 @@ class BallastCalculatorWhenDualTiltAndPSeriesTest(unittest.TestCase):
self.q_z = 18.43072
self.subject = BallastCalculator(self.values)
expected = Panel(wind_zone=0, panel_type=PanelType.NorthSouth, ballast=3, link_tray=0, cross_tray=0, wind_anchors=0, pressure=3.08)
expected = Panel(wind_zone=0, panel_type=PanelType.NorthSouth, ballast=3, link_tray=0, cross_tray=0, wind_anchors=0, pressure=3.350659)
received = self.subject.ballast_tray_and_anchor_count(0, PanelType.NorthSouth, 16, 8, self.c_p_matrix, self.q_z)
assert received.almost_equal(expected, decimal=2)
def test_ballast_count_when_base_weight_greater_than_uplift(self):
expected = Panel(wind_zone=4, panel_type=PanelType.Middle, ballast=0, link_tray=0, cross_tray=0, wind_anchors=0, pressure=1.99)
expected = Panel(wind_zone=4, panel_type=PanelType.Middle, ballast=0, link_tray=0, cross_tray=0, wind_anchors=0, pressure=2.277252)
assert self.subject.ballast_tray_and_anchor_count(4, PanelType.Middle, 14, 100, self.c_p_matrix, self.q_z).almost_equal(expected, decimal=2)
def test_individual_ballast_block_uplift_greater_than_base_weight(self):
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=15, link_tray=2, cross_tray=2, wind_anchors=3, pressure=6.47)
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=14, link_tray=2, cross_tray=2, wind_anchors=3, pressure=6.495729)
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Middle, 14, 100, self.c_p_matrix, self.q_z).almost_equal(expected, decimal=2)
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=11, link_tray=2, cross_tray=1, wind_anchors=3, pressure=6.61)
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=10, link_tray=2, cross_tray=1, wind_anchors=3, pressure=6.513135)
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Middle, 20, 100, self.c_p_matrix, self.q_z).almost_equal(expected, decimal=2)
def test_ballast_blocks_exceeding_tray_capacity(self):
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=18, link_tray=2, cross_tray=2, wind_anchors=3, pressure=6.59)
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=17, link_tray=2, cross_tray=2, wind_anchors=3, pressure=6.659545)
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Middle, 12, 100, self.c_p_matrix, self.q_z).almost_equal(expected, decimal=2)
def test_when_max_system_pressure_is_lower_than_base_weight_pressure(self):
max_system_pressure = 0
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=0, link_tray=0, cross_tray=0, wind_anchors=4, pressure=1.99)
expected = Panel(wind_zone=0, panel_type=PanelType.Middle, ballast=0, link_tray=0, cross_tray=0, wind_anchors=4, pressure=2.277252)
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Middle, 12, max_system_pressure, self.c_p_matrix, self.q_z).almost_equal(
expected,

View File

@@ -54,23 +54,23 @@ class BallastCalculatorWhenSingleTiltAnd128CellTest(unittest.TestCase):
expected_value = {
PanelType.Corner: {
'anchors': [7, 5, 3, 3, 2, 2, 0, 2, 7, 4, 0],
'ballast blocks': [9, 14, 5, 5, 3, 1, 13, 11, 11, 8, 11],
'pressure': ['7.43', '10.31', '4.95', '4.95', '3.79', '2.64', '9.55', '8.49', '8.58', '6.76', '8.40']
'ballast blocks': [10, 15, 5, 6, 3, 1, 13, 11, 12, 9, 11],
'pressure': ['7.89', '10.77', '4.83', '5.41', '3.68', '2.53', '9.53', '8.38', '9.04', '7.23', '8.29']
},
PanelType.NorthSouth: {
'anchors': [6, 5, 3, 2, 0, 1, 0, 2, 6, 4, 0],
'ballast blocks': [14, 6, 1, 15, 19, 13, 12, 6, 12, 1, 10],
'pressure': ['10.13', '5.44', '2.47', '10.71', '13.01', '9.46', '8.89', '5.35', '8.98', '2.47', '7.65']
'pressure': ['10.11', '5.41', '2.45', '10.68', '12.98', '9.44', '8.87', '5.32', '8.96', '2.45', '7.62']
},
PanelType.EastWest: {
'anchors': [5, 4, 2, 2, 1, 0, 0, 2, 6, 1, 0],
'ballast blocks': [11, 7, 2, 1, 15, 21, 12, 6, 11, 14, 6],
'pressure': ['8.49', '6.19', '3.16', '2.58', '10.79', '14.25', '8.98', '5.52', '8.58', '10.22', '5.46']
'ballast blocks': [12, 7, 2, 1, 15, 21, 12, 6, 12, 14, 6],
'pressure': ['8.94', '6.07', '3.05', '2.47', '10.67', '14.12', '8.85', '5.40', '9.03', '10.09', '5.35']
},
PanelType.Middle: {
'anchors': [5, 4, 2, 1, 0, 0, 0, 2, 6, 1, 0],
'ballast blocks': [9, 5, 2, 15, 18, 20, 12, 5, 7, 13, 6],
'pressure': ['7.21', '4.91', '3.02', '10.66', '12.39', '13.63', '8.93', '4.82', '6.06', '9.51', '5.32']
'pressure': ['7.14', '4.84', '2.97', '10.59', '12.32', '13.56', '8.87', '4.75', '5.99', '9.44', '5.27']
},
}
@@ -96,21 +96,21 @@ class BallastCalculatorWhenSingleTiltAnd128CellTest(unittest.TestCase):
expected_value = [
Panel(wind_zone=5, panel_type=PanelType.Corner, ballast=1, link_tray=0, cross_tray=0, wind_anchors=2,
pressure=2.64),
pressure=2.532134),
Panel(wind_zone=6, panel_type=PanelType.NorthSouth, ballast=12, link_tray=0, cross_tray=1, wind_anchors=0,
pressure=8.89),
pressure=8.866209),
Panel(wind_zone=7, panel_type=PanelType.EastWest, ballast=6, link_tray=2, cross_tray=0, wind_anchors=2,
pressure=5.52, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=9, link_tray=0, cross_tray=2, wind_anchors=7,
pressure=7.43, warnings=[PanelWarnings.MaxPsf]),
pressure=5.400562, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=10, link_tray=0, cross_tray=2, wind_anchors=7,
pressure=7.891559, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=8, panel_type=PanelType.Middle, ballast=7, link_tray=1, cross_tray=1, wind_anchors=6,
pressure=6.06, warnings=[PanelWarnings.MaxPsf]),
pressure=5.991164, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=9, panel_type=PanelType.NorthSouth, ballast=1, link_tray=0, cross_tray=0, wind_anchors=4,
pressure=2.47, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=6, panel_type=PanelType.Corner, ballast=13, link_tray=0, cross_tray=0, wind_anchors=0,
pressure=9.55),
pressure=2.446694, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=6, panel_type=PanelType.Corner, ballast=13, link_tray=0, cross_tray=1, wind_anchors=0,
pressure=9.527003),
Panel(wind_zone=6, panel_type=PanelType.EastWest, ballast=12, link_tray=2, cross_tray=0, wind_anchors=0,
pressure=8.98)
pressure=8.852688)
]
assert_array_is_close(self.subject.ballast_and_trays_matrix(self.c_p_matrix, self.q_z, panels),
@@ -169,19 +169,19 @@ class BallastCalculatorWhenSingleTiltAnd128CellTest(unittest.TestCase):
expected_value = [
Panel(wind_zone=7, panel_type=PanelType.Corner, ballast=11, link_tray=0, cross_tray=1,
wind_anchors=2, pressure=8.49, warnings=[PanelWarnings.MaxPsf]),
wind_anchors=2, pressure=8.376295, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=7, panel_type=PanelType.NorthSouth, ballast=6, link_tray=0, cross_tray=0,
wind_anchors=2, pressure=5.35, warnings=[PanelWarnings.MaxPsf]),
wind_anchors=2, pressure=5.323465, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=9, panel_type=PanelType.EastWest, ballast=29, link_tray=2, cross_tray=2,
wind_anchors=0, pressure=18.94),
wind_anchors=0, pressure=18.814946),
Panel(wind_zone=5, panel_type=PanelType.Corner, ballast=1, link_tray=0, cross_tray=0,
wind_anchors=2, pressure=2.64),
wind_anchors=2, pressure=2.532134),
Panel(wind_zone=6, panel_type=PanelType.Middle, ballast=12, link_tray=1, cross_tray=1,
wind_anchors=0, pressure=8.93),
wind_anchors=0, pressure=8.867935),
Panel(wind_zone=8, panel_type=PanelType.NorthSouth, ballast=12, link_tray=0, cross_tray=2,
wind_anchors=6, pressure=8.98, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=6, panel_type=PanelType.Corner, ballast=13, link_tray=0, cross_tray=0,
wind_anchors=0, pressure=9.55)
wind_anchors=6, pressure=8.956827, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=6, panel_type=PanelType.Corner, ballast=13, link_tray=0, cross_tray=1,
wind_anchors=0, pressure=9.527003)
]
assert_array_is_close(self.subject.ballast_and_trays_matrix(self.c_p_matrix, self.q_z, panel_data),
@@ -213,41 +213,41 @@ class BallastCalculatorWhenSingleTiltAnd128CellTest(unittest.TestCase):
self.q_z = 18.446284
expected = Panel(wind_zone=10, panel_type=PanelType.Middle, ballast=0, link_tray=0, cross_tray=0,
wind_anchors=0, pressure=1.87)
wind_anchors=0, pressure=1.819845)
assert self.subject.ballast_tray_and_anchor_count(10, PanelType.Middle, 14, 1000, self.c_p_matrix,
self.q_z).almost_equal(expected, decimal=2)
def test_individual_ballast_block_uplift_greater_than_base_weight(self):
expected = Panel(wind_zone=6, panel_type=PanelType.Middle, ballast=16, link_tray=1, cross_tray=1,
wind_anchors=0, pressure=8.47)
expected = Panel(wind_zone=6, panel_type=PanelType.Middle, ballast=17, link_tray=1, cross_tray=1,
wind_anchors=0, pressure=8.810399)
assert self.subject.ballast_tray_and_anchor_count(6, PanelType.Middle, 14, 1000, self.c_p_matrix,
self.q_z).almost_equal(expected, decimal=2)
expected = Panel(wind_zone=6, panel_type=PanelType.Middle, ballast=12, link_tray=1, cross_tray=1,
wind_anchors=0, pressure=8.93)
wind_anchors=0, pressure=8.867935)
assert self.subject.ballast_tray_and_anchor_count(6, PanelType.Middle, 20, 1000, self.c_p_matrix,
self.q_z).almost_equal(expected, decimal=2)
def test_ballast_blocks_exceeding_tray_capacity(self):
expected = Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=9, link_tray=0, cross_tray=2,
wind_anchors=7, pressure=7.43, warnings=[PanelWarnings.MaxPsf])
expected = Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=10, link_tray=0, cross_tray=2,
wind_anchors=7, pressure=7.891559, warnings=[PanelWarnings.MaxPsf])
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Corner, 20, 1000, self.c_p_matrix,
self.q_z).almost_equal(expected, decimal=2)
expected = Panel(wind_zone=8, panel_type=PanelType.Middle, ballast=7, link_tray=1, cross_tray=1,
wind_anchors=6, pressure=6.06, warnings=[PanelWarnings.MaxPsf])
wind_anchors=6, pressure=5.991164, warnings=[PanelWarnings.MaxPsf])
assert self.subject.ballast_tray_and_anchor_count(8, PanelType.Middle, 20, 1000, self.c_p_matrix,
self.q_z).almost_equal(expected, decimal=2)
def test_ballast_blocks_when_panel_exceeds_max_system_pressure(self):
expected = Panel(wind_zone=5, panel_type=PanelType.Middle, ballast=7, link_tray=1, cross_tray=0,
wind_anchors=1, pressure=4.76)
wind_anchors=1, pressure=4.692301)
assert self.subject.ballast_tray_and_anchor_count(5, PanelType.Middle, 14, 10, self.c_p_matrix,
self.q_z).almost_equal(expected, decimal=2)
expected = Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=13, link_tray=0, cross_tray=2,
wind_anchors=7, pressure=7.49, warnings=[PanelWarnings.MaxPsf])
expected = Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=14, link_tray=0, cross_tray=2,
wind_anchors=7, pressure=7.776488, warnings=[PanelWarnings.MaxPsf])
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Corner, 14, 10, self.c_p_matrix,
self.q_z).almost_equal(expected, decimal=2)
@@ -255,7 +255,7 @@ class BallastCalculatorWhenSingleTiltAnd128CellTest(unittest.TestCase):
max_system_pressure = 0
expected = Panel(wind_zone=5, panel_type=PanelType.Middle, ballast=0, link_tray=0, cross_tray=0,
wind_anchors=2, pressure=1.87)
wind_anchors=2, pressure=1.819845)
assert self.subject.ballast_tray_and_anchor_count(5, PanelType.Middle, 12, max_system_pressure,
self.c_p_matrix, self.q_z).almost_equal(
expected,

View File

@@ -54,24 +54,23 @@ class BallastCalculatorWhenSingleTiltAnd96CellTest(unittest.TestCase):
expected_value = {
PanelType.Corner: {
'anchors': [5, 4, 2, 2, 1, 1, 0, 2, 6, 3, 0],
'ballast blocks': [14, 10, 9, 9, 11, 10, 10, 2, 2, 9, 8],
'pressure': ['12.93', '9.79', '9.03', '9.03', '10.56', '9.70', '9.70', '3.60', '3.69', '9.03', '8.18']
'ballast blocks': [14, 10, 9, 10, 11, 10, 11, 2, 2, 9, 8],
'pressure': ['12.81', '9.67', '8.90', '9.67', '10.43', '9.58', '10.34', '3.47', '3.57', '8.90', '8.05']
},
PanelType.NorthSouth: {
'anchors': [5, 4, 2, 2, 0, 1, 0, 1, 5, 3, 0],
'ballast blocks': [5, 2, 5, 5, 15, 7, 9, 13, 3, 2, 7],
'pressure': ['5.79', '3.50', '5.70', '5.70', '13.41', '7.23', '8.84', '11.89', '4.26', '3.41', '7.23']
'pressure': ['5.74', '3.45', '5.74', '5.65', '13.37', '7.17', '8.79', '11.84', '4.22', '3.36', '7.17']
},
PanelType.EastWest: {
'anchors': [4, 3, 1, 1, 1, 0, 0, 1, 5, 1, 0],
'ballast blocks': [6, 6, 10, 9, 8, 16, 9, 13, 3, 7, 4],
'pressure': ['6.79', '6.70', '9.75', '8.99', '8.23', '14.42', '8.99', '12.13', '4.41', '7.46', '5.09']
'pressure': ['6.62', '6.52', '9.57', '8.81', '8.05', '14.24', '8.81', '11.96', '4.24', '7.29', '4.93']
},
PanelType.Middle: {
'anchors': [3, 2, 1, 0, 0, 0, 0, 1, 3, 0, 0],
'ballast blocks': [11, 13, 6, 19, 12, 13, 8, 7, 15, 18, 3],
'pressure': ['10.42', '11.95', '6.52', '16.61', '11.19', '11.95', '8.05', '7.29', '13.56', '15.76',
'4.15']
'ballast blocks': [11, 13, 6, 19, 12, 13, 8, 8, 15, 18, 3],
'pressure': ['10.40', '11.92', '6.49', '16.59', '11.16', '11.92', '8.02', '8.02', '13.54', '15.73', '4.13']
},
}
@@ -96,14 +95,14 @@ class BallastCalculatorWhenSingleTiltAnd96CellTest(unittest.TestCase):
self.values.ballast_block_weight.return_value = 20
expected_value = [
Panel(wind_zone=5, panel_type=PanelType.Corner, ballast=25, link_tray=0, cross_tray=2, wind_anchors=0, pressure=21.32),
Panel(wind_zone=6, panel_type=PanelType.NorthSouth, ballast=9, link_tray=0, cross_tray=1, wind_anchors=0, pressure=8.84),
Panel(wind_zone=7, panel_type=PanelType.EastWest, ballast=28, link_tray=2, cross_tray=2, wind_anchors=0, pressure=23.65),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=14, link_tray=0, cross_tray=2, wind_anchors=5, pressure=12.93, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=8, panel_type=PanelType.Middle, ballast=15, link_tray=1, cross_tray=2, wind_anchors=3, pressure=13.56, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=9, panel_type=PanelType.NorthSouth, ballast=2, link_tray=0, cross_tray=0, wind_anchors=3, pressure=3.41, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=6, panel_type=PanelType.Corner, ballast=10, link_tray=0, cross_tray=0, wind_anchors=0, pressure=9.70),
Panel(wind_zone=6, panel_type=PanelType.EastWest, ballast=9, link_tray=2, cross_tray=0, wind_anchors=0, pressure=8.99)
Panel(wind_zone=5, panel_type=PanelType.Corner, ballast=25, link_tray=0, cross_tray=2, wind_anchors=0, pressure=21.200142),
Panel(wind_zone=6, panel_type=PanelType.NorthSouth, ballast=9, link_tray=0, cross_tray=1, wind_anchors=0, pressure=8.791605),
Panel(wind_zone=7, panel_type=PanelType.EastWest, ballast=28, link_tray=2, cross_tray=2, wind_anchors=0, pressure=23.487752),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=14, link_tray=0, cross_tray=2, wind_anchors=5, pressure=12.810842, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=8, panel_type=PanelType.Middle, ballast=15, link_tray=1, cross_tray=2, wind_anchors=3, pressure=13.538805, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=9, panel_type=PanelType.NorthSouth, ballast=2, link_tray=0, cross_tray=0, wind_anchors=3, pressure=3.360677, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=6, panel_type=PanelType.Corner, ballast=11, link_tray=0, cross_tray=0, wind_anchors=0, pressure=10.337906),
Panel(wind_zone=6, panel_type=PanelType.EastWest, ballast=9, link_tray=2, cross_tray=0, wind_anchors=0, pressure=8.812197)
]
assert_array_is_close(self.subject.ballast_and_trays_matrix(self.c_p_matrix, self.q_z, panels), expected_value, decimal=2)
@@ -160,13 +159,13 @@ class BallastCalculatorWhenSingleTiltAnd96CellTest(unittest.TestCase):
]
expected_value = array([
Panel(wind_zone=7, panel_type=PanelType.Corner, ballast=2, link_tray=0, cross_tray=0, wind_anchors=2, pressure=3.60),
Panel(wind_zone=7, panel_type=PanelType.NorthSouth, ballast=13, link_tray=0, cross_tray=1, wind_anchors=1, pressure=11.89),
Panel(wind_zone=9, panel_type=PanelType.EastWest, ballast=22, link_tray=2, cross_tray=1, wind_anchors=0, pressure=18.99),
Panel(wind_zone=5, panel_type=PanelType.Corner, ballast=25, link_tray=0, cross_tray=2, wind_anchors=0, pressure=21.32),
Panel(wind_zone=6, panel_type=PanelType.Middle, ballast=8, link_tray=1, cross_tray=0, wind_anchors=0, pressure=8.05),
Panel(wind_zone=8, panel_type=PanelType.NorthSouth, ballast=3, link_tray=0, cross_tray=1, wind_anchors=5, pressure=4.26, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=6, panel_type=PanelType.Corner, ballast=10, link_tray=0, cross_tray=0, wind_anchors=0, pressure=9.70),
Panel(wind_zone=7, panel_type=PanelType.Corner, ballast=2, link_tray=0, cross_tray=0, wind_anchors=2, pressure=3.473933),
Panel(wind_zone=7, panel_type=PanelType.NorthSouth, ballast=13, link_tray=0, cross_tray=1, wind_anchors=1, pressure=11.842260),
Panel(wind_zone=9, panel_type=PanelType.EastWest, ballast=22, link_tray=2, cross_tray=1, wind_anchors=0, pressure=18.819488),
Panel(wind_zone=5, panel_type=PanelType.Corner, ballast=25, link_tray=0, cross_tray=2, wind_anchors=0, pressure=21.200142),
Panel(wind_zone=6, panel_type=PanelType.Middle, ballast=8, link_tray=1, cross_tray=0, wind_anchors=0, pressure=8.015213),
Panel(wind_zone=8, panel_type=PanelType.NorthSouth, ballast=3, link_tray=0, cross_tray=1, wind_anchors=5, pressure=4.215623, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=6, panel_type=PanelType.Corner, ballast=11, link_tray=0, cross_tray=0, wind_anchors=0, pressure=10.337906),
])
assert_array_is_close(self.subject.ballast_and_trays_matrix(self.c_p_matrix, self.q_z, panels), expected_value, decimal=2)
@@ -175,43 +174,43 @@ class BallastCalculatorWhenSingleTiltAnd96CellTest(unittest.TestCase):
assert self.subject.ballast_tray_and_anchor_count(10, PanelType.Middle, 14, 1000, self.c_p_matrix,
30.155851).almost_equal(
Panel(wind_zone=10, panel_type=PanelType.Middle, ballast=0, link_tray=0, cross_tray=0,
wind_anchors=0, pressure=1.86),
wind_anchors=0, pressure=1.842977),
decimal=2)
def test_individual_ballast_block_uplift_greater_than_base_weight(self):
assert self.subject.ballast_tray_and_anchor_count(6, PanelType.Middle, 14, 1000, self.c_p_matrix,
self.q_z).almost_equal(
Panel(wind_zone=6, panel_type=PanelType.Middle, ballast=11, link_tray=1, cross_tray=1,
wind_anchors=0, pressure=7.91),
wind_anchors=0, pressure=7.878697),
decimal=2)
assert self.subject.ballast_tray_and_anchor_count(6, PanelType.Middle, 20, 1000, self.c_p_matrix,
self.q_z).almost_equal(
Panel(wind_zone=6, panel_type=PanelType.Middle, ballast=8, link_tray=1, cross_tray=0,
wind_anchors=0, pressure=8.05),
wind_anchors=0, pressure=8.015213),
decimal=2)
def test_ballast_blocks_exceeding_tray_capacity(self):
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Corner, 20, 1000, self.c_p_matrix,
self.q_z).almost_equal(
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=14, link_tray=0, cross_tray=2,
wind_anchors=5, pressure=12.93, warnings=[PanelWarnings.MaxPsf]),
wind_anchors=5, pressure=12.810842, warnings=[PanelWarnings.MaxPsf]),
decimal=2)
assert self.subject.ballast_tray_and_anchor_count(8, PanelType.Middle, 20, 1000, self.c_p_matrix,
self.q_z).almost_equal(
Panel(wind_zone=8, panel_type=PanelType.Middle, ballast=15, link_tray=1, cross_tray=2,
wind_anchors=3, pressure=13.56, warnings=[PanelWarnings.MaxPsf]),
wind_anchors=3, pressure=13.538805, warnings=[PanelWarnings.MaxPsf]),
decimal=2)
def test_ballast_blocks_when_panel_exceeds_max_system_pressure(self):
assert self.subject.ballast_tray_and_anchor_count(5, PanelType.Middle, 14, 10, self.c_p_matrix,
self.q_z).almost_equal(
Panel(wind_zone=5, panel_type=PanelType.Middle, ballast=0, link_tray=0, cross_tray=0,
wind_anchors=1, pressure=1.86),
wind_anchors=1, pressure=1.842977),
decimal=2)
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Corner, 14, 10, self.c_p_matrix,
self.q_z).almost_equal(
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=0, link_tray=0, cross_tray=0,
wind_anchors=6, pressure=2.08, warnings=[PanelWarnings.MaxPsf]),
wind_anchors=6, pressure=1.948606, warnings=[PanelWarnings.MaxPsf]),
decimal=2)
def test_when_max_system_pressure_is_lower_than_base_weight_pressure(self):
@@ -220,7 +219,7 @@ class BallastCalculatorWhenSingleTiltAnd96CellTest(unittest.TestCase):
assert self.subject.ballast_tray_and_anchor_count(5, PanelType.Middle, 12, max_system_pressure, self.c_p_matrix,
self.q_z).almost_equal(
Panel(wind_zone=5, panel_type=PanelType.Middle, ballast=0, link_tray=0, cross_tray=0,
wind_anchors=1, pressure=1.86),
wind_anchors=1, pressure=1.842977),
decimal=2)
def test_uplift(self):

View File

@@ -55,27 +55,27 @@ class BallastCalculatorWhenSingleTiltAndPSeriesTest(unittest.TestCase):
PanelType.Corner: {
'anchors': [7, 5, 3, 3, 2, 1, 0, 2, 7, 4, 0],
'ballast blocks': [5, 11, 3, 4, 2, 15, 13, 9, 7, 6, 11],
'pressure': ['5.13', '8.75', '3.83', '4.43', '3.22', '11.16', '9.86', '7.45', '6.33', '5.73', '8.65']
'pressure': ['5.07', '8.69', '3.77', '4.37', '3.17', '11.10', '9.80', '7.39', '6.37', '5.67', '8.59']
},
PanelType.NorthSouth: {
'anchors': [6, 5, 2, 2, 0, 1, 0, 2, 6, 3, 0],
'ballast blocks': [10, 3, 15, 14, 18, 12, 12, 5, 8, 14, 9],
'pressure': ['8.06', '3.74', '11.07', '10.37', '12.88', '9.17', '9.17', '4.85', '6.85', '10.47', '7.26']
'pressure': ['8.09', '3.78', '11.11', '10.41', '12.82', '9.20', '9.20', '4.89', '6.89', '10.50', '7.30']
},
PanelType.EastWest: {
'anchors': [5, 4, 2, 1, 1, 0, 0, 2, 6, 1, 0],
'ballast blocks': [8, 5, 1, 15, 14, 20, 12, 5, 8, 13, 6],
'pressure': ['6.94', '5.04', '2.56', '11.16', '10.56', '14.18', '9.26', '5.04', '6.94', '9.96', '5.57']
'pressure': ['6.87', '4.97', '2.50', '11.09', '10.49', '14.11', '9.19', '4.97', '6.87', '9.89', '5.51']
},
PanelType.Middle: {
'anchors': [5, 4, 1, 1, 0, 0, 0, 2, 6, 1, 0],
'ballast blocks': [6, 3, 15, 14, 18, 20, 11, 4, 4, 12, 6],
'pressure': ['5.60', '3.69', '11.02', '10.42', '12.83', '14.13', '8.51', '4.29', '4.39', '9.21', '5.43']
'pressure': ['5.59', '3.68', '11.01', '10.41', '12.82', '14.12', '8.60', '4.29', '4.38', '9.20', '5.44']
},
}
received_table = self.subject.summary_table(self.c_p_matrix, self.q_z)
print("===\r\n",received_table)
eq_(received_table.keys(), expected_value.keys())
for key in expected_value.keys():
received_table[key].pop('warnings')
@@ -97,21 +97,21 @@ class BallastCalculatorWhenSingleTiltAndPSeriesTest(unittest.TestCase):
expected_value = [
Panel(wind_zone=5, panel_type=PanelType.Corner, ballast=15, link_tray=0, cross_tray=1, wind_anchors=1,
pressure=11.16),
pressure=11.101436),
Panel(wind_zone=6, panel_type=PanelType.NorthSouth, ballast=12, link_tray=0, cross_tray=1, wind_anchors=0,
pressure=9.17),
pressure=9.202700),
Panel(wind_zone=7, panel_type=PanelType.EastWest, ballast=5, link_tray=2, cross_tray=0, wind_anchors=2,
pressure=5.04, warnings=[PanelWarnings.MaxPsf]),
pressure=4.967106, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=5, link_tray=0, cross_tray=1, wind_anchors=7,
pressure=5.13, warnings=[PanelWarnings.MaxPsf]),
pressure=5.070833, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=8, panel_type=PanelType.Middle, ballast=4, link_tray=1, cross_tray=1, wind_anchors=6,
pressure=4.39, warnings=[PanelWarnings.MaxPsf]),
pressure=4.380027, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=9, panel_type=PanelType.NorthSouth, ballast=14, link_tray=0, cross_tray=2, wind_anchors=3,
pressure=10.47, warnings=[PanelWarnings.MaxPsf]),
pressure=10.503803, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=6, panel_type=PanelType.Corner, ballast=13, link_tray=0, cross_tray=0, wind_anchors=0,
pressure=9.86),
pressure=9.800333),
Panel(wind_zone=6, panel_type=PanelType.EastWest, ballast=12, link_tray=2, cross_tray=0, wind_anchors=0,
pressure=9.26)
pressure=9.188528)
]
assert_array_is_close(self.subject.ballast_and_trays_matrix(self.c_p_matrix, self.q_z, panels),
@@ -170,19 +170,19 @@ class BallastCalculatorWhenSingleTiltAndPSeriesTest(unittest.TestCase):
expected_value = [
Panel(wind_zone=7, panel_type=PanelType.Corner, ballast=9, link_tray=0, cross_tray=0,
wind_anchors=2, pressure=7.44, warnings=[PanelWarnings.MaxPsf]),
wind_anchors=2, pressure=7.388092, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=7, panel_type=PanelType.NorthSouth, ballast=5, link_tray=0, cross_tray=0,
wind_anchors=2, pressure=4.85, warnings=[PanelWarnings.MaxPsf]),
wind_anchors=2, pressure=4.886296, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=9, panel_type=PanelType.EastWest, ballast=28, link_tray=2, cross_tray=2,
wind_anchors=0, pressure=19.10),
wind_anchors=0, pressure=19.027457),
Panel(wind_zone=5, panel_type=PanelType.Corner, ballast=15, link_tray=0, cross_tray=1,
wind_anchors=1, pressure=11.16),
Panel(wind_zone=6, panel_type=PanelType.Middle, ballast=11, link_tray=1, cross_tray=0,
wind_anchors=0, pressure=8.51),
wind_anchors=1, pressure=11.101436),
Panel(wind_zone=6, panel_type=PanelType.Middle, ballast=11, link_tray=1, cross_tray=1,
wind_anchors=0, pressure= 8.601449),
Panel(wind_zone=8, panel_type=PanelType.NorthSouth, ballast=8, link_tray=0, cross_tray=2,
wind_anchors=6, pressure=6.85, warnings=[PanelWarnings.MaxPsf]),
wind_anchors=6, pressure=6.885441, warnings=[PanelWarnings.MaxPsf]),
Panel(wind_zone=6, panel_type=PanelType.Corner, ballast=13, link_tray=0, cross_tray=0,
wind_anchors=0, pressure=9.86)
wind_anchors=0, pressure=9.800333)
]
assert_array_is_close(self.subject.ballast_and_trays_matrix(self.c_p_matrix, self.q_z, panel_data),
@@ -221,22 +221,22 @@ class BallastCalculatorWhenSingleTiltAndPSeriesTest(unittest.TestCase):
def test_ballast_and_tray_count_iterates_on_link_and_cross_trays_changing_weight_and_ballast_requirements(self):
panel = self.subject.ballast_tray_and_anchor_count(3, PanelType.NorthSouth, 20, 17, self.c_p_matrix, self.q_z)
assert_almost_equal(panel.pressure, 10.37, 2)
assert_almost_equal(panel.pressure, 10.408820882088209, 2)
def test_individual_ballast_block_uplift_greater_than_base_weight(self):
expected = Panel(wind_zone=6, panel_type=PanelType.Middle, ballast=16, link_tray=1, cross_tray=1,
wind_anchors=0, pressure=8.73)
wind_anchors=0, pressure=8.722061)
assert self.subject.ballast_tray_and_anchor_count(6, PanelType.Middle, 14, 1000, self.c_p_matrix,
self.q_z).almost_equal(expected, decimal=2)
expected = Panel(wind_zone=6, panel_type=PanelType.Middle, ballast=11, link_tray=1, cross_tray=0,
wind_anchors=0, pressure=8.51)
expected = Panel(wind_zone=6, panel_type=PanelType.Middle, ballast=11, link_tray=1, cross_tray=1,
wind_anchors=0, pressure=8.601449)
assert self.subject.ballast_tray_and_anchor_count(6, PanelType.Middle, 20, 1000, self.c_p_matrix,
self.q_z).almost_equal(expected, decimal=2)
def test_ballast_blocks_exceeding_tray_capacity(self):
expected = Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=5, link_tray=0, cross_tray=1,
wind_anchors=7, pressure=5.13, warnings=[PanelWarnings.MaxPsf])
wind_anchors=7, pressure=5.070833, warnings=[PanelWarnings.MaxPsf])
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Corner, 20, 1000, self.c_p_matrix,
self.q_z).almost_equal(expected, decimal=2)
@@ -253,7 +253,7 @@ class BallastCalculatorWhenSingleTiltAndPSeriesTest(unittest.TestCase):
self.q_z).almost_equal(expected, decimal=2)
expected = Panel(wind_zone=0, panel_type=PanelType.Corner, ballast=7, link_tray=0, cross_tray=1,
wind_anchors=7, pressure=5.07, warnings=[PanelWarnings.MaxPsf])
wind_anchors=7, pressure=5.010527, warnings=[PanelWarnings.MaxPsf])
assert self.subject.ballast_tray_and_anchor_count(0, PanelType.Corner, 14, 10, self.c_p_matrix,
self.q_z).almost_equal(expected, decimal=2)

View File

@@ -56,7 +56,7 @@ class CalculatorTest(unittest.TestCase):
self.subject = Calculator(self.values)
expected = [
Subarray(subarray_number=1, origin=Coordinate(0, 0), required_seismic_anchors=435, weight=170237,
Subarray(subarray_number=1, origin=Coordinate(0, 0), required_seismic_anchors=436, weight=170698.330000,
start_row=0, size=863, column_counted_geometrically=False, row_counted_geometrically=False,
row_count=130.5, column_count=98)
]
@@ -70,43 +70,43 @@ class CalculatorTest(unittest.TestCase):
self.subject = Calculator(self.values)
expected = [
Subarray(subarray_number=1, origin=Coordinate(0, 0), required_seismic_anchors=11, weight=23783, start_row=0,
Subarray(subarray_number=1, origin=Coordinate(0, 0), required_seismic_anchors=11, weight=23804, start_row=0,
size=192, column_counted_geometrically=False, row_counted_geometrically=False,
row_count=18, column_count=20),
Subarray(subarray_number=2, origin=Coordinate(0, 0), required_seismic_anchors=0, weight=13113,
Subarray(subarray_number=2, origin=Coordinate(0, 0), required_seismic_anchors=0, weight=13271,
start_row=192, size=96, column_counted_geometrically=False, row_counted_geometrically=False,
row_count=4, column_count=24),
Subarray(subarray_number=3, origin=Coordinate(0, 0), required_seismic_anchors=110, weight=40406,
Subarray(subarray_number=3, origin=Coordinate(0, 0), required_seismic_anchors=110, weight=40328,
start_row=288, size=312, column_counted_geometrically=False, row_counted_geometrically=False,
row_count=25, column_count=29),
Subarray(subarray_number=4, origin=Coordinate(0, 0), required_seismic_anchors=72, weight=23404,
Subarray(subarray_number=4, origin=Coordinate(0, 0), required_seismic_anchors=71, weight=23324,
start_row=600, size=168, column_counted_geometrically=False, row_counted_geometrically=False,
row_count=22, column_count=13),
Subarray(subarray_number=5, origin=Coordinate(0, 0), required_seismic_anchors=75, weight=35365,
Subarray(subarray_number=5, origin=Coordinate(0, 0), required_seismic_anchors=74, weight=35170,
start_row=768, size=234, column_counted_geometrically=False, row_counted_geometrically=False,
row_count=26, column_count=14),
Subarray(subarray_number=6, origin=Coordinate(0, 0), required_seismic_anchors=0, weight=10897,
Subarray(subarray_number=6, origin=Coordinate(0, 0), required_seismic_anchors=1, weight=11461,
start_row=1002, size=96, column_counted_geometrically=False, row_counted_geometrically=False,
row_count=10, column_count=12),
Subarray(subarray_number=7, origin=Coordinate(0, 0), required_seismic_anchors=29, weight=10951,
Subarray(subarray_number=7, origin=Coordinate(0, 0), required_seismic_anchors=28, weight=10865,
start_row=1098, size=96, column_counted_geometrically=False, row_counted_geometrically=False,
row_count=8, column_count=16),
Subarray(subarray_number=8, origin=Coordinate(0, 0), required_seismic_anchors=11, weight=10596,
Subarray(subarray_number=8, origin=Coordinate(0, 0), required_seismic_anchors=12, weight=10780,
start_row=1194, size=96, column_counted_geometrically=False, row_counted_geometrically=False,
row_count=15, column_count=17.5),
Subarray(subarray_number=9, origin=Coordinate(0, 0), required_seismic_anchors=0, weight=10544,
Subarray(subarray_number=9, origin=Coordinate(0, 0), required_seismic_anchors=0, weight= 10989,
start_row=1290, size=96, column_counted_geometrically=False, row_counted_geometrically=False,
row_count=8, column_count=16),
Subarray(subarray_number=10, origin=Coordinate(0, 0), required_seismic_anchors=0, weight=4819,
Subarray(subarray_number=10, origin=Coordinate(0, 0), required_seismic_anchors=0, weight=4901,
start_row=1386, size=48, column_counted_geometrically=False, row_counted_geometrically=False,
row_count=8, column_count=6),
Subarray(subarray_number=11, origin=Coordinate(0, 0), required_seismic_anchors=0, weight=10857,
Subarray(subarray_number=11, origin=Coordinate(0, 0), required_seismic_anchors=0, weight=10766,
start_row=1434, size=96, column_counted_geometrically=False, row_counted_geometrically=False,
row_count=13, column_count=13),
Subarray(subarray_number=12, origin=Coordinate(0, 0), required_seismic_anchors=0, weight=6330,
Subarray(subarray_number=12, origin=Coordinate(0, 0), required_seismic_anchors=0, weight=6350,
start_row=1530, size=54, column_counted_geometrically=False, row_counted_geometrically=False,
row_count=8, column_count=8),
Subarray(subarray_number=13, origin=Coordinate(0, 0), required_seismic_anchors=0, weight=4201,
Subarray(subarray_number=13, origin=Coordinate(0, 0), required_seismic_anchors=0, weight=4239,
start_row=1584, size=48, column_counted_geometrically=False, row_counted_geometrically=False,
row_count=6, column_count=8)
]
@@ -140,15 +140,15 @@ class CalculatorTest(unittest.TestCase):
self.subject = Calculator(self.values)
received_values = self.subject.summary_values()
expected_values = array([
{'label': 'Total System Weight (lbs)', 'value': 2684},
{'label': 'Max PSF', 'value': 8.03},
{'label': 'Avg PSF', 'value': 5.39},
{'label': 'Total System Weight (lbs)', 'value': 2694},
{'label': 'Max PSF', 'value': 7.9},
{'label': 'Avg PSF', 'value': 5.41},
{'label': 'Total Anchors', 'value': 5},
{'label': 'Total Ballast', 'value': 131},
{'label': 'Max Possible System Weight', 'value': 2789.0},
{'label': 'Max System Weight Ballast Block', 'value': 16},
{'label': 'Total Ballast', 'value': 135},
{'label': 'Max Possible System Weight', 'value': 2792.0},
{'label': 'Max System Weight Ballast Block', 'value': 17},
{'label': 'Seismic Anchor Max. Spacing', 'value': 12},
])
assert_array_equal(received_values, expected_values)
@@ -173,13 +173,13 @@ class CalculatorTest(unittest.TestCase):
received_values = self.subject.documentation_summary_values()
expected_values = {
'total_system_weight': 2684,
'max_psf': 8.03,
'ave_psf': 5.39,
'total_system_weight': 2694,
'max_psf': 7.9,
'ave_psf': 5.41,
'total_anchors': 5,
'total_ballast': 131,
'max_possible_system_weight': 2789.0,
'max_system_weight_ballast_block': 16,
'total_ballast': 135,
'max_possible_system_weight': 2792.0,
'max_system_weight_ballast_block': 17,
'seismic_anchor_max_spacing': 12
}
eq_(received_values, expected_values)
@@ -326,6 +326,8 @@ class CalculatorTest(unittest.TestCase):
expected_csv = expected_file.read()
reader = csv.reader(expected_csv.splitlines(), dialect='excel-tab')
expected = array([row for row in reader])
print("EXPECTED")
print(expected)
power_station_1 = PowerStation(
description='1',
@@ -367,6 +369,8 @@ class CalculatorTest(unittest.TestCase):
)
self.site.power_stations = [power_station_1, power_station_2]
print("COMPUTE")
print(self.subject.compute_bom())
assert_array_equal(self.subject.compute_bom(), expected)
def test_documentation_bom(self):
@@ -404,91 +408,90 @@ class CalculatorTest(unittest.TestCase):
self.site.cad_file = csv_content
self.subject = Calculator(self.values)
expected = [
('104813', 50),
('105317', 0),
('106925', 50),
('107538', 0),
('107549', 100),
('107551', 50),
('107586', 100),
('111147', 0),
('114961', 50),
('507985', 0),
('512021', 9),
('512199', 140),
('512200', 3480),
('512510', 196),
('512511', 196),
('512575', 1),
('512660', 2),
('512661', 2),
('512662', 4),
('512663', 2),
('523923', 0),
('512910', 1),
('513007', 50),
('513299', 0),
('513300', 0),
('513301', 0),
('513302', 0),
('513303', 1),
('513304', 0),
('513586', 0),
('513831', 0),
('513832', 0),
('513833', 670),
('513836', 0),
('513843', 262),
('513844', 214),
('514056', 1000),
('514057', 1000),
('514265', 179),
('514435', 0),
('514436', 0),
('514437', 2),
('514438', 2),
('514439', 0),
('514440', 0),
('514477', 2),
('514478', 0),
('523924', 0),
('523921', 1),
('523922', 0),
('514697', 1),
('514698', 1),
('514865', 50),
('515059', 2),
('515063', 4000),
('515928', 261),
('515929', 0),
('516043', 0),
('516045', 0),
('517463', 0),
('517871', 139),
('518058', 2),
('518059', 0),
('518331', 2),
('518477', 275),
('519008', 0),
('520301', 0),
('520302', 0),
('520303', 0),
('520306', 0),
('521031', 2),
('521363', 0),
('521797', 0),
('521798', 0),
('522020', 0),
('805615', 2),
('521794', 196),
('521795', 196),
('anchors', 262),
('ballast', 6786),
('modules', 1726)
]
expected = [('521794', 196),
('521795', 196),
('514056', 1000),
('modules', 1726),
('513843', 263),
('anchors', 263),
('518477', 275),
('513833', 670),
('513844', 214),
('ballast', 6777),
('515928', 261),
('517871', 139),
('514057', 1000),
('515063', 4000),
('512200', 3480),
('514265', 179),
('513303', 1),
('512660', 2),
('512661', 2),
('512662', 4),
('512663', 2),
('518331', 2),
('518058', 2),
('104813', 50),
('107551', 50),
('514865', 50),
('106925', 50),
('523921', 1),
('514438', 2),
('514437', 2),
('512910', 1),
('805615', 2),
('521031', 2),
('512575', 1),
('514698', 1),
('513007', 50),
('114961', 50),
('107549', 100),
('107586', 100),
('512021', 9),
('512199', 140),
('512511', 196),
('512510', 196),
('515929', 0),
('514477', 2),
('515059', 2),
('514697', 1),
('513831', 0),
('513836', 0),
('520301', 0),
('520302', 0),
('520303', 0),
('520306', 0),
('513832', 0),
('514435', 0),
('514436', 0),
('514439', 0),
('514440', 0),
('523922', 0),
('523923', 0),
('523924', 0),
('513299', 0),
('513301', 0),
('514478', 0),
('513300', 0),
('513302', 0),
('513304', 0),
('519008', 0),
('518059', 0),
('105317', 0),
('111147', 0),
('107538', 0),
('516045', 0),
('516043', 0),
('513586', 0),
('507985', 0),
('522020', 0),
('521798', 0),
('521797', 0),
('521363', 0),
('517463', 0)]
print("===")
print(self.subject.documentation_bom())
assert_array_equal(sorted(self.subject.documentation_bom()), sorted(expected))
# Performance Tests

View File

@@ -118,7 +118,7 @@ class EbomCalculatorTest(unittest.TestCase):
wire_clip_large: 2907,
cable_support_lid: 368,
cable_support: 368,
rear_skirt: -140,
rear_skirt_1_1: -140,
dc_switch_bracket: 2,
front_legs: 23,
back_legs: 23,
@@ -193,10 +193,10 @@ class EbomCalculatorTest(unittest.TestCase):
self.user_values.module_type.return_value = ModuleType.Cell96
expected_output = {
stump: 65,
wire_clip_large: 513,
wire_clip_large: 590,
cable_support_lid: 0,
cable_support: 0,
rear_skirt: 0,
rear_skirt_1_1: 0,
rubber_foot: 9,
delta_inverter_leg: 9,
harness_4_string_mf: 4,
@@ -228,10 +228,10 @@ class EbomCalculatorTest(unittest.TestCase):
self.user_values.module_type.return_value = ModuleType.Cell96
expected_output = {
stump: 0,
wire_clip_large: 171,
wire_clip_large: 200,
cable_support_lid: 0,
cable_support: 0,
rear_skirt: 0,
rear_skirt_1_1: 0,
rubber_foot: 3,
delta_inverter_leg: 3,
harness_4_string_mf: 2,
@@ -380,7 +380,7 @@ class EbomCalculatorTest(unittest.TestCase):
wire_clip_large: 2394,
cable_support: 270,
cable_support_lid: 270,
rear_skirt: -103,
rear_skirt_1_1: -103,
ethernet_plug: 7.2,
monitor_power_plug: 1,
sunshade_bolt: 12,
@@ -492,7 +492,7 @@ class EbomCalculatorTest(unittest.TestCase):
wire_clip_large: 2394,
cable_support: 270,
cable_support_lid: 270,
rear_skirt: -103,
rear_skirt_1_1: -103,
ethernet_plug: 7.2,
monitor_power_plug: 1,
sunshade_bolt: 12,
@@ -604,7 +604,7 @@ class EbomCalculatorTest(unittest.TestCase):
wire_clip_large: 2394,
cable_support: 270,
cable_support_lid: 270,
rear_skirt: -103,
rear_skirt_1_1: -103,
ethernet_plug: 7.2,
monitor_power_plug: 1,
fuseshade: 14,
@@ -624,13 +624,13 @@ class EbomCalculatorTest(unittest.TestCase):
}]
self.user_values.system_type.return_value = SystemType.singleTilt
self.user_values.module_type.return_value = ModuleType.Cell96
expected_output = {
monitor_power_plug: 1,
stump: 0,
cable_support: 0,
cable_support_lid: 0,
rear_skirt: 0,
rear_skirt_1_1: 0,
monitor_controller_480_v:1,
}
@@ -644,13 +644,13 @@ class EbomCalculatorTest(unittest.TestCase):
}]
self.user_values.system_type.return_value = SystemType.singleTilt
self.user_values.module_type.return_value = ModuleType.Cell96
expected_output = {
monitor_power_plug: 1,
stump: 0,
cable_support: 0,
cable_support_lid: 0,
rear_skirt: 0,
rear_skirt_1_1: 0,
monitor_controller_480_v:1,
flat_washer: 4,
channel_nut: 4,
@@ -771,7 +771,7 @@ class EbomCalculatorTest(unittest.TestCase):
wire_clip_large: 2394,
cable_support: 694,
cable_support_lid: 694,
rear_skirt: -264,
rear_skirt_1_1: -264,
ethernet_plug: 7.2,
monitor_power_plug: 1,
fuseshade: 14,
@@ -922,7 +922,7 @@ class EbomCalculatorTest(unittest.TestCase):
wire_clip_large: 2394,
cable_support: 270,
cable_support_lid: 270,
rear_skirt: -103,
rear_skirt_1_1: -103,
ethernet_plug: 7.2,
monitor_power_plug: 1,
sunshade_bolt: 12,
@@ -968,7 +968,7 @@ class EbomCalculatorTest(unittest.TestCase):
wire_clip_large: 2394,
cable_support: 270,
cable_support_lid: 270,
rear_skirt: -103,
rear_skirt_1_1: -103,
ethernet_plug: 7.2,
sunshade_bolt: 12,
sunshade_washer: 12,
@@ -1027,7 +1027,7 @@ class EbomCalculatorTest(unittest.TestCase):
mounting_back_plate: 1,
cable_support: 0,
cable_support_lid: 0,
rear_skirt: 0,
rear_skirt_1_1: 0,
monitor_power_plug: 1,
}
@@ -1046,7 +1046,7 @@ class EbomCalculatorTest(unittest.TestCase):
mounting_back_plate: 1,
cable_support: 0,
cable_support_lid: 0,
rear_skirt: 0,
rear_skirt_1_1: 0,
monitor_power_plug: 1,
}
@@ -1055,14 +1055,14 @@ class EbomCalculatorTest(unittest.TestCase):
stump: 0,
cable_support: 0,
cable_support_lid: 0,
rear_skirt: 0
rear_skirt_1_1: 0
}
expected_output_240_pseries = {
stump: 0,
cable_support_lid: 0,
cable_support: 0,
rear_skirt: 0,
rear_skirt_1_1: 0,
monitor_controller_240_v: 1,
}
@@ -1116,14 +1116,13 @@ class EbomCalculatorTest(unittest.TestCase):
stump: 0,
cable_support: 0,
cable_support_lid: 0,
rear_skirt: 0,
rear_skirt_1_1: 0,
monitor_controller_480_v: 1,
ethernet_plug: 2,
}
assert_dictionary_equal(self.subject.compute_ebom(), expected_output)
def test_computes_ebom_with_power_monitor_SMA_brand(self):
def test_computes_ebom_with_power_monitor_SMA_brand(self):
self.user_values.inverter_brands.return_value = [{'inverter_brand_id':1}]
self.user_values.power_monitors.return_value = [{
'monitor_id': 'foo',
@@ -1131,13 +1130,13 @@ class EbomCalculatorTest(unittest.TestCase):
}]
self.user_values.system_type.return_value = SystemType.singleTilt
self.user_values.module_type.return_value = ModuleType.Cell96
expected_output = {
monitor_power_plug: 1,
stump: 0,
cable_support: 0,
cable_support_lid: 0,
rear_skirt: 0,
rear_skirt_1_1: 0,
monitor_controller_480_v:1,
flat_washer: 4,
channel_nut: 4,
@@ -1152,7 +1151,3 @@ class EbomCalculatorTest(unittest.TestCase):
}
assert_dictionary_equal(self.subject.compute_ebom(), expected_output)

View File

@@ -99,7 +99,7 @@ class MechanicalBomCalculatorWhenSingleTilt96CellTest(unittest.TestCase):
cross_tray: 5,
rubber_foot: 4.4,
front_skirt: 24,
rear_skirt: 44,
rear_skirt_1_1: 44,
leading_tray: 25,
ballast: 192,
anchor: 15, # 12 + the 3 seismic anchors

View File

@@ -54,8 +54,8 @@ class SummaryValuesTest(unittest.TestCase):
{'label': 'Avg PSF', 'value': 10.06},
{'label': 'Total Anchors', 'value': 7},
{'label': 'Total Ballast', 'value': 8},
{'label': 'Max Possible System Weight', 'value': 1568.0},
{'label': 'Max System Weight Ballast Block', 'value': 14},
{'label': 'Max Possible System Weight', 'value': 1563.0},
{'label': 'Max System Weight Ballast Block', 'value': 16},
{'label': 'Seismic Anchor Max. Spacing', 'value': seismic_interval}
]
eq_(result, expected)
@@ -83,8 +83,8 @@ class SummaryValuesTest(unittest.TestCase):
'ave_psf': 10.06,
'total_anchors': 7,
'total_ballast': 8,
'max_possible_system_weight': 1568.0,
'max_system_weight_ballast_block': 14,
'max_possible_system_weight': 1563.0,
'max_system_weight_ballast_block': 16,
'seismic_anchor_max_spacing': 10
}
@@ -111,7 +111,7 @@ class SummaryValuesTest(unittest.TestCase):
result = self.subject.find_max_system_weight(panels, self.c_p_matrix, self.q_z, self.ballast_calculator)
assert_almost_equal(result, (1568, 14), decimal=0)
assert_almost_equal(result, (1563, 16), decimal=0)
def test_find_max_system_weight_does_not_modify_panels_list(self):
panels = [

View File

@@ -1,8 +1,8 @@
HANDLE BLOCKNAME WIND POS SUBARRAY PSF BAL LTRAY XTRAY ANC ID XCOORD YCOORD ANGLE
'21EEE0 *U593 C 1 1 12.9 20 2 1 - 1 0 0 0
'21EECC *U824 C 2 1 11.35 17 2 1 - 2 0 0 0
'21EEB8 *U824 E 3 1 2.26 0 1 - - 3 0 0 0
'21EEE0 *U593 C 1 1 12.91 20 2 1 - 1 0 0 0
'21EECC *U824 C 2 1 11.34 17 2 1 - 2 0 0 0
'21EEB8 *U824 E 3 1 2.28 0 1 - - 3 0 0 0
'21EEA4 *U593 A 1 1 5.55 6 2 - 2 4 0 0 0
'21E940 *U171 B 4 1 13.83 22 1 2 - 5 0 0 0
'21E940 *U171 B 4 1 13.87 22 1 2 - 5 0 0 0
'21E92C *U824 D 1 1 7.1 9 2 - - 6 0 0 0
'21E918 *U593 B 1 1 9.8 14 2 1 1 7 0 0 0
1 HANDLE BLOCKNAME WIND POS SUBARRAY PSF BAL LTRAY XTRAY ANC ID XCOORD YCOORD ANGLE
2 '21EEE0 *U593 C 1 1 12.9 12.91 20 2 1 - 1 0 0 0
3 '21EECC *U824 C 2 1 11.35 11.34 17 2 1 - 2 0 0 0
4 '21EEB8 *U824 E 3 1 2.26 2.28 0 1 - - 3 0 0 0
5 '21EEA4 *U593 A 1 1 5.55 6 2 - 2 4 0 0 0
6 '21E940 *U171 B 4 1 13.83 13.87 22 1 2 - 5 0 0 0
7 '21E92C *U824 D 1 1 7.1 9 2 - - 6 0 0 0
8 '21E918 *U593 B 1 1 9.8 14 2 1 1 7 0 0 0

View File

@@ -1,7 +1,7 @@
Part # Description Total
512200 CLIP, WIRE FORMED, CABLE MANAGEMENT, INSIDE, 352MM ^ 2 3480
513833 TRAY, LINK, HELIX ROOF 670
513843 PLATE, ANCHOR, HELIX ROOF 262
513843 PLATE, ANCHOR, HELIX ROOF 263
513844 TRAY, OPTIONAL BALLAST, HELIX ROOF 214
514056 BASE, CHASSIS, DUAL TILT, HELIX ROOF 1000
514057 PLATFORM, CHASSIS, DUAL TILT, HELIX ROOF 1000
@@ -12,6 +12,6 @@ Part # Description Total
518477 WASHER, FLAT, 3/8, 1.00 OD, 18-8 SS 275
521794 DEFLECTOR, LH, HELIX ROOF V1.1 196
521795 DEFLECTOR, RH, HELIX ROOF V1.1 196
Contractor Supplied Ballast Blocks 6786
TBD Anchors 262
Contractor Supplied Ballast Blocks 6777
TBD Anchors 263
TBD Modules 1726
1 Part # Description Total
2 512200 CLIP, WIRE FORMED, CABLE MANAGEMENT, INSIDE, 352MM ^ 2 3480
3 513833 TRAY, LINK, HELIX ROOF 670
4 513843 PLATE, ANCHOR, HELIX ROOF 262 263
5 513844 TRAY, OPTIONAL BALLAST, HELIX ROOF 214
6 514056 BASE, CHASSIS, DUAL TILT, HELIX ROOF 1000
7 514057 PLATFORM, CHASSIS, DUAL TILT, HELIX ROOF 1000
12 518477 WASHER, FLAT, 3/8, 1.00 OD, 18-8 SS 275
13 521794 DEFLECTOR, LH, HELIX ROOF V1.1 196
14 521795 DEFLECTOR, RH, HELIX ROOF V1.1 196
15 Contractor Supplied Ballast Blocks 6786 6777
16 TBD Anchors 262 263
17 TBD Modules 1726

View File

@@ -14,7 +14,7 @@
513299 COMBINER BOX, AC, 4 INPUT, NO AUX, W/ CONNECTOR 3
513303 COMBINER BOX, AC, 2 INPUT, NO AUX, W/ CONNECTOR 1
513833 TRAY, LINK, HELIX ROOF 670
513843 PLATE, ANCHOR, HELIX ROOF 262
513843 PLATE, ANCHOR, HELIX ROOF 263
513844 TRAY, OPTIONAL BALLAST, HELIX ROOF 214
514056 BASE, CHASSIS, DUAL TILT, HELIX ROOF 1000
514057 PLATFORM, CHASSIS, DUAL TILT, HELIX ROOF 1000
@@ -42,6 +42,6 @@
523922 INVERTER, SMA, STP, 24000TL-US-10 (SPR-24000m-3-H), AFCI, CONNECTORIZED, UTX XL REV D DC CONNECTORS 1
523923 INVERTER, SMA, STP, 12000TL-US-10 (SPR-12000m-3-H), AFCI, CONNECTORIZED, UTX XL REV D DC CONNECTORS 3
523924 INVERTER, SMA, STP, 15000TL-US-10 (SPR-15000m-3-H), AFCI, CONNECTORIZED, UTX XL REV D DC CONNECTORS 9
Contractor Supplied Ballast Blocks 6786
TBD Anchors 262
Contractor Supplied Ballast Blocks 6777
TBD Anchors 263
TBD Modules 1726
Can't render this file because it contains an unexpected character in line 28 and column 30.

View File

@@ -1,20 +1,20 @@
HANDLE BLOCKNAME WIND POS SUBARRAY PSF BAL LTRAY XTRAY ANC ID XCOORD YCOORD ANGLE
'40EC39 *U5399 H 1 8 14.37 16 - 1 - 1 0 0 0
'40EC22 *U5402 H 3 8 12.89 14 2 1 - 2 0 0 0
'40EC0B *U5402 H 1 8 14.37 16 - 1 - 3 0 0 0
'40EBF4 *U5402 H 1 8 14.37 16 - 1 - 4 0 0 0
'40EBDD *U5402 H 3 8 12.89 14 2 1 - 5 0 0 0
'40EBC6 *U5402 H 3 8 12.89 14 2 1 - 6 0 0 0
'40EBAF *U5402 H 3 8 12.89 14 2 1 - 7 0 0 0
'40EB98 *U5399 H 1 8 14.37 16 - 1 - 8 0 0 0
'40EB81 *U5401 H 2 8 12.65 14 - 1 - 9 0 0 0
'40E8B8 *U5401 H 2 8 12.65 14 - 1 - 10 0 0 0
'40E8A1 *U5401 I 2 8 1.89 0 - - 2 11 0 0 0
'40E88A *U5400 H 4 8 10.42 11 1 1 - 12 0 0 0
'40E873 *U5400 H 4 8 10.42 11 1 1 - 13 0 0 0
'40E85C *U5400 H 4 8 10.42 11 1 1 - 14 0 0 0
'40E845 *U5400 H 4 8 10.42 11 1 1 - 15 0 0 0
'40E82E *U5400 H 4 8 10.42 11 1 1 - 16 0 0 0
'40E817 *U5400 H 4 8 10.42 11 1 1 - 17 0 0 0
'40E800 *U5401 H 2 8 12.65 14 - 1 - 18 0 0 0
'40E7E9 *U5401 I 2 8 1.89 0 - - 2 19 0 0 0
'40EC39 *U5399 H 1 8 14.24 16 - 1 - 1 0 0 0
'40EC22 *U5402 H 3 8 12.72 14 2 1 - 2 0 0 0
'40EC0B *U5402 H 1 8 14.24 16 - 1 - 3 0 0 0
'40EBF4 *U5402 H 1 8 14.24 16 - 1 - 4 0 0 0
'40EBDD *U5402 H 3 8 12.72 14 2 1 - 5 0 0 0
'40EBC6 *U5402 H 3 8 12.72 14 2 1 - 6 0 0 0
'40EBAF *U5402 H 3 8 12.72 14 2 1 - 7 0 0 0
'40EB98 *U5399 H 1 8 14.24 16 - 1 - 8 0 0 0
'40EB81 *U5401 H 2 8 12.6 14 - 1 - 9 0 0 0
'40E8B8 *U5401 H 2 8 12.6 14 - 1 - 10 0 0 0
'40E8A1 *U5401 I 2 8 1.84 0 - - 2 11 0 0 0
'40E88A *U5400 H 4 8 10.4 11 1 1 - 12 0 0 0
'40E873 *U5400 H 4 8 10.4 11 1 1 - 13 0 0 0
'40E85C *U5400 H 4 8 10.4 11 1 1 - 14 0 0 0
'40E845 *U5400 H 4 8 10.4 11 1 1 - 15 0 0 0
'40E82E *U5400 H 4 8 10.4 11 1 1 - 16 0 0 0
'40E817 *U5400 H 4 8 10.4 11 1 1 - 17 0 0 0
'40E800 *U5401 H 2 8 12.6 14 - 1 - 18 0 0 0
'40E7E9 *U5401 I 2 8 1.84 0 - - 2 19 0 0 0
1 HANDLE BLOCKNAME WIND POS SUBARRAY PSF BAL LTRAY XTRAY ANC ID XCOORD YCOORD ANGLE
2 '40EC39 *U5399 H 1 8 14.37 14.24 16 - 1 - 1 0 0 0
3 '40EC22 *U5402 H 3 8 12.89 12.72 14 2 1 - 2 0 0 0
4 '40EC0B *U5402 H 1 8 14.37 14.24 16 - 1 - 3 0 0 0
5 '40EBF4 *U5402 H 1 8 14.37 14.24 16 - 1 - 4 0 0 0
6 '40EBDD *U5402 H 3 8 12.89 12.72 14 2 1 - 5 0 0 0
7 '40EBC6 *U5402 H 3 8 12.89 12.72 14 2 1 - 6 0 0 0
8 '40EBAF *U5402 H 3 8 12.89 12.72 14 2 1 - 7 0 0 0
9 '40EB98 *U5399 H 1 8 14.37 14.24 16 - 1 - 8 0 0 0
10 '40EB81 *U5401 H 2 8 12.65 12.6 14 - 1 - 9 0 0 0
11 '40E8B8 *U5401 H 2 8 12.65 12.6 14 - 1 - 10 0 0 0
12 '40E8A1 *U5401 I 2 8 1.89 1.84 0 - - 2 11 0 0 0
13 '40E88A *U5400 H 4 8 10.42 10.4 11 1 1 - 12 0 0 0
14 '40E873 *U5400 H 4 8 10.42 10.4 11 1 1 - 13 0 0 0
15 '40E85C *U5400 H 4 8 10.42 10.4 11 1 1 - 14 0 0 0
16 '40E845 *U5400 H 4 8 10.42 10.4 11 1 1 - 15 0 0 0
17 '40E82E *U5400 H 4 8 10.42 10.4 11 1 1 - 16 0 0 0
18 '40E817 *U5400 H 4 8 10.42 10.4 11 1 1 - 17 0 0 0
19 '40E800 *U5401 H 2 8 12.65 12.6 14 - 1 - 18 0 0 0
20 '40E7E9 *U5401 I 2 8 1.89 1.84 0 - - 2 19 0 0 0

View File

@@ -27,7 +27,7 @@ Part # Description Total
513832 TRAY, FOLLOWING, HELIX ROOF 210
513833 TRAY, LINK, HELIX ROOF 1271
513836 SPOILER, SINGLE TILT, HELIX ROOF 1632
513843 PLATE, ANCHOR, HELIX ROOF 414
513843 PLATE, ANCHOR, HELIX ROOF 416
513844 TRAY, OPTIONAL BALLAST, HELIX ROOF 274
514265 FOOT, RECYCLED RUBBER, HELIX ROOF 230
514435 HARNESS, DC COMBINATION, W/ FUSE, 3 STRING, FEMALES TO MALE, HELIX 4
@@ -43,13 +43,13 @@ Part # Description Total
514865 BOLT, HH, 3/8-16 X 1/2, 18-8 SS 350
515059 ASSY, WHIP TRAY W/FUSE CLIPS, INVERTER, HELIX 16
515928 FRONT SKIRT, HELIX ROOF 197
515929 REAR SKIRT, HELIX ROOF 1469
516043 AC SWITCH, CONNECTORIZED, HELIX ROOF 2
516045 AC SPLICE BOX, CONNECTORIZED, HELIX ROOF 1
517871 TRAY, LEADING, HELIX ROOF, RIVETED VERSION 210
518058 CONNECTOR, ETHERNET, PLUG, RJ-45, WEATHERPROOF, SHIELDED 15
518331 MOUNTING BACK PLATE, INVERTER/PANEL BOARD, HELIX ROOF/TRACKER 22
518477 WASHER, FLAT, 3/8, 1.00 OD, 18-8 SS 425
520301 REAR SKIRT, HELIX ROOF V1.1 1469
521031 WASHER, FLAT, M10 X 20MM OD, SS 10
521794 DEFLECTOR, LH, HELIX ROOF V1.1 171
521795 DEFLECTOR, RH, HELIX ROOF V1.1 171
@@ -57,6 +57,6 @@ Part # Description Total
523922 INVERTER, SMA, STP, 24000TL-US-10 (SPR-24000m-3-H), AFCI, CONNECTORIZED, UTX XL REV D DC CONNECTORS 3
523924 INVERTER, SMA, STP, 15000TL-US-10 (SPR-15000m-3-H), AFCI, CONNECTORIZED, UTX XL REV D DC CONNECTORS 6
805615 SCREW, HEXAGONAL HEAD, M10X20, SS A2 10
Contractor Supplied Ballast Blocks 10150
TBD Anchors 414
Contractor Supplied Ballast Blocks 10448
TBD Anchors 416
TBD Modules 1632
Can't render this file because it contains an unexpected character in line 10 and column 30.

View File

@@ -1,3 +1,3 @@
HANDLE BLOCKNAME WIND POS SUBARRAY PSF BAL LTRAY XTRAY ANC ID XCOORD YCOORD ANGLE
'40EC39 *U5399 A 1 1 2.4 0 2 - 1 1 0 0 0
'40EC22 *U5402 A 1 1 2.4 0 2 - 1 2 0 0 0
'40EC39 *U5399 A 1 1 2.39 0 2 - 1 1 0 0 0
'40EC22 *U5402 A 1 1 2.39 0 2 - 1 2 0 0 0

1073
test/fixtures/irvine_fortune_result.txt vendored Normal file

File diff suppressed because it is too large Load Diff

169
test/fixtures/wind data_hdt.txt vendored Normal file
View File

@@ -0,0 +1,169 @@
HANDLE BLOCKNAME WIND POS SUBARRAY PSF BAL LTRAY XTRAY ANC ID XCOORD YCOORD ANGLE
'1A66A5 *U5350 B 1 3 2.76 1 2 - 1 1 13906.282 -20190.065 4.124
'1A667F *U5350 B 1 3 2.76 1 2 - 1 2 13818.27 -20196.412 4.124
'1A6633 *U5350 B 1 2 2.76 1 2 - 1 3 13647.543 -20208.723 4.124
'1A6529 *U5425 B 3 3 2.26 0 1 - 1 4 13910.752 -20252.064 4.124
'1A6503 *U5425 B 3 3 2.26 0 1 - 1 5 13822.741 -20258.411 4.124
'1A64B7 *U5425 B 3 2 2.26 0 1 - 1 6 13652.014 -20270.722 4.124
'1A63AD *U5425 B 3 3 2.26 0 1 - 1 7 13915.223 -20314.063 4.124
'1A6387 *U5425 B 3 3 2.26 0 1 - 1 8 13827.212 -20320.41 4.124
'1A633B *U5425 B 3 2 2.26 0 1 - 1 9 13656.484 -20332.721 4.124
'1A6231 *U5425 C 3 3 6.36 11 1 1 - 10 13919.694 -20376.062 4.124
'1A620B *U5425 C 3 3 6.36 11 1 1 - 11 13831.682 -20382.409 4.124
'1A61BF *U5425 C 3 2 6.36 11 1 1 - 12 13660.955 -20394.72 4.124
'1A60B5 *U5425 C 3 3 6.36 11 1 1 - 13 13924.164 -20438.061 4.124
'1A608F *U5425 C 3 3 6.36 11 1 1 - 14 13836.153 -20444.408 4.124
'1A6043 *U5425 C 3 2 6.36 11 1 1 - 15 13665.426 -20456.719 4.124
'1A5F39 *U5425 C 3 3 6.36 11 1 1 - 16 13928.635 -20500.06 4.124
'1A5F13 *U5425 C 3 3 6.36 11 1 1 - 17 13840.624 -20506.407 4.124
'1A5EC7 *U5425 C 3 2 6.36 11 1 1 - 18 13669.896 -20518.718 4.124
'1A5DBD *U5425 C 3 3 6.36 11 1 1 - 19 13933.106 -20562.059 4.124
'1A5D97 *U5425 C 3 3 6.36 11 1 1 - 20 13845.094 -20568.406 4.124
'1A5D4B *U5425 C 3 2 6.36 11 1 1 - 21 13674.367 -20580.717 4.124
'1A5C41 *U5425 C 3 3 6.36 11 1 1 - 22 13937.577 -20624.058 4.124
'1A5C1B *U5425 C 3 3 6.36 11 1 1 - 23 13849.565 -20630.405 4.124
'1A5BCF *U5425 C 3 2 6.36 11 1 1 - 24 13678.838 -20642.716 4.124
'1A5AC5 *U5425 C 3 3 6.36 11 1 1 - 25 13942.047 -20686.057 4.124
'1A5A9F *U5425 C 3 3 6.36 11 1 1 - 26 13854.036 -20692.404 4.124
'1A5A53 *U5425 C 3 2 6.36 11 1 1 - 27 13683.309 -20704.715 4.124
'1A5949 *U5425 C 3 3 6.36 11 1 1 - 28 13946.518 -20748.056 4.124
'1A5923 *U5425 C 3 3 6.36 11 1 1 - 29 13858.506 -20754.403 4.124
'1A58D7 *U5425 C 3 2 6.36 11 1 1 - 30 13687.779 -20766.714 4.124
'1A57CD *U5425 C 3 3 6.36 11 1 1 - 31 13950.989 -20810.056 4.124
'1A57A7 *U5425 C 3 3 6.36 11 1 1 - 32 13862.977 -20816.402 4.124
'1A575B *U5425 C 3 2 6.36 11 1 1 - 33 13692.25 -20828.713 4.124
'1A5651 *U5425 C 3 3 6.36 11 1 1 - 34 13955.459 -20872.055 4.124
'1A562B *U5425 C 3 3 6.36 11 1 1 - 35 13867.448 -20878.401 4.124
'1A55DF *U5425 C 3 2 6.36 11 1 1 - 36 13696.721 -20890.712 4.124
'1A54D5 *U5425 C 3 3 6.36 11 1 1 - 37 13959.93 -20934.054 4.124
'1A54AF *U5425 C 3 3 6.36 11 1 1 - 38 13871.918 -20940.4 4.124
'1A5463 *U5425 C 3 2 6.36 11 1 1 - 39 13701.191 -20952.711 4.124
'1A5359 *U5350 C 1 3 8.35 16 2 1 - 40 13964.401 -20996.053 4.124
'1A5333 *U5350 C 1 3 8.35 16 2 1 - 41 13876.389 -21002.399 4.124
'1A52E7 *U5350 C 1 2 8.35 16 2 1 - 42 13705.662 -21014.71 4.124
'1A52C1 *U5424 B 2 2 2.4 0 2 - 1 43 13559.532 -20215.069 4.124
'1A529B *U5424 B 2 2 2.4 0 2 - 1 44 13471.52 -20221.416 4.124
'1A5275 *U5424 B 2 2 2.4 0 2 - 1 45 13383.509 -20227.762 4.124
'1A524F *U5424 B 2 2 2.4 0 2 - 1 46 13295.497 -20234.108 4.124
'1A5229 *U5350 B 1 2 2.76 1 2 - 1 47 13207.486 -20240.455 4.124
'1A51DD *U5350 B 1 1 2.76 1 2 - 1 48 13031.463 -20253.148 4.124
'1A51B7 *U5424 B 2 1 2.4 0 2 - 1 49 12943.451 -20259.494 4.124
'1A5191 *U5424 B 2 1 2.4 0 2 - 1 50 12855.44 -20265.84 4.124
'1A516B *U5350 B 1 1 2.76 1 2 - 1 51 12767.428 -20272.187 4.124
'1A5145 *U5349 B 4 2 5.58 9 2 - - 52 13564.002 -20277.068 4.124
'1A511F *U5349 B 4 2 5.58 9 2 - - 53 13475.991 -20283.415 4.124
'1A50F9 *U5349 B 4 2 5.58 9 2 - - 54 13387.979 -20289.761 4.124
'1A50D3 *U5349 B 4 2 5.58 9 2 - - 55 13299.968 -20296.107 4.124
'1A50AD *U5425 B 3 2 2.26 0 1 - 1 56 13211.956 -20302.454 4.124
'1A5061 *U5425 B 3 1 2.26 0 1 - 1 57 13035.933 -20315.147 4.124
'1A503B *U5349 B 4 1 5.58 9 2 - - 58 12947.922 -20321.493 4.124
'1A5015 *U5349 B 4 1 5.58 9 2 - - 59 12859.91 -20327.839 4.124
'1A4FEF *U5425 B 3 1 2.26 0 1 - 1 60 12771.899 -20334.186 4.124
'1A4FC9 *U5349 B 4 2 5.58 9 2 - - 61 13568.473 -20339.067 4.124
'1A4FA3 *U5349 B 4 2 5.58 9 2 - - 62 13480.461 -20345.414 4.124
'1A4F7D *U5349 C 4 2 3.71 4 - - - 63 13392.45 -20351.76 4.124
'1A4F57 *U5349 C 4 2 3.71 4 - - - 64 13304.439 -20358.106 4.124
'1A4F31 *U5425 C 3 2 6.36 11 1 1 - 65 13216.427 -20364.453 4.124
'1A4EE5 *U5425 B 3 1 2.26 0 1 - 1 66 13040.404 -20377.146 4.124
'1A4EBF *U5349 B 4 1 5.58 9 2 - - 67 12952.393 -20383.492 4.124
'1A4E99 *U5349 B 4 1 5.58 9 2 - - 68 12864.381 -20389.838 4.124
'1A4E73 *U5425 B 3 1 2.26 0 1 - 1 69 12776.37 -20396.185 4.124
'1A4E4D *U5349 C 4 2 3.71 4 - - - 70 13572.944 -20401.066 4.124
'1A4E27 *U5349 C 4 2 3.71 4 - - - 71 13484.932 -20407.413 4.124
'1A4E01 *U5349 C 4 2 3.71 4 - - - 72 13396.921 -20413.759 4.124
'1A4DDB *U5349 C 4 2 3.71 4 - - - 73 13308.909 -20420.105 4.124
'1A4DB5 *U5425 C 3 2 6.36 11 1 1 - 74 13220.898 -20426.452 4.124
'1A4D69 *U5425 B 3 1 2.26 0 1 - 1 75 13044.875 -20439.145 4.124
'1A4D43 *U5349 B 4 1 5.58 9 2 - - 76 12956.863 -20445.491 4.124
'1A4D1D *U5349 B 4 1 5.58 9 2 - - 77 12868.852 -20451.837 4.124
'1A4CF7 *U5425 B 3 1 2.26 0 1 - 1 78 12780.84 -20458.184 4.124
'1A4CD1 *U5349 C 4 2 3.71 4 - - - 79 13577.414 -20463.065 4.124
'1A4CAB *U5349 C 4 2 3.71 4 - - - 80 13489.403 -20469.412 4.124
'1A4C85 *U5349 C 4 2 3.71 4 - - - 81 13401.391 -20475.758 4.124
'1A4C5F *U5349 C 4 2 3.71 4 - - - 82 13313.38 -20482.104 4.124
'1A4C39 *U5425 C 3 2 6.36 11 1 1 - 83 13225.368 -20488.451 4.124
'1A4BED *U5425 B 3 1 2.26 0 1 - 1 84 13049.345 -20501.144 4.124
'1A4BC7 *U5349 B 4 1 5.58 9 2 - - 85 12961.334 -20507.49 4.124
'1A4BA1 *U5349 B 4 1 5.58 9 2 - - 86 12873.322 -20513.836 4.124
'1A4B7B *U5425 B 3 1 2.26 0 1 - 1 87 12785.311 -20520.183 4.124
'1A4B55 *U5349 C 4 2 3.71 4 - - - 88 13581.885 -20525.064 4.124
'1A4B2F *U5349 C 4 2 3.71 4 - - - 89 13493.874 -20531.411 4.124
'1A4B09 *U5349 C 4 2 3.71 4 - - - 90 13405.862 -20537.757 4.124
'1A4AE3 *U5349 C 4 2 3.71 4 - - - 91 13317.851 -20544.103 4.124
'1A4ABD *U5425 C 3 2 6.36 11 1 1 - 92 13229.839 -20550.45 4.124
'1A4A71 *U5425 B 3 1 2.26 0 1 - 1 93 13053.816 -20563.143 4.124
'1A4A4B *U5349 B 4 1 5.58 9 2 - - 94 12965.805 -20569.489 4.124
'1A4A25 *U5349 B 4 1 5.58 9 2 - - 95 12877.793 -20575.836 4.124
'1A49FF *U5425 B 3 1 2.26 0 1 - 1 96 12789.782 -20582.182 4.124
'1A49D9 *U5349 B 4 2 5.58 9 2 - - 97 13586.356 -20587.063 4.124
'1A49B3 *U5349 B 4 2 5.58 9 2 - - 98 13498.344 -20593.41 4.124
'1A498D *U5349 B 4 2 5.58 9 2 - - 99 13410.333 -20599.756 4.124
'1A4967 *U5349 B 4 2 5.58 9 2 - - 100 13322.321 -20606.102 4.124
'1A4941 *U5425 B 3 2 2.26 0 1 - 1 101 13234.31 -20612.449 4.124
'1A48F5 *U5425 B 3 1 2.26 0 1 - 1 102 13058.287 -20625.142 4.124
'1A48CF *U5349 B 4 1 5.58 9 2 - - 103 12970.275 -20631.488 4.124
'1A48A9 *U5349 B 4 1 5.58 9 2 - - 104 12882.264 -20637.835 4.124
'1A4883 *U5425 B 3 1 2.26 0 1 - 1 105 12794.252 -20644.181 4.124
'1A485D *U5349 B 4 2 5.58 9 2 - - 106 13590.826 -20649.062 4.124
'1A4837 *U5349 B 4 2 5.58 9 2 - - 107 13502.815 -20655.409 4.124
'1A4811 *U5349 B 4 2 5.58 9 2 - - 108 13414.803 -20661.755 4.124
'1A47EB *U5349 B 4 2 5.58 9 2 - - 109 13326.792 -20668.101 4.124
'1A47C5 *U5425 B 3 2 2.26 0 1 - 1 110 13238.78 -20674.448 4.124
'1A4779 *U5425 B 3 1 2.26 0 1 - 1 111 13062.757 -20687.141 4.124
'1A4753 *U5349 B 4 1 5.58 9 2 - - 112 12974.746 -20693.487 4.124
'1A472D *U5349 B 4 1 5.58 9 2 - - 113 12886.735 -20699.834 4.124
'1A4707 *U5425 B 3 1 2.26 0 1 - 1 114 12798.723 -20706.18 4.124
'1A46E1 *U5349 B 4 2 5.58 9 2 - - 115 13595.297 -20711.061 4.124
'1A46BB *U5349 B 4 2 5.58 9 2 - - 116 13507.286 -20717.408 4.124
'1A4695 *U5349 B 4 2 5.58 9 2 - - 117 13419.274 -20723.754 4.124
'1A466F *U5349 B 4 2 5.58 9 2 - - 118 13331.263 -20730.101 4.124
'1A4649 *U5425 B 3 2 2.26 0 1 - 1 119 13243.251 -20736.447 4.124
'1A45FD *U5425 B 3 1 2.26 0 1 - 1 120 13067.228 -20749.14 4.124
'1A45D7 *U5349 B 4 1 5.58 9 2 - - 121 12979.217 -20755.486 4.124
'1A45B1 *U5349 B 4 1 5.58 9 2 - - 122 12891.205 -20761.833 4.124
'1A458B *U5425 B 3 1 2.26 0 1 - 1 123 12803.194 -20768.179 4.124
'1A4565 *U5349 B 4 2 5.58 9 2 - - 124 13599.768 -20773.06 4.124
'1A453F *U5349 B 4 2 5.58 9 2 - - 125 13511.756 -20779.407 4.124
'1A4519 *U5349 B 4 2 5.58 9 2 - - 126 13423.745 -20785.753 4.124
'1A44F3 *U5349 B 4 2 5.58 9 2 - - 127 13335.733 -20792.1 4.124
'1A44CD *U5425 B 3 2 2.26 0 1 - 1 128 13247.722 -20798.446 4.124
'1A4481 *U5425 B 3 1 2.26 0 1 - 1 129 13071.699 -20811.139 4.124
'1A445B *U5349 B 4 1 5.58 9 2 - - 130 12983.687 -20817.485 4.124
'1A4435 *U5349 B 4 1 5.58 9 2 - - 131 12895.676 -20823.832 4.124
'1A440F *U5425 B 3 1 2.26 0 1 - 1 132 12807.664 -20830.178 4.124
'1A43E9 *U5349 B 4 2 5.58 9 2 - - 133 13604.238 -20835.059 4.124
'1A43C3 *U5349 B 4 2 5.58 9 2 - - 134 13516.227 -20841.406 4.124
'1A439D *U5349 B 4 2 5.58 9 2 - - 135 13428.215 -20847.752 4.124
'1A4377 *U5349 B 4 2 5.58 9 2 - - 136 13340.204 -20854.099 4.124
'1A4351 *U5425 B 3 2 2.26 0 1 - 1 137 13252.192 -20860.445 4.124
'1A4305 *U5425 B 3 1 2.26 0 1 - 1 138 13076.17 -20873.138 4.124
'1A42DF *U5349 B 4 1 5.58 9 2 - - 139 12988.158 -20879.484 4.124
'1A42B9 *U5349 B 4 1 5.58 9 2 - - 140 12900.147 -20885.831 4.124
'1A4293 *U5425 B 3 1 2.26 0 1 - 1 141 12812.135 -20892.177 4.124
'1A426D *U5349 B 4 2 5.58 9 2 - - 142 13608.709 -20897.058 4.124
'1A4247 *U5349 B 4 2 5.58 9 2 - - 143 13520.698 -20903.405 4.124
'1A4221 *U5349 B 4 2 5.58 9 2 - - 144 13432.686 -20909.751 4.124
'1A41FB *U5349 B 4 2 5.58 9 2 - - 145 13344.675 -20916.098 4.124
'1A41D5 *U5425 B 3 2 2.26 0 1 - 1 146 13256.663 -20922.444 4.124
'1A4189 *U5425 B 3 1 2.26 0 1 - 1 147 13080.64 -20935.137 4.124
'1A4163 *U5349 B 4 1 5.58 9 2 - - 148 12992.629 -20941.483 4.124
'1A413D *U5349 B 4 1 5.58 9 2 - - 149 12904.617 -20947.83 4.124
'1A4117 *U5425 B 3 1 2.26 0 1 - 1 150 12816.606 -20954.176 4.124
'1A40F1 *U5349 B 4 2 5.58 9 2 - - 151 13613.18 -20959.057 4.124
'1A40CB *U5349 B 4 2 5.58 9 2 - - 152 13525.168 -20965.404 4.124
'1A40A5 *U5349 B 4 2 5.58 9 2 - - 153 13437.157 -20971.75 4.124
'1A407F *U5349 B 4 2 5.58 9 2 - - 154 13349.145 -20978.097 4.124
'1A4059 *U5425 B 3 2 2.26 0 1 - 1 155 13261.134 -20984.443 4.124
'1A400D *U5425 B 3 1 2.26 0 1 - 1 156 13085.111 -20997.136 4.124
'1A3FE7 *U5349 B 4 1 5.58 9 2 - - 157 12997.099 -21003.482 4.124
'1A3FC1 *U5349 B 4 1 5.58 9 2 - - 158 12909.088 -21009.829 4.124
'1A3F9B *U5425 B 3 1 2.26 0 1 - 1 159 12821.076 -21016.175 4.124
'1A3F75 *U5424 B 2 2 2.4 0 2 - 1 160 13617.65 -21021.056 4.124
'1A3F4F *U5424 B 2 2 2.4 0 2 - 1 161 13529.639 -21027.403 4.124
'1A3F29 *U5424 B 2 2 2.4 0 2 - 1 162 13441.628 -21033.749 4.124
'1A3F03 *U5424 B 2 2 2.4 0 2 - 1 163 13353.616 -21040.096 4.124
'1A3EDD *U5350 B 1 2 2.76 1 2 - 1 164 13265.605 -21046.442 4.124
'1A3E91 *U5350 B 1 1 2.76 1 2 - 1 165 13089.582 -21059.135 4.124
'1A3E6B *U5424 B 2 1 2.4 0 2 - 1 166 13001.57 -21065.481 4.124
'1A3E45 *U5424 B 2 1 2.4 0 2 - 1 167 12913.559 -21071.828 4.124
'1A3E1F *U5350 B 1 1 2.76 1 2 - 1 168 12825.547 -21078.174 4.124

View File

@@ -1,4 +1,5 @@
import unittest
import mock
from numpy.testing import assert_array_equal, assert_equal
from helix.constants.module_type import ModuleType
@@ -9,11 +10,15 @@ from helix.models.subarray import Subarray
from helix.presenters.panel_presenter import ProjectPresenter
from helix.constants.system_type import SystemType
from helix.constants.panel_type import PanelType
from test.test_helpers import feature_is_always_active
import flask_featureflags
flask_featureflags.is_active = feature_is_always_active
class PanelPresenterTest(unittest.TestCase):
def test_get_table_data_single_tilt_96cell(self):
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
panels = [
@@ -66,16 +71,70 @@ class PanelPresenterTest(unittest.TestCase):
def test_get_buildings_data(self):
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
buildings = [ [ Coordinate(-60,-60), Coordinate(60,-60), Coordinate(60,60), Coordinate(-60,60) ] ] # big square
buildings = [ [ Coordinate(0,0), Coordinate(60,0), Coordinate(60,60), Coordinate(0,60) ] ] # big square
expected_buildings = [[
{'x': -60, '_Coordinate__rounded_x': -60, 'y': -60, '_Coordinate__rounded_y': -60, 'rotation': 0.0},
{'x': 60, '_Coordinate__rounded_x': 60, 'y': -60, '_Coordinate__rounded_y': -60, 'rotation': 0.0},
{'x': 60, '_Coordinate__rounded_x': 60, 'y': 60, '_Coordinate__rounded_y': 60, 'rotation': 0.0},
{'x': -60, '_Coordinate__rounded_x': -60, 'y': 60, '_Coordinate__rounded_y': 60, 'rotation': 0.0}
{'x': 0, '_Coordinate__rounded_x': 0, 'y': 60, '_Coordinate__rounded_y': 0, 'rotation': 0.0},
{'x': 60, '_Coordinate__rounded_x': 60, 'y': 60, '_Coordinate__rounded_y': 0, 'rotation': 0.0},
{'x': 60, '_Coordinate__rounded_x': 60, 'y': 0, '_Coordinate__rounded_y': 60, 'rotation': 0.0},
{'x': 0, '_Coordinate__rounded_x': 0, 'y': 0, '_Coordinate__rounded_y': 60, 'rotation': 0.0}
]]
actual_buildings = self.subject.get_buildings(buildings)
actual_buildings = self.subject.get_buildings(buildings,60)
assert_array_equal(actual_buildings,expected_buildings)
#@mock.patch('flask_featureflags.is_active',side_effect=feature_is_always_active)
def test_get_corners_box_building(self):
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
buildings = [ [ [0, 0], [60, 0], [60,60], [0, 60] ] ] # big square
expected_corners = [[
{'x': 0, 'y': 0, 'length_cw': 60, 'length_ccw':60, 'angle':90},
{'x': 60, 'y': 0, 'length_cw': 60, 'length_ccw':60, 'angle':90},
{'x': 60, 'y': 60, 'length_cw': 60, 'length_ccw':60, 'angle':90},
{'x': 0, 'y': 60, 'length_cw': 60, 'length_ccw':60, 'angle':90}
]]
actual_corners = self.subject.get_corners(buildings)
assert_array_equal(actual_corners,expected_corners)
#@mock.patch('flask_featureflags.is_active',side_effect=feature_is_always_active)
def test_get_corners_box_building_rotated_30_degrees(self):
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
buildings = [ [ [0, 0], [51.96, 30], [21.96, 81.96], [-30, 51.96] ] ] # big square
expected_corners = [[
{'x': 0, 'y': 0, 'length_cw': 59.99867998547968, 'length_ccw':59.99867998547968, 'angle':90},
{'x': 51.96, 'y': 30, 'length_cw': 59.99867998547968, 'length_ccw':59.998679985479676, 'angle':90},
{'x': 21.96, 'y': 81.96, 'length_cw': 59.998679985479676, 'length_ccw':59.998679985479676, 'angle':90},
{'x': -30, 'y': 51.96, 'length_cw': 59.998679985479676, 'length_ccw':59.99867998547968, 'angle':90}
]]
actual_corners = self.subject.get_corners(buildings)
assert_array_equal(actual_corners,expected_corners)
#@mock.patch('flask_featureflags.is_active',side_effect=feature_is_always_active)
def test_get_corners_building(self):
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
buildings = [ [ [-3.42, 1.51], [-1.66, -1.64], [4.22, -0.87], [-0.8, 5.64]] ]
expected_corners = [[
{'x': -3.42, 'y': 1.51, 'length_cw': 4.890940604832571, 'length_ccw':3.6083375673570233, 'angle':118.41626123074676},
{'x': -1.66, 'y': -1.64, 'length_cw': 3.6083375673570233, 'length_ccw':5.930202357424239, 'angle':111.73284308914215},
{'x': 4.22, 'y': -0.87, 'length_cw': 5.930202357424239, 'length_ccw':8.22073597678456, 'angle':59.823982400990424},
{'x': -0.8, 'y': 5.64, 'length_cw': 8.22073597678456, 'length_ccw':4.890940604832571, 'angle':70.02691327912069}
]]
actual_corners = self.subject.get_corners(buildings)
assert_array_equal(actual_corners,expected_corners)
#@mock.patch('flask_featureflags.is_active',side_effect=feature_is_always_active)
def test_get_corners_wild_building_with_big_angles(self):
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
buildings = [ [ [-3.58, 3.32], [-0.78, -2.9], [-1.56,0.88], [0.66, -2.16], [1.5, 1.16], [2.72, 2.36], [-0.8, 5.64] ] ]
expected_corners = [[
{'x': -3.58, 'y': 3.32, 'length_cw': 3.6208838699963852, 'length_ccw':6.821172919667115, 'angle':105.6106862572924},
{'x': -0.78, 'y': -2.9, 'length_cw': 6.821172919667115, 'length_ccw':3.8596372886580936, 'angle':12.576112527956795},
{'x': 0.66, 'y': -2.16, 'length_cw': 3.7643060449437424, 'length_ccw':3.424616766880639, 'angle':50.33783308603299},
{'x': 2.72, 'y': 2.36, 'length_cw': 1.7112568480505783, 'length_ccw':4.811319985201567, 'angle':87.50512700090906},
{'x': -0.8, 'y': 5.64, 'length_cw': 4.811319985201567, 'length_ccw':3.6208838699963852, 'angle':97.17527350158382}
]]
actual_corners = self.subject.get_corners(buildings)
print(actual_corners)
assert_array_equal(actual_corners,expected_corners)
def test_get_max_y(self):
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
panels = [

View File

@@ -171,11 +171,11 @@ class ArraySummaryTest(unittest.TestCase):
def test_shows_summary_values(self):
self.fill_in_site_characterization_data()
self.upload_csv_file(file='test/fixtures/input_single_tilt_csv_for_bom.csv')
self.assert_summary_values_column_data(1, 'Total System Weight (lbs)', '216,074')
self.assert_summary_values_column_data(2, 'Max PSF', '9.11')
self.assert_summary_values_column_data(3, 'Avg PSF', '5.05')
self.assert_summary_values_column_data(4, 'Total Anchors', '421')
self.assert_summary_values_column_data(5, 'Total Ballast', '10,150')
self.assert_summary_values_column_data(1, 'Total System Weight (lbs)', '217,409')
self.assert_summary_values_column_data(2, 'Max PSF', '9.36')
self.assert_summary_values_column_data(3, 'Avg PSF', '5.08')
self.assert_summary_values_column_data(4, 'Total Anchors', '422')
self.assert_summary_values_column_data(5, 'Total Ballast', '10,448')
def test_shows_seismic_placement_interval(self):
self.fill_in_site_characterization_data(system_type=SystemType.dualTilt)
@@ -212,26 +212,26 @@ class ArraySummaryTest(unittest.TestCase):
eq_(response.content_type, "application/json")
data = json.loads(response.data.decode())
expected = [
{'data': {'ballast': 7, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 1, 'panel_type': 2, 'psf': 4.94, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 2, 'panel_type': 2, 'psf': 3.12, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 11, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 3, 'panel_type': 1, 'psf': 6.47, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 11, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 4, 'panel_type': 1, 'psf': 6.47, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 7, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 1, 'panel_type': 2, 'psf': 4.91, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 2, 'panel_type': 2, 'psf': 3.1, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 11, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 3, 'panel_type': 1, 'psf': 6.48, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 11, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 4, 'panel_type': 1, 'psf': 6.48, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 6, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 5, 'panel_type': 1, 'psf': 4.57, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 6, 'panel_type': 2, 'psf': 3.12, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 6, 'panel_type': 2, 'psf': 3.1, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 7, 'panel_type': 4, 'psf': 2.26, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 8, 'panel_type': 3, 'psf': 2.98, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 9, 'panel_type': 3, 'psf': 2.98, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 8, 'panel_type': 3, 'psf': 3.0, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 9, 'panel_type': 3, 'psf': 3.0, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 10, 'panel_type': 4, 'psf': 2.26, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 11, 'panel_type': 2, 'psf': 3.12, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 12, 'panel_type': 3, 'psf': 2.98, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 13, 'panel_type': 3, 'psf': 2.98, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 14, 'panel_type': 3, 'psf': 2.98, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 11, 'panel_type': 2, 'psf': 3.1, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 12, 'panel_type': 3, 'psf': 3.0, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 13, 'panel_type': 3, 'psf': 3.0, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 14, 'panel_type': 3, 'psf': 3.0, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 6, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 15, 'panel_type': 1, 'psf': 4.57, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 6, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 16, 'panel_type': 1, 'psf': 4.57, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 6, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 17, 'panel_type': 1, 'psf': 4.57, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 11, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 18, 'panel_type': 1, 'psf': 6.47, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 6, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 19, 'panel_type': 3, 'psf': 4.48, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 11, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 20, 'panel_type': 1, 'psf': 6.47, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5}
{'data': {'ballast': 11, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 18, 'panel_type': 1, 'psf': 6.48, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 6, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 19, 'panel_type': 3, 'psf': 4.5, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 11, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 20, 'panel_type': 1, 'psf': 6.48, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5}
]
# Removing these keys as they get calculated differently in CircleCI
@@ -290,28 +290,28 @@ class ArraySummaryTest(unittest.TestCase):
"status": "success",
"error": None,
"panel_data": [
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.08, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 1, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 2, 'panel_id': 1}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.04, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 3, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 2, 'seismic_anchors': 0, 'panel_id': 2}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.08, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 1, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 3}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.08, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 1, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 4}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.04, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 3, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 2, 'seismic_anchors': 0, 'panel_id': 5}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.04, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 3, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 2, 'seismic_anchors': 0, 'panel_id': 6}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.04, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 3, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 2, 'seismic_anchors': 0, 'panel_id': 7}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.08, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 1, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 8}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.89, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 2, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 9}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.89, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 2, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 10}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.89, 'wind_zones': 'I', 'ballast': 0, 'panel_type': 2, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 11}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.86, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 4, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 12}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.86, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 4, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 13}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.86, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 4, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 14}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.86, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 4, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 15}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.86, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 4, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 16}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.86, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 4, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 17}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.89, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 2, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 18}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.89, 'wind_zones': 'I', 'ballast': 0, 'panel_type': 2, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 19}},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 1, 'panel_type': 1, 'psf': 1.95, 'seismic_anchors': 2, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 2, 'panel_type': 3, 'psf': 1.88, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 3, 'panel_type': 1, 'psf': 1.95, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 4, 'panel_type': 1, 'psf': 1.95, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 5, 'panel_type': 3, 'psf': 1.88, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 6, 'panel_type': 3, 'psf': 1.88, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 7, 'panel_type': 3, 'psf': 1.88, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 8, 'panel_type': 1, 'psf': 1.95, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 9, 'panel_type': 2, 'psf': 1.84, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 10, 'panel_type': 2, 'psf': 1.84, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 11, 'panel_type': 2, 'psf': 1.84, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'I'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 12, 'panel_type': 4, 'psf': 1.84, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 13, 'panel_type': 4, 'psf': 1.84, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 14, 'panel_type': 4, 'psf': 1.84, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 15, 'panel_type': 4, 'psf': 1.84, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 16, 'panel_type': 4, 'psf': 1.84, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 17, 'panel_type': 4, 'psf': 1.84, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 18, 'panel_type': 2, 'psf': 1.84, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'H'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 19, 'panel_type': 2, 'psf': 1.84, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 1, 'wind_zones': 'I'}, 'height': 1, 'width': 1, 'x': 0.0, 'y': 0.0},
],
"subarray_data": [
{"subarray": 8, "weight": 972, "required_seismic_anchors": 0},
{"subarray": 8, "weight": 932, "required_seismic_anchors": 0},
],
}
received_result = flask.json.loads(result.data)
@@ -367,9 +367,10 @@ class ArraySummaryTest(unittest.TestCase):
expected_result = [
{"subarray": 7, "weight": 2253, "required_seismic_anchors": 4},
{"subarray": 8, "weight": 673, "required_seismic_anchors": 0},
{"subarray": 8, "weight": 674, "required_seismic_anchors": 0},
]
received_result = flask.json.loads(result.data)
eq_(received_result['subarray_data'], expected_result)
eq_(result.content_type, "application/json")
@@ -410,7 +411,7 @@ class ArraySummaryTest(unittest.TestCase):
"error": SeismicAnchorValidationError.TooFewAnchors.value,
"panel_data": None,
"subarray_data": [
{"subarray": 1, "weight": 2739, "required_seismic_anchors": 6},
{"subarray": 1, "weight": 2722, "required_seismic_anchors": 6},
],
}
eq_(flask.json.loads(result.data), expected_result)

View File

@@ -36,7 +36,7 @@ class BomIntegrationTest(unittest.TestCase):
expected = [
["512200", "CLIP, WIRE FORMED, CABLE MANAGEMENT, INSIDE, 352MM ^ 2", "510"],
["513833", "TRAY, LINK, HELIX ROOF", "78"], # TODO: should be 74 for part perfect bom
["513833", "TRAY, LINK, HELIX ROOF", "78"],
["513843", "PLATE, ANCHOR, HELIX ROOF", "86"],
["513844", "TRAY, OPTIONAL BALLAST, HELIX ROOF", "35"],
["514056", "BASE, CHASSIS, DUAL TILT, HELIX ROOF", "147"],
@@ -48,7 +48,7 @@ class BomIntegrationTest(unittest.TestCase):
["518477", "WASHER, FLAT, 3/8, 1.00 OD, 18-8 SS", "100"],
["521794", "DEFLECTOR, LH, HELIX ROOF V1.1", "30"],
["521795", "DEFLECTOR, RH, HELIX ROOF V1.1", "30"],
["Contractor Supplied", "Ballast Blocks", "608"],
["Contractor Supplied", "Ballast Blocks", "610"],
["TBD", "Anchors", "86"],
["TBD", "Modules", "252"]
]

View File

@@ -149,6 +149,8 @@ class FullUserFlowTest(unittest.TestCase):
self.advance_n_times(1)
self.browser.visit('/download/')
self.browser.click_link_by_partial_text('Download AutoCAD import file')
print("===")
print(self.browser.html)
eq_(self.browser.html, csv_content)
eq_(self.browser._response.headers['Content-Disposition'], 'attachment; filename=test_project_name_result.txt')

35
test/load/locustfile.py Normal file
View File

@@ -0,0 +1,35 @@
from locust import HttpLocust, TaskSet, task
class UserBehavior(TaskSet):
@task(1)
def wizard_sequence(self):
# files => enctype=multipart/form-data
self.client.post('/site_characterization/', files={
'project_name': (None, 'Amazon Fresno'),
'system_type': (None, '0'),
'module_type': (None, '96 Cell'),
'building_height': (None, '30'),
'building_width': (None, '600'),
'building_length': (None, '1500'),
'building_parapet_height': (None, '0'),
'wind_speed': (None, '110'),
'exposure_category': (None, 'C'),
'exposure_category_transition_distance': (None, '0'),
'ballast_block_weight': (None, '14.0'),
'max_system_pressure': (None, '12.0'),
'anchor_type': (None, 'OMG PowerGrip Plus'),
'design_spectral_response': (None, '0'),
'importance_factor': (None, '1'),
})
self.client.get('/array_summary/')
self.client.post('/array_summary/', files={'dxf_upload': open('Fresno First Half.dxf', 'rb')})
class WebsiteUser(HttpLocust):
task_set = UserBehavior
min_wait = 500
max_wait = 5000
# locust -f locustfile.py --host=http://localhost:5000

Some files were not shown because too many files have changed in this diff Show More