Пример #1
0
def make_revenues_costs_figure(
    resource_display_name: str,
    data: pd.DataFrame,
    forecast_data: pd.DataFrame,
    show_consumption_as_positive: bool,
    shared_x_range: Range1d,
    selected_market: Market,
    tools: List[str] = None,
) -> Figure:
    """Make a bokeh figure for revenues / costs data"""
    if show_consumption_as_positive:
        rev_cost_str = "Costs"
    else:
        rev_cost_str = "Revenues"

    return create_graph(
        data,
        unit=selected_market.
        unit[:
             3],  # First three letters of a price unit give the currency (ISO 4217)
        legend_location="top_right",
        forecasts=forecast_data,
        title=
        f"{rev_cost_str} for {resource_display_name} (on {selected_market.display_name})",
        x_range=shared_x_range,
        x_label="Time (resolution of %s)" %
        time_utils.freq_label_to_human_readable_label(session["resolution"]),
        y_label="%s (in %s)" % (rev_cost_str, selected_market.unit[:3]),
        show_y_floats=True,
        tools=tools,
    )
Пример #2
0
def make_power_figure(
    resource_display_name: str,
    data: pd.DataFrame,
    forecast_data: Optional[pd.DataFrame],
    schedule_data: Optional[pd.DataFrame],
    show_consumption_as_positive: bool,
    shared_x_range: Range1d,
    tools: List[str] = None,
) -> Figure:
    """Make a bokeh figure for power consumption or generation"""
    if show_consumption_as_positive:
        title = "Electricity consumption of %s" % resource_display_name
    else:
        title = "Electricity production from %s" % resource_display_name

    return create_graph(
        data,
        unit="MW",
        legend_location="top_right",
        legend_labels=("Actual", "Forecast") if schedule_data is None
        or schedule_data["event_value"].isnull().all() else
        ("Actual", "Forecast", "Schedule"),
        forecasts=forecast_data,
        schedules=schedule_data,
        title=title,
        x_range=shared_x_range,
        x_label="Time (resolution of %s)" %
        time_utils.freq_label_to_human_readable_label(session["resolution"]),
        y_label="Power (in MW)",
        show_y_floats=True,
        tools=tools,
    )
Пример #3
0
def make_weather_figure(
    selected_resource: Resource,
    data: pd.DataFrame,
    forecast_data: Union[None, pd.DataFrame],
    shared_x_range: Range1d,
    weather_sensor: Sensor,
    tools: List[str] = None,
    sizing_mode="scale_width",
) -> Figure:
    """Make a bokeh figure for weather data"""
    # Todo: plot average temperature/irradiance/wind speed for asset groups, and update title accordingly
    if weather_sensor is None:
        return create_graph(
            pd.DataFrame(columns=["event_value"]),
            title="Weather plot (no relevant weather sensor found)",
        )
    unit = weather_sensor.unit
    weather_axis_label = "%s (in %s)" % (
        weather_sensor.generic_asset.generic_asset_type.description,
        unit,
    )

    if selected_resource.is_unique_asset:
        title = "%s at %s" % (
            weather_sensor.generic_asset.generic_asset_type.description,
            selected_resource.display_name,
        )
    else:
        title = "%s" % weather_sensor.generic_asset.generic_asset_type.description
    return create_graph(
        data,
        unit=unit,
        forecasts=forecast_data,
        title=title,
        x_range=shared_x_range,
        x_label="Time (resolution of %s)" %
        determine_resolution(data, forecast_data),
        y_label=weather_axis_label,
        legend_location="top_right",
        show_y_floats=True,
        tools=tools,
        sizing_mode=sizing_mode,
    )
