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

56 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-06-23 21:51 +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 

8 

9ID_MAPPING = dict[ 

10 str, Tuple[PropertyInfo, PropertyValue] 

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

12 

13 

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

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

16 

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

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

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

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

21 database identifiers of the participating properties. 

22 

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

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

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

26 are missing on the simulation object. 

27 """ 

28 template: TemplateSchema = templates.get(key) 

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

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

31 add_template(operation, template) 

32 

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

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

35 """ 

36 

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

38 for required_property in template.required_properties: 

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

40 raise ValueError( 

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

42 ) 

43 

44 property_infos = [] 

45 property_values = [] 

46 

47 for field in template.fields: 

48 property_info = PropertyInfo( 

49 flowsheet=operation.flowsheet, 

50 displayName=field.name, 

51 key=field.key, 

52 set=operation.properties, 

53 ) 

54 property_value = PropertyValue( 

55 property=property_info, 

56 flowsheet=operation.flowsheet, 

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

58 ) 

59 property_infos.append(property_info) 

60 property_values.append(property_value) 

61 

62 PropertyInfo.objects.bulk_create(property_infos) 

63 PropertyValue.objects.bulk_create(property_values) 

64 

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

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

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

68 id_mapping: ID_MAPPING = { 

69 field.key: (property_info, property_value) 

70 for field, property_info, property_value in zip( 

71 template.fields, property_infos, property_values 

72 ) 

73 } 

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

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

76 if property_info.key not in id_mapping: 

77 property_value = ( 

78 property_info.values.first() 

79 ) # TODO: Support indexed properties 

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

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

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

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

84 stream = port.stream 

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

86 continue 

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

88 continue 

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

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

91 continue 

92 if port_config.many: 

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

94 else: 

95 port_name = port.key 

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

97 property_value = ( 

98 property_info.values.first() 

99 ) # TODO: Support indexed properties 

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

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

102 

103 

104 

105 for field, property_value, property_info in zip( 

106 template.fields, property_values, property_infos 

107 ): 

108 property_value.formula = validate_formula_length( 

109 replace_with_ids(field.formula, id_mapping) 

110 ) 

111 

112 PropertyValue.objects.bulk_update(property_values, ["formula"]) 

113 

114 

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

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

117 

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

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

120 the mapping collected during template application. 

121 

122 :param formula: Raw template formula string. 

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

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

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

126 """ 

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

128 formula = formula.replace( 

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

130 ) 

131 return validate_formula_length(formula)