Coverage for backend/django/Economics/results/services/comparison/contracts.py: 100%

99 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-07-22 05:22 +0000

1"""Typed service contracts for Economics study comparisons.""" 

2 

3from __future__ import annotations 

4 

5from datetime import datetime 

6from decimal import Decimal 

7from enum import StrEnum 

8 

9from pydantic import BaseModel, ConfigDict, Field, JsonValue 

10 

11from Economics.results.models import EconomicsResultRun 

12from Economics.results.services.financial_metrics.contracts import DiscountedCashFlowRow 

13from Economics.results.services.financial_metrics.metric_catalog import MetricComparisonDirection 

14from Economics.shared.choices import EconomicsBaselineMode 

15from Economics.studies.models import EconomicsStudy 

16 

17 

18class EconomicsComparisonContract(BaseModel): 

19 """Base model for typed study comparison service contracts.""" 

20 

21 model_config = ConfigDict(arbitrary_types_allowed=True) 

22 

23 

24class ComparisonTarget(EconomicsComparisonContract): 

25 """Resolved or saved comparison study metadata used during evaluation.""" 

26 

27 study_id: int | None 

28 flowsheet_id: int | None 

29 name: str 

30 flowsheet_name: str 

31 availability_status: str 

32 study: EconomicsStudy | None = None 

33 

34 

35class ComparisonAvailabilityStatus(StrEnum): 

36 AVAILABLE = "available" 

37 UNAVAILABLE = "unavailable" 

38 

39 

40class ComparisonStatus(StrEnum): 

41 BASELINE = "baseline" 

42 BASELINE_NOT_APPLICABLE = "baseline_not_applicable" 

43 COMPARABLE = "comparable" 

44 MISSING_BASELINE = "missing_baseline" 

45 MISSING_METRIC = "missing_metric" 

46 MISSING_RESULT = "missing_result" 

47 RECALCULATION_UNAVAILABLE = "recalculation_unavailable" 

48 UNAVAILABLE = "unavailable" 

49 UNIT_MISMATCH = "unit_mismatch" 

50 ZERO_BASELINE = "zero_baseline" 

51 

52 

53class ComparisonValidationError(ValueError): 

54 """Configuration validation error that maps cleanly to serializer errors.""" 

55 

56 

57class EconomicsStudyComparisonRequest(EconomicsComparisonContract): 

58 """Full saved comparison configuration replacement.""" 

59 

60 comparison_study_ids: list[int] = Field(min_length=1, max_length=10) 

61 baseline_mode: EconomicsBaselineMode = EconomicsBaselineMode.STUDY 

62 baseline_study_id: int | None = None 

63 

64 

65class WarningSummaryPayload(EconomicsComparisonContract): 

66 code: str 

67 severity: str 

68 message: str 

69 context: dict[str, JsonValue] | None = None 

70 

71 

72class ComparisonConfigPayload(EconomicsComparisonContract): 

73 id: int | None 

74 active_study: int 

75 baseline_mode: EconomicsBaselineMode 

76 baseline_study_id: int 

77 comparison_study_ids: list[int] 

78 updated_at: datetime | None 

79 

80 

81class ComparisonStudyPayload(EconomicsComparisonContract): 

82 id: int | None 

83 name: str 

84 flowsheet_id: int | None 

85 flowsheet_name: str 

86 result_run_id: int | None 

87 result_status: str 

88 result_currency: str 

89 availability_status: str 

90 

91 

92class ComparisonChartDatasetPayload(EconomicsComparisonContract): 

93 """Chart dataset assembled for study-comparison visualizations.""" 

94 

95 id: int 

96 chart_key: str 

97 title: str 

98 chart_type: str 

99 source_row_keys: list[str] 

100 chart_data: dict[str, JsonValue] 

101 rendering_metadata: dict[str, JsonValue] 

102 

103 

104class ComparisonValuePayload(EconomicsComparisonContract): 

105 study_id: int | None 

106 amount: str | None 

107 unit: str 

108 percentage_difference: str | None 

109 comparison_status: ComparisonStatus 

110 is_best: bool = False 

111 

112 

113class ComparisonRowPayload(EconomicsComparisonContract): 

114 row_key: str 

115 label: str 

116 comparison_direction: MetricComparisonDirection 

117 maximum_fraction_digits: int 

118 values: list[ComparisonValuePayload] 

119 

120 

121class ComparisonMetricValue(EconomicsComparisonContract): 

122 amount: Decimal | None 

123 unit: str 

124 comparison_status: ComparisonStatus | None = None 

125 

126 

127class ComparisonPayload(EconomicsComparisonContract): 

128 active_study_id: int 

129 baseline_mode: EconomicsBaselineMode 

130 baseline_study_id: int 

131 config: ComparisonConfigPayload 

132 studies: list[ComparisonStudyPayload] 

133 rows: list[ComparisonRowPayload] 

134 resource_rows: list[ComparisonRowPayload] 

135 chart_datasets: list[ComparisonChartDatasetPayload] 

136 warnings: list[WarningSummaryPayload] 

137 

138 

139class ResultContext(EconomicsComparisonContract): 

140 run: EconomicsResultRun 

141 classification: str 

142 lines: dict[str, ComparisonMetricValue] 

143 discounted_cash_flow: tuple[DiscountedCashFlowRow, ...] = ()