Coverage for backend/flowsheetInternals/unitops/models/summary_table_factory.py: 93%
122 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-11-06 23:27 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-11-06 23:27 +0000
1from core.auxiliary.enums.unitsLibrary import units_library
2from compounds import CompoundDB
3from core.auxiliary.enums.unitOpData import SimulationObjectClass
4from flowsheetInternals.unitops.config.config_base import stream_classes, unitop_classes
5from idaes_factory.unit_conversion.unit_conversion import convert_value
6from core.auxiliary.models.PropertyValue import PropertyValue
7from django.db.models import QuerySet
8from flowsheetInternals.unitops.models.SimulationObject import SimulationObject
9from pydantic import BaseModel
10from django.db import models
11from idaes_factory.queryset_lookup import get_property
13class MeasureType(models.TextChoices):
14 FLOW = "Flow" # absolute value
15 FRACTION = "Fraction" # relative value
17class CompoundMode(models.TextChoices):
18 MASS = "Mass"
19 MOLAR = "Molar"
20class TableData(BaseModel):
21 units: dict[str, list[str]]
22 data: list[dict[str, str | float | int]]
24PropertyKey = str
25Unit = str
26UnitMap = dict[PropertyKey, Unit]
27ObjectType = str
30def serialize_objects_for_table(objects_by_type: list[list[SimulationObject]], unit_map: UnitMap) -> dict[ObjectType, TableData]:
31 serialized = {}
33 for queryset in objects_by_type:
34 object_type = queryset[0].objectType
36 objs_to_add = []
37 units = {}
38 for obj in queryset:
39 properties = {"name": obj.componentName}
40 for prop_info in obj.properties.ContainedProperties.all():
41 if prop_info.key == "mole_frac_comp":
42 continue
43 for prop_value in prop_info.values.all():
44 indexedItem = prop_value.indexedItems.first()
45 key = f"{prop_value.property.displayName} {indexedItem.displayName}" if indexedItem else prop_value.property.displayName
47 target_unit = unit_map.get(key)
48 value = prop_value.value
49 if value and target_unit and (target_unit["value"] != prop_info.unit): 49 ↛ 50line 49 didn't jump to line 50 because the condition on line 49 was never true
50 value = convert_value(float(value), prop_info.unit, target_unit["value"])
52 units[key] = units_library.get(prop_info.unitType, [{"value": "dimensionless", "label": "—"}])
53 properties[key] = value
55 objs_to_add.append(properties)
57 serialized[object_type] = {
58 "units": units,
59 "data": objs_to_add
60 }
62 return serialized
64def get_stream_summary_table_data(queryset: QuerySet[SimulationObject], unit_map: UnitMap):
65 all_streams = queryset.filter(objectType__in=stream_classes)
67 streams_by_type = {}
68 for stream in all_streams:
69 streams_by_type.setdefault(stream.objectType, []).append(stream)
71 streams_by_type = list(streams_by_type.values())
73 return serialize_objects_for_table(streams_by_type, unit_map)
75def get_unitops_summary_table_data(queryset: QuerySet[SimulationObject], unit_map: UnitMap):
76 all_unitops = queryset.filter(objectType__in=unitop_classes)
78 unitops_by_type = {}
79 for unitop in all_unitops:
80 unitops_by_type.setdefault(unitop.objectType, []).append(unitop)
82 unitops_by_type = list(unitops_by_type.values())
84 return serialize_objects_for_table(unitops_by_type, unit_map)
86def get_composition_summary_table_data(queryset: QuerySet[SimulationObject], target_compound_mode: CompoundMode, measure_type: MeasureType):
87 results = {"data": [], "columns": None}
88 columns = set() # This is for collecting all possible columns to display in the table
90 streams = queryset.filter(objectType=SimulationObjectClass.Stream)
92 for stream in streams:
93 object = {"name": stream.componentName}
94 compounds = get_compounds(stream)
95 current_compound_mode = stream.properties.compoundMode
97 if measure_type == MeasureType.FRACTION:
98 if current_compound_mode != target_compound_mode: 98 ↛ 113line 98 didn't jump to line 113 because the condition on line 98 was always true
99 if target_compound_mode == CompoundMode.MASS: # Molar fraction to Mass fraction
100 mass_fractions = get_compound_mass_fractions(compounds)
102 for i, (key, prop) in enumerate(compounds):
103 object[key] = mass_fractions[i]
104 columns.add(key)
106 else: # Mass fraction to Molar fraction
107 for i, (key, prop) in enumerate(compounds):
108 columns.add(key)
109 compound_value = prop.value # already mass fraction
110 object[key] = compound_value
112 else: # things are already calculated in the right mode -> use displayValue directly
113 for i, (key, prop) in enumerate(compounds):
114 columns.add(key)
115 compound_value = prop.displayValue
116 object[key] = compound_value
118 else:
119 if target_compound_mode == CompoundMode.MASS: # Get Mass Flow
120 total_mass_flow = get_property(stream.properties, "flow_mass").get_value()
121 mass_fractions = get_compound_mass_fractions(compounds)
123 for i, (key, prop) in enumerate(compounds):
124 if total_mass_flow:
125 object[key] = total_mass_flow * mass_fractions[i]
126 else:
127 object[key] = None
128 columns.add(key)
130 else: # Get Molar Flow
131 total_molar_flow = get_property(stream.properties, "flow_mol").get_value()
133 for i, (key, prop) in enumerate(compounds):
134 if total_molar_flow:
135 object[key] = float(total_molar_flow) * float(prop.value)
136 else:
137 object[key] = None
138 columns.add(key)
140 results["data"].append(object)
142 results["columns"] = ["name"] + list(columns)
144 return results
146def get_compounds(stream) -> set[tuple[str, PropertyValue]]:
147 mole_frac_comp = get_property(stream.properties, "mole_frac_comp")
148 property_values = mole_frac_comp.values.all()
149 result = [
150 (prop.get_index("compound").key, prop)
151 for prop in property_values
152 ]
153 return result
155def get_compound_mass_fractions(compounds: set[tuple[str, PropertyValue]]) -> list[float]:
156 results = []
157 for key, prop in compounds:
158 try:
159 value = float(prop.value)
160 except (ValueError, TypeError):
161 return [None for _ in compounds]
163 molar_fraction = value
165 compound = CompoundDB.get_compound(key)
166 molecular_weight = compound["MolecularWeight"].value
168 results.append(molar_fraction * molecular_weight)
170 total_mass = sum(results)
171 if total_mass > 0: 171 ↛ 174line 171 didn't jump to line 174 because the condition on line 171 was always true
172 results = [mass / total_mass for mass in results]
173 else:
174 results = [None for _ in results]
175 return results