Пример #4
0
def make_weather_figure(
    selected_resource: Resource,
    data: pd.DataFrame,
    forecast_data: Union[None, pd.DataFrame],
    shared_x_range: Range1d,
    weather_sensor: WeatherSensor,
    tools: List[str] = None,
) -> Figure:
    """Make a bokeh figure for weather data"""
    # Todo: plot average temperature/total_radiation/wind_speed for asset groups, and update title accordingly
    if weather_sensor is None:
        return create_graph(pd.DataFrame())
    unit = weather_sensor.unit
    weather_axis_label = "%s (in %s)" % (
        humanize(weather_sensor.sensor_type.display_name),
        unit,
    )

    if selected_resource.is_unique_asset:
        title = "%s at %s" % (
            humanize(weather_sensor.sensor_type.display_name),
            selected_resource.display_name,
        )
    else:
        title = "%s" % humanize(weather_sensor.sensor_type.display_name)
    return create_graph(
        data,
        unit=unit,
        forecasts=forecast_data,
        title=title,
        x_range=shared_x_range,
        x_label="Time (resolution of %s)" %
        time_utils.freq_label_to_human_readable_label(session["resolution"]),
        y_label=weather_axis_label,
        legend_location="top_right",
        show_y_floats=True,
        tools=tools,
    )
Пример #5
0
def mock_flex_figure(x_range, x_index, fig_width) -> Figure:
    df_actions = pd.DataFrame(index=x_index, columns=["event_value"]).fillna(0)
    next_action_hour4 = get_flex_action_hour(4)
    if next_action_hour4 in df_actions.index:
        if current_user.is_authenticated:
            if current_user.has_role(ADMIN_ROLE):
                df_actions.loc[next_action_hour4] = -2.4  # mock two actions
            elif "wind" in current_user.email:
                df_actions.loc[next_action_hour4] = -1.3  # mock one action
            elif "charging" in current_user.email:
                df_actions.loc[next_action_hour4] = -1.1  # mock one action

    next_action_hour2 = get_flex_action_hour(2)
    if next_action_hour2 in df_actions.index:
        if next_action_hour2 < next_action_hour4 and (
                current_user.is_authenticated and
            (current_user.has_role(ADMIN_ROLE) or "wind" in current_user.email
             or "charging" in current_user.email)):
            # mock the shift "payback" (actually occurs earlier in our mock example)
            df_actions.loc[next_action_hour2] = 1.1

    next_action_hour9 = get_flex_action_hour(9)
    if next_action_hour9 in df_actions.index:
        # mock some other ordered actions that are not in an opportunity hour any more
        df_actions.loc[next_action_hour9] = 3.5

    fig_actions = plotting.create_graph(
        df_actions,
        unit="MW",
        title="Ordered balancing actions",
        x_range=x_range,
        y_label="Power (in MW)",
    )
    fig_actions.plot_height = 150
    fig_actions.plot_width = fig_width
    fig_actions.xaxis.visible = False

    if current_user.is_authenticated and (current_user.has_role(ADMIN_ROLE)
                                          or "wind" in current_user.email
                                          or "charging" in current_user.email):
        plotting.highlight(
            fig_actions,
            next_action_hour4,
            next_action_hour4 + timedelta(hours=1),
            redirect_to="/control",
        )
    return fig_actions
Пример #6
0
def make_prices_figure(
    data: pd.DataFrame,
    forecast_data: Union[None, pd.DataFrame],
    shared_x_range: Range1d,
    selected_market: Market,
    tools: List[str] = None,
) -> Figure:
    """Make a bokeh figure for price data"""
    return create_graph(
        data,
        unit=selected_market.unit,
        legend_location="top_right",
        forecasts=forecast_data,
        title=f"Prices for {selected_market.display_name}",
        x_range=shared_x_range,
        x_label="Time (resolution of %s)" %
        time_utils.freq_label_to_human_readable_label(session["resolution"]),
        y_label="Price (in %s)" % selected_market.unit,
        show_y_floats=True,
        tools=tools,
    )
