KSvend Claude Happy commited on
Commit
b7f7fb5
·
1 Parent(s): e9494fc

feat: implement NdviIndicator.submit_batch() for openEO batch jobs

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>

app/indicators/ndvi.py CHANGED
@@ -25,7 +25,7 @@ from app.models import (
25
  TrendDirection,
26
  ConfidenceLevel,
27
  )
28
- from app.openeo_client import get_connection, build_ndvi_graph, build_true_color_graph, _bbox_dict
29
 
30
  logger = logging.getLogger(__name__)
31
 
@@ -40,6 +40,49 @@ class NdviIndicator(BaseIndicator):
40
  estimated_minutes = 8
41
 
42
  _true_color_path: str | None = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
  async def process(
45
  self, aoi: AOI, time_range: TimeRange, season_months: list[int] | None = None
 
25
  TrendDirection,
26
  ConfidenceLevel,
27
  )
28
+ from app.openeo_client import get_connection, build_ndvi_graph, build_true_color_graph, _bbox_dict, submit_as_batch
29
 
30
  logger = logging.getLogger(__name__)
31
 
 
40
  estimated_minutes = 8
41
 
42
  _true_color_path: str | None = None
43
+ uses_batch = True
44
+
45
+ async def submit_batch(
46
+ self, aoi: AOI, time_range: TimeRange, season_months: list[int] | None = None
47
+ ) -> list:
48
+ conn = get_connection()
49
+ bbox = _bbox_dict(aoi.bbox)
50
+
51
+ current_start = time_range.start.isoformat()
52
+ current_end = time_range.end.isoformat()
53
+
54
+ baseline_start = date(
55
+ time_range.start.year - BASELINE_YEARS,
56
+ time_range.start.month,
57
+ time_range.start.day,
58
+ ).isoformat()
59
+ baseline_end = date(
60
+ time_range.start.year,
61
+ time_range.start.month,
62
+ time_range.start.day,
63
+ ).isoformat()
64
+
65
+ current_cube = build_ndvi_graph(
66
+ conn=conn, bbox=bbox,
67
+ temporal_extent=[current_start, current_end],
68
+ resolution_m=RESOLUTION_M,
69
+ )
70
+ baseline_cube = build_ndvi_graph(
71
+ conn=conn, bbox=bbox,
72
+ temporal_extent=[baseline_start, baseline_end],
73
+ resolution_m=RESOLUTION_M,
74
+ )
75
+ true_color_cube = build_true_color_graph(
76
+ conn=conn, bbox=bbox,
77
+ temporal_extent=[current_start, current_end],
78
+ resolution_m=RESOLUTION_M,
79
+ )
80
+
81
+ return [
82
+ submit_as_batch(conn, current_cube, f"ndvi-current-{aoi.name}"),
83
+ submit_as_batch(conn, baseline_cube, f"ndvi-baseline-{aoi.name}"),
84
+ submit_as_batch(conn, true_color_cube, f"ndvi-truecolor-{aoi.name}"),
85
+ ]
86
 
87
  async def process(
88
  self, aoi: AOI, time_range: TimeRange, season_months: list[int] | None = None
tests/test_indicator_ndvi.py CHANGED
@@ -125,3 +125,29 @@ def test_ndvi_compute_stats():
125
  assert 0 < stats["overall_mean"] < 1
126
  assert "valid_months" in stats
127
  assert stats["valid_months"] == 12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  assert 0 < stats["overall_mean"] < 1
126
  assert "valid_months" in stats
127
  assert stats["valid_months"] == 12
128
+
129
+
130
+ @pytest.mark.asyncio
131
+ async def test_ndvi_submit_batch_creates_three_jobs(test_aoi, test_time_range):
132
+ """submit_batch() creates current, baseline, and true-color batch jobs."""
133
+ from app.indicators.ndvi import NdviIndicator
134
+
135
+ indicator = NdviIndicator()
136
+
137
+ mock_conn = MagicMock()
138
+ mock_job = MagicMock()
139
+ mock_job.job_id = "j-test"
140
+ mock_conn.create_job.return_value = mock_job
141
+
142
+ with patch("app.indicators.ndvi.get_connection", return_value=mock_conn), \
143
+ patch("app.indicators.ndvi.build_ndvi_graph") as mock_ndvi_graph, \
144
+ patch("app.indicators.ndvi.build_true_color_graph") as mock_tc_graph:
145
+
146
+ mock_ndvi_graph.return_value = MagicMock()
147
+ mock_tc_graph.return_value = MagicMock()
148
+
149
+ jobs = await indicator.submit_batch(test_aoi, test_time_range)
150
+
151
+ assert len(jobs) == 3
152
+ assert mock_ndvi_graph.call_count == 2 # current + baseline
153
+ assert mock_tc_graph.call_count == 1 # true-color