Coverage for backend/django/flowsheetInternals/formula_templates/add_template.py: 87%

57 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-07-22 05:22 +0000

1from typing import Tuple, Dict 

2from flowsheetInternals.unitops.models import SimulationObject 

3from .template_schema import TemplateSchema 

4from .formula_templates import templates 

5from core.auxiliary.formula_limits import validate_formula_length 

6from core.auxiliary.models.PropertyInfo import PropertyInfo 

7from core.auxiliary.models.PropertyValue import PropertyValue 

8from flowsheetInternals.unitops.services.edit_operations.recorder import ( 

9 tracked_bulk_create, 

10 tracked_bulk_update, 

11) 

12 

13ID_MAPPING = dict[ 

14 str, Tuple[PropertyInfo, PropertyValue] 

15] # Mapping from property key to the info/value tuple persisted in the DB 

16 

17 

18def add_predefined_template(operation: SimulationObject, key: str): 

19 """Attach a property template and initialise its custom formulas. 

20 

21 The template describes one or more synthetic properties (for example cost 

22 expressions) that should be created on ``object``. Required properties are 

23 validated up-front; once the new ``PropertyInfo`` and ``PropertyValue`` 

24 records are persisted, each configured formula is rewritten to point at the 

25 database identifiers of the participating properties. 

26 

27 :param object: Simulation object that receives the template-driven properties. 

28 :param key: Template key, as defined in ``formula_templates``. 

29 :raises ValueError: If the template does not exist or required properties 

30 are missing on the simulation object. 

31 """ 

32 template: TemplateSchema = templates.get(key) 

33 if not template: 33 ↛ 34line 33 didn't jump to line 34 because the condition on line 33 was never true

34 raise ValueError(f"Template with key '{key}' does not exist.") 

35 add_template(operation, template) 

36 

37def add_template(operation: SimulationObject, template: TemplateSchema): 

38 """Attach a property template and initialise its custom formulas. 

39 """ 

40 

41 property_keys = [prop.key for prop in operation.properties.containedProperties.all()] 

42 for required_property in template.required_properties: 

43 if required_property not in property_keys: 43 ↛ 44line 43 didn't jump to line 44 because the condition on line 43 was never true

44 raise ValueError( 

45 f"Template requires property '{required_property}' which is not present in the object." 

46 ) 

47 

48 property_infos = [] 

49 property_values = [] 

50 

51 for field in template.fields: 

52 property_info = PropertyInfo( 

53 flowsheet_state=operation.flowsheet_state, 

54 displayName=field.name, 

55 key=field.key, 

56 set=operation.properties, 

57 ) 

58 property_value = PropertyValue( 

59 property=property_info, 

60 flowsheet_state=operation.flowsheet_state, 

61 value=None, # Default value can be set later 

62 ) 

63 property_infos.append(property_info) 

64 property_values.append(property_value) 

65 

66 tracked_bulk_create(PropertyInfo.objects, property_infos) 

67 tracked_bulk_create(PropertyValue.objects, property_values) 

68 

69 # Now all properties are created, we can set the formula for each property value. 

70 # This must be done later, because we need the IDs of the new objects in the database. 

71 # We now create a mapping so that we can set the formula information correctly. 

72 id_mapping: ID_MAPPING = { 

73 field.key: (property_info, property_value) 

74 for field, property_info, property_value in zip( 

75 template.fields, property_infos, property_values 

76 ) 

77 } 

78 # Also add all the properties that already exist on the object 

79 for property_info in operation.properties.containedProperties.all(): 

80 if property_info.key not in id_mapping: 

81 property_value = ( 

82 property_info.values.first() 

83 ) # TODO: Support indexed properties 

84 if property_value: 84 ↛ 79line 84 didn't jump to line 79 because the condition on line 84 was always true

85 id_mapping[property_info.key] = (property_info, property_value) 

86 # add all the properties in the inlet and outlet ports with a port_name.property_name convention 

87 for port in operation.ports.all(): 

88 stream = port.stream 

89 if stream is None: 89 ↛ 90line 89 didn't jump to line 90 because the condition on line 89 was never true

90 continue 

91 if not operation.schema.ports: 91 ↛ 92line 91 didn't jump to line 92 because the condition on line 91 was never true

92 continue 

93 port_config = operation.schema.ports[port.key] 

94 if not port_config: 94 ↛ 95line 94 didn't jump to line 95 because the condition on line 94 was never true

95 continue 

96 if port_config.many: 

97 port_name = f"{port.key}_{port.index + 1}" 

98 else: 

99 port_name = port.key 

100 for property_info in stream.properties.containedProperties.all(): 

101 property_value = ( 

102 property_info.values.first() 

103 ) # TODO: Support indexed properties 

104 if property_value: 104 ↛ 100line 104 didn't jump to line 100 because the condition on line 104 was always true

105 id_mapping[f"{port_name}.{property_info.key}"] = (property_info, property_value) 

106 

107 

108 

109 for field, property_value, property_info in zip( 

110 template.fields, property_values, property_infos 

111 ): 

112 property_value.formula = validate_formula_length( 

113 replace_with_ids(field.formula, id_mapping) 

114 ) 

115 

116 tracked_bulk_update(PropertyValue.objects, property_values, ["formula"]) 

117 

118 

119def replace_with_ids(formula: str, id_mapping: ID_MAPPING) -> str: 

120 """Swap template placeholders with the persisted property identifiers. 

121 

122 ``formula`` uses the template convention ``[key]``. This helper updates the 

123 formula with the ``@[property display name](prop<ID>)`` syntax expected by the front-end using 

124 the mapping collected during template application. 

125 

126 :param formula: Raw template formula string. 

127 :param id_mapping: Mapping from property keys to the ``PropertyInfo`` and 

128 ``PropertyValue`` instances created or found on the simulation object. 

129 :return: Updated formula string with property references rewritten. 

130 """ 

131 for key, (property_info, property_value) in id_mapping.items(): 

132 formula = formula.replace( 

133 f"[{key}]", f"@[{property_info.displayName}](prop{property_value.id})" 

134 ) 

135 return validate_formula_length(formula)