Files
Ai/location_data/test_remote_sensing_tasks.py
T
2026-05-11 00:36:02 +03:30

182 lines
6.9 KiB
Python

from datetime import date
from unittest.mock import Mock, patch
from django.test import TestCase
from location_data.models import AnalysisGridCell, AnalysisGridObservation, RemoteSensingRun, SoilLocation
from location_data.tasks import _upsert_grid_observations, run_remote_sensing_analysis
class RemoteSensingTaskDiagnosticsTests(TestCase):
def setUp(self):
self.boundary = {
"type": "Polygon",
"coordinates": [
[
[51.3890, 35.6890],
[51.3900, 35.6890],
[51.3900, 35.6900],
[51.3890, 35.6900],
[51.3890, 35.6890],
]
],
}
self.location = SoilLocation.objects.create(
latitude="35.689200",
longitude="51.389000",
farm_boundary=self.boundary,
)
self.run = RemoteSensingRun.objects.create(
soil_location=self.location,
block_code="",
chunk_size_sqm=900,
temporal_start=date(2026, 4, 9),
temporal_end=date(2026, 5, 9),
status=RemoteSensingRun.STATUS_PENDING,
metadata={},
)
self.cell = AnalysisGridCell.objects.create(
soil_location=self.location,
block_code="",
cell_code="cell-1",
chunk_size_sqm=900,
geometry=self.boundary,
centroid_lat="35.689200",
centroid_lon="51.389200",
)
def test_upsert_logs_and_stores_diagnostics_for_empty_observations(self):
metric_payload = {
"results": {},
"metadata": {
"backend": "openeo",
"backend_url": "https://openeofed.dataspace.copernicus.eu",
"collections_used": ["SENTINEL2_L2A"],
"job_refs": {"ndvi": "job-1"},
"failed_metrics": [],
"payload_diagnostics": {
"ndvi": {
"returned_cell_count": 0,
"payload_keys_sample": [],
"available_features": ["mean"],
}
},
},
}
with self.assertLogs("location_data.tasks", level="WARNING") as captured:
summary = _upsert_grid_observations(
cells=[self.cell],
run=self.run,
temporal_start=date(2026, 4, 9),
temporal_end=date(2026, 5, 9),
metric_payload=metric_payload,
)
log_output = "\n".join(captured.output)
self.assertIn("Persisting empty observation for cell=cell-1, run_id=", log_output)
self.assertIn("No payload cells matched DB cell_codes for run_id=", log_output)
self.assertIn("All persisted observations are empty for run_id=", log_output)
self.assertEqual(summary["total_observation_count"], 1)
self.assertEqual(summary["usable_observation_count"], 0)
self.assertEqual(summary["fully_null_observation_count"], 1)
self.assertEqual(summary["matched_cell_count"], 0)
self.assertEqual(summary["payload_keys_sample"], [])
self.assertEqual(summary["available_features"], ["mean"])
observation = AnalysisGridObservation.objects.get(cell=self.cell)
self.assertIsNone(observation.ndvi)
self.assertIsNone(observation.ndwi)
self.assertIsNone(observation.soil_vv)
self.assertIsNone(observation.soil_vv_db)
self.run.refresh_from_db()
diagnostics = self.run.metadata["diagnostics"]["empty_observations"]
self.assertEqual(diagnostics["job_ref"], {"ndvi": "job-1"})
self.assertEqual(diagnostics["total_cells"], 1)
self.assertEqual(diagnostics["matched_cells"], 0)
self.assertEqual(diagnostics["payload_keys_sample"], [])
self.assertEqual(diagnostics["available_features"], ["mean"])
def test_run_remote_sensing_analysis_refetches_when_cached_observations_are_empty(self):
AnalysisGridObservation.objects.create(
cell=self.cell,
run=self.run,
temporal_start=date(2026, 4, 9),
temporal_end=date(2026, 5, 9),
metadata={},
)
subdivision_result = Mock(
id=99,
cluster_count=1,
selected_features=["ndvi", "ndwi", "soil_vv_db"],
metadata={"used_cell_count": 1, "skipped_cell_count": 0, "kmeans_params": {}},
skipped_cell_codes=[],
)
remote_payload = {
"results": {
"cell-1": {
"ndvi": 0.52,
"ndwi": 0.21,
"soil_vv": 10.0,
"soil_vv_db": 10.0,
}
},
"metadata": {
"backend": "openeo",
"backend_url": "https://openeofed.dataspace.copernicus.eu",
"collections_used": ["SENTINEL2_L2A", "SENTINEL1_GRD"],
"job_refs": {"ndvi": "job-1"},
"failed_metrics": [],
"payload_diagnostics": {
"ndvi": {
"returned_cell_count": 1,
"payload_keys_sample": ["0"],
"available_features": ["mean"],
}
},
},
}
with patch(
"location_data.tasks.create_or_get_analysis_grid_cells",
return_value={
"created": False,
"block_code": "",
"total_count": 1,
"created_count": 0,
"chunk_size_sqm": 900,
"existing_count": 1,
},
), patch(
"location_data.tasks.compute_remote_sensing_metrics",
return_value=remote_payload,
) as compute_mock, patch(
"location_data.tasks._ensure_subdivision_result",
return_value=subdivision_result,
):
summary = run_remote_sensing_analysis(
soil_location_id=self.location.id,
block_code="",
temporal_start=date(2026, 4, 9),
temporal_end=date(2026, 5, 9),
run_id=self.run.id,
)
compute_mock.assert_called_once()
self.assertEqual(summary["source"], "openeo")
self.assertEqual(summary["processed_cell_count"], 1)
observation = AnalysisGridObservation.objects.get(cell=self.cell)
self.assertEqual(observation.ndvi, 0.52)
self.assertEqual(observation.ndwi, 0.21)
self.assertEqual(observation.soil_vv, 10.0)
self.assertEqual(observation.soil_vv_db, 10.0)
self.run.refresh_from_db()
cached_details = self.run.metadata["stage_details"]["using_cached_observations"]
self.assertEqual(cached_details["source"], "database")
self.assertFalse(cached_details["usable"])
self.assertTrue(cached_details["refetching"])