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

1import json 

2from core.auxiliary.enums.uiEnums import DisplayType 

3from common.config_types import * 

4 

5class _PropertyInfoNotSetException(Exception): 

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

7 if message == "": 

8 message = "PropertyInfo value not set" 

9 super().__init__(message) 

10 

11 

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 

37 

38 

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

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

41 

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 

49 

50 from core.auxiliary.models.DataColumn import DataColumn 

51 

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 ) 

60 

61 

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) 

66 

67 from core.auxiliary.models.DataCell import DataCell 

68 

69 return DataCell.objects.get( 

70 data_column_id=data_column_id, 

71 data_row=ctx.solve_index, 

72 ).value 

73 

74 

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) 

79 

80 from core.auxiliary.models.DataCell import DataCell 

81 

82 return [ 

83 cell.value 

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

85 ] 

86 

87 

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

92 

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 

120 

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) 

125 

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 

136 

137 elif ctx.is_dynamic() and is_indexed: 

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

139 

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