Required Calculation Models¶
Overview¶
The RequiredCalcModels class is a subclass of CalculationRequirement that is used to check if a list of calculation models are present for an object. This is useful for features that are based on calculation models, such as the ones based on ML models.
Usage¶
This requirement can be instantiated with a list of calculation models that need to be present for each object. Below there is an example of how to use this requirement:
requirement = RequiredCalcModels(calc_models={"SDM1-VRN1-01": [{"model_name": "fitted_power_curve", "model_type": "fitted_power_curve"}]})
After calling check and get_data methods, the data attribute of the requirement will be a dictionary with the object as the key and nested dictionaries with the model name as the key and the files for the model as the value. Below there is an example of how this data is stored:
{
"SDM1-VRN1-01": {
"fitted_power_curve": {
"model": <python object>
}
}
}
Database Requirements¶
This requirement expects that the four tables below are correctly set:
calculation_models: Definition of the model.calculation_model_files_def: Definition of the files for the model.calculation_model_files_data: Actual binary files for the model.calculation_model_files_data_object_connections: Connection between the object and the model files.
At the end, the v_calculation_model_files_data view will be used to get the files for the model.
Class Definition¶
RequiredCalcModels(calc_models, optional=False)
¶
Subclass of CalculationRequirement that defines the calculation models that are required for the calculation.
This will check the performance database for the existence of the required calculation models for the wanted objects.
Parameters:
-
(calc_models¶dict[str | None, list[dict[str, str | None]]]) –Calculation models that are required for the calculation. This should be in the format below:
Python{ object_name: [ { "model_name": "calculation_model_name", "model_type": "calculation_model_type" }, ... ], ... }Where:
- object_name: str | None Name of the object for which the calculation model is required. If None, we assume the calculation model is not connected no any objects.
- model_name: str Name of the calculation model as in performance_db. It will be treated as a regex to filter the calculation models.
- model_type: str | None Type of the calculation model as in performance_db. If None, we assume the calculation model is not connected no any objects.
-
(optional¶bool, default:False) –Set to True if this is an optional requirement. by default False
Source code in echo_energycalc/calculation_requirement_calc_models.py
def __init__(self, calc_models: dict[str | None, list[dict[str, str | None]]], optional: bool = False) -> None:
"""
Constructor of the RequiredCalcModels class.
This will check the performance database for the existence of the required calculation models for the wanted objects.
Parameters
----------
calc_models : dict[str | None, list[dict[str, str | None]]]
Calculation models that are required for the calculation. This should be in the format below:
```python
{
object_name: [
{
"model_name": "calculation_model_name",
"model_type": "calculation_model_type"
},
...
],
...
}
```
Where:
- object_name: str | None
Name of the object for which the calculation model is required. If None, we assume the calculation model is not connected no any objects.
- model_name: str
Name of the calculation model as in performance_db. It will be treated as a regex to filter the calculation models.
- model_type: str | None
Type of the calculation model as in performance_db. If None, we assume the calculation model is not connected no any objects.
optional : bool, optional
Set to True if this is an optional requirement. by default False
"""
super().__init__(optional=optional)
calc_models_schema = {
"description": "Keys must be the name of the objects.",
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"properties": {
"model_name": {
"type": ["string"],
"description": "Name of the calculation model as in performance_db. It will be treated as a regex to filter the calculation models.",
},
"model_type": {
"type": ["string", "null"],
"description": "Type of the calculation model as in performance_db",
},
},
"required": ["model_name", "model_type"],
"additionalProperties": False,
},
{
"type": "object",
"properties": {
"model_name": {
"type": ["string", "null"],
"description": "Name of the calculation model as in performance_db. It will be treated as a regex to filter the calculation models.",
},
"model_type": {
"type": ["string"],
"description": "Type of the calculation model as in performance_db",
},
},
"required": ["model_name", "model_type"],
"additionalProperties": False,
},
],
},
"minItems": 1,
},
}
try:
jsonschema.validate(calc_models, calc_models_schema)
except jsonschema.ValidationError as e:
raise ValueError("Invalid calc_models argument") from e
self._calc_models: dict[str | None, list[dict[str, str | None]]] = calc_models
# temporary directory used to store the calculation models
self._temp_dir = tempfile.mkdtemp()
calc_models
property
¶
Calculation models that are required for the calculation.
Returns:
-
dict[str | None, list[dict[str, str | None]]]–Calculation models that are required for the calculation.
checked
property
¶
Attribute that defines if the requirement has been checked. It's value will start as False and will be set to True after the check method is called.
Returns:
-
bool–True if the requirement has been checked.
data
property
¶
Data required for the calculation.
Returns:
-
dict[str, dict[str, dict[str, Any]]]–dict in the format {object_name: {calculation_model_name: {file_name: value}}}
fetched
property
¶
Attribute that defines if get_data() has been called on this requirement.
True even when the fetch returned no data (e.g. an optional requirement
that found nothing). Use this to distinguish "never fetched" from "fetched
but empty/None".
Returns:
-
bool–True if get_data() has been called at least once.
optional
property
¶
Attribute that defines if the requirement is optional.
If optional is True, the requirement is only validated to check if it could exist, not if it is actually present. This is useful for requirements that are not necessary for all calculations, but are useful for some of them.
Returns:
-
bool–True if the requirement is optional.
check()
¶
Check that the requirement is met.
This concrete implementation handles two concerns automatically so that
subclasses only need to implement _do_check():
- Already-checked guard — returns
Trueimmediately ifcheck()has already succeeded for this instance, avoiding redundant DB round-trips when_fetch_requirements()iterates requirements on every_compute()call. - Per-thread caching — when
_check_cache_key()returns a non-None key, the result produced by_do_check()is stored in a thread-local cache and reused by subsequent instances in the same thread with the same key. Because the cache is never shared across threads, no locking is needed and concurrent Polars operations inside_do_checkcannot deadlock.
The optional guard is intentionally delegated to _do_check() because
different subclasses have different optional semantics (see _do_check docs).
Returns:
-
bool–True if the requirement is met; raises on unmet non-optional requirements.
Source code in echo_energycalc/calculation_requirements_core.py
def check(self) -> bool:
"""
Check that the requirement is met.
This concrete implementation handles two concerns automatically so that
subclasses only need to implement ``_do_check()``:
1. **Already-checked guard** — returns ``True`` immediately if ``check()`` has
already succeeded for this instance, avoiding redundant DB round-trips when
``_fetch_requirements()`` iterates requirements on every ``_compute()`` call.
2. **Per-thread caching** — when ``_check_cache_key()`` returns a non-None key,
the result produced by ``_do_check()`` is stored in a thread-local cache and
reused by subsequent instances in the same thread with the same key. Because
the cache is never shared across threads, no locking is needed and concurrent
Polars operations inside ``_do_check`` cannot deadlock.
The **optional guard** is intentionally delegated to ``_do_check()`` because
different subclasses have different optional semantics (see ``_do_check`` docs).
Returns
-------
bool
True if the requirement is met; raises on unmet non-optional requirements.
"""
if self._checked:
return True
cache_key = self._check_cache_key()
if cache_key is not None:
_tl = type(self)._cache_local # noqa: SLF001
if not hasattr(_tl, "cache"):
_tl.cache = {}
cached = _tl.cache.get(cache_key)
if cached is None:
self._do_check()
_tl.cache[cache_key] = self._get_cache_value()
cached = _tl.cache[cache_key]
else:
logger.debug("Cache hit for %s (key=%s)", type(self).__name__, cache_key)
self._set_from_cache(cached)
else:
self._do_check()
self._checked = True
return True
get_data(**kwargs)
¶
Method used to get the data required for the calculation.
This will download all the files of all the required calculation models and return a dict with the model. This dict will also be available in the object property "data".
If the model does not have an associated object the key used for it will be "general".
Returns:
-
dict[str, dict[str, dict[str, Any]]]–dict in the format {object_name: {calculation_model_name: {file_name: value}}}
Source code in echo_energycalc/calculation_requirement_calc_models.py
def get_data(self, **kwargs) -> dict[str, dict[str, dict[str, Any]]]: # noqa: ARG002
"""
Method used to get the data required for the calculation.
This will download all the files of all the required calculation models and return a dict with the model.
This dict will also be available in the object property "data".
If the model does not have an associated object the key used for it will be "general".
Returns
-------
dict[str, dict[str, dict[str, Any]]]
dict in the format {object_name: {calculation_model_name: {file_name: value}}}
"""
# check if requirement has been checked
if not self._checked:
self.check()
if self._data is None:
self._download_all()
self._fetched = True
return self.data