KSvend Claude Happy commited on
Commit
1b15a5f
·
1 Parent(s): 934a3a1

feat: add season_start/season_end to JobRequest with wrap support

Browse files

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

Files changed (2) hide show
  1. app/models.py +13 -0
  2. tests/test_models.py +63 -0
app/models.py CHANGED
@@ -93,6 +93,19 @@ class JobRequest(BaseModel):
93
  time_range: TimeRange = Field(default_factory=TimeRange)
94
  indicator_ids: list[str]
95
  email: str
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
  @field_validator("indicator_ids")
98
  @classmethod
 
93
  time_range: TimeRange = Field(default_factory=TimeRange)
94
  indicator_ids: list[str]
95
  email: str
96
+ season_start: int = Field(default=1, ge=1, le=12)
97
+ season_end: int = Field(default=12, ge=1, le=12)
98
+
99
+ def season_months(self) -> list[int]:
100
+ """Return ordered list of month numbers in the analysis season.
101
+
102
+ Supports year-boundary wrapping: season_start=10, season_end=3
103
+ yields [10, 11, 12, 1, 2, 3].
104
+ """
105
+ if self.season_start <= self.season_end:
106
+ return list(range(self.season_start, self.season_end + 1))
107
+ else:
108
+ return list(range(self.season_start, 13)) + list(range(1, self.season_end + 1))
109
 
110
  @field_validator("indicator_ids")
111
  @classmethod
tests/test_models.py CHANGED
@@ -86,3 +86,66 @@ def test_indicator_result_fields():
86
  )
87
  assert result.status == "amber"
88
  assert result.trend == "deteriorating"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  )
87
  assert result.status == "amber"
88
  assert result.trend == "deteriorating"
89
+
90
+
91
+ def test_job_request_season_defaults():
92
+ """Default season is full year (1-12)."""
93
+ req = JobRequest(
94
+ aoi=AOI(name="Test", bbox=[36.75, -1.35, 36.95, -1.20]),
95
+ indicator_ids=["fires"],
96
+ email="t@t.com",
97
+ )
98
+ assert req.season_start == 1
99
+ assert req.season_end == 12
100
+
101
+
102
+ def test_job_request_season_months_normal():
103
+ """Non-wrapping season: Apr-Sep."""
104
+ req = JobRequest(
105
+ aoi=AOI(name="Test", bbox=[36.75, -1.35, 36.95, -1.20]),
106
+ indicator_ids=["fires"],
107
+ email="t@t.com",
108
+ season_start=4,
109
+ season_end=9,
110
+ )
111
+ assert req.season_months() == [4, 5, 6, 7, 8, 9]
112
+
113
+
114
+ def test_job_request_season_months_wrapping():
115
+ """Wrapping season: Oct-Mar (Southern Hemisphere)."""
116
+ req = JobRequest(
117
+ aoi=AOI(name="Test", bbox=[36.75, -1.35, 36.95, -1.20]),
118
+ indicator_ids=["fires"],
119
+ email="t@t.com",
120
+ season_start=10,
121
+ season_end=3,
122
+ )
123
+ assert req.season_months() == [10, 11, 12, 1, 2, 3]
124
+
125
+
126
+ def test_job_request_season_months_full_year():
127
+ """Full year default."""
128
+ req = JobRequest(
129
+ aoi=AOI(name="Test", bbox=[36.75, -1.35, 36.95, -1.20]),
130
+ indicator_ids=["fires"],
131
+ email="t@t.com",
132
+ )
133
+ assert req.season_months() == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
134
+
135
+
136
+ def test_job_request_season_validation():
137
+ """Season months must be 1-12."""
138
+ with pytest.raises(Exception):
139
+ JobRequest(
140
+ aoi=AOI(name="Test", bbox=[36.75, -1.35, 36.95, -1.20]),
141
+ indicator_ids=["fires"],
142
+ email="t@t.com",
143
+ season_start=0,
144
+ )
145
+ with pytest.raises(Exception):
146
+ JobRequest(
147
+ aoi=AOI(name="Test", bbox=[36.75, -1.35, 36.95, -1.20]),
148
+ indicator_ids=["fires"],
149
+ email="t@t.com",
150
+ season_end=13,
151
+ )