Skip to content

Introduce new component from eesyplan#492

Draft
Bachibouzouk wants to merge 31 commits into
mainfrom
feature/introduce-new-component
Draft

Introduce new component from eesyplan#492
Bachibouzouk wants to merge 31 commits into
mainfrom
feature/introduce-new-component

Conversation

@Bachibouzouk

@Bachibouzouk Bachibouzouk commented Jul 8, 2026

Copy link
Copy Markdown

PROTOCOL (option a or b)

  1. a) write a new class which inherits from Asset with the new needed fields
    b) add the fields within the Asset class

  2. add an asset_type for this new component with their visible fields (under static/resources/assettypes_list.csv)

  3. add the asset to the drag and drop within the projects/views.py::scenario_create_topology function. Right at the beginning there is a dict named components with asset_type as keys and the verbose name as Value

  4. provide an icon for this component in static/asset/gui, its name must be <asset_type>.svg, like "commodity.svg" for example. You then need to add a mapping "<asset_type>:<asset_type>," within $component-categories-mapping in the static/scss/abstracts/_variables.scss

    At this stage the component will be visible within the GUI and can be dragged to the energy system. However option a) doesn't provide out of the box access to the component's fields, whereas option b) does.
    To change this one need to be able to set the model attribute of the Meta class to something else than Asset (ie a decorator function which would find the new asset's Model from a dict and return a CreateAssetForm instance with modified Meta

After internal disscussion and testing option a) seems the most long term beneficial, as it will make the components much more aligned on eesyplan structure. This PR already made the structural changes necessary for:

  • export a scenario to json and load it again within the GUI
  • export a scenario to a datapackage
  • duplicate a scenario using the GUI (clicking on the icon next to the scenario in the list of scenarios on the project display page)

Here is a list of the components which need to have their own Model (inheriting Asset class)

  • all the dso component (dso, gas_dso, h2_dso, heat_dso)
  • heat_pump (for this one the separate Model COPCalculator might need to be removed and added to the heat_pump interface)
  • chp
  • chp_fixed_ratio
  • electrolyzer

There is also the storage components (hess, h2ss, bess, gess), but the migration might be a bit more complex as they had a structure with a parent asset and 3 children assets

One need to look over clean() method from AssetCreateForm in forms.py to remove code which will not be necessary there anymore. Idem for the to_datapackage() method af the Asset class It will break the dto.py classes but this is only used for MVS and will be removed in the future.

Also look into widgets attribute of the Meta class attribute of the AssetCreateForm

Need to migrate the Storage into an eesyplan storage (this might require quite some preparation?)

@Bachibouzouk
Bachibouzouk requested a review from paulapreuss July 8, 2026 13:11
Inherting from Asset might be preferable, as the class remain a child of
Asset, one can always use Asset.objects.filter(asset_type=...)
The ModelForm is instanciated with a Meta class referring to the
specific asset model if differing from Asset. Currently problems remain
at the export to datapackage and export scenarios
@Bachibouzouk
Bachibouzouk force-pushed the feature/introduce-new-component branch from b3b2e5d to fd1cf09 Compare July 13, 2026 14:02
@Bachibouzouk

Copy link
Copy Markdown
Author

Now export of the scenario produces an error but only when the scenario is reimported and the special component clicked

The new asset specific fields are successfully exported
com = Commodity(asset_type=at, scenario=scen, full_load_hours_max=30)
com.save()
print(com.to_datapackage())
pdb.set_trace()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remove before merge (linter is already flagging this anyway so shouldn't be a problem)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I would remove the whole file before merge, this is just for testing

Comment on lines +1048 to +1050
asset_type = ASSET_MAPPING.get(self.asset_type.asset_type, Asset)
existing_asset = get_object_or_404(asset_type, unique_id=self.unique_id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why do we need to do this lookup for the asset within the model class? isn't existing_asset just self?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ah, is this because Commodity is not treated as an Asset but as a separate class?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

If we use self within the Asset's methods, then self is of type Asset and I had the problem that I couldn't list the extra attributes

# 2) add an asset_type for this new component with their visible fields (under static/resources/assettypes_list.csv)


class Commodity(Asset):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I do like having the new component inherit from Asset rather than having a very bloated Asset class

Comment thread app/projects/forms.py


def get_asset_or_404(asset_type, asset_uuid):
asset_type = ASSET_MAPPING.get(asset_type, Asset)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
asset_type = ASSET_MAPPING.get(asset_type, Asset)
asset_model = ASSET_MAPPING.get(asset_type, Asset)

I would rename this to match the variable name also used in asset_form_factory

Comment thread app/projects/forms.py
widget=forms.HiddenInput(), required=False, label=""
)

if self.asset_type_name == "heat_pump":

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ideally if we split the assets into their own models it would be good to also handle these special cases within the asset models. Or if the parameters have different names anyway depending on the asset (coming from the eesyplan naming), could also be handled through the general widget definition.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

We would not need to handle this as special cases because here we are overloading "efficiency" and using it as many different things. If we simply inheritate from Asset, then we write cop as attribute of the heat pump and do not need this whole if statement block :)

