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

1from django.core.exceptions import ValidationError 

2from django.db import models 

3 

4from core.managers import AccessControlManager, AllFlowsheetStatesManager 

5from Economics.shared.choices import ResultDependencyType, ResultLineKind, ResultRunStatus 

6from Economics.shared.model_base import FlowsheetScopedEconomicsModel 

7 

8 

9class EconomicsResultRun(FlowsheetScopedEconomicsModel): 

10 same_flowsheet_fields = ("study",) 

11 

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) 

20 

21 objects = AccessControlManager() 

22 all_states = AllFlowsheetStatesManager() 

23 

24 class Meta: 

25 ordering = ["-created_at"] 

26 

27 def __str__(self): 

28 return f"{self.study.name} result run {self.pk or 'unsaved'}" 

29 

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 ) 

41 

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) 

130 

131 objects = AccessControlManager() 

132 all_states = AllFlowsheetStatesManager() 

133 

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 ] 

142 

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 ) 

154 

155 def __str__(self): 

156 return f"{self.dependency_type}:{self.dependency_key}" 

157 

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 ) 

166 

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) 

233 

234 objects = AccessControlManager() 

235 all_states = AllFlowsheetStatesManager() 

236 

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 ] 

245 

246 def __str__(self): 

247 return self.label 

248 

249class EconomicsChartDataset(FlowsheetScopedEconomicsModel): 

250 same_flowsheet_fields = ("result_run",) 

251 

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) 

261 

262 objects = AccessControlManager() 

263 all_states = AllFlowsheetStatesManager() 

264 

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 ] 

273 

274 def __str__(self): 

275 return self.title