Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make flexmeasures add schedule a subgroup #557

Merged
merged 2 commits into from
Dec 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion documentation/cli/change_log.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ since v0.12.0 | November XX, 2022
* Fix ``flexmeasures db-ops dump`` and ``flexmeasures db-ops restore`` incorrectly reporting a success when `pg_dump` and `pg_restore` are not installed.
* Add ``flexmeasures monitor last-seen``.
* Rename ``flexmeasures monitor tasks`` to ``flexmeasures monitor last-run``.
* Rename ``flexmeasures add schedule`` to ``flexmeasures add schedule-for-storage`` (in expectation of more scheduling commands, based on in-built flex models).
* Rename ``flexmeasures add schedule`` to ``flexmeasures add schedule for-storage`` (in expectation of more scheduling commands, based on in-built flex models).

since v0.11.0 | August 28, 2022
==============================
Expand Down
2 changes: 1 addition & 1 deletion documentation/cli/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ of which some are referred to in this documentation.
``flexmeasures add sensor`` Add a new sensor.
``flexmeasures add beliefs`` Load beliefs from file.
``flexmeasures add forecasts`` Create forecasts.
``flexmeasures add schedule-for-storage`` Create a charging schedule for a storage asset.
``flexmeasures add schedule for-storage`` Create a charging schedule for a storage asset.
``flexmeasures add holidays`` Add holiday annotations to accounts and/or assets.
``flexmeasures add annotation`` Add annotation to accounts, assets and/or sensors.
``flexmeasures add toy-account`` Create a toy account, for tutorials and trying things.
Expand Down
2 changes: 1 addition & 1 deletion documentation/dev/docker-compose.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ Next, we put a scheduling job in the worker's queue. This only works because we

.. code-block:: console

flexmeasures add schedule-for-storage --sensor-id 2 --optimization-context-id 3 \
flexmeasures add schedule for-storage --sensor-id 2 --optimization-context-id 3 \
--start ${TOMORROW}T07:00+01:00 --duration PT12H --soc-at-start 50% \
--roundtrip-efficiency 90% --as-job

Expand Down
2 changes: 1 addition & 1 deletion documentation/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ A tiny, but complete example: Let's install FlexMeasures from scratch. Then, usi
$ flexmeasures db upgrade # create tables
$ flexmeasures add toy-account --kind battery # setup account & a user, a battery (Id 2) and a market (Id 3)
$ flexmeasures add beliefs --sensor-id 3 --source toy-user prices-tomorrow.csv --timezone utc # load prices, also possible per API
$ flexmeasures add schedule-for-storage --sensor-id 2 --consumption-price-sensor 3 \
$ flexmeasures add schedule for-storage --sensor-id 2 --consumption-price-sensor 3 \
--start ${TOMORROW}T07:00+01:00 --duration PT12H \
--soc-at-start 50% --roundtrip-efficiency 90% # this is also possible per API
$ flexmeasures show beliefs --sensor-id 2 --start ${TOMORROW}T07:00:00+01:00 --duration PT12H # also visible per UI, of course
Expand Down
4 changes: 2 additions & 2 deletions documentation/tut/forecasting_scheduling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,13 @@ A second way to add scheduling jobs is via the CLI, so this is available for peo

.. code-block:: console

flexmeasures add schedule-for-storage --sensor-id 2 --optimization-context-id 3 \
flexmeasures add schedule for-storage --sensor-id 2 --optimization-context-id 3 \
--start 2022-07-05T07:00+01:00 --duration PT12H \
--soc-at-start 50% --roundtrip-efficiency 90% --as-job

Here, the ``--as-job`` parameter makes the difference for queueing ― without it, the schedule is computed right away.

Run ``flexmeasures add schedule-for-storage --help`` for more information.
Run ``flexmeasures add schedule for-storage --help`` for more information.


.. _getting_prognoses:
Expand Down
6 changes: 3 additions & 3 deletions documentation/tut/toy-example-from-scratch.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Below are the ``flexmeasures`` CLI commands we'll run, and which we'll explain s
# load prices to optimise the schedule against
$ flexmeasures add beliefs --sensor-id 3 --source toy-user prices-tomorrow.csv --timezone utc
# make the schedule
$ flexmeasures add schedule-for-storage --sensor-id 2 --consumption-price-sensor 3 \
$ flexmeasures add schedule for-storage --sensor-id 2 --consumption-price-sensor 3 \
--start ${TOMORROW}T07:00+01:00 --duration PT12H \
--soc-at-start 50% --roundtrip-efficiency 90%

