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

54 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-06-23 21:51 +0000

1from django.core.exceptions import ValidationError 

2from django.db import models 

3 

4from core.managers import AccessControlManager 

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 = models.ForeignKey( 

14 "core_auxiliary.Flowsheet", 

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 

44 class Meta: 

45 ordering = ["metric_key"] 

46 constraints = [ 

47 models.UniqueConstraint( 

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

49 name="unique_metric_formula_per_study", 

50 ), 

51 ] 

52 

53 def __str__(self): 

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

55 

56class EconomicsLineFormula(FlowsheetScopedEconomicsModel): 

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

58 

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

60 

61 flowsheet = models.ForeignKey( 

62 "core_auxiliary.Flowsheet", 

63 on_delete=models.CASCADE, 

64 related_name="economics_line_formulas", 

65 ) 

66 study = models.ForeignKey( 

67 "EconomicsStudy", 

68 on_delete=models.CASCADE, 

69 related_name="line_formulas", 

70 ) 

71 property_value = models.ForeignKey( 

72 "core_auxiliary.PropertyValue", 

73 on_delete=models.SET_NULL, 

74 related_name="economics_line_formulas", 

75 null=True, 

76 blank=True, 

77 ) 

78 capital_line = models.ForeignKey( 

79 "CapitalCostLine", 

80 on_delete=models.CASCADE, 

81 related_name="formula_records", 

82 null=True, 

83 blank=True, 

84 ) 

85 operating_line = models.ForeignKey( 

86 "OperatingCostLine", 

87 on_delete=models.CASCADE, 

88 related_name="formula_records", 

89 null=True, 

90 blank=True, 

91 ) 

92 line_key = models.CharField(max_length=128) 

93 formula_key = models.CharField(max_length=128) 

94 formula = models.TextField(blank=True) 

95 property_formula = models.TextField(blank=True) 

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

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

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

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

100 blocked_reason = models.TextField(blank=True) 

101 created_at = models.DateTimeField(auto_now_add=True) 

102 updated_at = models.DateTimeField(auto_now=True) 

103 

104 objects = AccessControlManager() 

105 

106 class Meta: 

107 ordering = ["line_key"] 

108 constraints = [ 

109 models.UniqueConstraint( 

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

111 name="unique_line_formula_per_study", 

112 ), 

113 ] 

114 

115 def clean(self): 

116 super().clean() 

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

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

119 

120 def __str__(self): 

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