def process(self, organization_id, cycle, property_view=None): """ Process the building file that was uploaded and create the correct models for the object :param organization_id: integer, ID of organization :param cycle: object, instance of cycle object :param property_view: Existing property view of the building file that will be updated from merging the property_view.state :return: list, [status, (PropertyState|None), (PropertyView|None), messages] """ Parser = self.BUILDING_FILE_PARSERS.get(self.file_type, None) if not Parser: acceptable_file_types = ', '.join( map( dict(self.BUILDING_FILE_TYPES).get, list(self.BUILDING_FILE_PARSERS.keys()))) return False, None, None, "File format was not one of: {}".format( acceptable_file_types) parser = Parser() try: parser.import_file(self.file.path) parser_args = [] parser_kwargs = {} # TODO: use table_mappings for BuildingSync process method data, messages = parser.process(*parser_args, **parser_kwargs) except ParsingError as e: return False, None, None, [str(e)] if len(messages['errors']) > 0 or not data: return False, None, None, messages # Create the property state if none already exists for this file if self.property_state is None: property_state = self._create_property_state(organization_id, data) else: property_state = self.property_state # save the property state self.property_state_id = property_state.id self.save() # add in the measures for m in data.get('measures', []): # Find the measure in the database try: measure = Measure.objects.get( category=m['category'], name=m['name'], organization_id=organization_id, ) except Measure.DoesNotExist: messages['warnings'].append( 'Measure category and name is not valid %s:%s' % (m['category'], m['name'])) continue # Add the measure to the join table. # Need to determine what constitutes the unique measure for a property implementation_status = m['implementation_status'] if m.get( 'implementation_status') else 'Proposed' application_scale = m['application_scale_of_application'] if m.get( 'application_scale_of_application' ) else PropertyMeasure.SCALE_ENTIRE_FACILITY category_affected = m['system_category_affected'] if m.get( 'system_category_affected') else PropertyMeasure.CATEGORY_OTHER join, _ = PropertyMeasure.objects.get_or_create( property_state_id=self.property_state_id, measure_id=measure.pk, property_measure_name=m.get('property_measure_name'), implementation_status=PropertyMeasure.str_to_impl_status( implementation_status), application_scale=PropertyMeasure.str_to_application_scale( application_scale), category_affected=PropertyMeasure.str_to_category_affected( category_affected), recommended=m.get('recommended', 'false') == 'true', ) join.description = m.get('description') join.cost_mv = m.get('mv_cost') join.cost_total_first = m.get('measure_total_first_cost') join.cost_installation = m.get('measure_installation_cost') join.cost_material = m.get('measure_material_cost') join.cost_capital_replacement = m.get( 'measure_capital_replacement_cost') join.cost_residual_value = m.get('measure_residual_value') join.save() # add in scenarios for s in data.get('scenarios', []): # measures = models.ManyToManyField(PropertyMeasure) # {'reference_case': 'Baseline', 'annual_savings_site_energy': None, # 'measures': [], 'id': 'Baseline', 'name': 'Baseline'} # If the scenario does not have a name then log a warning and continue if not s.get('name'): messages['warnings'].append( 'Skipping scenario because it does not have a name. ID = %s' % s.get('id')) continue scenario, _ = Scenario.objects.get_or_create( name=s.get('name'), property_state_id=self.property_state_id, ) scenario.description = s.get('description') scenario.annual_site_energy_savings = s.get( 'annual_site_energy_savings') scenario.annual_source_energy_savings = s.get( 'annual_source_energy_savings') scenario.annual_cost_savings = s.get('annual_cost_savings') scenario.summer_peak_load_reduction = s.get( 'summer_peak_load_reduction') scenario.winter_peak_load_reduction = s.get( 'winter_peak_load_reduction') scenario.hdd = s.get('hdd') scenario.hdd_base_temperature = s.get('hdd_base_temperature') scenario.cdd = s.get('cdd') scenario.cdd_base_temperature = s.get('cdd_base_temperature') scenario.annual_electricity_savings = s.get( 'annual_electricity_savings') scenario.annual_natural_gas_savings = s.get( 'annual_natural_gas_savings') scenario.annual_site_energy = s.get('annual_site_energy') scenario.annual_source_energy = s.get('annual_source_energy') scenario.annual_site_energy_use_intensity = s.get( 'annual_site_energy_use_intensity') scenario.annual_source_energy_use_intensity = s.get( 'annual_source_energy_use_intensity') scenario.annual_natural_gas_energy = s.get( 'annual_natural_gas_energy') scenario.annual_electricity_energy = s.get( 'annual_electricity_energy') scenario.annual_peak_demand = s.get('annual_peak_demand') # temporal_status = models.IntegerField(choices=TEMPORAL_STATUS_TYPES, # default=TEMPORAL_STATUS_CURRENT) if s.get('reference_case'): ref_case = Scenario.objects.filter( name=s.get('reference_case'), property_state_id=self.property_state_id, ) if len(ref_case) == 1: scenario.reference_case = ref_case.first() # set the list of measures. Note that this can be empty (e.g. baseline has no measures) for measure_name in s.get('measures', []): # find the join measure in the database measure = None try: measure = PropertyMeasure.objects.get( property_state_id=self.property_state_id, property_measure_name=measure_name, ) except PropertyMeasure.DoesNotExist: # PropertyMeasure is not in database, skipping silently messages['warnings'].append( 'Measure associated with scenario not found. Scenario: %s, Measure name: %s' % (s.get('name'), measure_name)) continue scenario.measures.add(measure) scenario.save() # meters for m in s.get('meters', []): # print("BUILDING FILE METER: {}".format(m)) # check by scenario_id and source_id meter, _ = Meter.objects.get_or_create( scenario_id=scenario.id, source_id=m.get('source_id'), ) meter.source = m.get('source') meter.type = m.get('type') meter.is_virtual = m.get('is_virtual') meter.save() # meterreadings # TODO: need to check that these are in kBtu already? readings = { MeterReading( start_time=mr.get('start_time'), end_time=mr.get('end_time'), reading=mr.get('reading'), source_unit=mr.get('source_unit'), meter_id=meter.id, conversion_factor=1.00, # assuming kBtu ) for mr in m.get('readings', []) } MeterReading.objects.bulk_create(readings) # merge or create the property state's view if property_view: # create a new blank state to merge the two together merged_state = PropertyState.objects.create( organization_id=organization_id) # assume the same cycle id as the former state. # should merge_state also copy/move over the relationships? priorities = Column.retrieve_priorities(organization_id) merged_state = merge_state(merged_state, property_view.state, property_state, priorities['PropertyState']) # log the merge # Not a fan of the parent1/parent2 logic here, seems error prone, what this # is also in here: https://github.com/SEED-platform/seed/blob/63536e99cf5be3a9a86391c5cead6dd4ff74462b/seed/data_importer/tasks.py#L1549 PropertyAuditLog.objects.create( organization_id=organization_id, parent1=PropertyAuditLog.objects.filter( state=property_view.state).first(), parent2=PropertyAuditLog.objects.filter( state=property_state).first(), parent_state1=property_view.state, parent_state2=property_state, state=merged_state, name='System Match', description='Automatic Merge', import_filename=None, record_type=AUDIT_IMPORT) property_view.state = merged_state property_view.save() merged_state.merge_state = MERGE_STATE_MERGED merged_state.save() # set the property_state to the new one property_state = merged_state elif not property_view: property_view = property_state.promote(cycle) else: # invalid arguments, must pass both or neither return False, None, None, "Invalid arguments passed to BuildingFile.process()" return True, property_state, property_view, messages
def delete_measures(self, request, pk=None): """ Delete measures. Allow the user to define which implementation type to delete --- type: status: required: true type: string message: required: true type: string parameters: - name: cycle_id description: The cycle id for filtering the property view required: true paramType: query - name: organization_id description: The organization_id for this user's organization required: true paramType: query - name: implementation_status description: Enum on type of measures. Recommended, Proposed, Implemented required: false paramType: form type: string enum: ["Recommended", "Proposed", "Implemented"] """ cycle_pk = request.query_params.get('cycle_id', None) if not cycle_pk: return JsonResponse({ 'status': 'error', 'message': 'Must pass cycle_id as query parameter' }) impl_status = request.data.get('implementation_status', None) if not impl_status: impl_status = [ PropertyMeasure.RECOMMENDED, PropertyMeasure.IMPLEMENTED, PropertyMeasure.PROPOSED ] else: impl_status = [PropertyMeasure.str_to_impl_status(impl_status)] result = self._get_property_view(pk) pv = None if result.get('status', None) != 'error': pv = result.pop('property_view') else: return JsonResponse(result) property_state_id = pv.state.pk del_count, _ = PropertyMeasure.objects.filter( property_state_id=property_state_id, implementation_status__in=impl_status, ).delete() return JsonResponse({ "status": "status", "message": "Deleted {} measures".format(del_count) })
def process(self, organization_id, cycle, property_view=None): """ Process the building file that was uploaded and create the correct models for the object :param organization_id: integer, ID of organization :param cycle: object, instance of cycle object :param property_view: Existing property view of the building file that will be updated from merging the property_view.state :return: list, [status, (PropertyState|None), (PropertyView|None), messages] """ Parser = self.BUILDING_FILE_PARSERS.get(self.file_type, None) if not Parser: acceptable_file_types = ', '.join( map( dict(self.BUILDING_FILE_TYPES).get, list(self.BUILDING_FILE_PARSERS.keys()))) return False, None, None, "File format was not one of: {}".format( acceptable_file_types) parser = Parser() parser.import_file(self.file.path) parser_args = [] parser_kwargs = {} if self.file_type == self.BUILDINGSYNC: parser_args.append(BuildingSync.BRICR_STRUCT) data, messages = parser.process(*parser_args, **parser_kwargs) if len(messages['errors']) > 0 or not data: return False, None, None, messages # sub-select the data that are needed to create the PropertyState object db_columns = Column.retrieve_db_field_table_and_names_from_db_tables() create_data = {"organization_id": organization_id} extra_data = {} for k, v in data.items(): # Skip the keys that are for measures and reports and process later if k in ['measures', 'reports', 'scenarios']: continue # Check if the column exists, if not, then create one. if ('PropertyState', k) in db_columns: create_data[k] = v else: extra_data[k] = v # always create the new object, then decide if we need to merge it. # create a new property_state for the object and promote to a new property_view property_state = PropertyState.objects.create(**create_data) property_state.extra_data = extra_data property_state.save() Column.save_column_names(property_state) PropertyAuditLog.objects.create( organization_id=organization_id, state_id=property_state.id, name='Import Creation', description='Creation from Import file.', import_filename=self.file.path, record_type=AUDIT_IMPORT) # set the property_state_id so that we can list the building files by properties self.property_state_id = property_state.id self.save() # add in the measures for m in data.get('measures', []): # Find the measure in the database try: measure = Measure.objects.get( category=m['category'], name=m['name'], organization_id=organization_id, ) except Measure.DoesNotExist: messages['warnings'].append( 'Measure category and name is not valid %s:%s' % (m['category'], m['name'])) continue # Add the measure to the join table. # Need to determine what constitutes the unique measure for a property join, _ = PropertyMeasure.objects.get_or_create( property_state_id=self.property_state_id, measure_id=measure.pk, property_measure_name=m.get('property_measure_name'), implementation_status=PropertyMeasure.str_to_impl_status( m.get('implementation_status', 'Proposed')), application_scale=PropertyMeasure.str_to_application_scale( m.get('application_scale_of_application', PropertyMeasure.SCALE_ENTIRE_FACILITY)), category_affected=PropertyMeasure.str_to_category_affected( m.get('system_category_affected', PropertyMeasure.CATEGORY_OTHER)), recommended=m.get('recommended', 'false') == 'true', ) join.description = m.get('description') join.cost_mv = m.get('mv_cost') join.cost_total_first = m.get('measure_total_first_cost') join.cost_installation = m.get('measure_installation_cost') join.cost_material = m.get('measure_material_cost') join.cost_capital_replacement = m.get( 'measure_capital_replacement_cost') join.cost_residual_value = m.get('measure_residual_value') join.save() # add in scenarios for s in data.get('scenarios', []): # measures = models.ManyToManyField(PropertyMeasure) # {'reference_case': 'Baseline', 'annual_savings_site_energy': None, # 'measures': [], 'id': 'Baseline', 'name': 'Baseline'} # If the scenario does not have a name then log a warning and continue if not s.get('name'): messages['warnings'].append( 'Scenario does not have a name. ID = %s' % s.get('id')) continue scenario, _ = Scenario.objects.get_or_create( name=s.get('name'), property_state_id=self.property_state_id, ) scenario.description = s.get('description') scenario.annual_site_energy_savings = s.get( 'annual_site_energy_savings') scenario.annual_source_energy_savings = s.get( 'annual_source_energy_savings') scenario.annual_cost_savings = s.get('annual_cost_savings') scenario.summer_peak_load_reduction = s.get( 'summer_peak_load_reduction') scenario.winter_peak_load_reduction = s.get( 'winter_peak_load_reduction') scenario.hdd = s.get('hdd') scenario.hdd_base_temperature = s.get('hdd_base_temperature') scenario.cdd = s.get('cdd') scenario.cdd_base_temperature = s.get('cdd_base_temperature') # temporal_status = models.IntegerField(choices=TEMPORAL_STATUS_TYPES, # default=TEMPORAL_STATUS_CURRENT) if s.get('reference_case'): ref_case = Scenario.objects.filter( name=s.get('reference_case'), property_state_id=self.property_state_id, ) if len(ref_case) == 1: scenario.reference_case = ref_case.first() # set the list of measures. Note that this can be empty (e.g. baseline has no measures) for measure_name in s.get('measures', []): # find the join measure in the database measure = None try: measure = PropertyMeasure.objects.get( property_state_id=self.property_state_id, property_measure_name=measure_name, ) except PropertyMeasure.DoesNotExist: # PropertyMeasure is not in database, skipping silently messages['warnings'].append( 'Measure associated with scenario not found. Scenario: %s, Measure name: %s' % (s.get('name'), measure_name)) continue scenario.measures.add(measure) scenario.save() if property_view: # create a new blank state to merge the two together merged_state = PropertyState.objects.create( organization_id=organization_id) # assume the same cycle id as the former state. # should merge_state also copy/move over the relationships? priorities = Column.retrieve_priorities(organization_id) merged_state = merge_state(merged_state, property_view.state, property_state, priorities['PropertyState']) # log the merge # Not a fan of the parent1/parent2 logic here, seems error prone, what this # is also in here: https://github.com/SEED-platform/seed/blob/63536e99cf5be3a9a86391c5cead6dd4ff74462b/seed/data_importer/tasks.py#L1549 PropertyAuditLog.objects.create( organization_id=organization_id, parent1=PropertyAuditLog.objects.filter( state=property_view.state).first(), parent2=PropertyAuditLog.objects.filter( state=property_state).first(), parent_state1=property_view.state, parent_state2=property_state, state=merged_state, name='System Match', description='Automatic Merge', import_filename=None, record_type=AUDIT_IMPORT) property_view.state = merged_state property_view.save() merged_state.merge_state = MERGE_STATE_MERGED merged_state.save() # set the property_state to the new one property_state = merged_state elif not property_view: property_view = property_state.promote(cycle) else: # invalid arguments, must pass both or neither return False, None, None, "Invalid arguments passed to BuildingFile.process()" return True, property_state, property_view, messages
def add_measures(self, request, pk=None): """ Update the measures applied to the building. There are two options, one for adding measures and one for removing measures. --- type: status: required: true type: string message: required: true type: object added_measure_ids: required: true type: array description: list of measure ids that were added to the property removed_measure_ids: required: true type: array description: list of measure ids that were removed from the property existing_measure_ids: required: true type: array description: list of measure ids that already existed for the property parameters: - name: cycle_id description: The cycle id for filtering the property view required: true paramType: query - name: organization_id description: The organization_id for this user's organization required: true paramType: query - name: add_measures description: list of measure_ids or measure long names to add to property type: array required: false paramType: form - name: remove_measures description: list of measure_ids or measure long names to remove from property type: array required: false paramType: form - name: implementation_status description: Enum on type of measures. Recommended, Proposed, Implemented required: true paramType: form type: string enum: ["Recommended", "Proposed", "Implemented"] """ cycle_pk = request.query_params.get('cycle_id', None) if not cycle_pk: return JsonResponse({ 'status': 'error', 'message': 'Must pass cycle_id as query parameter' }) implementation_status = PropertyMeasure.str_to_impl_status( request.data.get('implementation_status', None)) if not implementation_status: return JsonResponse({ 'status': 'error', 'message': 'None or invalid implementation_status type' }) result = self._get_property_view(pk) pv = None if result.get('status', None) != 'error': pv = result.pop('property_view') else: return JsonResponse(result) # get the list of measures to add/remove and return the ids add_measure_ids = Measure.validate_measures( request.data.get('add_measures', []).split(',')) remove_measure_ids = Measure.validate_measures( request.data.get('remove_measures', []).split(',')) # add_measures = request.data message_add = [] message_remove = [] message_existed = [] property_state_id = pv.state.pk for m in add_measure_ids: join, created = PropertyMeasure.objects.get_or_create( property_state_id=property_state_id, measure_id=m, implementation_status=implementation_status) if created: message_add.append(m) else: message_existed.append(m) for m in remove_measure_ids: qs = PropertyMeasure.objects.filter( property_state_id=property_state_id, measure_id=m, implementation_status=implementation_status) if qs.exists(): qs.delete() message_remove.append(m) return JsonResponse({ "status": "success", "message": "Updated measures for property state", "added_measure_ids": message_add, "removed_measure_ids": message_remove, "existing_measure_ids": message_existed, })