Expand Down Expand Up @@ -268,7 +268,7 @@ To keep it short, we'll only ask for a 12-hour window starting at 7am. Finally,

.. code-block:: console

$ flexmeasures add schedule-for-storage --sensor-id 2 --consumption-price-sensor 3 \
$ flexmeasures add schedule for-storage --sensor-id 2 --consumption-price-sensor 3 \
--start ${TOMORROW}T07:00+01:00 --duration PT12H \
--soc-at-start 50% --roundtrip-efficiency 90%
New schedule is stored.
Expand Down Expand Up @@ -314,4 +314,4 @@ We can also look at the charging schedule in the `FlexMeasures UI <http://localh
Recall that we only asked for a 12 hour schedule here. We started our schedule *after* the high price peak (at 5am) and it also had to end *before* the second price peak fully realised (at 9pm). Our scheduler didn't have many opportunities to optimize, but it found some. For instance, it does buy at the lowest price (around 3pm) and sells it off when prices start rising again (around 6pm).


.. note:: The ``flexmeasures add schedule-for-storage`` command also accepts state-of-charge targets, so the schedule can be more sophisticated. But that is not the point of this tutorial. See ``flexmeasures add schedule-for-storage --help``.
.. note:: The ``flexmeasures add schedule for-storage`` command also accepts state-of-charge targets, so the schedule can be more sophisticated. But that is not the point of this tutorial. See ``flexmeasures add schedule for-storage --help``.
118 changes: 19 additions & 99 deletions flexmeasures/cli/data_add.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import timely_beliefs.utils as tb_utils
from workalendar.registry import registry as workalendar_registry

from flexmeasures.cli.utils import DeprecatedDefaultGroup
from flexmeasures.data import db
from flexmeasures.data.scripts.data_gen import (
add_transmission_zone_asset,
Expand Down Expand Up @@ -821,111 +822,30 @@ def create_forecasts(
)


# TODO: deprecate in v0.13
@fm_add_data.command("schedule")
# todo: repurpose `flexmeasures add schedule` (deprecated since v0.12),
# - see https://github.com/FlexMeasures/flexmeasures/pull/537#discussion_r1048680231
# - hint for repurposing to invoke custom logic instead of a default subcommand:
# @fm_add_data.group("schedule", invoke_without_command=True)
# def create_schedule():
# if ctx.invoked_subcommand:
# ...
@fm_add_data.group(
"schedule",
cls=DeprecatedDefaultGroup,
default="storage",
deprecation_message="The command 'flexmeasures add schedule' is deprecated. Please use `flexmeasures add schedule storage` instead.",
)
@click.pass_context
@with_appcontext
@click.option(
"--sensor-id",
"power_sensor",
type=SensorIdField(),
required=True,
help="Create schedule for this sensor. Follow up with the sensor's ID.",
)
@click.option(
"--consumption-price-sensor",
"consumption_price_sensor",
type=SensorIdField(),
required=False,
help="Optimize consumption against this sensor. The sensor typically records an electricity price (e.g. in EUR/kWh), but this field can also be used to optimize against some emission intensity factor (e.g. in kg CO₂ eq./kWh). Follow up with the sensor's ID.",
)
@click.option(
"--production-price-sensor",
"production_price_sensor",
type=SensorIdField(),
required=False,
help="Optimize production against this sensor. Defaults to the consumption price sensor. The sensor typically records an electricity price (e.g. in EUR/kWh), but this field can also be used to optimize against some emission intensity factor (e.g. in kg CO₂ eq./kWh). Follow up with the sensor's ID.",
)
@click.option(
"--optimization-context-id",
"optimization_context_sensor",
type=SensorIdField(),
required=False,
help="To be deprecated. Use consumption-price-sensor instead.",
)
@click.option(
"--start",
"start",
type=AwareDateTimeField(format="iso"),
required=True,
help="Schedule starts at this datetime. Follow up with a timezone-aware datetime in ISO 6801 format.",
)
@click.option(
"--duration",
"duration",
type=DurationField(),
required=True,
help="Duration of schedule, after --start. Follow up with a duration in ISO 6801 format, e.g. PT1H (1 hour) or PT45M (45 minutes).",
)
@click.option(
"--soc-at-start",
"soc_at_start",
type=QuantityField("%", validate=validate.Range(min=0, max=1)),
required=True,
help="State of charge (e.g 32.8%, or 0.328) at the start of the schedule.",
)
@click.option(
"--soc-target",
"soc_target_strings",
type=click.Tuple(
types=[QuantityField("%", validate=validate.Range(min=0, max=1)), str]
),
multiple=True,
required=False,
help="Target state of charge (e.g 100%, or 1) at some datetime. Follow up with a float value and a timezone-aware datetime in ISO 6081 format."
" This argument can be given multiple times."
" For example: --soc-target 100% 2022-02-23T13:40:52+00:00",
)
@click.option(
"--soc-min",
"soc_min",
type=QuantityField("%", validate=validate.Range(min=0, max=1)),
required=False,
help="Minimum state of charge (e.g 20%, or 0.2) for the schedule.",
)
@click.option(
"--soc-max",
"soc_max",
type=QuantityField("%", validate=validate.Range(min=0, max=1)),
required=False,
help="Maximum state of charge (e.g 80%, or 0.8) for the schedule.",
)
@click.option(
"--roundtrip-efficiency",
"roundtrip_efficiency",
type=QuantityField("%", validate=validate.Range(min=0, max=1)),
required=False,
default=1,
help="Round-trip efficiency (e.g. 85% or 0.85) to use for the schedule. Defaults to 100% (no losses).",
)
@click.option(
"--as-job",
is_flag=True,
help="Whether to queue a scheduling job instead of computing directly. "
"To process the job, run a worker (on any computer, but configured to the same databases) to process the 'scheduling' queue. Defaults to False.",
)
def create_schedule(ctx, **kwargs):
"""[deprecated] Create a new schedule for a given power sensor.
def create_schedule(ctx):
"""(Deprecated) Create a new schedule for a given power sensor.

THIS COMMAND HAS BEEN RENAMED, please use `flexmeasures add storage-schedule`
THIS COMMAND HAS BEEN RENAMED TO `flexmeasures add schedule for-storage`
"""
click.echo(
"THIS COMMAND HAS BEEN RENAMED TO flexmeasures add schedule-for-storage. IT WILL BE DEPRECATED IN v0.13."
)
ctx.invoke(add_schedule_for_storage, **kwargs)
pass


@fm_add_data.command("schedule-for-storage")
@create_schedule.command("for-storage")
@with_appcontext
@click.option(
"--sensor-id",
Expand Down
48 changes: 48 additions & 0 deletions flexmeasures/cli/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import click
from click_default_group import DefaultGroup


class DeprecatedDefaultGroup(DefaultGroup):
"""Invokes a default subcommand, *and* shows a deprecation message.

Also adds the `invoked_default` boolean attribute to the context.
A group callback can use this information to figure out if it's being executed directly
(invoking the default subcommand) or because the execution flow passes onwards to a subcommand.
By default it's None, but it can be the name of the default subcommand to execute.

.. sourcecode:: python

import click
from flexmeasures.cli.utils import DeprecatedDefaultGroup

@click.group(cls=DeprecatedDefaultGroup, default="bar", deprecation_message="renamed to `foo bar`.")
def foo(ctx):
if ctx.invoked_default:
click.echo("foo")

@foo.command()
def bar():
click.echo("bar")

.. sourcecode:: console

$ flexmeasures foo
DeprecationWarning: renamed to `foo bar`.
foo
bar
$ flexmeasures foo bar
bar
"""

def __init__(self, *args, **kwargs):
self.deprecation_message = "DeprecationWarning: " + kwargs.pop(
"deprecation_message", ""
)
super().__init__(*args, **kwargs)

def get_command(self, ctx, cmd_name):
ctx.invoked_default = None
if cmd_name not in self.commands:
click.echo(click.style(self.deprecation_message, fg="red"), err=True)
ctx.invoked_default = self.default_cmd_name
return super().get_command(ctx, cmd_name)
1 change: 1 addition & 0 deletions requirements/app.in
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pytz
numpy
isodate
click
click-default-group
email_validator
rq
rq-dashboard
Expand Down
3 changes: 3 additions & 0 deletions requirements/app.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ charset-normalizer==2.1.1
click==8.1.3
# via
# -r requirements/app.in
# click-default-group
# flask
# rq
click-default-group==1.2.2
# via -r requirements/app.in
colour==0.1.5
# via -r requirements/app.in
convertdate==2.4.0
Expand Down