first commit

This commit is contained in:
Senad Uka
2017-11-07 09:23:57 +01:00
commit 0eee92660a
356 changed files with 747259 additions and 0 deletions

View File

View File

@@ -0,0 +1,92 @@
import csv
from helix.constants.file_validation_error import FileValidationError, FileValidationMessage
class CsvInputValidator(object):
def __init__(self, user_values):
self.user_values = user_values
def validate(self, csv_input):
try:
headers, rows = self.parse_cad_input(csv_input)
if len(headers) < 5:
return FileValidationError(FileValidationMessage.InvalidHeaders, 0)
if len(rows) == 0:
return FileValidationError(FileValidationMessage.InvalidRowCount, 0)
for row_index, row in enumerate(rows):
chain = [
CsvInputValidator.validate_for_csv,
CsvInputValidator.validate_for_wind_zones,
CsvInputValidator.validate_for_panel_type,
]
result = self.run_validation_chain(headers, row, chain)
if result:
return FileValidationError(result, row_index + 1)
file_validation_chain = [
CsvInputValidator.validate_file_for_panel_types,
]
result = self.run_validation_chain(headers, rows, file_validation_chain)
if result:
return FileValidationError(result, None)
return None
except:
return FileValidationError(FileValidationMessage.Generic, None)
# Row Validators
def validate_for_csv(self, headers, row):
if len(row) != len(headers):
return FileValidationMessage.Generic
return None
def validate_for_wind_zones(self, headers, row):
valid_wind_zones = self.user_values.system_type().system_constants().wind_zones
if row[2] not in valid_wind_zones:
return FileValidationMessage.invalid_wind_zones(self.user_values.system_type())
return None
def validate_for_panel_type(self, headers, row):
valid_panel_types = range(1, 5)
if int(row[3]) not in valid_panel_types:
return FileValidationMessage.PanelTypeOutOfBounds
return None
# File Validators
def validate_file_for_panel_types(self, headers, rows):
panel_type_counts_per_subarray = {}
for row in rows:
subarray = int(row[4])
panel_type_counts = panel_type_counts_per_subarray.get(subarray) or {}
panel_type = int(row[3])
count = panel_type_counts.get(panel_type) or 0
panel_type_counts[panel_type] = count + 1
panel_type_counts_per_subarray[subarray] = panel_type_counts
minimum_module_count = self.user_values.system_type().system_constants().minimum_corner_module_count
for panel_type_counts in panel_type_counts_per_subarray.values():
if (panel_type_counts.get(1) or 0) < minimum_module_count:
return FileValidationMessage.panel_type_too_few_corners(self.user_values.system_type())
return None
# Helpers
def run_validation_chain(self, headers, data, chain):
result = None
chain = list(chain)
while not result and len(chain) > 0:
method = chain.pop()
result = method(self, headers, data)
return result
def parse_cad_input(self, cad_input):
reader = csv.reader(cad_input.splitlines(), dialect='excel-tab')
headers = next(reader)
rows = [row for row in reader]
return headers, rows

View File

@@ -0,0 +1,9 @@
from helix.constants.file_validation_error import FileValidationMessage, FileValidationError
class DxfInputValidator(object):
def __init__(self, _):
pass
def validate(self, dxf_input):
return None

View File

@@ -0,0 +1,65 @@
"""
Created on Mar 23, 2017
@author: jvazquez
"""
from helix.constants.file_validation_error import FileValidationMessage
from helix.models.dxf.dxf_error import OldDxfFormatException
class DXFLayerValidator(object):
"""
In 2017, Aurora changed the format of their DXF files to meet new
specifications. The old-style DXF files will not
be supported, and this class will screen for old files.
Most noticably, old files used "Roofs" section headers while
new files use "Buildings"
"""
def __init__(self):
self.layers = set()
self.new_aurora_format = False
def add_layer(self, layer):
"""Add a layer in a set
param
layer (string)
"""
self.layers.add(layer)
def determine_file(self):
"""Based on the layers of the dxf,
we will determine if is a "new"
file format
or is the "old" format.
Bare in mind that we are <strong>assuming</strong>
this based on an assumption.
There's no drastic visible difference
in the files if you inspect them
with a text editor.
The whole difference comes
from the space in the panels.
The width remains the same, but there is a gap
in the middle in the new format.
At this moment, this is the simplest
format we know on how to determine this.
"""
try:
assert "Buildings" in self.layers and "Roofs" not in self.layers
self.new_aurora_format = True
except AssertionError:
error = FileValidationMessage.OldDxfFormat.value
raise OldDxfFormatException(error)
def is_new_aurora_format(self):
""" return boolean """
return self.new_aurora_format

View File

