Cookbook: Add a New Element Attribute¶
This cookbook shows how to add a new attribute to an element class in
ZEN-creator.
It applies to these element types:
ConversionTechnologyCarrierStorageTechnologyTransportTechnologyRetrofittingTechnologyTechnology
The implementation pattern is the same across all of them.
When to Edit Which Class¶
Choose the class where the attribute is conceptually owned:
Add to
Technologyif all technologies should have it.Add to
ConversionTechnologyif only conversion technologies need it.Add to
StorageTechnologyif only storage technologies need it.Add to
TransportTechnologyif only transport technologies need it.Add to
RetrofittingTechnologyif only retrofitting technologies need it.Add to
Carrierif it is a carrier-level property.
Rule of thumb: put shared attributes as high in the inheritance tree as possible, but not higher.
Core Recipe (6 Steps)¶
Step 1: Register the attribute name in _subclass_attribute_names¶
In the target class __init__, append the new key to
self._subclass_attribute_names and ensure it is added to
self._attribute_names.
Why this matters:
The attribute appears in the element’s
attributesmapping.It is included in serialization/writing logic that iterates over
_attribute_names.
Step 2: Initialize an Attribute object in default setup¶
In the class-specific default initializer (for example
set_default_values_conversion_technology), create the attribute with
Attribute(...).
Typical fields:
name: key written to attributes filesdefault_value: default scalar/listunit: unit string if relevantelement=self
Step 3: Add property getter and setter¶
Expose the attribute via a typed property:
@propertygetter returningAttributeSetter validating with
self._validate_attribute(value)
This preserves consistent typing and validation behavior.
Step 4: Decide if you need _set_<attribute>() hooks¶
Use _set_<attribute>() methods when the value is subclass-specific or
depends on references to other elements.
If the attribute is a plain default value, direct initialization in the default-values method is enough.
Step 5: Update concrete subclasses only when needed¶
If the new attribute is mandatory for all subclasses of an abstract base, override or populate it in each concrete subclass where required.
Example: for conversion technologies, attributes like
input_carrier/output_carrier are always subclass-defined.
Step 6: Validate by round-tripping a model¶
Recommended quick check:
Build/load a model.
Set the new attribute on one element.
Write the model.
Confirm the corresponding
attributes.jsoncontains the new key/value.
Worked Example: min_full_load_hours_fraction¶
Your recent change in conversion_technology.py is the reference example.
Registered the new name in
_subclass_attribute_names.Created the attribute in
set_default_values_conversion_technology.Added getter/setter with validation.
Minimal pattern:
class ConversionTechnology(Technology, ABC):
def __init__(self, model: Model, power_unit: str = "MW"):
super().__init__(model=model, power_unit=power_unit)
self._attribute_names = list(self._attribute_names)
self._subclass_attribute_names = [
"capex_specific_conversion",
"input_carrier",
"output_carrier",
"conversion_factor",
"min_full_load_hours_fraction",
]
self._attribute_names.extend(self._subclass_attribute_names)
self.set_default_values_conversion_technology()
def set_default_values_conversion_technology(self):
self.min_full_load_hours_fraction = Attribute(
name="min_full_load_hours_fraction",
default_value=0,
element=self,
)
@property
def min_full_load_hours_fraction(self) -> Attribute:
return self._min_full_load_hours_fraction
@min_full_load_hours_fraction.setter
def min_full_load_hours_fraction(self, value: Attribute) -> None:
self._validate_attribute(value)
self._min_full_load_hours_fraction = value
Class-Specific Notes¶
Technology¶
Adding here makes the attribute available to conversion, storage, transport, and retrofitting technologies.
Prefer this for truly cross-technology parameters.
ConversionTechnology / StorageTechnology /
TransportTechnology / RetrofittingTechnology
—————————————————–
Use these classes for family-specific attributes.
Keep family-only keys out of
Technologyto avoid bloating unrelated element types.
Carrier¶
Follow the same recipe: register name, initialize
Attribute, add getter/setter.Carrier attributes are written under
set_carriers/<carrier>/attributes.json.
Common Pitfalls¶
Forgetting to add the name to
_subclass_attribute_names.Defining a property but never initializing the underlying private variable.
Skipping
self._validate_attribute(value)in the setter.Choosing the wrong abstraction layer (for example adding a very specific key to
Technologyinstead of a subclass).
Quick Checklist¶
Before opening a PR, verify:
Attribute key is in
_subclass_attribute_names(or base class list).Default
Attributeobject is created in class initialization.Getter/setter exist and setter validates type.
Any needed
_set_<attribute>()logic is implemented.Model write output contains the new key in the expected
attributes.json.