Coverage for backend/django/idaes_factory/adapters/property_info_adapter.py: 99%
72 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-05-13 02:47 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-05-13 02:47 +0000
1from abc import ABC
2from typing import TYPE_CHECKING
4from ahuora_builder_types import PropertySchema, PropertiesSchema, PropertyValueSchema
5from common.config_types import *
6from ..queryset_lookup import get_property
7from .convert_expression import convert_expression
8from .property_value_adapter import serialise_property_value
10if TYPE_CHECKING:
11 from core.auxiliary.models.PropertyInfo import PropertyInfo
12 from ..idaes_factory_context import IdaesFactoryContext
14def serialise_property_info(ctx, property_info: "PropertyInfo",
15 is_tear: bool = False,
16 is_indexed: bool = True) -> PropertySchema:
17 """
18 A PropertyInfo object represents a single IndexedVar or ScalarVar in IDAES.
19 This method handles unpacking the indexes and putting it in the format that
20 idaes_service expects. see pump.json or any of the other idaes_service test files
21 as an example of what a property looks like.
22 """
24 property_values = property_info.values.all()
26 has_value = False
28 if ctx.scenario != None:
29 enable_rating = ctx.scenario.enable_rating
30 else:
31 enable_rating = False
33 data = []
35 for prop in property_values:
36 # get indexes
37 # ie. ["0", "benzene", "Vap"]
38 from core.auxiliary.models.PropertyValue import sort_indexes
40 unsorted_indexes = list(prop.indexedItems.all())
42 # Get the object type and property key (e.g.: split_fraction)
43 property_key = property_info.key
44 schema = property_info.set.simulationObject.schema
45 if property_key not in schema.properties:
46 # e.g machine learning block: doesn't use the normal property schema methods.
47 index_set_order = []
48 else:
49 index_set_order : list[str] = property_info.set.simulationObject.schema.properties[property_key].indexSets
51 # Sort the indexes so that they are in the order defined in the config file
52 # and continue serialization with the new indexes
53 indexes = [index.key for index in sort_indexes(index_set_order, unsorted_indexes)]
55 # get value data
56 # ie. {"id": 1, "value": 0.5, "controlled": int?, "guess": bool?, "constraint": str?} value_data = {
57 property_value = PropertyValueSchema(
58 id=prop.id,
59 discrete_indexes=indexes,
60 )
61 value = serialise_property_value(ctx, property_info, prop, is_indexed, is_tear=is_tear)
62 if value is not None:
63 has_value = True # used to determine if we should include the unit
64 if is_tear:
65 # only pass value for tear variables
66 property_value.value = value
67 if (
68 prop.is_control_set_point()
69 and prop.is_externally_controlled()
70 ):
71 # this property is externally controlled, and will be included
72 # on the other side of the tear. So we need to set the id to -1
73 # to avoid clashing ids.
74 # We need to discard this one because this side is just a guess,
75 # while the other may be used for constraints which can be
76 # deactivated during optimization.
77 property_value.id = -1
78 else:
79 property_value.value = value
80 if (prop.is_control_set_point()):
81 if not enable_rating:
82 # get the id of the thing we're manipulating
83 property_value.controlled = prop.controlSetPoint.manipulated.id
84 else:
85 property_value.value = None
86 if (prop.is_control_manipulated()
87 and not enable_rating):
88 property_value.guess = True
90 if prop.formula not in [None, ""]:
91 property_value.constraint = convert_expression(prop.formula)
93 # track dependencies
94 ctx.add_property_value_dependency(prop)
96 data.append(property_value)
98 property_schema = PropertySchema(data=data)
99 if has_value:
100 property_schema.unit = property_info.unit
102 return property_schema
106class ValueAdapter(ABC):
107 def serialise(self, ctx, model):
108 pass
113def is_group_enabled(model, ctx, prop_key: str) -> bool:
114 """
115 If the propertySetGroup that it's a part of is not enabled,
116 we don't need this property.
117 The main case for this is e.g heaters where "enable dynamics" can be
118 switched on or off.
119 """
120 # If the property is part of a group that is not enabled, don't serialise it
121 group = model.schema.properties[prop_key].propertySetGroup
122 group_schema = model.schema.propertySetGroups[group]
123 if group_schema.toggle:
124 toggle_property = ctx.get_property(model.properties, group_schema.toggle)
125 toggle_status = ctx.get_property_value(toggle_property).value
126 if toggle_status != True:
127 return False
128 # if it's the dynamics group, and it's not dynamic, don't serialise it
129 if group == "dynamics" and not ctx.is_dynamic():
130 return False
131 # Otherwise, it's enabled
132 return True
134class SerialisePropertiesAdapter(ValueAdapter):
135 """
136 This class replaced PropertyDictAdapter and PropertyKeyAdapter.
137 """
139 def serialise(self, ctx, model, is_tear: bool = False) -> PropertiesSchema:
140 """
141 Serialise all properties in the model.
142 """
143 property_set = model.properties
145 result: PropertiesSchema = {}
147 prop_key: str
148 prop_schema: PropertyType
149 for prop_key,prop_schema in model.schema.properties.items():
150 # if the propertySetGroup is not enabled, don't serialise this property
151 if not is_group_enabled(model, ctx, prop_key):
152 continue
153 # if schema type is not number, skip it too
154 if prop_schema.type != "numeric":
155 continue
156 # get the property from the unit model
157 prop = get_property(property_set, prop_key)
158 # serialise the property
159 result[prop_key] = serialise_property_info(
160 ctx,
161 prop,
162 is_tear=is_tear, # tears and streams are handled separately
163 is_indexed=prop_schema.hasTimeIndex
164 )
165 return result