This commit is contained in:
2026-04-29 23:54:30 +03:30
parent 03afe5dc5d
commit 88f56da582
12 changed files with 1263 additions and 196 deletions
-32
View File
@@ -1006,37 +1006,6 @@
---
### 9.3) خلاصه ریسک بیماری و آفات
- مسیر: `POST /api/pest-disease/risk-summary/`
#### ورودی
- `farm_uuid``string` — الزامی
- `sensor_uuid``string` — اختیاری
#### خروجی موفق
```json
{
"code": 200,
"msg": "success",
"data": {
"farm_uuid": "string",
"diseaseRisk": {},
"pestRisk": {},
"drivers": {}
}
}
```
#### خطاها
- `400`: نبودن `farm_uuid`
- `404`: مزرعه پیدا نشد
---
## 10) Irrigation
### 10.1) لیست روش‌های آبیاری
@@ -1548,7 +1517,6 @@
- `POST /api/plants/fetch-info/`
- `POST /api/pest-disease/detect/`
- `POST /api/pest-disease/risk/`
- `POST /api/pest-disease/risk-summary/`
- `GET /api/irrigation/`
- `POST /api/irrigation/`
- `GET /api/irrigation/<pk>/`
-14
View File
@@ -359,19 +359,6 @@
- `متوسط` برای آگاهی از ریسک کلی.
- `کم` برای عملیات خودکار یا قطعی.
### `POST /api/pest-disease/risk-summary/`
- منبع داده: خلاصه سبک‌تر از همان سرویس RAG.
- نقاط قوت فعلی:
- دیگر صرفا heuristic ثابت قبلی نیست و از RAG تغذیه می‌شود.
- ضعف‌ها:
- چون summary است، جزئیات uncertainty کمتر دیده می‌شود.
- اگر RAG fallback یا retrieval ضعیف داشته باشد، summary هم ضعیف می‌شود.
- سطح اعتماد:
- `متوسط رو به کم`
---
## 10) Crop Simulation API
### `POST /api/crop-simulation/growth/`
@@ -521,7 +508,6 @@
- `POST /api/irrigation/water-stress/` در حالت fallback
- `POST /api/pest-disease/detect/` برای diagnosis قطعی
- `POST /api/pest-disease/risk/`
- `POST /api/pest-disease/risk-summary/`
- `POST /api/rag/chat/` برای پاسخ‌های حساس و اجرایی
---
+4 -11
View File
@@ -110,7 +110,6 @@ Base: `/api/pest-disease/`
|---|---|---|
| POST | `/api/pest-disease/detect/` | تشخیص آفت/بیماری از تصویر |
| POST | `/api/pest-disease/risk/` | پیش‌بینی ریسک آفات و بیماری |
| POST | `/api/pest-disease/risk-summary/` | خلاصه KPI ریسک آفات و بیماری |
### App: Irrigation
@@ -249,16 +248,11 @@ Base: `/api/crop-simulation/`
- history واقعی، drift سنسور، عدم قطعیت، zoning مزرعه یا depth-specific map در آن لحاظ نشده‌اند.
- اثر عملی: heatmap برای visualization خوب است ولی برای تصمیم agronomy دقیق کافی نیست.
### 8) Pest/Disease Risk Summary یک heuristic ساده است
- امتیاز بیماری و آفت از ترکیب چند فرمول ثابت ساخته می‌شود: `pest_disease/services.py:39`
- وزن‌ها hard-coded هستند و مدل crop-specific یا region-specific ندارند.
- اثر عملی: `POST /api/pest-disease/risk-summary/` بیشتر KPI تقریبی است تا مدل پیش‌بینی دقیق.
### 9) توصیه‌های RAG در لایه نهایی deterministic merge می‌شوند
### 8) توصیه‌های RAG در لایه نهایی deterministic merge می‌شوند
- برای irrigation/fertilization، fallback همیشه ساختار نهایی را پر می‌کند: `rag/services/irrigation.py:153`, `rag/services/fertilization.py:135`
- اثر عملی: خروجی از نظر UI پایدار است، اما تشخیص اینکه کدام بخش واقعا از LLM آمده سخت می‌شود.
### 10) Crop Simulation ممکن است از engine اصلی به projection تقریبی سقوط کند
### 9) Crop Simulation ممکن است از engine اصلی به projection تقریبی سقوط کند
- fallback projection در خطا فعال می‌شود: `crop_simulation/growth_simulation.py:404`
- اگر consumer فقط status 200 ببیند و `simulation_warning` را ignore کند، ممکن است خروجی تقریبی را واقعی فرض کند.
@@ -274,7 +268,7 @@ Base: `/api/crop-simulation/`
| `farm_data` | متوسط | هسته aggregation خوب است، ولی center/merge چند سنسور ساده‌سازی شده |
| `location_data` | متوسط | SoilGrids واقعی است، NDVI وابسته به تنظیمات بیرونی است |
| `soile` | متوسط | داده واقعی دارد، اما مدل تحلیلی و interpolation ساده است |
| `pest_disease` | متوسط | fallback زیاد و بخش risk-summary heuristic است |
| `pest_disease` | متوسط | fallback زیاد در endpointهای تشخیص و ریسک دارد |
| `farm_alerts` | متوسط | خروجی قابل استفاده است، ولی در failure به fallback داخلی می‌رود |
| `irrigation` | متوسط رو به ضعیف | recommendation خوب، اما water-stress ساده و CRUD detail معیوب |
| `fertilization` | متوسط | recommendation موجود است ولی heavily fallback-assisted |
@@ -315,8 +309,7 @@ Base: `/api/crop-simulation/`
### اولویت متوسط
1. بهبود مدل water stress
2. بهبود IDW/heatmap و استفاده از time-series
3. بهبود risk-summary آفت/بیماری با crop profile و weather window دقیق‌تر
4. اضافه‌کردن flag صریح برای crop-simulation fallback
3. اضافه‌کردن flag صریح برای crop-simulation fallback
---
-4
View File
@@ -63,7 +63,3 @@
- در اين حالت، يک notification با `level` برابر `info` توليد کن که صريح بگويد فعلا مورد مهم جديدي شناسايي نشده است.
- براي اين notification حداقلي، `title` کوتاه و خنثي باشد، `message` شفاف بگويد هشدار مهم جديدي وجود ندارد، و `suggested_action` فقط يک اقدام پايشي سبک و مشخص باشد.
- اين notification حداقلي فقط وقتي استفاده شود که خروجي در غير اين صورت خالي مي شد.
- [TEMP_FORCE_MIN_NOTIFICATION_END]
- `headline` و `overview` هميشه الزامي هستند.
- عنوان ها کوتاه و عملياتي باشند.
- `suggested_action` بايد يک اقدام مشخص مزرعه اي باشد، نه توصيه کلي.
@@ -0,0 +1,512 @@
# توضیح `location_data/apps.py` و `farm_data/apps.py`
این فایل یک توضیح کوتاه ولی کاربردی از دو فایل تنظیمات اپ Django در پروژه می‌دهد:
- `location_data/apps.py`
- `farm_data/apps.py`
همچنین برای فهم بهتر، به فیلدهای مهم مدل‌های مرتبط هم اشاره می‌کند تا معلوم شود این دو app در عمل چه داده‌هایی را مدیریت می‌کنند.
---
## 1) فایل `location_data/apps.py`
این فایل AppConfig مربوط به اپ `location_data` را تعریف می‌کند.
کلاس اصلی:
```python
class SoilDataConfig(AppConfig):
```
### فیلدها و بخش‌ها
#### `default_auto_field = "django.db.models.BigAutoField"`
- مشخص می‌کند اگر در مدل‌های این اپ برای primary key چیزی تعریف نشده باشد، Django به‌صورت پیش‌فرض از `BigAutoField` استفاده کند.
- `BigAutoField` یک شناسه عددی auto-increment بزرگ است.
- این گزینه بیشتر برای مدل‌هایی مفید است که قرار است رکوردهای زیادی داشته باشند.
#### `name = "location_data"`
- نام کامل اپ Django است.
- Django با این مقدار اپ را register می‌کند.
- این مقدار باید با مسیر ماژول اپ یکی باشد.
#### `verbose_name = "Soil Data (SoilGrids)"`
- نام نمایشی اپ در Django admin یا جاهایی است که Django نام انسانی اپ را نشان می‌دهد.
- این مقدار بیشتر جنبه نمایشی دارد و روی منطق برنامه اثر مستقیم ندارد.
---
## propertyها و سرویس‌ها در `location_data/apps.py`
این فایل فقط metadata اپ را نگه نمی‌دارد؛ دو سرویس reusable هم از طریق AppConfig در اختیار بقیه پروژه می‌گذارد.
### `@cached_property def ndvi_health_service(self)`
- این property یک نمونه از `NdviHealthService` می‌سازد.
- import آن از فایل `.ndvi` انجام می‌شود.
- به دلیل `cached_property` فقط یک بار ساخته می‌شود و بعد همان instance دوباره استفاده می‌شود.
کاربرد:
- برای تحلیل یا سرویس‌های مرتبط با NDVI
- جلوگیری از ساخت مکرر object
### `@cached_property def soil_data_adapter(self)`
این property adapter مناسب برای داده خاک را بر اساس تنظیمات پروژه انتخاب می‌کند.
دو adapter پشتیبانی می‌شوند:
- `SoilGridsAdapter`
- `MockSoilDataAdapter`
#### منطق انتخاب provider
مقدار provider از این setting خوانده می‌شود:
```python
settings.SOIL_DATA_PROVIDER
```
اگر وجود نداشته باشد، مقدار پیش‌فرض:
```python
"mock"
```
#### حالت اول: `provider == "soilgrids"`
در این حالت:
- از `SoilGridsAdapter` استفاده می‌شود
- timeout آن از این setting می‌آید:
```python
settings.SOILGRIDS_TIMEOUT_SECONDS
```
اگر این setting هم نباشد، مقدار پیش‌فرض:
```python
60
```
یعنی درخواست به provider واقعی SoilGrids حداکثر 60 ثانیه صبر می‌کند.
#### حالت دوم: `provider == "mock"`
در این حالت:
- از `MockSoilDataAdapter` استفاده می‌شود
- delay آن از این setting می‌آید:
```python
settings.SOIL_MOCK_DELAY_SECONDS
```
اگر این setting هم نباشد، مقدار پیش‌فرض:
```python
0.8
```
یعنی adapter تستی/نمایشی با تاخیر مصنوعی 0.8 ثانیه کار می‌کند.
#### حالت نامعتبر
اگر `SOIL_DATA_PROVIDER` چیزی غیر از `soilgrids` یا `mock` باشد:
- `ValueError` رخ می‌دهد
- یعنی config پروژه اشتباه است و provider شناخته نشده
---
## ارتباط `location_data/apps.py` با فیلدهای واقعی داده
این فایل خودش مدل تعریف نمی‌کند، اما به‌صورت مستقیم برای کار با مدل‌های اپ `location_data` استفاده می‌شود؛ مهم‌ترین آن‌ها این‌ها هستند:
- `location_data.models.SoilLocation`
- `location_data.models.SoilDepthData`
- `location_data.models.NdviObservation`
### فیلدهای مهم `SoilLocation`
#### `latitude`
- عرض جغرافیایی مرکز زمین
- نوع آن `DecimalField` است
- روی آن index وجود دارد
- این نقطه معمولاً مرکز هندسی مزرعه است، نه لزوماً یکی از گوشه‌های مرز
#### `longitude`
- طول جغرافیایی مرکز زمین
- مثل `latitude` برای lookup و resolve کردن داده‌های خاک استفاده می‌شود
#### `task_id`
- شناسه تسک Celery برای پردازش‌های async
- وقتی fetch داده خاک یا پردازش مرتبط در صف باشد، می‌توان با این فیلد وضعیت را track کرد
#### `farm_boundary`
- مرز مزرعه را به‌صورت JSON نگه می‌دارد
- معمولاً به‌شکل `Polygon` یا ساختار corner-based ذخیره می‌شود
- این فیلد خیلی مهم است چون فقط یک نقطه center نگه نمی‌دارید، بلکه شکل کلی زمین هم ثبت می‌شود
#### `created_at` / `updated_at`
- زمان ایجاد و آخرین به‌روزرسانی رکورد
### propertyهای مهم `SoilLocation`
#### `center_latitude`
- فقط alias برای `latitude` است
#### `center_longitude`
- فقط alias برای `longitude` است
#### `is_complete`
- بررسی می‌کند آیا هر سه لایه خاک برای این location ثبت شده‌اند یا نه
- شرط آن این است که تعداد `depths` دقیقاً 3 باشد
### فیلدهای مهم `SoilDepthData`
این مدل برای هر location سه رکورد عمق خاک نگه می‌دارد:
- `0-5cm`
- `5-15cm`
- `15-30cm`
فیلدهای اصلی:
- `soil_location`: ارتباط به `SoilLocation`
- `depth_label`: مشخص می‌کند داده برای کدام عمق است
- `bdod`: چگالی ظاهری خاک
- `cec`: ظرفیت تبادل کاتیونی
- `cfvo`: حجم قطعات درشت خاک
- `clay`: درصد رس
- `nitrogen`: مقدار نیتروژن
- `ocd` و `ocs`: شاخص‌های کربن آلی
- `phh2o`: pH خاک
- `sand`: درصد شن
- `silt`: درصد سیلت
- `soc`: کربن آلی خاک
- `wv0010`: رطوبت حجمی در فشار 10 kPa
- `wv0033`: رطوبت در حدود ظرفیت زراعی
- `wv1500`: رطوبت در نقطه پژمردگی دائم
این فیلدها برای شبیه‌سازی، آبیاری، و تخمین وضعیت واقعی خاک مهم هستند.
### فیلدهای مهم `NdviObservation`
- `location`: ارتباط با `SoilLocation`
- `observation_date`: تاریخ مشاهده
- `mean_ndvi`: میانگین NDVI
- `ndvi_map`: داده مکانی NDVI
- `vegetation_health_class`: کلاس سلامت پوشش گیاهی
- `satellite_source`: منبع تصویر مثل `sentinel-2`
- `cloud_cover`: درصد پوشش ابر
- `metadata`: داده تکمیلی
---
## نکته مهم: grid بندی زمین انجام می‌شود
بله، در لایه داده و سنجش از دور، مفهوم grid بندی وجود دارد.
اما این grid بندی در پروژه بیشتر در این دو سطح دیده می‌شود:
### 1) grid در NDVI map
در `location_data/remote_sensing.py` داده NDVI به‌صورت grid محاسبه و ذخیره می‌شود.
یعنی:
- تصویر ماهواره‌ای به خانه‌های کوچک‌تر تقسیم می‌شود
- برای هر خانه مقدار NDVI محاسبه می‌شود
- خروجی در `ndvi_map` معمولاً به‌شکل grid نگه‌داری می‌شود
این یعنی وضعیت سلامت گیاه فقط به‌صورت یک عدد کلی نیست، بلکه می‌تواند روی بخش‌های مختلف زمین map شود.
### 2) grid/cell در adapter خاک
در `location_data/soil_adapters.py` هم منطق cell/grid دیده می‌شود، مخصوصاً در adapterهای mock یا interpolation-based.
یعنی:
- مختصات lat/lon به cellهای شبکه‌ای نگاشت می‌شود
- در بعضی محاسبات از `grid_x` و `grid_y` استفاده می‌شود
- این کمک می‌کند داده خاک برای ناحیه‌های نزدیک، رفتار مکانی منطقی‌تری داشته باشد
### نتیجه مهم
خود مدل `SoilLocation` یک مرکز زمین را نگه می‌دارد، ولی مرز مزرعه و NDVI grid باعث می‌شوند سیستم فقط point-based نباشد.
یعنی:
- مرکز زمین برای lookup سریع و اتصال به داده خاک/هوا استفاده می‌شود
- مرز مزرعه برای شکل واقعی زمین ذخیره می‌شود
- grid بندی برای تحلیل مکانی، مخصوصاً در NDVI، انجام می‌شود
---
## مرکز زمین چطور از مرز مزرعه به‌دست می‌آید
در `farm_data/services.py` از روی `farm_boundary`، مرکز هندسی polygon محاسبه می‌شود.
پس flow کلی این‌طور است:
1. مرز مزرعه ارسال می‌شود
2. polygon نرمال می‌شود
3. centroid هندسی آن محاسبه می‌شود
4. یک `SoilLocation` برای center ساخته یا پیدا می‌شود
5. بعد داده خاک، NDVI و هوا به این location متصل می‌شوند
پس زمین فقط با یک نقطه خام ثبت نمی‌شود؛ اول مرز دارد، بعد center از روی آن به‌دست می‌آید.
---
## متدهای کمکی `location_data/apps.py`
### `get_ndvi_health_service()`
- خروجی `self.ndvi_health_service` را برمی‌گرداند
- یک accessor ساده برای گرفتن سرویس NDVI است
### `get_soil_data_adapter()`
- خروجی `self.soil_data_adapter` را برمی‌گرداند
- بقیه بخش‌های پروژه از این متد برای گرفتن adapter فعال استفاده می‌کنند
/
---
## فیلدها و settingهای مهم مرتبط با `location_data/apps.py`
### فیلدهای AppConfig
- `default_auto_field`: نوع primary key پیش‌فرض مدل‌ها
- `name`: نام داخلی اپ
- `verbose_name`: نام نمایشی اپ
### settingهای استفاده‌شده
- `SOIL_DATA_PROVIDER`: انتخاب provider فعال خاک
- `SOILGRIDS_TIMEOUT_SECONDS`: timeout برای provider واقعی SoilGrids
- `SOIL_MOCK_DELAY_SECONDS`: تاخیر مصنوعی برای provider mock
---
## 2) فایل `farm_data/apps.py`
این فایل AppConfig مربوط به اپ `farm_data` را تعریف می‌کند.
کلاس اصلی:
```python
class FarmDataConfig(AppConfig):
```
### فیلدها
#### `default_auto_field = "django.db.models.BigAutoField"`
- مثل اپ قبلی، تعیین می‌کند primary key پیش‌فرض مدل‌های این اپ از نوع `BigAutoField` باشد.
#### `name = "farm_data"`
- نام داخلی و ماژول اپ Django است.
- برای شناسایی اپ در `INSTALLED_APPS` و registry داخلی Django استفاده می‌شود.
#### `label = "sensor_data"`
- label داخلی اپ در registry Django است.
- این فیلد زمانی مهم می‌شود که:
- بخواهید نام registry اپ با `name` فرق داشته باشد
- یا از تداخل نام اپ‌ها جلوگیری کنید
- در این پروژه، اپ `farm_data` با label داخلی `sensor_data` شناخته می‌شود.
نکته:
- `label` باید در کل پروژه یکتا باشد.
- این مقدار ممکن است در migrationها، relationها یا lookupهای app registry اثر داشته باشد.
#### `verbose_name = "farm-data"`
- نام نمایشی اپ است.
- بیشتر برای admin و نمایش انسانی استفاده می‌شود.
---
## نکته مهم درباره `farm_data/apps.py`
برخلاف `location_data/apps.py`، این فایل:
- `cached_property` ندارد
- service locator ندارد
- adapter یا provider انتخاب نمی‌کند
یعنی فعلاً فقط نقش config پایه اپ را دارد.
---
## ارتباط `farm_data/apps.py` با فیلدهای واقعی داده
این app بیشتر داده‌های farm-level و sensor-level را نگه می‌دارد. مهم‌ترین مدل‌هایش:
- `farm_data.models.SensorData`
- `farm_data.models.SensorParameter`
- `farm_data.models.ParameterUpdateLog`
### فیلدهای مهم `SensorData`
#### `farm_uuid`
- شناسه یکتای مزرعه
- primary key این مدل است
- هر رکورد `SensorData` نماینده یک مزرعه است
#### `center_location`
- ارتباط به `location_data.SoilLocation`
- یعنی این مزرعه به یک location مرکزی وصل است
- از همین نقطه مرکزی برای weather/soil/simulation استفاده می‌شود
#### `weather_forecast`
- ارتباط اختیاری با `weather.WeatherForecast`
- اگر موجود باشد، forecast منتخب یا آخرین forecast به مزرعه وصل می‌شود
#### `sensor_payload`
- مهم‌ترین فیلد این مدل است
- داده سنسورها به‌صورت JSON نگه‌داری می‌شود
- ساختار معمول آن شبیه این است:
```json
{
"sensor-7-1": {
"soil_moisture": 25.5,
"soil_temperature": 22.3,
"soil_ph": 7.2
}
}
```
مزیت این ساختار:
- چند سنسور در یک مزرعه پشتیبانی می‌شود
- هر سنسور می‌تواند فیلدهای خاص خودش را داشته باشد
- schema سنسورها rigid نیست
#### `plants`
- رابطه چندبه‌چند با `plant.Plant`
- یعنی یک farm می‌تواند چند گیاه مرتبط داشته باشد
#### `irrigation_method`
- روش آبیاری انتخاب‌شده برای مزرعه
- برای recommendation و planning مهم است
#### `created_at` / `updated_at`
- زمان ایجاد و آخرین ویرایش رکورد
### propertyهای مهم `SensorPayloadMixin`
مدل `SensorData` از `SensorPayloadMixin` ارث می‌گیرد و این helperها را دارد:
#### `_payload()`
- payload را فقط وقتی dict معتبر باشد برمی‌گرداند
#### `get_sensor_block(sensor_key=None)`
- اگر `sensor_key` بدهید، همان بلوک سنسور را برمی‌گرداند
- اگر ندهید، اولین بلوک معتبر را برمی‌گرداند
#### `get_metric(metric_name, sensor_key=None)`
- یک metric خاص را از payload پیدا می‌کند
- اول در sensor مشخص‌شده می‌گردد
- اگر پیدا نشد، در بقیه blockها جستجو می‌کند
#### propertyهای آماده
این propertyها shortcut هستند:
- `soil_moisture`
- `soil_temperature`
- `soil_ph`
- `electrical_conductivity`
- `nitrogen`
- `phosphorus`
- `potassium`
یعنی به‌جای parse دستی JSON، مستقیم می‌توان این متریک‌ها را خواند.
### فیلدهای مهم `SensorParameter`
این مدل dictionary پارامترهای سنسور را نگه می‌دارد.
#### `sensor_key`
- کلید سنسور مثل `sensor-7-1`
#### `code`
- کد پارامتر مثل `soil_moisture`
#### `name_fa`
- نام فارسی پارامتر
#### `unit`
- واحد پارامتر مثل `%` یا `dS/m`
#### `data_type`
- نوع داده مثل `float`, `int`, `string`, `bool`
#### `metadata`
- داده تکمیلی برای UI یا validation
- مثلاً:
- بازه مجاز
- توضیح
- تنظیمات نمایش
### فیلدهای مهم `ParameterUpdateLog`
- `parameter`: ارتباط به `SensorParameter`
- `action`: نوع عملیات مثل `added` یا `modified`
- `payload`: خلاصه تغییرات
- `updated_at`: زمان ثبت لاگ
این مدل برای audit و پیگیری تغییرات پارامترها مفید است.
---
## جمع‌بندی
### `location_data/apps.py`
- هم metadata اپ را نگه می‌دارد
- هم سرویس و adapter در اختیار پروژه می‌گذارد
- هم از settingها برای انتخاب provider واقعی یا mock استفاده می‌کند
- و در عمل با location center، مرز مزرعه، داده لایه‌های خاک و gridهای NDVI کار می‌کند
### `farm_data/apps.py`
- فقط config پایه AppConfig را تعریف می‌کند
- نقش آن بیشتر register کردن اپ با نام و label مشخص است
- اما داده‌های اصلی مزرعه مثل `farm_uuid`، `sensor_payload`، گیاه، روش آبیاری و اتصال به center location در مدل‌های همین app نگه‌داری می‌شوند
+746
View File
@@ -0,0 +1,746 @@
# راهنمای پیاده‌سازی API صفحه Yield & Harvest با PCSE و RAG
این فایل برای تیم بک‌اند نوشته شده تا صفحه `yield-harvest` را با تکیه بر:
- مستند `psce_doc.txt` پروژه
- سرویس‌های موجود در `crop_simulation/`
- معماری فعلی RAG در `rag/`
به شکل قابل اتکا پیاده‌سازی کند.
نکته: فایل `psce_doc.txt` در عمل مستند PCSE است و در این سند هم با عنوان PCSE به آن اشاره می‌شود.
---
## جمع‌بندی سریع
برای این صفحه، بهترین معماری این است:
1. عددها، تاریخ‌ها، درصدها و سری‌های نمودار از PCSE و داده‌های مزرعه ساخته شوند.
2. RAG فقط برای متن‌های توضیحی، خلاصه‌سازی، و wording کاربرپسند استفاده شود.
3. RAG اجازه نداشته باشد عدد جدید، تاریخ جدید، یا KPI جدید اختراع کند.
4. یک endpoint خلاصه برای فرانت برگردانده شود:
`GET /api/yield-harvest/summary/?farm_uuid=<uuid>`
اگر لازم شد از نظر convention داخلی پروژه همه‌چیز داخل `crop_simulation` بماند، می‌توانید معادل زیر را هم نگه دارید:
`GET /api/crop-simulation/yield-harvest-summary/?farm_uuid=<uuid>`
برای فرانت فعلی، مدل summary بهتر از چند endpoint پراکنده است.
---
## چیزی که همین الان در پروژه دارید
### لایه PCSE / شبیه‌سازی
الان پروژه این اجزا را دارد:
- `crop_simulation/services.py`
- `crop_simulation/growth_simulation.py`
- `crop_simulation/harvest_prediction.py`
- `crop_simulation/yield_prediction.py`
و همین حالا از PCSE این خروجی‌ها را می‌سازید:
- `yield` از `TWSO`
- `biomass` از `TAGP`
- `leaf_area_index` از `LAI`
- `development_stage` از `DVS`
- `soil_moisture` از `SM`
این دقیقاً با چیزی که در `psce_doc.txt` آمده هم‌راستا است: PCSE برای state/rate variables و خروجی روزانه مناسب است و برای ساخت KPI، trend و harvest timing گزینه خوبی است.
### لایه RAG
الان پروژه الگوی خوبی برای سرویس‌های ترکیبی deterministic + RAG دارد:
- `rag/services/irrigation.py`
- `rag/services/fertilization.py`
- `rag/chat.py`
- `config/rag_config.yaml`
الگوی درست در این پروژه این است:
- اول عددها و تصمیم‌های پایه از منطق deterministic ساخته می‌شوند
- بعد RAG فقط متن نهایی را polish می‌کند
- در صورت خراب شدن خروجی LLM، fallback ساختاریافته دارید
برای `yield-harvest` هم باید دقیقاً همین الگو تکرار شود.
---
## اصل طراحی مهم
### چه چیزی باید deterministic باشد
این فیلدها نباید از RAG بیایند:
- `daysUntil`
- `readiness`
- `share`
- `series[].data`
- `averageReadiness`
- `predicted yield`
- `harvest date`
- هر عددی که در progress bar، chart، KPI یا sorting استفاده می‌شود
### چه چیزی می‌تواند از RAG بیاید
این فیلدها مناسب RAG هستند:
- `season_highlights_card.subtitle`
- `season_highlights_card.spotlight.caption`
- `harvest_prediction_card.description`
- `harvest_operations_card.summary`
- `steps[].note`
- badgeها و labelهای توصیفی کوتاه
### قانون طلایی
RAG باید فقط روی context قطعی زیر کار کند:
- farm details
- خروجی‌های PCSE
- داده‌های آب‌وهوا
- داده‌های سنسور
- در صورت وجود: داده کیفیت، آفات، اقتصاد، بازار
و prompt آن باید صریح بگوید:
- عدد جدید نساز
- فقط از مقادیر داده‌شده استفاده کن
- اگر داده کافی نیست، کمبود را بگو
- خروجی را در JSON معتبر برگردان
---
## پیشنهاد معماری برای این صفحه
بهترین راه این است که یک orchestrator service جدید بسازید:
- `crop_simulation/yield_harvest_summary.py`
و داخل آن چند builder کوچک داشته باشید:
- `_build_yield_prediction_block()`
- `_build_harvest_prediction_block()`
- `_build_yield_prediction_chart_block()`
- `_build_harvest_readiness_zones_block()`
- `_build_yield_quality_bands_block()`
- `_build_harvest_operations_block()`
- `_build_season_highlights_block()`
و یک facade نهایی:
- `YieldHarvestSummaryService.get_summary(farm_uuid, season_year=None, crop_name=None)`
این service باید:
1. داده مزرعه را از `farm_data.services.get_farm_details()` بگیرد.
2. خروجی شبیه‌سازی را از `CurrentFarmChartSimulator` و سرویس‌های فعلی بگیرد.
3. بلوک‌های deterministic را بسازد.
4. اگر RAG فعال بود، متن‌های narrative را enrich کند.
5. یک payload نهایی برای فرانت برگرداند.
---
## Mapping پیشنهادی هر کارت به منبع داده
| بلوک | منبع اصلی | وضعیت امکان پیاده‌سازی |
|---|---|---|
| `yield_prediction` | `crop_simulation/yield_prediction.py` + chart metrics | قابل پیاده‌سازی همین الآن |
| `harvest_prediction_card` | `crop_simulation/harvest_prediction.py` | قابل پیاده‌سازی همین الآن |
| `yield_prediction_chart` | `CurrentFarmChartSimulator` + historical baseline | نیمه‌آماده، نیاز به تصمیم درباره سری مقایسه‌ای |
| `season_highlights_card` | aggregate از بقیه بلوک‌ها + RAG | قابل پیاده‌سازی اگر بقیه بلوک‌ها حاضر باشند |
| `harvest_readiness_zones` | داده قطعه/زون + PCSE per zone | فعلاً gap داده‌ای دارد |
| `yield_quality_bands` | quality model / grading rules / lab data | فعلاً gap داده‌ای دارد |
| `harvest_operations_card` | rules + optimizer + RAG | قابل پیاده‌سازی به‌صورت اولیه |
---
## نکته مهم: همه بلوک‌ها را PCSE به‌تنهایی تولید نمی‌کند
PCSE برای این بخش‌ها خیلی مناسب است:
- زمان برداشت
- روند رشد
- عملکرد پیش‌بینی‌شده
- stage / maturity / readiness proxy
- trend chart
اما PCSE به‌صورت پیش‌فرض برای این‌ها کافی نیست:
- `بریکس`
- `گرید ممتاز / درجه یک / فرآوری`
- `پتانسیل صادرات`
- `قیمت فروش`
- `premium`
- readiness per block وقتی مدل block-level ندارید
پس برای صفحه کامل، باید سه لایه را از هم جدا ببینید:
1. `PCSE layer`: عددهای زیستی و رشد
2. `Rules/Aggregation layer`: تبدیل خروجی PCSE به KPIهای محصولی
3. `RAG layer`: توضیح، جمع‌بندی، و متن قابل نمایش
---
## طراحی endpoint پیشنهادی
### Route
پیشنهاد اصلی:
- `GET /api/yield-harvest/summary/?farm_uuid=<uuid>&season_year=1404&crop_name=wheat`
### Query params
- `farm_uuid`: اجباری
- `season_year`: اختیاری
- `crop_name`: اختیاری
- `include_narrative`: اختیاری، پیش‌فرض `true`
### چرا `include_narrative` خوب است
چون اگر RAG کند یا موقتاً غیرفعال باشد، باز هم فرانت می‌تواند با داده deterministic رندر شود.
---
## ساختار پیشنهادی response
همان envelope فعلی پروژه مناسب است:
```json
{
"code": 200,
"msg": "success",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"season_highlights_card": {},
"yield_prediction": {},
"harvest_prediction_card": {},
"harvest_readiness_zones": {},
"yield_quality_bands": {},
"harvest_operations_card": {},
"yield_prediction_chart": {}
}
}
```
---
## پیشنهاد فنی برای ساخت هر بلوک
## 1) `yield_prediction`
### منبع
- `YieldPredictionService`
- یا مستقیم `CurrentFarmChartSimulator.simulate()`
### منطق پیشنهادی
- `predicted yield` از `TWSO`
- `harvest readiness` از `DVS` و فاصله تا maturity
- `quality score` فعلاً rule-based، نه RAG-based
- `loss risk` از ترکیب moisture/weather/pest signals اگر دارید، وگرنه با fallback ساده
### توصیه
اگر الآن فقط داده قطعی می‌خواهید:
- KPI اول را دقیق بسازید
- KPIهای دوم تا چهارم را با rules ساده و شفاف بسازید
- آن‌ها را به عنوان `estimated` در کد داخلی mark کنید
---
## 2) `harvest_prediction_card`
### منبع
- `crop_simulation/harvest_prediction.py`
### وضعیت
این بلوک تقریباً آماده است.
### توصیه
- `dateFormatted` را از serializer-ready formatter بگیرید
- `daysUntil` حتماً `number` بماند
- متن `description` اگر خواستید با RAG بهتر شود، version deterministic را هم نگه دارید
یعنی بهتر است این دو فیلد را داشته باشید:
- `description`
- `descriptionSource = "deterministic" | "rag"`
این source flag برای debugging خیلی کمک می‌کند.
---
## 3) `yield_prediction_chart`
### واقعیت فعلی
خروجی chart فعلی شما در `CurrentFarmChartSimulator` بیشتر این‌ها را می‌دهد:
- leaf count estimate
- biomass
- storage organ weight
- lai
- soil moisture
اما payload فرانت `yield-harvest` در سند شما نمودار `سال قبل / سال جاری` می‌خواهد.
### نتیجه
این بخش هنوز one-to-one با کد فعلی آماده نیست.
### راه درست
یکی از این دو مسیر را انتخاب کنید:
#### مسیر A - سریع‌تر
همان chart فعلی را به contract فرانت نزدیک کنید و فعلاً به‌جای `سال قبل/سال جاری` از:
- `عملکرد تجمعی پیش‌بینی‌شده`
- `بیوماس`
استفاده کنید.
#### مسیر B - محصولی‌تر
دو سری واقعی تولید کنید:
- `سال جاری`: از simulation جاری
- `سال قبل`: از historical scenario یا historical harvest data
اگر داده سال قبل ندارید، این سری را fake نکنید.
### توصیه نهایی
برای production بهتر است تا وقتی historical source ندارید، label سری‌ها را صادقانه تغییر دهید.
---
## 4) `harvest_readiness_zones`
### بزرگ‌ترین gap فعلی
مدل داده فعلی شما عمدتاً farm-level است:
- `SensorData`
- `center_location`
- `farm_details`
اما این کارت block-level data می‌خواهد:
- `A1`
- `A2`
- cultivar per block
- readiness per block
### نتیجه
بدون model یا source جدید برای block/zone، این کارت را نباید fake کنید.
### راه درست
یا باید یکی از این‌ها را اضافه کنید:
1. مدل `FarmBlock` یا `FarmZone`
2. چند sensor/polygon برای هر مزرعه
3. block metadata در JSON مزرعه
### پیاده‌سازی پیشنهادی
برای هر block:
1. weather/location را resolve کنید
2. crop parameters block-specific بسازید
3. یک simulation جدا اجرا کنید
4. `DVS`, `TWSO`, `SM`, `maturity date` را بگیرید
5. readiness را از rule زیر بسازید
مثال rule:
```text
readiness = clamp((current_dvs / 2.0) * 100, 0, 100)
```
یا:
```text
readiness = clamp(100 - days_until_harvest * 4, 0, 100)
```
بهتر است این rule داخل code comment شفاف توضیح داده شود.
---
## 5) `yield_quality_bands`
### نکته مهم
PCSE به‌صورت پیش‌فرض grade یا brix تولید نمی‌کند.
پس این کارت را باید از یکی از این منابع ساخت:
1. داده آزمایشگاهی
2. rule engine بر اساس moisture + disease risk + maturity + crop profile
3. مدل quality جدا
### چیزی که نباید انجام شود
این‌که RAG خودش بگوید:
- 46% گرید ممتاز
- 34% گرید یک
بدون منطق deterministic
این کار برای dashboard اشتباه است.
### پیشنهاد عملی
فاز اول:
- اگر quality data ندارید، این کارت را با flag `unavailable` برگردانید
- یا bands را از rule-based scoring بسازید و در backend صریح mark کنید که estimated هستند
مثلاً:
- `quality_score >= 85 -> premium`
- `70..84 -> grade_1`
- `< 70 -> processing`
اما این rule باید crop-specific باشد، نه universal.
---
## 6) `harvest_operations_card`
### بهترین جا برای ترکیب deterministic + RAG
این کارت candidate خیلی خوبی برای RAG است، چون:
- order و timing می‌تواند از rules و simulation بیاید
- متن summary و noteها می‌تواند از RAG بیاید
### طراحی پیشنهادی
ابتدا backend یک payload قطعی بسازد:
```json
{
"recommended_shift_count": 2,
"sorting_capacity_ton_per_day": 15,
"required_workers": 12,
"max_transfer_hours": 6,
"priority_blocks": ["A1", "A2"],
"reasoning": [
"A1 readiness بالاتر دارد",
"moisture در محدوده مناسب است"
]
}
```
بعد این payload را به RAG بدهید تا فقط این‌ها را تولید کند:
- `summary`
- `steps[].note`
- `steps[].status`
و حتی‌المقدور:
- `outputs[]` را deterministic نگه دارید
---
## 7) `season_highlights_card`
### بهترین روش
این کارت را از بقیه بلوک‌ها derive کنید، نه از یک منبع مستقل.
یعنی:
- `seasonLabel` از ورودی season
- `badges` از readiness/quality/risk
- `spotlight.value` از harvest window یا best sale window
- `metrics` از yield, grade, revenue estimate
### نکته
اگر هنوز economy data ندارید، فیلدهایی مثل:
- `درآمد هدف`
- `پنجره طلایی فروش`
نباید با RAG اختراع شوند.
یا باید:
- حذف شوند
- یا با `null`
- یا با fallback business rule
برگردند.
---
## پیشنهاد برای RAG service جدید
مثل `irrigation` و `fertilization` یک سرویس جدید اضافه کنید:
- `rag/services/yield_harvest.py`
و در `config/rag_config.yaml` هم service جدید تعریف کنید:
- `yield_harvest`
### نقش این سرویس
این سرویس نباید خودش KPI بسازد.
فقط باید:
- summary بنویسد
- subtitle بسازد
- operation notes تولید کند
- badge text را human-friendly کند
### ورودی پیشنهادی به RAG
```json
{
"farm_uuid": "...",
"plant_name": "...",
"deterministic_metrics": {
"predicted_yield_tons": 42.8,
"days_until_harvest": 18,
"readiness_avg": 84,
"quality_score": 91,
"loss_risk_percent": 6.5
},
"operations": {
"shift_count": 2,
"required_workers": 12,
"max_transfer_hours": 6
}
}
```
### خروجی پیشنهادی از RAG
فقط این بخش‌ها:
```json
{
"season_highlights_card": {
"subtitle": "...",
"spotlight": {
"caption": "..."
},
"badges": ["...", "..."]
},
"harvest_prediction_card": {
"description": "..."
},
"harvest_operations_card": {
"summary": "...",
"steps": [
{
"title": "...",
"note": "...",
"status": "..."
}
]
}
}
```
### Rule مهم
اگر JSON RAG خراب بود:
- fallback deterministic برگردان
- endpoint fail نکند
---
## فایل‌هایی که پیشنهاد می‌شود تغییر کنند
### در `crop_simulation/`
- `crop_simulation/apps.py`
- اضافه کردن getter برای summary service
- `crop_simulation/serializers.py`
- serializer درخواست و پاسخ summary
- `crop_simulation/views.py`
- view جدید برای summary
- `crop_simulation/urls.py`
- route جدید
- `crop_simulation/yield_harvest_summary.py`
- orchestrator اصلی
### در `Schemas/`
- `Schemas/crop_simulation_yield_harvest_summary.py`
- contract برای مستندسازی route
- `Schemas/__init__.py`
- register کردن contract جدید
### در `rag/`
- `rag/services/yield_harvest.py`
- narrative enrichment
### در config
- `config/rag_config.yaml`
- service جدید `yield_harvest`
- `config/tones/yield_harvest_tone.txt`
- tone مخصوص summary صفحه
- در صورت نیاز:
- `config/knowledge_base/yield_harvest/`
---
## ترتیب پیشنهادی پیاده‌سازی
### فاز 1 - سریع و قابل اتکا
فقط این بلوک‌ها را واقعی کنید:
- `yield_prediction`
- `harvest_prediction_card`
- `yield_prediction_chart`
- `harvest_operations_card` با rules ساده
و برای بقیه اگر داده ندارید:
- `null`
- یا `available: false`
برگردانید.
### فاز 2 - RAG narrative
به summary و operation notes متن کاربرپسند اضافه کنید.
### فاز 3 - block-level readiness
بعد از اضافه شدن مدل zone/block، `harvest_readiness_zones` را واقعی کنید.
### فاز 4 - quality/economy
بعد از اضافه شدن منبع quality یا market:
- `yield_quality_bands`
- revenue
- sale window
را کامل کنید.
---
## توصیه مهم درباره contract
اگر بعضی بلوک‌ها هنوز data source واقعی ندارند، بهتر است از همین حالا response را این‌طور طراحی کنید:
```json
{
"harvest_readiness_zones": {
"available": false,
"reason": "block_level_data_missing",
"averageReadiness": null,
"blocks": []
}
}
```
این بهتر از mock پنهان است، چون:
- فرانت می‌فهمد چرا کارت کامل نیست
- QA راحت‌تر تست می‌کند
- بعداً migration ساده‌تر می‌شود
---
## ریسک‌های اصلی
### 1. استفاده از RAG برای عددسازی
بزرگ‌ترین خطر این صفحه همین است. RAG فقط برای narrative مناسب است.
### 2. نبود داده block-level
`harvest_readiness_zones` بدون مدل block عملاً دقیق نمی‌شود.
### 3. نبود quality source
`yield_quality_bands` بدون مدل کیفیت یا rule engine crop-specific قابل اتکا نیست.
### 4. ناهماهنگی chart contract
chart فعلی backend دقیقاً همان chart مورد انتظار فرانت نیست.
---
## پیشنهاد نهایی من
اگر بخواهم برای همین repo کم‌ریسک‌ترین مسیر را انتخاب کنم:
1. یک summary service در `crop_simulation` بسازید.
2. `yield_prediction` و `harvest_prediction_card` را از سرویس‌های فعلی reuse کنید.
3. `yield_prediction_chart` را فعلاً با داده واقعی current simulation برگردانید، نه مقایسه fake سال قبل.
4. `harvest_operations_card` را با rules deterministic + optional RAG summary بسازید.
5. `season_highlights_card` را از همین بلوک‌ها aggregate کنید.
6. `harvest_readiness_zones` و `yield_quality_bands` را فقط وقتی source واقعی دارید production کنید.
این مسیر با معماری فعلی پروژه، با `psce_doc.txt`، و با pattern موجود RAG بیشترین سازگاری را دارد.
---
## یک pseudo flow پیشنهادی
```text
GET /api/yield-harvest/summary/?farm_uuid=...
->
load farm details
->
run/reuse PCSE simulation outputs
->
build deterministic blocks
->
optionally call RAG for narrative fields only
->
merge response
->
return code/msg/data
```
---
## نتیجه
برای این صفحه:
- PCSE باید موتور عددها باشد
- rules باید موتور KPIهای محصولی باشد
- RAG باید موتور متن و explanation باشد
اگر این مرزبندی رعایت شود، هم dashboard قابل اعتماد می‌شود، هم توسعه بعدی آن تمیز و قابل نگهداری می‌ماند.
@@ -278,16 +278,6 @@ class ReportingAndAiJourneyTests(IntegrationAPITestCase):
},
)
)
stack.enter_context(
patch(
"pest_disease.services.build_pest_disease_risk_summary",
return_value={
"riskLevel": "medium",
"headline": "Watch humidity trend",
"items": [{"title": "Powdery mildew", "severity": "medium"}],
},
)
)
chat_response = self.client.post(
"/api/rag/chat/",
@@ -357,14 +347,6 @@ class ReportingAndAiJourneyTests(IntegrationAPITestCase):
)
self.assertEqual(pest_risk_response.status_code, 200)
pest_summary_response = self.client.post(
"/api/pest-disease/risk-summary/",
data={"farm_uuid": str(self.farm_uuid)},
format="json",
)
self.assertEqual(pest_summary_response.status_code, 200)
self.assertEqual(pest_summary_response.json()["data"]["riskLevel"], "medium")
def test_alert_and_crop_simulation_endpoints_persist_records(self) -> None:
def tracker_stub(*, farm_uuid: str, query: str | None = None):
from farm_alerts.services import _save_notifications, _serialize_notification
-10
View File
@@ -1,17 +1,7 @@
from django.apps import AppConfig
from functools import cached_property
class PestDiseaseConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "pest_disease"
verbose_name = "Pest & Disease"
@cached_property
def risk_summary_service(self):
from .services import build_pest_disease_risk_summary
return build_pest_disease_risk_summary
def get_risk_summary_service(self):
return self.risk_summary_service
-19
View File
@@ -29,22 +29,3 @@ class PestDiseaseRiskRequestSerializer(serializers.Serializer):
raise serializers.ValidationError({"farm_uuid": "farm_uuid الزامی است."})
attrs["farm_uuid"] = farm_uuid
return attrs
class PestDiseaseRiskSummaryRequestSerializer(serializers.Serializer):
farm_uuid = serializers.CharField(required=False, help_text="شناسه یکتای مزرعه")
sensor_uuid = serializers.CharField(required=False, help_text="نام قدیمی برای farm_uuid")
def validate(self, attrs):
farm_uuid = attrs.get("farm_uuid") or attrs.get("sensor_uuid")
if not farm_uuid:
raise serializers.ValidationError({"farm_uuid": "farm_uuid الزامی است."})
attrs["farm_uuid"] = farm_uuid
return attrs
class PestDiseaseRiskSummaryResponseSerializer(serializers.Serializer):
farm_uuid = serializers.CharField()
diseaseRisk = serializers.JSONField()
pestRisk = serializers.JSONField()
drivers = serializers.JSONField()
-37
View File
@@ -1,37 +0,0 @@
from __future__ import annotations
from typing import Any
from rag.services import get_pest_disease_risk
def _stats_label(level: str | None) -> str:
if level == "high":
return "بالا"
if level == "medium":
return "متوسط"
return "پایین"
def _normalize_risk_block(block: dict[str, Any] | None) -> dict[str, Any]:
payload = dict(block or {})
payload.setdefault("score", 0.0)
payload.setdefault("level", "low")
payload["statsLabel"] = _stats_label(payload.get("level"))
return payload
def build_pest_disease_risk_summary(*, farm_uuid: str) -> dict[str, Any]:
rag_result = get_pest_disease_risk(farm_uuid=farm_uuid)
return {
"farm_uuid": farm_uuid,
"diseaseRisk": _normalize_risk_block(rag_result.get("disease_risk")),
"pestRisk": _normalize_risk_block(rag_result.get("pest_risk")),
"drivers": {
"keyDrivers": rag_result.get("key_drivers") or [],
"summary": rag_result.get("summary"),
"forecastWindow": rag_result.get("forecast_window"),
"source": "rag",
},
}
+1 -2
View File
@@ -1,10 +1,9 @@
from django.urls import path
from .views import PestDiseaseDetectionView, PestDiseaseRiskSummaryView, PestDiseaseRiskView
from .views import PestDiseaseDetectionView, PestDiseaseRiskView
urlpatterns = [
path("detect/", PestDiseaseDetectionView.as_view(), name="pest-disease-detect"),
path("risk/", PestDiseaseRiskView.as_view(), name="pest-disease-risk"),
path("risk-summary/", PestDiseaseRiskSummaryView.as_view(), name="pest-disease-risk-summary"),
]
-49
View File
@@ -13,8 +13,6 @@ from rag.services import get_pest_disease_detection, get_pest_disease_risk
from .serializers import (
PestDiseaseDetectionRequestSerializer,
PestDiseaseRiskRequestSerializer,
PestDiseaseRiskSummaryRequestSerializer,
PestDiseaseRiskSummaryResponseSerializer,
)
@@ -31,12 +29,6 @@ PestDiseaseRiskResponseSerializer = build_envelope_serializer(
"PestDiseaseRiskResponseSerializer",
data_schema=None,
)
PestDiseaseRiskSummaryEnvelopeSerializer = build_envelope_serializer(
"PestDiseaseRiskSummaryEnvelopeSerializer",
PestDiseaseRiskSummaryResponseSerializer,
)
class _ImageMixin:
parser_classes = [JSONParser, MultiPartParser, FormParser]
@@ -160,44 +152,3 @@ class PestDiseaseRiskView(APIView):
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
return Response({"code": 200, "msg": "success", "data": result}, status=status.HTTP_200_OK)
class PestDiseaseRiskSummaryView(APIView):
@extend_schema(
tags=["Pest & Disease"],
summary="خلاصه ریسک بیماری و آفات",
description=(
"با دریافت farm_uuid، خلاصه ریسک بیماری و آفات را از سرویس RAG "
"گرفته و در قالب سبک تر برای KPI بازمی گرداند."
),
request=PestDiseaseRiskSummaryRequestSerializer,
responses={
200: build_response(PestDiseaseRiskSummaryEnvelopeSerializer, "خلاصه ریسک بیماری و آفات."),
400: build_response(PestDiseaseValidationErrorSerializer, "پارامتر ورودی نامعتبر است."),
404: build_response(PestDiseaseValidationErrorSerializer, "مزرعه یافت نشد."),
},
examples=[
OpenApiExample(
"نمونه درخواست risk summary",
value={"farm_uuid": "11111111-1111-1111-1111-111111111111"},
request_only=True,
)
],
)
def post(self, request):
from .services import build_pest_disease_risk_summary
serializer = PestDiseaseRiskSummaryRequestSerializer(data=request.data)
if not serializer.is_valid():
return Response(
{"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors},
status=status.HTTP_400_BAD_REQUEST,
)
try:
result = build_pest_disease_risk_summary(farm_uuid=serializer.validated_data["farm_uuid"])
except ValueError as exc:
return Response(
{"code": 404, "msg": str(exc), "data": None},
status=status.HTTP_404_NOT_FOUND,
)
return Response({"code": 200, "msg": "success", "data": result}, status=status.HTTP_200_OK)