Comment thread app/projects/forms.py
return _AssetCreateForm(asset_type=asset_type, **kwargs)


class AssetCreateForm(OpenPlanModelForm):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Delete if keeping the form factory

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is just work in progress because of the nightmare StorageAssetForm which inherit from AssetForm. Hopefully we can get rid of the complicated StorageAsset with eesyplan :)

hess,energy_storage,Heat,storage,[name],kW (therm),"{'input_1':['bus_in_heat','Heat'],'output_1': ['bus_out_heat','Heat']}"
chp,energy_conversion,Electricity,extractionTurbineCHP,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_var,opex_fix,lifetime,optimize_cap,maximum_capacity,efficiency_multiple,efficiency,thermal_loss_rate]",kW,"{'input_1':['bus_in_fuel','Gas'],'output_1': ['bus_out_heat','Heat'], 'output_2': ['bus_out_electricity','Electricity']}"
chp_fixed_ratio,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_var,opex_fix,lifetime,optimize_cap,maximum_capacity,efficiency_multiple,efficiency]",kW,"{'input_1':['bus_in_fuel','Gas'],'output_1': ['bus_out_heat','Heat'], 'output_2': ['bus_out_electricity','Electricity']}"
commodity,energy_production,Electricity,source,"[name,installed_capacity,capex_var,commodity_type,full_load_hours_max,full_load_hours_max_asset]",kW,"{'output_1': ['bus_out_electricity','Electricity']}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

How do we want to handle the commodity bus? Maybe we can just use the fuel bus for now?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is why I started to implement the commodity. A quick solution is to do the same as with the busses in OpenPlan: we get 4 busses to choose from and we could get 4 commodities to choose from.

And not always the default Asset class
Only the asset count of a given asset_type is relevant here
At those occasion it might be needed to make sure the classes inheriting
from Asset appear in the listing explicitely and not simply as Asset
instances.
@Bachibouzouk
Bachibouzouk force-pushed the feature/introduce-new-component branch from d3f9747 to 3b207b1 Compare July 22, 2026 10:50
Bachibouzouk and others added 14 commits July 22, 2026 12:51
This is useful to see that a migrated component still produces the same
dto which is sent to MVS
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Scenario JSON export dropped subclass-only fields and loading recreated
subclassed assets as plain Asset rows, breaking duplicate/load for
commodity and upcoming eesyplan components.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This should also be done for conversion_factor_to_heat @claude
export(): my earlier dispatch fix (b388aea) was based on a wrong
premise, this code already resolved the asset subclass correctly.
Restore the TODO pointing at the real cleanup task, drop the
duplicate ASSET_MAPPING import.

CHP: add get_custom_form_fields(), a per-model hook the form factory
can use to install custom form fields (e.g. DualNumberField) instead
of hardcoding asset-type branches. Also make conversion_factor_to_heat
a TextField like conversion_factor_to_electricity, since both need to
accept a timeseries, not just a scalar.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the hardcoded chp DualNumberField branch with a generic hook
any migrated component's model can implement. Also guards the
conversion-factor sum check against timeseries values, since
DualNumberField.clean() can return a list, not just a float.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0031's _to_float collapsed any non-constant efficiency/efficiency_multiple
list to null, silently dropping real timeseries data. Now that both
target fields are TextField, a straight copy from the base Asset
fields is enough and keeps timeseries intact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
conversion_factor_to_electricity/heat are now DualNumberField-backed,
so their POST keys need the _scalar suffix; drop the now-dead old
efficiency/efficiency_multiple/thermal_loss_rate keys from the test
fixture data.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
We're keeping MVS export working for chp until the full split from
MVS, so CHP.save() mirrors conversion_factor_to_electricity/heat/beta
into efficiency/efficiency_multiple/thermal_loss_rate. dtos.py reads
those directly off the base Asset and needed no changes itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants