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, )
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, )
def determine_resolution_for(df: pd.DataFrame) -> str: if hasattr(df, "event_resolution"): # BeliefsDataFrame freq_str = time_utils.timedelta_to_pandas_freq_str( df.event_resolution) elif hasattr(df.index, "freqstr") and df.index.freqstr is not None: freq_str = data.index.freqstr elif hasattr(df.index, "inferred_freq") and df.index.inferred_freq is not None: freq_str = df.index.inferred_freq elif "resolution" in session: freq_str = session["resolution"] else: return "?" return time_utils.freq_label_to_human_readable_label(freq_str)
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, )
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, )
def render_flexmeasures_template(html_filename: str, **variables): """Render template and add all expected template variables, plus the ones given as **variables.""" variables["documentation_exists"] = False if os.path.exists( "%s/static/documentation/html/index.html" % flexmeasures_ui.root_path ): variables["documentation_exists"] = True variables["show_queues"] = False if current_user.is_authenticated: if ( current_user.has_role(ADMIN_ROLE) or current_app.config.get("FLEXMEASURES_MODE", "") == "demo" ): variables["show_queues"] = True variables["start_time"] = time_utils.get_default_start_time() if "start_time" in session: variables["start_time"] = session["start_time"] variables["end_time"] = time_utils.get_default_end_time() if "end_time" in session: variables["end_time"] = session["end_time"] variables["page"] = html_filename.split("/")[-1].replace(".html", "") if "show_datepicker" not in variables: variables["show_datepicker"] = variables["page"] in ("analytics", "portfolio") variables["contains_plots"] = False if any([n.endswith(("plots_div", "plots_divs")) for n in variables.keys()]): variables["contains_plots"] = True variables["bokeh_css_resources"] = CDN.render_css() variables["bokeh_js_resources"] = CDN.render_js() variables["resolution"] = session.get("resolution", "") variables["resolution_human"] = time_utils.freq_label_to_human_readable_label( session.get("resolution", "") ) variables["horizon_human"] = time_utils.freq_label_to_human_readable_label( session.get("forecast_horizon", "") ) variables["flexmeasures_version"] = flexmeasures_version ( variables["git_version"], variables["git_commits_since"], variables["git_hash"], ) = get_git_description() app_start_time = current_app.config.get("START_TIME") variables["app_running_since"] = time_utils.naturalized_datetime_str(app_start_time) variables["loaded_plugins"] = ", ".join( f"{p_name} (v{p_version})" for p_name, p_version in current_app.config.get("LOADED_PLUGINS", {}).items() ) variables["user_is_logged_in"] = current_user.is_authenticated variables[ "user_is_admin" ] = current_user.is_authenticated and current_user.has_role(ADMIN_ROLE) variables[ "user_is_anonymous" ] = current_user.is_authenticated and current_user.has_role("anonymous") variables["user_email"] = current_user.is_authenticated and current_user.email or "" variables["user_name"] = ( current_user.is_authenticated and current_user.username or "" ) variables["js_versions"] = current_app.config.get("FLEXMEASURES_JS_VERSIONS") variables["chart_options"] = json.dumps(chart_options) variables["menu_logo"] = current_app.config.get("FLEXMEASURES_MENU_LOGO_PATH") variables["extra_css"] = current_app.config.get("FLEXMEASURES_EXTRA_CSS_PATH") return render_template(html_filename, **variables)
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, )
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"), )