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