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
« prev ^ index » next coverage.py v7.10.7, created at 2026-07-22 05:22 +0000
1"""Typed contracts for economics production-schedule composition."""
3from __future__ import annotations
5from datetime import time
6from decimal import Decimal
8from pydantic import BaseModel, ConfigDict, Field, field_validator
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)
21class ScheduleContract(BaseModel):
22 """Base contract for schedule services and API payloads."""
24 model_config = ConfigDict(extra="forbid", frozen=True)
27class CompositeScheduleRuleDraft(ScheduleContract):
28 """One complete replacement rule in a composite schedule draft."""
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)
36 @field_validator("label")
37 @classmethod
38 def _trim_label(cls, value: str) -> str:
39 return value.strip()
42class CompositeSchedulePlanRequest(ScheduleContract):
43 """Full composite schedule replacement request."""
45 model_config = ConfigDict(extra="ignore", frozen=True)
47 rules: list[CompositeScheduleRuleDraft] = Field(min_length=1, max_length=COMPOSITE_SCHEDULE_RULE_MAX_COUNT)
50class ScheduleCoverageDiagnostic(ScheduleContract):
51 """One validation or informational diagnostic for a schedule draft."""
53 code: ScheduleDiagnosticCode
54 severity: ScheduleDiagnosticSeverity
55 message: str
56 rule_indices: list[int] = Field(default_factory=list)
59class ScheduleSourceSummary(ScheduleContract):
60 """Selectable source scenario metadata used by schedule payloads."""
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
71class CompositeScheduleRulePayload(ScheduleContract):
72 """Saved or previewed schedule rule returned to the frontend."""
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
83class CompiledScheduleStep(ScheduleContract):
84 """One compiled weekly timetable block, including off periods."""
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
102class CompositeSchedulePlanPayload(ScheduleContract):
103 """Composite schedule payload used by GET, PUT, and preview actions."""
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]