Пример #7
0
def make_prices_figure(
    data: pd.DataFrame,
    forecast_data: Union[None, pd.DataFrame],
    shared_x_range: Range1d,
    selected_market: Market,
    tools: List[str] = None,
    sizing_mode="scale_width",
) -> Figure:
    """Make a bokeh figure for price data"""
    return create_graph(
        data,
        unit=selected_market.unit,
        legend_location="top_right",
        forecasts=forecast_data,
        title=f"Prices for {selected_market.display_name}",
        x_range=shared_x_range,
        x_label="Time (resolution of %s)" %
        determine_resolution(data, forecast_data),
        y_label="Price (in %s)" % selected_market.unit,
        show_y_floats=True,
        tools=tools,
        sizing_mode=sizing_mode,
    )
Пример #8
0
def make_power_figure(
    resource_display_name: str,
    data: pd.DataFrame,
    forecast_data: Optional[pd.DataFrame],
    schedule_data: Optional[pd.DataFrame],
    show_consumption_as_positive: bool,
    shared_x_range: Range1d,
    tools: List[str] = None,
    sizing_mode="scale_width",
) -> Figure:
    """Make a bokeh figure for power consumption or generation"""
    if show_consumption_as_positive:
        title = "Electricity consumption of %s" % resource_display_name
    else:
        title = "Electricity production from %s" % resource_display_name
    if data.empty:
        title = title.replace("Electricity", "Prognosed")

    return create_graph(
        data,
        unit="MW",
        legend_location="top_right",
        legend_labels=("Actual", "Forecast", None) if schedule_data is None
        or schedule_data["event_value"].isnull().all() else
        ("Actual", "Forecast", "Schedule"),
        forecasts=forecast_data,
        schedules=schedule_data,
        title=title,
        x_range=shared_x_range,
        x_label="Time (resolution of %s)" %
        determine_resolution(data, forecast_data, schedule_data),
        y_label="Power (in MW)",
        show_y_floats=True,
        tools=tools,
        sizing_mode=sizing_mode,
    )
