示例#1
0
    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
示例#2
0
    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)
        })
示例#3
0
    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
示例#4
0
    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,
        })