Esempio n. 1
0
def test_basic_usage(meter_input):
    result = deserialize_meter_input(meter_input)
    assert isinstance(result["trace"], EnergyTrace)
    assert isinstance(result["project"]["modeling_period_set"],
                      ModelingPeriodSet)
    assert isinstance(result["project"]["zipcode"], str)
    baseline_period = result['project'][
        'modeling_period_set'].modeling_periods['baseline']
    assert baseline_period.end_date.tzinfo == pytz.utc
Esempio n. 2
0
    def evaluate(self,
                 meter_input,
                 formatter=None,
                 model=None,
                 weather_source=None,
                 weather_normal_source=None):
        ''' Main entry point to the meter, which models traces and calculates
        derivatives.

        Parameters
        ----------
        meter_input : dict
            Serialized input containing trace and project data.
        formatter : tuple of (class, dict), default None
            Formatter for trace and weather data. Used to create input
            for model. If None is provided, will be auto-matched to appropriate
            default formatter. Class name can be provided as a string
            (class.__name__) or object.
        model : tuple of (class, dict), default None
            Model to use in modeling. If None is provided,
            will be auto-matched to appropriate default model.
            Class can be provided as a string (class.__name__) or class object.
        weather_source : eemeter.weather.WeatherSource
            Weather source to be used for this meter. Overrides weather source
            found using :code:`project.site`. Useful for test mocking.
        weather_normal_source : eemeter.weather.WeatherSource
            Weather normal source to be used for this meter. Overrides weather
            source found using :code:`project.site`. Useful for test mocking.

        Returns
        -------
        results : dict
            Dictionary of results with the following keys:

            - :code:`"status"`: SUCCESS/FAILURE
            - :code:`"failure_message"`: if FAILURE, message indicates reason
              for failure, may include traceback
            - :code:`"logs"`: list of collected log messages
            - :code:`"model_class"`: Name of model class
            - :code:`"model_kwargs"`: dict of model keyword arguments
              (settings)
            - :code:`"formatter_class"`: Name of formatter class
            - :code:`"formatter_kwargs"`: dict of formatter keyword arguments
              (settings)
            - :code:`"eemeter_version"`: version of the eemeter package
            - :code:`"modeled_energy_trace"`: modeled energy trace
            - :code:`"derivatives"`: derivatives for each interpretation
            - :code:`"weather_source_station"`: Matched weather source station.
            - :code:`"weather_normal_source_station"`: Matched weather normal
              source station.
        '''

        SUCCESS = "SUCCESS"
        FAILURE = "FAILURE"

        output = OrderedDict([
            ("status", None),
            ("failure_message", None),
            ("logs", []),
            ("eemeter_version", get_version()),
            ("trace_id", None),
            ("project_id", None),
            ("interval", None),
            ("meter_kwargs", self.kwargs),
            ("model_class", None),
            ("model_kwargs", None),
            ("formatter_class", None),
            ("formatter_kwargs", None),
            ("weather_source_station", None),
            ("weather_normal_source_station", None),
            ("derivatives", None),
            ("modeled_energy_trace", None),
        ])

        # Step 1: Deserialize input and validate
        deserialized_input = deserialize_meter_input(meter_input)
        if "error" in deserialized_input:
            message = ("Meter input could not be deserialized:\n{}".format(
                deserialized_input))
            output['status'] = FAILURE
            output['failure_message'] = message
            return output

        # Assume that deserialized input fails without these keys, so don't
        # bother error checking
        trace = deserialized_input["trace"]
        project = deserialized_input["project"]
        zipcode = project["zipcode"]
        site = ZIPCodeSite(zipcode)

        # Can be blank for models capable of structural change analysis, so
        # provide default
        modeling_period_set = project.get("modeling_period_set", None)

        project_id = project["project_id"]
        trace_id = trace.trace_id
        interval = trace.interval

        output['project_id'] = project_id
        output['trace_id'] = trace_id
        output['interval'] = interval

        logger.debug('Running meter for for trace {} and project {}'.format(
            project_id, trace_id))

        # Step 2: Match weather
        use_cz2010 = (self.weather_station_mapping == 'CZ2010')
        if weather_source is None:
            weather_source = get_weather_source(site, use_cz2010=use_cz2010)

            if weather_source is None:
                message = (
                    "Could not find weather normal source matching site {}".
                    format(site))
                weather_source_usaf_id = None
            else:
                message = "Using weather_source {}".format(weather_source)
                weather_source_usaf_id = weather_source.usaf_id
        else:
            message = "Using supplied weather_source"
            weather_source_usaf_id = weather_source.usaf_id
        output['weather_source_station'] = weather_source_usaf_id
        output['logs'].append(message)
        logger.debug(message)

        if weather_normal_source is None:

            use_cz2010 = (self.weather_normal_station_mapping == 'CZ2010')
            weather_normal_source = get_weather_normal_source(
                site, use_cz2010=use_cz2010)
            if weather_normal_source is None:
                message = (
                    "Could not find weather normal source matching site {}".
                    format(site))
                weather_normal_source_usaf_id = None
            else:
                message = ("Using weather_normal_source {}".format(
                    weather_normal_source))
                weather_normal_source_usaf_id = weather_normal_source.usaf_id
        else:
            message = "Using supplied weather_normal_source"
            weather_normal_source_usaf_id = weather_normal_source.usaf_id
        output['weather_normal_source_station'] = weather_normal_source_usaf_id
        output['logs'].append(message)
        logger.debug(message)

        # Step 3: Check to see if trace is placeholder. If so,
        # return with SUCCESS, empty derivatives.
        if trace.placeholder:
            message = (
                'Skipping modeling for placeholder trace {}'.format(trace))
            logger.info(message)
            output['logs'].append(message)
            output['status'] = SUCCESS
            output['derivatives'] = []
            return output

        # Step 4: Determine trace interpretation and frequency
        # TODO use trace interval here. And enforce upstream that interval use
        # pandas interval strings?
        trace_frequency = get_approximate_frequency(trace)

        if trace_frequency not in ['H', 'D', '15T', '30T']:
            trace_frequency = None

        selector = (trace.interpretation, trace_frequency)

        # Step 5: create formatter instance
        FormatterClass, formatter_kwargs = self._get_formatter(
            formatter, selector)
        if FormatterClass is None:
            message = ("Default formatter mapping did not find a match for the"
                       " selector {}".format(selector))
            output['status'] = FAILURE
            output['failure_message'] = message
            return output
        output["formatter_class"] = FormatterClass.__name__
        output["formatter_kwargs"] = formatter_kwargs
        formatter_instance = FormatterClass(**formatter_kwargs)

        # Step 6: create model instance
        ModelClass, model_kwargs = self._get_model(model, selector)
        if ModelClass is None:
            message = ("Default model mapping did not find a match for the"
                       " selector {}".format(selector))
            output['status'] = FAILURE
            output['failure_message'] = message
            return output
        output["model_class"] = ModelClass.__name__
        output["model_kwargs"] = model_kwargs

        # Step 7: validate modeling period set. Always fails for now, since
        # no models are yet fully structural change analysis aware
        if modeling_period_set is None:
            message = (
                "Model is not structural-change capable, so `modeling_period`"
                " argument must be supplied.")
            output['status'] == FAILURE
            output['failure_message'] = message
            return output

        # Step 8: create split modeled energy trace
        model_mapping = {
            modeling_period_label:
            ModelClass(modeling_period_interpretation=modeling_period_label,
                       **model_kwargs)
            for modeling_period_label, _ in
            modeling_period_set.iter_modeling_periods()
        }

        modeled_trace = SplitModeledEnergyTrace(trace, formatter_instance,
                                                model_mapping,
                                                modeling_period_set)

        modeled_trace.fit(weather_source)
        output["modeled_energy_trace"] = \
            serialize_split_modeled_energy_trace(modeled_trace)

        # Step 9: for each modeling period group, create derivatives
        derivative_freq = 'D'
        if 'freq_str' in formatter_kwargs.keys() and \
                formatter_kwargs['freq_str'] == 'H':
            derivative_freq = 'H'

        derivatives = []
        for ((baseline_label, reporting_label),
                (baseline_period, reporting_period)) in \
                modeling_period_set.iter_modeling_period_groups():
            raw_derivatives = []
            deriv_input = unpack(modeled_trace,
                                 baseline_label,
                                 reporting_label,
                                 baseline_period,
                                 reporting_period,
                                 weather_source,
                                 weather_normal_source,
                                 site,
                                 derivative_freq=derivative_freq)
            if deriv_input is None:
                continue
            raw_derivatives.extend([
                hdd_balance_point_baseline(deriv_input),
                hdd_coefficient_baseline(deriv_input),
                cdd_balance_point_baseline(deriv_input),
                cdd_coefficient_baseline(deriv_input),
                intercept_baseline(deriv_input),
                hdd_balance_point_reporting(deriv_input),
                hdd_coefficient_reporting(deriv_input),
                cdd_balance_point_reporting(deriv_input),
                cdd_coefficient_reporting(deriv_input),
                intercept_reporting(deriv_input),
                cumulative_baseline_model_minus_reporting_model_normal_year(
                    deriv_input),
                baseline_model_minus_reporting_model_normal_year(deriv_input),
                cumulative_baseline_model_normal_year(deriv_input),
                baseline_model_normal_year(deriv_input),
                cumulative_baseline_model_reporting_period(deriv_input),
                baseline_model_reporting_period(deriv_input),
                masked_baseline_model_reporting_period(deriv_input),
                cumulative_baseline_model_minus_observed_reporting_period(
                    deriv_input),
                baseline_model_minus_observed_reporting_period(deriv_input),
                masked_baseline_model_minus_observed_reporting_period(
                    deriv_input),
                baseline_model_baseline_period(deriv_input),
                cumulative_reporting_model_normal_year(deriv_input),
                reporting_model_normal_year(deriv_input),
                reporting_model_reporting_period(deriv_input),
                cumulative_observed_reporting_period(deriv_input),
                observed_reporting_period(deriv_input),
                masked_observed_reporting_period(deriv_input),
                cumulative_observed_baseline_period(deriv_input),
                observed_baseline_period(deriv_input),
                observed_project_period(deriv_input),
                temperature_baseline_period(deriv_input),
                temperature_reporting_period(deriv_input),
                masked_temperature_reporting_period(deriv_input),
                temperature_normal_year(deriv_input),
                baseline_mask(deriv_input),
                reporting_mask(deriv_input),
                reporting_period_resource_curve(deriv_input)
            ])

            resource_curve_normal_year = normal_year_resource_curve(
                deriv_input)
            raw_derivatives.extend([resource_curve_normal_year])

            if resource_curve_normal_year is not None:
                resource_curve_normal_year = pd.Series(
                    resource_curve_normal_year['value'],
                    index=pd.to_datetime(
                        resource_curve_normal_year['orderable']))
                raw_derivatives.extend([
                    normal_year_co2_avoided(deriv_input,
                                            resource_curve_normal_year)
                ])

            derivatives += [
                Derivative(
                    (baseline_label, reporting_label),
                    d['series'],
                    reduce(lambda a, b: a + ' ' + b, d['description'].split()),
                    d['orderable'],
                    d['value'],
                    d['variance'],
                ) for d in raw_derivatives if d is not None
            ]

        output["derivatives"] = serialize_derivatives(derivatives)
        output["status"] = SUCCESS
        return output