Пример #9
0
def portfolio_view():  # noqa: C901
    """Portfolio view.
    By default, this page shows live results (production, consumption and market data) from the user's portfolio.
    Time windows for which the platform has identified upcoming balancing opportunities are highlighted.
    The page can also be used to navigate historical results.
    """

    set_time_range_for_session()
    start = session.get("start_time")
    end = session.get("end_time")
    resolution = session.get("resolution")

    # Get plot perspective
    perspectives = ["production", "consumption"]
    default_stack_side = "production"  # todo: move to user config setting
    show_stacked = request.values.get("show_stacked", default_stack_side)
    perspectives.remove(show_stacked)
    show_summed: str = perspectives[0]
    plot_label = f"Stacked {show_stacked} vs aggregated {show_summed}"

    # Get structure and data
    assets: List[Asset] = get_assets(
        order_by_asset_attribute="display_name", order_direction="asc"
    )
    represented_asset_types, markets, resource_dict = get_structure(assets)
    for resource_name, resource in resource_dict.items():
        resource.load_sensor_data(
            [Power, Price],
            start=start,
            end=end,
            resolution=resolution,
            exclude_source_types=["scheduling script"],
        )  # The resource caches the results
    (
        supply_resources_df_dict,
        demand_resources_df_dict,
        production_per_asset_type,
        consumption_per_asset_type,
        production_per_asset,
        consumption_per_asset,
    ) = get_power_data(resource_dict)
    price_bdf_dict, average_price_dict = get_price_data(resource_dict)

    # Pick a perspective for summing and for stacking
    sum_dict = (
        demand_resources_df_dict.values()
        if show_summed == "consumption"
        else supply_resources_df_dict.values()
    )
    power_sum_df = (
        pd.concat(sum_dict, axis=1).sum(axis=1).to_frame(name="event_value")
        if sum_dict
        else pd.DataFrame()
    )
    stack_dict = (
        rename_event_value_column_to_resource_name(supply_resources_df_dict).values()
        if show_summed == "consumption"
        else rename_event_value_column_to_resource_name(
            demand_resources_df_dict
        ).values()
    )
    df_stacked_data = pd.concat(stack_dict, axis=1) if stack_dict else pd.DataFrame()

    # Create summed plot
    power_sum_df = data_or_zeroes(power_sum_df, start, end, resolution)
    x_range = plotting.make_range(
        pd.date_range(start, end, freq=resolution, closed="left")
    )
    fig_profile = plotting.create_graph(
        power_sum_df,
        unit="MW",
        title=plot_label,
        x_range=x_range,
        x_label="Time (resolution of %s)"
        % time_utils.freq_label_to_human_readable_label(resolution),
        y_label="Power (in MW)",
        legend_location="top_right",
        legend_labels=(capitalize(show_summed), None, None),
        show_y_floats=True,
        non_negative_only=True,
    )
    fig_profile.plot_height = 450
    fig_profile.plot_width = 900

    # Create stacked plot
    df_stacked_data = data_or_zeroes(df_stacked_data, start, end, resolution)
    df_stacked_areas = stack_df(df_stacked_data)

    num_areas = df_stacked_areas.shape[1]
    if num_areas <= 2:
        colors = ["#99d594", "#dddd9d"]
    else:
        colors = palettes.brewer["Spectral"][num_areas]

    df_stacked_data = time_utils.tz_index_naively(df_stacked_data)
    x_points = np.hstack((df_stacked_data.index[::-1], df_stacked_data.index))

    fig_profile.grid.minor_grid_line_color = "#eeeeee"

    for a, area in enumerate(df_stacked_areas):
        fig_profile.patch(
            x_points,
            df_stacked_areas[area].values,
            color=colors[a],
            alpha=0.8,
            line_color=None,
            legend=df_stacked_data.columns[a],
            level="underlay",
        )

    # Flexibility numbers are mocked for now
    curtailment_per_asset = {a.name: 0 for a in assets}
    shifting_per_asset = {a.name: 0 for a in assets}
    profit_loss_flexibility_per_asset = {a.name: 0 for a in assets}
    curtailment_per_asset_type = {k: 0 for k in represented_asset_types.keys()}
    shifting_per_asset_type = {k: 0 for k in represented_asset_types.keys()}
    profit_loss_flexibility_per_asset_type = {
        k: 0 for k in represented_asset_types.keys()
    }
    shifting_per_asset["48_r"] = 1.1
    profit_loss_flexibility_per_asset["48_r"] = 76000
    shifting_per_asset_type["one-way EVSE"] = shifting_per_asset["48_r"]
    profit_loss_flexibility_per_asset_type[
        "one-way EVSE"
    ] = profit_loss_flexibility_per_asset["48_r"]
    curtailment_per_asset["hw-onshore"] = 1.3
    profit_loss_flexibility_per_asset["hw-onshore"] = 84000
    curtailment_per_asset_type["wind turbines"] = curtailment_per_asset["hw-onshore"]
    profit_loss_flexibility_per_asset_type[
        "wind turbines"
    ] = profit_loss_flexibility_per_asset["hw-onshore"]

    # Add referral to mocked control action
    this_hour = time_utils.get_most_recent_hour()
    next4am = [
        dt
        for dt in [this_hour + timedelta(hours=i) for i in range(1, 25)]
        if dt.hour == 4
    ][0]

    # TODO: show when user has (possible) actions in order book for a time slot
    if current_user.is_authenticated and (
        current_user.has_role("admin")
        or "wind" in current_user.email
        or "charging" in current_user.email
    ):
        plotting.highlight(
            fig_profile, next4am, next4am + timedelta(hours=1), redirect_to="/control"
        )

    # actions
    df_actions = pd.DataFrame(index=power_sum_df.index, columns=["event_value"]).fillna(
        0
    )
    if next4am in df_actions.index:
        if current_user.is_authenticated:
            if current_user.has_role("admin"):
                df_actions.loc[next4am] = -2.4  # mock two actions
            elif "wind" in current_user.email:
                df_actions.loc[next4am] = -1.3  # mock one action
            elif "charging" in current_user.email:
                df_actions.loc[next4am] = -1.1  # mock one action
    next2am = [
        dt
        for dt in [this_hour + timedelta(hours=i) for i in range(1, 25)]
        if dt.hour == 2
    ][0]
    if next2am in df_actions.index:
        if next2am < next4am and (
            current_user.is_authenticated
            and (
                current_user.has_role("admin")
                or "wind" in current_user.email
                or "charging" in current_user.email
            )
        ):
            # mock the shift "payback" (actually occurs earlier in our mock example)
            df_actions.loc[next2am] = 1.1
    next9am = [
        dt
        for dt in [this_hour + timedelta(hours=i) for i in range(1, 25)]
        if dt.hour == 9
    ][0]
    if next9am in df_actions.index:
        # mock some other ordered actions that are not in an opportunity hour anymore
        df_actions.loc[next9am] = 3.5

    fig_actions = plotting.create_graph(
        df_actions,
        unit="MW",
        title="Ordered balancing actions",
        x_range=x_range,
        y_label="Power (in MW)",
    )
    fig_actions.plot_height = 150
    fig_actions.plot_width = fig_profile.plot_width
    fig_actions.xaxis.visible = False

    if current_user.is_authenticated and (
        current_user.has_role("admin")
        or "wind" in current_user.email
        or "charging" in current_user.email
    ):
        plotting.highlight(
            fig_actions, next4am, next4am + timedelta(hours=1), redirect_to="/control"
        )

    portfolio_plots_script, portfolio_plots_divs = components(
        (fig_profile, fig_actions)
    )
    next24hours = [
        (time_utils.get_most_recent_hour() + timedelta(hours=i)).strftime("%I:00 %p")
        for i in range(1, 26)
    ]

    return render_flexmeasures_template(
        "views/portfolio.html",
        assets=assets,
        average_prices=average_price_dict,
        asset_types=represented_asset_types,
        markets=markets,
        production_per_asset=production_per_asset,
        consumption_per_asset=consumption_per_asset,
        curtailment_per_asset=curtailment_per_asset,
        shifting_per_asset=shifting_per_asset,
        profit_loss_flexibility_per_asset=profit_loss_flexibility_per_asset,
        production_per_asset_type=production_per_asset_type,
        consumption_per_asset_type=consumption_per_asset_type,
        curtailment_per_asset_type=curtailment_per_asset_type,
        shifting_per_asset_type=shifting_per_asset_type,
        profit_loss_flexibility_per_asset_type=profit_loss_flexibility_per_asset_type,
        sum_production=sum(production_per_asset_type.values()),
        sum_consumption=sum(consumption_per_asset_type.values()),
        sum_curtailment=sum(curtailment_per_asset_type.values()),
        sum_shifting=sum(shifting_per_asset_type.values()),
        sum_profit_loss_flexibility=sum(
            profit_loss_flexibility_per_asset_type.values()
        ),
        portfolio_plots_script=portfolio_plots_script,
        portfolio_plots_divs=portfolio_plots_divs,
        next24hours=next24hours,
        alt_stacking=show_summed,
    )
