Coverage for backend/django/Economics/results/models.py: 94%
115 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 django.core.exceptions import ValidationError
2from django.db import models
4from core.managers import AccessControlManager, AllFlowsheetStatesManager
5from Economics.shared.choices import ResultDependencyType, ResultLineKind, ResultRunStatus
6from Economics.shared.model_base import FlowsheetScopedEconomicsModel
9class EconomicsResultRun(FlowsheetScopedEconomicsModel):
10 same_flowsheet_fields = ("study",)
12 flowsheet_state = models.ForeignKey("core_auxiliary.FlowsheetState", on_delete=models.CASCADE, related_name="economics_result_runs")
13 study = models.ForeignKey("EconomicsStudy", on_delete=models.CASCADE, related_name="result_runs")
14 status = models.CharField(max_length=32, choices=ResultRunStatus.choices, default=ResultRunStatus.DRAFT)
15 result_currency = models.CharField(max_length=3, default="NZD")
16 result_basis_date = models.DateField(null=True, blank=True)
17 warning_payload = models.JSONField(default=dict, blank=True)
18 created_at = models.DateTimeField(auto_now_add=True)
19 completed_at = models.DateTimeField(null=True, blank=True)
21 objects = AccessControlManager()
22 all_states = AllFlowsheetStatesManager()
24 class Meta:
25 ordering = ["-created_at"]
27 def __str__(self):
28 return f"{self.study.name} result run {self.pk or 'unsaved'}"
30class EconomicsResultDependency(FlowsheetScopedEconomicsModel):
31 same_flowsheet_fields = (
32 "result_run",
33 "source_assumptions",
34 "source_baseline",
35 "source_costable_item",
36 "source_capital_line",
37 "source_operating_line",
38 "source_scenario",
39 "source_property_info",
40 )
42 flowsheet_state = models.ForeignKey("core_auxiliary.FlowsheetState", on_delete=models.CASCADE, related_name="economics_result_dependencies")
43 result_run = models.ForeignKey("EconomicsResultRun", on_delete=models.CASCADE, related_name="dependencies")
44 dependency_type = models.CharField(max_length=64, choices=ResultDependencyType.choices)
45 dependency_key = models.CharField(max_length=160)
46 fingerprint_value = models.CharField(max_length=160)
47 fingerprint_algorithm = models.CharField(max_length=32, default="sha256")
48 fingerprint_basis = models.CharField(max_length=128, blank=True)
49 source_label = models.CharField(max_length=160, blank=True)
50 source_row_key = models.CharField(max_length=160, blank=True)
51 source_version = models.CharField(max_length=128, blank=True)
52 source_assumptions = models.ForeignKey(
53 "EconomicsAssumptions",
54 on_delete=models.SET_NULL,
55 related_name="result_dependencies",
56 null=True,
57 blank=True,
58 )
59 source_baseline = models.ForeignKey(
60 "EconomicsBaseline",
61 on_delete=models.SET_NULL,
62 related_name="result_dependencies",
63 null=True,
64 blank=True,
65 )
66 source_settings_profile = models.ForeignKey(
67 "EconomicsSettingsProfile",
68 on_delete=models.SET_NULL,
69 related_name="result_dependencies",
70 null=True,
71 blank=True,
72 )
73 source_costable_item = models.ForeignKey(
74 "CostableItem",
75 on_delete=models.SET_NULL,
76 related_name="result_dependencies",
77 null=True,
78 blank=True,
79 )
80 source_cost_curve = models.ForeignKey(
81 "CostCurve",
82 on_delete=models.SET_NULL,
83 related_name="result_dependencies",
84 null=True,
85 blank=True,
86 )
87 source_capital_line = models.ForeignKey(
88 "CapitalCostLine",
89 on_delete=models.SET_NULL,
90 related_name="result_dependencies",
91 null=True,
92 blank=True,
93 )
94 source_operating_line = models.ForeignKey(
95 "OperatingCostLine",
96 on_delete=models.SET_NULL,
97 related_name="result_dependencies",
98 null=True,
99 blank=True,
100 )
101 source_index_series = models.ForeignKey(
102 "CostIndexSeries",
103 on_delete=models.SET_NULL,
104 related_name="result_dependencies",
105 null=True,
106 blank=True,
107 )
108 source_index_value = models.ForeignKey(
109 "CostIndexValue",
110 on_delete=models.SET_NULL,
111 related_name="result_dependencies",
112 null=True,
113 blank=True,
114 )
115 source_scenario = models.ForeignKey(
116 "core_auxiliary.Scenario",
117 on_delete=models.SET_NULL,
118 related_name="economics_result_dependencies",
119 null=True,
120 blank=True,
121 )
122 source_property_info = models.ForeignKey(
123 "core_auxiliary.PropertyInfo",
124 on_delete=models.SET_NULL,
125 related_name="economics_result_dependencies",
126 null=True,
127 blank=True,
128 )
129 created_at = models.DateTimeField(auto_now_add=True)
131 objects = AccessControlManager()
132 all_states = AllFlowsheetStatesManager()
134 class Meta:
135 ordering = ["dependency_type", "dependency_key"]
136 constraints = [
137 models.UniqueConstraint(
138 fields=["result_run", "dependency_type", "dependency_key"],
139 name="unique_result_dependency_key_per_run",
140 ),
141 ]
143 def clean(self):
144 super().clean()
145 if self.source_settings_profile_id is None or self.flowsheet_state_id is None: 145 ↛ 147line 145 didn't jump to line 147 because the condition on line 145 was always true
146 return
147 if (
148 self.source_settings_profile.project_id
149 != self.flowsheet_state.flowsheet.project_id
150 ):
151 raise ValidationError(
152 {"source_settings_profile": "Referenced profile must belong to the result flowsheet project."}
153 )
155 def __str__(self):
156 return f"{self.dependency_type}:{self.dependency_key}"
158class EconomicsResultLine(FlowsheetScopedEconomicsModel):
159 same_flowsheet_fields = (
160 "result_run",
161 "source_costable_item",
162 "source_capital_line",
163 "source_operating_line",
164 "source_property_info",
165 )
167 flowsheet_state = models.ForeignKey("core_auxiliary.FlowsheetState", on_delete=models.CASCADE, related_name="economics_result_lines")
168 result_run = models.ForeignKey("EconomicsResultRun", on_delete=models.CASCADE, related_name="lines")
169 kind = models.CharField(max_length=32, choices=ResultLineKind.choices)
170 group = models.CharField(max_length=64, blank=True)
171 label = models.CharField(max_length=160)
172 row_key = models.CharField(max_length=128)
173 amount = models.DecimalField(max_digits=18, decimal_places=4, null=True, blank=True)
174 unit = models.CharField(max_length=32, blank=True)
175 source_costable_item = models.ForeignKey(
176 "CostableItem",
177 on_delete=models.SET_NULL,
178 related_name="result_lines",
179 null=True,
180 blank=True,
181 )
182 source_cost_curve = models.ForeignKey(
183 "CostCurve",
184 on_delete=models.SET_NULL,
185 related_name="result_lines",
186 null=True,
187 blank=True,
188 )
189 source_capital_line = models.ForeignKey(
190 "CapitalCostLine",
191 on_delete=models.SET_NULL,
192 related_name="result_lines",
193 null=True,
194 blank=True,
195 )
196 source_operating_line = models.ForeignKey(
197 "OperatingCostLine",
198 on_delete=models.SET_NULL,
199 related_name="result_lines",
200 null=True,
201 blank=True,
202 )
203 source_index_value = models.ForeignKey(
204 "CostIndexValue",
205 on_delete=models.SET_NULL,
206 related_name="result_lines",
207 null=True,
208 blank=True,
209 )
210 source_property_info = models.ForeignKey(
211 "core_auxiliary.PropertyInfo",
212 on_delete=models.SET_NULL,
213 related_name="economics_result_lines",
214 null=True,
215 blank=True,
216 )
217 source_row_key = models.CharField(max_length=160, blank=True)
218 source_label = models.CharField(max_length=160, blank=True)
219 source_note = models.TextField(blank=True)
220 resource_source_kind = models.CharField(max_length=64, blank=True)
221 resource_source_object_name = models.CharField(max_length=160, blank=True)
222 resource_source_object_type = models.CharField(max_length=64, blank=True)
223 resource_property_name = models.CharField(max_length=160, blank=True)
224 resource_breakdown_category = models.CharField(max_length=64, blank=True)
225 resource_classification = models.CharField(max_length=64, blank=True, default="")
226 resource_metric_quantity = models.DecimalField(max_digits=24, decimal_places=8, null=True, blank=True)
227 resource_metric_unit = models.CharField(max_length=32, blank=True, default="")
228 annual_basis_quantity = models.DecimalField(max_digits=24, decimal_places=8, null=True, blank=True)
229 annual_basis_unit = models.CharField(max_length=32, blank=True)
230 warning_payload = models.JSONField(default=dict, blank=True)
231 sort_order = models.PositiveIntegerField(default=0)
232 created_at = models.DateTimeField(auto_now_add=True)
234 objects = AccessControlManager()
235 all_states = AllFlowsheetStatesManager()
237 class Meta:
238 ordering = ["sort_order", "created_at"]
239 constraints = [
240 models.UniqueConstraint(
241 fields=["result_run", "row_key"],
242 name="unique_result_line_key_per_run",
243 ),
244 ]
246 def __str__(self):
247 return self.label
249class EconomicsChartDataset(FlowsheetScopedEconomicsModel):
250 same_flowsheet_fields = ("result_run",)
252 flowsheet_state = models.ForeignKey("core_auxiliary.FlowsheetState", on_delete=models.CASCADE, related_name="economics_chart_datasets")
253 result_run = models.ForeignKey("EconomicsResultRun", on_delete=models.CASCADE, related_name="chart_datasets")
254 chart_key = models.CharField(max_length=128)
255 title = models.CharField(max_length=160)
256 chart_type = models.CharField(max_length=64)
257 source_row_keys = models.JSONField(default=list, blank=True)
258 chart_data = models.JSONField(default=dict, blank=True)
259 rendering_metadata = models.JSONField(default=dict, blank=True)
260 created_at = models.DateTimeField(auto_now_add=True)
262 objects = AccessControlManager()
263 all_states = AllFlowsheetStatesManager()
265 class Meta:
266 ordering = ["created_at"]
267 constraints = [
268 models.UniqueConstraint(
269 fields=["result_run", "chart_key"],
270 name="unique_chart_dataset_key_per_run",
271 ),
272 ]
274 def __str__(self):
275 return self.title