Coverage for backend/django/Economics/scheduling/composite/contracts.py: 100%

64 statements  

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

1"""Typed contracts for economics production-schedule composition.""" 

2 

3from __future__ import annotations 

4 

5from datetime import time 

6from decimal import Decimal 

7 

8from pydantic import BaseModel, ConfigDict, Field, field_validator 

9 

10from Economics.scheduling.composite.domain import ( 

11 COMPOSITE_SCHEDULE_LABEL_MAX_LENGTH, 

12 COMPOSITE_SCHEDULE_RULE_MAX_COUNT, 

13 ScheduleDiagnosticCode, 

14 ScheduleDiagnosticSeverity, 

15 ScheduleTimelineSourceKind, 

16 ScheduleValidationStatus, 

17 ScheduleWeekday, 

18) 

19 

20 

21class ScheduleContract(BaseModel): 

22 """Base contract for schedule services and API payloads.""" 

23 

24 model_config = ConfigDict(extra="forbid", frozen=True) 

25 

26 

27class CompositeScheduleRuleDraft(ScheduleContract): 

28 """One complete replacement rule in a composite schedule draft.""" 

29 

30 source_scenario: int = Field(gt=0) 

31 label: str = Field(default="", max_length=COMPOSITE_SCHEDULE_LABEL_MAX_LENGTH) 

32 days_mask: int = Field(ge=1, le=127) 

33 start_time: time 

34 sort_order: int = Field(default=0, ge=0) 

35 

36 @field_validator("label") 

37 @classmethod 

38 def _trim_label(cls, value: str) -> str: 

39 return value.strip() 

40 

41 

42class CompositeSchedulePlanRequest(ScheduleContract): 

43 """Full composite schedule replacement request.""" 

44 

45 model_config = ConfigDict(extra="ignore", frozen=True) 

46 

47 rules: list[CompositeScheduleRuleDraft] = Field(min_length=1, max_length=COMPOSITE_SCHEDULE_RULE_MAX_COUNT) 

48 

49 

50class ScheduleCoverageDiagnostic(ScheduleContract): 

51 """One validation or informational diagnostic for a schedule draft.""" 

52 

53 code: ScheduleDiagnosticCode 

54 severity: ScheduleDiagnosticSeverity 

55 message: str 

56 rule_indices: list[int] = Field(default_factory=list) 

57 

58 

59class ScheduleSourceSummary(ScheduleContract): 

60 """Selectable source scenario metadata used by schedule payloads.""" 

61 

62 id: int 

63 name: str 

64 row_count: int 

65 interval_value: int 

66 interval_unit: str 

67 interval_hours: Decimal 

68 schedule_length_hours: Decimal 

69 

70 

71class CompositeScheduleRulePayload(ScheduleContract): 

72 """Saved or previewed schedule rule returned to the frontend.""" 

73 

74 id: int | None = None 

75 source_scenario: int 

76 source_scenario_name: str 

77 label: str 

78 days_mask: int 

79 start_time: time 

80 sort_order: int 

81 

82 

83class CompiledScheduleStep(ScheduleContract): 

84 """One compiled weekly timetable block, including off periods.""" 

85 

86 index: int 

87 day: ScheduleWeekday 

88 elapsed_hours: Decimal 

89 start_time: time 

90 end_time: time 

91 duration_hours: Decimal 

92 source_kind: ScheduleTimelineSourceKind 

93 source_scenario: int | None = None 

94 source_scenario_name: str = "" 

95 source_rule: int | None = None 

96 source_rule_label: str = "" 

97 source_row_index: int | None = None 

98 source_row_elapsed_hours: Decimal | None = None 

99 source_row_duration_hours: Decimal | None = None 

100 

101 

102class CompositeSchedulePlanPayload(ScheduleContract): 

103 """Composite schedule payload used by GET, PUT, and preview actions.""" 

104 

105 study_id: int 

106 mode: str 

107 status: ScheduleValidationStatus 

108 rules: list[CompositeScheduleRulePayload] 

109 diagnostics: list[ScheduleCoverageDiagnostic] 

110 timeline: list[CompiledScheduleStep] 

111 sources: list[ScheduleSourceSummary]