@@ -0,0 +1,171 @@
from enum import Enum
import pathlib
from helix.constants.file_validation_error import FileValidationError
from helix.constants.file_validation_error import FileValidationMessage
from helix.validators.csv_input_validator import CsvInputValidator
from helix.validators.dxf_input_validator import DxfInputValidator
class UnknownFileValidator(object):
def __init__(self, _):
pass
def validate(self, _):
return FileValidationError(FileValidationMessage.UnknownFileUploaded,
None)
class FileType(Enum):
Csv = 0
AuroraDxf = 1
Unknown = 2
@property
def validator(self):
"""Provide the proper implementation
of the validation for the selected
FileType
"""
return {
FileType.Csv: CsvInputValidator,
FileType.AuroraDxf: DxfInputValidator,
FileType.Unknown: UnknownFileValidator
}.get(self)
def valid_mapping(self, extension):
"""Validates the extension obtained file
Arguments;
extension: string
Returns:
boolean
"""
try:
mapping = {FileType.Csv: ["txt", "csv"],
FileType.AuroraDxf: ["dxf"],
FileType.Unknown: [None]}.get(self)
assert extension in mapping
except AssertionError:
raise InvalidMappingException
def get_invalid_mapping_error(self):
"""Get the validation message
when choosing the wrong file for the
selected FileType
Returns:
dict
"""
mapping = {FileType.Csv:
FileValidationMessage.ExpectedTxtFile,
FileType.AuroraDxf:
FileValidationMessage.ExpectedDxfFile,
FileType.Unknown:
FileValidationMessage.UnknownFileUploaded}.get(self)
return mapping
class FileValidator(object):
"""Validates that the received stream
belongs to a valid FileType
"""
def __init__(self, user_values):
self.values = user_values
def obtain_stream(self, path):
"""Obtain the content of the file
Argument:
path (str): A path that will be opened
Returns:
str The content of the file, otherwise an empty string
"""
content = ""
try:
content = path.read().decode("utf-8")
except UnicodeDecodeError:
pass
finally:
return content
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
"""
try:
file_type = self.identify_file_type(stream)
assert file_type == expected
validator = file_type.validator(self.values)
extension = self.obtain_extension(file)
file_type.valid_mapping(extension)
return validator.validate(stream)
except AssertionError:
return FileValidationError(expected.get_invalid_mapping_error(),
None)
except InvalidMappingException:
error_type = file_type.get_invalid_mapping_error()
return FileValidationError(error_type, None)
except IndexError:
error_type = FileValidationMessage.UnknownFileUploaded
return FileValidationError(error_type, None)
except InvalidExtensionException:
error_type = FileValidationMessage.UnknownFileUploaded
return FileValidationError(error_type, None)
def obtain_extension(self, file_object):
"""Obtain the extension from the
user uploaded file
Arguments:
file_object (file): A file like object
"""
extension = pathlib.Path(file_object.filename).suffix
file_pieces = extension.split(".")
return file_pieces[1]
def identify_file_type(self, file):
"""Determines the FileType object
based on the beginning of the provided string
Arguments;
file (str): The raw stream
Returns:
enum
"""
if file.startswith("999\r\nDesign created by Aurora"):
return FileType.AuroraDxf
if file.startswith("HANDLE\t"):
return FileType.Csv
return FileType.Unknown
class InvalidExtensionException(Exception):
pass
class InvalidMappingException(Exception):
pass

View File

@@ -0,0 +1,15 @@
from helix.calculators.subarray_helper import extract_subarray
from helix.constants.seismic_anchor_validation_error import SeismicAnchorValidationError
class SeismicAnchorValidator(object):
def __init__(self, calculator):
self.calculator = calculator
def validate(self, panel_data):
subarray_summary = self.calculator.subarray_summary()
for subarray in subarray_summary:
subarray_panels = extract_subarray(panel_data, subarray.subarray_number)
seismic_anchor_count = sum(panel.seismic_anchors or 0 for panel in subarray_panels)
if subarray.required_seismic_anchors > seismic_anchor_count:
return SeismicAnchorValidationError.TooFewAnchors

View File

@@ -0,0 +1,47 @@
from helix.constants.subarray import SUBARRAY_SIZE_BIG
from helix.constants.system_type import SystemType
from helix.models.dxf.dxf_error import DXFError
from helix.models.dxf.graph_direction import GraphDirection
class SubarrayValidator(object):
"""
Tests to make sure that Single Tilt systems have East or West neighbors
(as a panel without an East/West neighbor is
invalid, and that Dual Tilt systems have North or South neighbors.
Also checks to make sure that all "sides" of the
subarray are less than 150' long (due to government safety requirements).
"""
@staticmethod
def validate_subarray(node_graph, subarray_number, system_type):
subarray_node_graph = [node for node in node_graph if node.panel.subarray == subarray_number]
furthest_west = subarray_node_graph[0].panel.coordinate.unrotate()
furthest_east = subarray_node_graph[0].panel.coordinate.unrotate()
furthest_north = subarray_node_graph[0].panel.coordinate.unrotate()
furthest_south = subarray_node_graph[0].panel.coordinate.unrotate()
for node in subarray_node_graph:
if system_type == SystemType.singleTilt:
if not (node.has_existing_neighbor(GraphDirection.East) or node.has_existing_neighbor(GraphDirection.West)):
raise DXFError("Error - Unsupported panel configuration in subarray %d." % subarray_number)
if not (node.has_existing_neighbor(GraphDirection.North) or node.has_existing_neighbor(GraphDirection.South)):
raise DXFError("Error - Unsupported panel configuration in subarray %d." % subarray_number)
rotated_coordinate = node.panel.coordinate.unrotate()
if rotated_coordinate.x < furthest_west.x:
furthest_west = rotated_coordinate
elif rotated_coordinate.x > furthest_east.x:
furthest_east = rotated_coordinate
if rotated_coordinate.y < furthest_south.y:
furthest_south = rotated_coordinate
elif rotated_coordinate.y > furthest_north.y:
furthest_north = rotated_coordinate
# detect subarray size
horizontal_distance = furthest_east.x - furthest_west.x
if horizontal_distance > (150 * 12):
raise DXFError(SUBARRAY_SIZE_BIG)
vertical_distance = furthest_north.y - furthest_south.y
if vertical_distance > (150 * 12):
raise DXFError(SUBARRAY_SIZE_BIG)