Coverage for backend/django/Economics/formulas/models.py: 93%

56 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.model_base import FlowsheetScopedEconomicsModel 

6 

7 

8class EconomicsMetricFormula(FlowsheetScopedEconomicsModel): 

9 """Persist the formula envelope used to calculate one study financial metric.""" 

10 

11 same_flowsheet_fields = ("study", "property_value") 

12 

13 flowsheet_state = models.ForeignKey( 

14 "core_auxiliary.FlowsheetState", 

15 on_delete=models.CASCADE, 

16 related_name="economics_metric_formulas", 

17 ) 

18 study = models.ForeignKey( 

19 "EconomicsStudy", 

20 on_delete=models.CASCADE, 

21 related_name="metric_formulas", 

22 ) 

23 property_value = models.ForeignKey( 

24 "core_auxiliary.PropertyValue", 

25 on_delete=models.SET_NULL, 

26 related_name="economics_metric_formulas", 

27 null=True, 

28 blank=True, 

29 ) 

30 metric_key = models.CharField(max_length=96) 

31 formula_key = models.CharField(max_length=128) 

32 formula = models.TextField(blank=True) 

33 property_formula = models.TextField(blank=True) 

34 unit = models.CharField(max_length=64, blank=True) 

35 value = models.CharField(max_length=128, null=True, blank=True) 

36 status = models.CharField(max_length=32, default="calculated") 

37 formula_audit = models.JSONField(default=dict, blank=True) 

38 blocked_reason = models.TextField(blank=True) 

39 created_at = models.DateTimeField(auto_now_add=True) 

40 updated_at = models.DateTimeField(auto_now=True) 

41 

42 objects = AccessControlManager() 

43 all_states = AllFlowsheetStatesManager() 

44 

45 class Meta: 

46 ordering = ["metric_key"] 

47 constraints = [ 

48 models.UniqueConstraint( 

49 fields=["study", "metric_key"], 

50 name="unique_metric_formula_per_study", 

51 ), 

52 ] 

53 

54 def __str__(self): 

55 return f"{self.study.name} {self.metric_key}" 

56 

57class EconomicsLineFormula(FlowsheetScopedEconomicsModel): 

58 """Persist the formula envelope used to calculate one economics line property.""" 

59 

60 same_flowsheet_fields = ("study", "property_value", "capital_line", "operating_line") 

61 

62 flowsheet_state = models.ForeignKey( 

63 "core_auxiliary.FlowsheetState", 

64 on_delete=models.CASCADE, 

65 related_name="economics_line_formulas", 

66 ) 

67 study = models.ForeignKey( 

68 "EconomicsStudy", 

69 on_delete=models.CASCADE, 

70 related_name="line_formulas", 

71 ) 

72 property_value = models.ForeignKey( 

73 "core_auxiliary.PropertyValue", 

74 on_delete=models.SET_NULL, 

75 related_name="economics_line_formulas", 

76 null=True, 

77 blank=True, 

78 ) 

79 capital_line = models.ForeignKey( 

80 "CapitalCostLine", 

81 on_delete=models.CASCADE, 

82 related_name="formula_records", 

83 null=True, 

84 blank=True, 

85 ) 

86 operating_line = models.ForeignKey( 

87 "OperatingCostLine", 

88 on_delete=models.CASCADE, 

89 related_name="formula_records", 

90 null=True, 

91 blank=True, 

92 ) 

93 line_key = models.CharField(max_length=128) 

94 formula_key = models.CharField(max_length=128) 

95 formula = models.TextField(blank=True) 

96 property_formula = models.TextField(blank=True) 

97 unit = models.CharField(max_length=64, blank=True) 

98 value = models.CharField(max_length=128, null=True, blank=True) 

99 status = models.CharField(max_length=32, default="calculated") 

100 formula_audit = models.JSONField(default=dict, blank=True) 

101 blocked_reason = models.TextField(blank=True) 

102 created_at = models.DateTimeField(auto_now_add=True) 

103 updated_at = models.DateTimeField(auto_now=True) 

104 

105 objects = AccessControlManager() 

106 all_states = AllFlowsheetStatesManager() 

107 

108 class Meta: 

109 ordering = ["line_key"] 

110 constraints = [ 

111 models.UniqueConstraint( 

112 fields=["study", "line_key"], 

113 name="unique_line_formula_per_study", 

114 ), 

115 ] 

116 

117 def clean(self): 

118 super().clean() 

119 if bool(self.capital_line_id) == bool(self.operating_line_id): 119 ↛ 120line 119 didn't jump to line 120 because the condition on line 119 was never true

120 raise ValidationError({"line_key": "Line formula records must reference exactly one economics line."}) 

121 

122 def __str__(self): 

123 return f"{self.study.name} {self.line_key}"