Compare commits
1 Commits
master
...
base-weigh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3eb3463d50 |
@@ -1,4 +1,4 @@
|
||||
from python:3.6
|
||||
from python:3.5
|
||||
|
||||
RUN apt-get update
|
||||
|
||||
|
||||
2
Procfile
@@ -1 +1 @@
|
||||
web: gunicorn -c gunicorn_config.py --pythonpath helix main:app
|
||||
web: python helix/main.py
|
||||
|
||||
28
README.md
@@ -1,6 +1,3 @@
|
||||
[](https://circleci.com/gh/SunPower/Helix_Roof_Calculator)
|
||||
|
||||
|
||||
## Helix Calculator
|
||||
|
||||
- [Staging](https://sp-helix-staging.herokuapp.com)
|
||||
@@ -34,23 +31,6 @@
|
||||
| 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
|
||||
@@ -67,7 +47,7 @@ SFDC_API_URL="https://sunpower--qa.cs8.my.salesforce.com"
|
||||
|
||||
- Log in the docker container
|
||||
|
||||
```docker exec -ti helix bash```
|
||||
```docker exec -t -i helix /bin/bash```
|
||||
|
||||
- Run this commands inside the docker conatiner
|
||||
|
||||
@@ -387,9 +367,3 @@ 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
|
||||
|
||||
|
||||

|
||||
|
||||
51
circle.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
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
|
||||
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 218 KiB |
@@ -1,13 +0,0 @@
|
||||
"""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
|
||||
@@ -234,43 +234,24 @@ 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
|
||||
x_spacing = (x * x_spacing_cos_rotation) - (y * y_spacing_sin_rotation)
|
||||
y_spacing = (x * x_spacing_sin_rotation) + (y * y_spacing_cos_rotation)
|
||||
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))
|
||||
|
||||
coordinate = Coordinate(node.coordinate.x + x_spacing, node.coordinate.y + y_spacing, node.coordinate.rotation)
|
||||
if coordinate.x < 0 or coordinate.y < 0:
|
||||
continue
|
||||
|
||||
# Optimization for `direction = GraphDirection((x, y))`
|
||||
direction = graph_directions_cache[(x, y)]
|
||||
direction = GraphDirection((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)
|
||||
@@ -436,7 +417,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
|
||||
@@ -457,7 +438,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
|
||||
@@ -529,7 +510,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 = []
|
||||
|
||||
@@ -2,6 +2,9 @@ import io
|
||||
|
||||
import dxfgrabber
|
||||
|
||||
from helix.constants.file_validation_error import FileValidationMessage
|
||||
from helix.models.dxf.dxf_error import OldDxfFormatException
|
||||
|
||||
|
||||
class DXFService(object):
|
||||
"""
|
||||
@@ -48,7 +51,6 @@ 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:
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
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
|
||||
@@ -5,6 +5,7 @@ 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')
|
||||
|
||||
@@ -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), self.subarrays).compute_ebom()
|
||||
ebom_parts_list = EbomCalculator(self.values, ceil(row_count), ceil(column_count), parts_list.get(module)).compute_ebom()
|
||||
|
||||
add_parts_to_list(parts_list, ebom_parts_list)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from math import ceil, floor
|
||||
import copy
|
||||
from helix.Repositories.graph_repository import GraphRepository
|
||||
from helix.calculators.ballast_calculator import BallastCalculator
|
||||
|
||||
@@ -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, panels = None):
|
||||
def __init__(self, user_values, row_count, column_count, modules_count = 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,14 +30,6 @@ 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 = {}
|
||||
|
||||
@@ -46,14 +38,25 @@ class EbomCalculator(object):
|
||||
monitors = self.values.power_monitors()
|
||||
module_type = self.values.module_type()
|
||||
system_type = self.values.system_type()
|
||||
|
||||
is_delta = self.resolve_is_delta()
|
||||
|
||||
|
||||
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
|
||||
|
||||
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']
|
||||
@@ -88,18 +91,15 @@ 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)
|
||||
|
||||
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, {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), is_delta)
|
||||
cable_supports = self.calculate_cable_supports(panel_board_counts, len(standalone_inverters))
|
||||
add_parts_to_list(part_list, {cable_support: 1, cable_support_lid: 1}, cable_supports)
|
||||
add_parts_to_list(part_list, {rear_skirt_1_1: -1}, ceil(cable_supports*.38))
|
||||
add_parts_to_list(part_list, {rear_skirt: -1}, ceil(cable_supports*.38))
|
||||
|
||||
dependent_part_list = {}
|
||||
|
||||
@@ -121,37 +121,21 @@ 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, is_delta):
|
||||
# There are some tests that don't have panels
|
||||
if is_delta and self.panels is None:
|
||||
def calculate_cable_supports(self, panel_board_counts, standalone_inverter_count):
|
||||
if sum(panel_board_counts) == 0:
|
||||
return 0
|
||||
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)
|
||||
|
||||
if self.values.system_type() == SystemType.dualTilt:
|
||||
dimension1 = self.column_count
|
||||
dimension2 = self.row_count
|
||||
else:
|
||||
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)
|
||||
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()
|
||||
|
||||
@@ -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
|
||||
from helix.constants.parts import link_tray, cross_tray, ballast, cross_tray_1_1, leading_tray
|
||||
from helix.constants.system_type import SystemType
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
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
|
||||
@@ -39,20 +38,13 @@ 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:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import copy
|
||||
from enum import Enum
|
||||
|
||||
from helix.constants.system_type import SystemType
|
||||
|
||||
@@ -5,7 +5,6 @@ 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'
|
||||
|
||||
@@ -49,7 +49,3 @@ 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
|
||||
|
||||
@@ -7,5 +7,3 @@ parapet_coefficients = 0.88, 1.2
|
||||
system_force_capacity = 418.
|
||||
|
||||
minimum_racking_capacity = 226
|
||||
|
||||
max_corner_angle = 135
|
||||
|
||||
@@ -7,10 +7,6 @@ class InverterTypeSMA(IntEnum):
|
||||
MODEL_20KW = 6
|
||||
MODEL_24KW = 8
|
||||
|
||||
@property
|
||||
def get_type(self):
|
||||
return "SMA"
|
||||
|
||||
@property
|
||||
def default_string(self):
|
||||
return {
|
||||
@@ -53,10 +49,6 @@ class InverterTypeDelta(IntEnum):
|
||||
MODEL_60KW = 11
|
||||
MODEL_80KW = 12
|
||||
|
||||
@property
|
||||
def get_type(self):
|
||||
return "Delta"
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
return {
|
||||
|
||||
@@ -2,7 +2,6 @@ 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'
|
||||
|
||||
@@ -55,7 +55,7 @@ class SingleTiltParts(object):
|
||||
|
||||
def module(self, module_type):
|
||||
if module_type == ModuleType.Cell96:
|
||||
rear_skirt_parts = rear_skirt_1_1
|
||||
rear_skirt_parts = rear_skirt
|
||||
spoiler_parts = spoiler
|
||||
else:
|
||||
rear_skirt_parts = rear_skirt_1_1
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
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
|
||||
|
||||
|
||||
@@ -130,14 +130,8 @@ class NodeQuadTree():
|
||||
self.nodeList = toKeep
|
||||
|
||||
# Return a list of all possible nodes that can be near this point
|
||||
# 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)
|
||||
def retrieve(self, nearPoint):
|
||||
retNodes = list(self.nodeList)
|
||||
|
||||
if self.quads[0] is not None:
|
||||
index = self.getIndex(nearPoint)
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
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) {
|
||||
// $('#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>');
|
||||
}
|
||||
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>');
|
||||
}
|
||||
});
|
||||
|
||||
$("#dxf_upload").change((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>');
|
||||
}
|
||||
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>');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -8,16 +8,13 @@ import AutoUpload from './auto_upload';
|
||||
|
||||
$(document).ready(function () {
|
||||
AutoUpload();
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
||||
});
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
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))
|
||||
367
helix/main.py
@@ -1,23 +1,19 @@
|
||||
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, jsonify, flash
|
||||
redirect, url_for
|
||||
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
|
||||
@@ -29,49 +25,15 @@ 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 = [
|
||||
@@ -105,10 +67,6 @@ 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'))
|
||||
@@ -151,9 +109,6 @@ 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()
|
||||
@@ -181,7 +136,6 @@ 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
|
||||
@@ -199,51 +153,10 @@ 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.
|
||||
@@ -262,7 +175,8 @@ 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, FileType.Csv, file)
|
||||
validation_error = validator.validate(file_contents, file,
|
||||
FileType.Csv)
|
||||
if not validation_error:
|
||||
session_manager.save_uploaded_file(file_contents,
|
||||
cad_file_name=file.filename)
|
||||
@@ -274,15 +188,35 @@ 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)
|
||||
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'))
|
||||
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)
|
||||
else:
|
||||
array_form.dxf_upload.errors.extend(errors)
|
||||
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)
|
||||
elif context['csv_available']:
|
||||
return redirect(url_for('power_station_configuration'))
|
||||
else:
|
||||
@@ -294,91 +228,39 @@ def array_summary():
|
||||
context['dxf_file_name'] = ''
|
||||
if context['site_data_available'] and context['csv_available']:
|
||||
user_values = session_manager.user_values()
|
||||
try:
|
||||
calculator = Calculator(user_values)
|
||||
system_type = user_values.system_type()
|
||||
module_type = user_values.module_type()
|
||||
project_presenter = ProjectPresenter(system_type, module_type)
|
||||
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()
|
||||
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, \
|
||||
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, \
|
||||
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()
|
||||
@@ -457,11 +339,6 @@ 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)
|
||||
|
||||
@@ -551,141 +428,6 @@ 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)
|
||||
@@ -715,13 +457,8 @@ def enum():
|
||||
|
||||
def main():
|
||||
host = '0.0.0.0'
|
||||
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)
|
||||
port = int(os.getenv('PORT', 5000))
|
||||
app.run(host=host, port=port, debug=bool(os.getenv("FLASK_DEBUG", False)))
|
||||
|
||||
|
||||
@app.route("/fail-test")
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
|
||||
|
||||
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}
|
||||
@@ -25,7 +25,6 @@ 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:
|
||||
@@ -34,15 +33,9 @@ class GraphNodeStore(list):
|
||||
self.quadTree.insert(node)
|
||||
del self[:]
|
||||
|
||||
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
|
||||
possibilities = self.quadTree.retrieve(coordinate)
|
||||
for node in possibilities:
|
||||
if self.distance_squared(node, coordinate) <= variance_square:
|
||||
if self.distance_squared(node, coordinate) <= self.variance ** 2:
|
||||
return node
|
||||
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
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
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):
|
||||
@@ -22,6 +21,8 @@ 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
|
||||
@@ -52,10 +53,9 @@ 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, max_y):
|
||||
def get_buildings(self, buildings):
|
||||
if self.offset is None:
|
||||
self.offset = 0
|
||||
|
||||
@@ -70,51 +70,11 @@ class ProjectPresenter(object):
|
||||
# origin = self.find_origin(building)
|
||||
for point in building:
|
||||
point.x = point.x * spacing_x
|
||||
point.y = abs((point.y * spacing_y) - max_y)
|
||||
point.y = point.y * spacing_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)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
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)]
|
||||
@@ -1,138 +0,0 @@
|
||||
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, {}
|
||||
@@ -72,12 +72,6 @@ i.icon-info-circled {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.info_message {
|
||||
color: black;
|
||||
padding-left: 5px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.error_message {
|
||||
color: red;
|
||||
padding-left: 5px;
|
||||
@@ -157,7 +151,7 @@ a.back {
|
||||
a {
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
margin: 2px 5px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.button {
|
||||
|
||||
@@ -59,35 +59,3 @@ 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;
|
||||
}
|
||||
|
||||
@@ -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('parapet_height', form_data.get('building_parapet_height', 0)),
|
||||
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', 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))),
|
||||
ballast_block_weight=form_data.get('ballast_block_weight', 0),
|
||||
max_psf=form_data.get('max_system_pressure', 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', 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))
|
||||
spectral_response=form_data.get('design_spectral_response', 1),
|
||||
seismic_importance_factor=form_data.get('importance_factor', 1)
|
||||
)
|
||||
self.db_session.add(self.site)
|
||||
self.db_session.commit()
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
||||
17
helix/static/css/fontello.css
vendored
@@ -1,11 +1,11 @@
|
||||
@font-face {
|
||||
font-family: 'fontello';
|
||||
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');
|
||||
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');
|
||||
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?24283821#fontello') format('svg');
|
||||
src: url('../font/fontello.svg?10976371#fontello') format('svg');
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -69,5 +69,4 @@
|
||||
.icon-info:before { content: '\e80b'; } /* '' */
|
||||
.icon-close:before { content: '\e80c'; } /* '' */
|
||||
.icon-sunpower-logo:before { content: '\e80d'; } /* '' */
|
||||
.icon-upload-cloud:before { content: '\e80e'; } /* '' */
|
||||
.icon-spin6:before { content: '\e839'; } /* '' */
|
||||
.icon-upload-cloud:before { content: '\e80e'; } /* '' */
|
||||
@@ -616,20 +616,13 @@ 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 87 */
|
||||
/* line 81 */
|
||||
input, select {
|
||||
width: calc(100% - 20px);
|
||||
padding: 10px;
|
||||
@@ -640,7 +633,7 @@ input, select {
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
/* line 97 */
|
||||
/* line 91 */
|
||||
select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
@@ -648,14 +641,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 104 */
|
||||
/* line 98 */
|
||||
.submit, .download {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
border-top: 1px solid #EAEAEA;
|
||||
}
|
||||
|
||||
/* line 110 */
|
||||
/* line 104 */
|
||||
.button, button {
|
||||
background: #5199F5;
|
||||
color: white;
|
||||
@@ -667,7 +660,7 @@ select {
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
/* line 121 */
|
||||
/* line 115 */
|
||||
.navigation_buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
@@ -675,18 +668,18 @@ select {
|
||||
border-bottom: 1px solid #EAEAEA;
|
||||
padding: 20px;
|
||||
}
|
||||
/* line 128 */
|
||||
/* line 122 */
|
||||
.navigation_buttons a {
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
margin: 0 20px;
|
||||
}
|
||||
/* line 134 */
|
||||
/* line 128 */
|
||||
.navigation_buttons .button {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* line 139 */
|
||||
/* line 133 */
|
||||
.back {
|
||||
border: 2px solid #5199F5;
|
||||
color: #5199F5;
|
||||
@@ -694,12 +687,12 @@ select {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* line 146 */
|
||||
/* line 140 */
|
||||
a.back {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* line 150 */
|
||||
/* line 144 */
|
||||
.sample_images {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
@@ -707,18 +700,18 @@ a.back {
|
||||
border-bottom: 1px solid #EAEAEA;
|
||||
padding: 15px;
|
||||
}
|
||||
/* line 157 */
|
||||
/* line 151 */
|
||||
.sample_images a {
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
margin: 2px 5px;
|
||||
}
|
||||
/* line 163 */
|
||||
/* line 157 */
|
||||
.sample_images .button {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* line 168 */
|
||||
/* line 162 */
|
||||
.image_button, button {
|
||||
background: #5199F5;
|
||||
color: white;
|
||||
@@ -839,41 +832,6 @@ 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;
|
||||
|
||||
@@ -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) 2017 by original authors @ fontello.com</metadata>
|
||||
<metadata>Copyright (C) 2016 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,8 +35,6 @@
|
||||
<glyph glyph-name="sunpower-logo" unicode="" 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="" 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="" 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: 6.1 KiB After Width: | Height: | Size: 5.7 KiB |
@@ -73,18 +73,15 @@
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
$(document).ready(function () {
|
||||
(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;
|
||||
}
|
||||
(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;
|
||||
});
|
||||
|
||||
/***/ }),
|
||||
@@ -24733,32 +24730,28 @@
|
||||
"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) {
|
||||
// $('#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>');
|
||||
}
|
||||
});
|
||||
$("#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>');
|
||||
}
|
||||
});
|
||||
|
||||
$("#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>');
|
||||
}
|
||||
});
|
||||
$("#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>');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.default = AutoUpload;
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
"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
|
||||
});
|
||||
});
|
||||
@@ -73,7 +73,7 @@
|
||||
}
|
||||
});
|
||||
inverterStringsPerInverter.val(inverterStringsPerInverterValue);
|
||||
inverterStringsPerInverterForm.css('display', 'block');
|
||||
inverterStringsPerInverterForm.css('display', 'inherit');
|
||||
} else {
|
||||
inverterStringsPerInverter.append($('<option selected value="0"></option>'));
|
||||
inverterStringsPerInverterForm.css('display', 'none');
|
||||
@@ -160,8 +160,7 @@
|
||||
if (data) {
|
||||
$('#standalone_inverter_id').val(uuid);
|
||||
$('#standalone_ac_run_length').val(data['ac_run_length']);
|
||||
var attach_point = data['attachment_point'][1];
|
||||
$('#attachment_point option[value=' + attach_point + ']').attr('selected','selected');
|
||||
$('#attachment-point').val(data['attachment_point'][0]);
|
||||
|
||||
fillInverterData(data, '#inverter');
|
||||
|
||||
|
||||
@@ -1,35 +1,6 @@
|
||||
{% 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 }}
|
||||
@@ -53,11 +24,6 @@
|
||||
<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>
|
||||
|
||||
|
||||
@@ -1,25 +1,6 @@
|
||||
{% 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>
|
||||
@@ -39,14 +20,6 @@
|
||||
<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 %}
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
<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 }}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
<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>
|
||||
@@ -59,23 +58,7 @@
|
||||
{% 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>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<div class="navigation_buttons">
|
||||
{% if context['hide_back'] %}
|
||||
{% else %}
|
||||
<a class="back button" href="{{ context['steps'][context['current_step'] - 2][2] }}">Back</a>
|
||||
{% endif %}
|
||||
|
||||
<a class="back button" href="{{ context['steps'][context['current_step'] - 2][2] }}">Back</a>
|
||||
{% 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>
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
{% block contents %}
|
||||
{% if context['site_data_available'] %}
|
||||
<div class="form_section">
|
||||
<h3>Summary <small>({{ context['project_name'] }})</small></h3>
|
||||
<h3>Summary</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>
|
||||
@@ -81,10 +80,8 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% include "navigation_buttons.html.jinja" %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -30,7 +30,7 @@ class CsvInputValidator(object):
|
||||
|
||||
file_validation_chain = [
|
||||
CsvInputValidator.validate_file_for_panel_types,
|
||||
# CsvInputValidator.validate_for_spacing, # disabling this feature for the moment
|
||||
CsvInputValidator.validate_for_spacing,
|
||||
]
|
||||
result = self.run_validation_chain(headers, rows, file_validation_chain)
|
||||
if result:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from helix.constants.file_validation_error import FileValidationMessage, FileValidationError
|
||||
|
||||
|
||||
class DxfInputValidator(object):
|
||||
def __init__(self, _):
|
||||
|
||||
@@ -100,13 +100,13 @@ class FileValidator(object):
|
||||
finally:
|
||||
return content
|
||||
|
||||
def validate(self, stream, expected, file=None, extension=None):
|
||||
def validate(self, stream, file, expected):
|
||||
"""Validates the uploaded file by extension
|
||||
and content
|
||||
|
||||
Arguments;
|
||||
stream (string): File content
|
||||
file (FileObject): A file object provided from the ui. Used only to get the file extension.
|
||||
file (FileObject): A file object provided from the ui
|
||||
|
||||
"""
|
||||
|
||||
@@ -114,8 +114,7 @@ class FileValidator(object):
|
||||
file_type = self.identify_file_type(stream)
|
||||
assert file_type == expected
|
||||
validator = file_type.validator(self.values)
|
||||
if extension is None:
|
||||
extension = self.obtain_extension(file)
|
||||
extension = self.obtain_extension(file)
|
||||
file_type.valid_mapping(extension)
|
||||
return validator.validate(stream)
|
||||
except AssertionError:
|
||||
|
||||
@@ -2,12 +2,8 @@ nose==1.3.7
|
||||
selenium==2.53.1
|
||||
splinter==0.7.3
|
||||
cssselect==0.9.1
|
||||
lxml==4.1.1
|
||||
lxml==3.6.0
|
||||
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
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
gunicorn==19.7.1
|
||||
meinheld==0.6.1
|
||||
Flask==0.10.1
|
||||
itsdangerous==0.24
|
||||
Jinja2==2.8
|
||||
@@ -20,8 +18,3 @@ 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
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
python-3.5.1
|
||||
|
||||
|
||||
24
tasks.py
@@ -1,5 +1,4 @@
|
||||
from invoke import run, task
|
||||
import os
|
||||
|
||||
|
||||
@task
|
||||
@@ -35,10 +34,9 @@ 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('SP_DOCGEN_API_KEY=DC97-20AF-567E gunicorn -c gunicorn_config.py --pythonpath helix main:app')
|
||||
run('PYTHONPATH=. SP_DOCGEN_API_KEY=DC97-20AF-567E python helix/main.py')
|
||||
|
||||
|
||||
@task
|
||||
@@ -54,23 +52,3 @@ 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')
|
||||
|
||||
@@ -118,7 +118,7 @@ class EbomCalculatorTest(unittest.TestCase):
|
||||
wire_clip_large: 2907,
|
||||
cable_support_lid: 368,
|
||||
cable_support: 368,
|
||||
rear_skirt_1_1: -140,
|
||||
rear_skirt: -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: 590,
|
||||
wire_clip_large: 513,
|
||||
cable_support_lid: 0,
|
||||
cable_support: 0,
|
||||
rear_skirt_1_1: 0,
|
||||
rear_skirt: 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: 200,
|
||||
wire_clip_large: 171,
|
||||
cable_support_lid: 0,
|
||||
cable_support: 0,
|
||||
rear_skirt_1_1: 0,
|
||||
rear_skirt: 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_1_1: -103,
|
||||
rear_skirt: -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_1_1: -103,
|
||||
rear_skirt: -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_1_1: -103,
|
||||
rear_skirt: -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_1_1: 0,
|
||||
rear_skirt: 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_1_1: 0,
|
||||
rear_skirt: 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_1_1: -264,
|
||||
rear_skirt: -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_1_1: -103,
|
||||
rear_skirt: -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_1_1: -103,
|
||||
rear_skirt: -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_1_1: 0,
|
||||
rear_skirt: 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_1_1: 0,
|
||||
rear_skirt: 0,
|
||||
monitor_power_plug: 1,
|
||||
}
|
||||
|
||||
@@ -1055,14 +1055,14 @@ class EbomCalculatorTest(unittest.TestCase):
|
||||
stump: 0,
|
||||
cable_support: 0,
|
||||
cable_support_lid: 0,
|
||||
rear_skirt_1_1: 0
|
||||
rear_skirt: 0
|
||||
}
|
||||
|
||||
expected_output_240_pseries = {
|
||||
stump: 0,
|
||||
cable_support_lid: 0,
|
||||
cable_support: 0,
|
||||
rear_skirt_1_1: 0,
|
||||
rear_skirt: 0,
|
||||
monitor_controller_240_v: 1,
|
||||
}
|
||||
|
||||
@@ -1116,13 +1116,14 @@ class EbomCalculatorTest(unittest.TestCase):
|
||||
stump: 0,
|
||||
cable_support: 0,
|
||||
cable_support_lid: 0,
|
||||
rear_skirt_1_1: 0,
|
||||
rear_skirt: 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',
|
||||
@@ -1130,13 +1131,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_1_1: 0,
|
||||
rear_skirt: 0,
|
||||
monitor_controller_480_v:1,
|
||||
flat_washer: 4,
|
||||
channel_nut: 4,
|
||||
@@ -1151,3 +1152,7 @@ class EbomCalculatorTest(unittest.TestCase):
|
||||
}
|
||||
|
||||
assert_dictionary_equal(self.subject.compute_ebom(), expected_output)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ class MechanicalBomCalculatorWhenSingleTilt96CellTest(unittest.TestCase):
|
||||
cross_tray: 5,
|
||||
rubber_foot: 4.4,
|
||||
front_skirt: 24,
|
||||
rear_skirt_1_1: 44,
|
||||
rear_skirt: 44,
|
||||
leading_tray: 25,
|
||||
ballast: 192,
|
||||
anchor: 15, # 12 + the 3 seismic anchors
|
||||
|
||||
2
test/fixtures/expected_single_tilt_bom.csv
vendored
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 10 and column 30.
|
1073
test/fixtures/irvine_fortune_result.txt
vendored
169
test/fixtures/wind data_hdt.txt
vendored
@@ -1,169 +0,0 @@
|
||||
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
|
||||
@@ -1,5 +1,4 @@
|
||||
import unittest
|
||||
import mock
|
||||
|
||||
from numpy.testing import assert_array_equal, assert_equal
|
||||
from helix.constants.module_type import ModuleType
|
||||
@@ -10,15 +9,11 @@ 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 = [
|
||||
@@ -71,70 +66,16 @@ class PanelPresenterTest(unittest.TestCase):
|
||||
|
||||
def test_get_buildings_data(self):
|
||||
self.subject = ProjectPresenter(SystemType.singleTilt, ModuleType.Cell96)
|
||||
buildings = [ [ Coordinate(0,0), Coordinate(60,0), Coordinate(60,60), Coordinate(0,60) ] ] # big square
|
||||
buildings = [ [ Coordinate(-60,-60), Coordinate(60,-60), Coordinate(60,60), Coordinate(-60,60) ] ] # big square
|
||||
expected_buildings = [[
|
||||
{'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}
|
||||
{'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}
|
||||
]]
|
||||
actual_buildings = self.subject.get_buildings(buildings,60)
|
||||
actual_buildings = self.subject.get_buildings(buildings)
|
||||
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 = [
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
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
|
||||
@@ -105,7 +105,3 @@ def assert_image_equal(image_1, image_2, error=5e-2):
|
||||
|
||||
average_error = total_error / pixels
|
||||
assert average_error <= error, "Images are not equal to within %f error (got %f)" % (error, average_error)
|
||||
|
||||
# used for mocking response of feature flags
|
||||
def feature_is_always_active(feature_name):
|
||||
return True
|
||||
|
||||
@@ -103,4 +103,4 @@ class CsvInputValidatorTest(unittest.TestCase):
|
||||
self.user_values.module_system_constants().panel_spacing = (88.24, 62.0)
|
||||
with open('test/fixtures/invalid_too_close.txt', 'r', newline='') as csv_file:
|
||||
cad_input = csv_file.read()
|
||||
#self.should_have_error(self.subject.validate(cad_input), FileValidationMessage.PanelsTooClose , None)
|
||||
self.should_have_error(self.subject.validate(cad_input), FileValidationMessage.PanelsTooClose , None)
|
||||
|
||||
@@ -28,12 +28,12 @@ class FileValidatorTest(unittest.TestCase):
|
||||
with open('test/fixtures/input_single_tilt.csv', 'r',
|
||||
newline='') as file:
|
||||
cad_input = file.read()
|
||||
eq_(self.subject.validate(cad_input, FileType.Csv, fake_file), None)
|
||||
eq_(self.subject.validate(cad_input, fake_file, FileType.Csv), None)
|
||||
|
||||
def test_unknown_files_are_invalid(self):
|
||||
fake_file = MagicMock()
|
||||
type(fake_file).filename = PropertyMock(return_value="Hi")
|
||||
result = self.subject.validate("Hi", FileType.Unknown, fake_file)
|
||||
result = self.subject.validate("Hi", fake_file, FileType.Unknown)
|
||||
self.should_have_error(result,
|
||||
FileValidationMessage.UnknownFileUploaded, None)
|
||||
|
||||
@@ -62,7 +62,8 @@ class FileValidatorTest(unittest.TestCase):
|
||||
fake_file.read.return_value = open(fname, "rb").read()
|
||||
fake_file.filename.return_value = "expected_dual_tilt_pseries_image.png"
|
||||
stream = self.subject.obtain_stream(fake_file)
|
||||
self.should_have_error(self.subject.validate(stream, FileType.AuroraDxf, fake_file),
|
||||
self.should_have_error(self.subject.validate(stream, fake_file,
|
||||
FileType.AuroraDxf),
|
||||
FileValidationMessage.ExpectedDxfFile,
|
||||
None)
|
||||
|
||||
|
||||