Files
Ai/crop_simulation/test_growth_simulation_api.py
T

291 lines
11 KiB
Python
Raw Normal View History

2026-04-24 18:34:17 +03:30
from __future__ import annotations
from types import SimpleNamespace
from unittest.mock import patch
from django.test import TestCase, override_settings
from rest_framework.test import APIClient
from plant.models import Plant
from .growth_simulation import paginate_growth_stages, run_growth_simulation
@override_settings(ROOT_URLCONF="crop_simulation.urls")
class PlantGrowthSimulationApiTests(TestCase):
def setUp(self):
self.client = APIClient()
self.plant = Plant.objects.create(
name="گوجه‌فرنگی",
growth_profile={
"base_temperature": 10,
"required_gdd_for_maturity": 1200,
"current_cumulative_gdd": 50,
},
)
self.weather = [
{
"DAY": "2026-04-01",
"LAT": 35.7,
"LON": 51.4,
"TMIN": 12,
"TMAX": 24,
"RAIN": 0.0,
"ET0": 0.32,
},
{
"DAY": "2026-04-02",
"LAT": 35.7,
"LON": 51.4,
"TMIN": 13,
"TMAX": 25,
"RAIN": 0.0,
"ET0": 0.34,
},
{
"DAY": "2026-04-03",
"LAT": 35.7,
"LON": 51.4,
"TMIN": 14,
"TMAX": 27,
"RAIN": 1.0,
"ET0": 0.36,
},
]
def test_run_growth_simulation_returns_stage_timeline(self):
2026-04-25 17:22:41 +03:30
with patch("crop_simulation.growth_simulation._run_simulation") as mock_run_simulation:
mock_run_simulation.return_value = (
{
"engine": "pcse",
"model_name": "wofost",
"metrics": {"yield_estimate": 10.0},
"daily_output": [
{"DAY": "2026-04-01", "DVS": 0.1, "LAI": 0.2, "TAGP": 10.0},
{"DAY": "2026-04-02", "DVS": 0.3, "LAI": 0.4, "TAGP": 20.0},
{"DAY": "2026-04-03", "DVS": 1.1, "LAI": 0.6, "TAGP": 30.0},
],
},
12,
None,
)
result = run_growth_simulation(
{
"plant_name": self.plant.name,
"dynamic_parameters": ["DVS", "LAI", "TAGP"],
"weather": self.weather,
"soil_parameters": {"SMFCF": 0.34, "SMW": 0.14, "RDMSOL": 120.0},
"site_parameters": {"WAV": 40.0},
"page_size": 2,
}
)
2026-04-24 18:34:17 +03:30
self.assertEqual(result["plant_name"], self.plant.name)
self.assertGreaterEqual(result["daily_records_count"], 3)
self.assertTrue(result["stage_timeline"])
self.assertEqual(result["pagination"]["page_size"], 2)
@patch("crop_simulation.views.run_growth_simulation_task.delay")
def test_queue_api_returns_task_id(self, mock_delay):
mock_delay.return_value = SimpleNamespace(id="growth-task-1")
response = self.client.post(
"/growth/",
data={
"plant_name": self.plant.name,
"dynamic_parameters": ["DVS", "LAI"],
"weather": self.weather,
},
format="json",
)
self.assertEqual(response.status_code, 202)
self.assertEqual(response.json()["data"]["task_id"], "growth-task-1")
@patch("crop_simulation.views._get_async_result")
def test_status_api_returns_paginated_stages(self, mock_get_async_result):
stage_timeline = [
{
"order": 1,
"stage_code": "establishment",
"stage_name": "استقرار",
"start_date": "2026-04-01",
"end_date": "2026-04-02",
"days_count": 2,
"metrics": {"DVS": {"start": 0.1, "end": 0.2, "min": 0.1, "max": 0.2, "avg": 0.15}},
},
{
"order": 2,
"stage_code": "vegetative",
"stage_name": "رشد رویشی",
"start_date": "2026-04-03",
"end_date": "2026-04-05",
"days_count": 3,
"metrics": {"DVS": {"start": 0.3, "end": 0.8, "min": 0.3, "max": 0.8, "avg": 0.55}},
},
{
"order": 3,
"stage_code": "flowering",
"stage_name": "گلدهی",
"start_date": "2026-04-06",
"end_date": "2026-04-07",
"days_count": 2,
"metrics": {"DVS": {"start": 1.0, "end": 1.2, "min": 1.0, "max": 1.2, "avg": 1.1}},
},
]
mock_get_async_result.return_value = SimpleNamespace(
state="SUCCESS",
result={
"plant_name": self.plant.name,
"dynamic_parameters": ["DVS"],
"engine": "growth_projection",
"model_name": "growth_projection_v1",
"scenario_id": None,
"simulation_warning": None,
"summary_metrics": {},
"stage_timeline": stage_timeline,
"stages_page": stage_timeline[:1],
"pagination": paginate_growth_stages(stage_timeline, page=1, page_size=1)["pagination"],
"daily_records_count": 7,
"default_page_size": 1,
},
)
response = self.client.get("/growth/growth-task-1/status/?page=2&page_size=1")
self.assertEqual(response.status_code, 200)
payload = response.json()["data"]["result"]
self.assertEqual(payload["pagination"]["page"], 2)
self.assertEqual(len(payload["stages_page"]), 1)
self.assertEqual(payload["stages_page"][0]["stage_code"], "vegetative")
2026-04-25 17:22:41 +03:30
@patch("crop_simulation.views.apps.get_app_config")
def test_current_farm_chart_api_returns_simulation_payload(self, mock_get_app_config):
mock_simulator = SimpleNamespace(
simulate=lambda **_kwargs: {
"farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
"plant_name": self.plant.name,
"engine": "growth_projection",
"model_name": "growth_projection_v1",
"scenario_id": 12,
"simulation_warning": None,
"categories": ["2026-04-01", "2026-04-02"],
"series": [
{"name": "تعداد برگ تخمینی", "key": "leaf_count_estimate", "data": [120.0, 140.0]},
{"name": "وزن بیوماس", "key": "biomass_weight", "data": [35.0, 45.0]},
],
"summary": [
{
"title": "تعداد برگ تخمینی",
"subtitle": "وضعیت فعلی",
"amount": 140.0,
"unit": "leaf",
"avatarColor": "success",
"avatarIcon": "tabler-leaf",
}
],
"current_state": {
"date": "2026-04-02",
"leaf_count_estimate": 140.0,
"leaf_area_index": 0.0117,
"biomass_weight": 45.0,
"storage_organ_weight": 10.0,
"soil_moisture_percent": 41.2,
"development_stage": 0.35,
"gdd": 9.0,
},
"metrics": {"yield_estimate": 10.0},
"daily_output": [],
}
)
mock_get_app_config.return_value = SimpleNamespace(
get_current_farm_chart_simulator=lambda: mock_simulator
)
response = self.client.post(
"/current-farm-chart/",
data={
"farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
"plant_name": self.plant.name,
},
format="json",
)
self.assertEqual(response.status_code, 200)
payload = response.json()["data"]
self.assertEqual(payload["plant_name"], self.plant.name)
self.assertEqual(payload["scenario_id"], 12)
self.assertEqual(payload["current_state"]["leaf_count_estimate"], 140.0)
self.assertEqual(payload["series"][0]["key"], "leaf_count_estimate")
@patch("crop_simulation.views.apps.get_app_config")
def test_harvest_prediction_api_returns_payload(self, mock_get_app_config):
mock_service = SimpleNamespace(
get_harvest_prediction=lambda **_kwargs: {
"date": "2026-05-14",
"dateFormatted": "14 May 2026",
"daysUntil": 43,
"description": "شبيه ساز نشان مي دهد حدود 43 روز ديگر تا برداشت باقي مانده است.",
"optimalWindowStart": "2026-05-11",
"optimalWindowEnd": "2026-05-17",
"gddDetails": {
"current_cumulative_gdd": 50.0,
"required_gdd_for_maturity": 1200.0,
"remaining_gdd": 1150.0,
"simulation_engine": "growth_projection",
},
}
)
mock_get_app_config.return_value = SimpleNamespace(
get_harvest_prediction_service=lambda: mock_service
)
response = self.client.post(
"/harvest-prediction/",
data={
"farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
"plant_name": self.plant.name,
},
format="json",
)
self.assertEqual(response.status_code, 200)
payload = response.json()["data"]
self.assertEqual(payload["daysUntil"], 43)
self.assertEqual(payload["gddDetails"]["simulation_engine"], "growth_projection")
@patch("crop_simulation.views.apps.get_app_config")
def test_yield_prediction_api_returns_payload(self, mock_get_app_config):
mock_service = SimpleNamespace(
get_yield_prediction=lambda **_kwargs: {
"farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
"plant_name": self.plant.name,
"predictedYieldTons": 5.4,
"predictedYieldRaw": 5400.0,
"unit": "تن",
"sourceUnit": "kg/ha",
"simulationEngine": "growth_projection",
"simulationModel": "growth_projection_v1",
"scenarioId": 12,
"simulationWarning": None,
"supportingMetrics": {"yield_estimate": 5400.0},
}
)
mock_get_app_config.return_value = SimpleNamespace(
get_yield_prediction_service=lambda: mock_service
)
response = self.client.post(
"/yield-prediction/",
data={
"farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
"plant_name": self.plant.name,
},
format="json",
)
self.assertEqual(response.status_code, 200)
payload = response.json()["data"]
self.assertEqual(payload["predictedYieldTons"], 5.4)
self.assertEqual(payload["sourceUnit"], "kg/ha")