def test_missing_project_type(meter_input):
    del meter_input["project"]
    result = deserialize_meter_input(meter_input)
    assert "type" in result["error"]
def test_missing_trace_records(meter_input):
    del meter_input["trace"]["records"]
    result = deserialize_meter_input(meter_input)
    assert "records" in result["error"]
def test_missing_trace_unit(meter_input):
    del meter_input["trace"]["unit"]
    result = deserialize_meter_input(meter_input)
    assert "unit" in result["error"]
def test_missing_trace_interpretation(meter_input):
    del meter_input["trace"]["interpretation"]
    result = deserialize_meter_input(meter_input)
    assert "interpretation" in result["error"]
def test_basic_usage(meter_input):
    result = deserialize_meter_input(meter_input)
    assert isinstance(result["trace"], EnergyTrace)
    assert isinstance(result["project"]["modeling_period_set"],
                      ModelingPeriodSet)
    assert isinstance(result["project"]["zipcode"], str)
def test_missing_project_modeling_period_group(meter_input):
    del meter_input["project"]["modeling_period_group"]
    result = deserialize_meter_input(meter_input)
    assert "modeling_period_group" in result["error"]
def test_missing_project_zipcode(meter_input):
    del meter_input["project"]["zipcode"]
    result = deserialize_meter_input(meter_input)
    assert "zipcode" in result["error"]