Пример #10
0
def portfolio_view():  # noqa: C901
    """Portfolio view.
    By default, this page shows live results (production, consumption and market data) from the user's portfolio.
    Time windows for which the platform has identified upcoming balancing opportunities are highlighted.
    The page can also be used to navigate historical results.
    """

    set_time_range_for_session()
    start = session.get("start_time")
    end = session.get("end_time")
    resolution = session.get("resolution")

    # Get plot perspective
    perspectives = ["production", "consumption"]
    default_stack_side = "production"  # todo: move to user config setting
    show_stacked = request.values.get("show_stacked", default_stack_side)
    perspectives.remove(show_stacked)
    show_summed: str = perspectives[0]
    plot_label = f"Stacked {show_stacked} vs aggregated {show_summed}"

    # Get structure and data
    assets: List[Asset] = get_assets(order_by_asset_attribute="display_name",
                                     order_direction="asc")
    represented_asset_types, markets, resource_dict = get_structure(assets)
    for resource_name, resource in resource_dict.items():
        resource.load_sensor_data(
            [Power, Price],
            start=start,
            end=end,
            resolution=resolution,
            exclude_source_types=["scheduling script"],
        )  # The resource caches the results
    (
        supply_resources_df_dict,
        demand_resources_df_dict,
        production_per_asset_type,
        consumption_per_asset_type,
        production_per_asset,
        consumption_per_asset,
    ) = get_power_data(resource_dict)
    price_bdf_dict, average_price_dict = get_price_data(resource_dict)

    # Pick a perspective for summing and for stacking
    sum_dict = (demand_resources_df_dict.values() if show_summed
                == "consumption" else supply_resources_df_dict.values())
    power_sum_df = (pd.concat(sum_dict, axis=1).sum(axis=1).to_frame(
        name="event_value") if sum_dict else pd.DataFrame())

    # Create summed plot
    power_sum_df = data_or_zeroes(power_sum_df, start, end, resolution)
    x_range = plotting.make_range(
        pd.date_range(start, end, freq=resolution, closed="left"))
    fig_profile = plotting.create_graph(
        power_sum_df,
        unit="MW",
        title=plot_label,
        x_range=x_range,
        x_label="Time (resolution of %s)" %
        time_utils.freq_label_to_human_readable_label(resolution),
        y_label="Power (in MW)",
        legend_location="top_right",
        legend_labels=(capitalize(show_summed), None, None),
        show_y_floats=True,
        non_negative_only=True,
    )
    fig_profile.plot_height = 450
    fig_profile.plot_width = 900

    # Create stacked plot
    stack_dict = (rename_event_value_column_to_resource_name(
        supply_resources_df_dict).values() if show_summed == "consumption" else
                  rename_event_value_column_to_resource_name(
                      demand_resources_df_dict).values())
    df_stacked_data = pd.concat(stack_dict,
                                axis=1) if stack_dict else pd.DataFrame()
    df_stacked_data = data_or_zeroes(df_stacked_data, start, end, resolution)
    df_stacked_areas = stack_df(df_stacked_data)

    num_areas = df_stacked_areas.shape[1]
    if num_areas <= 2:
        colors = ["#99d594", "#dddd9d"]
    else:
        colors = palettes.brewer["Spectral"][num_areas]

    df_stacked_data = time_utils.tz_index_naively(df_stacked_data)
    x_points = np.hstack((df_stacked_data.index[::-1], df_stacked_data.index))

    fig_profile.grid.minor_grid_line_color = "#eeeeee"

    for a, area in enumerate(df_stacked_areas):
        fig_profile.patch(
            x_points,
            df_stacked_areas[area].values,
            color=colors[a],
            alpha=0.8,
            line_color=None,
            legend=df_stacked_data.columns[a],
            level="underlay",
        )

    portfolio_plots_script, portfolio_plots_divs = components(fig_profile)

    # Flexibility numbers and a mocked control action are mocked for demo mode at the moment
    flex_info = {}
    if current_app.config.get("FLEXMEASURES_MODE") == "demo":
        flex_info = mock_flex_info(assets, represented_asset_types)
        fig_actions = mock_flex_figure(x_range, power_sum_df.index,
                                       fig_profile.plot_width)
        mock_flex_action_in_main_figure(fig_profile)
        portfolio_plots_script, portfolio_plots_divs = components(
            (fig_profile, fig_actions))

    return render_flexmeasures_template(
        "views/portfolio.html",
        assets=assets,
        average_prices=average_price_dict,
        asset_types=represented_asset_types,
        markets=markets,
        production_per_asset=production_per_asset,
        consumption_per_asset=consumption_per_asset,
        production_per_asset_type=production_per_asset_type,
        consumption_per_asset_type=consumption_per_asset_type,
        sum_production=sum(production_per_asset_type.values()),
        sum_consumption=sum(consumption_per_asset_type.values()),
        flex_info=flex_info,
        portfolio_plots_script=portfolio_plots_script,
        portfolio_plots_divs=portfolio_plots_divs,
        alt_stacking=show_summed,
        fm_mode=current_app.config.get("FLEXMEASURES_MODE"),
    )