Coverage for backend/django/idaes_factory/adapters/property_value_adapter.py: 80%
72 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-06-23 21:51 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-06-23 21:51 +0000
1import json
2from core.auxiliary.enums.uiEnums import DisplayType
3from common.config_types import *
5class _PropertyInfoNotSetException(Exception):
6 def __init__(self, message: str = ""):
7 if message == "":
8 message = "PropertyInfo value not set"
9 super().__init__(message)
12# Property info operator
13def check_fixed(ctx, property_info, property_value, is_tear: bool = False) -> bool:
14 """
15 Check that the property is fixed
16 Raise an error if not, or return False if variables are not required to be fixed
17 """
18 if is_tear and not property_value.is_control_set_point() and not property_value.has_value():
19 # recycle guess, allowed to be empty
20 return False
21 # Property must be fixed, and have a value
22 if not property_value.has_value():
23 if property_value.is_control_manipulated():
24 # this value is a guess variable, and allowed to be empty
25 return False
26 indexed_items = [x for x in property_value.indexedItems.all()]
27 property_values = [x for x in property_info.values.all()]
28 pv_ii = [[x for x in y.indexedItems.all()] for y in property_values]
29 # error, property should be fixed and have a value
30 if ctx.require_variables_fixed: 30 ↛ 31line 30 didn't jump to line 31 because the condition on line 30 was never true
31 raise _PropertyInfoNotSetException(f"Property `{property_info.displayName}` is not set")
32 else:
33 # RequireVariablesFixed is a context variable that can be set to False to allow for properties to be unset
34 # Allow this to pass for testing purposes only.
35 return False
36 return True
39def get_data_column_id(ctx, property_value) -> int | None:
40 """Return the scenario-specific data column for a property value.
42 `IdaesFactoryContext` provides a query-free annotated lookup. A small
43 fallback keeps direct adapter tests and other lightweight contexts working.
44 """
45 if hasattr(ctx, "get_data_column_id"):
46 return ctx.get_data_column_id(property_value)
47 if getattr(ctx, "scenario", None) is None: 47 ↛ 48line 47 didn't jump to line 48 because the condition on line 47 was never true
48 return None
50 from core.auxiliary.models.DataColumn import DataColumn
52 return (
53 DataColumn.objects.filter(
54 scenario=ctx.scenario,
55 property_value=property_value,
56 )
57 .values_list("id", flat=True)
58 .first()
59 )
62def get_solve_index_data_cell_value(ctx, data_column_id: int) -> float:
63 """Return a data-cell value for the current solve row."""
64 if hasattr(ctx, "get_solve_index_data_cell_value"):
65 return ctx.get_solve_index_data_cell_value(data_column_id)
67 from core.auxiliary.models.DataCell import DataCell
69 return DataCell.objects.get(
70 data_column_id=data_column_id,
71 data_row=ctx.solve_index,
72 ).value
75def get_dynamic_data_cell_values(ctx, data_column_id: int) -> list[float]:
76 """Return dynamic data-cell values for a data column."""
77 if hasattr(ctx, "get_dynamic_data_cell_values"): 77 ↛ 80line 77 didn't jump to line 80 because the condition on line 77 was always true
78 return ctx.get_dynamic_data_cell_values(data_column_id)
80 from core.auxiliary.models.DataCell import DataCell
82 return [
83 cell.value
84 for cell in DataCell.objects.filter(data_column_id=data_column_id).all()
85 ]
88def serialise_property_value(ctx, property_info, property_value, is_indexed: bool = True, is_tear: bool = False):
89 """
90 Serialise a property info object
91 """
93 # TODO: add time step handling: get the value at the given time step
94 match property_info.type:
95 case DisplayType.dropdown: 95 ↛ 96line 95 didn't jump to line 96 because the pattern on line 95 never matched
96 raise NotImplementedError("Dropdown properties are not yet supported in idaes_factory")
97 case DisplayType.checkbox: 97 ↛ 98line 97 didn't jump to line 98 because the pattern on line 97 never matched
98 return bool(json.loads(property_value.value)) # return True or False
99 case DisplayType.numeric_arg: 99 ↛ 100line 99 didn't jump to line 100 because the pattern on line 99 never matched
100 raise NotImplementedError("Dropdown properties are not yet supported in idaes_factory")
101 case DisplayType.segmented: 101 ↛ 102line 101 didn't jump to line 102 because the pattern on line 101 never matched
102 raise NotImplementedError("Segmented properties are not yet supported in idaes_factory")
103 case DisplayType.numeric: 103 ↛ 146line 103 didn't jump to line 146 because the pattern on line 103 always matched
104 if is_tear:
105 # tear properties are disabled, but we still need to serialise the recycle guesses
106 if not (
107 property_info.is_recycle_var()
108 or property_value.is_control_set_point()
109 ):
110 # skip, not enabled
111 return None
112 else:
113 if not property_value.is_enabled() and not property_value.is_control_manipulated():
114 # property is disabled (and not a guess), so don't serialise it
115 return None
116 # check that the property is fixed, and raise an error if not
117 if not check_fixed(ctx, property_info, property_value, is_tear):
118 # variables are not required to be fixed, so don't serialise this property
119 return None
121 value = property_value.value
122 # We are no longer supporting parsing expressions here, becuase that functionality
123 # is possible with specification and control blocks.
124 data_column_id = get_data_column_id(ctx, property_value)
126 if data_column_id is not None:
127 if ctx.solve_index is not None: # if index is given for a value from the csv
128 value = get_solve_index_data_cell_value(ctx, data_column_id)
129 elif ctx.is_dynamic() and is_indexed: # only use MSS for dynamics if the property is indexed, because otherwise it will be a single value. 129 ↛ 142line 129 didn't jump to line 142 because the condition on line 129 was always true
130 # We want to get all the data cells, and use as many data cells as there are timesteps
131 # (We are using the CSV data as input data)
132 # Note: assuming DataCell.objects.all() returns the values in the correct order
133 value = get_dynamic_data_cell_values(ctx, data_column_id)
134 value = value[0:len(ctx.time_steps)]
135 return value
137 elif ctx.is_dynamic() and is_indexed:
138 return [float(value) for _ in ctx.time_steps]
140 # we used to use the dynamic results as the input but we aren't doing that any more
141 # so just return the one value, idaes_service will assume it's constant.
142 if is_indexed:
143 return [float(value)]
144 else:
145 return float(value)
146 case _:
147 raise ValueError(f"Property type {property_info.type} is not supported in idaes_factory")