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
« 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)
13ID_MAPPING = dict[
14 str, Tuple[PropertyInfo, PropertyValue]
15] # Mapping from property key to the info/value tuple persisted in the DB
18def add_predefined_template(operation: SimulationObject, key: str):
19 """Attach a property template and initialise its custom formulas.
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.
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)
37def add_template(operation: SimulationObject, template: TemplateSchema):
38 """Attach a property template and initialise its custom formulas.
39 """
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 )
48 property_infos = []
49 property_values = []
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)
66 tracked_bulk_create(PropertyInfo.objects, property_infos)
67 tracked_bulk_create(PropertyValue.objects, property_values)
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)
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 )
116 tracked_bulk_update(PropertyValue.objects, property_values, ["formula"])
119def replace_with_ids(formula: str, id_mapping: ID_MAPPING) -> str:
120 """Swap template placeholders with the persisted property identifiers.
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.
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)