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

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 

7 

8class _PropertyInfoNotSetException(Exception): 

9 def __init__(self, message: str = ""): 

10 if message == "": 

11 message = "PropertyInfo value not set" 

12 super().__init__(message) 

13 

14 

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 

40 

41 

42def get_data_column_id(ctx, property_value) -> int | None: 

43 """Return the scenario-specific data column for a property value. 

44 

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 

52 

53 from core.auxiliary.models.DataColumn import DataColumn 

54 

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 ) 

63 

64 

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) 

69 

70 from core.auxiliary.models.DataCell import DataCell 

71 

72 return DataCell.objects.get( 

73 data_column_id=data_column_id, 

74 data_row=ctx.solve_index, 

75 ).value 

76 

77 

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) 

82 

83 from core.auxiliary.models.DataCell import DataCell 

84 

85 return [ 

86 cell.value 

87 for cell in DataCell.objects.filter(data_column_id=data_column_id).all() 

88 ] 

89 

90 

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 """ 

102 

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 

134 

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) 

139 

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 

150 

151 elif ctx.is_dynamic() and is_indexed: 

152 return [float(value) for _ in ctx.time_steps] 

153 

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")