frequenz-floss / frequenz-dispatch-python

High-level interface to dispatch API
https://frequenz-floss.github.io/frequenz-dispatch-python/
MIT License
0 stars 3 forks source link

Rewrite interface #28

Open Marenz opened 5 months ago

Marenz commented 5 months ago

What's needed?

See https://github.com/frequenz-floss/frequenz-dispatch-python/pull/22#discussion_r1584494188 for details

Proposed solution

No response

Use cases

No response

Alternatives and workarounds

No response

Additional context

No response

llucax commented 2 months ago

Some other ideas, after a long discussion.

If we make the running_status_change channel send events mapped 1-1 with a singular dispatch object, we can add some utility functions to merge dispatches to cope with different actor's needs.


For example, for the power setting actor, since it works with component pools, it would make sense to merge dispatches with the same selector, which will map with one component pool.

In this case:

  1. dispatch_id=1, type=set_power, selector=component_id:[1], start=1pm, duration=2h, power_w=1000
  2. dispatch_id=2, type=set_power, selector=component_id:[1], start=2pm duration=2h, power_w=1000
  3. dispatch_id=3, type=set_power, selector=component_id:[1,2], start=2pm duration=1h, power_w=2000
  4. dispatch_id=4, type=set_power, selector=component_id:[1], start=3pm duration=2h, power_w=2000

We would normally receive these events:

But it would make more sense to receive this (dispatch_id info is lost):

The code could look like this:

async for dispatch_event in merge_same_selector(dispatcher.running_status_change.new_receiver()):
    match dispatch_event:
        Start(dispatch):
            actor = PowerSettingActor(dispatch)
            actor.start()
            self._actors[dispatch.selector] = actor
        Stop(dispatch):
            if actor := self._actors.get(dispatch.selector):
                await actor.stop()
        Update(dispatch):
            if actor := self._actors.get(dispatch.selector):
                await actor.reconfigure(dispatch)

Where merge_same_selector() does the merging.


Another example, for FCR, we want to override any overlapping dispatches:

  1. dispatch_id=1, type=set_power, selector=component_id:[1], start=1pm, duration=2h, max_power_w=1000
  2. dispatch_id=2, type=set_power, selector=component_id:[1,2], start=2pm duration=2h, max_power_w=1000
  3. dispatch_id=2, type=set_power, selector=component_id:[1], start=3pm duration=2h, max_power_w=2000

We would normally receive these events:

But it would make more sense to receive this (dispatch_id info is lost):

The code will basically look the same but applying a different filter:

async for dispatch_event in merg_overlapping(dispatcher.running_status_change.new_receiver()):
    match dispatch_event:
        ...
llucax commented 2 months ago

To be able to do this, maybe we need to update the Dispatch.dispatch_id to be something like int | Merged (where Merged is a sentinel class).

Marenz commented 3 weeks ago

Instead of having int | Merged, the merger functions could return something like

MergedEvent:
    event: DispatchEvent # start/stop/config
    dispatches: list[Dispatch] # list of involved dispatches

after all, the event part is what one usually cares about?

llucax commented 3 weeks ago

I think you care about the dispatch contents, at least the payload. If you get a MergedEvent(Config, [dispatch1, dispatch2, dispatch3]) how do you know which dispatch to use to reconfigure the actor?

Marenz commented 3 weeks ago

I suppose they could be sorted by latest update_time and there could be a

MergedEvent:
    event: DispatchEvent # start/stop/config
    dispatches: list[Dispatch] # list of involved dispatches, ordered by modification  time

    @property
    def dispatch() -> Dispatch:
        """Return the most recently modified dispatch"""
        return self.dispatches[0]
llucax commented 3 weeks ago

Why latest update time? If you have one dispatch id=2 from 10 to 11, and dispatch id=3 from 11 to 12, and it is 10:30 and someone updates dispatch id=3, then the returned dispatch should be id=2 even when the last updated dispatch is id=3, right? I think you would need to search which dispatch is currently active based on the current time, no?

Marenz commented 3 weeks ago

i'd say there would be no event at all in your example because it wouldn't change the current running state? Only at 11 it would trigger an event containing only id 3?

llucax commented 3 weeks ago

Yeah, if the payload changed, a "reconfigure"/"update" event should be sent so the actor can act on it.

llucax commented 3 weeks ago

Only at 11 it would trigger an event containing only id 3?

Yeah, that's what I was thinking of. To put more context to the example, lets say disaptch id=2 says charge 5kw and id=3 says charge 1kw. The actor should switch from 5kw to 1 kw at 11, right?