This commit is contained in:
2026-04-25 17:22:41 +03:30
parent 569d520a5c
commit aa24fc22b0
124 changed files with 8491 additions and 2582 deletions
+81 -2
View File
@@ -11,6 +11,8 @@ from irrigation.models import IrrigationMethod
from plant.models import Plant
from weather.models import WeatherForecast
from farm_data.services import resolve_center_location_from_boundary
def square_boundary_for_center(lat: float, lon: float, delta: float = 0.01) -> dict:
return {
@@ -93,7 +95,8 @@ class FarmDetailApiTests(TestCase):
metric_sources = payload["soil"]["metric_sources"]
self.assertEqual(resolved_metrics["nitrogen"], 99.0)
self.assertEqual(metric_sources["nitrogen"], "sensor")
self.assertEqual(metric_sources["nitrogen"]["type"], "sensor")
self.assertEqual(metric_sources["nitrogen"]["strategy"], "single_value")
self.assertEqual(resolved_metrics["clay"], 22.0)
self.assertEqual(metric_sources["clay"], "soil")
self.assertEqual(len(payload["soil"]["depths"]), 2)
@@ -112,6 +115,38 @@ class FarmDetailApiTests(TestCase):
self.assertEqual(response.status_code, 404)
self.assertEqual(response.json()["msg"], "farm یافت نشد.")
def test_aggregates_conflicting_metrics_from_multiple_sensors_without_overwrite(self):
self.farm.sensor_payload = {
"sensor-a": {
"soil_moisture": 20.0,
"nitrogen": 90.0,
"status": "ok",
},
"sensor-b": {
"soil_moisture": 40.0,
"nitrogen": 110.0,
"status": "needs-check",
},
}
self.farm.save(update_fields=["sensor_payload"])
response = self.client.get(f"/api/farm-data/{self.farm_uuid}/detail/")
self.assertEqual(response.status_code, 200)
payload = response.json()["data"]
resolved_metrics = payload["soil"]["resolved_metrics"]
metric_sources = payload["soil"]["metric_sources"]
self.assertEqual(resolved_metrics["soil_moisture"], 30.0)
self.assertEqual(metric_sources["soil_moisture"]["strategy"], "average")
self.assertCountEqual(
metric_sources["soil_moisture"]["sensor_keys"],
["sensor-a", "sensor-b"],
)
self.assertEqual(metric_sources["soil_moisture"]["distinct_values"], [20.0, 40.0])
self.assertEqual(resolved_metrics["status"], ["ok", "needs-check"])
self.assertEqual(metric_sources["status"]["strategy"], "distinct_values")
class FarmDataUpsertApiTests(TestCase):
def setUp(self):
@@ -120,6 +155,21 @@ class FarmDataUpsertApiTests(TestCase):
latitude="35.710000",
longitude="51.410000",
)
SoilDepthData.objects.create(
soil_location=self.location,
depth_label="0-5cm",
clay=20.0,
)
SoilDepthData.objects.create(
soil_location=self.location,
depth_label="5-15cm",
clay=18.0,
)
SoilDepthData.objects.create(
soil_location=self.location,
depth_label="15-30cm",
clay=16.0,
)
self.boundary = square_boundary_for_center(35.71, 51.41)
self.weather = WeatherForecast.objects.create(
location=self.location,
@@ -176,7 +226,16 @@ class FarmDataUpsertApiTests(TestCase):
self.assertEqual(response.status_code, 400)
self.assertIn("farm_uuid", response.json()["data"])
def test_post_creates_center_location_from_boundary_when_missing(self):
@patch("farm_data.services.update_weather_for_location", return_value={"status": "no_data"})
@patch(
"farm_data.services.fetch_soil_data_for_coordinates",
return_value={"status": "completed", "depths": []},
)
def test_post_creates_center_location_from_boundary_when_missing(
self,
_mock_fetch_soil_data_for_coordinates,
_mock_update_weather_for_location,
):
farm_uuid = uuid.uuid4()
response = self.client.post(
@@ -203,6 +262,26 @@ class FarmDataUpsertApiTests(TestCase):
self.assertEqual(str(farm.center_location.longitude), "50.010000")
self.assertIsNone(farm.weather_forecast_id)
def test_resolve_center_location_uses_geometric_centroid_for_concave_polygon(self):
location = resolve_center_location_from_boundary(
{
"corners": [
{"lat": 0.0, "lon": 0.0},
{"lat": 0.0, "lon": 4.0},
{"lat": 4.0, "lon": 4.0},
{"lat": 4.0, "lon": 0.0},
{"lat": 1.0, "lon": 0.0},
{"lat": 1.0, "lon": 3.0},
{"lat": 3.0, "lon": 3.0},
{"lat": 3.0, "lon": 1.0},
{"lat": 0.0, "lon": 1.0},
]
}
)
self.assertEqual(str(location.latitude), "2.078947")
self.assertEqual(str(location.longitude), "2.078947")
@patch("farm_data.services.update_weather_for_location")
@patch("farm_data.services.fetch_soil_data_for_coordinates")
def test_post_fetches_missing_location_and_weather_data(