Skip to content

Allow targeting categories with subtypes #168

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

Draft
wants to merge 2 commits into
base: v0.x.x
Choose a base branch
from

Conversation

Marenz
Copy link
Contributor

@Marenz Marenz commented May 19, 2025

  • Update to latest dispatch-api
  • Implement extra classes for each target type

Fixes #139

Marenz added 2 commits May 19, 2025 17:49
Renames a few fields, but stays compatible otherwise.

Signed-off-by: Mathias L. Baumann <[email protected]>
..and support the latest CategoryAndType target type.

This implements extra classes for each target component case:
* List of Ids `TargetIds`
* List of categories: `TargetCategories`
* List of categories + subtypes: `TargetCategoriesAndTypes`

This was easier to do together as the addition of the new type
increased the places where it
was difficult to find out what the actual target type/ids is/are.

Signed-off-by: Mathias L. Baumann <[email protected]>
@github-actions github-actions bot added part:tests Affects the unit, integration and performance (benchmarks) tests part:tooling Affects the development tooling (CI, deployment, dependency management, etc.) part:cli Affects the command-line interface part:test-utils Affects the test utilities part:dispatcher labels May 19, 2025
@@ -86,7 +86,8 @@ def print_dispatch(dispatch: Dispatch) -> None:
# Format the target
if dispatch.target:
if len(dispatch.target) == 1:
target_str: str = str(dispatch.target[0])
(el,) = dispatch.target
target_str: str = str(el)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is because target is a frozenset now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, but why can't use use [0] for something frozen? You should be able, or is it because it is a set now?

Also what the hell does el stands for? It look it is pretty cryptic. Also if you need to clarify in the PR, maybe it is best to add a comment in the code itself for the future ourselves looking at this code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You cannot index-access a set, it has no order :)

el stands for element.. sure I'll make it more clear 😄
I was surprised as well, but this seems to be the easiest way to access the only element of a set with size 1

Copy link
Contributor

@llucax llucax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM in general, although not completely convinced about the current approach with CategoryAndType, looks a bit too low-level for the "high-level interface".

@@ -86,7 +86,8 @@ def print_dispatch(dispatch: Dispatch) -> None:
# Format the target
if dispatch.target:
if len(dispatch.target) == 1:
target_str: str = str(dispatch.target[0])
(el,) = dispatch.target
target_str: str = str(el)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, but why can't use use [0] for something frozen? You should be able, or is it because it is a set now?

Also what the hell does el stands for? It look it is pretty cryptic. Also if you need to clarify in the PR, maybe it is best to add a comment in the code itself for the future ourselves looking at this code.

Comment on lines +40 to +53
class EvChargerType(IntEnum):
"""Enum representing the type of EV charger."""

EV_CHARGER_TYPE_UNSPECIFIED = PBEvChargerType.EV_CHARGER_TYPE_UNSPECIFIED
"""Unspecified type of EV charger."""

EV_CHARGER_TYPE_AC = PBEvChargerType.EV_CHARGER_TYPE_AC
"""AC EV charger."""

EV_CHARGER_TYPE_DC = PBEvChargerType.EV_CHARGER_TYPE_DC
"""DC EV charger."""

EV_CHARGER_TYPE_HYBRID = PBEvChargerType.EV_CHARGER_TYPE_HYBRID
"""Hybrid EV charger."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have these in the microgrid API too, next step is to move them to client-common so we can share it. I will do a PR with this soon after I finish the microgrid API 0.17 PR, probably this week. But just FYI, I think we should move forward adding it where needed locally as it might take some time to agree on something we are all happy about to put in common + release. In any case, I would remove the EV_CHARGER_TYPE_ prefix here.

@@ -66,6 +76,30 @@ def generate_recurrence_rule(self) -> RecurrenceRule:
],
)

def generate_target_cat_and_type(self) -> CategoryAndType:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: This is already pretty long, I wouldn't save the 5 extra chars from egory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're worse than copilot


This is a frozen set, so it is immutable.
The target components are used to specify the components that a dispatch
should target.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe include a match example here?

@@ -99,7 +130,7 @@ def test_dispatch() -> None:
start_time=datetime(2024, 10, 10, tzinfo=timezone.utc),
end_time=datetime(2024, 10, 20, tzinfo=timezone.utc),
duration=timedelta(days=10),
target=[1, 2, 3],
target=TargetIds([1, 2, 3]),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can add a __init__() here to simply allow: TargetIds(1, 2, 3)?

@@ -120,7 +151,7 @@ def test_dispatch() -> None:
start_time=datetime(2024, 11, 10, tzinfo=timezone.utc),
end_time=datetime(2024, 11, 20, tzinfo=timezone.utc),
duration=timedelta(seconds=20),
target=[ComponentCategory.BATTERY],
target=TargetCategories([ComponentCategory.BATTERY]),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: TargetCategories(ComponentCategory.BATTERY) and for TargetCategoryAndTypes, maybe allow something like TargetCategoryAndTypes(ComponentCategory.BATTERY, InverterType.SOLAR) (as with the type, we can infer the category, seems to be pretty redundant to require both).

Comment on lines +85 to +93
@dataclass(frozen=True)
class CategoryAndType:
"""Represents a category and type of dispatch."""

category: ComponentCategory
"""The category of the dispatch."""

type: BatteryType | EvChargerType | InverterType | None
"""The type of the dispatch."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably for the API repo, but it seems we now have overlapping target options:

TargetCategories([cat1, cat2]) == TargetCategoryAndTypes([CategoryAndType(cat1), CategoryAndType(cat2)])

(figuratively speaking)

As for the high-level user interface, since as soon as we have a type, the category can be inferred, I would simplify for the end user:

# Could still be CategoryAndType, but it sounds a bit weird type is not always present
@dataclass(frozen=True)
class Group:
    target: ComponentCategory | BatteryType | EvChargerType | InverterType

    @property
    def category(self) -> ComponentCategory:
        match self.target:
            case ComponentCategory():
                return self.target
            case BattertyType():
                return ComponentCategory.BATTERY
            ...

    @property
    def type(self) -> ComponentCategory:
        match self.target:
            case ComponentCategory():
                return None
            case type:
                return type
            ...

And then add a ctor to class TargetCategoryAndTypes to allow for:

TargetCategoryAndTypes(ComponentCategory.BATTERY, InverterType.SOLAR)

to result in:

frozenset([Group(ComponentCategory.BATTERY), Group(InverterType.SOLAR)])

I'm not sure if we already touched on the redundancy between CategorySet and CategoryTypeSet in the protocol, if not maybe we should.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we already touched on the redundancy between CategorySet and CategoryTypeSet in the protocol, if not maybe we should.

We don't need to talk about those redundancies because they are deprecated and scheduled to be removed, the whole typeless category and it's field in the oneof construct will be gone once all clients are a capable of reading/writing both.

@@ -5,13 +5,23 @@

import json
from datetime import datetime, timedelta, timezone
from sre_constants import IN
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I bet this was Tab-oriented-programming + auto-import 😆

Suggested change
from sre_constants import IN

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was more desperation, but this is a draft after all :P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
part:cli Affects the command-line interface part:dispatcher part:test-utils Affects the test utilities part:tests Affects the unit, integration and performance (benchmarks) tests part:tooling Affects the development tooling (CI, deployment, dependency management, etc.)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improve how to get different types of Dispatch.target
2 participants