def test_basic_usage_daily(split_modeled_energy_trace_daily):
    serialized = serialize_split_modeled_energy_trace(
        split_modeled_energy_trace_daily)

    # no error
    json.dumps(serialized)

    type_ = serialized["type"]
    assert type_ == "SPLIT_MODELED_ENERGY_TRACE"

    fits = serialized["fits"]
    mp1 = fits["modeling_period_1"]
    mp2 = fits["modeling_period_2"]

    assert mp1['status'] == "SUCCESS"
    assert mp1['traceback'] is None
    assert mp1['input_data'] is not None
    assert mp1['start_date'] is not None
    assert mp1['end_date'] is not None
    assert mp1['n_rows'] is not None

    model_fit = mp1['model_fit']
    assert model_fit["r2"] is not None
    assert model_fit["cvrmse"] is not None
    assert model_fit["rmse"] is not None
    assert model_fit["lower"] is not None
    assert model_fit["upper"] is not None
    assert model_fit["n"] is not None
    assert model_fit["model_params"] is not None

    assert mp2['status'] == "FAILURE"
    assert mp2['traceback'] is not None
    assert len(mp2['input_data']) == 0
    assert mp2['start_date'] is None
    assert mp2['end_date'] is None
    assert mp2['n_rows'] is not None
    assert mp2['model_fit']['r2'] is None
    assert mp2['model_fit']['cvrmse'] is None
    assert mp2['model_fit']['rmse'] is None
    assert mp2['model_fit']['lower'] is None
    assert mp2['model_fit']['upper'] is None
    assert mp2['model_fit']['n'] is None
    assert mp2['model_fit']['model_params'] is not None

    modeling_period_set = serialized["modeling_period_set"]
    modeling_periods = modeling_period_set["modeling_periods"]

    modeling_period_groups = modeling_period_set["modeling_period_groups"]
    assert len(modeling_period_groups) == 1
    assert modeling_period_groups[0]["baseline"] == "modeling_period_1"
    assert modeling_period_groups[0]["reporting"] == "modeling_period_2"

    assert modeling_periods["modeling_period_1"]["end_date"] == \
        '2000-09-01T00:00:00+00:00'
def test_basic_usage_monthly(split_modeled_energy_trace_monthly):
    serialized = serialize_split_modeled_energy_trace(
        split_modeled_energy_trace_monthly)

    # no error
    json.dumps(serialized)

    fits = serialized["fits"]
    mp1 = fits["modeling_period_1"]
    mp2 = fits["modeling_period_2"]

    assert mp1['status'] == "SUCCESS"
    assert mp1['traceback'] is None
    assert mp1['input_data'] is not None
    assert mp1['start_date'] is not None
    assert mp1['end_date'] is not None
    assert mp1['n_rows'] is not None

    model_fit = mp1['model_fit']
    assert model_fit["r2"] is not None
    assert model_fit["cvrmse"] is not None
    assert model_fit["rmse"] is not None
    assert model_fit["lower"] is not None
    assert model_fit["upper"] is not None
    assert model_fit["n"] is not None
    assert model_fit["model_params"] is not None

    assert mp2['status'] == "SUCCESS"
    assert mp2['traceback'] is None
    assert len(mp2['input_data']) > 0
    assert mp2['start_date'] is not None
    assert mp2['end_date'] is not None
    assert mp2['n_rows'] is not None
    assert mp2['model_fit'] is not None

    modeling_period_set = serialized["modeling_period_set"]

    modeling_period_groups = modeling_period_set["modeling_period_groups"]
    assert len(modeling_period_groups) == 1
    assert modeling_period_groups[0]["baseline"] == "modeling_period_1"
    assert modeling_period_groups[0]["reporting"] == "modeling_period_2"

    modeling_periods = modeling_period_set["modeling_periods"]

    assert modeling_periods["modeling_period_1"]["end_date"] == \
        '2000-09-01T00:00:00+00:00'
Exemplo n.º 3
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